diff --git a/Cargo.lock b/Cargo.lock index eacfe13..66ec6e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1890,6 +1890,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ + "indexmap 2.13.0", "itoa", "memchr", "serde", diff --git a/Cargo.toml b/Cargo.toml index 04ac9e9..a47c858 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ jiff = { version = "0.2", features = ["tzdb-bundle-always"] } parking_lot = { version = "0.12" } ratatui = "0.30" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde_json = { version = "1.0"} serde_jsonc = "1.0" tokio = { version = "1.49", features = ["full"] } tokio-util = "0.7" @@ -47,6 +47,7 @@ uuid = { version = "1.20", features = ["fast-rng", "v4"] } [dev-dependencies] insta = "1.46" +serde_json = { version = "1.0", features = ["preserve_order"]} [profile.release] lto = true diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index 2ea607e..187ad5c 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -104,10 +104,10 @@ "k" ], // Horizontal scroll of the logs - "log_scroll_forward": [ + "scroll_forward": [ "right" ], - "log_scroll_back": [ + "scroll_back": [ "left" ], // Select next panel @@ -170,6 +170,10 @@ "log_section_toggle": [ "\\" ], + // Toggle to inspect container screen + "inspect": [ + "i" + ], // Force a complete clear & redraw of the screen "force_redraw": [ "f" diff --git a/example_config/example.config.toml b/example_config/example.config.toml index 8547375..bddc3da 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -95,8 +95,8 @@ scroll_start = ["home"] # scroll up a list by one item scroll_up = ["up", "k"] # Horizontal scroll of the logs -log_scroll_forward = ["right"] -log_scroll_back = ["left"] +scroll_forward = ["right"] +scroll_back = ["left"] # Select next panel select_next_panel = ["tab"] # Select previous panel @@ -122,6 +122,8 @@ log_section_height_decrease = ["-"] log_section_height_increase = ["+"] # Toggle visibility of the log section log_section_toggle = ["\\"] +# Toggle to inspect container screen +inspect = ["i"] diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 4924102..547c578 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -24,8 +24,12 @@ const ONE_GB: f64 = ONE_MB * 1000.0; #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub enum ScrollDirection { - Next, - Previous, + // Next, + // Previous, + Up, + Down, + Left, + Right, } #[derive(Debug, Clone, Eq, Hash, PartialEq)] @@ -185,8 +189,10 @@ impl StatefulList { pub fn scroll(&mut self, scroll: &ScrollDirection) { match scroll { - ScrollDirection::Next => self.next(), - ScrollDirection::Previous => self.previous(), + ScrollDirection::Down => self.next(), + ScrollDirection::Up => self.previous(), + // TODO set offset + _ => (), } } @@ -615,7 +621,8 @@ pub struct Logs { tz: HashSet, search_results: Vec, search_term: Option, - offset: u16, + offset: usize, + max_offset: usize, max_log_len: usize, adjusted_max_width: usize, adjust_max_width_text_len: usize, @@ -629,6 +636,7 @@ impl Default for Logs { lines, tz: HashSet::new(), offset: 0, + max_offset: 0, search_term: None, search_results: vec![], adjusted_max_width: 0, @@ -687,8 +695,10 @@ impl Logs { .position(|i| i == ¤t_selected) { if let Some(new_index) = match sd { - ScrollDirection::Next => current_position.checked_add(1), - ScrollDirection::Previous => current_position.checked_sub(1), + ScrollDirection::Down => current_position.checked_add(1), + ScrollDirection::Up => current_position.checked_sub(1), + // TODO set offset + _ => None, } && let Some(f) = self.search_results.get(new_index) { self.lines.state.select(Some(*f)); @@ -696,13 +706,15 @@ impl Logs { } } else { let range = match sd { - ScrollDirection::Previous => (0..=current_selected).rev().collect::>(), - ScrollDirection::Next => (current_selected + ScrollDirection::Up => (0..=current_selected).rev().collect::>(), + ScrollDirection::Down => (current_selected ..=self .search_results .last() .map_or_else(|| current_selected, |i| *i)) .collect::>(), + // TODO set offset + _ => vec![], }; for i in range { if self.search_results.contains(&i) { @@ -820,7 +832,7 @@ impl Logs { if self.horizontal_scroll_able(width) { let text_width = self.adjust_max_width_text_len; let arrow_left = if self.offset > 0 { " โ†" } else { " " }; - let arrow_right = if usize::from(self.offset) < self.adjusted_max_width { + let arrow_right = if self.offset < self.adjusted_max_width { "โ†’ " } else { " " @@ -883,10 +895,10 @@ impl Logs { pub fn get_visible_logs(&self, size: Size, padding: usize) -> Vec> { let current_index = self.lines.state.selected().unwrap_or_default(); let height_padding = usize::from(size.height) + padding; - let char_offset = if usize::from(self.offset) > self.max_log_len { + let char_offset = if self.offset > self.max_log_len { self.max_log_len } else { - self.offset.into() + self.offset }; self.lines @@ -920,10 +932,10 @@ impl Logs { /// Add a padding so one char will always be visilbe? pub fn forward(&mut self, width: u16) { - let offset = usize::from(self.offset); + // Need to set a max_offset, instead of using a width each time if self.horizontal_scroll_able(width) && self.adjusted_max_width > 0 - && offset < self.adjusted_max_width + && self.offset < self.adjusted_max_width { self.offset = self.offset.saturating_add(1); } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index f4f1cdc..915a6fd 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -1,4 +1,4 @@ -use bollard::models::ContainerSummary; +use bollard::{models::ContainerSummary, secret::ContainerInspectResponse}; use core::fmt; use parking_lot::Mutex; use ratatui::{layout::Size, text::Text, widgets::ListState}; @@ -114,6 +114,45 @@ impl Filter { } } +#[derive(Debug, Clone)] +pub struct InspectData { + pub width: usize, + pub height: usize, + pub as_string: String, + pub name: String, + pub id: ContainerId, // pub as_lines: Vec>, +} + +impl From for InspectData { + fn from(input: ContainerInspectResponse) -> Self { + let as_string = serde_json::to_string_pretty(&input) + .unwrap_or_default() + .lines() + .skip(1) + .collect::>() + .split_last() + .map(|(_, data)| data) + .unwrap_or_default() + .join("\n"); + + let height = as_string.lines().count(); + + let mut width = 0; + for i in as_string.lines() { + width = width.max(i.chars().count()); + } + + Self { + name: input.name.unwrap_or_default(), + // TODO maybe make this an Option? + id: ContainerId::from(input.id.unwrap_or_default().as_str()), + width, + height, + as_string, + } + } +} + /// Global app_state, stored in an Arc #[derive(Debug, Clone)] #[cfg(not(test))] @@ -122,6 +161,7 @@ pub struct AppData { error: Option, filter: Filter, hidden_containers: Vec, + inspect_data: Option, rerender: Arc, sorted_by: Option<(Header, SortedOrder)>, current_sorted_id: Vec, @@ -136,6 +176,7 @@ pub struct AppData { pub error: Option, pub filter: Filter, pub hidden_containers: Vec, + pub inspect_data: Option, pub current_sorted_id: Vec, pub rerender: Arc, pub sorted_by: Option<(Header, SortedOrder)>, @@ -151,6 +192,7 @@ impl AppData { error: None, filter: Filter::new(), hidden_containers: vec![], + inspect_data: None, rerender: Arc::clone(redraw), sorted_by: None, } @@ -165,6 +207,18 @@ impl AppData { .as_secs() } + pub fn clear_inspect_data(&mut self) { + self.inspect_data = None; + } + + pub fn set_inspect_data(&mut self, data: ContainerInspectResponse) { + self.inspect_data = Some(InspectData::from(data)) + // self.inspect_data = Some(data) + } + + pub fn get_inspect_data(&self) -> Option { + self.inspect_data.clone() + } /// Filter related methods /// Get the filterby and filter_term pub const fn get_filter(&self) -> (FilterBy, Option<&String>) { @@ -329,6 +383,7 @@ impl AppData { .iter() .position(|i| self.get_selected_container_id().as_ref() == Some(&i.id)), ); + self.rerender.update_draw(); } /// Remove the sorted header & order, and sort by default - created datetime @@ -667,19 +722,22 @@ impl AppData { } pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) { + // Change this to set a max_offset, instead of taking in width each time, then can be combined with the log_scroll beneath match sd { - ScrollDirection::Next => { + ScrollDirection::Down => { if let Some(i) = self.get_mut_selected_container() { i.logs.forward(width); self.rerender.update_draw(); } } - ScrollDirection::Previous => { + ScrollDirection::Up => { if let Some(i) = self.get_mut_selected_container() { i.logs.back(); self.rerender.update_draw(); } } + // TODO set offset + _ => (), } } @@ -687,8 +745,10 @@ impl AppData { pub fn log_scroll(&mut self, scroll: &ScrollDirection) { if let Some(i) = self.get_mut_selected_container() { match scroll { - ScrollDirection::Next => i.logs.next(), - ScrollDirection::Previous => i.logs.previous(), + ScrollDirection::Down => i.logs.next(), + ScrollDirection::Up => i.logs.previous(), + // TODO set offset + _ => (), } self.rerender.update_draw(); } @@ -876,7 +936,7 @@ impl AppData { // If removed container is currently selected, then change selected to previous // This will default to 0 in any edge cases if self.containers.state.selected().is_some() { - self.containers.scroll(&ScrollDirection::Previous); + self.containers.scroll(&ScrollDirection::Up); } // Check is some, else can cause out of bounds error, if containers get removed before a docker update if self.containers.items.get(index).is_some() { @@ -1443,7 +1503,7 @@ mod tests { ); // Calling previous when at start has no effect - app_data.containers_scroll(&ScrollDirection::Previous); + app_data.containers_scroll(&ScrollDirection::Up); let result = app_data.get_selected_container_id(); assert_eq!(result, Some(ContainerId::from("1"))); let result = app_data.get_selected_container_id_state_name(); @@ -1466,7 +1526,7 @@ mod tests { // Advance list state by 1 app_data.containers_start(); - app_data.containers.scroll(&ScrollDirection::Next); + app_data.containers.scroll(&ScrollDirection::Down); let result = app_data.get_container_state(); assert_eq!(result.selected(), Some(1)); @@ -1510,7 +1570,7 @@ mod tests { ); // Calling previous when at end has no effect - app_data.containers.scroll(&ScrollDirection::Next); + app_data.containers.scroll(&ScrollDirection::Down); let result = app_data.get_selected_container_id(); assert_eq!(result, Some(ContainerId::from("3"))); let result = app_data.get_selected_container_id_state_name(); @@ -1531,7 +1591,7 @@ mod tests { let mut app_data = gen_appdata(&containers); app_data.containers_end(); - app_data.containers.scroll(&ScrollDirection::Previous); + app_data.containers.scroll(&ScrollDirection::Up); let result = app_data.get_container_state(); assert_eq!(result.selected(), Some(1)); assert_eq!(result.offset(), 0); @@ -1547,7 +1607,7 @@ mod tests { assert_eq!(result, None); app_data.containers.start(); - app_data.containers.scroll(&ScrollDirection::Next); + app_data.containers.scroll(&ScrollDirection::Down); let result = app_data.get_selected_container(); assert_eq!(result, Some(&containers[1])); @@ -1634,7 +1694,7 @@ mod tests { let mut app_data = gen_appdata(&containers); app_data.containers_start(); app_data.docker_controls_start(); - app_data.docker_controls_scroll(&ScrollDirection::Next); + app_data.docker_controls_scroll(&ScrollDirection::Down); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Restart)); @@ -1652,7 +1712,7 @@ mod tests { assert_eq!(result, Some(DockerCommand::Delete)); // Next has no effect when at end - app_data.docker_controls_scroll(&ScrollDirection::Next); + app_data.docker_controls_scroll(&ScrollDirection::Down); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Delete)); } @@ -1664,14 +1724,14 @@ mod tests { let mut app_data = gen_appdata(&containers); app_data.containers_start(); app_data.docker_controls_end(); - app_data.docker_controls_scroll(&ScrollDirection::Previous); + app_data.docker_controls_scroll(&ScrollDirection::Up); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Stop)); // previous has no effect when at start app_data.docker_controls_start(); - app_data.docker_controls_scroll(&ScrollDirection::Previous); + app_data.docker_controls_scroll(&ScrollDirection::Up); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Pause)); } @@ -1935,7 +1995,7 @@ mod tests { assert_eq!(result, " 3/3 - container_1 - image_1"); // Change log state to no longer be at the end - app_data.log_scroll(&ScrollDirection::Previous); + app_data.log_scroll(&ScrollDirection::Up); let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_1 - image_1"); } @@ -1956,7 +2016,7 @@ mod tests { assert_eq!(result, " - container_1 - image_1"); // change container - app_data.containers_scroll(&ScrollDirection::Next); + app_data.containers_scroll(&ScrollDirection::Down); let result = app_data.get_log_title(); assert_eq!(result, " - container_2 - image_2"); @@ -1967,7 +2027,7 @@ mod tests { assert_eq!(result, " 3/3 - container_2 - image_2"); // Change log state to no longer be at the end - app_data.log_scroll(&ScrollDirection::Previous); + app_data.log_scroll(&ScrollDirection::Up); let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_2 - image_2"); } @@ -2074,7 +2134,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 1/3 - container_1 - image_1"); - app_data.log_scroll(&ScrollDirection::Next); + app_data.log_scroll(&ScrollDirection::Down); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(1)); @@ -2083,7 +2143,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_1 - image_1"); - app_data.log_scroll(&ScrollDirection::Next); + app_data.log_scroll(&ScrollDirection::Down); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(2)); @@ -2091,7 +2151,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 3/3 - container_1 - image_1"); - app_data.log_scroll(&ScrollDirection::Next); + app_data.log_scroll(&ScrollDirection::Down); let result = app_data.get_log_state(); assert!(result.is_some()); @@ -2122,7 +2182,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 3/3 - container_1 - image_1"); - app_data.log_scroll(&ScrollDirection::Previous); + app_data.log_scroll(&ScrollDirection::Up); let result = app_data.get_log_state(); assert!(result.is_some()); @@ -2131,7 +2191,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_1 - image_1"); - app_data.log_scroll(&ScrollDirection::Previous); + app_data.log_scroll(&ScrollDirection::Up); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(0)); @@ -2139,7 +2199,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 1/3 - container_1 - image_1"); - app_data.log_scroll(&ScrollDirection::Previous); + app_data.log_scroll(&ScrollDirection::Up); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(0)); @@ -2433,7 +2493,7 @@ mod tests { } for _ in 0..=500 { - app_data.log_scroll(&ScrollDirection::Next); + app_data.log_scroll(&ScrollDirection::Down); } let result = app_data.get_logs( Size { diff --git a/src/config/config.toml b/src/config/config.toml index 8547375..46f01b1 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -95,8 +95,8 @@ scroll_start = ["home"] # scroll up a list by one item scroll_up = ["up", "k"] # Horizontal scroll of the logs -log_scroll_forward = ["right"] -log_scroll_back = ["left"] +scroll_forward = ["right"] +scroll_back = ["left"] # Select next panel select_next_panel = ["tab"] # Select previous panel @@ -122,8 +122,8 @@ log_section_height_decrease = ["-"] log_section_height_increase = ["+"] # Toggle visibility of the log section log_section_toggle = ["\\"] - - +# Toggle to inspect container screen +inspect = ["i"] # Force a complete clear & redraw of the screen force_redraw = ["f"] diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs index 4fd0d15..28ae4aa 100644 --- a/src/config/keymap_parser.rs +++ b/src/config/keymap_parser.rs @@ -42,8 +42,9 @@ optional_config_struct!( exec, filter_mode, force_redraw, - log_scroll_back, - log_scroll_forward, + inspect, + scroll_back, + scroll_forward, log_search_mode, log_section_height_decrease, log_section_height_increase, @@ -77,9 +78,10 @@ config_struct!( delete_deny, exec, filter_mode, + inspect, force_redraw, - log_scroll_back, - log_scroll_forward, + scroll_back, + scroll_forward, log_search_mode, log_section_height_decrease, log_section_height_increase, @@ -113,10 +115,11 @@ impl Keymap { delete_confirm: (KeyCode::Char('y'), None), delete_deny: (KeyCode::Char('n'), None), exec: (KeyCode::Char('e'), None), + inspect: (KeyCode::Char('i'), None), filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))), force_redraw: (KeyCode::Char('f'), None), - log_scroll_back: (KeyCode::Left, None), - log_scroll_forward: (KeyCode::Right, None), + scroll_back: (KeyCode::Left, None), + scroll_forward: (KeyCode::Right, None), log_search_mode: (KeyCode::Char('#'), None), log_section_height_decrease: (KeyCode::Char('-'), None), log_section_height_increase: (KeyCode::Char('='), None), @@ -206,12 +209,8 @@ impl From> for Keymap { update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash); update_keymap(ck.scroll_up, &mut keymap.scroll_up, &mut clash); update_keymap(ck.log_search_mode, &mut keymap.log_search_mode, &mut clash); - update_keymap( - ck.log_scroll_forward, - &mut keymap.log_scroll_forward, - &mut clash, - ); - update_keymap(ck.log_scroll_back, &mut keymap.log_scroll_back, &mut clash); + update_keymap(ck.scroll_forward, &mut keymap.scroll_forward, &mut clash); + update_keymap(ck.scroll_back, &mut keymap.scroll_back, &mut clash); update_keymap( ck.select_next_panel, &mut keymap.select_next_panel, @@ -395,9 +394,10 @@ mod tests { exec: None, filter_mode: None, force_redraw: None, - log_scroll_back: None, + inspect: None, + scroll_back: None, log_search_mode: None, - log_scroll_forward: None, + scroll_forward: None, log_section_height_decrease: None, log_section_height_increase: None, log_section_toggle: None, @@ -441,8 +441,10 @@ mod tests { exec: gen_v(("g", "h")), filter_mode: gen_v(("i", "j")), force_redraw: gen_v(("k", "l")), - log_scroll_back: gen_v(("s", "t")), - log_scroll_forward: gen_v(("q", "r")), + // TODO test me + inspect: None, + scroll_back: gen_v(("s", "t")), + scroll_forward: gen_v(("q", "r")), log_search_mode: gen_v(("1", "2")), log_section_height_decrease: gen_v(("m", "n")), log_section_height_increase: gen_v(("o", "p")), @@ -479,8 +481,10 @@ mod tests { exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), - log_scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), - log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), + //todo test me + inspect: (KeyCode::Char('i'), None), + scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), + scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), log_search_mode: (KeyCode::Char('1'), Some(KeyCode::Char('2'))), log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), diff --git a/src/config/mod.rs b/src/config/mod.rs index b53bdac..db79281 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -25,7 +25,8 @@ pub struct Config { pub keymap: Keymap, pub log_search_case_sensitive: bool, pub raw_logs: bool, - pub save_dir: Option, + pub dir_config: Option, + pub dir_save: Option, pub show_logs: bool, pub show_self: bool, pub show_std_err: bool, @@ -47,7 +48,8 @@ impl From<&Args> for Config { keymap: Keymap::new(), log_search_case_sensitive: true, raw_logs: args.raw, - save_dir: Self::try_get_logs_dir(args.save_dir.as_ref()), + dir_save: Self::try_get_logs_dir(args.save_dir.as_ref()), + dir_config: args.config_file.as_ref().map(|i| PathBuf::from(&i)), show_logs: true, show_self: !args.show_self, show_std_err: !args.no_std_err, @@ -59,19 +61,20 @@ impl From<&Args> for Config { } } -impl From for Config { - fn from(config_file: ConfigFile) -> Self { +impl From<(ConfigFile, Option)> for Config { + fn from((config_file, dir): (ConfigFile, Option)) -> Self { Self { app_colors: AppColors::from(config_file.colors), color_logs: config_file.color_logs.unwrap_or(false), docker_interval_ms: config_file.docker_interval.unwrap_or(1000), + dir_config: dir, gui: config_file.gui.unwrap_or(true), host: config_file.host, in_container: Self::check_if_in_container(), keymap: Keymap::from(config_file.keymap), log_search_case_sensitive: config_file.log_search_case_sensitive.unwrap_or(true), raw_logs: config_file.raw_logs.unwrap_or(false), - save_dir: Self::try_get_logs_dir(config_file.save_dir.as_ref()), + dir_save: Self::try_get_logs_dir(config_file.save_dir.as_ref()), show_logs: config_file.show_logs.unwrap_or(true), show_self: config_file.show_self.unwrap_or(false), show_std_err: config_file.show_std_err.unwrap_or(true), @@ -182,8 +185,8 @@ impl Config { self.host = Some(host); } - if let Some(x) = config_from_cli.save_dir { - self.save_dir = Some(x); + if let Some(x) = config_from_cli.dir_save { + self.dir_save = Some(x); } if let Some(tz) = config_from_cli.timezone { @@ -208,15 +211,16 @@ impl Config { let args = Args::parse(); let config_from_cli = Self::from(&args); - if let Some(config_file) = &args.config_file + if let Some(dir_config_file) = &args.config_file && let Some(config_file) = - parse_config_file::ConfigFile::try_parse_from_file(config_file) + parse_config_file::ConfigFile::try_parse_from_file(dir_config_file) { - return Self::from(config_file).merge_args(config_from_cli); + return Self::from((config_file, Some(PathBuf::from(dir_config_file)))) + .merge_args(config_from_cli); } - if let Some(config_file) = parse_config_file::ConfigFile::try_parse(in_container) { - return Self::from(config_file).merge_args(config_from_cli); + if let Some((config_file, dir)) = parse_config_file::ConfigFile::try_parse(in_container) { + return Self::from((config_file, Some(dir))).merge_args(config_from_cli); } config_from_cli } diff --git a/src/config/parse_config_file.rs b/src/config/parse_config_file.rs index 541cc46..e685d57 100644 --- a/src/config/parse_config_file.rs +++ b/src/config/parse_config_file.rs @@ -81,7 +81,7 @@ pub struct ConfigFile { impl ConfigFile { /// Attempt to create a config.toml file, will attempt to recursively create the directories as well - fn crate_config_file(in_container: bool) -> Result<(), AppError> { + fn create_config_file(in_container: bool) -> Result<(), AppError> { if in_container { return Ok(()); } @@ -119,8 +119,6 @@ impl ConfigFile { toml::from_str::(input).map_err(|i| AppError::Parse(i.message().to_owned())) } } - - // TODO if on windows, omit the docker_host? } /// Read the config file path to string, then attempt to parse @@ -148,28 +146,26 @@ impl ConfigFile { /// Parse a config file using default config_file location /// This is executed first, then the CLI args are read, and if they contain a "--config-file" entry, then Self::try_parse_from_file() is executed - pub fn try_parse(in_container: bool) -> Option { - let mut config = None; + pub fn try_parse(in_container: bool) -> Option<(Self, PathBuf)> { + let mut output = None; for file_format in [ ConfigFileFormat::Toml, ConfigFileFormat::Jsonc, ConfigFileFormat::JsoncAsJson, ConfigFileFormat::Json, ] { - if let Ok(config_file) = Self::parse_config_file( - file_format, - &file_format.get_default_path_name(in_container), - ) { - config = Some(config_file); + let path = file_format.get_default_path_name(in_container); + if let Ok(config_file) = Self::parse_config_file(file_format, &path) { + output = Some((config_file, path)); break; } } - if config.is_none() { - Self::crate_config_file(in_container).ok(); + if output.is_none() { + Self::create_config_file(in_container).ok(); } - config + output } } diff --git a/src/docker_data/message.rs b/src/docker_data/message.rs index b0af01a..6030e29 100644 --- a/src/docker_data/message.rs +++ b/src/docker_data/message.rs @@ -9,5 +9,6 @@ pub enum DockerMessage { ConfirmDelete(ContainerId), Control((DockerCommand, ContainerId)), Exec(Sender>), + Inspect(ContainerId), Update, } diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 0d6310d..038dc6b 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -1,8 +1,8 @@ use bollard::{ Docker, query_parameters::{ - ListContainersOptions, LogsOptions, RemoveContainerOptions, RestartContainerOptions, - StartContainerOptions, StatsOptions, StopContainerOptions, + InspectContainerOptions, ListContainersOptions, LogsOptions, RemoveContainerOptions, + RestartContainerOptions, StartContainerOptions, StatsOptions, StopContainerOptions, }, secret::ContainerStatsResponse, service::ContainerSummary, @@ -413,6 +413,18 @@ impl DockerData { docker_tx.send(Arc::clone(&self.docker)).ok(); } DockerMessage::Update => self.update_everything().await, + DockerMessage::Inspect(id) => { + let t = self + .docker + .inspect_container(id.get(), Some(InspectContainerOptions { size: true })) + .await; + if let Ok(t) = t { + self.app_data.lock().set_inspect_data(t); + self.gui_state.lock().status_push(Status::Inspect); + } else { + // Set error here, can't inspect container + } + } } } } diff --git a/src/exec.rs b/src/exec.rs index fadab1b..bc4444e 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,5 +1,5 @@ use std::{ - io::{Read, Stdout, Write}, + io::{Read, Write}, sync::{Arc, atomic::AtomicBool, mpsc::Sender}, }; @@ -10,7 +10,7 @@ use bollard::{ use crossterm::terminal::enable_raw_mode; use futures_util::StreamExt; use parking_lot::Mutex; -use ratatui::{Terminal, backend::CrosstermBackend}; +use ratatui::layout::Size; use tokio::{ fs::File, io::{AsyncReadExt, AsyncWriteExt}, @@ -123,23 +123,29 @@ impl AsyncTTY { } } -/// This is used to set the terminal size when exec via the Internal method -#[derive(Debug, Clone)] -pub struct TerminalSize { - width: u16, - height: u16, -} +// impl TryFrom<&Terminal>> for HWU16 { +// type Error = None; +// fn try_from(terminal: &Terminal>) -> Option { +// terminal.size().map_or(None, |i| { +// Some(Self { +// width: i.width, +// height: i.height, +// }) +// }) +// } -impl TerminalSize { - pub fn new(terminal: &Terminal>) -> Option { - terminal.size().map_or(None, |i| { - Some(Self { - width: i.width, - height: i.height, - }) - }) - } -} +// } + +// impl TerminalSize { +// pub fn new(terminal: &Terminal>) -> Option { +// terminal.size().map_or(None, |i| { +// Some(Self { +// width: i.width, +// height: i.height, +// }) +// }) +// } +// } #[derive(Debug, Clone)] pub enum ExecMode { @@ -225,7 +231,7 @@ impl ExecMode { &self, id: &ContainerId, docker: &Arc, - terminal_size: Option, + terminal_size: Option, ) -> Result<(), AppError> { let cancel_token = CancellationToken::new(); @@ -341,7 +347,7 @@ impl ExecMode { } } - pub async fn run(&self, tty_size: Option) -> Result<(), AppError> { + pub async fn run(&self, tty_size: Option) -> Result<(), AppError> { match self { Self::External(id) => { Self::exec_external(id); diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index be1a8f2..4dd5ae3 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -119,6 +119,14 @@ impl InputHandler { self.gui_state.lock().set_delete_container(None); } + async fn inspect_key(&self) { + self.app_data.lock().clear_inspect_data(); + let selected = self.app_data.lock().get_selected_container().cloned(); + if let Some(g) = selected { + self.docker_tx.send(DockerMessage::Inspect(g.id)).await.ok(); + } + } + /// Validate that one can exec into a Docker container async fn exec_key(&self) { let is_oxker = self.app_data.lock().is_oxker(); @@ -179,7 +187,7 @@ impl InputHandler { let args = self.app_data.lock().config.clone(); let container = self.app_data.lock().get_selected_container_id_state_name(); if let Some((id, _, name)) = container - && let Some(log_path) = args.save_dir + && let Some(log_path) = args.dir_save { let (sx, rx) = tokio::sync::oneshot::channel(); self.docker_tx.send(DockerMessage::Exec(sx)).await?; @@ -296,6 +304,18 @@ impl InputHandler { } } + fn inspect_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) { + for _ in 0..self.get_modifier_total(modifier) { + self.gui_state.lock().set_inspect_offset(sd); + } + } + + // fn inspect_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) { + // for _ in 0..self.get_modifier_total(modifier) { + // self.gui_state.lock().set_inspect_offset(sd); + // } + // } + fn logs_horizontal_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) { let panel = self.gui_state.lock().get_selected_panel(); if panel == SelectablePanel::Logs { @@ -393,22 +413,22 @@ impl InputHandler { self.gui_state.lock().status_del(Status::SearchLogs); } - _ if self.keymap.log_scroll_back.0 == key_code - || self.keymap.log_scroll_back.1 == Some(key_code) => + _ if self.keymap.scroll_back.0 == key_code + || self.keymap.scroll_back.1 == Some(key_code) => { - self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous); + self.logs_horizontal_scroll(modifier, &ScrollDirection::Up); } - _ if self.keymap.log_scroll_forward.0 == key_code - || self.keymap.log_scroll_forward.1 == Some(key_code) => + _ if self.keymap.scroll_forward.0 == key_code + || self.keymap.scroll_forward.1 == Some(key_code) => { - self.logs_horizontal_scroll(modifier, &ScrollDirection::Next); + self.logs_horizontal_scroll(modifier, &ScrollDirection::Down); } _ if self.keymap.scroll_down.0 == key_code => { self.app_data .lock() - .log_search_scroll(&ScrollDirection::Next); + .log_search_scroll(&ScrollDirection::Down); // TODO should only do this is log_search_scroll returns some // Need to wait til app_data and gui_data is combined self.gui_state @@ -418,9 +438,7 @@ impl InputHandler { } _ if self.keymap.scroll_up.0 == key_code => { - self.app_data - .lock() - .log_search_scroll(&ScrollDirection::Previous); + self.app_data.lock().log_search_scroll(&ScrollDirection::Up); // TODO should only do this is log_search_scroll returns some // Need to wait til app_data and gui_data is combined self.gui_state @@ -439,6 +457,62 @@ impl InputHandler { } } + /// Actions to take when Filter status active + fn handle_inspect(&mut self, key_code: KeyCode, modifier: KeyModifiers) { + match key_code { + _ if self.keymap.inspect.0 == key_code + || self.keymap.inspect.1 == Some(key_code) + || self.keymap.clear.0 == key_code + || self.keymap.clear.1 == Some(key_code) => + { + self.app_data.lock().clear_inspect_data(); + self.gui_state.lock().clear_inspect_offset(); + self.gui_state.lock().status_del(Status::Inspect); + } + + _ if self.keymap.scroll_down.0 == key_code + || self.keymap.scroll_down.1 == Some(key_code) => + { + self.inspect_scroll(modifier, &ScrollDirection::Down); + } + + _ if self.keymap.scroll_up.0 == key_code + || self.keymap.scroll_up.1 == Some(key_code) => + { + self.inspect_scroll(modifier, &ScrollDirection::Up); + } + + _ if self.keymap.scroll_forward.0 == key_code + || self.keymap.scroll_forward.1 == Some(key_code) => + { + self.inspect_scroll(modifier, &ScrollDirection::Right); + } + + _ if self.keymap.scroll_back.0 == key_code + || self.keymap.scroll_back.1 == Some(key_code) => + { + self.inspect_scroll(modifier, &ScrollDirection::Left); + } + + _ if self.keymap.toggle_mouse_capture.0 == key_code + || self.keymap.toggle_mouse_capture.1 == Some(key_code) => + { + self.mouse_capture_key(); + } + _ if self.keymap.scroll_start.0 == key_code + || self.keymap.scroll_start.1 == Some(key_code) => + { + self.gui_state.lock().clear_inspect_offset(); + } + _ if self.keymap.scroll_end.0 == key_code + || self.keymap.scroll_end.1 == Some(key_code) => + { + self.gui_state.lock().set_inspect_offset_y_to_max(); + } + _ => (), + } + } + /// Actions to take when Filter status active fn handle_filter(&self, key_code: KeyCode) { match key_code { @@ -596,6 +670,10 @@ impl InputHandler { self.save_key().await; } + _ if self.keymap.inspect.0 == key_code || self.keymap.inspect.1 == Some(key_code) => { + self.inspect_key().await; + } + _ if self.keymap.select_next_panel.0 == key_code || self.keymap.select_next_panel.1 == Some(key_code) => { @@ -623,13 +701,13 @@ impl InputHandler { _ if self.keymap.scroll_up.0 == key_code || self.keymap.scroll_up.1 == Some(key_code) => { - self.scroll(modifier, &ScrollDirection::Previous); + self.scroll(modifier, &ScrollDirection::Up); } _ if self.keymap.scroll_down.0 == key_code || self.keymap.scroll_down.1 == Some(key_code) => { - self.scroll(modifier, &ScrollDirection::Next); + self.scroll(modifier, &ScrollDirection::Down); } _ if self.keymap.filter_mode.0 == key_code @@ -648,18 +726,17 @@ impl InputHandler { self.gui_state.lock().status_push(Status::SearchLogs); } - _ if self.keymap.log_scroll_back.0 == key_code - || self.keymap.log_scroll_back.1 == Some(key_code) => + _ if self.keymap.scroll_back.0 == key_code + || self.keymap.scroll_back.1 == Some(key_code) => { - self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous); + self.logs_horizontal_scroll(modifier, &ScrollDirection::Up); // self.logs_back(modifier); } - _ if self.keymap.log_scroll_forward.0 == key_code - || self.keymap.log_scroll_forward.1 == Some(key_code) => + _ if self.keymap.scroll_forward.0 == key_code + || self.keymap.scroll_forward.1 == Some(key_code) => { - self.logs_horizontal_scroll(modifier, &ScrollDirection::Next); - // self.logs_forward(modifier); + self.logs_horizontal_scroll(modifier, &ScrollDirection::Down); } KeyCode::Enter => self.enter_key().await, @@ -678,6 +755,7 @@ impl InputHandler { let contains_filter = contains(Status::Filter); let contains_delete = contains(Status::DeleteConfirm); let contains_search_logs = contains(Status::SearchLogs); + let contains_inspect = contains(Status::Inspect); if !contains_exec { let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1; @@ -698,6 +776,8 @@ impl InputHandler { self.handle_search_logs(key_code, key_modifier); } else if contains_delete { self.handle_delete(key_code).await; + } else if contains_inspect { + self.handle_inspect(key_code, key_modifier); } else { self.handle_others(key_code, key_modifier).await; } @@ -726,7 +806,18 @@ impl InputHandler { /// Handle mouse button events fn mouse_press(&self, mouse_event: MouseEvent, modifier: KeyModifiers) { let status = self.gui_state.lock().get_status(); - if status.contains(&Status::Help) { + + if status.contains(&Status::Inspect) { + match mouse_event.kind { + MouseEventKind::ScrollDown => self.inspect_scroll(modifier, &ScrollDirection::Down), + MouseEventKind::ScrollUp => self.inspect_scroll(modifier, &ScrollDirection::Up), + MouseEventKind::ScrollRight => { + self.inspect_scroll(modifier, &ScrollDirection::Right) + } + MouseEventKind::ScrollLeft => self.inspect_scroll(modifier, &ScrollDirection::Left), + _ => (), + } + } else if status.contains(&Status::Help) { let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1); let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point); if help_intersect { @@ -734,8 +825,9 @@ impl InputHandler { } } else { match mouse_event.kind { - MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Previous), - MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Next), + MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Up), + MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Down), + // TODO left and right for log offsets MouseEventKind::Down(MouseButton::Left) => { let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1); let header = self.gui_state.lock().get_intersect_header(mouse_point); diff --git a/src/ui/draw_blocks/commands.rs b/src/ui/draw_blocks/commands.rs index d01af86..2ca6221 100644 --- a/src/ui/draw_blocks/commands.rs +++ b/src/ui/draw_blocks/commands.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use super::RIGHT_ARROW; +use super::SELECT_ARROW; use crate::{ app_data::AppData, config::AppColors, @@ -44,7 +44,7 @@ pub fn draw( let items = List::new(items) .block(block) .highlight_style(Style::default().add_modifier(Modifier::BOLD)) - .highlight_symbol(RIGHT_ARROW); + .highlight_symbol(SELECT_ARROW); f.render_stateful_widget(items, area, i); } else { let paragraph = Paragraph::new("").block(block).alignment(Alignment::Center); @@ -173,7 +173,7 @@ mod tests { setup .app_data .lock() - .docker_controls_scroll(&ScrollDirection::Next); + .docker_controls_scroll(&ScrollDirection::Down); setup .terminal @@ -370,7 +370,7 @@ mod tests { setup .app_data .lock() - .docker_controls_scroll(&ScrollDirection::Next); + .docker_controls_scroll(&ScrollDirection::Down); setup .terminal diff --git a/src/ui/draw_blocks/filter.rs b/src/ui/draw_blocks/filter.rs index 101ce2d..1091cee 100644 --- a/src/ui/draw_blocks/filter.rs +++ b/src/ui/draw_blocks/filter.rs @@ -5,7 +5,14 @@ use ratatui::{ text::{Line, Span}, }; -use crate::{app_data::FilterBy, config::AppColors, ui::FrameData}; +use crate::{ + app_data::FilterBy, + config::AppColors, + ui::{ + FrameData, + draw_blocks::{LEFT_ARROW, RIGHT_ARROW}, + }, +}; /// Create the filter_by by spans, coloured dependant on which one is selected fn filter_by_spans(colors: AppColors, fd: &'_ FrameData) -> [Span<'_>; 4] { @@ -46,7 +53,7 @@ pub fn draw(area: Rect, colors: AppColors, frame: &mut Frame, fd: &FrameData) { let mut line = vec![ Span::styled(" Esc ", style_but), Span::styled(" clear ", style_desc), - Span::styled(" โ† by โ†’ ", style_but), + Span::styled(format!(" {LEFT_ARROW} by {RIGHT_ARROW} "), style_but), Span::from(" "), ]; line.extend_from_slice(&filter_by_spans(colors, fd)); diff --git a/src/ui/draw_blocks/help.rs b/src/ui/draw_blocks/help.rs index 7e16abc..1c1fd76 100644 --- a/src/ui/draw_blocks/help.rs +++ b/src/ui/draw_blocks/help.rs @@ -1,410 +1,751 @@ -use crossterm::event::KeyCode; -use jiff::tz::TimeZone; +use std::sync::LazyLock; + use ratatui::{ Frame, - layout::{Alignment, Constraint, Direction, Layout}, - style::{Color, Modifier, Style}, + layout::{Alignment, Constraint, Direction, Layout, Rect, Size}, + style::{Color, Style, Stylize}, text::{Line, Span}, - widgets::{Block, BorderType, Borders, Clear, Paragraph}, + widgets::{Block, BorderType, Borders, Clear, Padding, Paragraph}, }; use crate::{ - config::{AppColors, Keymap}, + config::{AppColors, Config, Keymap}, ui::gui_state::BoxLocation, }; use super::{DESCRIPTION, NAME_TEXT, REPO, VERSION, popup}; -/// Help popup box needs these three pieces of information -struct HelpInfo { - lines: Vec>, - width: usize, - height: usize, +macro_rules! to_u16 { + ($value:expr) => { + u16::try_from($value).unwrap_or_default() + }; } -impl HelpInfo { - /// Find the max width of a Span in &[Line] - fn calc_width(lines: &[Line]) -> usize { - lines - .iter() - .map(ratatui::prelude::Line::width) - .max() - .unwrap_or(1) +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum KeyDescriptions { + Clear, + Command, + Exec, + FilterMode, + Help, + InspectMode, + LogHeight, + LogVisibility, + MouseCapture, + Panel, + Quit, + Redraw, + Save, + ScrollEnd, + ScrollH, + ScrollSpeed, + ScrollStart, + ScrollV, + SearchMode, + SortCpu, + SortHeader, + SortId, + SortImage, + SortMem, + SortName, + SortRX, + SortState, + SortStatus, + SortStop, + SortTX, +} + +type Column = Vec<(Vec>, KeyDescriptions)>; + +#[derive(Debug, Clone, Hash)] +struct KeymapColumns { + left: Column, + right: Column, +} + +impl KeymapColumns { + fn default(keymap: &Keymap) -> Self { + Self { + left: vec![ + ( + vec![ + Some(keymap.quit.0.to_string()), + keymap.quit.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Quit, + ), + ( + vec![ + Some(keymap.scroll_down.0.to_string()), + Some(keymap.scroll_up.0.to_string()), + keymap.scroll_down.1.as_ref().map(|i| i.to_string()), + keymap.scroll_up.1.as_ref().map(|i| i.to_string()), + Some(keymap.scroll_start.0.to_string()), + Some(keymap.scroll_end.0.to_string()), + keymap.scroll_start.1.as_ref().map(|i| i.to_string()), + keymap.scroll_end.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::ScrollV, + ), + ( + vec![Some(keymap.scroll_many.to_string())], + KeyDescriptions::ScrollSpeed, + ), + ( + vec![ + Some(keymap.exec.0.to_string()), + keymap.exec.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Exec, + ), + ( + vec![ + Some(keymap.filter_mode.0.to_string()), + keymap.filter_mode.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::FilterMode, + ), + ( + vec![ + Some(keymap.toggle_help.0.to_string()), + keymap.toggle_help.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Help, + ), + ( + vec![ + Some(keymap.log_section_height_decrease.0.to_string()), + Some(keymap.log_section_height_increase.0.to_string()), + keymap + .log_section_height_decrease + .1 + .as_ref() + .map(|i| i.to_string()), + keymap + .log_section_height_increase + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::LogHeight, + ), + (vec![Some("1 ~ 9".to_owned())], KeyDescriptions::SortHeader), + ( + vec![ + Some(keymap.select_next_panel.0.to_string()), + Some(keymap.select_previous_panel.0.to_string()), + keymap + .select_previous_panel + .1 + .as_ref() + .map(|i| i.to_string()), + keymap.select_next_panel.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Panel, + ), + ( + vec![ + Some(keymap.save_logs.0.to_string()), + keymap.save_logs.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Save, + ), + ], + right: vec![ + ( + vec![ + Some(keymap.clear.0.to_string()), + keymap.clear.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Clear, + ), + ( + vec![ + Some(keymap.scroll_back.0.to_string()), + Some(keymap.scroll_forward.0.to_string()), + keymap.scroll_back.1.as_ref().map(|i| i.to_string()), + keymap.scroll_forward.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::ScrollH, + ), + (vec![Some(String::from("Enter"))], KeyDescriptions::Command), + ( + vec![ + Some(keymap.inspect.0.to_string()), + keymap.inspect.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::InspectMode, + ), + ( + vec![ + Some(keymap.log_search_mode.0.to_string()), + keymap.log_search_mode.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::SearchMode, + ), + ( + vec![ + Some(keymap.force_redraw.0.to_string()), + keymap.force_redraw.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Redraw, + ), + ( + vec![ + Some(keymap.log_section_toggle.0.to_string()), + keymap.log_section_toggle.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::LogVisibility, + ), + (vec![Some("0".to_owned())], KeyDescriptions::SortStop), + ( + vec![ + Some(keymap.toggle_mouse_capture.0.to_string()), + keymap + .toggle_mouse_capture + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::MouseCapture, + ), + ], + } } + fn custom(config: &Config) -> Self { + Self { + left: vec![ + ( + vec![ + Some(config.keymap.quit.0.to_string()), + config.keymap.quit.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Quit, + ), + ( + vec![ + Some(config.keymap.scroll_down.0.to_string()), + Some(config.keymap.scroll_up.0.to_string()), + config.keymap.scroll_down.1.as_ref().map(|i| i.to_string()), + config.keymap.scroll_up.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::ScrollV, + ), + ( + vec![ + Some(config.keymap.scroll_start.0.to_string()), + config.keymap.scroll_start.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::ScrollStart, + ), + ( + vec![Some(config.keymap.scroll_many.to_string())], + KeyDescriptions::ScrollSpeed, + ), + ( + vec![ + Some(config.keymap.exec.0.to_string()), + config.keymap.exec.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Exec, + ), + ( + vec![ + Some(config.keymap.filter_mode.0.to_string()), + config.keymap.filter_mode.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::FilterMode, + ), + ( + vec![ + Some(config.keymap.toggle_help.0.to_string()), + config.keymap.toggle_help.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Help, + ), + ( + vec![ + Some(config.keymap.log_section_height_decrease.0.to_string()), + Some(config.keymap.log_section_height_increase.0.to_string()), + config + .keymap + .log_section_height_decrease + .1 + .as_ref() + .map(|i| i.to_string()), + config + .keymap + .log_section_height_increase + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::LogHeight, + ), + ( + vec![ + Some(config.keymap.sort_by_name.0.to_string()), + config.keymap.sort_by_name.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::SortName, + ), + ( + vec![ + Some(config.keymap.sort_by_status.0.to_string()), + config + .keymap + .sort_by_status + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::SortStatus, + ), + ( + vec![ + Some(config.keymap.sort_by_memory.0.to_string()), + config + .keymap + .sort_by_memory + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::SortMem, + ), + ( + vec![ + Some(config.keymap.sort_by_image.0.to_string()), + config + .keymap + .sort_by_image + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::SortImage, + ), + ( + vec![ + Some(config.keymap.sort_by_tx.0.to_string()), + config.keymap.sort_by_tx.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::SortTX, + ), + ( + vec![ + Some(config.keymap.select_next_panel.0.to_string()), + Some(config.keymap.select_previous_panel.0.to_string()), + config + .keymap + .select_previous_panel + .1 + .as_ref() + .map(|i| i.to_string()), + config + .keymap + .select_next_panel + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::Panel, + ), + ( + vec![ + Some(config.keymap.save_logs.0.to_string()), + config.keymap.save_logs.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Save, + ), + ], + + right: vec![ + ( + vec![ + Some(config.keymap.clear.0.to_string()), + config.keymap.clear.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Clear, + ), + ( + vec![ + Some(config.keymap.scroll_back.0.to_string()), + Some(config.keymap.scroll_forward.0.to_string()), + config.keymap.scroll_back.1.as_ref().map(|i| i.to_string()), + config + .keymap + .scroll_forward + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::ScrollH, + ), + ( + vec![ + Some(config.keymap.scroll_end.0.to_string()), + config.keymap.scroll_end.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::ScrollEnd, + ), + (vec![Some(String::from("Enter"))], KeyDescriptions::Command), + ( + vec![ + Some(config.keymap.inspect.0.to_string()), + config.keymap.inspect.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::InspectMode, + ), + ( + vec![ + Some(config.keymap.log_search_mode.0.to_string()), + config + .keymap + .log_search_mode + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::SearchMode, + ), + ( + vec![ + Some(config.keymap.force_redraw.0.to_string()), + config.keymap.force_redraw.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::Redraw, + ), + ( + vec![ + Some(config.keymap.log_section_toggle.0.to_string()), + config + .keymap + .log_section_toggle + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::LogVisibility, + ), + ( + vec![ + Some(config.keymap.sort_by_state.0.to_string()), + config + .keymap + .sort_by_state + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::SortState, + ), + ( + vec![ + Some(config.keymap.sort_by_cpu.0.to_string()), + config.keymap.sort_by_cpu.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::SortCpu, + ), + ( + vec![ + Some(config.keymap.sort_by_id.0.to_string()), + config.keymap.sort_by_id.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::SortId, + ), + ( + vec![ + Some(config.keymap.sort_by_rx.0.to_string()), + config.keymap.sort_by_rx.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::SortRX, + ), + ( + vec![ + Some(config.keymap.sort_reset.0.to_string()), + config.keymap.sort_reset.1.as_ref().map(|i| i.to_string()), + ], + KeyDescriptions::SortStop, + ), + ( + vec![ + Some(config.keymap.toggle_mouse_capture.0.to_string()), + config + .keymap + .toggle_mouse_capture + .1 + .as_ref() + .map(|i| i.to_string()), + ], + KeyDescriptions::MouseCapture, + ), + ], + } + } + + /// Add 1 to allow spacing between the key and the definition + fn longest_line(column: &Column) -> usize { + column + .iter() + .map(|(keys, _)| { + keys.iter() + .filter_map(|k| k.as_deref()) + .collect::>() + .join(" ") + .len() + }) + .max() + .unwrap_or(0) + .saturating_add(1) + } + + fn create_button_line(column: &Column, colors: &AppColors) -> Vec> { + let longest_button = Self::longest_line(column); + column + .iter() + .map(|(keys, desc)| HelpInfo::create_button_line(keys, desc, *colors, longest_button)) + .collect::>() + } + + fn to_helpinfo(&self, config: &Config) -> (HelpInfo, HelpInfo) { + let left = Self::create_button_line(&self.left, &config.app_colors); + let right = Self::create_button_line(&self.right, &config.app_colors); + + let size_left = HelpInfo::calc_size(&left); + let size_right = HelpInfo::calc_size(&right); + ( + HelpInfo { + lines: left, + size: size_left, + }, + HelpInfo { + lines: right, + size: size_right, + }, + ) + } +} + +impl KeyDescriptions { + fn as_str(&self) -> &'static str { + match self { + Self::Clear => "close dialog", + Self::Command => "send docker command", + Self::Exec => "exec into a container", + Self::FilterMode => "filter mode", + Self::Help => "toggle this panel", + Self::InspectMode => "container inspect mode", + Self::LogHeight => "change log section height", + Self::LogVisibility => "toggle of section visibility", + Self::MouseCapture => "toggle mouse capture - allows text selection", + Self::Panel => "change panel", + Self::Quit => "quit", + Self::Redraw => "force clear screen and redraw", + Self::Save => "save logs to file", + Self::ScrollH => "scroll horizontally", + Self::ScrollStart => "scroll to start", + Self::ScrollEnd => "scroll to end", + Self::ScrollSpeed => "increase scroll speed", + Self::ScrollV => "scroll vertically", + Self::SearchMode => "log search mode", + Self::SortHeader => "sort by header - or click header", + Self::SortStop => "stop sort", + Self::SortCpu => "sort by CPU", + Self::SortId => "sort by ID", + Self::SortImage => "sort by Image", + Self::SortMem => "sort by memory", + Self::SortName => "sort by name", + Self::SortRX => "sort by RX", + Self::SortState => "sort by state", + Self::SortStatus => "sort by status", + Self::SortTX => "sort by TX", + } + } +} + +/// Help popup box needs these three pieces of information +/// Change this to a trait +#[derive(Debug, Clone, Hash)] +struct HelpInfo { + lines: Vec>, + size: Size, +} + +static DEFAULT_NAME: LazyLock = LazyLock::new(|| { + let colors = AppColors::new(); + HelpInfo::gen_name_description(colors) +}); + +static DEFAULT_COLUMNS: LazyLock = + LazyLock::new(|| KeymapColumns::default(&Keymap::new())); + +impl HelpInfo { + /// Find the height and width of an array of lines + fn calc_size(lines: &[Line]) -> Size { + Size { + width: to_u16!( + lines + .iter() + .map(ratatui::prelude::Line::width) + .max() + .unwrap_or(1) + ), + height: to_u16!(lines.len()), + } + } /// Just an empty span, i.e. a new line - fn empty_span<'a>() -> Line<'a> { + fn empty_line<'a>() -> Line<'a> { Line::from(String::new()) } /// generate a span, of given &str and given color - fn span<'a>(input: &str, color: Color) -> Span<'a> { - Span::styled(input.to_owned(), Style::default().fg(color)) + fn span<'a>(input: String, color: Color) -> Span<'a> { + Span::styled(input, Style::default().fg(color)) } /// &str to black text span - fn text_span<'a>(input: &str, color: AppColors) -> Span<'a> { + fn text_span<'a>(input: String, color: AppColors) -> Span<'a> { Self::span(input, color.popup_help.text) } /// &str to white text span - fn highlighted_text_span<'a>(input: &str, color: AppColors) -> Span<'a> { + fn highlighted_text_span<'a>(input: String, color: AppColors) -> Span<'a> { Self::span(input, color.popup_help.text_highlight) } - /// Generate the `oxker` name span + metadata - fn gen_name(colors: AppColors) -> Self { + /// Generate the `oxker` name section + fn gen_name_description(colors: AppColors) -> Self { let mut lines = NAME_TEXT .lines() - .map(|i| Line::from(Self::highlighted_text_span(i, colors))) + .map(|i| Line::from(Self::highlighted_text_span(i.to_owned(), colors))) .collect::>(); - lines.insert(0, Self::empty_span()); - let width = Self::calc_width(&lines); - let height = lines.len(); - - Self { - lines, - width, - height, - } + lines.extend([ + Self::empty_line(), + Line::from(Self::highlighted_text_span(DESCRIPTION.to_owned(), colors)).centered(), + ]); + let size = Self::calc_size(&lines); + Self { lines, size } } - /// Generate the description span + metadata - fn gen_description(colors: AppColors) -> Self { - let lines = [ - Self::empty_span(), - Line::from(Self::highlighted_text_span(DESCRIPTION, colors)), - Self::empty_span(), - ]; + fn create_button<'a>( + input: &[Option], // Use a slice for better flexibility + color: AppColors, + spacing: usize, + ) -> Span<'a> { + let label = input + .iter() + .flatten() + .map(|s| s.as_str()) + .collect::>() + .join(" "); - Self { - lines: lines.to_vec(), - width: Self::calc_width(&lines), - height: lines.len(), - } + let padded_text = format!("{label:, show_timestamp: bool) -> Self { - let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors); - let button_desc = |x: &str| Self::text_span(x, colors); - let or = || button_desc("or"); - let space = || button_desc(" "); + fn create_button_line<'a>( + keys: &[Option], + desc: &KeyDescriptions, + app_colors: AppColors, + longest_button: usize, + ) -> Line<'a> { + Line::from(vec![ + Self::create_button(keys, app_colors, longest_button), + Span::from(desc.as_str()), + ]) + } - let descriptions = [ - Line::from(vec![ - space(), - button_item("tab"), - or(), - button_item("shift+tab"), - button_desc("change panels"), - ]), - Line::from(vec![ - space(), - button_item("โ†‘ โ†“"), - or(), - button_item("j k"), - or(), - button_item("Home End"), - button_desc("scroll vertically"), - ]), - Line::from(vec![ - space(), - button_item("โ† โ†’"), - button_desc("horizontal scroll across logs"), - ]), - Line::from(vec![ - space(), - button_item("ctrl"), - button_desc("increase scroll speed, used in conjunction scroll keys"), - ]), - Line::from(vec![ - space(), - button_item("enter"), - button_desc("send docker container command"), - ]), - Line::from(vec![ - space(), - button_item("e"), - button_desc("exec into a container"), - #[cfg(target_os = "windows")] - button_desc(" - not available on Windows"), - ]), - Line::from(vec![ - space(), - button_item("f"), - button_desc("force clear the screen & redraw the gui"), - ]), - Line::from(vec![ - space(), - button_item("h"), - button_desc("toggle this help information - or click heading"), - ]), - Line::from(vec![ - space(), - button_item("s"), - button_desc("save logs to file"), - ]), - Line::from(vec![ - space(), - button_item("m"), - button_desc( - "toggle mouse capture - if disabled, text on screen can be selected & copied", - ), - ]), - Line::from(vec![ - space(), - button_item("F1"), - or(), - button_item("/"), - button_desc("enter filter mode"), - ]), - Line::from(vec![ - space(), - button_item("#"), - button_desc("enter log search mode"), - ]), - Line::from(vec![space(), button_item("0"), button_desc("stop sort")]), - Line::from(vec![ - space(), - button_item("1 - 9"), - button_desc("sort by header - or click header"), - ]), - Line::from(vec![ - space(), - button_item("- ="), - button_desc("change log section height"), - ]), - Line::from(vec![ - space(), - button_item("\\"), - button_desc("toggle log section visibility"), - ]), - Line::from(vec![ - space(), - button_item("esc"), - button_desc("close dialog"), - ]), - Line::from(vec![ - space(), - button_item("q"), - button_desc("quit at any time"), - ]), + fn gen_keymap_title(style: Style) -> Self { + let lines = vec![ + Self::empty_line(), + Line::from(Span::from("Keymap")) + .style(style) + .centered() + .underlined(), ]; + let size = Self::calc_size(&lines); + Self { lines, size } + } - let mut lines = if show_timestamp { - Vec::from([ - Self::custom_text(colors, &Keymap::new(), zone), - Self::empty_span(), - ]) + fn gen_keymap(config: &Config) -> (Self, Self) { + let columns = if config.keymap == Keymap::new() { + DEFAULT_COLUMNS.clone() } else { - vec![] + KeymapColumns::custom(config) }; - - lines.extend_from_slice(&descriptions); - let width = Self::calc_width(&lines); - let height = lines.len(); - - Self { - lines, - width, - height, - } + columns.to_helpinfo(config) } - /// Generate the final lines, GitHub link etc, + metadata - fn gen_final(colors: AppColors) -> Self { - let lines = [ - Self::empty_span(), - Line::from(vec![Self::text_span( - "currently an early work in progress, all and any input appreciated", - colors, - )]), - Line::from(vec![Span::styled( - REPO, - Style::default() - .fg(colors.popup_help.text_highlight) - .add_modifier(Modifier::UNDERLINED), - )]), - ]; + fn gen_locations(config: &Config) -> Self { + let mut entries = Vec::new(); - Self { - lines: lines.to_vec(), - width: Self::calc_width(&lines), - height: lines.len(), + if let Some(path) = &config.dir_config { + entries.push(("config location: ", path.display().to_string())); } - } - - /// Display timezone in timestamps are visible - /// Has ability to display if keymap or colors are customized, but currently not in use - fn custom_text<'a>(colors: AppColors, _keymap: &Keymap, zone: Option<&TimeZone>) -> Line<'a> { - let highlighted = |x: &str| Self::highlighted_text_span(x, colors); - let text = |x: &str| Self::text_span(x, colors); - let zone = zone.and_then(|i| i.iana_name()).unwrap_or("Etc/UTC"); - Line::from(Vec::from([text("logs timezone: "), highlighted(zone)])).centered() - } - - /// Generate the display information when a custom keymap is being used - fn gen_custom_keymap_info( - colors: AppColors, - km: &Keymap, - zone: Option<&TimeZone>, - show_timestamp: bool, - ) -> Self { - let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors); - let button_desc = |x: &str| Self::text_span(x, colors); - let or = || button_desc("or"); - let space = || button_desc(" "); - - let or_secondary = |a: (KeyCode, Option), desc: &str| { - a.1.map_or_else( - || { - Line::from(vec![ - space(), - button_item(&a.0.to_string()), - button_desc(desc), - ]) - }, - |secondary| { - Line::from(vec![ - space(), - button_item(&a.0.to_string()), - or(), - button_item(&secondary.to_string()), - button_desc(desc), - ]) - }, - ) - }; - let descriptions = [ - or_secondary(km.select_next_panel, "select next panel"), - or_secondary(km.select_previous_panel, "select previous panel"), - or_secondary(km.scroll_down, "scroll list down by one"), - or_secondary(km.scroll_up, "scroll list up by one"), - or_secondary(km.scroll_end, "scroll list to end"), - or_secondary(km.scroll_start, "scroll list to start"), - or_secondary(km.log_scroll_forward, "horizontal scroll logs right"), - or_secondary(km.log_scroll_back, "horizontal scroll logs left"), - Line::from(vec![ - space(), - button_item(km.scroll_many.to_string().as_str()), - button_desc("increase scroll speed, used in conjunction scroll keys"), - ]), - Line::from(vec![ - space(), - button_item("enter"), - button_desc("send docker container command"), - ]), - #[cfg(not(target_os = "windows"))] - or_secondary(km.exec, "exec into a container"), - #[cfg(target_os = "windows")] - or_secondary(km.exec, "exec into a container - not available on Windows"), - or_secondary(km.force_redraw, "force clear the screen & redraw the gui"), - or_secondary( - km.toggle_help, - "toggle this help information - or click heading", - ), - or_secondary(km.save_logs, "save logs to file"), - or_secondary( - km.toggle_mouse_capture, - "toggle mouse capture - if disabled, text on screen can be selected & copied", - ), - or_secondary(km.filter_mode, "enter filter mode"), - or_secondary(km.log_search_mode, "enter log search mode"), - or_secondary(km.sort_reset, "reset container sorting"), - or_secondary(km.sort_by_name, "sort containers by name"), - or_secondary(km.sort_by_state, "sort containers by state"), - or_secondary(km.sort_by_status, "sort containers by status"), - or_secondary(km.sort_by_cpu, "sort containers by cpu"), - or_secondary(km.sort_by_memory, "sort containers by memory"), - or_secondary(km.sort_by_id, "sort containers by id"), - or_secondary(km.sort_by_image, "sort containers by image"), - or_secondary(km.sort_by_rx, "sort containers by rx"), - or_secondary(km.sort_by_tx, "sort containers by tx"), - or_secondary( - km.log_section_height_decrease, - "decrease log section height", - ), - or_secondary( - km.log_section_height_increase, - "increase log section height", - ), - or_secondary(km.log_section_toggle, "toggle log section visibility"), - or_secondary(km.clear, "close dialog"), - or_secondary(km.quit, "quit at any time"), - ]; - - let mut lines = if show_timestamp { - Vec::from([Self::custom_text(colors, km, zone), Self::empty_span()]) - } else { - vec![] - }; - - lines.extend_from_slice(&descriptions); - let width = Self::calc_width(&lines); - let height = lines.len(); - - Self { - lines, - width, - height, + if let Some(path) = &config.dir_save { + entries.push(("export location: ", path.display().to_string())); } + if config.show_timestamp { + let tz = config + .timezone + .as_ref() + .and_then(|t| t.iana_name()) + .unwrap_or("Etc/UTC"); + entries.push((" logs timezone: ", tz.to_string())); + } + + let max_len = entries + .iter() + .map(|(_, val)| val.chars().count()) + .max() + .unwrap_or_default(); + + // 3. Map entries to Lines + let mut lines = entries + .into_iter() + .map(|(label, val)| { + let spacing = " ".repeat(max_len.saturating_sub(val.chars().count())); + Line::from(vec![ + Self::text_span(label.to_owned(), config.app_colors), + Self::highlighted_text_span(format!("{spacing}{val}"), config.app_colors), + ]) + .right_aligned() + }) + .collect::>(); + + lines.extend([ + Self::empty_line(), + Line::from(Self::text_span( + "a work in progress, all and any input appreciated".to_owned(), + config.app_colors, + )), + Self::highlighted_text_span(REPO.to_owned(), config.app_colors) + .underlined() + .into_centered_line(), + ]); + + let size = Self::calc_size(&lines); + Self { lines, size } } } -/// Draw the help box in the centre of the screen -pub fn draw( - colors: AppColors, +// Draw the oxker name on one half, other half shoe logs location, save location, timezone +fn draw_top_section( f: &mut Frame, - keymap: &Keymap, - show_timestamp: bool, - zone: Option<&TimeZone>, + area: Rect, + colors: AppColors, + style: Style, + name: HelpInfo, + config_dir: HelpInfo, ) { - let title = format!(" {VERSION} "); - - let name_info = HelpInfo::gen_name(colors); - let description_info = HelpInfo::gen_description(colors); - let final_info = HelpInfo::gen_final(colors); - - let button_info = if keymap == &Keymap::new() { - HelpInfo::gen_keymap_info(colors, zone, show_timestamp) - } else { - HelpInfo::gen_custom_keymap_info(colors, keymap, zone, show_timestamp) - }; - - let max_line_width = [ - name_info.width, - description_info.width, - button_info.width, - final_info.width, - ] - .into_iter() - .max() - .unwrap_or_default() - + 2; - - let max_height = - name_info.height + description_info.height + button_info.height + final_info.height + 2; - - let area = popup::draw( - max_height, - max_line_width, - f.area(), - BoxLocation::MiddleCentre, - ); - - let split_popup = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Max(name_info.height.try_into().unwrap_or_default()), - Constraint::Max(description_info.height.try_into().unwrap_or_default()), - Constraint::Max(button_info.height.try_into().unwrap_or_default()), - Constraint::Min(final_info.height.try_into().unwrap_or_default()), - ]) + let horizontal_split = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(area); - let name_paragraph = Paragraph::new(name_info.lines) + let name_paragraph = Paragraph::new(name.lines) .style( Style::default() .bg(colors.popup_help.background) @@ -412,71 +753,159 @@ pub fn draw( ) .alignment(Alignment::Center); - let style = || { - Style::default() - .bg(colors.popup_help.background) - .fg(colors.popup_help.text) + let location_top_padding = name.size.height.saturating_sub(config_dir.size.height); + + let right_vertical_split = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Max(location_top_padding), + Constraint::Max(config_dir.size.height), + ]) + .split(horizontal_split[1]); + + let config_paragraph = Paragraph::new(config_dir.lines).style(style); + + f.render_widget(name_paragraph, horizontal_split[0]); + f.render_widget(config_paragraph, right_vertical_split[1]); +} + +fn draw_keymap_title(f: &mut Frame, area: Rect, title: HelpInfo) { + f.render_widget(Paragraph::new(title.lines), area); +} + +fn draw_keymap(f: &mut Frame, area: Rect, style: Style, columns: (HelpInfo, HelpInfo)) { + // Calculate some padding + let horizontal_padding = area + .width + .saturating_sub(columns.0.size.width) + .saturating_sub(columns.1.size.width) + .saturating_div(2) + .saturating_sub(1); + + let horizontal_split = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Max(horizontal_padding), + Constraint::Max(columns.0.size.width), + Constraint::Max(2), + Constraint::Max(columns.1.size.width), + Constraint::Max(horizontal_padding), + ]) + .split(area); + + let left_column = Paragraph::new(columns.0.lines).style(style).left_aligned(); + let right_column = Paragraph::new(columns.1.lines).style(style).left_aligned(); + f.render_widget(left_column, horizontal_split[1]); + f.render_widget(right_column, horizontal_split[3]); +} + +pub fn draw(config: &Config, f: &mut Frame) { + let default_colors = config.app_colors == AppColors::new(); + let title = format!(" {VERSION} "); + let style = Style::default() + .bg(config.app_colors.popup_help.background) + .fg(config.app_colors.popup_help.text); + + let name_info = if default_colors { + DEFAULT_NAME.clone() + } else { + HelpInfo::gen_name_description(config.app_colors) }; - let description_paragraph = Paragraph::new(description_info.lines) - .style(style()) - .alignment(Alignment::Center); + let locations = HelpInfo::gen_locations(config); + let keymap_title = HelpInfo::gen_keymap_title(style); + let keymap_columns = HelpInfo::gen_keymap(config); - let help_paragraph = Paragraph::new(button_info.lines) - .style(style()) - .alignment(Alignment::Left); - - let final_paragraph = Paragraph::new(final_info.lines) - .style(style()) - .alignment(Alignment::Center); + let total_width = name_info + .size + .width + // Account for spacing between the two sections + .saturating_add(locations.size.width) + .saturating_add(2) + .max( + keymap_columns + .0 + .size + .width + .saturating_add(keymap_columns.1.size.width) + // Account for the spacing spacing between each column + .saturating_add(2), + ); + let top_height = name_info.size.height.max(locations.size.height); + let keymap_height = keymap_columns + .0 + .size + .height + .max(keymap_columns.1.size.height); + let total_height = top_height + .saturating_add(keymap_title.size.height) + .saturating_add(keymap_height); let block = Block::default() .title(title) .borders(Borders::ALL) .border_type(BorderType::Rounded) - .border_style( - Style::default() - .fg(colors.popup_help.text) - .bg(colors.popup_help.background), - ); + .border_style(style) + .style(style) + .padding(Padding::horizontal(1)); + + let area = popup::draw( + (total_height + 2).into(), + (total_width + 4).into(), + f.area(), + BoxLocation::MiddleCentre, + ); + + let inner_area = block.inner(area); + + let vertical_split = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(top_height), + Constraint::Length(keymap_title.size.height), + Constraint::Length(keymap_height), + ]) + .split(inner_area); - // Order is important here f.render_widget(Clear, area); - f.render_widget(name_paragraph, split_popup[0]); - f.render_widget(description_paragraph, split_popup[1]); - f.render_widget(help_paragraph, split_popup[2]); - f.render_widget(final_paragraph, split_popup[3]); f.render_widget(block, area); + draw_top_section( + f, + vertical_split[0], + config.app_colors, + style, + name_info, + locations, + ); + draw_keymap_title(f, vertical_split[1], keymap_title); + draw_keymap(f, vertical_split[2], style, keymap_columns); } #[cfg(test)] #[allow(clippy::unwrap_used, clippy::too_many_lines)] mod tests { + use std::path::PathBuf; + use crate::config::{AppColors, Keymap}; use crossterm::event::{KeyCode, KeyModifiers}; use insta::assert_snapshot; - use jiff::tz::TimeZone; - use ratatui::style::{Color, Modifier}; + use ratatui::style::Color; use crate::ui::draw_blocks::tests::{get_result, test_setup}; #[test] - /// This will cause issues once the version has more than the current 5 chars (0.5.0) /// This test is incredibly annoying /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); fn test_draw_blocks_help() { - let mut setup = test_setup(87, 39, true, true); - let tz = setup.app_data.lock().config.timezone.clone(); + let mut setup = test_setup(118, 25, true, true); + setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir")); + setup.app_data.lock().config.dir_config = + Some(PathBuf::from("/home/user/.config/oxker/config.toml")); + setup.app_data.lock().config.show_timestamp = true; setup .terminal .draw(|f| { - super::draw( - AppColors::new(), - f, - &setup.app_data.lock().config.keymap, - false, - tz.as_ref(), - ); + super::draw(&setup.app_data.lock().config, f); }) .unwrap(); @@ -485,100 +914,209 @@ mod tests { for (row_index, result_row) in get_result(&setup) { for (result_cell_index, result_cell) in result_row.iter().enumerate() { match (row_index, result_cell_index) { - // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 38, _) | (0..=37, 0 | 86) => { - assert_eq!(result_cell.bg, Color::Reset); - assert_eq!(result_cell.fg, Color::Reset); - } - // Buttons - (2..=10, 2..=84) - | (12, 19..=66) - | (14, 2..=10 | 13..=27) - | (15, 2..=10 | 13..=21 | 24..=37) - | (16 | 28 | 30, 2..=10) - | (19..=26 | 29 | 31, 2..=8) - | (17, 2..=11) - | (18 | 27, 2..=12) - | (24, 2..=9 | 12..=18) => { - assert_eq!(result_cell.bg, Color::Magenta); - assert_eq!(result_cell.fg, Color::White); - } - // The URL is white on yellow and underlined - (34, 25..=60) => { - assert_eq!(result_cell.bg, Color::Magenta); - assert_eq!(result_cell.fg, Color::White); - assert_eq!(result_cell.modifier, Modifier::UNDERLINED); - } - // The rest is black on magenta - _ => { - assert_eq!(result_cell.bg, Color::Magenta); - assert_eq!(result_cell.fg, Color::Black); - } - } + // The space around the popup + (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)), + // The borders + (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + // The oxker logo + // The description + (2..=10, 3..=58)| + // Config location + (5, 79..=114) | + // Export location + (6, 79..=114) | + // Timezone + (7, 79..=114) | + //url + (10, 69..=104) | + // Left column + (13..=22, 4..=24) | + // Right Column + (13..=21,59..=69) + => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)), + _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + }; } } } #[test] - /// Test that the help panel gets drawn with custom colors - /// This test is incredibly annoying - /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); - fn test_draw_blocks_help_custom_colors() { - let mut setup = test_setup(87, 39, true, true); - let mut colors = AppColors::new(); - let tz = setup.app_data.lock().config.timezone.clone(); - - colors.popup_help.background = Color::Black; - colors.popup_help.text = Color::Red; - colors.popup_help.text_highlight = Color::Yellow; + fn test_draw_blocks_help_no_config() { + let mut setup = test_setup(116, 25, true, true); + setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir")); + setup.app_data.lock().config.show_timestamp = true; setup .terminal .draw(|f| { - super::draw( - colors, - f, - &setup.app_data.lock().config.keymap, - false, - tz.as_ref(), - ); + super::draw(&setup.app_data.lock().config, f); }) .unwrap(); assert_snapshot!(setup.terminal.backend()); + for (row_index, result_row) in get_result(&setup) { for (result_cell_index, result_cell) in result_row.iter().enumerate() { match (row_index, result_cell_index) { - // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 38, _) | (0..=37, 0 | 86) => { - assert_eq!(result_cell.bg, Color::Reset); - assert_eq!(result_cell.fg, Color::Reset); - } - // Buttons - (2..=10, 2..=84) - | (12, 19..=66) - | (14, 2..=10 | 13..=27) - | (15, 2..=10 | 13..=21 | 24..=37) - | (16 | 28 | 30, 2..=10) - | (19..=26 | 29 | 31, 2..=8) - | (17, 2..=11) - | (18 | 27, 2..=12) - | (24, 2..=9 | 12..=18) => { - assert_eq!(result_cell.bg, Color::Black); - assert_eq!(result_cell.fg, Color::Yellow); - } - // The URL is white on yellow and underlined - (34, 25..=60) => { - assert_eq!(result_cell.bg, Color::Black); - assert_eq!(result_cell.fg, Color::Yellow); - assert_eq!(result_cell.modifier, Modifier::UNDERLINED); - } - // The rest is black on magenta - _ => { - assert_eq!(result_cell.bg, Color::Black); - assert_eq!(result_cell.fg, Color::Red); - } - } + // The space around the popup + (0|24, _) | (_, 0|115) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)), + // The borders + (1|23, 1..=23) | (_, 1|114) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + // The oxker logo + // The description + (2..=10, 3..=57)| + // Export location + (6, 104..=112) | + // Timezone + (7, 104..=112) | + //url + (10, 67..=102) | + // Left column + (13..=22, 3..=23) | + // Right Column + (13..=21,58..=68) + => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)), + _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + }; + } + } + } + + #[test] + fn test_draw_blocks_help_no_save() { + let mut setup = test_setup(118, 25, true, true); + setup.app_data.lock().config.dir_config = + Some(PathBuf::from("/home/user/.config/oxker/config.toml")); + setup.app_data.lock().config.show_timestamp = true; + + setup + .terminal + .draw(|f| { + super::draw(&setup.app_data.lock().config, f); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + // The space around the popup + (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)), + // The borders + (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + // The oxker logo + // The description + (2..=10, 3..=58)| + // Config location + (6, 79..=114) | + // Timezone + (7, 79..=114) | + //url + (10, 69..=104) | + // Left column + (13..=22, 4..=24) | + // Right Column + (13..=21,59..=69) + => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)), + _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + }; + } + } + } + + #[test] + fn test_draw_blocks_help_no_timezone() { + let mut setup = test_setup(118, 25, true, true); + setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir")); + setup.app_data.lock().config.dir_config = + Some(PathBuf::from("/home/user/.config/oxker/config.toml")); + + setup + .terminal + .draw(|f| { + super::draw(&setup.app_data.lock().config, f); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + // The space around the popup + (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)), + // The borders + (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + // The oxker logo + // The description + (2..=10, 3..=58)| + // Config location + (6, 79..=114) | + // Export location + (7, 79..=114) | + //url + (10, 69..=104) | + // Left column + (13..=22, 4..=24) | + // Right Column + (13..=21,59..=69) + => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)), + _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)), + }; + } + } + } + + #[test] + fn test_draw_blocks_help_custom_color() { + let mut setup = test_setup(118, 25, true, true); + setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir")); + setup.app_data.lock().config.dir_config = + Some(PathBuf::from("/home/user/.config/oxker/config.toml")); + setup.app_data.lock().config.show_timestamp = true; + + let mut colors = AppColors::new(); + colors.popup_help.background = Color::Black; + colors.popup_help.text = Color::Red; + colors.popup_help.text_highlight = Color::Yellow; + + setup.app_data.lock().config.app_colors = colors; + + setup + .terminal + .draw(|f| { + super::draw(&setup.app_data.lock().config, f); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + // The space around the popup + (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)), + // The borders + (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Black, Color::Red)), + // The oxker logo + // The description + (2..=10, 3..=58)| + // Config location + (5, 79..=114) | + // Export location + (6, 79..=114) | + // Timezone + (7, 79..=114) | + //url + (10, 69..=104) | + // Left column + (13..=22, 4..=24) | + // Right Column + (13..=21,59..=69) + => assert_eq!((result_cell.bg, result_cell.fg), (Color::Black, Color::Yellow)), + _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Black, Color::Red)), + }; } } } @@ -586,27 +1124,33 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with one definition for each entry fn test_draw_blocks_help_custom_keymap_one_definition() { - let mut setup = test_setup(98, 50, true, true); + let mut setup = test_setup(118, 25, true, true); - let input = Keymap { + setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir")); + setup.app_data.lock().config.dir_config = + Some(PathBuf::from("/home/user/.config/oxker/config.toml")); + setup.app_data.lock().config.show_timestamp = true; + + let keymap = Keymap { clear: (KeyCode::Char('a'), None), delete_confirm: (KeyCode::Char('b'), None), delete_deny: (KeyCode::Char('c'), None), exec: (KeyCode::Char('d'), None), - filter_mode: (KeyCode::Char('e'), None), - log_search_mode: (KeyCode::Char('7'), None), - force_redraw: (KeyCode::Char('f'), None), - log_scroll_back: (KeyCode::Char('g'), None), - log_scroll_forward: (KeyCode::Char('h'), None), - log_section_height_decrease: (KeyCode::Char('i'), None), - log_section_height_increase: (KeyCode::Char('j'), None), - log_section_toggle: (KeyCode::Char('k'), None), - quit: (KeyCode::Char('l'), None), - save_logs: (KeyCode::Char('m'), None), - scroll_down: (KeyCode::Char('o'), None), - scroll_end: (KeyCode::Char('p'), None), + inspect: (KeyCode::Char('e'), None), + filter_mode: (KeyCode::Char('f'), None), + log_search_mode: (KeyCode::Char('g'), None), + force_redraw: (KeyCode::Char('h'), None), + scroll_back: (KeyCode::Char('i'), None), + scroll_forward: (KeyCode::Char('j'), None), + log_section_height_decrease: (KeyCode::Char('k'), None), + log_section_height_increase: (KeyCode::Char('l'), None), + log_section_toggle: (KeyCode::Char('m'), None), + quit: (KeyCode::Char('n'), None), + save_logs: (KeyCode::Char('o'), None), + scroll_down: (KeyCode::Char('p'), None), + scroll_end: (KeyCode::Char('q'), None), scroll_many: KeyModifiers::ALT, - scroll_start: (KeyCode::Char('q'), None), + scroll_start: (KeyCode::Char('r'), None), scroll_up: (KeyCode::Char('s'), None), select_next_panel: (KeyCode::Char('t'), None), select_previous_panel: (KeyCode::Char('u'), None), @@ -624,10 +1168,12 @@ mod tests { toggle_mouse_capture: (KeyCode::Char('6'), None), }; + setup.app_data.lock().config.keymap = keymap; + setup .terminal .draw(|f| { - super::draw(AppColors::new(), f, &input, false, None); + super::draw(&setup.app_data.lock().config, f); }) .unwrap(); @@ -635,50 +1181,58 @@ mod tests { } #[test] - /// Help panel will show custom keymap if in use, with two definition for each entry - fn test_draw_blocks_help_custom_keymap_two_definitions() { - let mut setup = test_setup(110, 50, true, true); + /// Help panel will show custom keymap if in use, with two definitions for each entry + fn test_draw_blocks_help_custom_keymap_two_definition() { + let mut setup = test_setup(124, 30, true, true); + + setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir")); + setup.app_data.lock().config.dir_config = + Some(PathBuf::from("/home/user/.config/oxker/config.toml")); + setup.app_data.lock().config.show_timestamp = true; let keymap = Keymap { - clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))), - delete_confirm: (KeyCode::Char('b'), Some(KeyCode::Char('B'))), - delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), - exec: (KeyCode::Char('d'), Some(KeyCode::Char('D'))), - filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), - log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), - force_redraw: (KeyCode::Char('f'), Some(KeyCode::Char('F'))), - log_scroll_back: (KeyCode::Char('f'), Some(KeyCode::Char('F'))), - log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), - log_section_height_decrease: (KeyCode::Char('h'), Some(KeyCode::Char('H'))), - log_section_height_increase: (KeyCode::Char('i'), Some(KeyCode::Char('I'))), - log_section_toggle: (KeyCode::Char('j'), Some(KeyCode::Char('J'))), - quit: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), - save_logs: (KeyCode::Char('l'), Some(KeyCode::Char('L'))), - scroll_down: (KeyCode::Char('n'), Some(KeyCode::Char('N'))), - scroll_end: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), + clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), + delete_confirm: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), + delete_deny: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), + exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), + inspect: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), + filter_mode: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), + log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), + force_redraw: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), + scroll_back: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), + scroll_forward: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), + log_section_height_decrease: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), + log_section_height_increase: (KeyCode::Char('w'), Some(KeyCode::Char('x'))), + log_section_toggle: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), + quit: (KeyCode::Char('0'), Some(KeyCode::Char('1'))), + save_logs: (KeyCode::Char('2'), Some(KeyCode::Char('3'))), + scroll_down: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), + scroll_end: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), scroll_many: KeyModifiers::ALT, - scroll_start: (KeyCode::Char('p'), Some(KeyCode::Char('P'))), - scroll_up: (KeyCode::Char('r'), Some(KeyCode::Char('R'))), - select_next_panel: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), - select_previous_panel: (KeyCode::Char('t'), Some(KeyCode::Char('T'))), - sort_by_cpu: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), - sort_by_id: (KeyCode::Char('v'), Some(KeyCode::Char('V'))), - sort_by_image: (KeyCode::Char('w'), Some(KeyCode::Char('W'))), - sort_by_memory: (KeyCode::Char('x'), Some(KeyCode::Char('X'))), - sort_by_name: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))), - sort_by_rx: (KeyCode::Char('z'), Some(KeyCode::Char('Z'))), - sort_by_state: (KeyCode::Char('0'), Some(KeyCode::Char('9'))), - sort_by_status: (KeyCode::Char('1'), Some(KeyCode::Char('8'))), - sort_by_tx: (KeyCode::Char('2'), Some(KeyCode::Char('7'))), - sort_reset: (KeyCode::Char('3'), Some(KeyCode::Char('6'))), - toggle_help: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), - toggle_mouse_capture: (KeyCode::Char('5'), Some(KeyCode::PageDown)), + scroll_start: (KeyCode::Char('8'), Some(KeyCode::Char('9'))), + scroll_up: (KeyCode::CapsLock, Some(KeyCode::ScrollLock)), + select_next_panel: (KeyCode::PrintScreen, Some(KeyCode::Right)), + select_previous_panel: (KeyCode::Left, Some(KeyCode::Up)), + sort_by_cpu: (KeyCode::Down, Some(KeyCode::Delete)), + sort_by_id: (KeyCode::BackTab, Some(KeyCode::Backspace)), + sort_by_image: (KeyCode::End, Some(KeyCode::Esc)), + sort_by_memory: (KeyCode::Home, Some(KeyCode::Insert)), + sort_by_name: (KeyCode::KeypadBegin, Some(KeyCode::Menu)), + sort_by_rx: (KeyCode::NumLock, Some(KeyCode::PageDown)), + sort_by_state: (KeyCode::PageUp, Some(KeyCode::Pause)), + sort_by_status: (KeyCode::PrintScreen, Some(KeyCode::Tab)), + sort_by_tx: (KeyCode::F(1), Some(KeyCode::F(2))), + sort_reset: (KeyCode::F(3), Some(KeyCode::F(4))), + toggle_help: (KeyCode::F(5), Some(KeyCode::F(6))), + toggle_mouse_capture: (KeyCode::F(7), Some(KeyCode::F(8))), }; + setup.app_data.lock().config.keymap = keymap; + setup .terminal .draw(|f| { - super::draw(AppColors::new(), f, &keymap, false, None); + super::draw(&setup.app_data.lock().config, f); }) .unwrap(); @@ -686,90 +1240,61 @@ mod tests { } #[test] - /// Help panel will show custom keymap if in use, with either one or two definition for each entry - #[allow(clippy::todo)] - fn test_draw_blocks_help_one_and_two_definitions() { - let mut setup = test_setup(110, 50, true, true); + /// Help panel will show custom keymap if in use, with one or two definitions for each entry + fn test_draw_blocks_help_custom_keymap_one_two_definition() { + let mut setup = test_setup(124, 30, true, true); + + setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir")); + setup.app_data.lock().config.dir_config = + Some(PathBuf::from("/home/user/.config/oxker/config.toml")); + setup.app_data.lock().config.show_timestamp = true; let keymap = Keymap { - clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))), - delete_confirm: (KeyCode::Char('b'), None), - delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), - exec: (KeyCode::Char('d'), None), - filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), - log_search_mode: (KeyCode::Char('8'), None), - force_redraw: (KeyCode::Char('f'), None), - log_scroll_back: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), - log_scroll_forward: (KeyCode::Char('h'), None), - log_section_height_decrease: (KeyCode::Char('i'), Some(KeyCode::Char('I'))), - log_section_height_increase: (KeyCode::Char('j'), None), - log_section_toggle: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), - quit: (KeyCode::Char('l'), None), - save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), - scroll_down: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), - scroll_end: (KeyCode::Char('p'), None), + clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), + delete_confirm: (KeyCode::Char('c'), None), + delete_deny: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), + exec: (KeyCode::Char('g'), None), + inspect: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), + filter_mode: (KeyCode::Char('k'), None), + log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), + force_redraw: (KeyCode::Char('o'), None), + scroll_back: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), + scroll_forward: (KeyCode::Char('s'), None), + log_section_height_decrease: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), + log_section_height_increase: (KeyCode::Char('w'), None), + log_section_toggle: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), + quit: (KeyCode::Char('0'), None), + save_logs: (KeyCode::Char('2'), Some(KeyCode::Char('3'))), + scroll_down: (KeyCode::Char('4'), None), + scroll_end: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), scroll_many: KeyModifiers::ALT, - scroll_start: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))), - scroll_up: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), - select_next_panel: (KeyCode::Char('t'), None), - select_previous_panel: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), - sort_by_cpu: (KeyCode::Char('v'), None), - sort_by_id: (KeyCode::Char('w'), Some(KeyCode::Char('W'))), - sort_by_image: (KeyCode::Char('x'), None), - sort_by_memory: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))), - sort_by_name: (KeyCode::Char('z'), None), - sort_by_rx: (KeyCode::Char('0'), Some(KeyCode::Char('9'))), - sort_by_state: (KeyCode::Char('1'), None), - sort_by_status: (KeyCode::Char('2'), Some(KeyCode::Char('7'))), - sort_by_tx: (KeyCode::Char('3'), None), - sort_reset: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), - toggle_help: (KeyCode::Char('5'), None), - toggle_mouse_capture: (KeyCode::Char('6'), Some(KeyCode::Char('#'))), + scroll_start: (KeyCode::Char('8'), None), + scroll_up: (KeyCode::CapsLock, Some(KeyCode::ScrollLock)), + select_next_panel: (KeyCode::PrintScreen, None), + select_previous_panel: (KeyCode::Left, Some(KeyCode::Up)), + sort_by_cpu: (KeyCode::Down, None), + sort_by_id: (KeyCode::BackTab, None), + sort_by_image: (KeyCode::End, Some(KeyCode::Esc)), + sort_by_memory: (KeyCode::Home, None), + sort_by_name: (KeyCode::KeypadBegin, Some(KeyCode::Menu)), + sort_by_rx: (KeyCode::NumLock, None), + sort_by_state: (KeyCode::PageUp, Some(KeyCode::Pause)), + sort_by_status: (KeyCode::PrintScreen, None), + sort_by_tx: (KeyCode::F(1), Some(KeyCode::F(2))), + sort_reset: (KeyCode::F(3), None), + toggle_help: (KeyCode::F(5), Some(KeyCode::F(6))), + toggle_mouse_capture: (KeyCode::F(7), None), }; - let tz = setup.app_data.lock().config.timezone.clone(); + setup.app_data.lock().config.keymap = keymap; setup .terminal .draw(|f| { - super::draw(AppColors::new(), f, &keymap, false, tz.as_ref()); + super::draw(&setup.app_data.lock().config, f); }) .unwrap(); assert_snapshot!(setup.terminal.backend()); } - - #[test] - fn test_draw_blocks_help_show_timezone() { - let mut setup = test_setup(87, 39, true, true); - - setup - .terminal - .draw(|f| { - super::draw( - AppColors::new(), - f, - &Keymap::new(), - true, - Some(&TimeZone::get("asia/tokyo").unwrap()), - ); - }) - .unwrap(); - - assert_snapshot!(setup.terminal.backend()); - - for (row_index, result_row) in get_result(&setup) { - for (result_cell_index, result_cell) in result_row.iter().enumerate() { - match (row_index, result_cell_index) { - (13, 31..=45) => { - assert_eq!(result_cell.fg, AppColors::new().popup_help.text); - } - (13, 46..=55) => { - assert_eq!(result_cell.fg, AppColors::new().popup_help.text_highlight); - } - _ => (), - } - } - } - } } diff --git a/src/ui/draw_blocks/inspect.rs b/src/ui/draw_blocks/inspect.rs new file mode 100644 index 0000000..24b6483 --- /dev/null +++ b/src/ui/draw_blocks/inspect.rs @@ -0,0 +1,782 @@ +use std::sync::Arc; + +use parking_lot::Mutex; +use ratatui::{ + Frame, + layout::Rect, + style::{Style, Stylize}, + text::Line, + widgets::{Block, Borders, Paragraph, Wrap}, +}; + +use crate::{ + app_data::InspectData, + config::{AppColors, Keymap}, + ui::{ + GuiState, + draw_blocks::{DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, UP_ARROW}, + gui_state::ScrollOffset, + }, +}; + +/// Create a bordered block with a title. +fn title_block<'a>(upper_title: &'a str, lower_title: &'a str, colors: &AppColors) -> Block<'a> { + Block::default() + .borders(Borders::all()) + .border_type(ratatui::widgets::BorderType::Rounded) + .border_style(Style::default().fg(colors.borders.selected)) + .title(upper_title.bold().into_centered_line()) + .title_bottom(lower_title.bold().into_centered_line()) +} + +/// Create the upper title, with container name, id, and keymap to clear +fn generate_upper_title(data: &InspectData, keymap: &Keymap) -> String { + let mut output = String::from(" inspecting: "); + let name = if data.name.starts_with("/") { + data.name.replacen('/', "", 1) + } else { + data.name.clone() + }; + + output.push_str(&format!("{} {} ", name, data.id.get_short())); + let mut inspect_key = keymap.inspect.0.to_string(); + if let Some(x) = keymap.inspect.1 { + inspect_key.push_str(&format!(" or {x}")); + } + let mut clear_key = keymap.clear.0.to_string(); + if let Some(x) = keymap.clear.1 { + clear_key.push_str(&format!(" or {x}")); + } + output.push_str(&format!(" - {clear_key} or {inspect_key} to exit")); + output.push(' '); + output +} + +/// Generate the lower title, with the current scroll and the scrolling limits +fn generate_lower_title(length: usize, width: usize, offset: ScrollOffset) -> String { + let length_width = length + .to_string() + .chars() + .count() + .max(offset.y.to_string().chars().count()); + let width_width = width + .to_string() + .chars() + .count() + .max(offset.x.to_string().chars().count()); + + let left_arrow = if offset.x == 0 { " " } else { LEFT_ARROW }; + let right_arrow = if offset.x == width { " " } else { RIGHT_ARROW }; + let up_arrow = if offset.y == 0 { " " } else { UP_ARROW }; + let down_arrow = if offset.y == length { " " } else { DOWN_ARROW }; + + format!( + " {up_arrow} {:>length_width$}/{:>length_width$} {down_arrow} {left_arrow} {:>width_width$}/{:>width_width$} {right_arrow} ", + offset.y, length, offset.x, width + ) +} + +/// Generate the Lines, remove lines & chars based on the offset and viewport +fn gen_lines<'a>(data_as_str: &'a str, offset: &ScrollOffset, rect: &Rect) -> Vec> { + let first_line_index = offset.y.max(0); + let first_char_index = offset.x.max(0); + let last_char_index = usize::from(rect.width.saturating_sub(2)); + let take_lines = usize::from(rect.height); + //todo see if log scrolling does this - What? + + data_as_str + .lines() + .skip(first_line_index) + .take(take_lines) + .map(|line| { + Line::from( + line.chars() + .skip(first_char_index) + .take(last_char_index) + .collect::(), + ) + }) + .collect() +} + +/// Draw the InspectContainer widget to the entire screen +pub fn draw( + f: &mut Frame, + colors: AppColors, + data: InspectData, + gui_state: &Arc>, + keymap: &Keymap, +) { + let rect = f.area(); + let offset = gui_state.lock().get_inspect_offset(); + // +2 to account for the border + let height = data + .height + .saturating_sub(usize::from(rect.height)) + .saturating_add(2); + let width = data + .width + .saturating_sub(usize::from(rect.width)) + .saturating_add(2); + let upper_title = generate_upper_title(&data, keymap); + let lower_title = generate_lower_title(height, width, offset); + + gui_state.lock().set_inspect_offset_max(ScrollOffset { + x: width, + y: height, + }); + + let paragraph = Paragraph::new(gen_lines(&data.as_string, &offset, &rect)) + .block(title_block(&upper_title, &lower_title, &colors)) + .gray() + .left_aligned() + .wrap(Wrap { trim: false }); + f.render_widget(paragraph, rect); +} + +// TODO TESTS +// Test keymap +// Test colors +// Test offset y & x + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use std::{collections::HashMap, sync::LazyLock}; + + use crate::{ + app_data::InspectData, + config::{AppColors, Keymap}, + ui::draw_blocks::tests::{get_result, test_setup}, + }; + use bollard::secret::{ + ContainerConfig, ContainerInspectResponse, ContainerState, ContainerStateStatusEnum, + DriverData, EndpointSettings, HostConfig, HostConfigLogConfig, MountPoint, + MountPointTypeEnum, NetworkSettings, RestartPolicy, RestartPolicyNameEnum, + }; + use crossterm::event::KeyCode; + use insta::assert_snapshot; + use ratatui::style::Color; + + static INSPECT_DATA: LazyLock = + LazyLock::new(|| InspectData::from(gen_container_inspect_response())); + + #[test] + /// Test a inspect container with default settings, keymap, and position + fn test_draw_blocks_inspect_default_valid() { + let mut setup = test_setup(100, 50, true, true); + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &Keymap::new(), + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + // Assert border colors + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Test a inspect container with custom colors + fn test_draw_blocks_inspect_custom_color() { + let mut setup = test_setup(100, 50, true, true); + + let mut colors = AppColors::new(); + colors.borders.selected = Color::Red; + setup + .terminal + .draw(|f| { + super::draw( + f, + colors, + INSPECT_DATA.clone(), + &setup.gui_state, + &Keymap::new(), + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + // Assert custom border colors + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Red); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Test a inspect container with custom keymap for one clear key + fn test_draw_blocks_inspect_custom_keymap_clear_one() { + let mut setup = test_setup(100, 50, true, true); + + let mut keymap = Keymap::new(); + + keymap.clear.0 = KeyCode::Char('F'); + + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &keymap, + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + // Assert border colors + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Test a inspect container with custom keymap for both clear keys + fn test_draw_blocks_inspect_custom_keymap_clear_two() { + let mut setup = test_setup(100, 50, true, true); + + let mut keymap = Keymap::new(); + + keymap.clear.0 = KeyCode::Char('F'); + keymap.clear.1 = Some(KeyCode::Char('Z')); + + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &keymap, + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + // Assert border colors + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Test a inspect container with custom keymap for one inspect key + fn test_draw_blocks_inspect_custom_keymap_inspect_one() { + let mut setup = test_setup(100, 50, true, true); + + let mut keymap = Keymap::new(); + + keymap.inspect.0 = KeyCode::Char('4'); + + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &keymap, + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + // Assert border colors + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Test a inspect container with custom keymap for both inspect keys + fn test_draw_blocks_inspect_custom_keymap_inspect_two() { + let mut setup = test_setup(100, 50, true, true); + + let mut keymap = Keymap::new(); + + keymap.inspect.0 = KeyCode::Char('4'); + keymap.inspect.1 = Some(KeyCode::Char('5')); + + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &keymap, + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + // Assert border colors + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Test a inspect container with all custom keymaps + fn test_draw_blocks_inspect_custom_keymap_all() { + let mut setup = test_setup(100, 50, true, true); + + let mut keymap = Keymap::new(); + + keymap.clear.0 = KeyCode::Char('F'); + keymap.clear.1 = Some(KeyCode::Char('Z')); + keymap.inspect.0 = KeyCode::Char('4'); + keymap.inspect.1 = Some(KeyCode::Char('5')); + + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &keymap, + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + // Assert border colors + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Inspect details are offset 10 in x and y axis + fn test_draw_blocks_inspect_offset() { + let mut setup = test_setup(100, 50, true, true); + + // Why does one need to draw first, although it *should* be impossible to scroll before an inital drawing + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &Keymap::new(), + ); + }) + .unwrap(); + + { + let mut gui_state = setup.gui_state.lock(); + for _ in 0..=9 { + gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Down); + gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Right); + } + } + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &Keymap::new(), + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + #[test] + /// Inspect details are offset to the maximum allowed + fn test_draw_blocks_inspect_offset_max() { + let mut setup = test_setup(100, 50, true, true); + + // Why does one need to draw first, although it *should* be impossible to scroll before an inital drawing + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &Keymap::new(), + ); + }) + .unwrap(); + + // Lazy way of getting the max offset + { + let mut gui_state = setup.gui_state.lock(); + for _ in 0..=1000 { + gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Down); + gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Right); + } + } + setup + .terminal + .draw(|f| { + super::draw( + f, + AppColors::new(), + INSPECT_DATA.clone(), + &setup.gui_state, + &Keymap::new(), + ); + }) + .unwrap(); + assert_snapshot!(setup.terminal.backend()); + + for (row_index, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match (row_index, result_cell_index) { + (0 | 49, _) | (_, 0 | 99) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::LightCyan); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + } + } + } + } + + fn gen_container_inspect_response() -> ContainerInspectResponse { + ContainerInspectResponse { + id: Some("0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7".to_owned()), + created: Some("2026-01-23T22:20:19.927967311Z".to_owned()), + path: Some("docker-entrypoint.sh".to_owned()), + args: Some(vec!["postgres".to_owned()]), + state: Some(ContainerState { + status: Some(ContainerStateStatusEnum::RUNNING), + running: Some(true), + paused: Some(false), + restarting: Some(false), + oom_killed: Some(false), + dead: Some(false), + pid: Some(782), + exit_code: Some(0), + error: Some("".to_owned()), + started_at: Some("2026-01-30T08:09:01.574885915Z".to_owned()), + finished_at: Some("2026-01-30T08:09:01.180567927Z".to_owned()), + health: None, + }), + image: Some("sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352".to_owned()), + resolv_conf_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/resolv.conf".to_owned()), + hostname_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/hostname".to_owned()), + hosts_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/hosts".to_owned()), + log_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7-json.log".to_owned()), + name: Some("/postgres".to_owned()), + restart_count: Some(0), + driver: Some("overlay2".to_owned()), + platform: Some("linux".to_owned()), + image_manifest_descriptor: None, + mount_label: Some("".to_owned()), + process_label: Some("".to_owned()), + app_armor_profile: Some("".to_owned()), + exec_ids: None, + host_config: Some(HostConfig { + cpu_shares: Some(0), + memory: Some(1073741824), + cgroup_parent: Some("".to_owned()), + blkio_weight: Some(0), + blkio_weight_device: None, + blkio_device_read_bps: None, + blkio_device_write_bps: None, + blkio_device_read_iops: None, + blkio_device_write_iops: None, + cpu_period: Some(0), + cpu_quota: Some(0), + cpu_realtime_period: Some(0), + cpu_realtime_runtime: Some(0), + cpuset_cpus: Some("".to_owned()), + cpuset_mems: Some("".to_owned()), + devices: None, + device_cgroup_rules: None, + device_requests: None, + memory_reservation: Some(0), + memory_swap: Some(2147483648), + memory_swappiness: None, + nano_cpus: Some(0), + oom_kill_disable: Some(false), + init: None, + pids_limit: None, + ulimits: None, + cpu_count: Some(0), + cpu_percent: Some(0), + io_maximum_iops: Some(0), + io_maximum_bandwidth: Some(0), + binds: None, + container_id_file: Some("".to_owned()), + log_config: Some(HostConfigLogConfig { + typ: Some("json-file".to_owned()), + config: Some(HashMap::new()), + }), + network_mode: Some("oxker-examaple-net".to_owned()), + port_bindings: Some(HashMap::new()), + restart_policy: Some(RestartPolicy { + name: Some(RestartPolicyNameEnum::ALWAYS), + maximum_retry_count: Some(0), + }), + auto_remove: Some(false), + volume_driver: Some("".to_owned()), + volumes_from: None, + mounts: None, + console_size: Some(vec![0, 0]), + annotations: None, + cap_add: None, + cap_drop: None, + cgroupns_mode: Some(bollard::secret::HostConfigCgroupnsModeEnum::HOST), + dns: Some(vec![]), + dns_options: Some(vec![]), + dns_search: Some(vec![]), + extra_hosts: Some(vec![]), + group_add: None, + ipc_mode: Some("private".to_owned()), + cgroup: Some("".to_owned()), + links: None, + oom_score_adj: Some(0), + pid_mode: Some("".to_owned()), + privileged: Some(false), + publish_all_ports: Some(false), + readonly_rootfs: Some(false), + security_opt: None, + storage_opt: None, + tmpfs: None, + uts_mode: Some("".to_owned()), + userns_mode: Some("".to_owned()), + shm_size: Some(268435456), + sysctls: None, + runtime: Some("runc".to_owned()), + isolation: Some(bollard::secret::HostConfigIsolationEnum::EMPTY), + masked_paths: Some(vec![ + "/proc/acpi".to_owned(), + "/proc/asound".to_owned(), + "/proc/interrupts".to_owned(), + "/proc/kcore".to_owned(), + "/proc/keys".to_owned(), + "/proc/latency_stats".to_owned(), + "/proc/sched_debug".to_owned(), + "/proc/scsi".to_owned(), + "/proc/timer_list".to_owned(), + "/proc/timer_stats".to_owned(), + "/sys/devices/virtual/powercap".to_owned(), + "/sys/firmware".to_owned(), + ]), + readonly_paths: Some(vec![ + "/proc/bus".to_owned(), + "/proc/fs".to_owned(), + "/proc/irq".to_owned(), + "/proc/sys".to_owned(), + "/proc/sysrq-trigger".to_owned(), + ]), + }), + graph_driver: Some(DriverData { + name: "overlay2".to_owned(), + data: HashMap::from([ + ("LowerDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea-init/diff:/var/lib/docker/overlay2/51b93846f7ba3e00cb1ed86564e3e1d7c30df2bb1cd5a8469d54625f1e5a2eca/diff:/var/lib/docker/overlay2/c1364ead843d3af87ce286013b6301329d3089422b22b001e156e45d29b5b4dd/diff:/var/lib/docker/overlay2/0e6dc322cad77b1db3906a3a4e5e6d6b80fbffd138437e550d8849fcf4f4c1f2/diff:/var/lib/docker/overlay2/cc0f967a7471cf06e0c9ad3d474650c668a4cf0c02efe20e9c250c436f93033b/diff:/var/lib/docker/overlay2/5c59e0919969987c96a5d0e0a512a0a1a0f67ea747596af9a9c14a9566198d91/diff:/var/lib/docker/overlay2/d7709b7685c9704e1e392c515b6155517270541f6ccde426ef784403e1681fca/diff:/var/lib/docker/overlay2/c891528563fff91bffaf07416e77bcd3bdebb03e5d32ed0e3d4ee1ec5e80e880/diff:/var/lib/docker/overlay2/2b25c179a432c35cc599a082cd709c8c9a1523f8d1959f72fda21fc76e50ad00/diff:/var/lib/docker/overlay2/3b409d2f7a2455578148892302823a7f03c7c36482d08bb68fd6c1aeeec05f05/diff:/var/lib/docker/overlay2/55dbb2fab0ae8bb3bfe8183093cdd576686f7333e2b2c41e6e4178a7b6407554/diff".to_owned()), + ("MergedDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/merged".to_owned()), + ("WorkDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/work".to_owned()), + ("ID".to_owned(), "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7".to_owned()), + ("UpperDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/diff".to_owned()), + ]), + }), + storage: None, + size_rw: None, + size_root_fs: None, + mounts: Some(vec![MountPoint { + typ: Some(MountPointTypeEnum::VOLUME), + name: Some("93bc4e4c8d3823964b58105a99a7b3a7e02c801d5560338bdaf7589966a1b02d".to_owned()), + source: Some("/var/lib/docker/volumes/93bc4e4c8d3823964b58105a99a7b3a7e02c801d5560338bdaf7589966a1b02d/_data".to_owned()), + destination: Some("/var/lib/postgresql/data".to_owned()), + driver: Some("local".to_owned()), + mode: Some("".to_owned()), + rw: Some(true), + propagation: Some("".to_owned()), + }]), + config: Some(ContainerConfig { + hostname: Some("0bdea64212f9".to_owned()), + domainname: Some("".to_owned()), + user: Some("".to_owned()), + attach_stdin: Some(false), + attach_stdout: Some(true), + attach_stderr: Some(true), + exposed_ports: Some(vec!["5432/tcp".to_owned()]), + tty: Some(false), + open_stdin: Some(false), + stdin_once: Some(false), + env: Some(vec![ + "POSTGRES_PASSWORD=never_use_this_password_in_production".to_owned(), + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_owned(), + "GOSU_VERSION=1.19".to_owned(), + "LANG=en_US.utf8".to_owned(), + "PG_MAJOR=17".to_owned(), + "PG_VERSION=17.7".to_owned(), + "PG_SHA256=ef9e343302eccd33112f1b2f0247be493cb5768313adeb558b02de8797a2e9b5".to_owned(), + "DOCKER_PG_LLVM_DEPS=llvm19-dev \t\tclang19".to_owned(), + "PGDATA=/var/lib/postgresql/data".to_owned(), + ]), + cmd: Some(vec!["postgres".to_owned()]), + healthcheck: None, + args_escaped: None, + image: Some("postgres:17-alpine".to_owned()), + volumes: Some(vec!["/var/lib/postgresql/data".to_owned()]), + working_dir: Some("/".to_owned()), + entrypoint: Some(vec!["docker-entrypoint.sh".to_owned()]), + network_disabled: None, + on_build: None, + labels: Some(HashMap::from([ + ("com.docker.compose.oneoff".to_owned(), "False".to_owned()), + ("com.docker.compose.project.config_files".to_owned(), "/workspaces/oxker/docker/docker-compose.yml".to_owned()), + ("com.docker.compose.image".to_owned(), "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352".to_owned()), + ("com.docker.compose.project.working_dir".to_owned(), "/workspaces/oxker/docker".to_owned()), + ("com.docker.compose.service".to_owned(), "postgres".to_owned()), + ("com.docker.compose.config-hash".to_owned(), "e06d69ffb3f9b69dd51b356b60c2297df57caf0da16792ccafaabffdb920e443".to_owned()), + ("com.docker.compose.depends_on".to_owned(), "".to_owned()), + ("com.docker.compose.container-number".to_owned(), "1".to_owned()), + ("com.docker.compose.version".to_owned(), "2.40.3".to_owned()), + ("com.docker.compose.project".to_owned(), "docker".to_owned()), + ])), + stop_signal: Some("SIGINT".to_owned()), + stop_timeout: None, + shell: None, + }), + network_settings: Some(NetworkSettings { + sandbox_id: Some("dab64a66594dd8d06478184e2928c81acdcd9c931f643bd5ca62b7edb6345f8d".to_owned()), + sandbox_key: Some("/var/run/docker/netns/dab64a66594d".to_owned()), + ports: Some(HashMap::from([("5432/tcp".to_owned(), None)])), + networks: Some(HashMap::from([( + "oxker-examaple-net".to_owned(), + EndpointSettings { + ipam_config: None, + links: None, + mac_address: Some("a2:bd:4e:61:25:c7".to_owned()), + aliases: Some(vec!["postgres".to_owned(), "postgres".to_owned()]), + driver_opts: None, + gw_priority: Some(0), + network_id: Some("3cbeb475d81676f89a7aa205d8749ec2ad78d685e45d77b638992956f6dc569a".to_owned()), + endpoint_id: Some("31718069b2a3ea77487f3ece36b014d5d1329bc3294568e2621e5c0999071bed".to_owned()), + gateway: Some("172.19.0.1".to_owned()), + ip_address: Some("172.19.0.4".to_owned()), + ip_prefix_len: Some(16), + ipv6_gateway: Some("".to_owned()), + global_ipv6_address: Some("".to_owned()), + global_ipv6_prefix_len: Some(0), + dns_names: Some(vec!["postgres".to_owned(), "0bdea64212f9".to_owned()]), + }, + )])), + }), +} + } +} diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs index ebf6ec7..98b643f 100644 --- a/src/ui/draw_blocks/logs.rs +++ b/src/ui/draw_blocks/logs.rs @@ -14,7 +14,7 @@ use crate::{ ui::{FrameData, GuiState, SelectablePanel, Status}, }; -use super::{RIGHT_ARROW, generate_block}; +use super::{SELECT_ARROW, generate_block}; /// Draw the logs panel pub fn draw( @@ -52,7 +52,7 @@ pub fn draw( } else if fd.color_logs { let items = List::new(logs) .block(block) - .highlight_symbol(RIGHT_ARROW) + .highlight_symbol(SELECT_ARROW) .scroll_padding(padding) .highlight_style(Style::default().add_modifier(Modifier::BOLD)); // This should always return Some, as logs is not empty @@ -63,7 +63,7 @@ pub fn draw( let items = List::new(logs) .fg(colors.logs.text) .block(block) - .highlight_symbol(RIGHT_ARROW) + .highlight_symbol(SELECT_ARROW) .highlight_style(Style::default().add_modifier(Modifier::BOLD)); // This should always return Some, as logs is not empty if let Some(log_state) = app_data.lock().get_log_state() { @@ -309,7 +309,7 @@ mod tests { ); }) .unwrap(); - setup.app_data.lock().log_scroll(&ScrollDirection::Previous); + setup.app_data.lock().log_scroll(&ScrollDirection::Up); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index 08054a9..de4fdc6 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -20,26 +20,30 @@ pub mod filter; pub mod headers; pub mod help; pub mod info; +pub mod inspect; pub mod logs; pub mod popup; pub mod ports; pub mod search_logs; -pub const NAME_TEXT: &str = r#" - 88 - 88 - 88 - ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, -a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 -8b d8 )888( 8888[ 8PP""""""" 88 -"8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 - `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 "#; +pub const NAME_TEXT: &str = r#" 88 + 88 + ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, +a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 +8b d8 )888( 8888( 8PP""""""" 88 +"8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 + `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 "#; pub const NAME: &str = env!("CARGO_PKG_NAME"); pub const REPO: &str = env!("CARGO_PKG_REPOSITORY"); pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); pub const MARGIN: &str = " "; -pub const RIGHT_ARROW: &str = "โ–ถ "; +pub const SELECT_ARROW: &str = "โ–ถ "; +// TODO use me all over the place +pub const LEFT_ARROW: &str = "โ†"; +pub const RIGHT_ARROW: &str = "โ†’"; +pub const DOWN_ARROW: &str = "โ†“"; +pub const UP_ARROW: &str = "โ†‘"; pub const CIRCLE: &str = "โšช "; #[cfg(not(test))] diff --git a/src/ui/draw_blocks/search_logs.rs b/src/ui/draw_blocks/search_logs.rs index 81b1e31..335aa31 100644 --- a/src/ui/draw_blocks/search_logs.rs +++ b/src/ui/draw_blocks/search_logs.rs @@ -241,11 +241,11 @@ mod tests { setup .app_data .lock() - .log_scroll(&crate::app_data::ScrollDirection::Previous); + .log_scroll(&crate::app_data::ScrollDirection::Up); setup .app_data .lock() - .log_scroll(&crate::app_data::ScrollDirection::Previous); + .log_scroll(&crate::app_data::ScrollDirection::Up); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup @@ -298,7 +298,7 @@ mod tests { setup .app_data .lock() - .log_scroll(&crate::app_data::ScrollDirection::Previous); + .log_scroll(&crate::app_data::ScrollDirection::Up); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup @@ -404,7 +404,7 @@ mod tests { setup .app_data .lock() - .log_scroll(&crate::app_data::ScrollDirection::Previous); + .log_scroll(&crate::app_data::ScrollDirection::Up); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup @@ -433,7 +433,7 @@ mod tests { setup .app_data .lock() - .log_scroll(&crate::app_data::ScrollDirection::Previous); + .log_scroll(&crate::app_data::ScrollDirection::Up); let mut colors = AppColors::new(); diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap index 9d29eeb..a68dbd3 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap @@ -2,42 +2,28 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " -" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " -" โ”‚ โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚ " -" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ”‚ " -" โ”‚ 8b d8 )888( 8888[ 8PP""""""" 88 โ”‚ " -" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ " -" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ A simple tui to view & control docker containers โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ ( tab ) or ( shift+tab ) change panels โ”‚ " -" โ”‚ ( โ†‘ โ†“ ) or ( j k ) or ( Home End ) scroll vertically โ”‚ " -" โ”‚ ( โ† โ†’ ) horizontal scroll across logs โ”‚ " -" โ”‚ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ”‚ " -" โ”‚ ( enter ) send docker container command โ”‚ " -" โ”‚ ( e ) exec into a container โ”‚ " -" โ”‚ ( f ) force clear the screen & redraw the gui โ”‚ " -" โ”‚ ( h ) toggle this help information - or click heading โ”‚ " -" โ”‚ ( s ) save logs to file โ”‚ " -" โ”‚ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ”‚ " -" โ”‚ ( F1 ) or ( / ) enter filter mode โ”‚ " -" โ”‚ ( # ) enter log search mode โ”‚ " -" โ”‚ ( 0 ) stop sort โ”‚ " -" โ”‚ ( 1 - 9 ) sort by header - or click header โ”‚ " -" โ”‚ ( - = ) change log section height โ”‚ " -" โ”‚ ( \ ) toggle log section visibility โ”‚ " -" โ”‚ ( esc ) close dialog โ”‚ " -" โ”‚ ( q ) quit at any time โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ currently an early work in progress, all and any input appreciated โ”‚ " -" โ”‚ https://github.com/mrjackwills/oxker โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ โ”‚ " -" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " -" " +" " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ”‚ " +" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " +" โ”‚ a work in progress, all and any input appreciated โ”‚ " +" โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ q quit c Esc close dialog โ”‚ " +" โ”‚ Down Up j k Home End scroll vertically Left Right scroll horizontally โ”‚ " +" โ”‚ Control increase scroll speed Enter send docker command โ”‚ " +" โ”‚ e exec into a container i container inspect mode โ”‚ " +" โ”‚ / F1 filter mode # log search mode โ”‚ " +" โ”‚ h toggle this panel f force clear screen and redraw โ”‚ " +" โ”‚ - = change log section height \ toggle of section visibility โ”‚ " +" โ”‚ 1 ~ 9 sort by header - or click header 0 stop sort โ”‚ " +" โ”‚ Tab Back Tab change panel m toggle mouse capture - allows text selection โ”‚ " +" โ”‚ s save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_color.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_color.snap new file mode 100644 index 0000000..a68dbd3 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_color.snap @@ -0,0 +1,29 @@ +--- +source: src/ui/draw_blocks/help.rs +expression: setup.terminal.backend() +--- +" " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ”‚ " +" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " +" โ”‚ a work in progress, all and any input appreciated โ”‚ " +" โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ q quit c Esc close dialog โ”‚ " +" โ”‚ Down Up j k Home End scroll vertically Left Right scroll horizontally โ”‚ " +" โ”‚ Control increase scroll speed Enter send docker command โ”‚ " +" โ”‚ e exec into a container i container inspect mode โ”‚ " +" โ”‚ / F1 filter mode # log search mode โ”‚ " +" โ”‚ h toggle this panel f force clear screen and redraw โ”‚ " +" โ”‚ - = change log section height \ toggle of section visibility โ”‚ " +" โ”‚ 1 ~ 9 sort by header - or click header 0 stop sort โ”‚ " +" โ”‚ Tab Back Tab change panel m toggle mouse capture - allows text selection โ”‚ " +" โ”‚ s save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap index 9d29eeb..d9916e9 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap @@ -3,41 +3,41 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- " " -" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " -" โ”‚ โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚ " -" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ”‚ " -" โ”‚ 8b d8 )888( 8888[ 8PP""""""" 88 โ”‚ " -" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ " -" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ A simple tui to view & control docker containers โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ ( tab ) or ( shift+tab ) change panels โ”‚ " -" โ”‚ ( โ†‘ โ†“ ) or ( j k ) or ( Home End ) scroll vertically โ”‚ " -" โ”‚ ( โ† โ†’ ) horizontal scroll across logs โ”‚ " -" โ”‚ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ”‚ " -" โ”‚ ( enter ) send docker container command โ”‚ " -" โ”‚ ( e ) exec into a container โ”‚ " -" โ”‚ ( f ) force clear the screen & redraw the gui โ”‚ " -" โ”‚ ( h ) toggle this help information - or click heading โ”‚ " -" โ”‚ ( s ) save logs to file โ”‚ " -" โ”‚ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ”‚ " -" โ”‚ ( F1 ) or ( / ) enter filter mode โ”‚ " -" โ”‚ ( # ) enter log search mode โ”‚ " -" โ”‚ ( 0 ) stop sort โ”‚ " -" โ”‚ ( 1 - 9 ) sort by header - or click header โ”‚ " -" โ”‚ ( - = ) change log section height โ”‚ " -" โ”‚ ( \ ) toggle log section visibility โ”‚ " -" โ”‚ ( esc ) close dialog โ”‚ " -" โ”‚ ( q ) quit at any time โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ currently an early work in progress, all and any input appreciated โ”‚ " -" โ”‚ https://github.com/mrjackwills/oxker โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ โ”‚ " -" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " +" " +" " +" " +" " +" " +" " +"โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ 88 โ”‚" +"โ”‚ 88 โ”‚" +"โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPY โ”‚" +"โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P____ โ”‚" +"โ”‚ 8b d8 )888( 8888( 8PP"""" โ”‚" +"โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, โ”‚" +"โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd โ”‚" +"โ”‚ a work in progress, all and any input app โ”‚" +"โ”‚ A simple tui to view & control docker cont https://github.com/mrjackwills/oxker โ”‚" +"โ”‚ โ”‚" +"โ”‚ Keymap โ”‚" +"โ”‚ q quit c Esc close dialog โ”‚" +"โ”‚ Down Up j k Home End scrol Left Right scroll horizontally โ”‚" +"โ”‚ Control incre Enter send docker command โ”‚" +"โ”‚ e exec i container inspect mode โ”‚" +"โ”‚ / F1 filte # log search mode โ”‚" +"โ”‚ h toggl f force clear screen and redraw โ”‚" +"โ”‚ - = chang \ toggle of section visibility โ”‚" +"โ”‚ 1 ~ 9 sort 0 stop sort โ”‚" +"โ”‚ Tab Back Tab chang m toggle mouse capture - allows text selection โ”‚" +"โ”‚ s save โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" +" " +" " +" " +" " +" " +" " +" " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap index 97b4d43..a7b1daf 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap @@ -2,53 +2,28 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " -" โ”‚ โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚ " -" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ”‚ " -" โ”‚ 8b d8 )888( 8888[ 8PP""""""" 88 โ”‚ " -" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ " -" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ A simple tui to view & control docker containers โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ ( t ) select next panel โ”‚ " -" โ”‚ ( u ) select previous panel โ”‚ " -" โ”‚ ( o ) scroll list down by one โ”‚ " -" โ”‚ ( s ) scroll list up by one โ”‚ " -" โ”‚ ( p ) scroll list to end โ”‚ " -" โ”‚ ( q ) scroll list to start โ”‚ " -" โ”‚ ( h ) horizontal scroll logs right โ”‚ " -" โ”‚ ( g ) horizontal scroll logs left โ”‚ " -" โ”‚ ( Alt ) increase scroll speed, used in conjunction scroll keys โ”‚ " -" โ”‚ ( enter ) send docker container command โ”‚ " -" โ”‚ ( d ) exec into a container โ”‚ " -" โ”‚ ( f ) force clear the screen & redraw the gui โ”‚ " -" โ”‚ ( 5 ) toggle this help information - or click heading โ”‚ " -" โ”‚ ( m ) save logs to file โ”‚ " -" โ”‚ ( 6 ) toggle mouse capture - if disabled, text on screen can be selected & copied โ”‚ " -" โ”‚ ( e ) enter filter mode โ”‚ " -" โ”‚ ( 7 ) enter log search mode โ”‚ " -" โ”‚ ( 4 ) reset container sorting โ”‚ " -" โ”‚ ( z ) sort containers by name โ”‚ " -" โ”‚ ( 1 ) sort containers by state โ”‚ " -" โ”‚ ( 2 ) sort containers by status โ”‚ " -" โ”‚ ( v ) sort containers by cpu โ”‚ " -" โ”‚ ( y ) sort containers by memory โ”‚ " -" โ”‚ ( w ) sort containers by id โ”‚ " -" โ”‚ ( x ) sort containers by image โ”‚ " -" โ”‚ ( 0 ) sort containers by rx โ”‚ " -" โ”‚ ( 3 ) sort containers by tx โ”‚ " -" โ”‚ ( i ) decrease log section height โ”‚ " -" โ”‚ ( j ) increase log section height โ”‚ " -" โ”‚ ( k ) toggle log section visibility โ”‚ " -" โ”‚ ( a ) close dialog โ”‚ " -" โ”‚ ( l ) quit at any time โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ currently an early work in progress, all and any input appreciated โ”‚ " -" โ”‚ https://github.com/mrjackwills/oxker โ”‚ " -" โ”‚ โ”‚ " -" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ n quit a close dialog โ”‚ " +" โ”‚ p s scroll vertically i j scroll horizontally โ”‚ " +" โ”‚ r scroll to start q scroll to end โ”‚ " +" โ”‚ Alt increase scroll speed Enter send docker command โ”‚ " +" โ”‚ d exec into a container e container inspect mode โ”‚ " +" โ”‚ f filter mode g log search mode โ”‚ " +" โ”‚ 5 toggle this panel h force clear screen and redraw โ”‚ " +" โ”‚ k l change log section height m toggle of section visibility โ”‚ " +" โ”‚ z sort by name 1 sort by state โ”‚ " +" โ”‚ 2 sort by status v sort by CPU โ”‚ " +" โ”‚ y sort by memory w sort by ID โ”‚ " +" โ”‚ x sort by Image 0 sort by RX โ”‚ " +" โ”‚ 3 sort by TX 4 stop sort โ”‚ " +" โ”‚ t u change panel 6 toggle mouse capture - allows text selection โ”‚ " +" โ”‚ o save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_two_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_two_definition.snap new file mode 100644 index 0000000..ed14a24 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_two_definition.snap @@ -0,0 +1,34 @@ +--- +source: src/ui/draw_blocks/help.rs +expression: setup.terminal.backend() +--- +" " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ”‚ " +" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " +" โ”‚ a work in progress, all and any input appreciated โ”‚ " +" โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ 0 quit a b close dialog โ”‚ " +" โ”‚ 4 Caps Lock Scroll Lock scroll vertically q s r scroll horizontally โ”‚ " +" โ”‚ 8 scroll to start 6 7 scroll to end โ”‚ " +" โ”‚ Alt increase scroll speed Enter send docker command โ”‚ " +" โ”‚ g exec into a container i j container inspect mode โ”‚ " +" โ”‚ k filter mode m n log search mode โ”‚ " +" โ”‚ F5 F6 toggle this panel o force clear screen and redraw โ”‚ " +" โ”‚ u w v change log section height y z toggle of section visibility โ”‚ " +" โ”‚ Begin Menu sort by name Page Up Pause sort by state โ”‚ " +" โ”‚ Print Screen sort by status Down sort by CPU โ”‚ " +" โ”‚ Home sort by memory Back Tab sort by ID โ”‚ " +" โ”‚ End Esc sort by Image Num Lock sort by RX โ”‚ " +" โ”‚ F1 F2 sort by TX F3 stop sort โ”‚ " +" โ”‚ Print Screen Left Up change panel F7 toggle mouse capture - allows text selection โ”‚ " +" โ”‚ 2 3 save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definition.snap new file mode 100644 index 0000000..1aecc71 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definition.snap @@ -0,0 +1,34 @@ +--- +source: src/ui/draw_blocks/help.rs +expression: setup.terminal.backend() +--- +" " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 config location: /home/user/.config/oxker/config.toml โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ”‚ " +" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " +" โ”‚ a work in progress, all and any input appreciated โ”‚ " +" โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ 0 1 quit a b close dialog โ”‚ " +" โ”‚ 4 Caps Lock 5 Scroll Lock scroll vertically q s r t scroll horizontally โ”‚ " +" โ”‚ 8 9 scroll to start 6 7 scroll to end โ”‚ " +" โ”‚ Alt increase scroll speed Enter send docker command โ”‚ " +" โ”‚ g h exec into a container i j container inspect mode โ”‚ " +" โ”‚ k l filter mode m n log search mode โ”‚ " +" โ”‚ F5 F6 toggle this panel o p force clear screen and redraw โ”‚ " +" โ”‚ u w v x change log section height y z toggle of section visibility โ”‚ " +" โ”‚ Begin Menu sort by name Page Up Pause sort by state โ”‚ " +" โ”‚ Print Screen Tab sort by status Down Del sort by CPU โ”‚ " +" โ”‚ Home Insert sort by memory Back Tab Backspace sort by ID โ”‚ " +" โ”‚ End Esc sort by Image Num Lock Page Down sort by RX โ”‚ " +" โ”‚ F1 F2 sort by TX F3 F4 stop sort โ”‚ " +" โ”‚ Print Screen Left Up Right change panel F7 F8 toggle mouse capture - allows text selection โ”‚ " +" โ”‚ 2 3 save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap deleted file mode 100644 index f884948..0000000 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap +++ /dev/null @@ -1,54 +0,0 @@ ---- -source: src/ui/draw_blocks/help.rs -expression: setup.terminal.backend() ---- -" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " -" โ”‚ โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚ " -" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ”‚ " -" โ”‚ 8b d8 )888( 8888[ 8PP""""""" 88 โ”‚ " -" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ " -" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ A simple tui to view & control docker containers โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ ( s ) or ( S ) select next panel โ”‚ " -" โ”‚ ( t ) or ( T ) select previous panel โ”‚ " -" โ”‚ ( n ) or ( N ) scroll list down by one โ”‚ " -" โ”‚ ( r ) or ( R ) scroll list up by one โ”‚ " -" โ”‚ ( o ) or ( O ) scroll list to end โ”‚ " -" โ”‚ ( p ) or ( P ) scroll list to start โ”‚ " -" โ”‚ ( g ) or ( G ) horizontal scroll logs right โ”‚ " -" โ”‚ ( f ) or ( F ) horizontal scroll logs left โ”‚ " -" โ”‚ ( Alt ) increase scroll speed, used in conjunction scroll keys โ”‚ " -" โ”‚ ( enter ) send docker container command โ”‚ " -" โ”‚ ( d ) or ( D ) exec into a container โ”‚ " -" โ”‚ ( f ) or ( F ) force clear the screen & redraw the gui โ”‚ " -" โ”‚ ( 4 ) or ( 5 ) toggle this help information - or click heading โ”‚ " -" โ”‚ ( l ) or ( L ) save logs to file โ”‚ " -" โ”‚ ( 5 ) or ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied โ”‚ " -" โ”‚ ( e ) or ( E ) enter filter mode โ”‚ " -" โ”‚ ( m ) or ( M ) enter log search mode โ”‚ " -" โ”‚ ( 3 ) or ( 6 ) reset container sorting โ”‚ " -" โ”‚ ( y ) or ( Y ) sort containers by name โ”‚ " -" โ”‚ ( 0 ) or ( 9 ) sort containers by state โ”‚ " -" โ”‚ ( 1 ) or ( 8 ) sort containers by status โ”‚ " -" โ”‚ ( u ) or ( U ) sort containers by cpu โ”‚ " -" โ”‚ ( x ) or ( X ) sort containers by memory โ”‚ " -" โ”‚ ( v ) or ( V ) sort containers by id โ”‚ " -" โ”‚ ( w ) or ( W ) sort containers by image โ”‚ " -" โ”‚ ( z ) or ( Z ) sort containers by rx โ”‚ " -" โ”‚ ( 2 ) or ( 7 ) sort containers by tx โ”‚ " -" โ”‚ ( h ) or ( H ) decrease log section height โ”‚ " -" โ”‚ ( i ) or ( I ) increase log section height โ”‚ " -" โ”‚ ( j ) or ( J ) toggle log section visibility โ”‚ " -" โ”‚ ( a ) or ( A ) close dialog โ”‚ " -" โ”‚ ( k ) or ( K ) quit at any time โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ currently an early work in progress, all and any input appreciated โ”‚ " -" โ”‚ https://github.com/mrjackwills/oxker โ”‚ " -" โ”‚ โ”‚ " -" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_config.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_config.snap new file mode 100644 index 0000000..b6ae220 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_config.snap @@ -0,0 +1,29 @@ +--- +source: src/ui/draw_blocks/help.rs +expression: setup.terminal.backend() +--- +" " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYb โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' " โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ”‚ " +" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " +" โ”‚ a work in progress, all and any input appreciated โ”‚ " +" โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ q quit c Esc close dialog โ”‚ " +" โ”‚ Down Up j k Home End scroll vertically Left Right scroll horizontally โ”‚ " +" โ”‚ Control increase scroll speed Enter send docker command โ”‚ " +" โ”‚ e exec into a container i container inspect mode โ”‚ " +" โ”‚ / F1 filter mode # log search mode โ”‚ " +" โ”‚ h toggle this panel f force clear screen and redraw โ”‚ " +" โ”‚ - = change log section height \ toggle of section visibility โ”‚ " +" โ”‚ 1 ~ 9 sort by header - or click header 0 stop sort โ”‚ " +" โ”‚ Tab Back Tab change panel m toggle mouse capture - allows text selection โ”‚ " +" โ”‚ s save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_save.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_save.snap new file mode 100644 index 0000000..a1240bf --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_save.snap @@ -0,0 +1,29 @@ +--- +source: src/ui/draw_blocks/help.rs +expression: setup.terminal.backend() +--- +" " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 config location: /home/user/.config/oxker/config.toml โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ”‚ " +" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " +" โ”‚ a work in progress, all and any input appreciated โ”‚ " +" โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ q quit c Esc close dialog โ”‚ " +" โ”‚ Down Up j k Home End scroll vertically Left Right scroll horizontally โ”‚ " +" โ”‚ Control increase scroll speed Enter send docker command โ”‚ " +" โ”‚ e exec into a container i container inspect mode โ”‚ " +" โ”‚ / F1 filter mode # log search mode โ”‚ " +" โ”‚ h toggle this panel f force clear screen and redraw โ”‚ " +" โ”‚ - = change log section height \ toggle of section visibility โ”‚ " +" โ”‚ 1 ~ 9 sort by header - or click header 0 stop sort โ”‚ " +" โ”‚ Tab Back Tab change panel m toggle mouse capture - allows text selection โ”‚ " +" โ”‚ s save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_timezone.snap new file mode 100644 index 0000000..01aa561 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_timezone.snap @@ -0,0 +1,29 @@ +--- +source: src/ui/draw_blocks/help.rs +expression: setup.terminal.backend() +--- +" " +" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " +" โ”‚ 88 โ”‚ " +" โ”‚ 88 โ”‚ " +" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ”‚ " +" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y โ”‚ " +" โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 config location: /home/user/.config/oxker/config.toml โ”‚ " +" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 export location: /test_dir โ”‚ " +" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " +" โ”‚ a work in progress, all and any input appreciated โ”‚ " +" โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ " +" โ”‚ โ”‚ " +" โ”‚ Keymap โ”‚ " +" โ”‚ q quit c Esc close dialog โ”‚ " +" โ”‚ Down Up j k Home End scroll vertically Left Right scroll horizontally โ”‚ " +" โ”‚ Control increase scroll speed Enter send docker command โ”‚ " +" โ”‚ e exec into a container i container inspect mode โ”‚ " +" โ”‚ / F1 filter mode # log search mode โ”‚ " +" โ”‚ h toggle this panel f force clear screen and redraw โ”‚ " +" โ”‚ - = change log section height \ toggle of section visibility โ”‚ " +" โ”‚ 1 ~ 9 sort by header - or click header 0 stop sort โ”‚ " +" โ”‚ Tab Back Tab change panel m toggle mouse capture - allows text selection โ”‚ " +" โ”‚ s save logs to file โ”‚ " +" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " +" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap deleted file mode 100644 index 6fcfd2e..0000000 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap +++ /dev/null @@ -1,54 +0,0 @@ ---- -source: src/ui/draw_blocks/help.rs -expression: setup.terminal.backend() ---- -" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " -" โ”‚ โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚ " -" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ”‚ " -" โ”‚ 8b d8 )888( 8888[ 8PP""""""" 88 โ”‚ " -" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ " -" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ A simple tui to view & control docker containers โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ ( t ) select next panel โ”‚ " -" โ”‚ ( u ) or ( U ) select previous panel โ”‚ " -" โ”‚ ( o ) or ( O ) scroll list down by one โ”‚ " -" โ”‚ ( s ) or ( S ) scroll list up by one โ”‚ " -" โ”‚ ( p ) scroll list to end โ”‚ " -" โ”‚ ( q ) or ( Q ) scroll list to start โ”‚ " -" โ”‚ ( h ) horizontal scroll logs right โ”‚ " -" โ”‚ ( g ) or ( G ) horizontal scroll logs left โ”‚ " -" โ”‚ ( Alt ) increase scroll speed, used in conjunction scroll keys โ”‚ " -" โ”‚ ( enter ) send docker container command โ”‚ " -" โ”‚ ( d ) exec into a container โ”‚ " -" โ”‚ ( f ) force clear the screen & redraw the gui โ”‚ " -" โ”‚ ( 5 ) toggle this help information - or click heading โ”‚ " -" โ”‚ ( m ) or ( M ) save logs to file โ”‚ " -" โ”‚ ( 6 ) or ( # ) toggle mouse capture - if disabled, text on screen can be selected & copied โ”‚ " -" โ”‚ ( e ) or ( E ) enter filter mode โ”‚ " -" โ”‚ ( 8 ) enter log search mode โ”‚ " -" โ”‚ ( 4 ) or ( 5 ) reset container sorting โ”‚ " -" โ”‚ ( z ) sort containers by name โ”‚ " -" โ”‚ ( 1 ) sort containers by state โ”‚ " -" โ”‚ ( 2 ) or ( 7 ) sort containers by status โ”‚ " -" โ”‚ ( v ) sort containers by cpu โ”‚ " -" โ”‚ ( y ) or ( Y ) sort containers by memory โ”‚ " -" โ”‚ ( w ) or ( W ) sort containers by id โ”‚ " -" โ”‚ ( x ) sort containers by image โ”‚ " -" โ”‚ ( 0 ) or ( 9 ) sort containers by rx โ”‚ " -" โ”‚ ( 3 ) sort containers by tx โ”‚ " -" โ”‚ ( i ) or ( I ) decrease log section height โ”‚ " -" โ”‚ ( j ) increase log section height โ”‚ " -" โ”‚ ( k ) or ( K ) toggle log section visibility โ”‚ " -" โ”‚ ( a ) or ( A ) close dialog โ”‚ " -" โ”‚ ( l ) quit at any time โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ currently an early work in progress, all and any input appreciated โ”‚ " -" โ”‚ https://github.com/mrjackwills/oxker โ”‚ " -" โ”‚ โ”‚ " -" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap deleted file mode 100644 index 6c8b9ef..0000000 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: src/ui/draw_blocks/help.rs -expression: setup.terminal.backend() ---- -" โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ " -" โ”‚ โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ 88 โ”‚ " -" โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚ " -" โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ”‚ " -" โ”‚ 8b d8 )888( 8888[ 8PP""""""" 88 โ”‚ " -" โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ " -" โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ A simple tui to view & control docker containers โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ logs timezone: Asia/Tokyo โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ ( tab ) or ( shift+tab ) change panels โ”‚ " -" โ”‚ ( โ†‘ โ†“ ) or ( j k ) or ( Home End ) scroll vertically โ”‚ " -" โ”‚ ( โ† โ†’ ) horizontal scroll across logs โ”‚ " -" โ”‚ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ”‚ " -" โ”‚ ( enter ) send docker container command โ”‚ " -" โ”‚ ( e ) exec into a container โ”‚ " -" โ”‚ ( f ) force clear the screen & redraw the gui โ”‚ " -" โ”‚ ( h ) toggle this help information - or click heading โ”‚ " -" โ”‚ ( s ) save logs to file โ”‚ " -" โ”‚ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ”‚ " -" โ”‚ ( F1 ) or ( / ) enter filter mode โ”‚ " -" โ”‚ ( # ) enter log search mode โ”‚ " -" โ”‚ ( 0 ) stop sort โ”‚ " -" โ”‚ ( 1 - 9 ) sort by header - or click header โ”‚ " -" โ”‚ ( - = ) change log section height โ”‚ " -" โ”‚ ( \ ) toggle log section visibility โ”‚ " -" โ”‚ ( esc ) close dialog โ”‚ " -" โ”‚ ( q ) quit at any time โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ currently an early work in progress, all and any input appreciated โ”‚ " -" โ”‚ https://github.com/mrjackwills/oxker โ”‚ " -" โ”‚ โ”‚ " -" โ”‚ โ”‚ " -" โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_color.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_color.snap new file mode 100644 index 0000000..0c5cf65 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_color.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - c or Esc or i to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ”‚" +"โ”‚ "Created": "2026-01-23T22:20:19.927967311Z", โ”‚" +"โ”‚ "Path": "docker-entrypoint.sh", โ”‚" +"โ”‚ "Args": [ โ”‚" +"โ”‚ "postgres" โ”‚" +"โ”‚ ], โ”‚" +"โ”‚ "State": { โ”‚" +"โ”‚ "Status": "running", โ”‚" +"โ”‚ "Running": true, โ”‚" +"โ”‚ "Paused": false, โ”‚" +"โ”‚ "Restarting": false, โ”‚" +"โ”‚ "OOMKilled": false, โ”‚" +"โ”‚ "Dead": false, โ”‚" +"โ”‚ "Pid": 782, โ”‚" +"โ”‚ "ExitCode": 0, โ”‚" +"โ”‚ "Error": "", โ”‚" +"โ”‚ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ }, โ”‚" +"โ”‚ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ”‚" +"โ”‚ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ”‚" +"โ”‚ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ”‚" +"โ”‚ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ”‚" +"โ”‚ "Name": "/postgres", โ”‚" +"โ”‚ "RestartCount": 0, โ”‚" +"โ”‚ "Driver": "overlay2", โ”‚" +"โ”‚ "Platform": "linux", โ”‚" +"โ”‚ "MountLabel": "", โ”‚" +"โ”‚ "ProcessLabel": "", โ”‚" +"โ”‚ "AppArmorProfile": "", โ”‚" +"โ”‚ "HostConfig": { โ”‚" +"โ”‚ "CpuShares": 0, โ”‚" +"โ”‚ "Memory": 1073741824, โ”‚" +"โ”‚ "CgroupParent": "", โ”‚" +"โ”‚ "BlkioWeight": 0, โ”‚" +"โ”‚ "CpuPeriod": 0, โ”‚" +"โ”‚ "CpuQuota": 0, โ”‚" +"โ”‚ "CpuRealtimePeriod": 0, โ”‚" +"โ”‚ "CpuRealtimeRuntime": 0, โ”‚" +"โ”‚ "CpusetCpus": "", โ”‚" +"โ”‚ "CpusetMems": "", โ”‚" +"โ”‚ "MemoryReservation": 0, โ”‚" +"โ”‚ "MemorySwap": 2147483648, โ”‚" +"โ”‚ "NanoCpus": 0, โ”‚" +"โ”‚ "OomKillDisable": false, โ”‚" +"โ”‚ "CpuCount": 0, โ”‚" +"โ”‚ "CpuPercent": 0, โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0/158 โ†“ 0/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_all.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_all.snap new file mode 100644 index 0000000..9ab52b8 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_all.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - F or Z or 4 or 5 to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ”‚" +"โ”‚ "Created": "2026-01-23T22:20:19.927967311Z", โ”‚" +"โ”‚ "Path": "docker-entrypoint.sh", โ”‚" +"โ”‚ "Args": [ โ”‚" +"โ”‚ "postgres" โ”‚" +"โ”‚ ], โ”‚" +"โ”‚ "State": { โ”‚" +"โ”‚ "Status": "running", โ”‚" +"โ”‚ "Running": true, โ”‚" +"โ”‚ "Paused": false, โ”‚" +"โ”‚ "Restarting": false, โ”‚" +"โ”‚ "OOMKilled": false, โ”‚" +"โ”‚ "Dead": false, โ”‚" +"โ”‚ "Pid": 782, โ”‚" +"โ”‚ "ExitCode": 0, โ”‚" +"โ”‚ "Error": "", โ”‚" +"โ”‚ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ }, โ”‚" +"โ”‚ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ”‚" +"โ”‚ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ”‚" +"โ”‚ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ”‚" +"โ”‚ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ”‚" +"โ”‚ "Name": "/postgres", โ”‚" +"โ”‚ "RestartCount": 0, โ”‚" +"โ”‚ "Driver": "overlay2", โ”‚" +"โ”‚ "Platform": "linux", โ”‚" +"โ”‚ "MountLabel": "", โ”‚" +"โ”‚ "ProcessLabel": "", โ”‚" +"โ”‚ "AppArmorProfile": "", โ”‚" +"โ”‚ "HostConfig": { โ”‚" +"โ”‚ "CpuShares": 0, โ”‚" +"โ”‚ "Memory": 1073741824, โ”‚" +"โ”‚ "CgroupParent": "", โ”‚" +"โ”‚ "BlkioWeight": 0, โ”‚" +"โ”‚ "CpuPeriod": 0, โ”‚" +"โ”‚ "CpuQuota": 0, โ”‚" +"โ”‚ "CpuRealtimePeriod": 0, โ”‚" +"โ”‚ "CpuRealtimeRuntime": 0, โ”‚" +"โ”‚ "CpusetCpus": "", โ”‚" +"โ”‚ "CpusetMems": "", โ”‚" +"โ”‚ "MemoryReservation": 0, โ”‚" +"โ”‚ "MemorySwap": 2147483648, โ”‚" +"โ”‚ "NanoCpus": 0, โ”‚" +"โ”‚ "OomKillDisable": false, โ”‚" +"โ”‚ "CpuCount": 0, โ”‚" +"โ”‚ "CpuPercent": 0, โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0/158 โ†“ 0/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_one.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_one.snap new file mode 100644 index 0000000..ed18a89 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_one.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - F or Esc or i to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ”‚" +"โ”‚ "Created": "2026-01-23T22:20:19.927967311Z", โ”‚" +"โ”‚ "Path": "docker-entrypoint.sh", โ”‚" +"โ”‚ "Args": [ โ”‚" +"โ”‚ "postgres" โ”‚" +"โ”‚ ], โ”‚" +"โ”‚ "State": { โ”‚" +"โ”‚ "Status": "running", โ”‚" +"โ”‚ "Running": true, โ”‚" +"โ”‚ "Paused": false, โ”‚" +"โ”‚ "Restarting": false, โ”‚" +"โ”‚ "OOMKilled": false, โ”‚" +"โ”‚ "Dead": false, โ”‚" +"โ”‚ "Pid": 782, โ”‚" +"โ”‚ "ExitCode": 0, โ”‚" +"โ”‚ "Error": "", โ”‚" +"โ”‚ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ }, โ”‚" +"โ”‚ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ”‚" +"โ”‚ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ”‚" +"โ”‚ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ”‚" +"โ”‚ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ”‚" +"โ”‚ "Name": "/postgres", โ”‚" +"โ”‚ "RestartCount": 0, โ”‚" +"โ”‚ "Driver": "overlay2", โ”‚" +"โ”‚ "Platform": "linux", โ”‚" +"โ”‚ "MountLabel": "", โ”‚" +"โ”‚ "ProcessLabel": "", โ”‚" +"โ”‚ "AppArmorProfile": "", โ”‚" +"โ”‚ "HostConfig": { โ”‚" +"โ”‚ "CpuShares": 0, โ”‚" +"โ”‚ "Memory": 1073741824, โ”‚" +"โ”‚ "CgroupParent": "", โ”‚" +"โ”‚ "BlkioWeight": 0, โ”‚" +"โ”‚ "CpuPeriod": 0, โ”‚" +"โ”‚ "CpuQuota": 0, โ”‚" +"โ”‚ "CpuRealtimePeriod": 0, โ”‚" +"โ”‚ "CpuRealtimeRuntime": 0, โ”‚" +"โ”‚ "CpusetCpus": "", โ”‚" +"โ”‚ "CpusetMems": "", โ”‚" +"โ”‚ "MemoryReservation": 0, โ”‚" +"โ”‚ "MemorySwap": 2147483648, โ”‚" +"โ”‚ "NanoCpus": 0, โ”‚" +"โ”‚ "OomKillDisable": false, โ”‚" +"โ”‚ "CpuCount": 0, โ”‚" +"โ”‚ "CpuPercent": 0, โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0/158 โ†“ 0/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_two.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_two.snap new file mode 100644 index 0000000..c4b4d46 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_two.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - F or Z or i to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ”‚" +"โ”‚ "Created": "2026-01-23T22:20:19.927967311Z", โ”‚" +"โ”‚ "Path": "docker-entrypoint.sh", โ”‚" +"โ”‚ "Args": [ โ”‚" +"โ”‚ "postgres" โ”‚" +"โ”‚ ], โ”‚" +"โ”‚ "State": { โ”‚" +"โ”‚ "Status": "running", โ”‚" +"โ”‚ "Running": true, โ”‚" +"โ”‚ "Paused": false, โ”‚" +"โ”‚ "Restarting": false, โ”‚" +"โ”‚ "OOMKilled": false, โ”‚" +"โ”‚ "Dead": false, โ”‚" +"โ”‚ "Pid": 782, โ”‚" +"โ”‚ "ExitCode": 0, โ”‚" +"โ”‚ "Error": "", โ”‚" +"โ”‚ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ }, โ”‚" +"โ”‚ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ”‚" +"โ”‚ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ”‚" +"โ”‚ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ”‚" +"โ”‚ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ”‚" +"โ”‚ "Name": "/postgres", โ”‚" +"โ”‚ "RestartCount": 0, โ”‚" +"โ”‚ "Driver": "overlay2", โ”‚" +"โ”‚ "Platform": "linux", โ”‚" +"โ”‚ "MountLabel": "", โ”‚" +"โ”‚ "ProcessLabel": "", โ”‚" +"โ”‚ "AppArmorProfile": "", โ”‚" +"โ”‚ "HostConfig": { โ”‚" +"โ”‚ "CpuShares": 0, โ”‚" +"โ”‚ "Memory": 1073741824, โ”‚" +"โ”‚ "CgroupParent": "", โ”‚" +"โ”‚ "BlkioWeight": 0, โ”‚" +"โ”‚ "CpuPeriod": 0, โ”‚" +"โ”‚ "CpuQuota": 0, โ”‚" +"โ”‚ "CpuRealtimePeriod": 0, โ”‚" +"โ”‚ "CpuRealtimeRuntime": 0, โ”‚" +"โ”‚ "CpusetCpus": "", โ”‚" +"โ”‚ "CpusetMems": "", โ”‚" +"โ”‚ "MemoryReservation": 0, โ”‚" +"โ”‚ "MemorySwap": 2147483648, โ”‚" +"โ”‚ "NanoCpus": 0, โ”‚" +"โ”‚ "OomKillDisable": false, โ”‚" +"โ”‚ "CpuCount": 0, โ”‚" +"โ”‚ "CpuPercent": 0, โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0/158 โ†“ 0/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_one.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_one.snap new file mode 100644 index 0000000..f1144ec --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_one.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - c or Esc or 4 to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ”‚" +"โ”‚ "Created": "2026-01-23T22:20:19.927967311Z", โ”‚" +"โ”‚ "Path": "docker-entrypoint.sh", โ”‚" +"โ”‚ "Args": [ โ”‚" +"โ”‚ "postgres" โ”‚" +"โ”‚ ], โ”‚" +"โ”‚ "State": { โ”‚" +"โ”‚ "Status": "running", โ”‚" +"โ”‚ "Running": true, โ”‚" +"โ”‚ "Paused": false, โ”‚" +"โ”‚ "Restarting": false, โ”‚" +"โ”‚ "OOMKilled": false, โ”‚" +"โ”‚ "Dead": false, โ”‚" +"โ”‚ "Pid": 782, โ”‚" +"โ”‚ "ExitCode": 0, โ”‚" +"โ”‚ "Error": "", โ”‚" +"โ”‚ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ }, โ”‚" +"โ”‚ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ”‚" +"โ”‚ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ”‚" +"โ”‚ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ”‚" +"โ”‚ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ”‚" +"โ”‚ "Name": "/postgres", โ”‚" +"โ”‚ "RestartCount": 0, โ”‚" +"โ”‚ "Driver": "overlay2", โ”‚" +"โ”‚ "Platform": "linux", โ”‚" +"โ”‚ "MountLabel": "", โ”‚" +"โ”‚ "ProcessLabel": "", โ”‚" +"โ”‚ "AppArmorProfile": "", โ”‚" +"โ”‚ "HostConfig": { โ”‚" +"โ”‚ "CpuShares": 0, โ”‚" +"โ”‚ "Memory": 1073741824, โ”‚" +"โ”‚ "CgroupParent": "", โ”‚" +"โ”‚ "BlkioWeight": 0, โ”‚" +"โ”‚ "CpuPeriod": 0, โ”‚" +"โ”‚ "CpuQuota": 0, โ”‚" +"โ”‚ "CpuRealtimePeriod": 0, โ”‚" +"โ”‚ "CpuRealtimeRuntime": 0, โ”‚" +"โ”‚ "CpusetCpus": "", โ”‚" +"โ”‚ "CpusetMems": "", โ”‚" +"โ”‚ "MemoryReservation": 0, โ”‚" +"โ”‚ "MemorySwap": 2147483648, โ”‚" +"โ”‚ "NanoCpus": 0, โ”‚" +"โ”‚ "OomKillDisable": false, โ”‚" +"โ”‚ "CpuCount": 0, โ”‚" +"โ”‚ "CpuPercent": 0, โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0/158 โ†“ 0/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_two.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_two.snap new file mode 100644 index 0000000..e2eb4bc --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_two.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - c or Esc or 4 or 5 to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ”‚" +"โ”‚ "Created": "2026-01-23T22:20:19.927967311Z", โ”‚" +"โ”‚ "Path": "docker-entrypoint.sh", โ”‚" +"โ”‚ "Args": [ โ”‚" +"โ”‚ "postgres" โ”‚" +"โ”‚ ], โ”‚" +"โ”‚ "State": { โ”‚" +"โ”‚ "Status": "running", โ”‚" +"โ”‚ "Running": true, โ”‚" +"โ”‚ "Paused": false, โ”‚" +"โ”‚ "Restarting": false, โ”‚" +"โ”‚ "OOMKilled": false, โ”‚" +"โ”‚ "Dead": false, โ”‚" +"โ”‚ "Pid": 782, โ”‚" +"โ”‚ "ExitCode": 0, โ”‚" +"โ”‚ "Error": "", โ”‚" +"โ”‚ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ }, โ”‚" +"โ”‚ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ”‚" +"โ”‚ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ”‚" +"โ”‚ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ”‚" +"โ”‚ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ”‚" +"โ”‚ "Name": "/postgres", โ”‚" +"โ”‚ "RestartCount": 0, โ”‚" +"โ”‚ "Driver": "overlay2", โ”‚" +"โ”‚ "Platform": "linux", โ”‚" +"โ”‚ "MountLabel": "", โ”‚" +"โ”‚ "ProcessLabel": "", โ”‚" +"โ”‚ "AppArmorProfile": "", โ”‚" +"โ”‚ "HostConfig": { โ”‚" +"โ”‚ "CpuShares": 0, โ”‚" +"โ”‚ "Memory": 1073741824, โ”‚" +"โ”‚ "CgroupParent": "", โ”‚" +"โ”‚ "BlkioWeight": 0, โ”‚" +"โ”‚ "CpuPeriod": 0, โ”‚" +"โ”‚ "CpuQuota": 0, โ”‚" +"โ”‚ "CpuRealtimePeriod": 0, โ”‚" +"โ”‚ "CpuRealtimeRuntime": 0, โ”‚" +"โ”‚ "CpusetCpus": "", โ”‚" +"โ”‚ "CpusetMems": "", โ”‚" +"โ”‚ "MemoryReservation": 0, โ”‚" +"โ”‚ "MemorySwap": 2147483648, โ”‚" +"โ”‚ "NanoCpus": 0, โ”‚" +"โ”‚ "OomKillDisable": false, โ”‚" +"โ”‚ "CpuCount": 0, โ”‚" +"โ”‚ "CpuPercent": 0, โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0/158 โ†“ 0/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_default_valid.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_default_valid.snap new file mode 100644 index 0000000..0c5cf65 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_default_valid.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - c or Esc or i to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ”‚" +"โ”‚ "Created": "2026-01-23T22:20:19.927967311Z", โ”‚" +"โ”‚ "Path": "docker-entrypoint.sh", โ”‚" +"โ”‚ "Args": [ โ”‚" +"โ”‚ "postgres" โ”‚" +"โ”‚ ], โ”‚" +"โ”‚ "State": { โ”‚" +"โ”‚ "Status": "running", โ”‚" +"โ”‚ "Running": true, โ”‚" +"โ”‚ "Paused": false, โ”‚" +"โ”‚ "Restarting": false, โ”‚" +"โ”‚ "OOMKilled": false, โ”‚" +"โ”‚ "Dead": false, โ”‚" +"โ”‚ "Pid": 782, โ”‚" +"โ”‚ "ExitCode": 0, โ”‚" +"โ”‚ "Error": "", โ”‚" +"โ”‚ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ }, โ”‚" +"โ”‚ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ”‚" +"โ”‚ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ”‚" +"โ”‚ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ”‚" +"โ”‚ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ”‚" +"โ”‚ "Name": "/postgres", โ”‚" +"โ”‚ "RestartCount": 0, โ”‚" +"โ”‚ "Driver": "overlay2", โ”‚" +"โ”‚ "Platform": "linux", โ”‚" +"โ”‚ "MountLabel": "", โ”‚" +"โ”‚ "ProcessLabel": "", โ”‚" +"โ”‚ "AppArmorProfile": "", โ”‚" +"โ”‚ "HostConfig": { โ”‚" +"โ”‚ "CpuShares": 0, โ”‚" +"โ”‚ "Memory": 1073741824, โ”‚" +"โ”‚ "CgroupParent": "", โ”‚" +"โ”‚ "BlkioWeight": 0, โ”‚" +"โ”‚ "CpuPeriod": 0, โ”‚" +"โ”‚ "CpuQuota": 0, โ”‚" +"โ”‚ "CpuRealtimePeriod": 0, โ”‚" +"โ”‚ "CpuRealtimeRuntime": 0, โ”‚" +"โ”‚ "CpusetCpus": "", โ”‚" +"โ”‚ "CpusetMems": "", โ”‚" +"โ”‚ "MemoryReservation": 0, โ”‚" +"โ”‚ "MemorySwap": 2147483648, โ”‚" +"โ”‚ "NanoCpus": 0, โ”‚" +"โ”‚ "OomKillDisable": false, โ”‚" +"โ”‚ "CpuCount": 0, โ”‚" +"โ”‚ "CpuPercent": 0, โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 0/158 โ†“ 0/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset.snap new file mode 100644 index 0000000..503c826 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - c or Esc or i to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚rting": false, โ”‚" +"โ”‚lled": false, โ”‚" +"โ”‚: false, โ”‚" +"โ”‚ 782, โ”‚" +"โ”‚ode": 0, โ”‚" +"โ”‚": "", โ”‚" +"โ”‚edAt": "2026-01-30T08:09:01.574885915Z", โ”‚" +"โ”‚hedAt": "2026-01-30T08:09:01.180567927Z" โ”‚" +"โ”‚ โ”‚" +"โ”‚ "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ”‚" +"โ”‚onfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bbโ”‚" +"โ”‚ePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60โ”‚" +"โ”‚th": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/โ”‚" +"โ”‚": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/0bโ”‚" +"โ”‚"/postgres", โ”‚" +"โ”‚Count": 0, โ”‚" +"โ”‚: "overlay2", โ”‚" +"โ”‚m": "linux", โ”‚" +"โ”‚bel": "", โ”‚" +"โ”‚Label": "", โ”‚" +"โ”‚rProfile": "", โ”‚" +"โ”‚fig": { โ”‚" +"โ”‚ares": 0, โ”‚" +"โ”‚y": 1073741824, โ”‚" +"โ”‚pParent": "", โ”‚" +"โ”‚Weight": 0, โ”‚" +"โ”‚riod": 0, โ”‚" +"โ”‚ota": 0, โ”‚" +"โ”‚altimePeriod": 0, โ”‚" +"โ”‚altimeRuntime": 0, โ”‚" +"โ”‚tCpus": "", โ”‚" +"โ”‚tMems": "", โ”‚" +"โ”‚yReservation": 0, โ”‚" +"โ”‚ySwap": 2147483648, โ”‚" +"โ”‚pus": 0, โ”‚" +"โ”‚llDisable": false, โ”‚" +"โ”‚unt": 0, โ”‚" +"โ”‚rcent": 0, โ”‚" +"โ”‚imumIOps": 0, โ”‚" +"โ”‚imumBandwidth": 0, โ”‚" +"โ”‚inerIDFile": "", โ”‚" +"โ”‚nfig": { โ”‚" +"โ”‚e": "json-file", โ”‚" +"โ”‚fig": {} โ”‚" +"โ”‚ โ”‚" +"โ”‚rkMode": "oxker-examaple-net", โ”‚" +"โ”‚indings": {}, โ”‚" +"โ”‚rtPolicy": { โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ†‘ 10/158 โ†“ โ† 10/972 โ†’ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset_max.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset_max.snap new file mode 100644 index 0000000..b7e3d72 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset_max.snap @@ -0,0 +1,54 @@ +--- +source: src/ui/draw_blocks/inspect.rs +expression: setup.terminal.backend() +--- +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspecting: postgres 0bdea642 - c or Esc or i to exit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ”‚ โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ†‘ 158/158 โ† 972/972 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap index f4f5ab3..16bf330 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap @@ -4,41 +4,41 @@ expression: setup.terminal.backend() --- " name state status cpu memory/limit id image โ†“ rx โ†‘ tx ( h ) exit help " "โ•ญ Containers 1/3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎโ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" -"โ”‚โšช container_1 โœ“ running Up 1 hoโ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚โ”‚โ–ถ pause โ”‚" Hidden by multi-width symbols: [(2, " ")] -"โ”‚ container_2 โœ“ running Up 2 hoโ”‚ โ”‚ โ”‚โ”‚ restart โ”‚" -"โ”‚ container_3 โœ“ running Up 3 hoโ”‚ 88 โ”‚ โ”‚โ”‚ stop โ”‚" -"โ”‚ โ”‚ 88 โ”‚ โ”‚โ”‚ delete โ”‚" -"โ”‚ โ”‚ 88 โ”‚ โ”‚โ”‚ โ”‚" -"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" -"โ•ญ Logs 3/3 - container_1 - image_1 โ”€โ”€โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" -"โ”‚ line 1 โ”‚ 8b d8 )888( 8888[ 8PP""""""" 88 โ”‚ โ”‚" -"โ”‚ line 2 โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ โ”‚" -"โ”‚โ–ถ line 3 โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ โ”‚" -"โ”‚ โ”‚ โ”‚ โ”‚" -"โ”‚ โ”‚ A simple tui to view & control docker containers โ”‚ โ”‚" -"โ”‚ โ”‚ โ”‚ โ”‚" -"โ”‚ โ”‚ ( tab ) or ( shift+tab ) change panels โ”‚ โ”‚" -"โ”‚ โ”‚ ( โ†‘ โ†“ ) or ( j k ) or ( Home End ) scroll vertically โ”‚ โ”‚" -"โ”‚ โ”‚ ( โ† โ†’ ) horizontal scroll across logs โ”‚ โ”‚" -"โ”‚ โ”‚ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ”‚ โ”‚" -"โ”‚ โ”‚ ( enter ) send docker container command โ”‚ โ”‚" -"โ”‚ โ”‚ ( e ) exec into a container โ”‚ โ”‚" -"โ”‚ โ”‚ ( f ) force clear the screen & redraw the gui โ”‚ โ”‚" -"โ”‚ โ”‚ ( h ) toggle this help information - or click heading โ”‚ โ”‚" -"โ”‚ โ”‚ ( s ) save logs to file โ”‚ โ”‚" -"โ”‚ โ”‚ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ”‚ โ”‚" -"โ”‚ โ”‚ ( F1 ) or ( / ) enter filter mode โ”‚ โ”‚" -"โ”‚ โ”‚ ( # ) enter log search mode โ”‚ โ”‚" -"โ”‚ โ”‚ ( 0 ) stop sort โ”‚ โ”‚" -"โ”‚ โ”‚ ( 1 - 9 ) sort by header - or click header โ”‚ โ”‚" -"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ ( - = ) change log section height โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" -"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ cpu 03.00%โ”‚ ( \ ) toggle log section visibility โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎโ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ports โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" -"โ”‚10.00%โ”‚ โ€ข โ”‚ ( esc ) close dialog โ”‚ โ”‚โ”‚ ip private publicโ”‚" -"โ”‚ โ”‚ โ€ขโ€ขโ€ข โ”‚ ( q ) quit at any time โ”‚ โ”‚โ”‚ 8001 โ”‚" -"โ”‚ โ”‚ โ€ข โ€ข โ”‚ โ”‚ โ”‚โ”‚127.0.0.1 8003 8003โ”‚" -"โ”‚ โ”‚ โ€ข โ€ข โ”‚ currently an early work in progress, all and any input appreciated โ”‚ โ”‚โ”‚ โ”‚" -"โ”‚ โ”‚ โ€ขโ€ข โ€ข โ”‚ https://github.com/mrjackwills/oxker โ”‚ โ”‚โ”‚ โ”‚" -"โ”‚ โ”‚ โ€ข โ€ข โ€ข โ”‚ โ”‚ โ”‚โ”‚ โ”‚" -"โ”‚ โ”‚โ€ขโ€ข โ€ขโ€ข โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚โ”‚ โ”‚" +"โ”‚โšช container_1 โœ“ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB โ”‚โ”‚โ–ถ pause โ”‚" Hidden by multi-width symbols: [(2, " ")] +"โ”‚ container_2 โœ“ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB โ”‚โ”‚ restart โ”‚" +"โ”‚ container_3 โœ“ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB โ”‚โ”‚ stop โ”‚" +"โ”‚ โ”‚โ”‚ delete โ”‚" +"โ”‚ โ”‚โ”‚ โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" +"โ•ญ Logs 3/3 - container_โ•ญ 0.00.000 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚ line 1 โ”‚ 88 โ”‚ โ”‚" +"โ”‚ line 2 โ”‚ 88 โ”‚ โ”‚" +"โ”‚โ–ถ line 3 โ”‚ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYb โ”‚ โ”‚" +"โ”‚ โ”‚ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' " โ”‚ โ”‚" +"โ”‚ โ”‚ 8b d8 )888( 8888( 8PP""""""" 88 โ”‚ โ”‚" +"โ”‚ โ”‚ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ”‚ โ”‚" +"โ”‚ โ”‚ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ”‚ โ”‚" +"โ”‚ โ”‚ a work in progress, all and any input appreciated โ”‚ โ”‚" +"โ”‚ โ”‚ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ”‚ โ”‚" +"โ”‚ โ”‚ โ”‚ โ”‚" +"โ”‚ โ”‚ Keymap โ”‚ โ”‚" +"โ”‚ โ”‚ q quit c Esc close dialog โ”‚ โ”‚" +"โ”‚ โ”‚ Down Up j k Home End scroll vertically Left Right scroll horizontally โ”‚ โ”‚" +"โ”‚ โ”‚ Control increase scroll speed Enter send docker command โ”‚ โ”‚" +"โ”‚ โ”‚ e exec into a container i container inspect mode โ”‚ โ”‚" +"โ”‚ โ”‚ / F1 filter mode # log search mode โ”‚ โ”‚" +"โ”‚ โ”‚ h toggle this panel f force clear screen and redraw โ”‚ โ”‚" +"โ”‚ โ”‚ - = change log section height \ toggle of section visibility โ”‚ โ”‚" +"โ”‚ โ”‚ 1 ~ 9 sort by header - or click header 0 stop sort โ”‚ โ”‚" +"โ”‚ โ”‚ Tab Back Tab change panel m toggle mouse capture - allows text selection โ”‚ โ”‚" +"โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ s save logs to file โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" +"โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ โ”‚โ”€โ”€โ”€โ”€ ports โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ" +"โ”‚10.00%โ”‚ โ€ข โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ip private publicโ”‚" +"โ”‚ โ”‚ โ€ขโ€ขโ€ข โ”‚โ”‚ โ”‚ โ€ขโ€ขโ€ข โ”‚โ”‚ 8001 โ”‚" +"โ”‚ โ”‚ โ€ข โ€ข โ”‚โ”‚ โ”‚ โ€ข โ€ข โ”‚โ”‚127.0.0.1 8003 8003โ”‚" +"โ”‚ โ”‚ โ€ข โ€ข โ”‚โ”‚ โ”‚ โ€ข โ€ข โ”‚โ”‚ โ”‚" +"โ”‚ โ”‚ โ€ขโ€ข โ€ข โ”‚โ”‚ โ”‚ โ€ข โ€ข โ”‚โ”‚ โ”‚" +"โ”‚ โ”‚ โ€ข โ€ข โ€ข โ”‚โ”‚ โ”‚ โ€ข โ€ขโ€ข โ”‚โ”‚ โ”‚" +"โ”‚ โ”‚โ€ขโ€ข โ€ขโ€ข โ”‚โ”‚ โ”‚โ€ขโ€ข โ€ขโ€ข โ”‚โ”‚ โ”‚" "โ”‚ โ”‚ โ”‚โ”‚ โ”‚ โ”‚โ”‚ โ”‚" "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏโ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index 6addccf..46ad5f1 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -9,7 +9,7 @@ use tokio::task::JoinHandle; use uuid::Uuid; use crate::{ - app_data::{AppData, ContainerId, Header}, + app_data::{AppData, ContainerId, Header, ScrollDirection}, exec::ExecMode, }; @@ -169,10 +169,17 @@ pub enum Status { Filter, Help, Init, + Inspect, Logs, SearchLogs, } +#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)] +pub struct ScrollOffset { + pub x: usize, + pub y: usize, +} + /// Global gui_state, stored in an Arc #[derive(Debug)] pub struct GuiState { @@ -190,6 +197,8 @@ pub struct GuiState { selected_panel: SelectablePanel, screen_width: u16, show_logs: bool, + inspect_offset: ScrollOffset, + inspect_offset_max: ScrollOffset, status: HashSet, pub info_box_text: Option<(String, Instant)>, } @@ -203,6 +212,8 @@ impl GuiState { intersect_heading: HashMap::new(), intersect_help: None, intersect_panel: HashMap::new(), + inspect_offset: ScrollOffset::default(), + inspect_offset_max: ScrollOffset::default(), loading_handle: None, loading_index: 0, loading_set: HashSet::new(), @@ -235,6 +246,50 @@ impl GuiState { } } + pub fn set_inspect_offset(&mut self, sd: &ScrollDirection) { + match sd { + ScrollDirection::Up => self.inspect_offset.y = self.inspect_offset.y.saturating_sub(1), + ScrollDirection::Down => { + self.inspect_offset.y = self + .inspect_offset + .y + .saturating_add(1) + .min(self.inspect_offset_max.y) + } + ScrollDirection::Left => { + self.inspect_offset.x = self.inspect_offset.x.saturating_sub(1) + } + ScrollDirection::Right => { + self.inspect_offset.x = self + .inspect_offset + .x + .saturating_add(1) + .min(self.inspect_offset_max.x) + } + } + self.rerender.update_draw(); + } + + pub fn get_inspect_offset(&self) -> ScrollOffset { + self.inspect_offset + } + + pub fn set_inspect_offset_max(&mut self, offset: ScrollOffset) { + self.inspect_offset_max = offset + } + + pub fn set_inspect_offset_y_to_max(&mut self) { + self.inspect_offset.y = self.inspect_offset_max.y; + self.rerender.update_draw(); + } + + pub fn clear_inspect_offset(&mut self) { + self.inspect_offset.x = 0; + self.inspect_offset.y = 0; + self.inspect_offset_max = ScrollOffset::default(); + self.rerender.update_draw(); + } + /// Set the screen width, used for offset char calculations pub const fn set_screen_width(&mut self, width: u16) { self.screen_width = width; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c11118a..734a9aa 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -35,7 +35,6 @@ use crate::{ }, app_error::AppError, config::{AppColors, Keymap}, - exec::TerminalSize, input_handler::InputMessages, }; @@ -184,7 +183,8 @@ impl Ui { if let Some(mode) = exec_mode { self.reset_terminal().ok(); self.terminal.clear().ok(); - if let Err(e) = mode.run(TerminalSize::new(&self.terminal)).await { + + if let Err(e) = mode.run(self.terminal.size().ok()).await { self.app_data .lock() .set_error(e, &self.gui_state, Status::Error); @@ -359,113 +359,114 @@ fn draw_frame( let contains_filter = fd.status.contains(&Status::Filter); let contains_search_logs = fd.status.contains(&Status::SearchLogs); - let whole_layout = Layout::default() - .direction(Direction::Vertical) - .constraints(if contains_filter || contains_search_logs { - vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)] - } else { - vec![Constraint::Max(1), Constraint::Min(1)] - }) - .split(f.area()); + let contains_inspect = fd.status.contains(&Status::Inspect); - draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap); + let inspect_data = app_data.lock().get_inspect_data(); + if contains_inspect && let Some(inspect_data) = inspect_data { + draw_blocks::inspect::draw(f, colors, inspect_data, gui_state, keymap); + } else { + let whole_layout = Layout::default() + .direction(Direction::Vertical) + .constraints(if contains_filter || contains_search_logs { + vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)] + } else { + vec![Constraint::Max(1), Constraint::Min(1)] + }) + .split(f.area()); - if let Some(rect) = whole_layout.get(2) { - if contains_filter { - draw_blocks::filter::draw(*rect, colors, f, fd); - } else { - draw_blocks::search_logs::draw(*rect, colors, f, fd, keymap); + draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap); + + if let Some(rect) = whole_layout.get(2) { + if contains_filter { + draw_blocks::filter::draw(*rect, colors, f, fd); + } else { + draw_blocks::search_logs::draw(*rect, colors, f, fd, keymap); + } } - } - let upper_main = Layout::default() - .direction(Direction::Vertical) - .constraints(if fd.has_containers { - vec![Constraint::Percentage(75), Constraint::Percentage(25)] - } else { - vec![Constraint::Percentage(100), Constraint::Percentage(0)] - }) - .split(whole_layout[1]); + let upper_main = Layout::default() + .direction(Direction::Vertical) + .constraints(if fd.has_containers { + vec![Constraint::Percentage(75), Constraint::Percentage(25)] + } else { + vec![Constraint::Percentage(100), Constraint::Percentage(0)] + }) + .split(whole_layout[1]); - let containers_logs_section = Layout::default() - .direction(Direction::Vertical) - .constraints(if fd.show_logs { - vec![Constraint::Min(6), Constraint::Percentage(fd.log_height)] - } else { - vec![Constraint::Percentage(100)] - }) - .split(upper_main[0]); + let containers_logs_section = Layout::default() + .direction(Direction::Vertical) + .constraints(if fd.show_logs { + vec![Constraint::Min(6), Constraint::Percentage(fd.log_height)] + } else { + vec![Constraint::Percentage(100)] + }) + .split(upper_main[0]); - // Containers + docker commands - let containers_commands = Layout::default() - .direction(Direction::Horizontal) - .constraints(if fd.has_containers { - vec![Constraint::Percentage(90), Constraint::Percentage(10)] - } else { - vec![Constraint::Percentage(100)] - }) - .split(containers_logs_section[0]); - - draw_blocks::containers::draw(app_data, containers_commands[0], colors, f, fd, gui_state); - - if fd.show_logs { - draw_blocks::logs::draw( - app_data, - containers_logs_section[1], - colors, - f, - fd, - gui_state, - ); - } - - if let Some(id) = fd.delete_confirm.as_ref() { - app_data.lock().get_container_name_by_id(id).map_or_else( - || { - // If a container is deleted outside of oxker but whilst the Delete Confirm dialog is open, it can get caught in kind of a dead lock situation - // so if in that unique situation, just clear the delete_container id - gui_state.lock().set_delete_container(None); - }, - |name| { - draw_blocks::delete_confirm::draw(colors, f, gui_state, keymap, name); - }, - ); - } - - // only draw commands + charts if there are containers - if let Some(rect) = containers_commands.get(1) { - draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state); - - // Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border) - let ports_len = - u16::try_from(fd.port_max_lens.0 + fd.port_max_lens.1 + fd.port_max_lens.2 + 2) - .unwrap_or(26); - - let lower = Layout::default() + // Containers + docker commands + let containers_commands = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Min(1), Constraint::Max(ports_len)]) - .split(upper_main[1]); + .constraints(if fd.has_containers { + vec![Constraint::Percentage(90), Constraint::Percentage(10)] + } else { + vec![Constraint::Percentage(100)] + }) + .split(containers_logs_section[0]); - draw_blocks::charts::draw(lower[0], colors, f, fd); - draw_blocks::ports::draw(lower[1], colors, f, fd); + draw_blocks::containers::draw(app_data, containers_commands[0], colors, f, fd, gui_state); + + if fd.show_logs { + draw_blocks::logs::draw( + app_data, + containers_logs_section[1], + colors, + f, + fd, + gui_state, + ); + } + + if let Some(id) = fd.delete_confirm.as_ref() { + app_data.lock().get_container_name_by_id(id).map_or_else( + || { + // If a container is deleted outside of oxker but whilst the Delete Confirm dialog is open, it can get caught in kind of a dead lock situation + // so if in that unique situation, just clear the delete_container id + gui_state.lock().set_delete_container(None); + }, + |name| { + draw_blocks::delete_confirm::draw(colors, f, gui_state, keymap, name); + }, + ); + } + + // only draw commands + charts if there are containers + if let Some(rect) = containers_commands.get(1) { + draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state); + + // Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border) + let ports_len = + u16::try_from(fd.port_max_lens.0 + fd.port_max_lens.1 + fd.port_max_lens.2 + 2) + .unwrap_or(26); + + let lower = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Min(1), Constraint::Max(ports_len)]) + .split(upper_main[1]); + + draw_blocks::charts::draw(lower[0], colors, f, fd); + draw_blocks::ports::draw(lower[1], colors, f, fd); + } + + // Check if error, and show popup if so + if fd.status.contains(&Status::Help) { + let config = app_data.lock().config.clone(); + draw_blocks::help::draw(&config, f); + } } if let Some((text, instant)) = fd.info_text.as_ref() { draw_blocks::info::draw(colors, f, gui_state, instant, text.to_owned()); } - // Check if error, and show popup if so - if fd.status.contains(&Status::Help) { - let tz = app_data.lock().config.timezone.clone(); - draw_blocks::help::draw( - colors, - f, - keymap, - app_data.lock().config.show_timestamp, - tz.as_ref(), - ); - } - if let Some(error) = fd.has_error.as_ref() { draw_blocks::error::draw(colors, error, f, None, keymap, None); }