diff --git a/.github/release-body.md b/.github/release-body.md index 6339c93..31b0f6c 100644 --- a/.github/release-body.md +++ b/.github/release-body.md @@ -1,7 +1,14 @@ -### 2022-07-23 +### 2022-08-04 + +### Chores ++ dependencies updated, [d9801cdf372521fe5624a8d68fac83ed39ef81f4] ++ linting: nursery, pedantic, unused_unwraps, [1bd61d4ce8b369d6d078201add3eea0f59fe0dea], [1263662bd9412afacddbc10721bf216ae3a843f1], [ca3315a69f593ad705eb637f227f195edd7781b2] + +### Features ++ build all production targets on release, [44f8140eaec330abe5a94f3ddae9e8b223688aa8] ### Fixes -+ remove reqwest dependency, [10ff8bab5f01f097fd6cdec60b2be947f238197b] ++ toml keywords, [dd2d82d114537e09dbeb12f360157f0e68e7846e] see CHANGELOG.md for more details diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..660f195 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'oxker'", + "cargo": { + "args": [ + "build", + "--bin=oxker", + "--package=oxker" + ], + "filter": { + "name": "oxker", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'oxker'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=oxker", + "--package=oxker" + ], + "filter": { + "name": "oxker", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed95b3..602b578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# v0.1.3 +### 2022-08-04 + +### Chores ++ dependencies updated, [d9801cdf](https://github.com/mrjackwills/oxker/commit/d9801cdf372521fe5624a8d68fac83ed39ef81f4), ++ linting: nursery, pedantic, unused_unwraps, [1bd61d4c](https://github.com/mrjackwills/oxker/commit/1bd61d4ce8b369d6d078201add3eea0f59fe0dea),, [1263662b](https://github.com/mrjackwills/oxker/commit/1263662bd9412afacddbc10721bf216ae3a843f1),, [ca3315a6](https://github.com/mrjackwills/oxker/commit/ca3315a69f593ad705eb637f227f195edd7781b2), + +### Features ++ build all production targets on release, [44f8140e](https://github.com/mrjackwills/oxker/commit/44f8140eaec330abe5a94f3ddae9e8b223688aa8), + +### Fixes ++ toml keywords, [dd2d82d1](https://github.com/mrjackwills/oxker/commit/dd2d82d114537e09dbeb12f360157f0e68e7846e), + # v0.1.2 ### 2022-07-23 diff --git a/Cargo.toml b/Cargo.toml index 3e5be83..3d7654b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxker" -version = "0.1.2" +version = "0.1.3" edition = "2021" authors = ["Jack Wills "] description = "a simple tui to view & control docker containers" @@ -8,7 +8,7 @@ repository = "https://github.com/mrjackwills/oxker" homepage = "https://github.com/mrjackwills/oxker" license = "MIT" readme = "README.md" -keywords = ["docker", "tui", "tui-rs", "tokio"] +keywords = ["docker", "tui", "tui-rs", "tokio", "terminal", "podman", "container"] categories = ["command-line-utilities"] [dependencies] diff --git a/create_release.sh b/create_release.sh index 50a90e9..4fe3c85 100755 --- a/create_release.sh +++ b/create_release.sh @@ -179,11 +179,26 @@ cargo_test () { ask_continue } +# Build all releases that GitHub workflow would +# This will download GB's of docker images +cargo_build () { + cargo install cross + cargo build --release + ask_continue + cross build --target aarch64-unknown-linux-musl --release + ask_continue + cross build --target arm-unknown-linux-musleabihf --release + ask_continue + cross build --target x86_64-pc-windows-gnu --release + ask_continue +} + # Full flow to create a new release release_flow() { check_git get_git_remote_url cargo_test + cargo_build cd "${CWD}" || error_close "Can't find ${CWD}" check_tag diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 4dbb32b..183186c 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -68,15 +68,10 @@ impl StatefulList { String::from("") } else { let len = self.items.len(); - let c = if let Some(value) = self.state.selected() { - if len > 0 { - value + 1 - } else { - value - } - } else { - 0 - }; + let c = self + .state + .selected() + .map_or(0, |value| if len > 0 { value + 1 } else { value }); format!("{}/{}", c, self.items.len()) } } @@ -95,7 +90,7 @@ pub enum State { } impl State { - pub fn get_color(&self) -> Color { + pub const fn get_color(&self) -> Color { match self { Self::Running => Color::Green, Self::Removing => Color::LightRed, @@ -105,7 +100,7 @@ impl State { } } // Dirty way to create order for the state, rather than impl Ord - pub fn order(&self) -> &'static str { + pub const fn order(&self) -> &'static str { match self { Self::Running => "a", Self::Paused => "b", @@ -158,7 +153,7 @@ pub enum DockerControls { } impl DockerControls { - pub fn get_color(&self) -> Color { + pub const fn get_color(&self) -> Color { match self { Self::Start => Color::Green, Self::Stop => Color::Red, @@ -205,7 +200,7 @@ pub struct CpuStats { } impl CpuStats { - pub fn new(value: f64) -> Self { + pub const fn new(value: f64) -> Self { Self { value } } } @@ -228,7 +223,7 @@ impl Ord for CpuStats { fn cmp(&self, other: &Self) -> Ordering { if self.value > other.value { Ordering::Greater - } else if self.value == other.value { + } else if (self.value - other.value).abs() < 0.01 { Ordering::Equal } else { Ordering::Less @@ -276,7 +271,7 @@ impl Ord for ByteStats { } impl ByteStats { - pub fn new(value: u64) -> Self { + pub const fn new(value: u64) -> Self { Self { value } } pub fn update(&mut self, value: u64) { @@ -352,7 +347,7 @@ impl ContainerItem { /// Find the max value in the last 30 items in the cpu stats vec fn max_cpu_stats(&self) -> CpuStats { match self.cpu_stats.iter().max() { - Some(value) => value.to_owned(), + Some(value) => value.clone(), None => CpuStats::new(0.0), } } @@ -360,7 +355,7 @@ impl ContainerItem { /// Find the max value in the last 30 items in the mem stats vec fn max_mem_stats(&self) -> ByteStats { match self.mem_stats.iter().max() { - Some(value) => value.to_owned(), + Some(value) => value.clone(), None => ByteStats::new(0), } } @@ -423,8 +418,8 @@ pub struct Columns { } impl Columns { - // (Column titles, minimum header string length) - pub fn new() -> Self { + /// (Column titles, minimum header string length) + pub const fn new() -> Self { Self { state: (Header::State, 11), status: (Header::Status, 16), diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index e2ed0da..39bc5ee 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -71,7 +71,7 @@ impl AppData { self.containers .items .iter() - .position(|i| Some(i.id.to_owned()) == id), + .position(|i| Some(i.id.clone()) == id), ); } /// Generate a default app_state @@ -87,8 +87,9 @@ impl AppData { } } - // Current time as unix timestamp - fn get_systemtime(&self) -> u64 { + /// Current time as unix timestamp + #[allow(clippy::expect_used)] + fn get_systemtime() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .expect("In our known reality, this error should never occur") @@ -106,7 +107,7 @@ impl AppData { .selected() { output = - Some(self.containers.items[index].docker_controls.items[control_index].clone()) + Some(self.containers.items[index].docker_controls.items[control_index].clone()); } } output @@ -115,28 +116,28 @@ impl AppData { /// Change selected choice of docker commands of selected container pub fn docker_command_next(&mut self) { if let Some(index) = self.containers.state.selected() { - self.containers.items[index].docker_controls.next() + self.containers.items[index].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() { - self.containers.items[index].docker_controls.previous() + self.containers.items[index].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() { - self.containers.items[index].docker_controls.start() + self.containers.items[index].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() { - self.containers.items[index].docker_controls.end() + self.containers.items[index].docker_controls.end(); } } @@ -167,9 +168,9 @@ impl AppData { .iter() .skip(index) .take(1) - .map(|i| i.id.to_owned()) + .map(|i| i.id.clone()) .collect::(); - output = Some(id) + output = Some(id); } output } @@ -225,7 +226,7 @@ impl AppData { Header::Image => match so { SortedOrder::Asc => self.containers.items.sort_by(|a, b| a.image.cmp(&b.image)), SortedOrder::Desc => { - self.containers.items.sort_by(|a, b| b.image.cmp(&a.image)) + self.containers.items.sort_by(|a, b| b.image.cmp(&a.image)); } }, Header::Name => match so { @@ -258,38 +259,37 @@ impl AppData { /// Get the title for log panel for selected container /// will be "logs x/x" pub fn get_log_title(&self) -> String { - if let Some(index) = self.get_selected_log_index() { - self.containers.items[index].logs.get_state_title() - } else { - String::from("") - } + self.get_selected_log_index().map_or_else( + || String::from(""), + |index| self.containers.items[index].logs.get_state_title(), + ) } /// select next selected log line pub fn log_next(&mut self) { if let Some(index) = self.get_selected_log_index() { - self.containers.items[index].logs.next() + self.containers.items[index].logs.next(); } } /// select previous selected log line pub fn log_previous(&mut self) { if let Some(index) = self.get_selected_log_index() { - self.containers.items[index].logs.previous() + self.containers.items[index].logs.previous(); } } /// select last selected log line pub fn log_end(&mut self) { if let Some(index) = self.get_selected_log_index() { - self.containers.items[index].logs.end() + self.containers.items[index].logs.end(); } } /// select first selected log line pub fn log_start(&mut self) { if let Some(index) = self.get_selected_log_index() { - self.containers.items[index].logs.start() + self.containers.items[index].logs.start(); } } @@ -315,7 +315,7 @@ impl AppData { let mut output = Columns::new(); let count = |x: &String| x.chars().count(); - for container in self.containers.items.iter() { + for container in &self.containers.items { let cpu_count = count( &container .cpu_stats @@ -329,8 +329,8 @@ impl AppData { container.mem_limit )); - let net_rx_count = count(&container.rx.to_string()); - let net_tx_count = count(&container.tx.to_string()); + let rx_count = count(&container.rx.to_string()); + let tx_count = count(&container.tx.to_string()); let image_count = count(&container.image); let name_count = count(&container.name); let state_count = count(&container.state.to_string()); @@ -354,11 +354,11 @@ impl AppData { if status_count > output.status.1 { output.status.1 = status_count; }; - if net_rx_count > output.net_rx.1 { - output.net_rx.1 = net_rx_count; + if rx_count > output.net_rx.1 { + output.net_rx.1 = rx_count; }; - if net_tx_count > output.net_tx.1 { - output.net_tx.1 = net_tx_count; + if tx_count > output.net_tx.1 { + output.net_tx.1 = tx_count; }; } output @@ -369,7 +369,7 @@ impl AppData { self.containers .items .iter() - .map(|i| i.id.to_owned()) + .map(|i| i.id.clone()) .collect::>() } @@ -381,14 +381,14 @@ impl AppData { /// Update container mem, cpu, & network stats, in single function so only need to call .lock() once pub fn update_stats( &mut self, - id: String, + id: &str, cpu_stat: Option, mem_stat: Option, mem_limit: u64, rx: u64, tx: u64, ) { - if let Some(container) = self.get_container_by_id(&id) { + if let Some(container) = self.get_container_by_id(id) { if container.cpu_stats.len() >= 60 { container.cpu_stats.pop_front(); } @@ -443,7 +443,7 @@ impl AppData { .unwrap_or(&vec!["".to_owned()]) .get(0) .unwrap_or(&String::from("")) - .to_owned(); + .clone(); if let Some(c) = name.chars().next() { if c == '/' { name.remove(0); @@ -460,10 +460,10 @@ impl AppData { let image = i.image.as_ref().unwrap_or(&"".to_owned()).trim().to_owned(); if let Some(current_container) = self.get_container_by_id(id) { if current_container.name != name { - current_container.name = name + current_container.name = name; }; if current_container.status != status { - current_container.status = status + current_container.status = status; }; if current_container.state != state { current_container.docker_controls.items = DockerControls::gen_vec(&state); @@ -471,18 +471,17 @@ impl AppData { // Update the list state, needs to be None if the gen_vec returns an empty vec match state { State::Removing | State::Restarting | State::Unknown => { - current_container.docker_controls.state.select(None) + current_container.docker_controls.state.select(None); } _ => current_container.docker_controls.start(), }; current_container.state = state; }; if current_container.image != image { - current_container.image = image + current_container.image = image; }; } else { - let mut container = - ContainerItem::new(id.to_owned(), status, image, state, name); + let mut container = ContainerItem::new(id.clone(), status, image, state, name); container.logs.end(); self.containers.items.push(container); } @@ -491,25 +490,25 @@ impl AppData { } /// update logs of a given container, based on id - pub fn update_log_by_id(&mut self, output: Vec, id: String) { - let tz = self.get_systemtime(); + pub fn update_log_by_id(&mut self, output: &[String], id: &str) { + let tz = Self::get_systemtime(); let color = self.args.color; let raw = self.args.raw; - if let Some(container) = self.get_container_by_id(&id) { + if let Some(container) = self.get_container_by_id(id) { container.last_updated = tz; let current_len = container.logs.items.len(); - output.iter().for_each(|i| { + for i in output.iter() { let lines = if color { - log_sanitizer::colorize_logs(i.to_owned()) + log_sanitizer::colorize_logs(i) } else if raw { - log_sanitizer::raw(i.to_owned()) + log_sanitizer::raw(i.clone()) } else { - log_sanitizer::remove_ansi(i.to_owned()) + log_sanitizer::remove_ansi(i) }; container.logs.items.push(ListItem::new(lines)); - }); + } if container.logs.state.selected().is_none() || container.logs.state.selected().unwrap_or_default() + 1 == current_len diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 4e09a9a..19019ba 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -102,11 +102,10 @@ impl DockerData { let mem_stat = stats.memory_stats.usage.unwrap_or(0); let mem_limit = stats.memory_stats.limit.unwrap_or(0); - let some_key = if let Some(networks) = &stats.networks { - networks.keys().next().map(|x| x.to_owned()) - } else { - None - }; + let some_key = stats + .networks + .as_ref() + .and_then(|networks| networks.keys().next().cloned()); let cpu_stats = Self::calculate_usage(&stats); @@ -122,7 +121,7 @@ impl DockerData { if is_running { app_data.lock().update_stats( - id.clone(), + &id, Some(cpu_stats), Some(mem_stat), mem_limit, @@ -132,10 +131,9 @@ impl DockerData { } else { app_data .lock() - .update_stats(id.clone(), None, None, mem_limit, rx, tx); + .update_stats(&id, None, None, mem_limit, rx, tx); } - let key = SpawnId::Stats(id.to_owned()); - spawns.lock().remove(&key); + spawns.lock().remove(&SpawnId::Stats(id.clone())); } } @@ -146,13 +144,13 @@ impl DockerData { let app_data = Arc::clone(&self.app_data); let spawns = Arc::clone(&self.spawns); let is_running = *is_running; - let id = id.to_owned(); + let id = id.clone(); - let key = SpawnId::Stats(id.to_owned()); + let key = SpawnId::Stats(id.clone()); let spawn_contains_id = spawns.lock().contains_key(&key); let s = tokio::spawn(Self::update_container_stat( docker, - id.to_owned(), + id.clone(), app_data, is_running, spawns, @@ -180,7 +178,7 @@ impl DockerData { containers .iter() .filter(|i| i.id.is_some()) - .for_each(|c| output.push(c.to_owned())); + .for_each(|c| output.push(c.clone())); self.app_data.lock().update_containers(&output); @@ -193,7 +191,7 @@ impl DockerData { i.id.as_ref().map(|id| { ( i.state.as_ref().unwrap_or(&String::new()) == "running", - id.to_owned(), + id.clone(), ) }) }) @@ -230,9 +228,8 @@ impl DockerData { } } } - let key = SpawnId::Log(id.to_owned()); - spawns.lock().remove(&key); - app_data.lock().update_log_by_id(output, id.to_owned()); + spawns.lock().remove(&SpawnId::Log(id.clone())); + app_data.lock().update_log_by_id(&output, &id); } /// Update all logs, spawn each container into own tokio::spawn thread @@ -240,10 +237,10 @@ impl DockerData { for (_, id) in all_ids.iter() { let docker = Arc::clone(&self.docker); let timestamps = self.timestamps; - let id = id.to_owned(); + let id = id.clone(); let app_data = Arc::clone(&self.app_data); let spawns = Arc::clone(&self.spawns); - let key = SpawnId::Log(id.to_owned()); + let key = SpawnId::Log(id.clone()); let s = tokio::spawn(Self::update_log( docker, id, timestamps, 0, app_data, spawns, )); @@ -256,9 +253,9 @@ impl DockerData { 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 { - let id = self.app_data.lock().containers.items[index].id.to_owned(); + let id = self.app_data.lock().containers.items[index].id.clone(); - let key = SpawnId::Log(id.to_owned()); + let key = SpawnId::Log(id.clone()); let running = self.spawns.lock().contains_key(&key); if !running { @@ -290,7 +287,7 @@ impl DockerData { } /// Stop the loading_spin function, and reset gui loading status - fn stop_loading_spin(&mut self, handle: JoinHandle<()>) { + fn stop_loading_spin(&mut self, handle: &JoinHandle<()>) { handle.abort(); self.gui_state.lock().reset_loading(); } @@ -315,7 +312,7 @@ impl DockerData { self.initialised = self.app_data.lock().initialised(&all_ids); } self.app_data.lock().init = true; - self.stop_loading_spin(loading_spin); + self.stop_loading_spin(&loading_spin); } /// Handle incoming messages, container controls & all container information update @@ -329,9 +326,9 @@ impl DockerData { docker.pause_container(&id).await.unwrap_or_else(|_| { app_data .lock() - .set_error(AppError::DockerCommand(DockerControls::Pause)) + .set_error(AppError::DockerCommand(DockerControls::Pause)); }); - self.stop_loading_spin(loading_spin); + self.stop_loading_spin(&loading_spin); } DockerMessage::Restart(id) => { let loading_spin = self.loading_spin().await; @@ -341,9 +338,9 @@ impl DockerData { .unwrap_or_else(|_| { app_data .lock() - .set_error(AppError::DockerCommand(DockerControls::Restart)) + .set_error(AppError::DockerCommand(DockerControls::Restart)); }); - self.stop_loading_spin(loading_spin); + self.stop_loading_spin(&loading_spin); } DockerMessage::Start(id) => { let loading_spin = self.loading_spin().await; @@ -353,28 +350,28 @@ impl DockerData { .unwrap_or_else(|_| { app_data .lock() - .set_error(AppError::DockerCommand(DockerControls::Start)) + .set_error(AppError::DockerCommand(DockerControls::Start)); }); - self.stop_loading_spin(loading_spin); + self.stop_loading_spin(&loading_spin); } DockerMessage::Stop(id) => { let loading_spin = self.loading_spin().await; docker.stop_container(&id, None).await.unwrap_or_else(|_| { app_data .lock() - .set_error(AppError::DockerCommand(DockerControls::Stop)) + .set_error(AppError::DockerCommand(DockerControls::Stop)); }); - self.stop_loading_spin(loading_spin); + self.stop_loading_spin(&loading_spin); } DockerMessage::Unpause(id) => { let loading_spin = self.loading_spin().await; docker.unpause_container(&id).await.unwrap_or_else(|_| { app_data .lock() - .set_error(AppError::DockerCommand(DockerControls::Unpause)) + .set_error(AppError::DockerCommand(DockerControls::Unpause)); }); - self.stop_loading_spin(loading_spin); - self.update_everything().await + self.stop_loading_spin(&loading_spin); + self.update_everything().await; } DockerMessage::Update => self.update_everything().await, DockerMessage::Quit => { @@ -382,7 +379,7 @@ impl DockerData { .lock() .values() .into_iter() - .for_each(|i| i.abort()); + .for_each(tokio::task::JoinHandle::abort); self.is_running.store(false, Ordering::SeqCst); } } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 9bbdbcc..97d49ee 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -110,7 +110,7 @@ impl InputHandler { // Show the info box - with "mouse capture enabled / disabled", for 4000 ms self.info_sleep = Some(tokio::spawn(async move { tokio::time::sleep(std::time::Duration::from_millis(4000)).await; - gui_state.lock().reset_info_box() + gui_state.lock().reset_info_box(); })); self.mouse_capture = !self.mouse_capture; @@ -118,14 +118,14 @@ impl InputHandler { /// Sort containers based on a given header, switch asc to desc if already sorted, else always desc fn sort(&self, header: Header) { - let mut output = Some((header.to_owned(), SortedOrder::Desc)); + let mut output = Some((header.clone(), SortedOrder::Desc)); let mut locked_data = self.app_data.lock(); if let Some((h, order)) = locked_data.get_sorted().as_ref() { if &SortedOrder::Desc == order && h == &header { - output = Some((header, SortedOrder::Asc)) + output = Some((header, SortedOrder::Asc)); } } - locked_data.set_sorted(output) + locked_data.set_sorted(output); } /// Send a quit message to docker, to abort all spawns, if error, quit here instead @@ -219,13 +219,13 @@ impl InputHandler { KeyCode::Up | KeyCode::Char('k') => self.previous(), KeyCode::PageUp => { for _ in 0..=6 { - self.previous() + self.previous(); } } KeyCode::Down | KeyCode::Char('j') => self.next(), KeyCode::PageDown => { for _ in 0..=6 { - self.next() + self.next(); } } KeyCode::Enter => { diff --git a/src/main.rs b/src/main.rs index 30c2141..b7fda0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,13 @@ +#![forbid(unsafe_code)] +#![warn(clippy::unused_async, clippy::unwrap_used, clippy::expect_used)] +// Wanring - These are indeed pedantic +// #![warn(clippy::pedantic)] +// #![warn(clippy::nursery)] +// #![allow(clippy::module_name_repetitions, clippy::doc_markdown)] + +// Only allow when debugging +// #![allow(unused)] + use app_data::AppData; use app_error::AppError; use bollard::Docker; @@ -35,23 +45,28 @@ async fn main() { let (docker_sx, docker_rx) = tokio::sync::mpsc::channel(16); // Create docker daemon handler, and only spawn up the docker data handler if ping returns non-error - let docker = Arc::new(Docker::connect_with_socket_defaults().unwrap()); - match docker.ping().await { - Ok(_) => { - let docker = Arc::clone(&docker); - let is_running = Arc::clone(&is_running); - tokio::spawn(DockerData::init( - docker_args, - docker_app_data, - docker, - docker_gui_state, - docker_rx, - is_running, - )); + + match Docker::connect_with_socket_defaults() { + Ok(docker) => { + let docker = Arc::new(docker); + match docker.ping().await { + Ok(_) => { + let docker = Arc::clone(&docker); + let is_running = Arc::clone(&is_running); + tokio::spawn(DockerData::init( + docker_args, + docker_app_data, + docker, + docker_gui_state, + docker_rx, + is_running, + )); + } + Err(_) => app_data.lock().set_error(AppError::DockerConnect), + } } Err(_) => app_data.lock().set_error(AppError::DockerConnect), } - let input_app_data = Arc::clone(&app_data); let (input_sx, input_rx) = tokio::sync::mpsc::channel(16); @@ -70,13 +85,8 @@ async fn main() { )); // Debug mode for testing, mostly pointless, doesn't take terminal nor draw gui - if !args.gui { - loop { - info!("in debug mode"); - tokio::time::sleep(std::time::Duration::from_millis(5000)).await; - } - } else { - let update_duration = std::time::Duration::from_millis(args.docker_interval as u64); + if args.gui { + let update_duration = std::time::Duration::from_millis(u64::from(args.docker_interval)); create_ui( app_data, input_sx, @@ -86,6 +96,11 @@ async fn main() { update_duration, ) .await - .unwrap_or(()) + .unwrap_or(()); + } else { + loop { + info!("in debug mode"); + tokio::time::sleep(std::time::Duration::from_millis(5000)).await; + } } } diff --git a/src/parse_args/mod.rs b/src/parse_args/mod.rs index d76f88a..a115aed 100644 --- a/src/parse_args/mod.rs +++ b/src/parse_args/mod.rs @@ -31,7 +31,7 @@ pub struct CliArgs { impl CliArgs { /// Parse cli arguments pub fn new() -> Self { - let args = CliArgs::parse(); + let args = Self::parse(); // Quit the program if the docker update argument is 0 // Should maybe change it to check if less than 100 diff --git a/src/ui/color_match.rs b/src/ui/color_match.rs index 14543b6..149d74a 100644 --- a/src/ui/color_match.rs +++ b/src/ui/color_match.rs @@ -1,36 +1,36 @@ pub mod log_sanitizer { - use cansi::{categorise_text, Color as CansiColor, Intensity}; + use cansi::{v3::categorise_text, Color as CansiColor, Intensity}; use tui::{ style::{Color, Modifier, Style}, text::{Span, Spans}, }; /// Attempt to colorize the given string to tui-rs standars - pub fn colorize_logs(input: String) -> Vec> { + pub fn colorize_logs(input: &str) -> Vec> { vec![Spans::from( - categorise_text(&input) + categorise_text(input) .into_iter() .map(|i| { - let fg_color = color_ansi_to_tui(i.fg_colour); - let bg_color = color_ansi_to_tui(i.bg_colour); + let fg_color = color_ansi_to_tui(i.fg.unwrap_or(CansiColor::White)); + let bg_color = color_ansi_to_tui(i.bg.unwrap_or(CansiColor::Black)); let style = Style::default().bg(bg_color).fg(fg_color); - if i.blink { + if i.blink.is_some() { style.add_modifier(Modifier::SLOW_BLINK); } - if i.underline { + if i.underline.is_some() { style.add_modifier(Modifier::UNDERLINED); } - if i.reversed { + if i.reversed.is_some() { style.add_modifier(Modifier::REVERSED); } - if i.intensity == Intensity::Bold { + if i.intensity == Some(Intensity::Bold) { style.add_modifier(Modifier::BOLD); } - if i.hidden { + if i.hidden.is_some() { style.add_modifier(Modifier::HIDDEN); } - if i.strikethrough { + if i.strikethrough.is_some() { style.add_modifier(Modifier::CROSSED_OUT); } Span::styled(i.text.to_owned(), style) @@ -40,10 +40,10 @@ pub mod log_sanitizer { } /// Remove all ansi formatting from a given string and create tui-rs spans - pub fn remove_ansi(input: String) -> Vec> { + pub fn remove_ansi(input: &str) -> Vec> { let mut output = String::from(""); - for i in categorise_text(&input) { - output.push_str(i.text) + for i in categorise_text(input) { + output.push_str(i.text); } raw(output) } @@ -54,24 +54,22 @@ pub mod log_sanitizer { } /// Change from ansi to tui colors - fn color_ansi_to_tui(color: CansiColor) -> Color { + const fn color_ansi_to_tui(color: CansiColor) -> Color { match color { - CansiColor::Black => Color::Black, + CansiColor::Black | CansiColor::BrightBlack => Color::Black, CansiColor::Red => Color::Red, CansiColor::Green => Color::Green, CansiColor::Yellow => Color::Yellow, CansiColor::Blue => Color::Blue, CansiColor::Magenta => Color::Magenta, CansiColor::Cyan => Color::Cyan, - CansiColor::White => Color::White, - CansiColor::BrightBlack => Color::Black, + CansiColor::White | CansiColor::BrightWhite => Color::White, CansiColor::BrightRed => Color::LightRed, CansiColor::BrightGreen => Color::LightGreen, CansiColor::BrightYellow => Color::LightYellow, CansiColor::BrightBlue => Color::LightBlue, CansiColor::BrightMagenta => Color::LightMagenta, CansiColor::BrightCyan => Color::LightCyan, - CansiColor::BrightWhite => Color::White, } } } diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index 3e4b86c..30d594a 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -64,7 +64,7 @@ fn generate_block<'a>( SelectablePanel::Logs => { format!(" {} {} ", panel.title(), app_data.lock().get_log_title()) } - _ => String::from(""), + SelectablePanel::Commands => String::from(""), }; block = block.title(title); if current_selected_panel == panel { @@ -74,7 +74,7 @@ fn generate_block<'a>( } /// Draw the command panel -pub fn draw_commands( +pub fn commands( app_data: &Arc>, area: Rect, f: &mut Frame<'_, B>, @@ -111,12 +111,12 @@ pub fn draw_commands( let paragraph = Paragraph::new(debug_text) .block(block) .alignment(Alignment::Center); - f.render_widget(paragraph, area) + f.render_widget(paragraph, area); } } /// Draw the containers panel -pub fn draw_containers( +pub fn containers( app_data: &Arc>, area: Rect, f: &mut Frame<'_, B>, @@ -196,7 +196,7 @@ pub fn draw_containers( let paragraph = Paragraph::new(debug_text) .block(block) .alignment(Alignment::Center); - f.render_widget(paragraph, area) + f.render_widget(paragraph, area); } else { let items = List::new(items) .block(block) @@ -208,13 +208,13 @@ pub fn draw_containers( } /// Draw the logs panel -pub fn draw_logs( +pub fn logs( app_data: &Arc>, area: Rect, f: &mut Frame<'_, B>, gui_state: &Arc>, index: Option, - loading_icon: String, + loading_icon: &str, ) { let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs); @@ -225,14 +225,14 @@ pub fn draw_logs( .style(Style::default()) .block(block) .alignment(Alignment::Center); - f.render_widget(paragraph, area) + f.render_widget(paragraph, area); } else if let Some(index) = index { let items = app_data.lock().containers.items[index] .logs .items .iter() .enumerate() - .map(|i| i.1.to_owned()) + .map(|i| i.1.clone()) .collect::>(); let items = List::new(items) @@ -249,12 +249,12 @@ pub fn draw_logs( let paragraph = Paragraph::new(debug_text) .block(block) .alignment(Alignment::Center); - f.render_widget(paragraph, area) + f.render_widget(paragraph, area); } } /// Draw the cpu + mem charts -pub fn draw_chart( +pub fn chart( f: &mut Frame<'_, B>, area: Rect, app_data: &Arc>, @@ -279,20 +279,10 @@ pub fn draw_chart( .style(Style::default().fg(Color::Cyan)) .graph_type(GraphType::Line) .data(&mem.0)]; - let cpu_chart = make_chart( - cpu.2, - String::from("cpu"), - cpu_dataset, - CpuStats::new(cpu.0.last().unwrap_or(&(0.00, 0.00)).1), - cpu.1, - ); - let mem_chart = make_chart( - mem.2, - String::from("memory"), - mem_dataset, - ByteStats::new(mem.0.last().unwrap_or(&(0.0, 0.0)).1 as u64), - mem.1, - ); + let cpu_stats = CpuStats::new(cpu.0.last().unwrap_or(&(0.00, 0.00)).1); + let mem_stats = ByteStats::new(mem.0.last().unwrap_or(&(0.0, 0.0)).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]); @@ -301,13 +291,13 @@ pub fn draw_chart( } /// Create charts -fn make_chart( - state: State, - name: String, - dataset: Vec, - current: T, - max: T, -) -> Chart { +fn make_chart<'a, T: Stats + Display>( + state: &State, + name: &'a str, + dataset: Vec>, + current: &'a T, + max: &'a T, +) -> Chart<'a> { let title_color = match state { State::Running => Color::Green, _ => state.get_color(), @@ -351,13 +341,13 @@ fn make_chart( } /// Draw heading bar at top of program, always visible -pub fn draw_heading_bar( +pub fn heading_bar( area: Rect, columns: &Columns, f: &mut Frame<'_, B>, has_containers: bool, - loading_icon: String, - sorted_by: Option<(Header, SortedOrder)>, + loading_icon: &str, + sorted_by: &Option<(Header, SortedOrder)>, gui_state: &Arc>, ) { let block = || Block::default().style(Style::default().bg(Color::Magenta).fg(Color::Black)); @@ -377,7 +367,7 @@ pub fn draw_heading_bar( SortedOrder::Desc => suffix = " ⌄", } suffix_margin = 2; - color = Color::White + color = Color::White; }; }; ( @@ -439,11 +429,7 @@ pub fn draw_heading_bar( .iter() .map(|i| { let header_block = gen_header(&i.0, i.1); - ( - header_block.0, - i.0.to_owned(), - Constraint::Max(header_block.1), - ) + (header_block.0, i.0.clone(), Constraint::Max(header_block.1)) }) .collect::>(); @@ -500,7 +486,7 @@ fn max_line_width(text: &str) -> usize { } /// Draw the help box in the centre of the screen -pub fn draw_help_box(f: &mut Frame<'_, B>) { +pub fn help_box(f: &mut Frame<'_, B>) { let title = format!(" {} ", VERSION); let description_text = format!("\n{}", DESCRIPTION); @@ -550,7 +536,7 @@ pub fn draw_help_box(f: &mut Frame<'_, B>) { .border_type(BorderType::Rounded) .border_style(Style::default().fg(Color::Black)); - let area = draw_popup( + let area = popup( lines as u16, max_line_width as u16, f.size(), @@ -578,7 +564,7 @@ pub fn draw_help_box(f: &mut Frame<'_, B>) { } /// Draw an error popup over whole screen -pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Option) { +pub fn error(f: &mut Frame<'_, B>, error: &AppError, seconds: Option) { let block = Block::default() .title(" Error ") .border_type(BorderType::Rounded) @@ -614,7 +600,7 @@ pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Op .block(block) .alignment(Alignment::Center); - let area = draw_popup( + let area = popup( lines as u16, max_line_width as u16, f.size(), @@ -625,7 +611,7 @@ pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Op } /// Draw info box in one of the 9 BoxLocations -pub fn draw_info(f: &mut Frame<'_, B>, text: String) { +pub fn info(f: &mut Frame<'_, B>, text: String) { let block = Block::default() .title("") .title_alignment(Alignment::Center) @@ -643,7 +629,7 @@ pub fn draw_info(f: &mut Frame<'_, B>, text: String) { .block(block) .alignment(Alignment::Center); - let area = draw_popup( + let area = popup( lines as u16, max_line_width as u16, f.size(), @@ -654,7 +640,7 @@ pub fn draw_info(f: &mut Frame<'_, B>, text: String) { } /// draw a box in the one of the BoxLocations, based on max line width + number of lines -fn draw_popup(text_lines: u16, text_width: u16, r: Rect, box_location: BoxLocation) -> Rect { +fn popup(text_lines: u16, text_width: u16, r: Rect, box_location: BoxLocation) -> Rect { // Make sure blank_space can't be an negative, as will crash let blank_vertical = if r.height > text_lines { (r.height - text_lines) / 2 diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index 34add3e..5711148 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -30,7 +30,7 @@ pub enum BoxLocation { } impl BoxLocation { - pub fn get_indexes(&self) -> (usize, usize) { + pub const fn get_indexes(self) -> (usize, usize) { match self { Self::TopLeft => (0, 0), Self::TopCentre => (0, 1), @@ -45,8 +45,8 @@ impl BoxLocation { } // Should combine and just return a tuple? - pub fn get_horizontal_constraints( - &self, + pub const fn get_horizontal_constraints( + self, blank_vertical: u16, text_width: u16, ) -> [Constraint; 3] { @@ -68,8 +68,8 @@ impl BoxLocation { ], } } - pub fn get_vertical_constraints( - &self, + pub const fn get_vertical_constraints( + self, blank_vertical: u16, number_lines: u16, ) -> [Constraint; 3] { @@ -108,7 +108,7 @@ pub enum Loading { } impl Loading { - pub fn next(&self) -> Self { + pub const fn next(&self) -> Self { match self { Self::One => Self::Two, Self::Two => Self::Three, @@ -143,21 +143,21 @@ impl fmt::Display for Loading { } impl SelectablePanel { - pub fn title(self) -> &'static str { + pub const fn title(self) -> &'static str { match self { Self::Containers => "Containers", Self::Logs => "Logs", - _ => "", + Self::Commands => "", } } - pub fn next(self) -> Self { + pub const fn next(self) -> Self { match self { Self::Containers => Self::Commands, Self::Commands => Self::Logs, Self::Logs => Self::Containers, } } - pub fn prev(self) -> Self { + pub const fn prev(self) -> Self { match self { Self::Containers => Self::Logs, Self::Commands => Self::Containers, @@ -221,7 +221,7 @@ impl GuiState { .filter(|i| i.1.intersects(rect)) .collect::>() .get(0) - .map(|data| data.0.to_owned()) + .map(|data| data.0.clone()) } /// Insert, or updatem header area panel into heading_map diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 8b7504a..9a529fd 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -30,7 +30,6 @@ use crate::{ app_data::AppData, app_error::AppError, docker_data::DockerMessage, input_handler::InputMessages, }; -use draw_blocks::*; /// Take control of the terminal in order to draw gui pub async fn create_ui( @@ -73,7 +72,7 @@ pub async fn create_ui( } /// Run a loop to draw the gui -async fn run_app( +async fn run_app( terminal: &mut Terminal, app_data: Arc>, sender: Sender, @@ -94,9 +93,12 @@ async fn run_app( is_running.store(false, Ordering::SeqCst); break; } - terminal - .draw(|f| draw_error(f, AppError::DockerConnect, Some(seconds))) - .unwrap(); + if terminal + .draw(|f| draw_blocks::error(f, &AppError::DockerConnect, Some(seconds))) + .is_err() + { + return Err(AppError::Terminal); + } tokio::time::sleep(std::time::Duration::from_secs(1)).await; seconds -= 1; } @@ -104,7 +106,9 @@ async fn run_app( } else { let mut now = Instant::now(); loop { - terminal.draw(|f| ui(f, &app_data, &gui_state)).unwrap(); + if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() { + return Err(AppError::Terminal); + } if crossterm::event::poll(input_poll_rate).unwrap_or_default() { if let Ok(event) = event::read() { if let Event::Key(key) = event { @@ -143,12 +147,8 @@ fn ui( gui_state: &Arc>, ) { // set max height for container section, needs +4 to deal with docker commands list and borders - let mut height = app_data.lock().get_container_len(); - if height < 12 { - height += 4; - } else { - height = 12 - } + let height = app_data.lock().get_container_len(); + let height = if height < 12 { (height + 4) as u16 } else { 12 }; let column_widths = app_data.lock().get_width(); let has_containers = !app_data.lock().containers.items.is_empty(); @@ -194,47 +194,47 @@ fn ui( .constraints(lower_split.as_ref()) .split(upper_main[1]); - draw_containers(app_data, top_panel[0], f, gui_state, &column_widths); + draw_blocks::containers(app_data, top_panel[0], f, gui_state, &column_widths); if has_containers { - draw_commands(app_data, top_panel[1], f, gui_state, log_index); + draw_blocks::commands(app_data, top_panel[1], f, gui_state, log_index); } - draw_logs( + draw_blocks::logs( app_data, lower_main[0], f, gui_state, log_index, - loading_icon.to_owned(), + &loading_icon, ); - draw_heading_bar( + draw_blocks::heading_bar( whole_layout[0], &column_widths, f, has_containers, - loading_icon, - sorted_by, + &loading_icon, + &sorted_by, gui_state, ); // only draw charts if there are containers if has_containers { - draw_chart(f, lower_main[1], app_data, log_index); + draw_blocks::chart(f, lower_main[1], app_data, log_index); } if let Some(info) = info_text { - draw_info(f, info); + draw_blocks::info(f, info); } // Check if error, and show popup if so if show_help { - draw_help_box(f); + draw_blocks::help_box(f); } if let Some(error) = has_error { app_data.lock().show_error = true; - draw_error(f, error, None); + draw_blocks::error(f, &error, None); } }