Add boot diagnostic sections
Still very much WIP
This commit is contained in:
parent
8f8f78a161
commit
7b0deb4cc7
8 changed files with 578 additions and 208 deletions
|
|
@ -15,7 +15,7 @@
|
|||
//
|
||||
use crate::diags;
|
||||
use core::{
|
||||
action::Action,
|
||||
action::{Action, DiagResult},
|
||||
components::{
|
||||
Component,
|
||||
footer::Footer,
|
||||
|
|
@ -39,6 +39,7 @@ use core::{
|
|||
tui::{Event, Tui},
|
||||
};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
env,
|
||||
iter::zip,
|
||||
path::PathBuf,
|
||||
|
|
@ -75,6 +76,7 @@ pub struct App {
|
|||
selections: Vec<Option<usize>>,
|
||||
system32: String,
|
||||
tasks: Tasks,
|
||||
task_groups: VecDeque<Option<String>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -100,6 +102,8 @@ impl App {
|
|||
Box::new(Right::new()),
|
||||
Box::new(Footer::new()),
|
||||
Box::new(popup::Popup::new()),
|
||||
Box::new(crate::components::progress::Progress::new()),
|
||||
Box::new(crate::components::results::Results::new()),
|
||||
],
|
||||
config: Config::new()?,
|
||||
diag_groups: diags::Groups::new(),
|
||||
|
|
@ -116,6 +120,7 @@ impl App {
|
|||
system32: String::new(),
|
||||
selections: vec![None, None],
|
||||
tasks,
|
||||
task_groups: VecDeque::new(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -124,10 +129,10 @@ impl App {
|
|||
if let Some(disk_index) = self.clone.disk_index_dest {
|
||||
let disk_list = self.clone.disk_list.lock().unwrap();
|
||||
if let Some(disk) = disk_list.get(disk_index) {
|
||||
if let Some(boot_index) = self.clone.part_index_boot {
|
||||
if let Some(os_index) = self.clone.part_index_os {
|
||||
if let Ok(task) = boot::inject_driver(
|
||||
driver,
|
||||
disk.get_part_letter(boot_index).as_str(),
|
||||
disk.get_part_letter(os_index).as_str(),
|
||||
&self.system32,
|
||||
) {
|
||||
self.tasks.add(task);
|
||||
|
|
@ -192,122 +197,7 @@ impl App {
|
|||
popup::Type::Info,
|
||||
String::from("Gathering info..."),
|
||||
))?;
|
||||
|
||||
// Get System32 path
|
||||
let system32 = if cfg!(windows) {
|
||||
if let Ok(path) = env::var("SYSTEMROOT") {
|
||||
format!("{path}\\System32")
|
||||
} else {
|
||||
self.action_tx.send(Action::Error(String::from(
|
||||
"ERROR\n\n\nFailed to find SYSTEMROOT",
|
||||
)))?;
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
String::from(".")
|
||||
};
|
||||
|
||||
// Add tasks
|
||||
let disk_list = self.clone.disk_list.lock().unwrap();
|
||||
if let Some(disk_index) = self.clone.disk_index_dest {
|
||||
if let Some(disk) = disk_list.get(disk_index) {
|
||||
let table_type = disk.part_type.clone();
|
||||
let letter_boot = disk.get_part_letter(self.clone.part_index_boot.unwrap());
|
||||
let letter_os = disk.get_part_letter(self.clone.part_index_os.unwrap());
|
||||
|
||||
// Safety check
|
||||
if letter_boot.is_empty() || letter_os.is_empty() {
|
||||
self.action_tx.send(Action::Error(String::from(
|
||||
"ERROR\n\n\nFailed to get drive letters for the destination",
|
||||
)))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// BCD
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\bcdedit.exe")),
|
||||
vec![
|
||||
String::from("/store"),
|
||||
format!(
|
||||
"{letter_boot}:{}\\Boot\\BCD",
|
||||
if table_type == PartitionTableType::Guid {
|
||||
"\\EFI\\Microsoft"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
String::from("/enum"),
|
||||
],
|
||||
));
|
||||
|
||||
// Bitlocker
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\manage-bde.exe")),
|
||||
vec![String::from("-status"), format!("{letter_os}:")],
|
||||
));
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\manage-bde.exe")),
|
||||
vec![
|
||||
String::from("-protectors"),
|
||||
String::from("-get"),
|
||||
format!("{letter_os}:"),
|
||||
],
|
||||
));
|
||||
|
||||
// DISM Health
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\dism.exe")),
|
||||
vec![format!("{letter_os}:")],
|
||||
));
|
||||
|
||||
// Filesystem Health
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\chkdsk.exe")),
|
||||
vec![format!("{letter_os}:")],
|
||||
));
|
||||
|
||||
// Registry
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
||||
vec![
|
||||
String::from("load"),
|
||||
String::from("HKLM\\TmpSoftware"),
|
||||
format!("{letter_os}:\\Windows\\System32\\config\\SOFTWARE"),
|
||||
],
|
||||
));
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
||||
vec![
|
||||
String::from("load"),
|
||||
String::from("HKLM\\TmpSystem"),
|
||||
format!("{letter_os}:\\Windows\\System32\\config\\SYSTEM"),
|
||||
],
|
||||
));
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
||||
vec![
|
||||
String::from("load"),
|
||||
String::from("HKU\\TmpDefault"),
|
||||
format!("{letter_os}:\\Windows\\System32\\config\\DEFAULT"),
|
||||
],
|
||||
));
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
||||
vec![String::from("unload"), String::from("HKLM\\TmpSoftware")],
|
||||
));
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
||||
vec![String::from("unload"), String::from("HKLM\\TmpSystem")],
|
||||
));
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{system32}\\reg.exe")),
|
||||
vec![String::from("unload"), String::from("HKU\\TmpDefault")],
|
||||
));
|
||||
|
||||
// Files/Folders
|
||||
// TODO: Check for critical folders (e.g. /Windows, /Windows/System32, etc)
|
||||
}
|
||||
}
|
||||
self.queue_boot_scan_tasks()?;
|
||||
}
|
||||
Mode::InjectDrivers | Mode::InstallDrivers => self.clone.scan_drivers(),
|
||||
Mode::ScanDisks => {
|
||||
|
|
@ -510,6 +400,34 @@ impl App {
|
|||
self.action_tx.send(build_right_items(self))?;
|
||||
self.action_tx.send(Action::Select(None, None))?;
|
||||
}
|
||||
Action::TaskStart(ref task_type) => {
|
||||
if self.cur_mode == Mode::BootScan {
|
||||
let title: Option<String> = match task_type {
|
||||
TaskType::CommandWait(cmd, _args) => {
|
||||
let cmd_str = cmd.to_string_lossy().into_owned();
|
||||
let diag_type = diags::get_type(&cmd_str);
|
||||
Some(format!("{diag_type}"))
|
||||
}
|
||||
TaskType::TestPaths(_) => Some(String::from("Critical Paths")),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(title) = title {
|
||||
info!("{:?}", self.task_groups.front());
|
||||
if self.task_groups.front().is_some() {
|
||||
if self.task_groups.front().unwrap().is_some() {
|
||||
// None here means that we're in the middle of a group of tasks
|
||||
// i.e. don't start a new diag line
|
||||
self.action_tx.send(Action::DiagStartLine {
|
||||
text: title.clone(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
if !self.diag_groups.contains(&title) {
|
||||
self.diag_groups.update(title, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::TasksComplete => {
|
||||
if self.cur_mode == Mode::BootDiags {
|
||||
self.action_tx.send(Action::DismissPopup)?;
|
||||
|
|
@ -536,71 +454,109 @@ impl App {
|
|||
|
||||
fn handle_task(&mut self, task: &Task) -> Result<()> {
|
||||
info!("Handling Task: {task:?}");
|
||||
let title: Option<String>;
|
||||
match self.cur_mode {
|
||||
Mode::BootScan => {
|
||||
if let TaskType::CommandWait(cmd_path, _cmd_args) = &task.task_type {
|
||||
let mut cmd_name = "";
|
||||
if let Some(path) = cmd_path.file_name() {
|
||||
if let Some(cmd_str) = path.to_str() {
|
||||
cmd_name = cmd_str;
|
||||
}
|
||||
};
|
||||
let diag_title = match diags::get_type(cmd_name) {
|
||||
diags::Type::BootConfigData => Some("Boot Files"),
|
||||
diags::Type::Bitlocker => Some("Bitlocker"),
|
||||
diags::Type::FileSystem => {
|
||||
if let Some(result) = &task.result {
|
||||
let passed: bool;
|
||||
let info: String;
|
||||
match result {
|
||||
TaskResult::Error(msg) => {
|
||||
passed = false;
|
||||
info = msg.to_owned();
|
||||
}
|
||||
TaskResult::Output(stdout, _stderr, success) => {
|
||||
passed = *success;
|
||||
info = parse_chkdsk(stdout);
|
||||
}
|
||||
}
|
||||
self.diag_groups.update(
|
||||
String::from("Filesystem"),
|
||||
passed,
|
||||
info.to_owned(),
|
||||
);
|
||||
let task_group = self.task_groups.pop_front();
|
||||
match &task.task_type {
|
||||
TaskType::CommandWait(cmd_path, _cmd_args) => {
|
||||
let mut cmd_name = "";
|
||||
if let Some(path) = cmd_path.file_name() {
|
||||
if let Some(cmd_str) = path.to_str() {
|
||||
cmd_name = cmd_str;
|
||||
}
|
||||
None // Don't set title since we handle the logic here
|
||||
}
|
||||
diags::Type::Registry => Some("Registry"),
|
||||
diags::Type::FilesAndFolders => Some("Critical Files"),
|
||||
_ => {
|
||||
warn!("Unrecognized command: {:?}", &cmd_path);
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(title) = diag_title {
|
||||
// Just use command output
|
||||
if let Some(result) = &task.result {
|
||||
let passed: bool;
|
||||
let info: String;
|
||||
match result {
|
||||
TaskResult::Error(msg) => {
|
||||
passed = false;
|
||||
info = msg.to_owned();
|
||||
}
|
||||
TaskResult::Output(stdout, stderr, success) => {
|
||||
passed = *success;
|
||||
let div = if !(stdout.is_empty() || stderr.is_empty()) {
|
||||
"\n\n-----------\n\n"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
info = format!("{stdout}{div}{stderr}");
|
||||
};
|
||||
let diag_type = diags::get_type(cmd_name);
|
||||
match diag_type {
|
||||
diags::Type::Bitlocker
|
||||
| diags::Type::BootConfigData
|
||||
| diags::Type::Registry
|
||||
| diags::Type::System => {
|
||||
title = Some(format!("{diag_type}"));
|
||||
}
|
||||
diags::Type::FileSystem => {
|
||||
title = None;
|
||||
if let Some(result) = &task.result {
|
||||
let passed: bool;
|
||||
let info: String;
|
||||
match result {
|
||||
TaskResult::Error(msg) => {
|
||||
passed = false;
|
||||
info = msg.to_owned();
|
||||
}
|
||||
TaskResult::Output(stdout, _stderr, success) => {
|
||||
passed = *success;
|
||||
info = parse_chkdsk(stdout);
|
||||
}
|
||||
}
|
||||
self.diag_groups.update(
|
||||
String::from("Filesystem"),
|
||||
Some(passed),
|
||||
Some(info.to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.diag_groups
|
||||
.update(title.to_string(), passed, info.to_owned());
|
||||
}
|
||||
diags::Type::Unknown => {
|
||||
title = None;
|
||||
warn!("Unrecognized command: {:?}", &cmd_path);
|
||||
}
|
||||
};
|
||||
}
|
||||
TaskType::TestPaths(_) => {
|
||||
title = Some(format!("{}", diags::Type::FileSystem));
|
||||
}
|
||||
_ => title = None,
|
||||
}
|
||||
if let Some(title_str) = title {
|
||||
if let Some(result) = &task.result {
|
||||
let passed: bool;
|
||||
let info: String;
|
||||
match result {
|
||||
TaskResult::Error(msg) => {
|
||||
passed = false;
|
||||
info = msg.to_owned();
|
||||
}
|
||||
TaskResult::Output(stdout, stderr, success) => {
|
||||
passed = *success;
|
||||
let div = if !(stdout.is_empty() || stderr.is_empty()) {
|
||||
"\n\n-----------\n\n"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
info = format!("{stdout}{div}{stderr}");
|
||||
}
|
||||
}
|
||||
self.diag_groups
|
||||
.update(title_str, Some(passed), Some(info.to_owned()));
|
||||
if let Some(group) = task_group {
|
||||
if let Some(wat) = group {
|
||||
info!("WAT? // {wat:?}");
|
||||
if passed {
|
||||
self.action_tx.send(Action::DiagEndLine {
|
||||
result: DiagResult::Pass,
|
||||
text: String::from("Maybe?"),
|
||||
})?;
|
||||
} else {
|
||||
self.action_tx.send(Action::DiagEndLine {
|
||||
result: DiagResult::Fail,
|
||||
text: String::from("Nope?"),
|
||||
})?;
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If title was set but there wasn't a result
|
||||
self.action_tx.send(Action::DiagEndLine {
|
||||
result: DiagResult::Warn,
|
||||
text: String::from("Yellow result?"),
|
||||
})?;
|
||||
}
|
||||
} else {
|
||||
// title was not set
|
||||
self.action_tx.send(Action::DiagEndLine {
|
||||
result: DiagResult::Warn,
|
||||
text: String::from("Yellow title?"),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
|
@ -639,13 +595,156 @@ impl App {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn queue_boot_scan_tasks(&mut self) -> Result<()> {
|
||||
let disk_list = self.clone.disk_list.lock().unwrap();
|
||||
if let Some(disk_index) = self.clone.disk_index_dest {
|
||||
if let Some(disk) = disk_list.get(disk_index) {
|
||||
let table_type = disk.part_type.clone();
|
||||
let letter_boot = disk.get_part_letter(self.clone.part_index_boot.unwrap());
|
||||
let letter_os = disk.get_part_letter(self.clone.part_index_os.unwrap());
|
||||
|
||||
// Safety check
|
||||
if letter_os.is_empty() {
|
||||
if letter_boot.is_empty() {
|
||||
self.action_tx.send(Action::Error(String::from(
|
||||
"ERROR\n\n\nFailed to get drive letters for the boot and OS volumes",
|
||||
)))?;
|
||||
} else {
|
||||
self.action_tx.send(Action::Error(String::from(
|
||||
"ERROR\n\n\nFailed to get drive letter for the OS volume",
|
||||
)))?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// BCD
|
||||
if !letter_boot.is_empty() {
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\bcdedit.exe", &self.system32)),
|
||||
vec![
|
||||
String::from("/store"),
|
||||
format!(
|
||||
"{letter_boot}:{}\\Boot\\BCD",
|
||||
if table_type == PartitionTableType::Guid {
|
||||
"\\EFI\\Microsoft"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
String::from("/enum"),
|
||||
],
|
||||
));
|
||||
self.task_groups
|
||||
.push_back(Some(format!("{}", diags::Type::BootConfigData)));
|
||||
}
|
||||
|
||||
// Bitlocker
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\manage-bde.exe", &self.system32)),
|
||||
vec![String::from("-status"), format!("{letter_os}:")],
|
||||
));
|
||||
self.task_groups.push_back(None);
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\manage-bde.exe", &self.system32)),
|
||||
vec![
|
||||
String::from("-protectors"),
|
||||
String::from("-get"),
|
||||
format!("{letter_os}:"),
|
||||
],
|
||||
));
|
||||
self.task_groups
|
||||
.push_back(Some(format!("{}", diags::Type::Bitlocker)));
|
||||
|
||||
// Filesystem Health
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\chkdsk.exe", &self.system32)),
|
||||
vec![format!("{letter_os}:")],
|
||||
));
|
||||
self.task_groups.push_back(None);
|
||||
|
||||
// Files/Folders
|
||||
let paths: Vec<PathBuf> = [
|
||||
"Users",
|
||||
"Program Files",
|
||||
"Program Files (x86)",
|
||||
"ProgramData",
|
||||
"Windows\\System32\\config",
|
||||
]
|
||||
.iter()
|
||||
.map(|s| PathBuf::from(format!("{letter_os}:\\{s}")))
|
||||
.collect();
|
||||
self.tasks.add(TaskType::TestPaths(paths));
|
||||
self.task_groups
|
||||
.push_back(Some(format!("{}", diags::Type::FileSystem)));
|
||||
|
||||
// DISM Health
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\dism.exe", &self.system32)),
|
||||
vec![format!("{letter_os}:")],
|
||||
));
|
||||
self.task_groups
|
||||
.push_back(Some(format!("{}", diags::Type::System)));
|
||||
|
||||
// Registry
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||
vec![
|
||||
String::from("load"),
|
||||
String::from("HKLM\\TmpSoftware"),
|
||||
format!("{letter_os}:\\Windows\\System32\\config\\SOFTWARE"),
|
||||
],
|
||||
));
|
||||
self.task_groups.push_back(None);
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||
vec![
|
||||
String::from("load"),
|
||||
String::from("HKLM\\TmpSystem"),
|
||||
format!("{letter_os}:\\Windows\\System32\\config\\SYSTEM"),
|
||||
],
|
||||
));
|
||||
self.task_groups.push_back(None);
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||
vec![
|
||||
String::from("load"),
|
||||
String::from("HKU\\TmpDefault"),
|
||||
format!("{letter_os}:\\Windows\\System32\\config\\DEFAULT"),
|
||||
],
|
||||
));
|
||||
self.task_groups.push_back(None);
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||
vec![String::from("unload"), String::from("HKLM\\TmpSoftware")],
|
||||
));
|
||||
self.task_groups.push_back(None);
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||
vec![String::from("unload"), String::from("HKLM\\TmpSystem")],
|
||||
));
|
||||
self.task_groups.push_back(None);
|
||||
self.tasks.add(TaskType::CommandWait(
|
||||
PathBuf::from(format!("{}\\reg.exe", &self.system32)),
|
||||
vec![String::from("unload"), String::from("HKU\\TmpDefault")],
|
||||
));
|
||||
self.task_groups
|
||||
.push_back(Some(format!("{}", diags::Type::Registry)));
|
||||
self.tasks.add(TaskType::Sleep); // NOTE: DELETEME
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
||||
tui.draw(|frame| {
|
||||
if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] {
|
||||
if let [header, _body, footer, left, right, popup, progress] =
|
||||
get_chunks(frame.area())[..]
|
||||
{
|
||||
let component_areas = vec![
|
||||
header, // Title Bar
|
||||
header, // FPS Counter
|
||||
left, right, footer, popup,
|
||||
left, right, footer, popup, // core
|
||||
progress, header, // boot-diags
|
||||
];
|
||||
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
||||
if let Err(err) = component.draw(frame, area) {
|
||||
|
|
@ -710,6 +809,9 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
|||
// Popup
|
||||
chunks.push(centered_rect(60, 25, r));
|
||||
|
||||
// Progress
|
||||
chunks.push(centered_rect(60, 80, chunks[1]));
|
||||
|
||||
// Done
|
||||
chunks
|
||||
}
|
||||
|
|
@ -849,12 +951,7 @@ fn build_right_items(app: &App) -> Action {
|
|||
let mut labels: Vec<Vec<DVLine>> = Vec::new();
|
||||
let mut start_index = 0;
|
||||
// TODO: DELETE THIS SECTION
|
||||
start_index = 1;
|
||||
items.push(vec![
|
||||
DVLine {
|
||||
line_parts: vec![format!("Mode: {:?}", app.cur_mode)],
|
||||
line_colors: vec![Color::Reset],
|
||||
},
|
||||
DVLine {
|
||||
line_parts: vec![format!(
|
||||
"Parts: {:?} // {:?}",
|
||||
|
|
@ -875,6 +972,7 @@ fn build_right_items(app: &App) -> Action {
|
|||
line_colors: vec![Color::Reset],
|
||||
},
|
||||
]);
|
||||
start_index += 1;
|
||||
// TODO: DELETE THIS SECTION
|
||||
match app.cur_mode {
|
||||
Mode::DiagMenu => {
|
||||
|
|
@ -904,10 +1002,8 @@ fn build_right_items(app: &App) -> Action {
|
|||
|
||||
// Add header
|
||||
if !header_lines.is_empty() {
|
||||
items[0].append(&mut header_lines);
|
||||
// TODO: Replace line above with lines below
|
||||
// items.push(header_lines);
|
||||
// start_index = 1;
|
||||
items.push(header_lines);
|
||||
start_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -952,7 +1048,7 @@ fn build_right_items(app: &App) -> Action {
|
|||
line_parts: vec![get_cpu_name()],
|
||||
line_colors: vec![Color::Reset],
|
||||
}]);
|
||||
start_index = 2;
|
||||
start_index += 2;
|
||||
}
|
||||
Mode::SelectDisks => {
|
||||
let dest_dv_line = DVLine {
|
||||
|
|
@ -973,7 +1069,7 @@ fn build_right_items(app: &App) -> Action {
|
|||
}])
|
||||
});
|
||||
if let Some(index) = app.clone.disk_index_dest {
|
||||
start_index = 1;
|
||||
start_index += 1;
|
||||
let disk_list = app.clone.disk_list.lock().unwrap();
|
||||
if let Some(disk) = disk_list.get(index) {
|
||||
// Disk Details
|
||||
|
|
|
|||
2
boot_diags/src/components.rs
Normal file
2
boot_diags/src/components.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod progress;
|
||||
pub mod results;
|
||||
142
boot_diags/src/components/progress.rs
Normal file
142
boot_diags/src/components/progress.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
// 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,
|
||||
style::{Color, Style, Stylize},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Clear, Paragraph},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tracing::info;
|
||||
|
||||
use core::{
|
||||
action::{Action, DiagResult},
|
||||
components::Component,
|
||||
config::Config,
|
||||
state::Mode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ProgressLine {
|
||||
name: String,
|
||||
text: String,
|
||||
result: DiagResult,
|
||||
}
|
||||
|
||||
impl ProgressLine {
|
||||
pub fn len_name(&self) -> usize {
|
||||
self.name.chars().count()
|
||||
}
|
||||
|
||||
pub fn len_text(&self) -> usize {
|
||||
self.text.chars().count()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Progress {
|
||||
command_tx: Option<UnboundedSender<Action>>,
|
||||
config: Config,
|
||||
lines: Vec<ProgressLine>,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl Progress {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
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 Progress {
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::DiagStartLine { text } => {
|
||||
info!("Caught Action::DiagStartLine");
|
||||
self.lines.push(ProgressLine {
|
||||
name: text,
|
||||
text: String::new(),
|
||||
result: DiagResult::Pass,
|
||||
});
|
||||
}
|
||||
Action::DiagEndLine { result, text } => {
|
||||
info!("Caught Action::DiagEndLine");
|
||||
if let Some(line) = self.lines.last_mut() {
|
||||
line.result = result;
|
||||
line.text = text;
|
||||
}
|
||||
}
|
||||
Action::SetMode(mode) => self.mode = mode,
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
if self.mode != Mode::BootScan {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut body_text = Vec::with_capacity(20); // TODO: Needs fine tuning?
|
||||
body_text.push(Line::default());
|
||||
|
||||
// Add line(s)
|
||||
self.lines.iter().for_each(|line| {
|
||||
//.for_each(|(part, color)| spans.push(Span::styled(part, Style::default().fg(color))));
|
||||
//Line::from(spans)
|
||||
let color = match line.result {
|
||||
DiagResult::Pass => Color::Green,
|
||||
DiagResult::Fail => Color::Red,
|
||||
DiagResult::Warn => Color::Yellow,
|
||||
};
|
||||
let text = if line.text.is_empty() {
|
||||
String::from("...")
|
||||
} else {
|
||||
line.text.clone()
|
||||
};
|
||||
let text = format!(" [ {text} ] ");
|
||||
let width = area.width as usize - line.len_name() - 1 - text.chars().count() - 7; // ' [ ]'
|
||||
let spans = vec![
|
||||
Span::raw(format!("{} {:.<width$}", &line.name, "")),
|
||||
Span::styled(text, Style::default().fg(color)),
|
||||
];
|
||||
body_text.push(Line::from(spans).centered());
|
||||
body_text.push(Line::default());
|
||||
body_text.push(Line::default());
|
||||
});
|
||||
|
||||
// Build block
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().bold());
|
||||
let body = Paragraph::new(body_text).block(block);
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_widget(body, area);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
53
boot_diags/src/components/results.rs
Normal file
53
boot_diags/src/components/results.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// 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};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use core::{action::Action, components::Component, config::Config};
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Results {
|
||||
command_tx: Option<UnboundedSender<Action>>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Results {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
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>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -13,17 +13,30 @@
|
|||
// 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 std::collections::HashMap;
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
pub enum Type {
|
||||
Bitlocker,
|
||||
BootConfigData,
|
||||
FileSystem,
|
||||
FilesAndFolders,
|
||||
Registry,
|
||||
System,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Type::Bitlocker => write!(f, "Bitlocker"),
|
||||
Type::BootConfigData => write!(f, "Boot Files"),
|
||||
Type::FileSystem => write!(f, "Filesystem"),
|
||||
Type::Registry => write!(f, "Registry"),
|
||||
Type::System => write!(f, "System Files"),
|
||||
Type::Unknown => write!(f, "Unknown Type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Groups {
|
||||
items: HashMap<String, Line>,
|
||||
|
|
@ -38,6 +51,10 @@ impl Groups {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, name: &str) -> bool {
|
||||
self.items.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Vec<&Line> {
|
||||
let mut lines = Vec::new();
|
||||
self.order.iter().for_each(|key| {
|
||||
|
|
@ -48,17 +65,22 @@ impl Groups {
|
|||
lines
|
||||
}
|
||||
|
||||
pub fn update(&mut self, title: String, passed: bool, info: String) {
|
||||
pub fn update(&mut self, title: String, passed: Option<bool>, info: Option<String>) {
|
||||
if let Some(line) = self.items.get_mut(&title) {
|
||||
line.update(passed, info);
|
||||
} else {
|
||||
let info_list = if info.is_some() {
|
||||
vec![info.unwrap()]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
self.order.push(title.clone());
|
||||
self.items.insert(
|
||||
title.clone(),
|
||||
Line {
|
||||
title,
|
||||
passed,
|
||||
info: vec![info],
|
||||
passed: passed.unwrap_or(false),
|
||||
info: info_list,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -73,29 +95,33 @@ pub struct Line {
|
|||
}
|
||||
|
||||
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);
|
||||
pub fn update(&mut self, passed: Option<bool>, info: Option<String>) {
|
||||
if let Some(result) = passed {
|
||||
self.passed &= result; // We fail if any tests in this group fail
|
||||
}
|
||||
if let Some(info_str) = info {
|
||||
self.info.push(String::from(info_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(cmd_name: &str) -> Type {
|
||||
if cmd_name == "exa" {
|
||||
if cmd_name.ends_with("exa") {
|
||||
return Type::BootConfigData;
|
||||
}
|
||||
if cmd_name == "bcdedit.exe" {
|
||||
if cmd_name.ends_with("bcdedit.exe") {
|
||||
return Type::BootConfigData;
|
||||
}
|
||||
if cmd_name == "dir" {
|
||||
return Type::FilesAndFolders;
|
||||
if cmd_name.ends_with("dism.exe") {
|
||||
return Type::System;
|
||||
}
|
||||
if cmd_name == "reg.exe" {
|
||||
if cmd_name.ends_with("reg.exe") {
|
||||
return Type::Registry;
|
||||
}
|
||||
if cmd_name == "chkdsk.exe" {
|
||||
if cmd_name.ends_with("chkdsk.exe") {
|
||||
return Type::FileSystem;
|
||||
}
|
||||
if cmd_name == "manage-bde.exe" {
|
||||
if cmd_name.ends_with("manage-bde.exe") {
|
||||
return Type::Bitlocker;
|
||||
}
|
||||
Type::Unknown
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use color_eyre::Result;
|
|||
use crate::app::App;
|
||||
|
||||
mod app;
|
||||
mod components;
|
||||
mod diags;
|
||||
|
||||
#[tokio::main]
|
||||
|
|
|
|||
|
|
@ -21,12 +21,22 @@ use crate::{
|
|||
line::DVLine,
|
||||
state::Mode,
|
||||
system::disk::Disk,
|
||||
tasks::TaskType,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||
pub enum DiagResult {
|
||||
Pass,
|
||||
Fail,
|
||||
Warn,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||
pub enum Action {
|
||||
// App (Boot-Diags)
|
||||
BootScan,
|
||||
DiagStartLine { text: String },
|
||||
DiagEndLine { result: DiagResult, text: String },
|
||||
// App (Clone)
|
||||
Highlight(usize),
|
||||
InstallDriver,
|
||||
|
|
@ -34,6 +44,7 @@ pub enum Action {
|
|||
ScanDisks,
|
||||
Select(Option<usize>, Option<usize>), // indicies for (source, dest) etc
|
||||
SelectRight(Option<usize>, Option<usize>), // indicies for right info pane
|
||||
TaskStart(TaskType),
|
||||
TasksComplete,
|
||||
UpdateDiskList(Vec<Disk>),
|
||||
UpdateFooter(String),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use std::{
|
|||
};
|
||||
|
||||
use color_eyre::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::info;
|
||||
|
||||
|
|
@ -41,13 +42,14 @@ pub enum TaskResult {
|
|||
Output(String, String, bool), // stdout, stderr, success
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TaskType {
|
||||
CommandNoWait(PathBuf, Vec<String>), // (command, args)
|
||||
CommandWait(PathBuf, Vec<String>), // (command, args)
|
||||
Diskpart(String), // (script_as_string)
|
||||
ScanDisks,
|
||||
Sleep,
|
||||
TestPaths(Vec<PathBuf>),
|
||||
UpdateDestDisk(usize), // (disk_index)
|
||||
UpdateDiskList,
|
||||
}
|
||||
|
|
@ -67,6 +69,7 @@ impl fmt::Display for TaskType {
|
|||
}
|
||||
TaskType::ScanDisks => write!(f, "ScanDisks"),
|
||||
TaskType::Sleep => write!(f, "Sleep"),
|
||||
TaskType::TestPaths(_) => write!(f, "TestPaths"),
|
||||
TaskType::UpdateDestDisk(_) => write!(f, "UpdateDestDisk"),
|
||||
TaskType::UpdateDiskList => write!(f, "UpdateDiskList"),
|
||||
}
|
||||
|
|
@ -170,6 +173,8 @@ impl Tasks {
|
|||
self.cur_task = self.task_list.pop_front();
|
||||
if let Some(task) = self.cur_task.take() {
|
||||
let task_tx = self.task_tx.clone();
|
||||
self.action_tx
|
||||
.send(Action::TaskStart(task.task_type.clone()))?;
|
||||
match task.task_type {
|
||||
TaskType::CommandNoWait(ref cmd_path, ref cmd_args) => {
|
||||
self.cur_handle = None;
|
||||
|
|
@ -199,6 +204,9 @@ impl Tasks {
|
|||
TaskType::Sleep => {
|
||||
self.cur_handle = Some(thread::spawn(|| sleep(Duration::from_millis(250))));
|
||||
}
|
||||
TaskType::TestPaths(ref list) => {
|
||||
self.cur_handle = Some(test_paths(list.clone(), task_tx.clone()));
|
||||
}
|
||||
TaskType::UpdateDestDisk(index) => {
|
||||
self.action_tx.send(Action::DisplayPopup(
|
||||
popup::Type::Info,
|
||||
|
|
@ -272,7 +280,7 @@ fn run_task_command(
|
|||
})
|
||||
} else {
|
||||
// Simulate task if not running under Windows
|
||||
thread::spawn(|| sleep(Duration::from_millis(250)))
|
||||
thread::spawn(|| sleep(Duration::from_millis(500)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -294,3 +302,34 @@ fn run_task_diskpart(script: &str, task_tx: mpsc::UnboundedSender<TaskResult>) -
|
|||
thread::spawn(|| sleep(Duration::from_millis(250)))
|
||||
}
|
||||
}
|
||||
|
||||
fn test_paths(
|
||||
path_list: Vec<PathBuf>,
|
||||
task_tx: mpsc::UnboundedSender<TaskResult>,
|
||||
) -> JoinHandle<()> {
|
||||
thread::spawn(move || {
|
||||
let mut missing_paths = Vec::new();
|
||||
let task_result: TaskResult;
|
||||
path_list.iter().for_each(|path| {
|
||||
if !path.exists() {
|
||||
missing_paths.push(String::from(path.to_string_lossy()));
|
||||
}
|
||||
});
|
||||
|
||||
if missing_paths.is_empty() {
|
||||
// No missing paths
|
||||
task_result = TaskResult::Output(String::from("OK"), String::new(), true);
|
||||
} else {
|
||||
task_result = TaskResult::Output(
|
||||
String::from("Missing item(s)"),
|
||||
missing_paths.join(",\n"),
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
let err_str = format!("Failed to send TaskResult: {:?}", &task_result);
|
||||
task_tx
|
||||
.send(task_result)
|
||||
.unwrap_or_else(|_| panic!("{}", err_str));
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue