diff --git a/.github/demo_01.webp b/.github/demo_01.webp index 8a7f085..a159342 100644 Binary files a/.github/demo_01.webp and b/.github/demo_01.webp differ diff --git a/.github/screenshot_01.png b/.github/screenshot_01.png index f39df48..27b91c9 100644 Binary files a/.github/screenshot_01.png and b/.github/screenshot_01.png differ diff --git a/README.md b/README.md index 7aaaa01..b2fccd4 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@

+ An animated demo of oxker in operation + link to alternative screenshot @@ -111,7 +113,7 @@ In application controls, these, amongst many other settings, can be customized w |--|--| | ```( tab )``` or ```( shift+tab )``` | Change panel, clicking on a panel also changes the selected panel.| | ```( ↑ ↓ )``` or ```( j k )``` or ```( Home End )```| Scroll line in selected panel - mouse wheel will also scroll.| -| ```( ← → )``` | When logs panel selected, scroll horizontally across the text of the logs.| +| ```( ← → )``` | Scroll horizontally across text.| | ```( ctrl )``` | Increase scroll speed, used in conjunction with scroll keys.| | ```( enter )```| Run selected docker command.| | ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. | @@ -121,6 +123,7 @@ In application controls, these, amongst many other settings, can be customized w | ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.| | ```( \ )``` | Toggle the visibility of the logs panel.| | ```( e )``` | Exec into the selected container - not available on Windows.| +| ```( i )``` | Enter container insepct mode. | | ```( f )``` | Force clear the screen & redraw the gui.| | ```( h )``` | Toggle help menu.| | ```( m )``` | Toggle mouse capture - if disabled, text on screen can be selected.| diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index 187ad5c..db1251f 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -171,7 +171,7 @@ "\\" ], // Toggle to inspect container screen - "inspect": [ + "inspect": [ "i" ], // Force a complete clear & redraw of the screen @@ -284,6 +284,27 @@ // Ports & IP listing text "text": "white" }, + // The bandwidth chart + "chart_bandwidth": { + //Background color of panel + "background": "reset", + // Border color + "border": "white", + // Maximum RX value - again paused & stopped colors not yet customizable + "max_rx": "#FFE9C1", + // # Maximum TX value - again paused & stopped colors not yet customizable + "max_tx": "#CD8C8C", + // RX points on the chart - again paused & stopped colors not yet customizable + "points_rx": "#FFE9C1", + // TX points on the chart - again paused & stopped colors not yet customizable + "points_tx": "#CD8C8C", + // RX title color + "title_rx": "#FFE9C1", + // TX title color + "title_tx": "#CD8C8C", + // The charts y-axis + "y_axis": "white" + }, // The filter panel "filter": { // Background color of panel diff --git a/example_config/example.config.toml b/example_config/example.config.toml index bddc3da..1b1a0c1 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -256,6 +256,27 @@ points = "cyan" # The charts y-axis y_axis = "white" +# The bandwidth chart +[colors.chart_bandwidth] +# Background color of panel +background = "reset" +# Border color +border = "white" +# Maximum RX value - again paused & stopped colors not yet customizable +max_rx = "#FFE9C1" +# Maximum TX value - again paused & stopped colors not yet customizable +max_tx = "#CD8C8C" +# RX points on the chart - again paused & stopped colors not yet customizable +points_rx = "#FFE9C1" +# TX points on the chart - again paused & stopped colors not yet customizable +points_tx = "#CD8C8C" +# TX title color +title_tx = "#FFE9C1" +# RX title color +title_rx = "#CD8C8C" +# The charts y-axis +y_axis = "white" + # The ports chart [colors.chart_ports] # Background color of panel diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 6125db1..252d982 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -576,8 +576,116 @@ impl fmt::Display for ByteStats { } } -pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State); -pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State); +#[derive(Debug, Default, Clone, Copy, Eq)] +pub struct BandwidthStat(u64); + +#[cfg(test)] +impl BandwidthStat { + pub fn new(x: u64) -> Self { + Self(x) + } +} + +impl PartialEq for BandwidthStat { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl PartialOrd for BandwidthStat { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for BandwidthStat { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +#[allow(clippy::cast_precision_loss)] +impl Stats for BandwidthStat { + fn get_value(&self) -> f64 { + self.0 as f64 + } +} + +/// convert from bytes to per second, using 1000 instead of 1024 +impl fmt::Display for BandwidthStat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let as_f64 = self.get_value(); + let p = match as_f64 { + x if x >= ONE_GB => format!("{y:.2} GB/s", y = as_f64 / ONE_GB), + x if x >= ONE_MB => format!("{y:.2} Mb/s", y = as_f64 / ONE_MB), + _ => format!("{y:.2} kb/s", y = as_f64 / ONE_KB), + }; + write!(f, "{p:>x$}", x = f.width().unwrap_or(1)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NetworkBandwidth(VecDeque); + +impl NetworkBandwidth { + pub fn new() -> Self { + Self(VecDeque::with_capacity(60)) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Find the highest speed recorded in the vecque + pub fn max(&self) -> BandwidthStat { + self.to_vec_f64() + .iter() + .map(|(_, speed)| *speed) + .max_by(|a, b| a.total_cmp(b)) + .map(|m| BandwidthStat(m as u64)) + .unwrap_or(BandwidthStat(0)) + } + + pub fn push(&mut self, x: u64) { + if self.0.len() >= 60 { + self.0.pop_front(); + } + self.0.push_back(BandwidthStat(x)); + } + + /// Get the current total amount of traffic on a given device + pub fn current_total(&self) -> ByteStats { + self.0 + .back() + .map_or(ByteStats::default(), |i| ByteStats::new(i.0)) + } + + /// Convert to f64 for use in the network graph + pub fn to_vec_f64(&self) -> Vec<(f64, f64)> { + self.0 + .iter() + .zip(self.0.iter().skip(1)) + .enumerate() + .map(|(i, (prev, current))| (i as f64, current.0.saturating_sub(prev.0) as f64)) + .collect() + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ChartsData { + pub memory: ChartSeries, + pub cpu: ChartSeries, + pub rx: ChartSeries, + pub tx: ChartSeries, + pub state: State, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ChartSeries { + pub dataset: Vec<(f64, f64)>, + pub max: T, + pub current: T, +} /// Used to make sure that each log entry, for each container, is unique, /// will only push a log entry into the logs vec if timestamp of said log entry isn't in the hashset @@ -992,10 +1100,10 @@ pub struct ContainerItem { pub mem_stats: VecDeque, pub name: ContainerName, pub ports: Vec, - pub rx: ByteStats, + pub rx: NetworkBandwidth, pub state: State, pub status: ContainerStatus, - pub tx: ByteStats, + pub tx: NetworkBandwidth, } /// Basic display information, for when running in debug mode @@ -1042,10 +1150,10 @@ impl ContainerItem { mem_stats: VecDeque::with_capacity(60), name: name.into(), ports, - rx: ByteStats::default(), + rx: NetworkBandwidth::new(), state, status, - tx: ByteStats::default(), + tx: NetworkBandwidth::new(), } } @@ -1071,7 +1179,7 @@ impl ContainerItem { self.cpu_stats .iter() .enumerate() - .map(|i| (i.0 as f64, i.1.0)) + .map(|(i, v)| (i as f64, v.0)) .collect::>() } @@ -1081,24 +1189,65 @@ impl ContainerItem { self.mem_stats .iter() .enumerate() - .map(|i| (i.0 as f64, i.1.0 as f64)) + .map(|(i, v)| (i as f64, v.0 as f64)) .collect::>() } /// Get all cpu chart data - fn get_cpu_chart_data(&self) -> CpuTuple { - (self.get_cpu_dataset(), self.max_cpu_stats(), self.state) + fn get_cpu_chart_data(&self) -> ChartSeries { + ChartSeries { + dataset: self.get_cpu_dataset(), + max: self.max_cpu_stats(), + current: self + .cpu_stats + .back() + .map_or_else(|| CpuStats::new(0.0), |i| *i), + } } /// Get all mem chart data - fn get_mem_chart_data(&self) -> MemTuple { - (self.get_mem_dataset(), self.max_mem_stats(), self.state) + fn get_mem_chart_data(&self) -> ChartSeries { + ChartSeries { + dataset: self.get_mem_dataset(), + max: self.max_mem_stats(), + current: self + .mem_stats + .back() + .map_or_else(|| ByteStats::new(0), |i| *i), + } + } + + /// Get all mem chart data + /// Don't understand what we are doing here + fn get_bandwidth_chart_tx_data(&self) -> ChartSeries { + let data = self.tx.to_vec_f64(); + ChartSeries { + current: BandwidthStat(data.last().map_or(0, |i| i.1 as u64)), + dataset: data, + max: self.tx.max(), + } + } + + /// Get all mem chart data + fn get_bandwidth_chart_rx_data(&self) -> ChartSeries { + let data = self.rx.to_vec_f64(); + ChartSeries { + current: BandwidthStat(data.last().map_or(0, |i| i.1 as u64)), + dataset: data, + max: self.rx.max(), + } } /// Get chart info for cpu & memory in one function /// So only need to call .lock() once - pub fn get_chart_data(&self) -> (CpuTuple, MemTuple) { - (self.get_cpu_chart_data(), self.get_mem_chart_data()) + pub fn get_chart_data(&self) -> ChartsData { + ChartsData { + memory: self.get_mem_chart_data(), + cpu: self.get_cpu_chart_data(), + rx: self.get_bandwidth_chart_rx_data(), + tx: self.get_bandwidth_chart_tx_data(), + state: self.state, + } } } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 915a6fd..1561c1d 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -467,12 +467,14 @@ impl AppData { Header::Rx => item_ord .0 .rx - .cmp(&item_ord.1.rx) + .current_total() + .cmp(&item_ord.1.rx.current_total()) .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())), Header::Tx => item_ord .0 .tx - .cmp(&item_ord.1.tx) + .current_total() + .cmp(&item_ord.1.tx.current_total()) .then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())), Header::Name => item_ord .0 @@ -610,10 +612,17 @@ impl AppData { } /// Get a mutable container by given id + #[cfg(not(test))] fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { self.containers.items.iter_mut().find(|i| &i.id == id) } + /// As above, but make it public to testing + #[cfg(test)] + pub fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { + self.containers.items.iter_mut().find(|i| &i.id == id) + } + /// Get a mutable container by given id in the tmp_container vec fn get_hidden_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { self.hidden_containers.iter_mut().find(|i| &i.id == id) @@ -790,7 +799,7 @@ impl AppData { /// Chart data related methods /// Get mutable Option of the currently selected container chart data - pub fn get_chart_data(&self) -> Option<(CpuTuple, MemTuple)> { + pub fn get_chart_data(&self) -> Option { self.containers .state .selected() @@ -839,6 +848,7 @@ impl AppData { for container in [&self.containers.items, &self.hidden_containers] { for container in container { + // TODO refactor these let cpu_count = container.cpu_stats.back().map_or_else( || count(&CpuStats::default().to_string()), |i| count(&i.to_string()), @@ -848,14 +858,19 @@ impl AppData { || count(&ByteStats::default().to_string()), |i| count(&i.to_string()), ); - columns.cpu.1 = columns.cpu.1.max(cpu_count); columns.image.1 = columns.image.1.max(count(&container.image.to_string())); columns.mem.1 = columns.mem.1.max(mem_current_count); columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string())); columns.name.1 = columns.name.1.max(count(&container.name.to_string())); - columns.net_rx.1 = columns.net_rx.1.max(count(&container.rx.to_string())); - columns.net_tx.1 = columns.net_tx.1.max(count(&container.tx.to_string())); + columns.net_rx.1 = columns + .net_rx + .1 + .max(count(&container.rx.current_total().to_string())); + columns.net_tx.1 = columns + .net_tx + .1 + .max(count(&container.tx.current_total().to_string())); columns.state.1 = columns.state.1.max(count(&container.state.to_string())); columns.status.1 = columns.status.1.max(count(container.status.get())); } @@ -899,8 +914,12 @@ impl AppData { container.mem_stats.push_back(ByteStats::new(mem)); } - container.rx.update(rx); - container.tx.update(tx); + // Only insert if alive, or if is empty, need two to create an entry in the bandwidth chart, so instead this fills in the RX/TX total columns + if container.rx.is_empty() || container.state.is_alive() { + container.rx.push(rx); + container.tx.push(tx); + } + container.mem_limit.update(mem_limit); } if self.is_selected_container(id) { @@ -1336,13 +1355,16 @@ mod tests { assert_eq!(result, &containers); if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { - i.rx = ByteStats::new(40); + i.rx = NetworkBandwidth::new(); + i.rx.push(40); } if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { - i.rx = ByteStats::new(80); + i.rx = NetworkBandwidth::new(); + i.rx.push(80); } if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { - i.rx = ByteStats::new(2); + i.rx = NetworkBandwidth::new(); + i.rx.push(2); } // descending @@ -1373,13 +1395,16 @@ mod tests { assert_eq!(result, &containers); if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { - i.rx = ByteStats::new(400); + i.rx = NetworkBandwidth::new(); + i.rx.push(400); } if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { - i.rx = ByteStats::new(80); + i.rx = NetworkBandwidth::new(); + i.rx.push(80); } if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { - i.rx = ByteStats::new(83); + i.rx = NetworkBandwidth::new(); + i.rx.push(83); } // descending @@ -1437,13 +1462,16 @@ mod tests { assert_eq!(result, &containers); if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) { - i.rx = ByteStats::new(400); + i.rx = NetworkBandwidth::new(); + i.rx.push(400); } if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) { - i.rx = ByteStats::new(80); + i.rx = NetworkBandwidth::new(); + i.rx.push(80); } if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) { - i.rx = ByteStats::new(83); + i.rx = NetworkBandwidth::new(); + i.rx.push(83); } app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc))); @@ -2223,26 +2251,49 @@ mod tests { app_data.containers_start(); + let mut rx = NetworkBandwidth::new(); + rx.push(200); + rx.push(100); + rx.push(200); + + let mut tx = NetworkBandwidth::new(); + tx.push(300); + tx.push(600); + tx.push(900); + if let Some(item) = app_data.get_container_by_id(&ContainerId::from("1")) { - item.cpu_stats = VecDeque::from([CpuStats::new(1.1), CpuStats::new(1.2)]); + item.cpu_stats = VecDeque::from([CpuStats::new(1.2), CpuStats::new(1.2)]); item.mem_stats = VecDeque::from([ByteStats::new(1), ByteStats::new(2)]); + item.rx = rx; + item.tx = tx; } let result = app_data.get_chart_data(); assert_eq!( result, - Some(( - ( - vec![(0.0, 1.1), (1.0, 1.2)], - CpuStats::new(1.2), - State::Running(RunningState::Healthy), - ), - ( - vec![(0.0, 1.0), (1.0, 2.0)], - ByteStats::new(2), - State::Running(RunningState::Healthy), - ) - )) + Some(ChartsData { + memory: ChartSeries { + dataset: vec![(0.0, 1.0), (1.0, 2.0)], + max: ByteStats::new(2), + current: ByteStats::new(2) + }, + cpu: ChartSeries { + dataset: vec![(0.0, 1.2), (1.0, 1.2)], + max: CpuStats::new(1.2), + current: CpuStats::new(1.2) + }, + rx: ChartSeries { + dataset: vec![(0.0, 0.0), (1.0, 100.0)], + max: BandwidthStat::new(100), + current: BandwidthStat::new(100) + }, + tx: ChartSeries { + dataset: vec![(0.0, 300.0), (1.0, 300.0)], + max: BandwidthStat::new(300), + current: BandwidthStat::new(300) + }, + state: State::Running(RunningState::Healthy) + }) ); } @@ -2392,8 +2443,15 @@ mod tests { assert_eq!(result[0].cpu_stats, VecDeque::from([CpuStats::new(10.0)])); assert_eq!(result[0].mem_stats, VecDeque::from([ByteStats::new(10)])); assert_eq!(result[0].mem_limit, ByteStats::new(10)); - assert_eq!(result[0].rx, ByteStats::new(10)); - assert_eq!(result[0].tx, ByteStats::new(10)); + + let mut rx = NetworkBandwidth::new(); + rx.push(10); + let mut tx = NetworkBandwidth::new(); + tx.push(10); + assert_eq!(result[0].rx, rx); + // VecDeque::from([ByteStats::new(10)])); + assert_eq!(result[0].tx, tx); + // VecDeque::from([ByteStats::new(10)])); } #[test] diff --git a/src/config/color_parser.rs b/src/config/color_parser.rs index 87dacd7..14c505d 100644 --- a/src/config/color_parser.rs +++ b/src/config/color_parser.rs @@ -1,5 +1,8 @@ use ratatui::style::Color; +static COLOR_RX: Color = Color::Rgb(255, 233, 193); +static COLOR_TX: Color = Color::Rgb(205, 140, 140); + /// The macro accepts a list of struct names with key names /// Returns a struct where every key name is an Option, with the correct derived attributes macro_rules! optional_config_struct { @@ -249,6 +252,8 @@ optional_config_struct!( ConfigBackgroundText, background, text; ConfigBackgroundTextHighlight, background, text, text_highlight; ConfigBorders, selected, unselected; + ConfigChartBandwidth, background, border, max_rx, max_tx, title_tx, title_rx, points_rx, points_tx, y_axis; + ConfigChartCpu, background, border, order, title, max, points,y_axis; ConfigChartMemory, background, border, title, max, points, y_axis; ConfigChartPorts, background, border, title, headings, text; @@ -265,6 +270,8 @@ config_struct!( Borders, selected, unselected; ChartCpu, background, border, title, max, points, y_axis; ChartMemory, background, border, title, max, points, y_axis; + ChartBandwidth, background, border, max_rx, max_tx, title_rx, title_tx, points_rx, points_tx, y_axis; + ChartPorts, background, border, title, headings, text; Commands, background, pause, restart, stop, delete, resume, start; Containers, background, icon, text, text_rx, text_tx; @@ -284,6 +291,7 @@ pub struct ConfigColors { borders: Option, chart_cpu: Option, chart_memory: Option, + chart_bandwidth: Option, chart_ports: Option, commands: Option, container_state: Option, @@ -335,7 +343,24 @@ impl Commands { } } -/// Default colours for the help popup +/// Default colours for the Bandwidth chart +impl ChartBandwidth { + const fn new() -> Self { + Self { + background: Color::Reset, + border: Color::White, + max_rx: COLOR_RX, + title_rx: COLOR_RX, + title_tx: COLOR_TX, + max_tx: COLOR_TX, + points_rx: COLOR_RX, + points_tx: COLOR_TX, + y_axis: Color::White, + } + } +} + +/// Default colours for the CPU chart impl ChartCpu { const fn new() -> Self { Self { @@ -383,8 +408,8 @@ impl Containers { background: Color::Reset, icon: Color::White, text: Color::Blue, - text_rx: Color::Rgb(255, 233, 193), - text_tx: Color::Rgb(205, 140, 140), + text_rx: COLOR_RX, + text_tx: COLOR_TX, } } } @@ -487,6 +512,7 @@ pub struct AppColors { pub borders: Borders, pub chart_cpu: ChartCpu, pub chart_memory: ChartMemory, + pub chart_bandwidth: ChartBandwidth, pub chart_ports: ChartPorts, pub commands: Commands, pub container_state: ContainerState, @@ -507,6 +533,7 @@ impl AppColors { borders: Borders::new(), chart_cpu: ChartCpu::new(), chart_memory: ChartMemory::new(), + chart_bandwidth: ChartBandwidth::new(), chart_ports: ChartPorts::new(), commands: Commands::new(), container_state: ContainerState::new(), diff --git a/src/config/config.toml b/src/config/config.toml index 46f01b1..f70c116 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -254,6 +254,27 @@ points = "cyan" # The charts y-axis y_axis = "white" +# The bandwidth chart +[colors.chart_bandwidth] +# Background color of panel +background = "reset" +# Border color +border = "white" +# Maximum RX value - again paused & stopped colors not yet customizable +max_rx = "#FFE9C1" +# Maximum TX value - again paused & stopped colors not yet customizable +max_tx = "#CD8C8C" +# RX points on the chart - again paused & stopped colors not yet customizable +points_rx = "#FFE9C1" +# TX points on the chart - again paused & stopped colors not yet customizable +points_tx = "#CD8C8C" +# TX title color +title_rx = "#FFE9C1" +# RX title color +title_tx = "#CD8C8C" +# The charts y-axis +y_axis = "white" + # The ports chart [colors.chart_ports] # Background color of panel diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs index 28ae4aa..ee94411 100644 --- a/src/config/keymap_parser.rs +++ b/src/config/keymap_parser.rs @@ -441,8 +441,7 @@ mod tests { exec: gen_v(("g", "h")), filter_mode: gen_v(("i", "j")), force_redraw: gen_v(("k", "l")), - // TODO test me - inspect: None, + inspect: gen_v(("m", "n")), scroll_back: gen_v(("s", "t")), scroll_forward: gen_v(("q", "r")), log_search_mode: gen_v(("1", "2")), @@ -481,7 +480,6 @@ 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'))), - //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'))), diff --git a/src/ui/draw_blocks/chart_bandwidth.rs b/src/ui/draw_blocks/chart_bandwidth.rs new file mode 100644 index 0000000..79ffd48 --- /dev/null +++ b/src/ui/draw_blocks/chart_bandwidth.rs @@ -0,0 +1,700 @@ +use std::fmt::Display; + +use ratatui::{ + Frame, + layout::{Alignment, Rect}, + style::{Color, Modifier, Style, Stylize}, + symbols::{self, Marker}, + text::{Line, Span}, + widgets::{Axis, Block, BorderType, Borders, Chart, Dataset, GraphType}, +}; + +use super::FrameData; +use crate::{ + app_data::{State, Stats}, + config::AppColors, +}; + +fn make_chart<'a, T: Stats + Display>( + state: State, + colors: AppColors, + dataset: Vec>, + current_rx: &'a T, + max_rx: &'a T, + current_tx: &'a T, + max_tx: &'a T, +) -> Chart<'a> { + let gen_color = |state: &State, default: Color| { + if state.is_healthy() { + default + } else { + state.get_color(colors) + } + }; + + let mut labels = [ + Span::raw(""), + Span::styled( + format!("{max_rx}"), + Style::default() + .add_modifier(Modifier::BOLD) + .fg(gen_color(&state, colors.chart_bandwidth.max_rx)), + ), + Span::styled( + format!("{max_tx}"), + Style::default() + .add_modifier(Modifier::BOLD) + .fg(gen_color(&state, colors.chart_bandwidth.max_tx)), + ), + Span::raw(""), + ]; + + // Set the order of rx/tx on the y axis, based on which is the highest value + if max_rx.get_value() > max_tx.get_value() { + labels.reverse(); + } + + Chart::new(dataset) + .bg(colors.chart_bandwidth.background) + .block( + Block::default() + .title_alignment(Alignment::Center) + .title(Line::from(vec![ + Span::styled( + format!(" rx: {current_rx}"), + Style::default() + .add_modifier(Modifier::BOLD) + .fg(gen_color(&state, colors.chart_bandwidth.title_rx)), + ), + Span::styled( + format!(" tx: {current_tx} "), + Style::default() + .add_modifier(Modifier::BOLD) + .fg(gen_color(&state, colors.chart_bandwidth.title_tx)), + ), + ])) + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(Style::default().fg(colors.chart_bandwidth.border)), + ) + .x_axis(Axis::default().bounds([0.0, 60.0])) + .y_axis( + Axis::default() + .labels(labels) + .style(Style::default().fg(colors.chart_bandwidth.y_axis)) + .bounds([0.0, (max_rx.get_value()).max(max_tx.get_value()) + 0.01]), + ) +} + +/// Draw bandwidth chart +pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) { + if let Some(x) = fd.chart_data.as_ref() { + let mut dataset = vec![ + Dataset::default() + .marker(symbols::Marker::Dot) + .style(Style::default().fg(colors.chart_bandwidth.points_tx)) + .graph_type(GraphType::Line) + .marker(Marker::Dot) + .style(Style::default().fg(colors.chart_bandwidth.points_tx)) + .data(&x.tx.dataset), + ]; + dataset.extend(vec![ + Dataset::default() + .marker(symbols::Marker::Dot) + .style(Style::default().fg(colors.chart_bandwidth.points_rx)) + .marker(Marker::Dot) + .style(Style::default().fg(colors.chart_bandwidth.points_rx)) + .graph_type(GraphType::Line) + .data(&x.rx.dataset), + ]); + + let chart = make_chart( + x.state, + colors, + dataset, + &x.rx.current, + &x.rx.max, + &x.tx.current, + &x.tx.max, + ); + + f.render_widget(chart, area); + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use insta::assert_snapshot; + use ratatui::style::Color; + + use crate::{ + app_data::{ContainerId, NetworkBandwidth, State}, + config::AppColors, + ui::{ + FrameData, + draw_blocks::tests::{COLOR_RX, COLOR_TX, get_result, test_setup}, + }, + }; + + const TX_DOTS: [(usize, usize); 14] = [ + (1, 21), + (2, 19), + (2, 20), + (3, 18), + (3, 19), + (4, 10), + (4, 11), + (4, 17), + (4, 18), + (5, 16), + (6, 14), + (6, 15), + (7, 13), + (7, 14), + ]; + + const RX_DOTS: [(usize, usize); 15] = [ + (1, 21), + (2, 19), + (2, 20), + (3, 18), + (3, 19), + (4, 10), + (4, 11), + (4, 17), + (4, 18), + (5, 16), + (6, 16), + (6, 15), + (7, 13), + (7, 14), + (8, 13), + ]; + + const COMBINED_DOTS_RX: [(usize, usize); 15] = [ + (1, 21), + (2, 19), + (2, 20), + (3, 18), + (3, 19), + (4, 10), + (4, 11), + (4, 17), + (4, 18), + (5, 16), + (6, 15), + (6, 16), + (7, 13), + (7, 14), + (8, 13), + ]; + + const COMBINED_DOTS_TX: [(usize, usize); 8] = [ + (7, 19), + (7, 20), + (7, 21), + (8, 14), + (8, 15), + (8, 16), + (8, 17), + (8, 18), + ]; + + #[test] + /// When status is Running, but not data, charts drawn without dots etc, colours correct + fn test_draw_blocks_charts_running_none() { + let mut setup = test_setup(40, 10, true, true); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd); + }) + .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) { + // border + (9, _) | (1..=9, 0 | 39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Border first row only + (0, 0..=4 | 34..=39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Title RX + (0, 5..=18) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + // Title TX + (0, 19..=33) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // Y axis + (1..=8, 10) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // TX max + (4, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // RX max + (6, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + _ => { + assert_eq!(result_cell.fg, Color::Reset); + assert_eq!(result_cell.bg, Color::Reset); + } + } + } + } + } + + #[test] + /// Test with TX data + fn test_draw_blocks_charts_running_with_data_tx() { + let mut setup = test_setup(40, 10, true, true); + let mut tx = NetworkBandwidth::new(); + + for i in 0..=20 { + tx.push(1000 * i * (10 + 5 * i)); + } + + if let Some(item) = setup + .app_data + .lock() + .get_container_by_id(&ContainerId::from("1")) + { + item.tx = tx; + } + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + setup + .terminal + .draw(|f| { + super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd); + }) + .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) { + // border + (9, _) | (1..=9, 0 | 39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Border first row only + (0, 0..=3 | 35..=39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Title RX + (0, 4..=17) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + // Title TX + (0, 18..=34) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // Y axis + (1..=8, 12) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // TX max + (4, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // RX max + (6, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + // TX dots + x if TX_DOTS.contains(&(row_index, result_cell_index)) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + _ => { + assert_eq!(result_cell.fg, Color::Reset); + assert_eq!(result_cell.bg, Color::Reset); + } + } + } + } + } + + #[test] + /// Test with RX data + fn test_draw_blocks_charts_running_with_data_rx() { + let mut setup = test_setup(40, 10, true, true); + let mut rx = NetworkBandwidth::new(); + + for i in 0..=20 { + rx.push(2000 * i * (10 + 7 * i)); + } + + if let Some(item) = setup + .app_data + .lock() + .get_container_by_id(&ContainerId::from("1")) + { + item.rx = rx; + } + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + setup + .terminal + .draw(|f| { + super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd); + }) + .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) { + // border + (9, _) | (1..=9, 0 | 39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Border first row only + (0, 0..=3 | 35..=39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Title RX + (0, 4..=19) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + // Title TX + (0, 20..=34) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // Y axis + (1..=8, 12) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // RX max + (4, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + // TX max + (6, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // RX dots + x if RX_DOTS.contains(&(row_index, result_cell_index)) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + _ => { + assert_eq!(result_cell.fg, Color::Reset); + assert_eq!(result_cell.bg, Color::Reset); + } + } + } + } + } + + #[test] + /// Test with RX & TX data + fn test_draw_blocks_charts_running_with_data_tx_and_rx() { + let mut setup = test_setup(40, 10, true, true); + let mut rx = NetworkBandwidth::new(); + let mut tx = NetworkBandwidth::new(); + for i in 0..=20 { + rx.push(2000 * i * (10 + 7 * i)); + tx.push(200 * i * (10 + 7 * i)); + } + + if let Some(item) = setup + .app_data + .lock() + .get_container_by_id(&ContainerId::from("1")) + { + item.rx = rx; + item.tx = tx; + } + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + setup + .terminal + .draw(|f| { + super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd); + }) + .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) { + // border + (9, _) | (1..=9, 0 | 39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Border first row only + (0, 0..=3 | 36..=39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Title RX + (0, 4..=19) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + // Title TX + (0, 20..=35) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // Y axis + (1..=8, 12) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // RX max + (4, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + // TX max + (6, 1..=10) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // TX dots + x if COMBINED_DOTS_TX.contains(&(row_index, result_cell_index)) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_TX); + } + // RX dots + x if COMBINED_DOTS_RX.contains(&(row_index, result_cell_index)) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, COLOR_RX); + } + _ => { + assert_eq!(result_cell.fg, Color::Reset); + assert_eq!(result_cell.bg, Color::Reset); + } + } + } + } + } + + #[test] + /// Whens status paused, some text is now Yellow + fn test_draw_blocks_charts_paused() { + let mut setup = test_setup(40, 10, true, true); + setup.app_data.lock().containers.items[0].state = State::Paused; + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd); + }) + .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) { + // border + (9, _) | (1..=9, 0 | 39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Border first row only + (0, 0..=4 | 34..=39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Title & y-axis max + (0, 5..=33) | (4 | 6, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Yellow); + } + // Y axis + (1..=8, 10) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + _ => { + assert_eq!(result_cell.fg, Color::Reset); + assert_eq!(result_cell.bg, Color::Reset); + } + } + } + } + } + + #[test] + /// Whens status dead, some text is now red + fn test_draw_blocks_charts_dead() { + let mut setup = test_setup(40, 10, true, true); + setup.app_data.lock().containers.items[0].state = State::Dead; + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd); + }) + .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) { + // border + (9, _) | (1..=9, 0 | 39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Border first row only + (0, 0..=4 | 34..=39) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + // Title & y-axis max + (0, 5..=33) | (4 | 6, 1..=9) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Red); + } + // Y axis + (1..=8, 10) => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::White); + } + _ => { + assert_eq!(result_cell.fg, Color::Reset); + assert_eq!(result_cell.bg, Color::Reset); + } + } + } + } + } + + #[test] + /// Custom colours correctly applied to each part of the charts + fn test_draw_blocks_charts_custom_colors() { + let mut colors = AppColors::new(); + + colors.chart_bandwidth.background = Color::White; + colors.chart_bandwidth.border = Color::Red; + colors.chart_bandwidth.max_rx = Color::Green; + colors.chart_bandwidth.max_tx = Color::Magenta; + colors.chart_bandwidth.title_rx = Color::LightGreen; + colors.chart_bandwidth.title_tx = Color::LightRed; + colors.chart_bandwidth.points_rx = Color::Black; + colors.chart_bandwidth.points_tx = Color::Blue; + colors.chart_bandwidth.y_axis = Color::Yellow; + + let mut setup = test_setup(40, 10, true, true); + + let mut rx = NetworkBandwidth::new(); + let mut tx = NetworkBandwidth::new(); + for i in 0..=20 { + rx.push(2000 * i * (10 + 7 * i)); + tx.push(200 * i * (10 + 7 * i)); + } + + if let Some(item) = setup + .app_data + .lock() + .get_container_by_id(&ContainerId::from("1")) + { + item.rx = rx; + item.tx = tx; + } + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + setup + .terminal + .draw(|f| { + super::draw(setup.area, colors, f, &fd); + }) + .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) { + // border + (9, _) | (1..=9, 0 | 39) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Red); + } + // Border first row only + (0, 0..=3 | 36..=39) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Red); + } + // Title RX + (0, 4..=19) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::LightGreen); + } + // Title TX + (0, 20..=35) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::LightRed); + } + // Y axis + (1..=8, 12) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Yellow); + } + // RX max + (4, 1..=11) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Green); + } + // TX max + (6, 1..=10) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Magenta); + } + // TX dots + x if COMBINED_DOTS_TX.contains(&(row_index, result_cell_index)) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Blue); + } + // RX dots + x if COMBINED_DOTS_RX.contains(&(row_index, result_cell_index)) => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Black); + } + _ => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } +} diff --git a/src/ui/draw_blocks/charts.rs b/src/ui/draw_blocks/chart_cpu_mem.rs similarity index 95% rename from src/ui/draw_blocks/charts.rs rename to src/ui/draw_blocks/chart_cpu_mem.rs index c5da7db..1cd9952 100644 --- a/src/ui/draw_blocks/charts.rs +++ b/src/ui/draw_blocks/chart_cpu_mem.rs @@ -11,7 +11,7 @@ use ratatui::{ use super::{CONSTRAINT_50_50, FrameData}; use crate::{ - app_data::{ByteStats, CpuStats, State, Stats}, + app_data::{State, Stats}, config::AppColors, }; @@ -118,7 +118,7 @@ fn make_chart<'a, T: Stats + Display>( /// Draw the cpu + mem charts pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) { - if let Some((cpu, mem)) = fd.chart_data.as_ref() { + if let Some(x) = fd.chart_data.as_ref() { let area = Layout::default() .direction(Direction::Horizontal) .constraints(CONSTRAINT_50_50) @@ -129,34 +129,34 @@ pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) { .marker(symbols::Marker::Dot) .style(Style::default().fg(colors.chart_cpu.points)) .graph_type(GraphType::Line) - .data(&cpu.0), + .data(&x.cpu.dataset), ]; let mem_dataset = vec![ Dataset::default() .marker(symbols::Marker::Dot) .style(Style::default().fg(colors.chart_memory.points)) .graph_type(GraphType::Line) - .data(&mem.0), + .data(&x.memory.dataset), ]; - let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1)); - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64)); + // let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1)); + // #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + // let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64)); let cpu_chart = make_chart( ChartVariant::Cpu, colors, - &cpu_stats, + &x.cpu.current, cpu_dataset, - &cpu.1, - cpu.2, + &x.cpu.max, + x.state, ); let mem_chart = make_chart( ChartVariant::Memory, colors, - &mem_stats, + &x.memory.current, mem_dataset, - &mem.1, - mem.2, + &x.memory.max, + x.state, ); f.render_widget(cpu_chart, area[0]); @@ -175,7 +175,7 @@ mod tests { config::AppColors, ui::{ FrameData, - draw_blocks::tests::{COLOR_ORANGE, get_result, insert_chart_data, test_setup}, + draw_blocks::tests::{COLOR_ORANGE, get_result, insert_all_chart_data, test_setup}, }, }; @@ -274,7 +274,7 @@ mod tests { fn test_draw_blocks_charts_running_some() { let mut setup = test_setup(80, 10, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup @@ -323,7 +323,7 @@ mod tests { fn test_draw_blocks_charts_paused() { let mut setup = test_setup(80, 10, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); setup.app_data.lock().containers.items[0].state = State::Paused; let fd = FrameData::from((&setup.app_data, &setup.gui_state)); @@ -369,7 +369,7 @@ mod tests { /// When dead, text is red fn test_draw_blocks_charts_dead() { let mut setup = test_setup(80, 10, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); setup.app_data.lock().containers.items[0].state = State::Dead; let fd = FrameData::from((&setup.app_data, &setup.gui_state)); @@ -429,7 +429,7 @@ mod tests { let mut setup = test_setup(80, 10, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup diff --git a/src/ui/draw_blocks/containers.rs b/src/ui/draw_blocks/containers.rs index a63283c..7078d1b 100644 --- a/src/ui/draw_blocks/containers.rs +++ b/src/ui/draw_blocks/containers.rs @@ -82,11 +82,19 @@ fn format_containers<'a>(colors: AppColors, i: &ContainerItem, widths: &Columns) colors.containers.text, ), Span::styled( - format!("{:>width$}{MARGIN}", i.rx, width = widths.net_rx.1.into()), + format!( + "{:>width$}{MARGIN}", + i.rx.current_total(), + width = widths.net_rx.1.into() + ), Style::default().fg(colors.containers.text_rx), ), Span::styled( - format!("{:>width$}{MARGIN}", i.tx, width = widths.net_tx.1.into()), + format!( + "{:>width$}{MARGIN}", + i.tx.current_total(), + width = widths.net_tx.1.into() + ), Style::default().fg(colors.containers.text_tx), ), ]) diff --git a/src/ui/draw_blocks/error.rs b/src/ui/draw_blocks/error.rs index 01a2d55..ddb85ef 100644 --- a/src/ui/draw_blocks/error.rs +++ b/src/ui/draw_blocks/error.rs @@ -190,7 +190,6 @@ mod tests { AppColors::new(), &AppError::DockerExec, f, - // TODO test me None, &Keymap::new(), Some(4), @@ -229,7 +228,6 @@ mod tests { setup .terminal .draw(|f| { - // TODO test me super::draw( colors, &AppError::DockerExec, @@ -272,7 +270,6 @@ mod tests { setup .terminal .draw(|f| { - // TODO test me super::draw( AppColors::new(), &AppError::DockerExec, @@ -297,7 +294,6 @@ mod tests { setup .terminal .draw(|f| { - // TODO test me super::draw( AppColors::new(), &AppError::DockerExec, @@ -322,7 +318,6 @@ mod tests { setup .terminal .draw(|f| { - // TODO test me super::draw( AppColors::new(), &AppError::DockerExec, diff --git a/src/ui/draw_blocks/inspect.rs b/src/ui/draw_blocks/inspect.rs index 24b6483..bb2286e 100644 --- a/src/ui/draw_blocks/inspect.rs +++ b/src/ui/draw_blocks/inspect.rs @@ -134,11 +134,6 @@ pub fn draw( f.render_widget(paragraph, rect); } -// TODO TESTS -// Test keymap -// Test colors -// Test offset y & x - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index de4fdc6..147e166 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -11,7 +11,8 @@ use crate::config::AppColors; use super::{FrameData, GuiState, SelectablePanel, Status, gui_state::Region}; -pub mod charts; +pub mod chart_bandwidth; +pub mod chart_cpu_mem; pub mod commands; pub mod containers; pub mod delete_confirm; @@ -39,7 +40,6 @@ pub const REPO: &str = env!("CARGO_PKG_REPOSITORY"); pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); pub const MARGIN: &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 = "↓"; @@ -246,7 +246,7 @@ pub mod tests { #[allow(clippy::cast_precision_loss)] // Add fixed data to the cpu & mem vecdeques - pub fn insert_chart_data(setup: &TuiTestSetup) { + pub fn insert_all_chart_data(setup: &TuiTestSetup) { for i in 1..=10 { setup.app_data.lock().update_stats_by_id( &setup.ids[0], @@ -277,7 +277,7 @@ pub mod tests { fn test_draw_blocks_whole_layout() { let mut setup = test_setup(160, 30, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports @@ -305,7 +305,7 @@ pub mod tests { /// Check that the whole layout is drawn correctly fn test_draw_blocks_whole_layout_with_filter_bar() { let mut setup = test_setup(160, 30, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[1] @@ -341,7 +341,7 @@ pub mod tests { fn test_draw_blocks_whole_layout_long_name() { let mut setup = test_setup(190, 30, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports @@ -374,7 +374,7 @@ pub mod tests { fn test_draw_blocks_whole_layout_no_logs() { let mut setup = test_setup(160, 30, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports @@ -403,7 +403,7 @@ pub mod tests { fn test_draw_blocks_whole_layout_short_height_logs() { let mut setup = test_setup(160, 30, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports @@ -435,7 +435,7 @@ pub mod tests { fn test_draw_blocks_whole_layout_help_panel() { let mut setup = test_setup(160, 40, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports @@ -465,7 +465,7 @@ pub mod tests { fn test_draw_blocks_whole_layout_error() { let mut setup = test_setup(160, 40, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports @@ -499,7 +499,7 @@ pub mod tests { fn test_draw_blocks_whole_layout_delete() { let mut setup = test_setup(160, 40, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports @@ -531,7 +531,7 @@ pub mod tests { fn test_draw_blocks_whole_layout_info_box() { let mut setup = test_setup(160, 40, true, true); - insert_chart_data(&setup); + insert_all_chart_data(&setup); insert_logs(&setup); setup.app_data.lock().containers.items[0] .ports diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_custom_colors.snap new file mode 100644 index 0000000..699967c --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_custom_colors.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭─── rx: 566.00 kb/s tx: 56.60 kb/s ───╮" +"│ │ • │" +"│ │ •• │" +"│ │ •• │" +"│566.00 kb/s│ •• │" +"│ │ • │" +"│56.60 kb/s │ •• │" +"│ │•• ••• │" +"│ │•••••• │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_dead.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_dead.snap new file mode 100644 index 0000000..64b250a --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_dead.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭──── rx: 0.00 kb/s tx: 0.00 kb/s ─────╮" +"│ │ │" +"│ │ │" +"│ │ │" +"│0.00 kb/s│ │" +"│ │ │" +"│0.00 kb/s│ │" +"│ │ │" +"│ │ │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_paused.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_paused.snap new file mode 100644 index 0000000..64b250a --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_paused.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭──── rx: 0.00 kb/s tx: 0.00 kb/s ─────╮" +"│ │ │" +"│ │ │" +"│ │ │" +"│0.00 kb/s│ │" +"│ │ │" +"│0.00 kb/s│ │" +"│ │ │" +"│ │ │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_none.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_none.snap new file mode 100644 index 0000000..64b250a --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_none.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭──── rx: 0.00 kb/s tx: 0.00 kb/s ─────╮" +"│ │ │" +"│ │ │" +"│ │ │" +"│0.00 kb/s│ │" +"│ │ │" +"│0.00 kb/s│ │" +"│ │ │" +"│ │ │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data.snap new file mode 100644 index 0000000..f2451e2 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭─── rx: 0.00 kb/s tx: 205.00 kb/s ────╮" +"│ │ • │" +"│ │ •• │" +"│ │ •• │" +"│205.00 kb/s│ •• │" +"│ │ • │" +"│0.00 kb/s │ •• │" +"│ │•• │" +"│ │ │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_rx.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_rx.snap new file mode 100644 index 0000000..bff8635 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_rx.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭─── rx: 566.00 kb/s tx: 0.00 kb/s ────╮" +"│ │ • │" +"│ │ •• │" +"│ │ •• │" +"│566.00 kb/s│ •• │" +"│ │ • │" +"│0.00 kb/s │ •• │" +"│ │•• │" +"│ │• │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx.snap new file mode 100644 index 0000000..f2451e2 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭─── rx: 0.00 kb/s tx: 205.00 kb/s ────╮" +"│ │ • │" +"│ │ •• │" +"│ │ •• │" +"│205.00 kb/s│ •• │" +"│ │ • │" +"│0.00 kb/s │ •• │" +"│ │•• │" +"│ │ │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx_and_rx.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx_and_rx.snap new file mode 100644 index 0000000..699967c --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx_and_rx.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_bandwidth.rs +expression: setup.terminal.backend() +--- +"╭─── rx: 566.00 kb/s tx: 56.60 kb/s ───╮" +"│ │ • │" +"│ │ •• │" +"│ │ •• │" +"│566.00 kb/s│ •• │" +"│ │ • │" +"│56.60 kb/s │ •• │" +"│ │•• ••• │" +"│ │•••••• │" +"╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_custom_colors.snap new file mode 100644 index 0000000..8a75efc --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_custom_colors.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_cpu_mem.rs +expression: setup.terminal.backend() +--- +"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮" +"│10.00%│ • ││100.00 kB│ • │" +"│ │ •• ││ │ •• │" +"│ │ • • ││ │ •• │" +"│ │ • • ││ │ • • │" +"│ │ • • ││ │ •• • │" +"│ │ • •• ││ │ • • │" +"│ │•• •• ││ │• • │" +"│ │ ││ │ │" +"╰──────────────────────────────────────╯╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_dead.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_dead.snap new file mode 100644 index 0000000..8a75efc --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_dead.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_cpu_mem.rs +expression: setup.terminal.backend() +--- +"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮" +"│10.00%│ • ││100.00 kB│ • │" +"│ │ •• ││ │ •• │" +"│ │ • • ││ │ •• │" +"│ │ • • ││ │ • • │" +"│ │ • • ││ │ •• • │" +"│ │ • •• ││ │ • • │" +"│ │•• •• ││ │• • │" +"│ │ ││ │ │" +"╰──────────────────────────────────────╯╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_paused.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_paused.snap new file mode 100644 index 0000000..8a75efc --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_paused.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_cpu_mem.rs +expression: setup.terminal.backend() +--- +"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮" +"│10.00%│ • ││100.00 kB│ • │" +"│ │ •• ││ │ •• │" +"│ │ • • ││ │ •• │" +"│ │ • • ││ │ • • │" +"│ │ • • ││ │ •• • │" +"│ │ • •• ││ │ • • │" +"│ │•• •• ││ │• • │" +"│ │ ││ │ │" +"╰──────────────────────────────────────╯╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_none.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_none.snap new file mode 100644 index 0000000..8632071 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_none.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_cpu_mem.rs +expression: setup.terminal.backend() +--- +"╭───────────── cpu 00.00% ─────────────╮╭─────────── memory 0.00 kB ───────────╮" +"│00.00%│ ││0.00 kB│ │" +"│ │ ││ │ │" +"│ │ ││ │ │" +"│ │ ││ │ │" +"│ │ ││ │ │" +"│ │ ││ │ │" +"│ │ ││ │ │" +"│ │ ││ │ │" +"╰──────────────────────────────────────╯╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_some.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_some.snap new file mode 100644 index 0000000..8a75efc --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_some.snap @@ -0,0 +1,14 @@ +--- +source: src/ui/draw_blocks/chart_cpu_mem.rs +expression: setup.terminal.backend() +--- +"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮" +"│10.00%│ • ││100.00 kB│ • │" +"│ │ •• ││ │ •• │" +"│ │ • • ││ │ •• │" +"│ │ • • ││ │ • • │" +"│ │ • • ││ │ •• • │" +"│ │ • •• ││ │ • • │" +"│ │•• •• ││ │• • │" +"│ │ ││ │ │" +"╰──────────────────────────────────────╯╰──────────────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap index 053be88..b9fa938 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap @@ -25,10 +25,10 @@ expression: setup.terminal.backend() "│ │" "│ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" -"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│" -"│ │ • • ││ │ •• • ││ 8001 │" -"│ │ ••• • ││ │ •• • ││127.0.0.1 8003 8003│" -"│ │ •• ••• ││ │ •• •• ││ │" -"│ │• • ││ │• • ││ │" -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭────────────── cpu 03.00% ───────────────╮╭──────────── memory 30.00 kB ────────────╮╭────── rx: 0.00 kb/s tx: 0.00 kb/s ──────╮ ╭────────── ports ───────────╮" +"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│" +"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │" +"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │127.0.0.1 8003 8003│" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │• • ││ │• • ││ │ • │ │ │" +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap index cba5b78..0f52b1d 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap @@ -32,13 +32,13 @@ expression: setup.terminal.backend() "│ │" "│ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" -"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ • ││100.00 kB│ • ││ ip private public│" -"│ │ ••• ││ │ ••• ││ 8001 │" -"│ │ • • ││ │ • • ││127.0.0.1 8003 8003│" -"│ │ • • ││ │ • • ││ │" -"│ │ •• • ││ │ • • ││ │" -"│ │ • • • ││ │ • •• ││ │" -"│ │•• •• ││ │•• •• ││ │" -"│ │ ││ │ ││ │" -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭────────────── cpu 03.00% ───────────────╮╭──────────── memory 30.00 kB ────────────╮╭────── rx: 0.00 kb/s tx: 0.00 kb/s ──────╮ ╭────────── ports ───────────╮" +"│10.00%│ • ││100.00 kB│ • ││ │••••••• │ │ ip private public│" +"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │" +"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│" +"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │" +"│ │ • • ││ │ • • ││ │ • │ │ │" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │•• • ││ │•• •• ││ │ • │ │ │" +"│ │ ││ │ ││ │ • │ │ │" +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap index 8e3ffe9..90db02f 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap @@ -32,13 +32,13 @@ expression: setup.terminal.backend() "│ │" "│ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" -"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ • ││100.00 kB│ • ││ ip private public│" -"│ │ ••• ││ │ ••• ││ 8001 │" -"│ │ • • ││ │ • • ││127.0.0.1 8003 8003│" -"│ │ • • ││ │ • • ││ │" -"│ │ •• • ││ │ • • ││ │" -"│ │ • • • ││ │ • •• ││ │" -"│ │•• •• ││ │•• •• ││ │" -"│ │ ││ │ ││ │" -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭────────────── cpu 03.00% ───────────────╮╭──────────── memory 30.00 kB ────────────╮╭────── rx: 0.00 kb/s tx: 0.00 kb/s ──────╮ ╭────────── ports ───────────╮" +"│10.00%│ • ││100.00 kB│ • ││ │••••••• │ │ ip private public│" +"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │" +"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│" +"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │" +"│ │ • • ││ │ • • ││ │ • │ │ │" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │•• • ││ │•• •• ││ │ • │ │ │" +"│ │ ││ │ ││ │ • │ │ │" +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯" 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 16bf330..706c766 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 @@ -32,13 +32,13 @@ expression: setup.terminal.backend() "│ │ 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│" -"│ │ • • ││ │ • • ││ │" -"│ │ •• • ││ │ • • ││ │" -"│ │ • • • ││ │ • •• ││ │" -"│ │•• •• ││ │•• •• ││ │" -"│ │ ││ │ ││ │" -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭────────────── cpu 03.│ │──── ports ───────────╮" +"│10.00%│ • ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ip private public│" +"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │" +"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│" +"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │" +"│ │ • • ││ │ • • ││ │ • │ │ │" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │•• • ││ │•• •• ││ │ • │ │ │" +"│ │ ││ │ ││ │ • │ │ │" +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap index ceb6606..b12a958 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap @@ -32,13 +32,13 @@ expression: setup.terminal.backend() "│ │" "│ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" -"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ • ││100.00 kB│ • ││ ip private public│" -"│ │ ••• ││ │ ••• ││ 8001 │" -"│ │ • • ││ │ • • ││127.0.0.1 8003 8003│" -"│ │ • • ││ │ • • ││ │" -"│ │ •• • ││ │ • • ││ │" -"│ │ • • • ││ │ • •• ││ " -"│ │•• •• ││ │•• •• ││ This is a test " -"│ │ ││ │ ││ " -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰─────── " +"╭────────────── cpu 03.00% ───────────────╮╭──────────── memory 30.00 kB ────────────╮╭────── rx: 0.00 kb/s tx: 0.00 kb/s ──────╮ ╭────────── ports ───────────╮" +"│10.00%│ • ││100.00 kB│ • ││ │••••••• │ │ ip private public│" +"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │" +"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│" +"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │" +"│ │ • • ││ │ • • ││ │ • │ │ │" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ " +"│ │•• • ││ │•• •• ││ │ • │ │ This is a test " +"│ │ ││ │ ││ │ • │ │ " +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰─────── " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap index c905150..e7b9019 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap @@ -25,10 +25,10 @@ expression: setup.terminal.backend() "│ │" "│ │" "╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" -"╭───────────────────────────────── cpu 03.00% ─────────────────────────────────╮╭────────────────────────────── memory 30.00 kB ───────────────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│" -"│ │ •• • ││ │ •• • ││ 8001 │" -"│ │ ••• • ││ │ •••• • ││127.0.0.1 8003 8003│" -"│ │ ••• ••• ││ │ •• ••• ││ │" -"│ │• • ││ │• • ││ │" -"╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭─────────────────── cpu 03.00% ────────────────────╮╭───────────────── memory 30.00 kB ─────────────────╮╭────────── rx: 0.00 kb/s tx: 0.00 kb/s ───────────╮ ╭────────── ports ───────────╮" +"│10.00%│ • ││100.00 kB│ •• ││ │•••••• • │ │ ip private public│" +"│ │ ••• ││ │ ••• ││ │ • • │ │ 8001 │" +"│ │ ••• • ││ │ •• • ││0.00 kb/s│ • • │ │127.0.0.1 8003 8003│" +"│ │ • ••• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │• • ││ │• • ││ │ • │ │ │" +"╰───────────────────────────────────────────────────╯╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯ ╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap index 5e2d504..e0131b5 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap @@ -25,10 +25,10 @@ expression: setup.terminal.backend() "│ ││ │" "│ ││ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" -"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│" -"│ │ • • ││ │ •• • ││ 8001 │" -"│ │ ••• • ││ │ •• • ││127.0.0.1 8003 8003│" -"│ │ •• ••• ││ │ •• •• ││ │" -"│ │• • ││ │• • ││ │" -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭────────────── cpu 03.00% ───────────────╮╭──────────── memory 30.00 kB ────────────╮╭────── rx: 0.00 kb/s tx: 0.00 kb/s ──────╮ ╭────────── ports ───────────╮" +"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│" +"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │" +"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │127.0.0.1 8003 8003│" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │• • ││ │• • ││ │ • │ │ │" +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap index 9084985..06bb20f 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap @@ -25,10 +25,10 @@ expression: setup.terminal.backend() "│ line 2 │" "│▶ line 3 │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" -"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│" -"│ │ • • ││ │ •• • ││ 8001 │" -"│ │ ••• • ││ │ •• • ││127.0.0.1 8003 8003│" -"│ │ •• ••• ││ │ •• •• ││ │" -"│ │• • ││ │• • ││ │" -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭────────────── cpu 03.00% ───────────────╮╭──────────── memory 30.00 kB ────────────╮╭────── rx: 0.00 kb/s tx: 0.00 kb/s ──────╮ ╭────────── ports ───────────╮" +"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│" +"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │" +"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │127.0.0.1 8003 8003│" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │• • ││ │• • ││ │ • │ │ │" +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap index 7c55590..d3a3751 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap @@ -24,11 +24,11 @@ expression: setup.terminal.backend() "│ │" "│ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" -"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│" -"│ │ • • ││ │ •• • ││ 8001 │" -"│ │ ••• • ││ │ •• • ││ │" -"│ │ •• ••• ││ │ •• •• ││ │" -"│ │• • ││ │• • ││ │" -"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" +"╭────────────── cpu 03.00% ───────────────╮╭──────────── memory 30.00 kB ────────────╮╭────── rx: 0.00 kb/s tx: 0.00 kb/s ──────╮ ╭────────── ports ───────────╮" +"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│" +"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │" +"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │ │" +"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │" +"│ │• • ││ │• • ││ │ • │ │ │" +"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯" " Esc clear ← by → Name Image Status All filter term: r_1 " diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 914ffe6..096191c 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -30,8 +30,8 @@ pub use self::color_match::*; pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status}; use crate::{ app_data::{ - AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, LogSearch, - MemTuple, SortedOrder, State, + AppData, ChartsData, Columns, ContainerId, ContainerPorts, FilterBy, Header, LogSearch, + SortedOrder, State, }, app_error::AppError, config::{AppColors, Keymap}, @@ -301,7 +301,7 @@ impl Ui { #[derive(Debug, Clone)] #[allow(clippy::struct_excessive_bools)] pub struct FrameData { - chart_data: Option<(CpuTuple, MemTuple)>, + chart_data: Option, color_logs: bool, columns: Columns, container_title: String, @@ -462,7 +462,14 @@ fn draw_frame( .constraints([Constraint::Min(1), Constraint::Max(ports_len)]) .split(upper_main[1]); - draw_blocks::charts::draw(lower[0], colors, f, fd); + let charts_rect = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(66), Constraint::Percentage(33)]) + .split(lower[0]); + + draw_blocks::chart_cpu_mem::draw(charts_rect[0], colors, f, fd); + draw_blocks::chart_bandwidth::draw(charts_rect[1], colors, f, fd); + draw_blocks::ports::draw(lower[1], colors, f, fd); }