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
This commit is contained in:
Jack Wills
2024-01-02 19:04:08 +00:00
parent ccf8b55a74
commit e936bb4b78
4 changed files with 85 additions and 23 deletions
+51 -6
View File
@@ -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<String> 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::<String>()
)
} else {
write!(
f,
"{}",
self.0
)
}
}
}
};
}
string_wrapper!(ContainerName);
string_wrapper!(ContainerImage);
/// Info for each container /// Info for each container
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ContainerItem { pub struct ContainerItem {
@@ -435,12 +480,12 @@ pub struct ContainerItem {
pub cpu_stats: VecDeque<CpuStats>, pub cpu_stats: VecDeque<CpuStats>,
pub docker_controls: StatefulList<DockerControls>, pub docker_controls: StatefulList<DockerControls>,
pub id: ContainerId, pub id: ContainerId,
pub image: String, pub image: ContainerImage,
pub last_updated: u64, pub last_updated: u64,
pub logs: Logs, pub logs: Logs,
pub mem_limit: ByteStats, pub mem_limit: ByteStats,
pub mem_stats: VecDeque<ByteStats>, pub mem_stats: VecDeque<ByteStats>,
pub name: String, pub name: ContainerName,
pub rx: ByteStats, pub rx: ByteStats,
pub state: State, pub state: State,
pub status: String, pub status: String,
@@ -480,13 +525,13 @@ impl ContainerItem {
cpu_stats: VecDeque::with_capacity(60), cpu_stats: VecDeque::with_capacity(60),
docker_controls, docker_controls,
id, id,
image, image: image.into(),
is_oxker, is_oxker,
last_updated: 0, last_updated: 0,
logs: Logs::default(), logs: Logs::default(),
mem_limit: ByteStats::default(), mem_limit: ByteStats::default(),
mem_stats: VecDeque::with_capacity(60), mem_stats: VecDeque::with_capacity(60),
name, name: name.into(),
rx: ByteStats::default(), rx: ByteStats::default(),
state, state,
status, status,
@@ -550,12 +595,12 @@ impl ContainerItem {
/// Container information panel headings + widths, for nice pretty formatting /// Container information panel headings + widths, for nice pretty formatting
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Columns { pub struct Columns {
pub name: (Header, u8),
pub state: (Header, u8), pub state: (Header, u8),
pub status: (Header, u8), pub status: (Header, u8),
pub cpu: (Header, u8), pub cpu: (Header, u8),
pub mem: (Header, u8, u8), pub mem: (Header, u8, u8),
pub id: (Header, u8), pub id: (Header, u8),
pub name: (Header, u8),
pub image: (Header, u8), pub image: (Header, u8),
pub net_rx: (Header, u8), pub net_rx: (Header, u8),
pub net_tx: (Header, u8), pub net_tx: (Header, u8),
@@ -565,12 +610,12 @@ impl Columns {
/// (Column titles, minimum header string length) /// (Column titles, minimum header string length)
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
name: (Header::Name, 4),
state: (Header::State, 11), state: (Header::State, 11),
status: (Header::Status, 16), status: (Header::Status, 16),
cpu: (Header::Cpu, 7), cpu: (Header::Cpu, 7),
mem: (Header::Memory, 7, 7), mem: (Header::Memory, 7, 7),
id: (Header::Id, 8), id: (Header::Id, 8),
name: (Header::Name, 4),
image: (Header::Image, 5), image: (Header::Image, 5),
net_rx: (Header::Rx, 7), net_rx: (Header::Rx, 7),
net_tx: (Header::Tx, 7), net_tx: (Header::Tx, 7),
+1
View File
@@ -306,6 +306,7 @@ impl DockerData {
self.init_all_logs(&all_ids); self.init_all_logs(&all_ids);
while let Some(x) = self.init.as_ref() { while let Some(x) = self.init.as_ref() {
self.app_data.lock().sort_containers();
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
if x.load(std::sync::atomic::Ordering::SeqCst) == all_ids.len() { if x.load(std::sync::atomic::Ordering::SeqCst) == all_ids.len() {
self.init = None; self.init = None;
+6 -6
View File
@@ -393,12 +393,12 @@ impl InputHandler {
} else { } else {
match key_code { match key_code {
KeyCode::Char('0') => self.app_data.lock().reset_sorted(), KeyCode::Char('0') => self.app_data.lock().reset_sorted(),
KeyCode::Char('1') => self.sort(Header::State), KeyCode::Char('1') => self.sort(Header::Name),
KeyCode::Char('2') => self.sort(Header::Status), KeyCode::Char('2') => self.sort(Header::State),
KeyCode::Char('3') => self.sort(Header::Cpu), KeyCode::Char('3') => self.sort(Header::Status),
KeyCode::Char('4') => self.sort(Header::Memory), KeyCode::Char('4') => self.sort(Header::Cpu),
KeyCode::Char('5') => self.sort(Header::Id), KeyCode::Char('5') => self.sort(Header::Memory),
KeyCode::Char('6') => self.sort(Header::Name), KeyCode::Char('6') => self.sort(Header::Id),
KeyCode::Char('7') => self.sort(Header::Image), KeyCode::Char('7') => self.sort(Header::Image),
KeyCode::Char('8') => self.sort(Header::Rx), KeyCode::Char('8') => self.sort(Header::Rx),
KeyCode::Char('9') => self.sort(Header::Tx), KeyCode::Char('9') => self.sort(Header::Tx),
+25 -9
View File
@@ -13,7 +13,7 @@ use ratatui::{
use std::{default::Default, time::Instant}; use std::{default::Default, time::Instant};
use std::{fmt::Display, sync::Arc}; use std::{fmt::Display, sync::Arc};
use crate::app_data::{ContainerItem, Header, SortedOrder}; use crate::app_data::{ContainerItem, Header, SortedOrder, ContainerName};
use crate::{ use crate::{
app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats}, app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats},
app_error::AppError, 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 state_style = Style::default().fg(i.state.get_color());
let blue = Style::default().fg(Color::Blue); let blue = Style::default().fg(Color::Blue);
// Truncate?
Line::from(vec![ Line::from(vec![
Span::styled( Span::styled(
format!( format!(
"{:<width$}", "{:>width$}",
i.name.to_string(),
width = widths.name.1.into()
),
blue,
),
Span::styled(
format!(
"{MARGIN}{:<width$}",
i.state.to_string(), i.state.to_string(),
width = widths.state.1.into() width = widths.state.1.into()
), ),
@@ -173,11 +182,11 @@ fn format_containers<'a>(i: &ContainerItem, widths: &Columns) -> Line<'a> {
blue, blue,
), ),
Span::styled( Span::styled(
format!("{MARGIN}{:>width$}", i.name, width = widths.name.1.into()), format!(
blue, "{MARGIN}{:>width$}",
i.image.to_string(),
width = widths.image.1.into()
), ),
Span::styled(
format!("{MARGIN}{:>width$}", i.image, width = widths.image.1.into()),
blue, blue,
), ),
Span::styled( Span::styled(
@@ -378,6 +387,7 @@ pub fn heading_bar(
// width is dependant on it that column is selected to sort - or not // width is dependant on it that column is selected to sort - or not
let gen_header = |header: &Header, width: usize| { let gen_header = |header: &Header, width: usize| {
let block = header_block(header); let block = header_block(header);
// Yes this is a mess, needs documenting correctly
let text = match header { let text = match header {
Header::State => format!( Header::State => format!(
" {:>width$}{ic}", " {:>width$}{ic}",
@@ -385,6 +395,12 @@ pub fn heading_bar(
ic = block.1, ic = block.1,
width = width - block.2, width = width - block.2,
), ),
Header::Name => format!(
" {:>width$}{ic}",
header,
ic = block.1,
width = width - block.2,
),
Header::Status => format!( Header::Status => format!(
"{} {:>width$}{ic}", "{} {:>width$}{ic}",
MARGIN, MARGIN,
@@ -409,12 +425,12 @@ pub fn heading_bar(
// Meta data to iterate over to create blocks with correct widths // Meta data to iterate over to create blocks with correct widths
let header_meta = [ let header_meta = [
(Header::Name, data.columns.name.1),
(Header::State, data.columns.state.1), (Header::State, data.columns.state.1),
(Header::Status, data.columns.status.1), (Header::Status, data.columns.status.1),
(Header::Cpu, data.columns.cpu.1), (Header::Cpu, data.columns.cpu.1),
(Header::Memory, data.columns.mem.1 + data.columns.mem.2 + 3), (Header::Memory, data.columns.mem.1 + data.columns.mem.2 + 3),
(Header::Id, data.columns.id.1), (Header::Id, data.columns.id.1),
(Header::Name, data.columns.name.1),
(Header::Image, data.columns.image.1), (Header::Image, data.columns.image.1),
(Header::Rx, data.columns.net_rx.1), (Header::Rx, data.columns.net_rx.1),
(Header::Tx, data.columns.net_tx.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 /// Draw the delete confirm box in the centre of the screen
/// take in container id and container name here? /// take in container id and container name here?
pub fn delete_confirm(f: &mut Frame, gui_state: &Arc<Mutex<GuiState>>, name: &str) { pub fn delete_confirm(f: &mut Frame, gui_state: &Arc<Mutex<GuiState>>, name: &ContainerName) {
let block = Block::default() let block = Block::default()
.title(" Confirm Delete ") .title(" Confirm Delete ")
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
@@ -746,7 +762,7 @@ pub fn delete_confirm(f: &mut Frame, gui_state: &Arc<Mutex<GuiState>>, name: &st
let confirm = Line::from(vec![ let confirm = Line::from(vec![
Span::from("Are you sure you want to delete container: "), Span::from("Are you sure you want to delete container: "),
Span::styled( Span::styled(
name, name.to_string(),
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD), Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
), ),
]); ]);