diff --git a/boot_diags/src/app.rs b/boot_diags/src/app.rs index 2272e52..a526732 100644 --- a/boot_diags/src/app.rs +++ b/boot_diags/src/app.rs @@ -38,8 +38,6 @@ use core::{ tui::{Event, Tui}, }; use std::{ - array, - collections::HashMap, env, iter::zip, path::PathBuf, @@ -83,17 +81,8 @@ pub struct App { impl App { pub fn new(tick_rate: f64, frame_rate: f64) -> Result { 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 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 = 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 mut list = StatefulList::default(); list.set_items(vec![ @@ -114,8 +103,8 @@ impl App { Box::new(Footer::new()), Box::new(popup::Popup::new()), Box::new(crate::components::progress::Progress::new()), - Box::new(crate::components::results::Results::new( - results_arc.clone(), + Box::new(crate::components::logview::LogView::new( + diag_groups_arc.clone(), )), ], config: Config::new()?, @@ -127,7 +116,7 @@ impl App { // App clone: CloneSettings::new(disk_list_arc), cur_mode: Mode::Home, - diag_groups: Arc::new(Mutex::new(Vec::new())), + diag_groups: diag_groups_arc, list, boot_modes: vec![SafeMode::Enable, SafeMode::Disable], system32: String::new(), @@ -356,6 +345,9 @@ impl App { self.action_tx.send(Action::SetMode(next_mode))?; } Action::Process => match self.cur_mode { + Mode::BootDiags => { + self.action_tx.send(Action::SetMode(Mode::LogView))?; + } Mode::DiagMenu => { // Use highlighted entry if let Some(new_mode) = self.list.get_selected() { @@ -365,7 +357,7 @@ impl App { Mode::Done => { self.action_tx.send(Action::NextScreen)?; } - Mode::BootDiags | Mode::BootSetup => { + Mode::BootSetup => { //let new_mode = self.next_mode(); //self.action_tx.send(Action::SetMode(new_mode))?; } @@ -736,7 +728,7 @@ fn get_chunks(r: Rect) -> Vec { fn build_footer_string(cur_mode: Mode) -> String { match cur_mode { - Mode::BootDiags => { + 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 => { @@ -825,7 +817,7 @@ fn build_left_items(app: &App) -> Action { } } } - Mode::BootDiags => { + Mode::BootDiags | Mode::LogView => { select_type = SelectionType::Loop; let (new_title, _) = get_mode_strings(app.cur_mode); title = new_title; @@ -1030,7 +1022,7 @@ fn build_right_items(app: &App) -> Action { fn get_mode_strings(mode: Mode) -> (String, String) { match mode { - Mode::BootScan | Mode::BootDiags => ( + Mode::BootScan | Mode::BootDiags | Mode::LogView => ( String::from("Boot Diagnostics"), String::from("Check for common Windows boot issues"), ), diff --git a/boot_diags/src/components.rs b/boot_diags/src/components.rs index ef613dc..6454104 100644 --- a/boot_diags/src/components.rs +++ b/boot_diags/src/components.rs @@ -1,2 +1,2 @@ +pub mod logview; pub mod progress; -pub mod results; diff --git a/boot_diags/src/components/logview.rs b/boot_diags/src/components/logview.rs new file mode 100644 index 0000000..58b11a0 --- /dev/null +++ b/boot_diags/src/components/logview.rs @@ -0,0 +1,125 @@ +// 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 . +// +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>, + config: Config, + line_index: u16, + list: StatefulList, + mode: Mode, + diag_groups: Arc>>, +} + +impl LogView { + #[must_use] + pub fn new(diag_groups: Arc>>) -> Self { + LogView { + diag_groups, + ..Default::default() + } + } +} + +impl Component for LogView { + fn register_action_handler(&mut self, tx: UnboundedSender) -> 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> { + 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(()) + } +} diff --git a/boot_diags/src/components/progress.rs b/boot_diags/src/components/progress.rs index e1817fb..3ab25ea 100644 --- a/boot_diags/src/components/progress.rs +++ b/boot_diags/src/components/progress.rs @@ -123,7 +123,7 @@ impl Component for Progress { DiagResult::Warn => Color::Yellow, }; let text = if line.running || line.text.is_empty() { - String::from("...") + String::from("..") } else { line.text.clone() }; diff --git a/boot_diags/src/components/results.rs b/boot_diags/src/components/results.rs deleted file mode 100644 index 22b0f9a..0000000 --- a/boot_diags/src/components/results.rs +++ /dev/null @@ -1,136 +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 . -// -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>, - // config: Config, - key_index: usize, - line_index: u16, - results: Arc>>, - show: bool, -} - -impl Results { - #[must_use] - pub fn new(results: Arc>>) -> Self { - Results { - key_index: 0, - line_index: 0, - results, - show: false, - } - } - - // fn register_action_handler(&mut self, tx: UnboundedSender) -> 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> { - 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(()) - } -} diff --git a/config/config.json5 b/config/config.json5 index 906ecb5..d54ebb2 100644 --- a/config/config.json5 +++ b/config/config.json5 @@ -109,8 +109,6 @@ "": "Process", "": "KeyUp", "": "KeyDown", - "": "KeyLeft", - "": "KeyRight", "": "BootScan", "": "ScanDisks", "": "Quit", @@ -133,6 +131,15 @@ "": "Quit", "": "Suspend" }, + "LogView": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, "InjectDrivers": { "": "Process", "": "KeyUp", diff --git a/core/src/components/left.rs b/core/src/components/left.rs index d891947..2492847 100644 --- a/core/src/components/left.rs +++ b/core/src/components/left.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::UnboundedSender; use super::{Component, state::StatefulList}; -use crate::{action::Action, config::Config}; +use crate::{action::Action, config::Config, state::Mode}; #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub enum SelectionType { @@ -39,6 +39,7 @@ pub struct Left { config: Config, labels: Vec, list: StatefulList, + mode: Mode, select_type: SelectionType, selections: Vec>, selections_saved: Vec>, @@ -82,9 +83,21 @@ impl Component for Left { fn update(&mut self, action: Action) -> Result> { match action { Action::Highlight(index) => self.set_highlight(index), - Action::KeyUp => self.list.previous(), - Action::KeyDown => self.list.next(), + Action::KeyUp => { + if self.mode != Mode::LogView { + self.list.previous(); + } + } + Action::KeyDown => { + if self.mode != Mode::LogView { + self.list.next(); + } + } Action::Process => { + if self.mode == Mode::LogView { + // Avoid updating selections/etc while log is open + return Ok(None); + } if self.select_type == SelectionType::Loop { // Selections aren't being used so this is a no-op } else if let Some(command_tx) = self.command_tx.clone() { @@ -123,7 +136,8 @@ impl Component for Left { self.selections_saved[0] = one; self.selections_saved[1] = two; } - Action::SetMode(_) => { + Action::SetMode(new_mode) => { + self.mode = new_mode; self.selections[0] = None; self.selections[1] = None; } diff --git a/core/src/components/right.rs b/core/src/components/right.rs index e36d3bc..68ab8c0 100644 --- a/core/src/components/right.rs +++ b/core/src/components/right.rs @@ -22,7 +22,7 @@ use ratatui::{ use tokio::sync::mpsc::UnboundedSender; use super::{Component, state::StatefulList}; -use crate::{action::Action, config::Config, line::DVLine}; +use crate::{action::Action, config::Config, line::DVLine, state::Mode}; #[derive(Default)] pub struct Right { @@ -31,6 +31,7 @@ pub struct Right { list_header: Vec, list_labels: Vec>, list: StatefulList>, + mode: Mode, selections: Vec>, selections_saved: Vec>, title: String, @@ -93,8 +94,16 @@ impl Component for Right { fn update(&mut self, action: Action) -> Result> { match action { Action::Highlight(index) => self.set_highlight(index), - Action::KeyUp => self.list.previous(), - Action::KeyDown => self.list.next(), + Action::KeyUp => { + if self.mode != Mode::LogView { + self.list.previous(); + } + } + Action::KeyDown => { + if self.mode != Mode::LogView { + self.list.next(); + } + } Action::Select(one, two) => { self.selections[0] = one; self.selections[1] = two; @@ -109,7 +118,8 @@ impl Component for Right { self.selections_saved[0] = one; self.selections_saved[1] = two; } - Action::SetMode(_) => { + Action::SetMode(new_mode) => { + self.mode = new_mode; self.selections[0] = None; self.selections[1] = None; self.selections_saved[0] = None; diff --git a/core/src/state.rs b/core/src/state.rs index 8c8eb8d..d304575 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -35,6 +35,7 @@ pub enum Mode { BootDiags, BootScan, BootSetup, + LogView, InjectDrivers, SetBootMode, // Clone diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 4a4f595..5055e87 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -126,6 +126,7 @@ impl App { | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers + | Mode::LogView | Mode::PEMenu | Mode::SetBootMode => panic!("This shouldn't happen?"), } @@ -149,6 +150,7 @@ impl App { | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers + | Mode::LogView | Mode::PEMenu | Mode::SetBootMode => panic!("This shouldn't happen?"), }; @@ -627,6 +629,7 @@ fn build_footer_string(cur_mode: Mode) -> String { | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers + | Mode::LogView | Mode::PEMenu | Mode::SetBootMode => panic!("This shouldn't happen?"), } @@ -698,6 +701,7 @@ fn build_left_items(app: &App, cur_mode: Mode) -> Action { | Mode::BootSetup | Mode::DiagMenu | Mode::InjectDrivers + | Mode::LogView | Mode::PEMenu | Mode::SetBootMode => panic!("This shouldn't happen?"), };