Compare commits
No commits in common. "92f7874584ddf681fd72d6c5effc417cc8b1f90f" and "dc49006af845b142c9fde2ec450f48798a3bca5a" have entirely different histories.
92f7874584
...
dc49006af8
11 changed files with 361 additions and 429 deletions
|
|
@ -13,8 +13,9 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
use crate::diags;
|
||||||
use core::{
|
use core::{
|
||||||
action::Action,
|
action::{Action, DiagResult},
|
||||||
components::{
|
components::{
|
||||||
Component,
|
Component,
|
||||||
footer::Footer,
|
footer::Footer,
|
||||||
|
|
@ -38,6 +39,8 @@ use core::{
|
||||||
tui::{Event, Tui},
|
tui::{Event, Tui},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
array,
|
||||||
|
collections::HashMap,
|
||||||
env,
|
env,
|
||||||
iter::zip,
|
iter::zip,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
|
@ -54,14 +57,13 @@ use ratatui::{
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::diags::{DiagGroup, Type as DiagType, get_diag_type, parse_chkdsk};
|
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
// TUI
|
// TUI
|
||||||
action_rx: mpsc::UnboundedReceiver<Action>,
|
action_rx: mpsc::UnboundedReceiver<Action>,
|
||||||
action_tx: mpsc::UnboundedSender<Action>,
|
action_tx: mpsc::UnboundedSender<Action>,
|
||||||
components: Vec<Box<dyn Component>>,
|
components: Vec<Box<dyn Component>>,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
diag_groups: diags::Groups,
|
||||||
frame_rate: f64,
|
frame_rate: f64,
|
||||||
last_tick_key_events: Vec<KeyEvent>,
|
last_tick_key_events: Vec<KeyEvent>,
|
||||||
should_quit: bool,
|
should_quit: bool,
|
||||||
|
|
@ -70,19 +72,28 @@ pub struct App {
|
||||||
// App
|
// App
|
||||||
clone: CloneSettings,
|
clone: CloneSettings,
|
||||||
cur_mode: Mode,
|
cur_mode: Mode,
|
||||||
diag_groups: Arc<Mutex<Vec<DiagGroup>>>,
|
|
||||||
list: StatefulList<Mode>,
|
list: StatefulList<Mode>,
|
||||||
boot_modes: Vec<SafeMode>,
|
boot_modes: Vec<SafeMode>,
|
||||||
selections: Vec<Option<usize>>,
|
selections: Vec<Option<usize>>,
|
||||||
system32: String,
|
system32: String,
|
||||||
|
results: Arc<Mutex<HashMap<String, String>>>,
|
||||||
tasks: Tasks,
|
tasks: Tasks,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
|
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
|
||||||
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
||||||
let diag_groups_arc = Arc::new(Mutex::new(Vec::new()));
|
|
||||||
let disk_list_arc = Arc::new(Mutex::new(Vec::new()));
|
let disk_list_arc = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let mut results_fake = HashMap::new();
|
||||||
|
results_fake.insert(String::from("1. One"), String::from("Line one,\nline two"));
|
||||||
|
results_fake.insert(
|
||||||
|
String::from("2. Two"),
|
||||||
|
String::from("Another example?\n¯\\_(ツ)_/¯"),
|
||||||
|
);
|
||||||
|
let too_many_lines: [usize; 75] = array::from_fn(|i| i + 1);
|
||||||
|
let too_many_lines: Vec<String> = too_many_lines.iter().map(|x| format!("{x}")).collect();
|
||||||
|
results_fake.insert(String::from("3. Three"), too_many_lines.join("\n"));
|
||||||
|
let results_arc = Arc::new(Mutex::new(results_fake));
|
||||||
let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
|
let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone());
|
||||||
let mut list = StatefulList::default();
|
let mut list = StatefulList::default();
|
||||||
list.set_items(vec![
|
list.set_items(vec![
|
||||||
|
|
@ -103,11 +114,12 @@ impl App {
|
||||||
Box::new(Footer::new()),
|
Box::new(Footer::new()),
|
||||||
Box::new(popup::Popup::new()),
|
Box::new(popup::Popup::new()),
|
||||||
Box::new(crate::components::progress::Progress::new()),
|
Box::new(crate::components::progress::Progress::new()),
|
||||||
Box::new(crate::components::logview::LogView::new(
|
Box::new(crate::components::results::Results::new(
|
||||||
diag_groups_arc.clone(),
|
results_arc.clone(),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
config: Config::new()?,
|
config: Config::new()?,
|
||||||
|
diag_groups: diags::Groups::new(),
|
||||||
frame_rate,
|
frame_rate,
|
||||||
last_tick_key_events: Vec::new(),
|
last_tick_key_events: Vec::new(),
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
|
|
@ -116,11 +128,11 @@ impl App {
|
||||||
// App
|
// App
|
||||||
clone: CloneSettings::new(disk_list_arc),
|
clone: CloneSettings::new(disk_list_arc),
|
||||||
cur_mode: Mode::Home,
|
cur_mode: Mode::Home,
|
||||||
diag_groups: diag_groups_arc,
|
|
||||||
list,
|
list,
|
||||||
boot_modes: vec![SafeMode::Enable, SafeMode::Disable],
|
boot_modes: vec![SafeMode::Enable, SafeMode::Disable],
|
||||||
system32: String::new(),
|
system32: String::new(),
|
||||||
selections: vec![None, None],
|
selections: vec![None, None],
|
||||||
|
results: results_arc,
|
||||||
tasks,
|
tasks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -345,9 +357,6 @@ impl App {
|
||||||
self.action_tx.send(Action::SetMode(next_mode))?;
|
self.action_tx.send(Action::SetMode(next_mode))?;
|
||||||
}
|
}
|
||||||
Action::Process => match self.cur_mode {
|
Action::Process => match self.cur_mode {
|
||||||
Mode::BootDiags => {
|
|
||||||
self.action_tx.send(Action::SetMode(Mode::LogView))?;
|
|
||||||
}
|
|
||||||
Mode::DiagMenu => {
|
Mode::DiagMenu => {
|
||||||
// Use highlighted entry
|
// Use highlighted entry
|
||||||
if let Some(new_mode) = self.list.get_selected() {
|
if let Some(new_mode) = self.list.get_selected() {
|
||||||
|
|
@ -357,9 +366,9 @@ impl App {
|
||||||
Mode::Done => {
|
Mode::Done => {
|
||||||
self.action_tx.send(Action::NextScreen)?;
|
self.action_tx.send(Action::NextScreen)?;
|
||||||
}
|
}
|
||||||
Mode::BootSetup => {
|
Mode::BootDiags | Mode::BootSetup => {
|
||||||
//let new_mode = self.next_mode();
|
let new_mode = self.next_mode();
|
||||||
//self.action_tx.send(Action::SetMode(new_mode))?;
|
self.action_tx.send(Action::SetMode(new_mode))?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
@ -410,9 +419,7 @@ impl App {
|
||||||
self.action_tx.send(Action::DiagLineStart {
|
self.action_tx.send(Action::DiagLineStart {
|
||||||
text: title.clone(),
|
text: title.clone(),
|
||||||
})?;
|
})?;
|
||||||
if let Ok(mut diag_groups) = self.diag_groups.lock() {
|
self.diag_groups.start(title.to_owned());
|
||||||
diag_groups.push(DiagGroup::new(get_diag_type(&title)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::TasksComplete => {
|
Action::TasksComplete => {
|
||||||
|
|
@ -441,60 +448,90 @@ impl App {
|
||||||
|
|
||||||
fn handle_task(&mut self, task: &Task) -> Result<()> {
|
fn handle_task(&mut self, task: &Task) -> Result<()> {
|
||||||
info!("Handling Task: {task:?}");
|
info!("Handling Task: {task:?}");
|
||||||
if self.cur_mode == Mode::BootScan {
|
match self.cur_mode {
|
||||||
if let Ok(mut diag_groups) = self.diag_groups.lock() {
|
Mode::BootScan => {
|
||||||
if let Some(current_group) = diag_groups.last_mut() {
|
|
||||||
match current_group.diag_type {
|
|
||||||
DiagType::CheckDisk => {
|
|
||||||
if let Some(task_result) = &task.result {
|
|
||||||
//
|
|
||||||
parse_chkdsk(current_group, task_result.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
match task.task_type {
|
|
||||||
TaskType::CommandNoWait(_, _) | TaskType::CommandWait(_, _) | TaskType::Diskpart(_) => {
|
|
||||||
// Check result
|
|
||||||
if let Some(result) = &task.result {
|
if let Some(result) = &task.result {
|
||||||
|
let title = self.diag_groups.current_group();
|
||||||
|
let passed: bool;
|
||||||
|
let info: String;
|
||||||
match result {
|
match result {
|
||||||
TaskResult::Error(msg) => {
|
TaskResult::Error(msg) => {
|
||||||
self.action_tx
|
passed = false;
|
||||||
.send(Action::Error(format!("{task:?} Failed: {msg}")))?;
|
info = msg.to_owned();
|
||||||
}
|
}
|
||||||
TaskResult::Output(stdout, stderr, success) => {
|
TaskResult::Output(stdout, stderr, success) => {
|
||||||
if !success {
|
passed = *success;
|
||||||
let msg = if !stdout.is_empty() {
|
if title == "Filesystem" {
|
||||||
stdout.clone()
|
info = parse_chkdsk(stdout);
|
||||||
} else if !stderr.is_empty() {
|
} else {
|
||||||
stderr.clone()
|
let div = if !(stdout.is_empty() || stderr.is_empty()) {
|
||||||
|
"\n\n-----------\n\n"
|
||||||
} else {
|
} else {
|
||||||
String::from("Unknown Error")
|
""
|
||||||
};
|
};
|
||||||
self.action_tx
|
info = format!("{stdout}{div}{stderr}");
|
||||||
.send(Action::Error(format!("{task:?} Failed: {msg}")))?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.diag_groups.update(title, passed, info);
|
||||||
|
if passed {
|
||||||
|
self.action_tx.send(Action::DiagLineUpdate {
|
||||||
|
result: DiagResult::Pass,
|
||||||
|
text: String::from("Pass?"),
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
self.action_tx.send(Action::DiagLineUpdate {
|
||||||
|
result: DiagResult::Fail,
|
||||||
|
text: String::from("Fail?"),
|
||||||
|
})?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TaskType::GroupEnd { ref label } => {
|
_ => {
|
||||||
self.action_tx.send(Action::DiagLineEnd {
|
match task.task_type {
|
||||||
text: label.clone(),
|
TaskType::CommandNoWait(_, _)
|
||||||
})?;
|
| TaskType::CommandWait(_, _)
|
||||||
|
| TaskType::Diskpart(_) => {
|
||||||
|
// Check result
|
||||||
|
if let Some(result) = &task.result {
|
||||||
|
match result {
|
||||||
|
TaskResult::Error(msg) => {
|
||||||
|
self.action_tx
|
||||||
|
.send(Action::Error(format!("{task:?} Failed: {msg}")))?;
|
||||||
|
}
|
||||||
|
TaskResult::Output(stdout, stderr, success) => {
|
||||||
|
if !success {
|
||||||
|
let msg = if !stdout.is_empty() {
|
||||||
|
stdout.clone()
|
||||||
|
} else if !stderr.is_empty() {
|
||||||
|
stderr.clone()
|
||||||
|
} else {
|
||||||
|
String::from("Unknown Error")
|
||||||
|
};
|
||||||
|
self.action_tx.send(Action::Error(format!(
|
||||||
|
"{task:?} Failed: {msg}"
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TaskType::GroupEnd { ref label } => {
|
||||||
|
self.action_tx.send(Action::DiagLineEnd {
|
||||||
|
text: label.clone(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_boot_scan_tasks(&mut self) -> Result<()> {
|
fn queue_boot_scan_tasks(&mut self) -> Result<()> {
|
||||||
if let Ok(mut diag_groups) = self.diag_groups.lock() {
|
self.diag_groups.reset();
|
||||||
diag_groups.clear();
|
if let Ok(mut results) = self.results.lock() {
|
||||||
|
results.clear();
|
||||||
}
|
}
|
||||||
let disk_list = self.clone.disk_list.lock().unwrap();
|
let disk_list = self.clone.disk_list.lock().unwrap();
|
||||||
if let Some(disk_index) = self.clone.disk_index_dest {
|
if let Some(disk_index) = self.clone.disk_index_dest {
|
||||||
|
|
@ -520,7 +557,7 @@ impl App {
|
||||||
// BCD
|
// BCD
|
||||||
if !letter_boot.is_empty() {
|
if !letter_boot.is_empty() {
|
||||||
self.tasks.add_group(
|
self.tasks.add_group(
|
||||||
DiagType::BootConfigData.to_string().as_str(),
|
"Boot Files",
|
||||||
vec![TaskType::CommandWait(
|
vec![TaskType::CommandWait(
|
||||||
PathBuf::from(format!("{}\\bcdedit.exe", &self.system32)),
|
PathBuf::from(format!("{}\\bcdedit.exe", &self.system32)),
|
||||||
vec![
|
vec![
|
||||||
|
|
@ -541,7 +578,7 @@ impl App {
|
||||||
|
|
||||||
// Bitlocker
|
// Bitlocker
|
||||||
self.tasks.add_group(
|
self.tasks.add_group(
|
||||||
DiagType::Bitlocker.to_string().as_str(),
|
"Bitlocker",
|
||||||
vec![
|
vec![
|
||||||
TaskType::CommandWait(
|
TaskType::CommandWait(
|
||||||
PathBuf::from(format!("{}\\manage-bde.exe", &self.system32)),
|
PathBuf::from(format!("{}\\manage-bde.exe", &self.system32)),
|
||||||
|
|
@ -559,24 +596,6 @@ impl App {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filesystem Health
|
// Filesystem Health
|
||||||
self.tasks.add_group(
|
|
||||||
DiagType::CheckDisk.to_string().as_str(),
|
|
||||||
vec![TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{}\\chkdsk.exe", &self.system32)),
|
|
||||||
vec![format!("{letter_os}:")],
|
|
||||||
)],
|
|
||||||
);
|
|
||||||
|
|
||||||
// DISM Health
|
|
||||||
self.tasks.add_group(
|
|
||||||
DiagType::ComponentStore.to_string().as_str(),
|
|
||||||
vec![TaskType::CommandWait(
|
|
||||||
PathBuf::from(format!("{}\\dism.exe", &self.system32)),
|
|
||||||
vec![format!("{letter_os}:")],
|
|
||||||
)],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Critical Files/Folders
|
|
||||||
let paths: Vec<PathBuf> = [
|
let paths: Vec<PathBuf> = [
|
||||||
// Files/Folders
|
// Files/Folders
|
||||||
"Users",
|
"Users",
|
||||||
|
|
@ -590,13 +609,28 @@ impl App {
|
||||||
.map(|s| PathBuf::from(format!("{letter_os}:\\{s}")))
|
.map(|s| PathBuf::from(format!("{letter_os}:\\{s}")))
|
||||||
.collect();
|
.collect();
|
||||||
self.tasks.add_group(
|
self.tasks.add_group(
|
||||||
DiagType::SystemFiles.to_string().as_str(),
|
"Filesystem",
|
||||||
vec![TaskType::TestPaths(paths)],
|
vec![
|
||||||
|
TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\chkdsk.exe", &self.system32)),
|
||||||
|
vec![format!("{letter_os}:")],
|
||||||
|
),
|
||||||
|
TaskType::TestPaths(paths),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// DISM Health
|
||||||
|
self.tasks.add_group(
|
||||||
|
"System Files",
|
||||||
|
vec![TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{}\\dism.exe", &self.system32)),
|
||||||
|
vec![format!("{letter_os}:")],
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Registry
|
// Registry
|
||||||
self.tasks.add_group(
|
self.tasks.add_group(
|
||||||
DiagType::Registry.to_string().as_str(),
|
"Registry",
|
||||||
vec![
|
vec![
|
||||||
TaskType::CommandWait(
|
TaskType::CommandWait(
|
||||||
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||||
|
|
@ -728,16 +762,13 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
||||||
|
|
||||||
fn build_footer_string(cur_mode: Mode) -> String {
|
fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
match cur_mode {
|
match cur_mode {
|
||||||
Mode::BootDiags | Mode::LogView => {
|
|
||||||
String::from("(Enter) to select / (m) for menu / (s) to start over / (q) to quit")
|
|
||||||
}
|
|
||||||
Mode::BootScan | Mode::BootSetup | Mode::Home | Mode::ScanDisks => {
|
Mode::BootScan | Mode::BootSetup | Mode::Home | Mode::ScanDisks => {
|
||||||
String::from("(q) to quit")
|
String::from("(q) to quit")
|
||||||
}
|
}
|
||||||
Mode::InstallDrivers | Mode::InjectDrivers | Mode::SetBootMode => {
|
Mode::InstallDrivers | Mode::InjectDrivers | Mode::SetBootMode => {
|
||||||
String::from("(Enter) to select / (q) to quit")
|
String::from("(Enter) to select / (q) to quit")
|
||||||
}
|
}
|
||||||
Mode::DiagMenu | Mode::SelectParts => {
|
Mode::BootDiags | Mode::DiagMenu | Mode::SelectParts => {
|
||||||
String::from("(Enter) to select / (s) to start over / (q) to quit")
|
String::from("(Enter) to select / (s) to start over / (q) to quit")
|
||||||
}
|
}
|
||||||
Mode::Done => String::from("(Enter) to continue / (q) to quit"),
|
Mode::Done => String::from("(Enter) to continue / (q) to quit"),
|
||||||
|
|
@ -817,25 +848,18 @@ fn build_left_items(app: &App) -> Action {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::BootDiags | Mode::LogView => {
|
Mode::BootDiags => {
|
||||||
select_type = SelectionType::Loop;
|
select_type = SelectionType::Loop;
|
||||||
let (new_title, _) = get_mode_strings(app.cur_mode);
|
let (new_title, _) = get_mode_strings(app.cur_mode);
|
||||||
title = new_title;
|
title = new_title;
|
||||||
if let Ok(diag_groups) = app.diag_groups.lock() {
|
app.diag_groups.get().iter().for_each(|group| {
|
||||||
let labels: Vec<String> = diag_groups
|
info!("BootDiags Group: {:?}", group);
|
||||||
.iter()
|
items.push(if group.passed {
|
||||||
.map(|group| {
|
group.title.clone()
|
||||||
let label = group.diag_type.to_string();
|
} else {
|
||||||
let status = if group.passed {
|
format!("{} - Issues detected!", group.title)
|
||||||
"" // Leave blank if OK
|
});
|
||||||
} else {
|
});
|
||||||
" -- Issue(s) detected"
|
|
||||||
};
|
|
||||||
format!("{label}{status}")
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
items.extend(labels.into_iter());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Mode::BootSetup => {
|
Mode::BootSetup => {
|
||||||
select_type = SelectionType::Loop;
|
select_type = SelectionType::Loop;
|
||||||
|
|
@ -946,13 +970,18 @@ fn build_right_items(app: &App) -> Action {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Mode::BootDiags => {
|
Mode::BootDiags => {
|
||||||
if let Ok(diag_groups) = app.diag_groups.lock() {
|
app.diag_groups.get().iter().for_each(|group| {
|
||||||
let mut summary: Vec<DVLine> = Vec::new();
|
let mut lines = Vec::new();
|
||||||
diag_groups
|
group.info.iter().for_each(|text| {
|
||||||
.iter()
|
text.lines().for_each(|line| {
|
||||||
.for_each(|group| summary.extend(group.get_logs_summary().into_iter()));
|
lines.push(DVLine {
|
||||||
items.push(summary);
|
line_parts: vec![String::from(line)],
|
||||||
}
|
line_colors: vec![Color::Reset],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
items.push(lines);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Mode::InjectDrivers | Mode::InstallDrivers => {
|
Mode::InjectDrivers | Mode::InstallDrivers => {
|
||||||
items.push(vec![DVLine {
|
items.push(vec![DVLine {
|
||||||
|
|
@ -1022,7 +1051,7 @@ fn build_right_items(app: &App) -> Action {
|
||||||
|
|
||||||
fn get_mode_strings(mode: Mode) -> (String, String) {
|
fn get_mode_strings(mode: Mode) -> (String, String) {
|
||||||
match mode {
|
match mode {
|
||||||
Mode::BootScan | Mode::BootDiags | Mode::LogView => (
|
Mode::BootScan | Mode::BootDiags => (
|
||||||
String::from("Boot Diagnostics"),
|
String::from("Boot Diagnostics"),
|
||||||
String::from("Check for common Windows boot issues"),
|
String::from("Check for common Windows boot issues"),
|
||||||
),
|
),
|
||||||
|
|
@ -1041,3 +1070,22 @@ fn get_mode_strings(mode: Mode) -> (String, String) {
|
||||||
_ => panic!("This shouldn't happen"),
|
_ => panic!("This shouldn't happen"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_chkdsk(output: &str) -> String {
|
||||||
|
// Split lines
|
||||||
|
let lines: Vec<_> = output.split("\r\n").collect();
|
||||||
|
|
||||||
|
// Omit progress lines and unhelpful messages
|
||||||
|
lines
|
||||||
|
.into_iter()
|
||||||
|
.filter(|line| {
|
||||||
|
!(line.contains("\r")
|
||||||
|
|| line.contains("Class not registered")
|
||||||
|
|| line.contains("/F parameter")
|
||||||
|
|| line.contains("Running CHKDSK")
|
||||||
|
|| line.contains("Total duration:")
|
||||||
|
|| line.contains("Failed to transfer logged messages"))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod logview;
|
|
||||||
pub mod progress;
|
pub mod progress;
|
||||||
|
pub mod results;
|
||||||
|
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
// This file is part of Deja-Vu.
|
|
||||||
//
|
|
||||||
// Deja-Vu is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Deja-Vu is distributed in the hope that it will be useful, but
|
|
||||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
// See the GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
use color_eyre::Result;
|
|
||||||
use ratatui::{
|
|
||||||
Frame,
|
|
||||||
layout::Rect,
|
|
||||||
widgets::{Block, Clear, Padding, Paragraph, Wrap},
|
|
||||||
};
|
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
|
||||||
|
|
||||||
use core::{
|
|
||||||
action::Action,
|
|
||||||
components::{Component, state::StatefulList},
|
|
||||||
config::Config,
|
|
||||||
state::Mode,
|
|
||||||
};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use crate::diags::DiagGroup;
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
pub struct LogView {
|
|
||||||
command_tx: Option<UnboundedSender<Action>>,
|
|
||||||
config: Config,
|
|
||||||
line_index: u16,
|
|
||||||
list: StatefulList<String>,
|
|
||||||
mode: Mode,
|
|
||||||
diag_groups: Arc<Mutex<Vec<DiagGroup>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LogView {
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(diag_groups: Arc<Mutex<Vec<DiagGroup>>>) -> Self {
|
|
||||||
LogView {
|
|
||||||
diag_groups,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for LogView {
|
|
||||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
|
||||||
self.command_tx = Some(tx);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
|
||||||
self.config = config;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
|
||||||
match action {
|
|
||||||
Action::KeyUp => {
|
|
||||||
if self.mode == Mode::LogView {
|
|
||||||
if self.line_index > 0 {
|
|
||||||
self.line_index = self.line_index - 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.list.previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::KeyDown => {
|
|
||||||
if self.mode == Mode::LogView {
|
|
||||||
self.line_index = self.line_index + 1;
|
|
||||||
} else {
|
|
||||||
self.list.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Process => {
|
|
||||||
if self.mode == Mode::LogView {
|
|
||||||
if let Some(command_tx) = self.command_tx.clone() {
|
|
||||||
command_tx.send(Action::SetMode(Mode::BootDiags))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::SetMode(new_mode) => {
|
|
||||||
self.line_index = 0;
|
|
||||||
self.mode = new_mode;
|
|
||||||
if self.mode == Mode::BootDiags {
|
|
||||||
self.list.clear_items();
|
|
||||||
if let Ok(diag_groups) = self.diag_groups.lock() {
|
|
||||||
let raw_logs = diag_groups
|
|
||||||
.iter()
|
|
||||||
.map(|group| group.get_logs_raw())
|
|
||||||
.collect();
|
|
||||||
self.list.set_items(raw_logs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&mut self, frame: &mut Frame, rect: Rect) -> Result<()> {
|
|
||||||
if self.mode != Mode::LogView {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(log_text) = self.list.get_selected() {
|
|
||||||
let paragraph = Paragraph::new(log_text)
|
|
||||||
.wrap(Wrap { trim: true })
|
|
||||||
.scroll((self.line_index, 0))
|
|
||||||
.block(Block::bordered().padding(Padding::horizontal(1)));
|
|
||||||
frame.render_widget(Clear, rect);
|
|
||||||
frame.render_widget(paragraph, rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -123,7 +123,7 @@ impl Component for Progress {
|
||||||
DiagResult::Warn => Color::Yellow,
|
DiagResult::Warn => Color::Yellow,
|
||||||
};
|
};
|
||||||
let text = if line.running || line.text.is_empty() {
|
let text = if line.running || line.text.is_empty() {
|
||||||
String::from("..")
|
String::from("...")
|
||||||
} else {
|
} else {
|
||||||
line.text.clone()
|
line.text.clone()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
136
boot_diags/src/components/results.rs
Normal file
136
boot_diags/src/components/results.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
// This file is part of Deja-Vu.
|
||||||
|
//
|
||||||
|
// Deja-Vu is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Deja-Vu is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
// See the GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
use color_eyre::Result;
|
||||||
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
style::{Style, Stylize},
|
||||||
|
widgets::{Block, Clear, Padding, Paragraph, Tabs},
|
||||||
|
};
|
||||||
|
|
||||||
|
use core::{action::Action, components::Component, state::Mode};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Results {
|
||||||
|
// command_tx: Option<UnboundedSender<Action>>,
|
||||||
|
// config: Config,
|
||||||
|
key_index: usize,
|
||||||
|
line_index: u16,
|
||||||
|
results: Arc<Mutex<HashMap<String, String>>>,
|
||||||
|
show: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Results {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(results: Arc<Mutex<HashMap<String, String>>>) -> Self {
|
||||||
|
Results {
|
||||||
|
key_index: 0,
|
||||||
|
line_index: 0,
|
||||||
|
results,
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||||
|
// self.command_tx = Some(tx);
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||||
|
// self.config = config;
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Results {
|
||||||
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
|
match action {
|
||||||
|
Action::KeyUp => {
|
||||||
|
if self.line_index > 0 {
|
||||||
|
self.line_index = self.line_index - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action::KeyDown => {
|
||||||
|
self.line_index = self.line_index + 1;
|
||||||
|
}
|
||||||
|
Action::KeyLeft => {
|
||||||
|
if self.key_index > 0 {
|
||||||
|
self.key_index -= 1;
|
||||||
|
self.line_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action::KeyRight => {
|
||||||
|
if self.key_index < 2 {
|
||||||
|
self.key_index += 1;
|
||||||
|
self.line_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action::SetMode(mode) => {
|
||||||
|
self.show = mode == Mode::BootDiags;
|
||||||
|
self.key_index = 0;
|
||||||
|
self.line_index = 0;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, frame: &mut Frame, rect: Rect) -> Result<()> {
|
||||||
|
return Ok(());
|
||||||
|
if !self.show {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let areas = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
||||||
|
.split(rect)
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
if let Ok(results_hashmap) = self.results.lock() {
|
||||||
|
// Labels
|
||||||
|
let mut tab_labels: Vec<&str> = results_hashmap.keys().map(|k| k.as_str()).collect();
|
||||||
|
tab_labels.sort();
|
||||||
|
let hash_key = tab_labels.get(self.key_index).unwrap().to_string();
|
||||||
|
let tabs = Tabs::new(tab_labels)
|
||||||
|
.block(Block::bordered())
|
||||||
|
.style(Style::default().white())
|
||||||
|
.highlight_style(Style::default().green())
|
||||||
|
.select(self.key_index)
|
||||||
|
.divider("")
|
||||||
|
.padding(" [", "] ");
|
||||||
|
|
||||||
|
// Details
|
||||||
|
let details = if let Some(lines) = results_hashmap.get(&hash_key) {
|
||||||
|
lines.clone()
|
||||||
|
} else {
|
||||||
|
String::from("¯\\_(ツ)_/¯")
|
||||||
|
};
|
||||||
|
let paragraph = Paragraph::new(details)
|
||||||
|
.scroll((self.line_index, 0))
|
||||||
|
.block(Block::bordered().padding(Padding::horizontal(1)));
|
||||||
|
|
||||||
|
// Render
|
||||||
|
frame.render_widget(Clear, rect);
|
||||||
|
frame.render_widget(tabs, areas[0]);
|
||||||
|
frame.render_widget(paragraph, areas[1]);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,169 +13,78 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
// along with Deja-Vu. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use core::{line::DVLine, tasks::TaskResult};
|
use std::collections::HashMap;
|
||||||
use std::{fmt, thread::sleep, time::Duration};
|
|
||||||
|
|
||||||
use ratatui::style::Color;
|
use tracing::warn;
|
||||||
use tracing::{info, warn};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Type {
|
pub struct Groups {
|
||||||
Bitlocker,
|
items: HashMap<String, Line>,
|
||||||
BootConfigData,
|
order: Vec<String>,
|
||||||
CheckDisk,
|
|
||||||
ComponentStore,
|
|
||||||
Registry,
|
|
||||||
SystemFiles,
|
|
||||||
Unknown,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Type {
|
impl Groups {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
pub fn new() -> Self {
|
||||||
match self {
|
Groups {
|
||||||
Type::Bitlocker => write!(f, "Bitlocker"),
|
items: HashMap::new(),
|
||||||
Type::BootConfigData => write!(f, "Boot Files"),
|
order: Vec::new(),
|
||||||
Type::CheckDisk => write!(f, "CHKDSK"),
|
|
||||||
Type::ComponentStore => write!(f, "DISM ScanHealth"),
|
|
||||||
Type::Registry => write!(f, "Registry"),
|
|
||||||
Type::SystemFiles => write!(f, "System Files"),
|
|
||||||
Type::Unknown => write!(f, "Unknown Type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Log {
|
|
||||||
pub label: String,
|
|
||||||
pub summary: Vec<DVLine>,
|
|
||||||
pub raw: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct DiagGroup {
|
|
||||||
pub complete: bool,
|
|
||||||
pub diag_type: Type,
|
|
||||||
pub passed: bool,
|
|
||||||
pub logs: Vec<Log>,
|
|
||||||
pub result: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiagGroup {
|
|
||||||
pub fn new(diag_type: Type) -> Self {
|
|
||||||
DiagGroup {
|
|
||||||
complete: false,
|
|
||||||
diag_type,
|
|
||||||
passed: true,
|
|
||||||
logs: Vec::new(),
|
|
||||||
result: String::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_logs_raw(&self) -> String {
|
pub fn current_group(&self) -> String {
|
||||||
let raw_logs: Vec<String> = self
|
match self.order.last() {
|
||||||
.logs
|
Some(label) => label.clone(),
|
||||||
.iter()
|
None => String::from("No current group"),
|
||||||
.map(|log| format!("-- {} --\n{}", &log.label, &log.raw))
|
|
||||||
.collect();
|
|
||||||
raw_logs.join("\n\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_logs_summary(&self) -> Vec<DVLine> {
|
|
||||||
let mut summaries: Vec<DVLine> = Vec::new();
|
|
||||||
self.logs
|
|
||||||
.iter()
|
|
||||||
.for_each(|log| summaries.extend(log.summary.clone().into_iter()));
|
|
||||||
summaries
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_diag_type(label: &str) -> Type {
|
|
||||||
info!("Getting Diag type for {label}");
|
|
||||||
match label {
|
|
||||||
"Bitlocker" => Type::Bitlocker,
|
|
||||||
"Boot Files" => Type::BootConfigData,
|
|
||||||
"DISM ScanHealth" => Type::ComponentStore,
|
|
||||||
"CHKDSK" => Type::CheckDisk,
|
|
||||||
"Registry" => Type::Registry,
|
|
||||||
"System Files" => Type::SystemFiles,
|
|
||||||
_ => {
|
|
||||||
warn!("Failed to determine type");
|
|
||||||
Type::Unknown
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_chkdsk(diag_group: &mut DiagGroup, task_result: TaskResult) {
|
pub fn get(&self) -> Vec<&Line> {
|
||||||
if !cfg!(windows) {
|
let mut lines = Vec::new();
|
||||||
sleep(Duration::from_millis(500));
|
self.order.iter().for_each(|key| {
|
||||||
return ();
|
if let Some(line) = self.items.get(key) {
|
||||||
}
|
lines.push(line);
|
||||||
match task_result {
|
|
||||||
TaskResult::Error(err) => {
|
|
||||||
diag_group.passed = false;
|
|
||||||
diag_group.result = String::from("Error");
|
|
||||||
diag_group.logs.push(Log {
|
|
||||||
label: String::from("CHKDSK"),
|
|
||||||
summary: vec![DVLine {
|
|
||||||
line_parts: vec![String::from("CHKDSK: "), String::from("Error")],
|
|
||||||
line_colors: vec![Color::Reset, Color::Red],
|
|
||||||
}],
|
|
||||||
raw: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
TaskResult::Output(stdout, stderr, _) => {
|
|
||||||
if stdout.contains("Windows has scanned the file system and found no problems.") {
|
|
||||||
diag_group.passed &= true;
|
|
||||||
diag_group.result = String::from("OK");
|
|
||||||
diag_group.logs.push(Log {
|
|
||||||
label: String::from("CHKDSK"),
|
|
||||||
summary: vec![DVLine {
|
|
||||||
line_parts: vec![String::from("CHKDSK: "), String::from("OK")],
|
|
||||||
line_colors: vec![Color::Reset, Color::Green],
|
|
||||||
}],
|
|
||||||
raw: format!("{stdout}\n\n-------\n\n{stderr}"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
diag_group.passed = false;
|
|
||||||
diag_group.result = String::from("Error");
|
|
||||||
diag_group.logs.push(Log {
|
|
||||||
label: String::from("CHKDSK"),
|
|
||||||
summary: vec![DVLine {
|
|
||||||
line_parts: vec![String::from("CHKDSK: "), String::from("Error")],
|
|
||||||
line_colors: vec![Color::Reset, Color::Red],
|
|
||||||
}],
|
|
||||||
raw: format!("{stdout}\n\n-------\n\n{stderr}"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
lines
|
||||||
}
|
}
|
||||||
|
|
||||||
// let mut summary = Vec::new();
|
pub fn reset(&mut self) {
|
||||||
// let raw = if stderr.is_empty() {
|
self.items.clear();
|
||||||
// stdout.to_string()
|
self.order.clear();
|
||||||
// } else {
|
}
|
||||||
// format!("{stdout}\n\n --stderr-- \n\n{stderr}")
|
|
||||||
// };
|
pub fn start(&mut self, title: String) {
|
||||||
// // TODO: Implement actual logic for result
|
self.order.push(title.clone());
|
||||||
// Log {
|
self.items.insert(
|
||||||
// label: String::from("CHKDSK"),
|
title.clone(),
|
||||||
// raw,
|
Line {
|
||||||
// summary,
|
title,
|
||||||
// }
|
passed: true,
|
||||||
// // Split lines
|
info: Vec::new(),
|
||||||
// let lines: Vec<_> = output.split("\r\n").collect();
|
},
|
||||||
//
|
);
|
||||||
// // Omit progress lines and unhelpful messages
|
}
|
||||||
// lines
|
|
||||||
// .into_iter()
|
pub fn update(&mut self, title: String, passed: bool, info: String) {
|
||||||
// .filter(|line| {
|
if let Some(line) = self.items.get_mut(&title) {
|
||||||
// !(line.contains("\r")
|
line.update(passed, info);
|
||||||
// || line.contains("Class not registered")
|
} else {
|
||||||
// || line.contains("/F parameter")
|
warn!("WARNING/DELETEME - This shouldn't happen?!");
|
||||||
// || line.contains("Running CHKDSK")
|
self.start(title);
|
||||||
// || line.contains("Total duration:")
|
}
|
||||||
// || line.contains("Failed to transfer logged messages"))
|
}
|
||||||
// })
|
}
|
||||||
// .collect::<Vec<_>>()
|
|
||||||
// .join("\n")
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Line {
|
||||||
|
pub title: String,
|
||||||
|
pub passed: bool,
|
||||||
|
pub info: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Line {
|
||||||
|
pub fn update(&mut self, passed: bool, info: String) {
|
||||||
|
self.passed &= passed; // We fail if any tests in this group fail
|
||||||
|
self.info.push(info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,8 @@
|
||||||
"<Enter>": "Process",
|
"<Enter>": "Process",
|
||||||
"<Up>": "KeyUp",
|
"<Up>": "KeyUp",
|
||||||
"<Down>": "KeyDown",
|
"<Down>": "KeyDown",
|
||||||
|
"<Left>": "KeyLeft",
|
||||||
|
"<Right>": "KeyRight",
|
||||||
"<r>": "BootScan",
|
"<r>": "BootScan",
|
||||||
"<s>": "ScanDisks",
|
"<s>": "ScanDisks",
|
||||||
"<q>": "Quit",
|
"<q>": "Quit",
|
||||||
|
|
@ -131,15 +133,6 @@
|
||||||
"<Ctrl-c>": "Quit",
|
"<Ctrl-c>": "Quit",
|
||||||
"<Ctrl-z>": "Suspend"
|
"<Ctrl-z>": "Suspend"
|
||||||
},
|
},
|
||||||
"LogView": {
|
|
||||||
"<Enter>": "Process",
|
|
||||||
"<Up>": "KeyUp",
|
|
||||||
"<Down>": "KeyDown",
|
|
||||||
"<q>": "Quit",
|
|
||||||
"<Ctrl-d>": "Quit",
|
|
||||||
"<Ctrl-c>": "Quit",
|
|
||||||
"<Ctrl-z>": "Suspend"
|
|
||||||
},
|
|
||||||
"InjectDrivers": {
|
"InjectDrivers": {
|
||||||
"<Enter>": "Process",
|
"<Enter>": "Process",
|
||||||
"<Up>": "KeyUp",
|
"<Up>": "KeyUp",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use super::{Component, state::StatefulList};
|
use super::{Component, state::StatefulList};
|
||||||
use crate::{action::Action, config::Config, state::Mode};
|
use crate::{action::Action, config::Config};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum SelectionType {
|
pub enum SelectionType {
|
||||||
|
|
@ -39,7 +39,6 @@ pub struct Left {
|
||||||
config: Config,
|
config: Config,
|
||||||
labels: Vec<String>,
|
labels: Vec<String>,
|
||||||
list: StatefulList<String>,
|
list: StatefulList<String>,
|
||||||
mode: Mode,
|
|
||||||
select_type: SelectionType,
|
select_type: SelectionType,
|
||||||
selections: Vec<Option<usize>>,
|
selections: Vec<Option<usize>>,
|
||||||
selections_saved: Vec<Option<usize>>,
|
selections_saved: Vec<Option<usize>>,
|
||||||
|
|
@ -83,21 +82,9 @@ impl Component for Left {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::Highlight(index) => self.set_highlight(index),
|
Action::Highlight(index) => self.set_highlight(index),
|
||||||
Action::KeyUp => {
|
Action::KeyUp => self.list.previous(),
|
||||||
if self.mode != Mode::LogView {
|
Action::KeyDown => self.list.next(),
|
||||||
self.list.previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::KeyDown => {
|
|
||||||
if self.mode != Mode::LogView {
|
|
||||||
self.list.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Process => {
|
Action::Process => {
|
||||||
if self.mode == Mode::LogView {
|
|
||||||
// Avoid updating selections/etc while log is open
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
if self.select_type == SelectionType::Loop {
|
if self.select_type == SelectionType::Loop {
|
||||||
// Selections aren't being used so this is a no-op
|
// Selections aren't being used so this is a no-op
|
||||||
} else if let Some(command_tx) = self.command_tx.clone() {
|
} else if let Some(command_tx) = self.command_tx.clone() {
|
||||||
|
|
@ -136,8 +123,7 @@ impl Component for Left {
|
||||||
self.selections_saved[0] = one;
|
self.selections_saved[0] = one;
|
||||||
self.selections_saved[1] = two;
|
self.selections_saved[1] = two;
|
||||||
}
|
}
|
||||||
Action::SetMode(new_mode) => {
|
Action::SetMode(_) => {
|
||||||
self.mode = new_mode;
|
|
||||||
self.selections[0] = None;
|
self.selections[0] = None;
|
||||||
self.selections[1] = None;
|
self.selections[1] = None;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use ratatui::{
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use super::{Component, state::StatefulList};
|
use super::{Component, state::StatefulList};
|
||||||
use crate::{action::Action, config::Config, line::DVLine, state::Mode};
|
use crate::{action::Action, config::Config, line::DVLine};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Right {
|
pub struct Right {
|
||||||
|
|
@ -31,7 +31,6 @@ pub struct Right {
|
||||||
list_header: Vec<DVLine>,
|
list_header: Vec<DVLine>,
|
||||||
list_labels: Vec<Vec<DVLine>>,
|
list_labels: Vec<Vec<DVLine>>,
|
||||||
list: StatefulList<Vec<DVLine>>,
|
list: StatefulList<Vec<DVLine>>,
|
||||||
mode: Mode,
|
|
||||||
selections: Vec<Option<usize>>,
|
selections: Vec<Option<usize>>,
|
||||||
selections_saved: Vec<Option<usize>>,
|
selections_saved: Vec<Option<usize>>,
|
||||||
title: String,
|
title: String,
|
||||||
|
|
@ -94,16 +93,8 @@ impl Component for Right {
|
||||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
match action {
|
match action {
|
||||||
Action::Highlight(index) => self.set_highlight(index),
|
Action::Highlight(index) => self.set_highlight(index),
|
||||||
Action::KeyUp => {
|
Action::KeyUp => self.list.previous(),
|
||||||
if self.mode != Mode::LogView {
|
Action::KeyDown => self.list.next(),
|
||||||
self.list.previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::KeyDown => {
|
|
||||||
if self.mode != Mode::LogView {
|
|
||||||
self.list.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Select(one, two) => {
|
Action::Select(one, two) => {
|
||||||
self.selections[0] = one;
|
self.selections[0] = one;
|
||||||
self.selections[1] = two;
|
self.selections[1] = two;
|
||||||
|
|
@ -118,8 +109,7 @@ impl Component for Right {
|
||||||
self.selections_saved[0] = one;
|
self.selections_saved[0] = one;
|
||||||
self.selections_saved[1] = two;
|
self.selections_saved[1] = two;
|
||||||
}
|
}
|
||||||
Action::SetMode(new_mode) => {
|
Action::SetMode(_) => {
|
||||||
self.mode = new_mode;
|
|
||||||
self.selections[0] = None;
|
self.selections[0] = None;
|
||||||
self.selections[1] = None;
|
self.selections[1] = None;
|
||||||
self.selections_saved[0] = None;
|
self.selections_saved[0] = None;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ pub enum Mode {
|
||||||
BootDiags,
|
BootDiags,
|
||||||
BootScan,
|
BootScan,
|
||||||
BootSetup,
|
BootSetup,
|
||||||
LogView,
|
|
||||||
InjectDrivers,
|
InjectDrivers,
|
||||||
SetBootMode,
|
SetBootMode,
|
||||||
// Clone
|
// Clone
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,6 @@ impl App {
|
||||||
| Mode::BootSetup
|
| Mode::BootSetup
|
||||||
| Mode::DiagMenu
|
| Mode::DiagMenu
|
||||||
| Mode::InjectDrivers
|
| Mode::InjectDrivers
|
||||||
| Mode::LogView
|
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +149,6 @@ impl App {
|
||||||
| Mode::BootSetup
|
| Mode::BootSetup
|
||||||
| Mode::DiagMenu
|
| Mode::DiagMenu
|
||||||
| Mode::InjectDrivers
|
| Mode::InjectDrivers
|
||||||
| Mode::LogView
|
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
};
|
};
|
||||||
|
|
@ -629,7 +627,6 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
| Mode::BootSetup
|
| Mode::BootSetup
|
||||||
| Mode::DiagMenu
|
| Mode::DiagMenu
|
||||||
| Mode::InjectDrivers
|
| Mode::InjectDrivers
|
||||||
| Mode::LogView
|
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
}
|
}
|
||||||
|
|
@ -701,7 +698,6 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action {
|
||||||
| Mode::BootSetup
|
| Mode::BootSetup
|
||||||
| Mode::DiagMenu
|
| Mode::DiagMenu
|
||||||
| Mode::InjectDrivers
|
| Mode::InjectDrivers
|
||||||
| Mode::LogView
|
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue