From e936bb4b78980d0e34a1ef5e9f6f82a9ed0ddc7f Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Tue, 2 Jan 2024 19:04:08 +0000 Subject: [PATCH] feat: re-arrange columns + ContainerName + ContainerImage, closes #32 Have container name as first column. Wrap name and image using the StringWrapper macro, so that can have a custom fmt::Display, which will only show the firs 29 chars of both the name and image name --- src/app_data/container_state.rs | 57 +++++++++++++++++++++++++++++---- src/docker_data/mod.rs | 1 + src/input_handler/mod.rs | 12 +++---- src/ui/draw_blocks.rs | 38 +++++++++++++++------- 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 8c4973f..a081afa 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -428,6 +428,51 @@ impl Logs { } } +/// ContainerName and ContainerImage are simple structs, used so can implement custom fmt functions to them +macro_rules! string_wrapper { + ($name:ident) => { + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub struct $name(String); + + impl From for $name { + fn from(value: String) -> Self { + Self(value) + } + } + + impl$name { + pub fn get(&self) -> String { + self.0.clone() + } + + pub fn set(&mut self, value: String) { + self.0 = value; + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.0.chars().count() >= 30 { + write!( + f, + "{}…", + self.0.chars().take(29).collect::() + ) + } else { + write!( + f, + "{}", + self.0 + ) + } + } + } + }; +} + +string_wrapper!(ContainerName); +string_wrapper!(ContainerImage); + /// Info for each container #[derive(Debug, Clone)] pub struct ContainerItem { @@ -435,12 +480,12 @@ pub struct ContainerItem { pub cpu_stats: VecDeque, pub docker_controls: StatefulList, pub id: ContainerId, - pub image: String, + pub image: ContainerImage, pub last_updated: u64, pub logs: Logs, pub mem_limit: ByteStats, pub mem_stats: VecDeque, - pub name: String, + pub name: ContainerName, pub rx: ByteStats, pub state: State, pub status: String, @@ -480,13 +525,13 @@ impl ContainerItem { cpu_stats: VecDeque::with_capacity(60), docker_controls, id, - image, + image: image.into(), is_oxker, last_updated: 0, logs: Logs::default(), mem_limit: ByteStats::default(), mem_stats: VecDeque::with_capacity(60), - name, + name: name.into(), rx: ByteStats::default(), state, status, @@ -550,12 +595,12 @@ impl ContainerItem { /// Container information panel headings + widths, for nice pretty formatting #[derive(Debug, Clone, Copy)] pub struct Columns { + pub name: (Header, u8), pub state: (Header, u8), pub status: (Header, u8), pub cpu: (Header, u8), pub mem: (Header, u8, u8), pub id: (Header, u8), - pub name: (Header, u8), pub image: (Header, u8), pub net_rx: (Header, u8), pub net_tx: (Header, u8), @@ -565,12 +610,12 @@ impl Columns { /// (Column titles, minimum header string length) pub const fn new() -> Self { Self { + name: (Header::Name, 4), state: (Header::State, 11), status: (Header::Status, 16), cpu: (Header::Cpu, 7), mem: (Header::Memory, 7, 7), id: (Header::Id, 8), - name: (Header::Name, 4), image: (Header::Image, 5), net_rx: (Header::Rx, 7), net_tx: (Header::Tx, 7), diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 99d0bcf..8964ebf 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -306,6 +306,7 @@ impl DockerData { self.init_all_logs(&all_ids); while let Some(x) = self.init.as_ref() { + self.app_data.lock().sort_containers(); tokio::time::sleep(std::time::Duration::from_millis(100)).await; if x.load(std::sync::atomic::Ordering::SeqCst) == all_ids.len() { self.init = None; diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index d226f67..1f7fad0 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -393,12 +393,12 @@ impl InputHandler { } else { match key_code { KeyCode::Char('0') => self.app_data.lock().reset_sorted(), - KeyCode::Char('1') => self.sort(Header::State), - KeyCode::Char('2') => self.sort(Header::Status), - KeyCode::Char('3') => self.sort(Header::Cpu), - KeyCode::Char('4') => self.sort(Header::Memory), - KeyCode::Char('5') => self.sort(Header::Id), - KeyCode::Char('6') => self.sort(Header::Name), + KeyCode::Char('1') => self.sort(Header::Name), + KeyCode::Char('2') => self.sort(Header::State), + KeyCode::Char('3') => self.sort(Header::Status), + KeyCode::Char('4') => self.sort(Header::Cpu), + KeyCode::Char('5') => self.sort(Header::Memory), + KeyCode::Char('6') => self.sort(Header::Id), KeyCode::Char('7') => self.sort(Header::Image), KeyCode::Char('8') => self.sort(Header::Rx), KeyCode::Char('9') => self.sort(Header::Tx), diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index b38be52..a3acb47 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -13,7 +13,7 @@ use ratatui::{ use std::{default::Default, time::Instant}; use std::{fmt::Display, sync::Arc}; -use crate::app_data::{ContainerItem, Header, SortedOrder}; +use crate::app_data::{ContainerItem, Header, SortedOrder, ContainerName}; use crate::{ app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats}, app_error::AppError, @@ -127,10 +127,19 @@ fn format_containers<'a>(i: &ContainerItem, widths: &Columns) -> Line<'a> { let state_style = Style::default().fg(i.state.get_color()); let blue = Style::default().fg(Color::Blue); + // Truncate? Line::from(vec![ Span::styled( format!( - "{:width$}", + i.name.to_string(), + width = widths.name.1.into() + ), + blue, + ), + Span::styled( + format!( + "{MARGIN}{:(i: &ContainerItem, widths: &Columns) -> Line<'a> { blue, ), Span::styled( - format!("{MARGIN}{:>width$}", i.name, width = widths.name.1.into()), - blue, - ), - Span::styled( - format!("{MARGIN}{:>width$}", i.image, width = widths.image.1.into()), + format!( + "{MARGIN}{:>width$}", + i.image.to_string(), + width = widths.image.1.into() + ), blue, ), Span::styled( @@ -378,9 +387,16 @@ pub fn heading_bar( // width is dependant on it that column is selected to sort - or not let gen_header = |header: &Header, width: usize| { let block = header_block(header); + // Yes this is a mess, needs documenting correctly let text = match header { Header::State => format!( - "{:>width$}{ic}", + " {:>width$}{ic}", + header, + ic = block.1, + width = width - block.2, + ), + Header::Name => format!( + " {:>width$}{ic}", header, ic = block.1, width = width - block.2, @@ -409,12 +425,12 @@ pub fn heading_bar( // Meta data to iterate over to create blocks with correct widths let header_meta = [ + (Header::Name, data.columns.name.1), (Header::State, data.columns.state.1), (Header::Status, data.columns.status.1), (Header::Cpu, data.columns.cpu.1), (Header::Memory, data.columns.mem.1 + data.columns.mem.2 + 3), (Header::Id, data.columns.id.1), - (Header::Name, data.columns.name.1), (Header::Image, data.columns.image.1), (Header::Rx, data.columns.net_rx.1), (Header::Tx, data.columns.net_tx.1), @@ -735,7 +751,7 @@ pub fn help_box(f: &mut Frame) { /// Draw the delete confirm box in the centre of the screen /// take in container id and container name here? -pub fn delete_confirm(f: &mut Frame, gui_state: &Arc>, name: &str) { +pub fn delete_confirm(f: &mut Frame, gui_state: &Arc>, name: &ContainerName) { let block = Block::default() .title(" Confirm Delete ") .border_type(BorderType::Rounded) @@ -746,7 +762,7 @@ pub fn delete_confirm(f: &mut Frame, gui_state: &Arc>, name: &st let confirm = Line::from(vec![ Span::from("Are you sure you want to delete container: "), Span::styled( - name, + name.to_string(), Style::default().fg(Color::Red).add_modifier(Modifier::BOLD), ), ]);