From c129f474fe2976454b1868d00e8d7d99b87ec23b Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 26 Jan 2023 20:58:30 +0000 Subject: [PATCH 01/15] chores: dependencies updated --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94fe363..03d017e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.1" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ "bitflags", "clap_derive", From a174dafe1b05908735680a874dc551a86da24777 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 26 Jan 2023 21:00:32 +0000 Subject: [PATCH 02/15] refactor: needless (double) referencing removed --- src/docker_data/mod.rs | 2 +- src/main.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index c700654..1bc10d7 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -293,7 +293,7 @@ impl DockerData { /// Animate the loading icon async fn loading_spin(loading_uuid: Uuid, gui_state: &Arc>) -> JoinHandle<()> { - let gui_state = Arc::clone(&gui_state); + let gui_state = Arc::clone(gui_state); tokio::spawn(async move { loop { tokio::time::sleep(std::time::Duration::from_millis(100)).await; diff --git a/src/main.rs b/src/main.rs index 863ad56..16608eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,9 +49,9 @@ async fn docker_init( ) { if let Ok(docker) = Docker::connect_with_socket_defaults() { if docker.ping().await.is_ok() { - let app_data = Arc::clone(&app_data); - let gui_state = Arc::clone(&gui_state); - let is_running = Arc::clone(&is_running); + let app_data = Arc::clone(app_data); + let gui_state = Arc::clone(gui_state); + let is_running = Arc::clone(is_running); tokio::spawn(DockerData::init( app_data, docker, docker_rx, gui_state, is_running, )); @@ -66,16 +66,16 @@ async fn docker_init( } /// Create data for, and then spawn a tokio thread, for the input handler -async fn handler_init( +fn handler_init( app_data: &Arc>, docker_sx: &Sender, gui_state: &Arc>, input_rx: Receiver, is_running: &Arc, ) { - let input_app_data = Arc::clone(&app_data); - let input_gui_state = Arc::clone(&gui_state); - let input_is_running = Arc::clone(&is_running); + let input_app_data = Arc::clone(app_data); + let input_gui_state = Arc::clone(gui_state); + let input_is_running = Arc::clone(is_running); tokio::spawn(input_handler::InputHandler::init( input_app_data, input_rx, @@ -97,7 +97,7 @@ async fn main() { docker_init(&app_data, docker_rx, &gui_state, &is_running).await; - handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running).await; + handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running); if args.gui { create_ui(app_data, docker_sx, gui_state, is_running, input_sx) From e4ca41dfd8ec3acae202a2d2464b8e18f5c5bdd5 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 17:13:55 +0000 Subject: [PATCH 03/15] fix: github workflow release on main only (with semver tag) --- .github/workflows/create_release_and_build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml index 636ce0b..eb35dcf 100644 --- a/.github/workflows/create_release_and_build.yml +++ b/.github/workflows/create_release_and_build.yml @@ -1,6 +1,8 @@ name: Release CI on: push: + branch: + - 'main' tags: - 'v*.*.*' jobs: From 9788b8afd98e59b1d4412a8adc54b34d2c5671fd Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 17:23:06 +0000 Subject: [PATCH 04/15] chore: dependencies updated --- Cargo.lock | 36 ++++++++++++++++++++++++++---------- Cargo.toml | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03d017e..a5ac178 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,6 +200,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f67c7faacd4db07a939f55d66a983a5355358a1f17d32cc9a8d01d1266b9ce" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.0" @@ -211,9 +227,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e" +checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8" dependencies = [ "cc", "cxxbridge-flags", @@ -223,9 +239,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200" +checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8" dependencies = [ "cc", "codespan-reporting", @@ -238,15 +254,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea" +checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971" [[package]] name = "cxxbridge-macro" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e" +checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e" dependencies = [ "proc-macro2", "quote", @@ -680,7 +696,7 @@ dependencies = [ "bollard", "cansi", "clap", - "crossterm", + "crossterm 0.26.0", "futures-util", "parking_lot", "tokio", @@ -1223,7 +1239,7 @@ checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" dependencies = [ "bitflags", "cassowary", - "crossterm", + "crossterm 0.25.0", "unicode-segmentation", "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index f01d9ef..a21975b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ anyhow = "1.0" bollard = "0.14" cansi = "2.2" clap={version="4.1", features = ["derive", "unicode", "color"] } -crossterm = "0.25" +crossterm = "0.26" futures-util = "0.3" parking_lot = {version= "0.12"} tokio = {version = "1.24", features=["full"]} From 1025579138f11e4987263c7bfe936c4c8542f8b3 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 17:23:27 +0000 Subject: [PATCH 05/15] docs: comment typo --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 16608eb..9e3fdfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ #![forbid(unsafe_code)] #![warn(clippy::unused_async, clippy::unwrap_used, clippy::expect_used)] -// Wanring - These are indeed pedantic +// Warning - These are indeed pedantic #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![allow( From b4488e4bdb0252f5c5680cee6a46427f22a282ab Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 20:44:09 +0000 Subject: [PATCH 06/15] refactor: major refactor of internal data handling What started off as an inquisitive look at how the gui drawing blocks get the data they require in order to draw to the screen, ended up as a realisation that it could be achieved in a better manner. Basically just use x.get(y), instead of using x[y] all over the place --- src/app_data/mod.rs | 292 +++++++++++++++++++++++---------------- src/docker_data/mod.rs | 31 ++--- src/input_handler/mod.rs | 16 +-- src/ui/draw_blocks.rs | 131 ++++++++---------- src/ui/gui_state.rs | 1 + src/ui/mod.rs | 16 +-- 6 files changed, 251 insertions(+), 236 deletions(-) diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index fc106f4..d8be8be 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -1,7 +1,9 @@ use bollard::models::ContainerSummary; use core::fmt; use std::time::{SystemTime, UNIX_EPOCH}; -use tui::widgets::ListItem; +use tui::{ + widgets::{ListItem, ListState}, +}; mod container_state; @@ -11,10 +13,10 @@ pub use container_state::*; /// Global app_state, stored in an Arc #[derive(Debug, Clone)] pub struct AppData { + containers: StatefulList, error: Option, sorted_by: Option<(Header, SortedOrder)>, pub args: CliArgs, - pub containers: StatefulList, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -69,6 +71,34 @@ impl AppData { self.sorted_by } + pub fn has_containers(&self) -> bool { + self.containers.items.is_empty() + } + + pub fn container_title(&self) -> String { + self.containers.get_state_title() + } + + /// Select the first container + pub fn containers_start(&mut self) { + self.containers.start(); + } + + // select the last container + pub fn containers_end(&mut self) { + self.containers.end(); + } + + /// Select the next container + pub fn containers_next(&mut self) { + self.containers.next(); + } + + // select the previous container + pub fn containers_previous(&mut self) { + self.containers.previous(); + } + /// Remove the sorted header & order, and sort by default - created datetime pub fn reset_sorted(&mut self) { self.set_sorted(None); @@ -109,55 +139,122 @@ impl AppData { .as_secs() } - /// Get the current select docker command + /// Get the current selected docker command /// So know which command to execute - pub fn get_docker_command(&self) -> Option { - let mut output = None; - if let Some(index) = self.containers.state.selected() { - if let Some(control_index) = self.containers.items[index] - .docker_controls - .state - .selected() - { - output = Some(self.containers.items[index].docker_controls.items[control_index]); - } - } - output + pub fn selected_docker_command(&self) -> Option { + self.get_selected_container().and_then(|i| { + i.docker_controls.state.selected().and_then(|x| { + i.docker_controls + .items + .get(x) + .map(std::borrow::ToOwned::to_owned) + }) + }) + } + + /// Get Option of the current selected container + pub fn get_selected_container(&self) -> Option<&ContainerItem> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get(i)) + } + + /// Get Option of the current selected container + pub const fn get_container_items(&self) -> &Vec { + &self.containers.items + } + + pub fn get_container_state(&mut self) -> &mut ListState { + &mut self.containers.state + } + + /// Get mutable Option of the current selected container + fn get_mut_selected_container(&mut self) -> Option<&mut ContainerItem> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + } + + /// Get mutable Option of the currently selected container chart data + pub fn get_chart_data(&mut self) -> Option<(CpuTuple, MemTuple)> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + .map(|i| i.get_chart_data()) + } + + /// Get mutable Vec of current containers logs + pub fn get_logs(&mut self) -> Vec> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + .map_or(vec![], |i| i.logs.to_vec()) + } + + /// Get mutable Option of the currently selected container Logs state + pub fn get_log_state(&mut self) -> Option<&mut ListState> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + .map(|i| i.logs.state()) + } + + /// Get mutable Option of the currently selected container DockerControls state + pub fn get_control_state(&mut self) -> Option<&mut ListState> { + self.get_mut_selected_container() + .map(|i| &mut i.docker_controls.state) + } + + /// Get mutable Option of the currently selected container DockerControls items + pub fn get_control_items(&mut self) -> Option<&mut Vec> { + self.get_mut_selected_container() + .map(|i| &mut i.docker_controls.items) + } + + /// Get all containers ids + pub fn get_all_ids(&self) -> Vec { + self.containers + .items + .iter() + .map(|i| i.id.clone()) + .collect::>() + } + + /// return a mutable container by given id + fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { + self.containers.items.iter_mut().find(|i| &i.id == id) } /// Change selected choice of docker commands of selected container pub fn docker_command_next(&mut self) { - if let Some(index) = self.containers.state.selected() { - if let Some(i) = self.containers.items.get_mut(index) { - i.docker_controls.next(); - } + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.next(); } } /// Change selected choice of docker commands of selected container pub fn docker_command_previous(&mut self) { - if let Some(index) = self.containers.state.selected() { - if let Some(i) = self.containers.items.get_mut(index) { - i.docker_controls.previous(); - } + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.previous(); } } /// Change selected choice of docker commands of selected container pub fn docker_command_start(&mut self) { - if let Some(index) = self.containers.state.selected() { - if let Some(i) = self.containers.items.get_mut(index) { - i.docker_controls.start(); - } + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.start(); } } /// Change selected choice of docker commands of selected container pub fn docker_command_end(&mut self) { - if let Some(index) = self.containers.state.selected() { - if let Some(i) = self.containers.items.get_mut(index) { - i.docker_controls.end(); - } + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.end(); } } @@ -180,25 +277,14 @@ impl AppData { /// If any containers on system, will always return a ContainerId /// Only returns None when no containers found. pub fn get_selected_container_id(&self) -> Option { - let mut output = None; - if let Some(index) = self.containers.state.selected() { - if let Some(x) = self.containers.items.get(index) { - output = Some(x.id.clone()); - } - } - output + self.get_selected_container().map(|i| i.id.clone()) } /// Check if the selected container is a dockerised version of oxker /// So that can disallow commands to be send /// Is a shabby way of implementing this pub fn selected_container_is_oxker(&self) -> bool { - if let Some(index) = self.containers.state.selected() { - if let Some(x) = self.containers.items.get(index) { - return x.is_oxker; - } - } - false + self.get_selected_container().map_or(false, |i| i.is_oxker) } /// Sort the containers vec, based on a heading, either ascending or descending, @@ -276,68 +362,48 @@ impl AppData { } } - /// Find the index of the currently selected single log line - pub fn get_selected_log_index(&self) -> Option { - let mut output = None; - if let Some(id) = self.get_selected_container_id() { - if let Some(index) = self.containers.items.iter().position(|i| i.id == id) { - output = Some(index); - } - } - output - } - - /// Get the title for log panel for selected container - /// will be either + /// Get the title for log panel for selected container, will be either /// 1) "logs x/x - container_name" where container_name is 32 chars max /// 2) "logs - container_name" when no logs found, again 32 chars max + /// 3) "" no container currently selected - aka no containers on system pub fn get_log_title(&self) -> String { - self.get_selected_log_index() - .map_or(String::new(), |index| { - let logs_len = self.containers.items[index].logs.get_state_title(); - let mut name = self.containers.items[index].name.clone(); - name.truncate(32); - if logs_len.is_empty() { - format!("- {name} ") - } else { - format!("{logs_len} - {name}") - } - }) + self.get_selected_container().map_or_else(String::new, |y| { + let logs_len = y.logs.get_state_title(); + let mut name = y.name.clone(); + name.truncate(32); + if logs_len.is_empty() { + format!("- {name} ") + } else { + format!("{logs_len} - {name}") + } + }) } /// select next selected log line pub fn log_next(&mut self) { - if let Some(index) = self.get_selected_log_index() { - if let Some(i) = self.containers.items.get_mut(index) { - i.logs.next(); - } + if let Some(i) = self.get_mut_selected_container() { + i.logs.next(); } } /// select previous selected log line pub fn log_previous(&mut self) { - if let Some(index) = self.get_selected_log_index() { - if let Some(i) = self.containers.items.get_mut(index) { - i.logs.previous(); - } + if let Some(i) = self.get_mut_selected_container() { + i.logs.previous(); } } /// select last selected log line pub fn log_end(&mut self) { - if let Some(index) = self.get_selected_log_index() { - if let Some(i) = self.containers.items.get_mut(index) { - i.logs.end(); - } + if let Some(i) = self.get_mut_selected_container() { + i.logs.end(); } } /// select first selected log line pub fn log_start(&mut self) { - if let Some(index) = self.get_selected_log_index() { - if let Some(i) = self.containers.items.get_mut(index) { - i.logs.start(); - } + if let Some(i) = self.get_mut_selected_container() { + i.logs.start(); } } @@ -361,7 +427,7 @@ impl AppData { /// Find the widths for the strings in the containers panel. /// So can display nicely and evenly pub fn get_width(&self) -> Columns { - let mut output = Columns::new(); + let mut columns = Columns::new(); let count = |x: &String| u8::try_from(x.chars().count()).unwrap_or(12); // Should probably find a refactor here somewhere @@ -389,49 +455,35 @@ impl AppData { ); let mem_limit_count = count(&container.mem_limit.to_string()); - if cpu_count > output.cpu.1 { - output.cpu.1 = cpu_count; + if cpu_count > columns.cpu.1 { + columns.cpu.1 = cpu_count; }; - if image_count > output.image.1 { - output.image.1 = image_count; + if image_count > columns.image.1 { + columns.image.1 = image_count; }; - if mem_current_count > output.mem.1 { - output.mem.1 = mem_current_count; + if mem_current_count > columns.mem.1 { + columns.mem.1 = mem_current_count; }; - if mem_limit_count > output.mem.2 { - output.mem.2 = mem_limit_count; + if mem_limit_count > columns.mem.2 { + columns.mem.2 = mem_limit_count; }; - if name_count > output.name.1 { - output.name.1 = name_count; + if name_count > columns.name.1 { + columns.name.1 = name_count; }; - if state_count > output.state.1 { - output.state.1 = state_count; + if state_count > columns.state.1 { + columns.state.1 = state_count; }; - if status_count > output.status.1 { - output.status.1 = status_count; + if status_count > columns.status.1 { + columns.status.1 = status_count; }; - if rx_count > output.net_rx.1 { - output.net_rx.1 = rx_count; + if rx_count > columns.net_rx.1 { + columns.net_rx.1 = rx_count; }; - if tx_count > output.net_tx.1 { - output.net_tx.1 = tx_count; + if tx_count > columns.net_tx.1 { + columns.net_tx.1 = tx_count; }; } - output - } - - /// Get all containers ids - pub fn get_all_ids(&self) -> Vec { - self.containers - .items - .iter() - .map(|i| i.id.clone()) - .collect::>() - } - - /// return a mutable container by given id - fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { - self.containers.items.iter_mut().find(|i| &i.id == id) + columns } /// Update container mem, cpu, & network stats, in single function so only need to call .lock() once @@ -563,7 +615,7 @@ impl AppData { } /// update logs of a given container, based on id - pub fn update_log_by_id(&mut self, output: Vec, id: &ContainerId) { + pub fn update_log_by_id(&mut self, logs: Vec, id: &ContainerId) { let color = self.args.color; let raw = self.args.raw; @@ -573,7 +625,7 @@ impl AppData { container.last_updated = Self::get_systemtime(); let current_len = container.logs.len(); - for mut i in output { + for mut i in logs { let tz = LogsTz::from(&i); // Strip the timestamp if `-t` flag set if !timestamp { diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 1bc10d7..09ee6e7 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -267,25 +267,18 @@ impl DockerData { /// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed) async fn update_everything(&mut self) { let all_ids = self.update_all_containers().await; - let optional_index = self.app_data.lock().get_selected_log_index(); - if let Some(index) = optional_index { - if let Some(container) = self.app_data.lock().containers.items.get(index) { - self.spawns - .lock() - .entry(SpawnId::Log(container.id.clone())) - .or_insert_with(|| { - let docker = Arc::clone(&self.docker); - let app_data = Arc::clone(&self.app_data); - let spawns = Arc::clone(&self.spawns); - tokio::spawn(Self::update_log( - app_data, - docker, - container.id.clone(), - container.last_updated, - spawns, - )) - }); - } + if let Some(container) = self.app_data.lock().get_selected_container() { + let id = container.id.clone(); + let last_updated = container.last_updated; + self.spawns + .lock() + .entry(SpawnId::Log(id.clone())) + .or_insert_with(|| { + let docker = Arc::clone(&self.docker); + let app_data = Arc::clone(&self.app_data); + let spawns = Arc::clone(&self.spawns); + tokio::spawn(Self::update_log(app_data, docker, id, last_updated, spawns)) + }); }; self.update_all_container_stats(&all_ids); self.app_data.lock().sort_containers(); diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 9d88df9..97472b9 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -207,7 +207,7 @@ impl InputHandler { KeyCode::Home => { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.start(), + SelectablePanel::Containers => locked_data.containers_start(), SelectablePanel::Logs => locked_data.log_start(), SelectablePanel::Commands => locked_data.docker_command_start(), } @@ -215,7 +215,7 @@ impl InputHandler { KeyCode::End => { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.end(), + SelectablePanel::Containers => locked_data.containers_end(), SelectablePanel::Logs => locked_data.log_end(), SelectablePanel::Commands => locked_data.docker_command_end(), } @@ -236,7 +236,7 @@ impl InputHandler { // This isn't great, just means you can't send docker commands before full initialization of the program let panel = self.gui_state.lock().selected_panel; if panel == SelectablePanel::Commands { - let option_command = self.app_data.lock().get_docker_command(); + let option_command = self.app_data.lock().selected_docker_command(); if let Some(command) = option_command { let option_id = self.app_data.lock().get_selected_container_id(); @@ -287,14 +287,12 @@ impl InputHandler { MouseEventKind::ScrollUp => self.previous(), MouseEventKind::ScrollDown => self.next(), MouseEventKind::Down(MouseButton::Left) => { - let header_intersects = self.gui_state.lock().header_intersect(Rect::new( + if let Some(header) = self.gui_state.lock().header_intersect(Rect::new( mouse_event.column, mouse_event.row, 1, 1, - )); - - if let Some(header) = header_intersects { + )) { self.sort(header); } @@ -313,7 +311,7 @@ impl InputHandler { fn next(&mut self) { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.next(), + SelectablePanel::Containers => locked_data.containers_next(), SelectablePanel::Logs => locked_data.log_next(), SelectablePanel::Commands => locked_data.docker_command_next(), }; @@ -323,7 +321,7 @@ impl InputHandler { fn previous(&mut self) { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.previous(), + SelectablePanel::Containers => locked_data.containers_previous(), SelectablePanel::Logs => locked_data.log_previous(), SelectablePanel::Commands => locked_data.docker_command_previous(), } diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index 6f48b2e..a3d384c 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -57,11 +57,7 @@ fn generate_block<'a>( let current_selected_panel = gui_state.lock().selected_panel; let mut title = match panel { SelectablePanel::Containers => { - format!( - "{} {}", - panel.title(), - app_data.lock().containers.get_state_title() - ) + format!("{} {}", panel.title(), app_data.lock().container_title()) } SelectablePanel::Logs => { format!("{} {}", panel.title(), app_data.lock().get_log_title()) @@ -87,35 +83,31 @@ pub fn commands( area: Rect, f: &mut Frame<'_, B>, gui_state: &Arc>, - index: Option, ) { - let block = generate_block(app_data, area, gui_state, SelectablePanel::Commands); - if let Some(i) = index { - let items = app_data.lock().containers.items[i] - .docker_controls - .items - .iter() - .map(|i| { + let block = || generate_block(app_data, area, gui_state, SelectablePanel::Commands); + let items = app_data.lock().get_control_items().map_or(vec![], |i| { + i.iter() + .map(|c| { let lines = Spans::from(vec![Span::styled( - i.to_string(), - Style::default().fg(i.get_color()), + c.to_string(), + Style::default().fg(c.get_color()), )]); ListItem::new(lines) }) - .collect::>(); + .collect::>() + }); - let items = List::new(items) - .block(block) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)) - .highlight_symbol(ARROW); + let items = List::new(items) + .block(block()) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)) + .highlight_symbol(ARROW); - f.render_stateful_widget( - items, - area, - &mut app_data.lock().containers.items[i].docker_controls.state, - ); + if let Some(i) = app_data.lock().get_control_state() { + f.render_stateful_widget(items, area, i); } else { - let paragraph = Paragraph::new("").block(block).alignment(Alignment::Center); + let paragraph = Paragraph::new("") + .block(block()) + .alignment(Alignment::Center); f.render_widget(paragraph, area); } } @@ -132,8 +124,7 @@ pub fn containers( let items = app_data .lock() - .containers - .items + .get_container_items() .iter() .map(|i| { let state_style = Style::default().fg(i.state.get_color()); @@ -204,6 +195,7 @@ pub fn containers( ListItem::new(lines) }) .collect::>(); + if items.is_empty() { let paragraph = Paragraph::new("no containers running") .block(block) @@ -215,7 +207,7 @@ pub fn containers( .highlight_style(Style::default().add_modifier(Modifier::BOLD)) .highlight_symbol(CIRCLE); - f.render_stateful_widget(items, area, &mut app_data.lock().containers.state); + f.render_stateful_widget(items, area, app_data.lock().get_container_state()); } } @@ -225,69 +217,58 @@ pub fn logs( area: Rect, f: &mut Frame<'_, B>, gui_state: &Arc>, - index: Option, loading_icon: &str, ) { - let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs); - let contains_init = gui_state.lock().status_contains(&[Status::Init]); - if contains_init { + let block = || generate_block(app_data, area, gui_state, SelectablePanel::Logs); + if gui_state.lock().status_contains(&[Status::Init]) { let paragraph = Paragraph::new(format!("parsing logs {loading_icon}")) .style(Style::default()) - .block(block) + .block(block()) .alignment(Alignment::Center); f.render_widget(paragraph, area); - } else if let Some(index) = index { - let items = List::new(app_data.lock().containers.items[index].logs.to_vec()) - .block(block) + } else { + let items = app_data.lock().get_logs(); + let items = List::new(items) + .block(block()) .highlight_symbol(ARROW) .highlight_style(Style::default().add_modifier(Modifier::BOLD)); - f.render_stateful_widget( - items, - area, - app_data.lock().containers.items[index].logs.state(), - ); - } else { - let paragraph = Paragraph::new("no logs found") - .block(block) - .alignment(Alignment::Center); - f.render_widget(paragraph, area); + + if let Some(i) = app_data.lock().get_log_state() { + f.render_stateful_widget(items, area, i); + } else { + let paragraph = Paragraph::new("no logs found") + .block(block()) + .alignment(Alignment::Center); + f.render_widget(paragraph, area); + } } } /// Draw the cpu + mem charts -pub fn chart( - f: &mut Frame<'_, B>, - area: Rect, - app_data: &Arc>, - index: Option, -) { - if let Some(index) = index { +pub fn chart(f: &mut Frame<'_, B>, area: Rect, app_data: &Arc>) { + if let Some((cpu, mem)) = app_data.lock().get_chart_data() { let area = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(area); - // Check is some, else can cause out of bounds error, if containers get removed before a docker update - if let Some(data) = app_data.lock().containers.items.get(index) { - let (cpu, mem) = data.get_chart_data(); - let cpu_dataset = vec![Dataset::default() - .marker(symbols::Marker::Dot) - .style(Style::default().fg(Color::Magenta)) - .graph_type(GraphType::Line) - .data(&cpu.0)]; - let mem_dataset = vec![Dataset::default() - .marker(symbols::Marker::Dot) - .style(Style::default().fg(Color::Cyan)) - .graph_type(GraphType::Line) - .data(&mem.0)]; - let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1)); - let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64)); - let cpu_chart = make_chart(cpu.2, "cpu", cpu_dataset, &cpu_stats, &cpu.1); - let mem_chart = make_chart(mem.2, "memory", mem_dataset, &mem_stats, &mem.1); + let cpu_dataset = vec![Dataset::default() + .marker(symbols::Marker::Dot) + .style(Style::default().fg(Color::Magenta)) + .graph_type(GraphType::Line) + .data(&cpu.0)]; + let mem_dataset = vec![Dataset::default() + .marker(symbols::Marker::Dot) + .style(Style::default().fg(Color::Cyan)) + .graph_type(GraphType::Line) + .data(&mem.0)]; + let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1)); + let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64)); + let cpu_chart = make_chart(cpu.2, "cpu", cpu_dataset, &cpu_stats, &cpu.1); + let mem_chart = make_chart(mem.2, "memory", mem_dataset, &mem_stats, &mem.1); - f.render_widget(cpu_chart, area[0]); - f.render_widget(mem_chart, area[1]); - } + f.render_widget(cpu_chart, area[0]); + f.render_widget(mem_chart, area[1]); } } @@ -490,8 +471,6 @@ pub fn heading_bar( // If no containers, don't display the headers, could maybe do this first? let help_index = if has_containers { 2 } else { 0 }; - // render help info - f.render_widget(help_paragraph, split_bar[help_index]); } diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index 1c197ef..923c15a 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -242,6 +242,7 @@ impl GuiState { } /// Check if the current gui_status contains any of the given status' + /// Don't really like this methodology for gui state, needs a re-think pub fn status_contains(&self, status: &[Status]) -> bool { status.iter().any(|i| self.status.contains(i)) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 43ff005..32d387e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -141,9 +141,8 @@ fn ui( let height = if height < 12 { height + 4 } else { 12 }; let column_widths = app_data.lock().get_width(); - let has_containers = !app_data.lock().containers.items.is_empty(); + let has_containers = !app_data.lock().has_containers(); let has_error = app_data.lock().get_error(); - let log_index = app_data.lock().get_selected_log_index(); let sorted_by = app_data.lock().get_sorted(); let show_help = gui_state.lock().status_contains(&[Status::Help]); @@ -193,17 +192,10 @@ fn ui( draw_blocks::containers(app_data, top_panel[0], f, gui_state, &column_widths); if has_containers { - draw_blocks::commands(app_data, top_panel[1], f, gui_state, log_index); + draw_blocks::commands(app_data, top_panel[1], f, gui_state); } - draw_blocks::logs( - app_data, - lower_main[0], - f, - gui_state, - log_index, - &loading_icon, - ); + draw_blocks::logs(app_data, lower_main[0], f, gui_state, &loading_icon); draw_blocks::heading_bar( whole_layout[0], @@ -217,7 +209,7 @@ fn ui( // only draw charts if there are containers if has_containers { - draw_blocks::chart(f, lower_main[1], app_data, log_index); + draw_blocks::chart(f, lower_main[1], app_data); } if let Some(info) = info_text { From c4d80061dab94afd08d4d793dc147f878c965ad6 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 20:45:02 +0000 Subject: [PATCH 07/15] revert: is_running AtomicBool back to SeqCst --- src/docker_data/mod.rs | 2 +- src/input_handler/mod.rs | 4 ++-- src/ui/mod.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 09ee6e7..43a2124 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -405,7 +405,7 @@ impl DockerData { .values() .into_iter() .for_each(tokio::task::JoinHandle::abort); - self.is_running.store(false, Ordering::Relaxed); + self.is_running.store(false, Ordering::SeqCst); } } } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 97472b9..b99c5be 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -73,7 +73,7 @@ impl InputHandler { } } } - if !self.is_running.load(Ordering::Relaxed) { + if !self.is_running.load(Ordering::SeqCst) { break; } } @@ -134,7 +134,7 @@ impl InputHandler { .lock() .status_contains(&[Status::Error, Status::Init]); if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() { - self.is_running.store(false, Ordering::Relaxed); + self.is_running.store(false, Ordering::SeqCst); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 32d387e..b5a8c80 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -98,7 +98,7 @@ async fn run_app( } } } else { - while is_running.load(Ordering::Relaxed) { + while is_running.load(Ordering::SeqCst) { if crossterm::event::poll(input_poll_rate).unwrap_or(false) { if let Ok(event) = event::read() { if let Event::Key(key) = event { From 68e444bfc393eb46bac2b99eb57697bb9b0451af Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 21:53:21 +0000 Subject: [PATCH 08/15] fix: deadlock on draw logs when no containers found --- src/ui/draw_blocks.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index a3d384c..d1c844c 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -227,19 +227,24 @@ pub fn logs( .alignment(Alignment::Center); f.render_widget(paragraph, area); } else { - let items = app_data.lock().get_logs(); - let items = List::new(items) - .block(block()) - .highlight_symbol(ARROW) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)); + let logs = app_data.lock().get_logs(); - if let Some(i) = app_data.lock().get_log_state() { - f.render_stateful_widget(items, area, i); + if logs.is_empty() { + let paragraph = Paragraph::new("no logs found") + .block(block()) + .alignment(Alignment::Center); + f.render_widget(paragraph, area); } else { - let paragraph = Paragraph::new("no logs found") + let items = List::new(logs) .block(block()) - .alignment(Alignment::Center); - f.render_widget(paragraph, area); + .highlight_symbol(ARROW) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)); + + // This should always return Some, as logs is not empty + if let Some(i) = app_data.lock().get_log_state() { + f.render_stateful_widget(items, area, i); + } + } } } From 749ec712f07cff2c941aed6726c56bdbd5cb8d2c Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 21:55:23 +0000 Subject: [PATCH 09/15] chore: github workflow use regex from tag --- .github/workflows/create_release_and_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml index eb35dcf..253058a 100644 --- a/.github/workflows/create_release_and_build.yml +++ b/.github/workflows/create_release_and_build.yml @@ -4,7 +4,7 @@ on: branch: - 'main' tags: - - 'v*.*.*' + - 'v[0-9]+.[0-9]+.[0-9]+' jobs: deploy: runs-on: ubuntu-18.04 From 2ab88eb26e9bbbc4dad4651256d8d9b044ea3272 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 21:55:40 +0000 Subject: [PATCH 10/15] chore: dependencies updated --- Cargo.lock | 4 ++-- src/app_data/mod.rs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5ac178..29ad5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index d8be8be..428a2fa 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -1,9 +1,7 @@ use bollard::models::ContainerSummary; use core::fmt; use std::time::{SystemTime, UNIX_EPOCH}; -use tui::{ - widgets::{ListItem, ListState}, -}; +use tui::widgets::{ListItem, ListState}; mod container_state; From 690bd01ba1b4fd3ac6fccc826ff98223a4e703e9 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 28 Jan 2023 21:58:18 +0000 Subject: [PATCH 11/15] docs: changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7db59b..0896f21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +### Chores ++ dependencies updated, [c129f474fe2976454b1868d00e8d7d99b87ec23b], [9788b8afd98e59b1d4412a8adc54b34d2c5671fd], [2ab88eb26e9bbbc4dad4651256d8d9b044ea3272] ++ github workflow use regex from tag, [749ec712f07cff2c941aed6726c56bdbd5cb8d2c] + +### Docs ++ comment typo, [1025579138f11e4987263c7bfe936c4c8542f8b3] + +### Fixes ++ deadlock on draw logs when no containers found, [68e444bfc393eb46bac2b99eb57697bb9b0451af] ++ github workflow release on main only (with semver tag), [e4ca41dfd8ec3acae202a2d2464b8e18f5c5bdd5] + +### Refactors ++ major refactor of internal data handling, [b4488e4bdb0252f5c5680cee6a46427f22a282ab] ++ needless (double) referencing removed, [a174dafe1b05908735680a874dc551a86da24777] + +### Reverts ++ is_running AtomicBool back to SeqCst, [c4d80061dab94afd08d4d793dc147f878c965ad6] + # v0.2.0 ### 2023-01-21 From c0bb5355d6a5d352260655110ce3d5ab695acda9 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sun, 29 Jan 2023 01:52:38 +0000 Subject: [PATCH 12/15] refactor: app_data methods re-ordered & renamed --- src/app_data/mod.rs | 427 ++++++++++++++++++++------------------- src/input_handler/mod.rs | 2 +- src/main.rs | 2 +- src/ui/draw_blocks.rs | 9 +- src/ui/mod.rs | 4 +- 5 files changed, 226 insertions(+), 218 deletions(-) diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 428a2fa..fccd1f7 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -55,67 +55,6 @@ impl fmt::Display for Header { } impl AppData { - /// Generate a default app_state - pub fn default(args: CliArgs) -> Self { - Self { - args, - containers: StatefulList::new(vec![]), - error: None, - sorted_by: None, - } - } - - pub const fn get_sorted(&self) -> Option<(Header, SortedOrder)> { - self.sorted_by - } - - pub fn has_containers(&self) -> bool { - self.containers.items.is_empty() - } - - pub fn container_title(&self) -> String { - self.containers.get_state_title() - } - - /// Select the first container - pub fn containers_start(&mut self) { - self.containers.start(); - } - - // select the last container - pub fn containers_end(&mut self) { - self.containers.end(); - } - - /// Select the next container - pub fn containers_next(&mut self) { - self.containers.next(); - } - - // select the previous container - pub fn containers_previous(&mut self) { - self.containers.previous(); - } - - /// Remove the sorted header & order, and sort by default - created datetime - pub fn reset_sorted(&mut self) { - self.set_sorted(None); - } - - /// Sort containers based on a given header, if headings match, and already ascending, remove sorting - pub fn set_sort_by_header(&mut self, selected_header: Header) { - let mut output = Some((selected_header, SortedOrder::Asc)); - if let Some((current_header, order)) = self.get_sorted() { - if current_header == selected_header { - match order { - SortedOrder::Desc => output = None, - SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)), - } - } - } - self.set_sorted(output); - } - /// Change the sorted order, also set the selected container state to match new order fn set_sorted(&mut self, x: Option<(Header, SortedOrder)>) { self.sorted_by = x; @@ -137,152 +76,39 @@ impl AppData { .as_secs() } - /// Get the current selected docker command - /// So know which command to execute - pub fn selected_docker_command(&self) -> Option { - self.get_selected_container().and_then(|i| { - i.docker_controls.state.selected().and_then(|x| { - i.docker_controls - .items - .get(x) - .map(std::borrow::ToOwned::to_owned) - }) - }) - } - - /// Get Option of the current selected container - pub fn get_selected_container(&self) -> Option<&ContainerItem> { - self.containers - .state - .selected() - .and_then(|i| self.containers.items.get(i)) - } - - /// Get Option of the current selected container - pub const fn get_container_items(&self) -> &Vec { - &self.containers.items - } - - pub fn get_container_state(&mut self) -> &mut ListState { - &mut self.containers.state - } - - /// Get mutable Option of the current selected container - fn get_mut_selected_container(&mut self) -> Option<&mut ContainerItem> { - self.containers - .state - .selected() - .and_then(|i| self.containers.items.get_mut(i)) - } - - /// Get mutable Option of the currently selected container chart data - pub fn get_chart_data(&mut self) -> Option<(CpuTuple, MemTuple)> { - self.containers - .state - .selected() - .and_then(|i| self.containers.items.get_mut(i)) - .map(|i| i.get_chart_data()) - } - - /// Get mutable Vec of current containers logs - pub fn get_logs(&mut self) -> Vec> { - self.containers - .state - .selected() - .and_then(|i| self.containers.items.get_mut(i)) - .map_or(vec![], |i| i.logs.to_vec()) - } - - /// Get mutable Option of the currently selected container Logs state - pub fn get_log_state(&mut self) -> Option<&mut ListState> { - self.containers - .state - .selected() - .and_then(|i| self.containers.items.get_mut(i)) - .map(|i| i.logs.state()) - } - - /// Get mutable Option of the currently selected container DockerControls state - pub fn get_control_state(&mut self) -> Option<&mut ListState> { - self.get_mut_selected_container() - .map(|i| &mut i.docker_controls.state) - } - - /// Get mutable Option of the currently selected container DockerControls items - pub fn get_control_items(&mut self) -> Option<&mut Vec> { - self.get_mut_selected_container() - .map(|i| &mut i.docker_controls.items) - } - - /// Get all containers ids - pub fn get_all_ids(&self) -> Vec { - self.containers - .items - .iter() - .map(|i| i.id.clone()) - .collect::>() - } - - /// return a mutable container by given id - fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { - self.containers.items.iter_mut().find(|i| &i.id == id) - } - - /// Change selected choice of docker commands of selected container - pub fn docker_command_next(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.next(); + /// Generate a default app_state + pub fn default(args: CliArgs) -> Self { + Self { + args, + containers: StatefulList::new(vec![]), + error: None, + sorted_by: None, } } - /// Change selected choice of docker commands of selected container - pub fn docker_command_previous(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.previous(); + /// Container sort related methods + + /// Remove the sorted header & order, and sort by default - created datetime + pub fn reset_sorted(&mut self) { + self.set_sorted(None); + } + + /// Sort containers based on a given header, if headings match, and already ascending, remove sorting + pub fn set_sort_by_header(&mut self, selected_header: Header) { + let mut output = Some((selected_header, SortedOrder::Asc)); + if let Some((current_header, order)) = self.get_sorted() { + if current_header == selected_header { + match order { + SortedOrder::Desc => output = None, + SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)), + } + } } + self.set_sorted(output); } - /// Change selected choice of docker commands of selected container - pub fn docker_command_start(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.start(); - } - } - - /// Change selected choice of docker commands of selected container - pub fn docker_command_end(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.end(); - } - } - - /// return single app_state error - pub const fn get_error(&self) -> Option { - self.error - } - - /// remove single app_state error - pub fn remove_error(&mut self) { - self.error = None; - } - - /// insert single app_state error - pub fn set_error(&mut self, error: AppError) { - self.error = Some(error); - } - - /// Find the id of the currently selected container. - /// If any containers on system, will always return a ContainerId - /// Only returns None when no containers found. - pub fn get_selected_container_id(&self) -> Option { - self.get_selected_container().map(|i| i.id.clone()) - } - - /// Check if the selected container is a dockerised version of oxker - /// So that can disallow commands to be send - /// Is a shabby way of implementing this - pub fn selected_container_is_oxker(&self) -> bool { - self.get_selected_container().map_or(false, |i| i.is_oxker) + pub const fn get_sorted(&self) -> Option<(Header, SortedOrder)> { + self.sorted_by } /// Sort the containers vec, based on a heading, either ascending or descending, @@ -360,6 +186,120 @@ impl AppData { } } + /// Container state methods + + /// Just get the total number of containers + pub fn get_container_len(&self) -> usize { + self.containers.items.len() + } + + /// Get title for containers section + pub fn container_title(&self) -> String { + self.containers.get_state_title() + } + + /// Select the first container + pub fn containers_start(&mut self) { + self.containers.start(); + } + + // select the last container + pub fn containers_end(&mut self) { + self.containers.end(); + } + + /// Select the next container + pub fn containers_next(&mut self) { + self.containers.next(); + } + + // select the previous container + pub fn containers_previous(&mut self) { + self.containers.previous(); + } + + /// Get Container items + pub const fn get_container_items(&self) -> &Vec { + &self.containers.items + } + + /// Get Option of the current selected container + pub fn get_selected_container(&self) -> Option<&ContainerItem> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get(i)) + } + + /// Get mutable Option of the current selected container + fn get_mut_selected_container(&mut self) -> Option<&mut ContainerItem> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + } + + /// Get ListState of containers + pub fn get_container_state(&mut self) -> &mut ListState { + &mut self.containers.state + } + + /// Selected DockerCommand methods + + /// Get the current selected docker command + /// So know which command to execute + pub fn selected_docker_command(&self) -> Option { + self.get_selected_container().and_then(|i| { + i.docker_controls.state.selected().and_then(|x| { + i.docker_controls + .items + .get(x) + .map(std::borrow::ToOwned::to_owned) + }) + }) + } + /// Get mutable Option of the currently selected container DockerControls state + pub fn get_control_state(&mut self) -> Option<&mut ListState> { + self.get_mut_selected_container() + .map(|i| &mut i.docker_controls.state) + } + + /// Get mutable Option of the currently selected container DockerControls items + pub fn get_control_items(&mut self) -> Option<&mut Vec> { + self.get_mut_selected_container() + .map(|i| &mut i.docker_controls.items) + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_next(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.next(); + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_previous(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.previous(); + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_start(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.start(); + } + } + + /// Change selected choice of docker commands of selected container + pub fn docker_command_end(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.docker_controls.end(); + } + } + + /// Logs related methods + /// Get the title for log panel for selected container, will be either /// 1) "logs x/x - container_name" where container_name is 32 chars max /// 2) "logs - container_name" when no logs found, again 32 chars max @@ -405,6 +345,61 @@ impl AppData { } } + /// Chart data related methods + + /// Get mutable Option of the currently selected container chart data + pub fn get_chart_data(&mut self) -> Option<(CpuTuple, MemTuple)> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + .map(|i| i.get_chart_data()) + } + + /// Logs related methods + + /// Get mutable Vec of current containers logs + pub fn get_logs(&mut self) -> Vec> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + .map_or(vec![], |i| i.logs.to_vec()) + } + + /// Get mutable Option of the currently selected container Logs state + pub fn get_log_state(&mut self) -> Option<&mut ListState> { + self.containers + .state + .selected() + .and_then(|i| self.containers.items.get_mut(i)) + .map(|i| i.logs.state()) + } + + /// Error realted methods + + /// return single app_state error + pub const fn get_error(&self) -> Option { + self.error + } + + /// remove single app_state error + pub fn remove_error(&mut self) { + self.error = None; + } + + /// insert single app_state error + pub fn set_error(&mut self, error: AppError) { + self.error = Some(error); + } + + /// Check if the selected container is a dockerised version of oxker + /// So that can disallow commands to be send + /// Is a shabby way of implementing this + pub fn is_oxker(&self) -> bool { + self.get_selected_container().map_or(false, |i| i.is_oxker) + } + /// Check if the initial parsing has been completed, by making sure that all ids given (which are running) have a non empty cpu_stats vecdec pub fn initialised(&mut self, all_ids: &[(bool, ContainerId)]) -> bool { let count_is_running = all_ids.iter().filter(|i| i.0).count(); @@ -417,11 +412,6 @@ impl AppData { count_is_running == number_with_cpu_status } - /// Just get the total number of containers - pub fn get_container_len(&self) -> usize { - self.containers.items.len() - } - /// Find the widths for the strings in the containers panel. /// So can display nicely and evenly pub fn get_width(&self) -> Columns { @@ -484,6 +474,20 @@ impl AppData { columns } + /// Update related methods + + /// return a mutable container by given id + fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> { + self.containers.items.iter_mut().find(|i| &i.id == id) + } + + /// Find the id of the currently selected container. + /// If any containers on system, will always return a ContainerId + /// Only returns None when no containers found. + pub fn get_selected_container_id(&self) -> Option { + self.get_selected_container().map(|i| i.id.clone()) + } + /// Update container mem, cpu, & network stats, in single function so only need to call .lock() once /// Will also, if a sort is set, sort the containers pub fn update_stats( @@ -515,14 +519,19 @@ impl AppData { container.mem_limit.update(mem_limit); } // need to benchmark this? - if self.get_sorted().is_some() { + // if self.get_sorted().is_some() { self.sort_containers(); - } + // } } /// Update, or insert, containers pub fn update_containers(&mut self, all_containers: &mut [ContainerSummary]) { - let all_ids = self.get_all_ids(); + let all_ids = self + .containers + .items + .iter() + .map(|i| i.id.to_owned()) + .collect::>(); // Only sort it no containers currently set, as afterwards the order is fixed if self.containers.items.is_empty() { diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index b99c5be..7e1f649 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -241,7 +241,7 @@ impl InputHandler { if let Some(command) = option_command { let option_id = self.app_data.lock().get_selected_container_id(); // Poor way of disallowing commands to be sent to a containerised okxer - if self.app_data.lock().selected_container_is_oxker() { + if self.app_data.lock().is_oxker() { return; }; if let Some(id) = option_id { diff --git a/src/main.rs b/src/main.rs index 9e3fdfe..f7b7e1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code)] #![warn(clippy::unused_async, clippy::unwrap_used, clippy::expect_used)] // Warning - These are indeed pedantic -#![warn(clippy::pedantic)] +// #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![allow( clippy::module_name_repetitions, diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index d1c844c..8cc66b5 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -230,10 +230,10 @@ pub fn logs( let logs = app_data.lock().get_logs(); if logs.is_empty() { - let paragraph = Paragraph::new("no logs found") - .block(block()) - .alignment(Alignment::Center); - f.render_widget(paragraph, area); + let paragraph = Paragraph::new("no logs found") + .block(block()) + .alignment(Alignment::Center); + f.render_widget(paragraph, area); } else { let items = List::new(logs) .block(block()) @@ -244,7 +244,6 @@ pub fn logs( if let Some(i) = app_data.lock().get_log_state() { f.render_stateful_widget(items, area, i); } - } } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b5a8c80..a7006a1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -60,7 +60,7 @@ pub async fn create_ui( terminal.show_cursor()?; if let Err(err) = res { - println!("{err}"); + println!("error: {err}"); } std::io::stdout().flush().unwrap_or(()); Ok(()) @@ -141,7 +141,7 @@ fn ui( let height = if height < 12 { height + 4 } else { 12 }; let column_widths = app_data.lock().get_width(); - let has_containers = !app_data.lock().has_containers(); + let has_containers = app_data.lock().get_container_len() > 0; let has_error = app_data.lock().get_error(); let sorted_by = app_data.lock().get_sorted(); From d7a8639d8bcbf6253955c62a530f438c21620c76 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sun, 29 Jan 2023 02:12:02 +0000 Subject: [PATCH 13/15] docs: changelog --- CHANGELOG.md | 1 + src/app_data/mod.rs | 2 +- src/main.rs | 2 +- src/ui/draw_blocks.rs | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0896f21..e8436be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Refactors + major refactor of internal data handling, [b4488e4bdb0252f5c5680cee6a46427f22a282ab] + needless (double) referencing removed, [a174dafe1b05908735680a874dc551a86da24777] ++ app_data methods re-ordered & renamed, [c0bb5355d6a5d352260655110ce3d5ab695acda9] ### Reverts + is_running AtomicBool back to SeqCst, [c4d80061dab94afd08d4d793dc147f878c965ad6] diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index fccd1f7..77ba29a 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -530,7 +530,7 @@ impl AppData { .containers .items .iter() - .map(|i| i.id.to_owned()) + .map(|i| i.id.clone()) .collect::>(); // Only sort it no containers currently set, as afterwards the order is fixed diff --git a/src/main.rs b/src/main.rs index f7b7e1d..9e3fdfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code)] #![warn(clippy::unused_async, clippy::unwrap_used, clippy::expect_used)] // Warning - These are indeed pedantic -// #![warn(clippy::pedantic)] +#![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![allow( clippy::module_name_repetitions, diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index 8cc66b5..9e1a0fe 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -266,6 +266,7 @@ pub fn chart(f: &mut Frame<'_, B>, area: Rect, app_data: &Arc Date: Sun, 29 Jan 2023 02:34:11 +0000 Subject: [PATCH 14/15] docs: changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8436be..c38e00b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,12 @@ ### Chores + dependencies updated, [c129f474fe2976454b1868d00e8d7d99b87ec23b], [9788b8afd98e59b1d4412a8adc54b34d2c5671fd], [2ab88eb26e9bbbc4dad4651256d8d9b044ea3272] -+ github workflow use regex from tag, [749ec712f07cff2c941aed6726c56bdbd5cb8d2c] ### Docs + comment typo, [1025579138f11e4987263c7bfe936c4c8542f8b3] ### Fixes + deadlock on draw logs when no containers found, [68e444bfc393eb46bac2b99eb57697bb9b0451af] -+ github workflow release on main only (with semver tag), [e4ca41dfd8ec3acae202a2d2464b8e18f5c5bdd5] ++ github workflow release on main only (with semver tag), [e4ca41dfd8ec3acae202a2d2464b8e18f5c5bdd5], [749ec712f07cff2c941aed6726c56bdbd5cb8d2c] ### Refactors + major refactor of internal data handling, [b4488e4bdb0252f5c5680cee6a46427f22a282ab] From c77c60e151973a7b5f542a66c340a1eb17b47930 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sun, 29 Jan 2023 02:46:38 +0000 Subject: [PATCH 15/15] chore: release v0.2.1 --- .github/release-body.md | 26 ++++++++++++++------------ CHANGELOG.md | 19 +++++++++++-------- Cargo.lock | 2 +- Cargo.toml | 2 +- src/app_data/mod.rs | 2 +- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/.github/release-body.md b/.github/release-body.md index e92a330..dae66a3 100644 --- a/.github/release-body.md +++ b/.github/release-body.md @@ -1,20 +1,22 @@ -### 2023-01-21 +### 2023-01-29 ### Chores -+ dependencies updated, [8cd199db49186fad6ce432bb277e3a10f0a08d34], [d880b829c123dbe57deccadef97810e45c083737], [66d57c99558ca14d9593d6dbfd5b0e8e5d59055d], [33f9374908942f4a3b90be227fad94ca353cf351], [007d5d83d7f1b93e1e78777a4417b2740db706bd] -+ create_release.sh typos, [9a27d46a044452080144ee1367dc95886b10abf8] -+ dev container post create install cross, [2d253f034182741d434e4bac12317f24221d0d4a] ++ dependencies updated, [c129f474fe2976454b1868d00e8d7d99b87ec23b], [9788b8afd98e59b1d4412a8adc54b34d2c5671fd], [2ab88eb26e9bbbc4dad4651256d8d9b044ea3272] -### Features -**all potentially considered breaking changes** -+ store Logs in own struct, use a hashset to track timestamps, hopefully closes #11, [657ea2d751a71f05b17547b47c492d5676817336] -+ Spawn docker commands into own thread, can now execute multiple docker commands at the same time, [9ec43e124a62a80f4e78acba85fc3af5980ce260] -+ align memory columns correctly, minimum byte display value now `0.00 kB`, rather than `0 B`, closes #20, [bd7dfcd2c512a527d66a1388f90006988a487186], [51c580010a24de2427373795803936d498dc8cee] +### Docs ++ comment typo, [1025579138f11e4987263c7bfe936c4c8542f8b3] + +### Fixes ++ deadlock on draw logs when no containers found, [68e444bfc393eb46bac2b99eb57697bb9b0451af] ++ github workflow release on main only (with semver tag), [e4ca41dfd8ec3acae202a2d2464b8e18f5c5bdd5], [749ec712f07cff2c941aed6726c56bdbd5cb8d2c] ### Refactors -+ main.rs tidy up, [97b89349dc2de275ca514a1e6420255a63d775e8] -+ derive Default for GuiState, [9dcd0509efeb464f58fb53d813bd78de2447949d] -+ param reduction, AtomicBool to Relaxed, [0350293de3c00c6e5e5d787b7596bb3413d1cda1] ++ major refactor of internal data handling, [b4488e4bdb0252f5c5680cee6a46427f22a282ab] ++ needless (double) referencing removed, [a174dafe1b05908735680a874dc551a86da24777] ++ app_data methods re-ordered & renamed, [c0bb5355d6a5d352260655110ce3d5ab695acda9] + +### Reverts ++ is_running AtomicBool back to SeqCst, [c4d80061dab94afd08d4d793dc147f878c965ad6] see CHANGELOG.md for more details diff --git a/CHANGELOG.md b/CHANGELOG.md index c38e00b..5c5bd32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,23 @@ +# v0.2.1 +### 2023-01-29 + ### Chores -+ dependencies updated, [c129f474fe2976454b1868d00e8d7d99b87ec23b], [9788b8afd98e59b1d4412a8adc54b34d2c5671fd], [2ab88eb26e9bbbc4dad4651256d8d9b044ea3272] ++ dependencies updated, [c129f474](https://github.com/mrjackwills/oxker/commit/c129f474fe2976454b1868d00e8d7d99b87ec23b), [9788b8af](https://github.com/mrjackwills/oxker/commit/9788b8afd98e59b1d4412a8adc54b34d2c5671fd), [2ab88eb2](https://github.com/mrjackwills/oxker/commit/2ab88eb26e9bbbc4dad4651256d8d9b044ea3272) ### Docs -+ comment typo, [1025579138f11e4987263c7bfe936c4c8542f8b3] ++ comment typo, [10255791](https://github.com/mrjackwills/oxker/commit/1025579138f11e4987263c7bfe936c4c8542f8b3) ### Fixes -+ deadlock on draw logs when no containers found, [68e444bfc393eb46bac2b99eb57697bb9b0451af] -+ github workflow release on main only (with semver tag), [e4ca41dfd8ec3acae202a2d2464b8e18f5c5bdd5], [749ec712f07cff2c941aed6726c56bdbd5cb8d2c] ++ deadlock on draw logs when no containers found, [68e444bf](https://github.com/mrjackwills/oxker/commit/68e444bfc393eb46bac2b99eb57697bb9b0451af) ++ github workflow release on main only (with semver tag), [e4ca41df](https://github.com/mrjackwills/oxker/commit/e4ca41dfd8ec3acae202a2d2464b8e18f5c5bdd5), [749ec712](https://github.com/mrjackwills/oxker/commit/749ec712f07cff2c941aed6726c56bdbd5cb8d2c) ### Refactors -+ major refactor of internal data handling, [b4488e4bdb0252f5c5680cee6a46427f22a282ab] -+ needless (double) referencing removed, [a174dafe1b05908735680a874dc551a86da24777] -+ app_data methods re-ordered & renamed, [c0bb5355d6a5d352260655110ce3d5ab695acda9] ++ major refactor of internal data handling, [b4488e4b](https://github.com/mrjackwills/oxker/commit/b4488e4bdb0252f5c5680cee6a46427f22a282ab) ++ needless (double) referencing removed, [a174dafe](https://github.com/mrjackwills/oxker/commit/a174dafe1b05908735680a874dc551a86da24777) ++ app_data methods re-ordered & renamed, [c0bb5355](https://github.com/mrjackwills/oxker/commit/c0bb5355d6a5d352260655110ce3d5ab695acda9) ### Reverts -+ is_running AtomicBool back to SeqCst, [c4d80061dab94afd08d4d793dc147f878c965ad6] ++ is_running AtomicBool back to SeqCst, [c4d80061](https://github.com/mrjackwills/oxker/commit/c4d80061dab94afd08d4d793dc147f878c965ad6) # v0.2.0 ### 2023-01-21 diff --git a/Cargo.lock b/Cargo.lock index 29ad5d6..e6dc54d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,7 +690,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "oxker" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "bollard", diff --git a/Cargo.toml b/Cargo.toml index a21975b..2b3cc38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxker" -version = "0.2.0" +version = "0.2.1" edition = "2021" authors = ["Jack Wills "] description = "A simple tui to view & control docker containers" diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 77ba29a..0837c24 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -520,7 +520,7 @@ impl AppData { } // need to benchmark this? // if self.get_sorted().is_some() { - self.sort_containers(); + self.sort_containers(); // } }