diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ed5301e..41a7ebe 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,14 +13,11 @@ "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", - "seccomp=unconfined", + "seccomp=unconfined" ], "mounts": [ - // //"source=/etc/timezone,target=/etc/timezone,type=bind,readonly", - "source=/dev/shm,target=/ramdrive,type=bind", - "source=${localEnv:HOME}/.cargo/bin/cargo-watch,target=/usr/local/cargo/bin/cargo-watch,type=bind,readonly", - "source=${localEnv:HOME}/.cargo/bin/cross,target=/usr/local/cargo/bin/cross,type=bind,readonly", + "source=/etc/timezone,target=/etc/timezone,type=bind,readonly" ], // Set *default* container specific settings.json values on container create. diff --git a/.github/release-body.md b/.github/release-body.md index 77dcf87..bf70699 100644 --- a/.github/release-body.md +++ b/.github/release-body.md @@ -1,13 +1,19 @@ -### 2022-05-30 +### 2022-07-06 ### Docs -+ Readme one-liner to download & install latest version, [11d5ba361ee4c11d080f1c3c14d8bb677cbfd1fc] -+ Example docker-compose.yml bump alpine version to 3.16, [98c83f2f68f59e78f0c78270c59886630d98913c] ++ readme update, [f29e29ad151ddf424ba630e6d33edf19acfd7636] ++ comments improved, [1674db8a20aafa447732deb2e44ac8b97cf0471b] ++ readme logo size, [a733efa65865e04d9ec86c7ca8785dfbae635695] ### Fixes -+ use Some() checks to make sure that container item indexes are still valid, else can create out-of-bounds errors, closes [#8], [4cf02e3f04426ef44ec5a7421687f2104ac5102f] -+ Remove + replace as many unwrap()'s as possible, [d8e22d7444965f1874d7367259310440a889432b] -+ Help panel typo, [e497f3f2d9e1dca99469860c2e728c99e29353ad] ++ Remove unwraps(), [61db81ecfe5684ddb8a360715f43357a042162c0] ++ Help menu alt+tab > shift+tab typo, thanks [siph](https://github.com/siph), [04466803481b75feb7d7f275248279fdb8729862] + +### Refactors ++ tokio spawns, [1fd230f2f3cf4e376058359515e76f4fa6e425c2] ++ max_line_width(), [a5d7dabbd68dc15a081df33352ce3b55d9a9891c] ++ create_release dead code removed, [297979c197c2defd409053d8da724f922b0bba1b] + see CHANGELOG.md for more details diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b9ffd..c90a51e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# v0.0.6 +### 2022-07-06 + +### Docs ++ readme update, [f29e29ad](https://github.com/mrjackwills/oxker/commit/f29e29ad151ddf424ba630e6d33edf19acfd7636), ++ comments improved, [1674db8a](https://github.com/mrjackwills/oxker/commit/1674db8a20aafa447732deb2e44ac8b97cf0471b), ++ readme logo size, [a733efa6](https://github.com/mrjackwills/oxker/commit/a733efa65865e04d9ec86c7ca8785dfbae635695), + +### Fixes ++ Remove unwraps(), [61db81ec](https://github.com/mrjackwills/oxker/commit/61db81ecfe5684ddb8a360715f43357a042162c0), ++ Help menu alt+tab > shift+tab typo, thanks [siph](https://github.com/siph), [04466803](https://github.com/mrjackwills/oxker/commit/04466803481b75feb7d7f275248279fdb8729862), + +### Refactors ++ tokio spawns, [1fd230f2](https://github.com/mrjackwills/oxker/commit/1fd230f2f3cf4e376058359515e76f4fa6e425c2), ++ max_line_width(), [a5d7dabb](https://github.com/mrjackwills/oxker/commit/a5d7dabbd68dc15a081df33352ce3b55d9a9891c), ++ create_release dead code removed, [297979c1](https://github.com/mrjackwills/oxker/commit/297979c197c2defd409053d8da724f922b0bba1b), + + # v0.0.5 ### 2022-05-30 diff --git a/Cargo.toml b/Cargo.toml index 66c0e4f..0f64201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxker" -version = "0.0.5" +version = "0.0.6" edition = "2021" authors = ["Jack Wills "] description = "a simple tui to view & control docker containers" @@ -11,14 +11,14 @@ readme = "README.md" [dependencies] anyhow = "1.0" bollard = "0.12.0" -cansi = "2.1.1" -clap={version="3.1.0", features = ["derive", "unicode"] } -crossterm = "0.23.2" -futures-util = "0.3.21" -parking_lot = {version= "0.12.0"} -tokio = {version = "1.17.0", features=["full"]} -tracing = "0.1.32" -tracing-subscriber = "0.3.9" +cansi = "2.1" +clap={version="3.1", features = ["derive", "unicode"] } +crossterm = "0.23" +futures-util = "0.3" +parking_lot = {version= "0.12"} +tokio = {version = "1.19", features=["full"]} +tracing = "0.1" +tracing-subscriber = "0.3" tui = "0.18" [dev-dependencies] diff --git a/LICENSE b/LICENSE index 461de66..b04fba6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Jack Wills +Copyright (c) 2022- Jack Wills Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8d8db64..c4e7f8e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

@@ -92,7 +92,7 @@ or individually ```docker run --name redis -d redis:alpine3.16``` -```docker run --name postgres -e POSTGRES_PASSWORD=never_use_this_password_in_production -d postgres:alpine``` +```docker run --name postgres -e POSTGRES_PASSWORD=never_use_this_password_in_production -d postgres:alpine3.16``` ```docker run -d --hostname my-rabbit --name rabbitmq rabbitmq:3``` diff --git a/create_release.sh b/create_release.sh index 7b451ee..db8ccf8 100755 --- a/create_release.sh +++ b/create_release.sh @@ -205,16 +205,16 @@ release_flow() { git push --atomic origin main "$NEW_TAG_WITH_V" git checkout dev git merge --no-ff main -m 'chore: merge main into dev' + git push origin dev git branch -d "$RELEASE_BRANCH" } main() { - cmd=(dialog --backtitle "Choose build option" --radiolist "choose" 14 80 16) + cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16) options=( - 1 "build" off - 2 "test" off - 3 "release" off + 1 "test" off + 2 "release" off ) choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) exitStatus=$? @@ -229,14 +229,10 @@ main() { exit break;; 1) - cargo_build_all - main - break;; - 2) cargo_test main break;; - 3) + 2) release_flow break;; esac diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 7f9bfdd..c8b241a 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -134,8 +134,6 @@ impl fmt::Display for State { } /// Items for the container control list -/// Should probably have a vec for each container -/// so that can remove Pause if container currently Paused etc #[derive(Debug, Clone)] pub enum DockerControls { Pause, @@ -411,6 +409,7 @@ pub struct Columns { } impl Columns { + //. (Column titles, minimum header string length) pub fn new() -> Self { Self { state: (String::from("state"), 11), diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index e39a7ec..5cec71a 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -99,7 +99,7 @@ impl AppData { self.error = Some(error); } - /// Find the if of the currently selected container. + /// Find the id of the currently selected container. /// If any containers on system, will always return a string. /// Only returns None when no containers found. pub fn get_selected_container_id(&self) -> Option { @@ -183,7 +183,7 @@ impl AppData { self.containers.items.len() } - /// Find the widths for the strings in the containers panel + /// 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(); @@ -228,11 +228,9 @@ 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 net_tx_count > output.net_tx.1 { output.net_tx.1 = net_tx_count; }; @@ -254,7 +252,7 @@ impl AppData { self.containers.items.iter_mut().find(|i| i.id == id) } - /// Update container mem + cpu stats, in single function so only need to call .lock() once + /// Update container mem, cpu, & network stats, in single function so only need to call .lock() once pub fn update_stats( &mut self, id: String, @@ -318,7 +316,7 @@ impl AppData { .as_ref() .unwrap_or(&vec!["".to_owned()]) .get(0) - .unwrap() + .unwrap_or(&String::from("")) .to_owned(); if let Some(c) = name.chars().next() { if c == '/' { @@ -391,6 +389,7 @@ impl AppData { self.logs_parsed = true; } + /// Update all containers logs, should only be used on first initialisation pub fn update_all_logs(&mut self, all_logs: Vec>) { for (index, output) in all_logs.into_iter().enumerate() { self.update_log_by_index(output, index); diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 0074d1e..0776794 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -31,12 +31,12 @@ impl DockerData { let mut cpu_percentage = 0.0; let previous_cpu = stats.precpu_stats.cpu_usage.total_usage; let cpu_delta = stats.cpu_stats.cpu_usage.total_usage as f64 - previous_cpu as f64; - if stats.cpu_stats.system_cpu_usage.is_some() - && stats.precpu_stats.system_cpu_usage.is_some() - { - let system_delta = (stats.cpu_stats.system_cpu_usage.unwrap_or(0) - - stats.precpu_stats.system_cpu_usage.unwrap_or(0)) - as f64; + + if let (Some(cpu_stats_usage), Some(precpu_stats_usage)) = ( + stats.cpu_stats.system_cpu_usage, + stats.precpu_stats.system_cpu_usage, + ) { + let system_delta = (cpu_stats_usage - precpu_stats_usage) as f64; let online_cpus = stats.cpu_stats.online_cpus.unwrap_or_else(|| { stats .cpu_stats @@ -54,7 +54,7 @@ impl DockerData { } /// Get a single docker stat in order to update mem and cpu usage - /// don't take &self, so that can tokio::spawn into it's on thread + /// don't take &self, so that can tokio::spawn into it's own thread async fn update_container_stat( docker: Arc, id: String, @@ -117,14 +117,15 @@ impl DockerData { let app_data = Arc::clone(&self.app_data); let is_running = *is_running; let id = id.to_owned(); - tokio::spawn(async move { - Self::update_container_stat(docker, id, app_data, is_running).await - }); + tokio::spawn(Self::update_container_stat( + docker, id, app_data, is_running, + )); } } /// Get all current containers, handle into ContainerItem in the app_data struct rather than here /// Just make sure that items sent are guaranteed to have an id + /// return Vec<(is_running, id)> pub async fn update_all_containers(&mut self) -> Vec<(bool, String)> { let containers = self .docker @@ -136,11 +137,11 @@ impl DockerData { .unwrap_or_default(); let mut output = vec![]; - // iter over containers, to only send ones which have an id, as use ID for extensivley! - // alternative is to create my own container struct, and will out with details - containers.iter().filter(|i| i.id.is_some()).for_each(|c| { - output.push(c.to_owned()); - }); + // iter over containers, to only send ones which have an id, as use id for identification throughout! + containers + .iter() + .filter(|i| i.id.is_some()) + .for_each(|c| output.push(c.to_owned())); self.app_data.lock().update_containers(&output); output @@ -157,7 +158,7 @@ impl DockerData { } /// Update single container logs - /// don't take &self, so that can tokio::spawn into it's on thread + /// don't take &self, so that can tokio::spawn into it's own thread async fn update_log( docker: Arc, id: String, @@ -202,12 +203,12 @@ impl DockerData { async fn update_everything(&mut self) { let all_ids = self.update_all_containers().await; - let op_index = self.app_data.lock().get_selected_log_index(); - if let Some(index) = op_index { - let docker = Arc::clone(&self.docker); - let since = self.app_data.lock().containers.items[index].last_updated as i64; - let timestamps = self.timestamps; + 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 since = self.app_data.lock().containers.items[index].last_updated as i64; + let docker = Arc::clone(&self.docker); + let timestamps = self.timestamps; let logs = Self::update_log(docker, id, timestamps, since).await; self.app_data.lock().update_log_by_index(logs, index); }; @@ -226,7 +227,7 @@ impl DockerData { }) } - /// Stop the loading_spin fn, and reset gui loading status + /// Stop the loading_spin function, and reset gui loading status fn stop_loading_spin(&mut self, handle: JoinHandle<()>) { handle.abort(); self.gui_state.lock().reset_loading(); diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index d8b863e..ec613f0 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -175,8 +175,6 @@ impl InputHandler { } KeyCode::Enter => { // This isn't great, just means you can't send docker commands before full initialization of the program - // could change to to if loading = true, although at the moment don't have a loading bool - // Does is matter though? let panel = self.gui_state.lock().selected_panel; if panel == SelectablePanel::Commands { let option_command = self.app_data.lock().get_docker_command(); diff --git a/src/main.rs b/src/main.rs index e94064a..8a5e9df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,21 +32,19 @@ async fn main() { let docker_gui_state = Arc::clone(&gui_state); 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); - tokio::spawn(async move { - DockerData::init( - docker_args, - docker_app_data, - docker, - docker_gui_state, - docker_rx, - ) - .await; - }); + tokio::spawn(DockerData::init( + docker_args, + docker_app_data, + docker, + docker_gui_state, + docker_rx, + )); } Err(_) => app_data.lock().set_error(AppError::DockerConnect), } @@ -55,23 +53,19 @@ async fn main() { let (input_sx, input_rx) = tokio::sync::mpsc::channel(16); - // let input_docker = Arc::clone(&docker); let is_running = Arc::new(AtomicBool::new(true)); let input_is_running = Arc::clone(&is_running); let input_gui_state = Arc::clone(&gui_state); let input_docker_sender = docker_sx.clone(); // Spawn input handling into own tokio thread - tokio::spawn(async { - input_handler::InputHandler::init( - input_app_data, - input_rx, - input_docker_sender, - input_gui_state, - input_is_running, - ) - .await; - }); + tokio::spawn(input_handler::InputHandler::init( + input_app_data, + input_rx, + input_docker_sender, + input_gui_state, + input_is_running, + )); // Debug mode for testing, mostly pointless, doesn't take terminal nor draw gui if !args.gui { diff --git a/src/parse_args/mod.rs b/src/parse_args/mod.rs index aa5fb90..d76f88a 100644 --- a/src/parse_args/mod.rs +++ b/src/parse_args/mod.rs @@ -44,7 +44,6 @@ impl CliArgs { docker_interval: args.docker_interval, gui: !args.gui, raw: args.raw, - // install: args.install, timestamp: !args.timestamp, } } diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index 38d34b2..4a2135d 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -72,7 +72,7 @@ fn generate_block<'a>( block } -/// Draw the selectable panels +/// Draw the command panel pub fn draw_commands( app_data: &Arc>, area: Rect, @@ -114,7 +114,7 @@ pub fn draw_commands( } } -/// Draw the selectable panels +/// Draw the containers panel pub fn draw_containers( app_data: &Arc>, area: Rect, @@ -207,7 +207,7 @@ pub fn draw_containers( } } -/// Draw the logs panels +/// Draw the logs panel pub fn draw_logs( app_data: &Arc>, area: Rect, @@ -269,13 +269,11 @@ pub fn draw_chart( // 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)) @@ -347,11 +345,12 @@ fn make_chart( .fg(label_color), ), ]) + // Add 0.01, so that max point is always visible? .bounds([0.0, max.get_value() + 0.01]), ) } -/// Show error popup over whole screen +/// Draw heading bar at top of program, always visible pub fn draw_heading_bar( area: Rect, columns: &Columns, @@ -457,13 +456,25 @@ pub fn draw_heading_bar( f.render_widget(paragraph, split_bar[index]); } -/// Show error popup over whole screen +/// From a given &String, return the maximum number of chars on a single line +fn max_line_width(text: &str) -> usize { + let mut max_line_width = 0; + text.lines().into_iter().for_each(|line| { + let width = line.chars().count(); + if width > max_line_width { + max_line_width = width; + } + }); + max_line_width +} + +/// Draw the help box in the centre of the screen pub fn draw_help_box(f: &mut Frame<'_, B>) { let title = format!(" {} ", VERSION); let description_text = format!("\n{}", DESCRIPTION); - let mut help_text = String::from("\n ( tab ) or ( alt+tab ) to change panels"); + let mut help_text = String::from("\n ( tab ) or ( shift+tab ) to change panels"); help_text .push_str("\n ( ↑ ↓ ) or ( j k ) or (PgUp PgDown) or (Home End) to change selected line"); help_text.push_str("\n ( enter ) to send docker container commands"); @@ -476,17 +487,9 @@ pub fn draw_help_box(f: &mut Frame<'_, B>) { help_text.push_str("\n\n currenty an early work in progress, all and any input appreciated"); help_text.push_str(format!("\n {}", REPO.trim()).as_str()); - let mut max_line_width = 0; - + // Find the maximum line widths & height let all_text = format!("{}{}{}", NAME_TEXT, description_text, help_text); - - all_text.lines().into_iter().for_each(|line| { - let width = line.chars().count(); - if width > max_line_width { - max_line_width = width; - } - }); - + let mut max_line_width = max_line_width(&all_text); let mut lines = all_text.lines().count(); // Add some vertical and horizontal padding to the info box @@ -541,7 +544,7 @@ pub fn draw_help_box(f: &mut Frame<'_, B>) { f.render_widget(block, area); } -/// Show error popup over whole screen +/// Draw an error popup over whole screen pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Option) { let block = Block::default() .title(" Error ") @@ -565,14 +568,8 @@ pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Op text.push_str(to_push.as_str()); - let mut max_line_width = 0; - text.lines().into_iter().for_each(|line| { - let width = line.chars().count(); - if width > max_line_width { - max_line_width = width; - } - }); - + // Find the maximum line width & height + let mut max_line_width = max_line_width(&text); let mut lines = text.lines().count(); // Add some horizontal & vertical margins @@ -594,21 +591,14 @@ pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Op f.render_widget(paragraph, area); } -/// Show info box in bottom right corner +/// Draw info box in one of the 9 BoxLocations pub fn draw_info(f: &mut Frame<'_, B>, text: String) { let block = Block::default() .title("") .title_alignment(Alignment::Center) .borders(Borders::NONE); - let mut max_line_width = 0; - text.lines().into_iter().for_each(|line| { - let width = line.chars().count(); - if width > max_line_width { - max_line_width = width; - } - }); - + let mut max_line_width = max_line_width(&text); let mut lines = text.lines().count(); // Add some horizontal & vertical margins @@ -630,7 +620,7 @@ pub fn draw_info(f: &mut Frame<'_, B>, text: String) { f.render_widget(paragraph, area); } -/// draw a box in the center of the screen, based on max line width + number of lines +/// 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 { // Make sure blank_space can't be an negative, as will crash let blank_vertical = if r.height > text_lines { @@ -644,19 +634,18 @@ fn draw_popup(text_lines: u16, text_width: u16, r: Rect, box_location: BoxLocati 1 }; - let vertical_constraints = box_location.get_vertical_constraints(blank_vertical, text_lines); - let horizontal_constraints = - box_location.get_horizontal_constraints(blank_horizontal, text_width); + let v_constraints = box_location.get_vertical_constraints(blank_vertical, text_lines); + let h_constraints = box_location.get_horizontal_constraints(blank_horizontal, text_width); let indexes = box_location.get_indexes(); let popup_layout = Layout::default() .direction(Direction::Vertical) - .constraints(vertical_constraints) + .constraints(v_constraints) .split(r); Layout::default() .direction(Direction::Horizontal) - .constraints(horizontal_constraints) + .constraints(h_constraints) .split(popup_layout[indexes.0])[indexes.1] }