From 6edf99e0846bb4134d8ee5b646065b8cda8074d7 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:34:22 +0000 Subject: [PATCH] feat: change log panel size, closes #50 --- Cargo.lock | 12 +- create_release.sh | 7 +- example_config/example.config.jsonc | 14 ++ example_config/example.config.toml | 161 +++++++++--------- src/app_data/container_state.rs | 24 +++ src/app_data/mod.rs | 57 ++++--- src/config/config.toml | 159 ++++++++--------- src/config/keymap_parser.rs | 54 ++++-- src/config/mod.rs | 3 + src/config/parse_config_file.rs | 1 + src/input_handler/mod.rs | 49 ++++-- src/main.rs | 11 +- src/ui/draw_blocks/commands.rs | 5 +- src/ui/draw_blocks/containers.rs | 15 +- src/ui/draw_blocks/help.rs | 78 +++++---- src/ui/draw_blocks/logs.rs | 10 +- src/ui/draw_blocks/mod.rs | 90 ++++++++-- ...blocks__help__tests__draw_blocks_help.snap | 3 + ...tests__draw_blocks_help_custom_colors.snap | 2 + ...cks_help_custom_keymap_one_definition.snap | 6 +- ...ks_help_custom_keymap_two_definitions.snap | 6 +- ...w_blocks_help_one_and_two_definitions.snap | 6 +- ...tests__draw_blocks_help_show_timezone.snap | 2 + ...ocks__tests__draw_blocks_whole_layout.snap | 10 +- ...s__draw_blocks_whole_layout_long_name.snap | 10 +- ...sts__draw_blocks_whole_layout_no_logs.snap | 34 ++++ ...blocks_whole_layout_short_height_logs.snap | 34 ++++ src/ui/gui_state.rs | 100 +++++++++-- src/ui/mod.rs | 111 +++++++----- src/ui/redraw.rs | 6 +- 30 files changed, 738 insertions(+), 342 deletions(-) create mode 100644 src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap create mode 100644 src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap diff --git a/Cargo.lock b/Cargo.lock index c938e75..8af8478 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,9 +959,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "jiff-tzdb", @@ -975,9 +975,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", @@ -1017,9 +1017,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "libredox" diff --git a/create_release.sh b/create_release.sh index 7eebeb3..26bdc63 100755 --- a/create_release.sh +++ b/create_release.sh @@ -211,6 +211,7 @@ cross_build_x86_linux() { # Build, using cross-rs, for linux arm64 musl cross_build_aarch64_linux() { + clear check_cross echo -e "${YELLOW}cross build --target aarch64-unknown-linux-musl --release${RESET}" cross build --target aarch64-unknown-linux-musl --release @@ -371,7 +372,7 @@ release_flow() { } build_choice() { - cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16) + cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16) options=( 1 "x86 musl linux" off 2 "aarch64 musl linux" off @@ -415,7 +416,7 @@ build_choice() { } build_container_choice() { - cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16) + cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16) options=( 1 "x86 " off 2 "aarch64" off @@ -455,7 +456,7 @@ build_container_choice() { } main() { - cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16) + cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16) options=( 1 "test" off 2 "release" off diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index f62f643..0d9f818 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -29,6 +29,8 @@ // "save_dir": "$HOME", // Force use of docker cli when execing into containers, honestly mostly pointless "use_cli": false, + // Show the logs section - this can be changed during operation with the container_height_decrease / container_height_increase keys + "show_logs": true, ////////////////// // Custom Keymap // ////////////////// @@ -144,6 +146,18 @@ // Toggle mouse capture "toggle_mouse_capture": [ "m" + ], + // Reduce the height of the logs list section + "log_section_height_decrease": [ + "-" + ], + // Increase the height of the logs list section + "log_section_height_increase": [ + "+" + ], + // Toggle visibility of the log section + "log_section_toggle": [ + "\\" ] }, //////////////////// diff --git a/example_config/example.config.toml b/example_config/example.config.toml index b8d4534..467bbf3 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -32,7 +32,7 @@ timezone = "Etc/UTC" # Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.012345678Z # *Should* accept any valid strftime string up to 32 chars, see https://strftime.org/ -timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" +timestamp_format = "%Y-%m-%dT%H:%M:%S.%8f" # Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows # save_dir = "$HOME" @@ -40,6 +40,9 @@ timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" # Force use of docker cli when execing into containers, honestly mostly pointless use_cli = false +# Show the logs section - this can be changed during operation with the container_height_decrease / container_height_increase keys +show_logs = true + ################# # Custom Keymap # ################# @@ -56,52 +59,57 @@ use_cli = false [keymap] # Clear any popup boxes, filter panel, or help panel -clear=["c", "esc"] +clear = ["c", "esc"] # Cancel delete - clear also works here -delete_deny=["n"] +delete_deny = ["n"] # Confirm Delete -delete_confirm=["y"] +delete_confirm = ["y"] # Exec into the selected container -exec=["e"] +exec = ["e"] # Enter filter mode -filter_mode=["/", "F1"] +filter_mode = ["/", "F1"] # Quit at anytime quit = ["q"] # Save logs of selected container to file on disk -save_logs=["s"] +save_logs = ["s"] # scroll down a list by many -scroll_down_many=["pagedown"] +scroll_down_many = ["pagedown"] # scroll down a list by one item -scroll_down_one=["down", "j"] +scroll_down_one = ["down", "j"] # scroll down to the end of a list -scroll_end=["end"] +scroll_end = ["end"] # scroll up to the start of a list -scroll_start=["home"] +scroll_start = ["home"] # scroll up a list by many -scroll_up_many=["pageup"] +scroll_up_many = ["pageup"] # scroll up a list by one item -scroll_up_one=["up", "k"] +scroll_up_one = ["up", "k"] # Select next panel -select_next_panel=["tab"] +select_next_panel = ["tab"] # Select previous panel -select_previous_panel=["backtab"] +select_previous_panel = ["backtab"] # Sort the containers based on specific column -sort_by_name=["1"] -sort_by_state=["2"] -sort_by_status=["3"] -sort_by_cpu=["4"] -sort_by_memory=["5"] -sort_by_id=["6"] -sort_by_image=["7"] -sort_by_rx=["8"] -sort_by_tx=["9"] +sort_by_name = ["1"] +sort_by_state = ["2"] +sort_by_status = ["3"] +sort_by_cpu = ["4"] +sort_by_memory = ["5"] +sort_by_id = ["6"] +sort_by_image = ["7"] +sort_by_rx = ["8"] +sort_by_tx = ["9"] # Reset the sorted containers -sort_reset=["0"] +sort_reset = ["0"] # Toggle the help panel -toggle_help=["h"] +toggle_help = ["h"] # Toggle mouse capture -toggle_mouse_capture=["m"] - +toggle_mouse_capture = ["m"] +# Reduce the height of the logs list section +log_section_height_decrease = ["-"] +# Increase the height of the logs list section +log_section_height_increase = ["+"] +# Toggle visibility of the log section +log_section_toggle = ["\\"] ################# # Custom Colors # @@ -119,7 +127,7 @@ toggle_mouse_capture=["m"] # Background color of the entire line background = "magenta" # Animated loading icon at the start of the bar -loading_spinner="white" +loading_spinner = "white" # Text color text = "black" # Text color of a selected header @@ -128,137 +136,136 @@ text_selected = "gray" # The borders around the selectable panels - Containers, Commands, Logs [colors.borders] # Border when selected -selected="lightcyan" +selected = "lightcyan" # Border when not selected -unselected="grey" +unselected = "grey" # The containers sections, in the future more color customization options should be made available in this section [colors.containers] # The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol -icon="white" +icon = "white" # Background color of panel background = "reset" # At the moment, this will only change the color of the name, id, and image columns -text="blue" +text = "blue" # Text color of the RX column -text_rx="#FFE9C1" +text_rx = "#FFE9C1" # Text color of the TX column -text_tx="#CD8C8C" +text_tx = "#CD8C8C" # The logs panel, will only be applied if color_logs is false [colors.logs] # Background color of panel background = "reset" # text color -text="reset" +text = "reset" # Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section [colors.container_state] -dead="red" -exited="red" +dead = "red" +exited = "red" paused = "yellow" -removing ="lightred" -restarting ="lightgreen" -running_healthy ="green" -running_unhealthy="#FFB224" -unknown="red" +removing = "lightred" +restarting = "lightgreen" +running_healthy = "green" +running_unhealthy = "#FFB224" +unknown = "red" # The filter panel [colors.filter] # Background color of panel background = "reset" # color of text -text="gray" +text = "gray" # background color of the selected filter by item (Name/Image/Status/All) -selected_filter_background="gray" +selected_filter_background = "gray" # text color of the selected filter by item (Name/Image/Status/All) -selected_filter_text="black" +selected_filter_text = "black" # Highlighted text color -highlight="magenta" - +highlight = "magenta" # The color the of Docker commands available for each container [colors.commands] # Background color of panel background = "reset" -pause="yellow" -restart="magenta" +pause = "yellow" +restart = "magenta" stop = "red" -delete ="gray" -resume ="blue" -start ="green" +delete = "gray" +resume = "blue" +start = "green" # The cpu chart [colors.chart_cpu] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color? -title="green" +title = "green" # Maximum CPU percentage - again paused & stopped colors not yet customizable -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped colors not yet customizable -points="magenta" +points = "magenta" # The charts y-axis -y_axis="white" +y_axis = "white" # The memory chart [colors.chart_memory] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Maximum memory use - again paused & stopped will use colors.container_state -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped will use colors.container_state -points="cyan" +points = "cyan" # The charts y-axis -y_axis="white" +y_axis = "white" # The ports chart [colors.chart_ports] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Private/Public/IP headings -headings="yellow" +headings = "yellow" # Ports & IP listing text -text="white" +text = "white" # The help popup [colors.popup_help] # Background color -background="magenta" +background = "magenta" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="white" +text_highlight = "white" # The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings [colors.popup_info] # Background color -background="blue" +background = "blue" # Text color -text="white" +text = "white" # The delete popup - used to display a confirmation warning when about to delete a container [colors.popup_delete] # Background color -background="white" +background = "white" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="red" +text_highlight = "red" # The error popup - hopefully you'll never have to see this [colors.popup_error] # Background color -background="red" +background = "red" # Text color -text="white" \ No newline at end of file +text = "white" diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index a56b8a1..095c679 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -30,6 +30,14 @@ impl From<&str> for ContainerId { } impl ContainerId { + // TODO remove this once zigbuild uses Rust v1.87.0 + #[cfg(target_os = "macos")] + #[allow(clippy::missing_const_for_fn)] + pub fn get(&self) -> &str { + self.0.as_str() + } + + #[cfg(not(target_os = "macos"))] pub const fn get(&self) -> &str { self.0.as_str() } @@ -76,6 +84,14 @@ macro_rules! unit_struct { } impl $name { + #[cfg(target_os = "macos")] + #[allow(clippy::missing_const_for_fn)] + // TODO remove this once zigbuild uses Rust v1.87.0 + pub fn get(&self) -> &str { + self.0.as_str() + } + + #[cfg(not(target_os = "macos"))] pub const fn get(&self) -> &str { self.0.as_str() } @@ -594,6 +610,14 @@ impl Logs { self.logs.start(); } + // TODO remove this once zigbuild uses Rust v1.87.0 + #[cfg(target_os = "macos")] + #[allow(clippy::missing_const_for_fn)] + pub fn len(&self) -> usize { + self.logs.items.len() + } + + #[cfg(not(target_os = "macos"))] pub const fn len(&self) -> usize { self.logs.items.len() } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index c4bb787..8bf5ae3 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -14,7 +14,7 @@ use crate::{ ENTRY_POINT, app_error::AppError, config::Config, - ui::{GuiState, Redraw, Status, log_sanitizer}, + ui::{GuiState, Rerender, Status, log_sanitizer}, }; pub use container_state::*; @@ -122,7 +122,7 @@ pub struct AppData { error: Option, filter: Filter, hidden_containers: Vec, - redraw: Arc, + redraw: Arc, sorted_by: Option<(Header, SortedOrder)>, current_sorted_id: Vec, pub config: Config, @@ -137,13 +137,13 @@ pub struct AppData { pub filter: Filter, pub hidden_containers: Vec, pub current_sorted_id: Vec, - pub redraw: Arc, + pub redraw: Arc, pub sorted_by: Option<(Header, SortedOrder)>, } impl AppData { /// Generate a default app_state - pub fn new(config: Config, redraw: &Arc) -> Self { + pub fn new(config: Config, redraw: &Arc) -> Self { Self { config, containers: StatefulList::new(vec![]), @@ -192,7 +192,7 @@ impl AppData { /// sets the state to start if any filtering has occurred /// Also search in the "hidden" vec for items and insert back into the main containers vec fn filter_containers(&mut self) { - self.redraw.set_true(); + self.redraw.update(); let pre_len = self.get_container_len(); if !self.hidden_containers.is_empty() { @@ -296,7 +296,7 @@ impl AppData { /// Remove the sorted header & order, and sort by default - created datetime pub fn reset_sorted(&mut self) { self.set_sorted(None); - self.redraw.set_true(); + self.redraw.update(); } /// Sort containers based on a given header, if headings match, and already ascending, remove sorting @@ -392,7 +392,7 @@ impl AppData { self.containers.items.sort_by(sort_closure); if pre_order != self.get_current_ids() { - self.redraw.set_true(); + self.redraw.update(); } } else if self.current_sorted_id != self.get_current_ids() { self.containers.items.sort_by(|a, b| { @@ -400,13 +400,20 @@ impl AppData { .cmp(&b.created) .then_with(|| a.name.get().cmp(b.name.get())) }); - self.redraw.set_true(); + self.redraw.update(); self.current_sorted_id = self.get_current_ids(); } } /// Container state methods /// Get the total number of none "hidden" containers + // TODO remove this once zigbuild uses Rust v1.87.0 + #[cfg(target_os = "macos")] + pub fn get_container_len(&self) -> usize { + self.containers.items.len() + } + + #[cfg(not(target_os = "macos"))] pub const fn get_container_len(&self) -> usize { self.containers.items.len() } @@ -439,25 +446,25 @@ impl AppData { /// Select the first container pub fn containers_start(&mut self) { self.containers.start(); - self.redraw.set_true(); + self.redraw.update(); } /// select the last container pub fn containers_end(&mut self) { self.containers.end(); - self.redraw.set_true(); + self.redraw.update(); } /// Select the next container pub fn containers_next(&mut self) { self.containers.next(); - self.redraw.set_true(); + self.redraw.update(); } /// select the previous container pub fn containers_previous(&mut self) { self.containers.previous(); - self.redraw.set_true(); + self.redraw.update(); } /// Get ListState of containers @@ -579,7 +586,7 @@ impl AppData { pub fn docker_controls_next(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.next(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -587,7 +594,7 @@ impl AppData { pub fn docker_controls_previous(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.previous(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -595,7 +602,7 @@ impl AppData { pub fn docker_controls_start(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.start(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -603,7 +610,7 @@ impl AppData { pub fn docker_controls_end(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.end(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -641,7 +648,7 @@ impl AppData { pub fn log_next(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.next(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -649,7 +656,7 @@ impl AppData { pub fn log_previous(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.previous(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -657,7 +664,7 @@ impl AppData { pub fn log_end(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.end(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -665,7 +672,7 @@ impl AppData { pub fn log_start(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.start(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -706,14 +713,14 @@ impl AppData { /// Remove single app_state error pub fn remove_error(&mut self) { self.error = None; - self.redraw.set_true(); + self.redraw.update(); } /// Insert single app_state error pub fn set_error(&mut self, error: AppError, gui_state: &Arc>, status: Status) { gui_state.lock().status_push(status); self.error = Some(error); - self.redraw.set_true(); + self.redraw.update(); } /// Check if the selected container is a dockerised version of oxker @@ -803,7 +810,7 @@ impl AppData { container.mem_limit.update(mem_limit); } if self.is_selected_container(id) { - self.redraw.set_true(); + self.redraw.update(); } self.sort_containers(); } @@ -841,7 +848,7 @@ impl AppData { if self.containers.items.get(index).is_some() { self.containers.items.remove(index); if self.is_selected_container(id) { - self.redraw.set_true(); + self.redraw.update(); } } } @@ -971,7 +978,7 @@ impl AppData { } } if self.is_selected_container(id) { - self.redraw.set_true(); + self.redraw.update(); } } } diff --git a/src/config/config.toml b/src/config/config.toml index b8d4534..05984f6 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -32,7 +32,7 @@ timezone = "Etc/UTC" # Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.012345678Z # *Should* accept any valid strftime string up to 32 chars, see https://strftime.org/ -timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" +timestamp_format = "%Y-%m-%dT%H:%M:%S.%8f" # Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows # save_dir = "$HOME" @@ -40,6 +40,9 @@ timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" # Force use of docker cli when execing into containers, honestly mostly pointless use_cli = false +# Show the logs section - this can be changed during operation with the container_height_decrease / container_height_increase keys +show_logs = true + ################# # Custom Keymap # ################# @@ -56,52 +59,56 @@ use_cli = false [keymap] # Clear any popup boxes, filter panel, or help panel -clear=["c", "esc"] +clear = ["c", "esc"] # Cancel delete - clear also works here -delete_deny=["n"] +delete_deny = ["n"] # Confirm Delete -delete_confirm=["y"] +delete_confirm = ["y"] # Exec into the selected container -exec=["e"] +exec = ["e"] # Enter filter mode -filter_mode=["/", "F1"] +filter_mode = ["/", "F1"] # Quit at anytime quit = ["q"] # Save logs of selected container to file on disk -save_logs=["s"] +save_logs = ["s"] # scroll down a list by many -scroll_down_many=["pagedown"] +scroll_down_many = ["pagedown"] # scroll down a list by one item -scroll_down_one=["down", "j"] +scroll_down_one = ["down", "j"] # scroll down to the end of a list -scroll_end=["end"] +scroll_end = ["end"] # scroll up to the start of a list -scroll_start=["home"] +scroll_start = ["home"] # scroll up a list by many -scroll_up_many=["pageup"] +scroll_up_many = ["pageup"] # scroll up a list by one item -scroll_up_one=["up", "k"] +scroll_up_one = ["up", "k"] # Select next panel -select_next_panel=["tab"] +select_next_panel = ["tab"] # Select previous panel -select_previous_panel=["backtab"] +select_previous_panel = ["backtab"] # Sort the containers based on specific column -sort_by_name=["1"] -sort_by_state=["2"] -sort_by_status=["3"] -sort_by_cpu=["4"] -sort_by_memory=["5"] -sort_by_id=["6"] -sort_by_image=["7"] -sort_by_rx=["8"] -sort_by_tx=["9"] +sort_by_name = ["1"] +sort_by_state = ["2"] +sort_by_status = ["3"] +sort_by_cpu = ["4"] +sort_by_memory = ["5"] +sort_by_id = ["6"] +sort_by_image = ["7"] +sort_by_rx = ["8"] +sort_by_tx = ["9"] # Reset the sorted containers -sort_reset=["0"] +sort_reset = ["0"] # Toggle the help panel -toggle_help=["h"] +toggle_help = ["h"] # Toggle mouse capture -toggle_mouse_capture=["m"] - +toggle_mouse_capture = ["m"] +# Reduce the height of the logs list section +log_section_height_decrease = ["-"] +log_section_height_increase = ["+"] +# Toggle visibility of the log section +log_section_toggle = ["\\"] ################# # Custom Colors # @@ -119,7 +126,7 @@ toggle_mouse_capture=["m"] # Background color of the entire line background = "magenta" # Animated loading icon at the start of the bar -loading_spinner="white" +loading_spinner = "white" # Text color text = "black" # Text color of a selected header @@ -128,137 +135,137 @@ text_selected = "gray" # The borders around the selectable panels - Containers, Commands, Logs [colors.borders] # Border when selected -selected="lightcyan" +selected = "lightcyan" # Border when not selected -unselected="grey" +unselected = "grey" # The containers sections, in the future more color customization options should be made available in this section [colors.containers] # The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol -icon="white" +icon = "white" # Background color of panel background = "reset" # At the moment, this will only change the color of the name, id, and image columns -text="blue" +text = "blue" # Text color of the RX column -text_rx="#FFE9C1" +text_rx = "#FFE9C1" # Text color of the TX column -text_tx="#CD8C8C" +text_tx = "#CD8C8C" # The logs panel, will only be applied if color_logs is false [colors.logs] # Background color of panel background = "reset" # text color -text="reset" +text = "reset" # Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section [colors.container_state] -dead="red" -exited="red" +dead = "red" +exited = "red" paused = "yellow" -removing ="lightred" -restarting ="lightgreen" -running_healthy ="green" -running_unhealthy="#FFB224" -unknown="red" +removing = "lightred" +restarting = "lightgreen" +running_healthy = "green" +running_unhealthy = "#FFB224" +unknown = "red" # The filter panel [colors.filter] # Background color of panel background = "reset" # color of text -text="gray" +text = "gray" # background color of the selected filter by item (Name/Image/Status/All) -selected_filter_background="gray" +selected_filter_background = "gray" # text color of the selected filter by item (Name/Image/Status/All) -selected_filter_text="black" +selected_filter_text = "black" # Highlighted text color -highlight="magenta" +highlight = "magenta" # The color the of Docker commands available for each container [colors.commands] # Background color of panel background = "reset" -pause="yellow" -restart="magenta" +pause = "yellow" +restart = "magenta" stop = "red" -delete ="gray" -resume ="blue" -start ="green" +delete = "gray" +resume = "blue" +start = "green" # The cpu chart [colors.chart_cpu] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color? -title="green" +title = "green" # Maximum CPU percentage - again paused & stopped colors not yet customizable -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped colors not yet customizable -points="magenta" +points = "magenta" # The charts y-axis -y_axis="white" +y_axis = "white" # The memory chart [colors.chart_memory] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Maximum memory use - again paused & stopped will use colors.container_state -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped will use colors.container_state -points="cyan" +points = "cyan" # The charts y-axis -y_axis="white" +y_axis = "white" # The ports chart [colors.chart_ports] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Private/Public/IP headings -headings="yellow" +headings = "yellow" # Ports & IP listing text -text="white" +text = "white" # The help popup [colors.popup_help] # Background color -background="magenta" +background = "magenta" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="white" +text_highlight = "white" # The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings [colors.popup_info] # Background color -background="blue" +background = "blue" # Text color -text="white" +text = "white" # The delete popup - used to display a confirmation warning when about to delete a container [colors.popup_delete] # Background color -background="white" +background = "white" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="red" +text_highlight = "red" # The error popup - hopefully you'll never have to see this [colors.popup_error] # Background color -background="red" +background = "red" # Text color -text="white" \ No newline at end of file +text = "white" diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs index 0de79ec..9847800 100644 --- a/src/config/keymap_parser.rs +++ b/src/config/keymap_parser.rs @@ -39,6 +39,9 @@ optional_config_struct!( delete_confirm, exec, filter_mode, + log_section_height_increase, + log_section_height_decrease, + log_section_toggle, quit, save_logs, scroll_down_many, @@ -70,6 +73,9 @@ config_struct!( delete_confirm, exec, filter_mode, + log_section_height_increase, + log_section_height_decrease, + log_section_toggle, quit, save_logs, scroll_down_many, @@ -98,10 +104,13 @@ impl Keymap { pub const fn new() -> Self { Self { clear: (KeyCode::Char('c'), Some(KeyCode::Esc)), - delete_deny: (KeyCode::Char('n'), None), delete_confirm: (KeyCode::Char('y'), None), + delete_deny: (KeyCode::Char('n'), None), exec: (KeyCode::Char('e'), None), filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))), + log_section_height_decrease: (KeyCode::Char('-'), None), + log_section_height_increase: (KeyCode::Char('='), None), + log_section_toggle: (KeyCode::Char('\\'), None), quit: (KeyCode::Char('q'), None), save_logs: (KeyCode::Char('s'), None), scroll_down_many: (KeyCode::PageDown, None), @@ -112,14 +121,14 @@ impl Keymap { scroll_up_one: (KeyCode::Up, Some(KeyCode::Char('k'))), select_next_panel: (KeyCode::Tab, None), select_previous_panel: (KeyCode::BackTab, None), - sort_by_name: (KeyCode::Char('1'), None), - sort_by_state: (KeyCode::Char('2'), None), - sort_by_status: (KeyCode::Char('3'), None), sort_by_cpu: (KeyCode::Char('4'), None), - sort_by_memory: (KeyCode::Char('5'), None), sort_by_id: (KeyCode::Char('6'), None), sort_by_image: (KeyCode::Char('7'), None), + sort_by_memory: (KeyCode::Char('5'), None), + sort_by_name: (KeyCode::Char('1'), None), sort_by_rx: (KeyCode::Char('8'), None), + sort_by_state: (KeyCode::Char('2'), None), + sort_by_status: (KeyCode::Char('3'), None), sort_by_tx: (KeyCode::Char('9'), None), sort_reset: (KeyCode::Char('0'), None), toggle_help: (KeyCode::Char('h'), None), @@ -162,6 +171,22 @@ impl From> for Keymap { update_keymap(ck.clear, &mut keymap.clear, &mut clash); update_keymap(ck.delete_deny, &mut keymap.delete_deny, &mut clash); update_keymap(ck.delete_confirm, &mut keymap.delete_confirm, &mut clash); + update_keymap( + ck.log_section_height_decrease, + &mut keymap.log_section_height_decrease, + &mut clash, + ); + update_keymap( + ck.log_section_height_increase, + &mut keymap.log_section_height_increase, + &mut clash, + ); + update_keymap( + ck.log_section_toggle, + &mut keymap.log_section_toggle, + &mut clash, + ); + update_keymap(ck.exec, &mut keymap.exec, &mut clash); update_keymap(ck.filter_mode, &mut keymap.filter_mode, &mut clash); update_keymap(ck.quit, &mut keymap.quit, &mut clash); @@ -339,6 +364,8 @@ mod tests { delete_deny: Some(vec!["s".to_owned()]), delete_confirm: None, exec: None, + log_section_height_decrease: None, + log_section_height_increase: None, filter_mode: None, quit: None, save_logs: None, @@ -349,6 +376,7 @@ mod tests { scroll_up_many: None, scroll_up_one: None, select_next_panel: None, + log_section_toggle: None, select_previous_panel: None, sort_by_name: None, sort_by_state: None, @@ -376,10 +404,13 @@ mod tests { let input = ConfigKeymap { clear: gen_v(("a", "b")), - delete_deny: gen_v(("c", "d")), delete_confirm: gen_v(("e", "f")), + delete_deny: gen_v(("c", "d")), exec: gen_v(("g", "h")), filter_mode: gen_v(("i", "j")), + log_section_height_decrease: gen_v(("-", "Z")), + log_section_height_increase: gen_v(("=", "X")), + log_section_toggle: gen_v(("Y", "W")), quit: gen_v(("k", "l")), save_logs: gen_v(("m", "n")), scroll_down_many: gen_v(("o", "p")), @@ -390,14 +421,14 @@ mod tests { scroll_up_one: gen_v(("y", "z")), select_next_panel: gen_v(("0", "1")), select_previous_panel: gen_v(("2", "3")), - sort_by_name: gen_v(("4", "5")), - sort_by_state: gen_v(("6", "7")), - sort_by_status: gen_v(("8", "9")), sort_by_cpu: gen_v(("F1", "F12")), - sort_by_memory: gen_v(("/", "\\")), sort_by_id: gen_v(("[", "]")), sort_by_image: gen_v(("A", "B")), + sort_by_memory: gen_v(("/", "\\")), + sort_by_name: gen_v(("4", "5")), sort_by_rx: gen_v(("C", "D")), + sort_by_state: gen_v(("6", "7")), + sort_by_status: gen_v(("8", "9")), sort_by_tx: gen_v(("insert", "TAB")), sort_reset: gen_v(("up", "down")), toggle_help: gen_v(("home", "end")), @@ -410,6 +441,9 @@ mod tests { clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), + log_section_height_decrease: (KeyCode::Char('-'), Some(KeyCode::Char('Z'))), + log_section_height_increase: (KeyCode::Char('='), Some(KeyCode::Char('X'))), + log_section_toggle: (KeyCode::Char('Y'), Some(KeyCode::Char('W'))), exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), diff --git a/src/config/mod.rs b/src/config/mod.rs index bb95d79..24e6877 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -30,6 +30,7 @@ pub struct Config { pub show_timestamp: bool, pub timezone: Option, pub timestamp_format: String, + pub show_logs: bool, pub use_cli: bool, } @@ -51,6 +52,7 @@ impl From<&Args> for Config { timezone: Self::parse_timezone(args.timezone.clone()), timestamp_format: Self::parse_timestamp_format(None), use_cli: args.use_cli, + show_logs: true, } } } @@ -73,6 +75,7 @@ impl From for Config { timezone: Self::parse_timezone(config_file.timezone), timestamp_format: Self::parse_timestamp_format(config_file.timestamp_format), use_cli: config_file.use_cli.unwrap_or(false), + show_logs: config_file.show_logs.unwrap_or(true), } } } diff --git a/src/config/parse_config_file.rs b/src/config/parse_config_file.rs index b198f1b..ead111a 100644 --- a/src/config/parse_config_file.rs +++ b/src/config/parse_config_file.rs @@ -75,6 +75,7 @@ pub struct ConfigFile { pub timestamp_format: Option, pub timezone: Option, pub use_cli: Option, + pub show_logs: Option, } impl ConfigFile { diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index a52c8dd..4abd32d 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -288,23 +288,15 @@ impl InputHandler { /// Change the the "next" selectable panel /// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state fn next_panel_key(&self) { - self.gui_state.lock().next_panel(); - if self.app_data.lock().get_container_len() == 0 - && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands - { - self.gui_state.lock().next_panel(); - } + self.gui_state.lock().selectable_panel_next(&self.app_data); } /// Change to previously selected panel /// Need to skip the commands planel if there no are current containers running fn previous_panel_key(&self) { - self.gui_state.lock().previous_panel(); - if self.app_data.lock().get_container_len() == 0 - && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands - { - self.gui_state.lock().previous_panel(); - } + self.gui_state + .lock() + .selectable_panel_previous(&self.app_data); } fn scroll_start_key(&self) { @@ -458,9 +450,25 @@ impl InputHandler { } } + // Increase the log panel height + fn log_panel_height_increase(&self) { + self.gui_state.lock().log_height_increase(); + } + + // Decrease the log panel height + fn log_panel_height_decrease(&self) { + self.gui_state.lock().log_height_decrease(); + } + + // Toggle visibility of the log panel + fn log_panel_toggle(&self) { + self.gui_state.lock().toggle_show_logs(); + } + /// Handle button presses in all other scenarios async fn handle_others(&mut self, key_code: KeyCode) { self.handle_sort(key_code); + // shift key plus arrows match key_code { _ if self.keymap.exec.0 == key_code || self.keymap.exec.1 == Some(key_code) => { self.exec_key().await; @@ -477,6 +485,23 @@ impl InputHandler { { self.mouse_capture_key(); } + _ if self.keymap.log_section_height_decrease.0 == key_code + || self.keymap.log_section_height_decrease.1 == Some(key_code) => + { + self.log_panel_height_decrease(); + } + + _ if self.keymap.log_section_height_increase.0 == key_code + || self.keymap.log_section_height_increase.1 == Some(key_code) => + { + self.log_panel_height_increase(); + } + + _ if self.keymap.log_section_toggle.0 == key_code + || self.keymap.log_section_toggle.1 == Some(key_code) => + { + self.log_panel_toggle(); + } _ if self.keymap.save_logs.0 == key_code || self.keymap.save_logs.1 == Some(key_code) => diff --git a/src/main.rs b/src/main.rs index 6a0142d..66c642b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ mod exec; mod input_handler; mod ui; -use ui::{GuiState, Redraw, Status, Ui}; +use ui::{GuiState, Rerender, Status, Ui}; use crate::docker_data::DockerMessage; @@ -98,10 +98,10 @@ fn handler_init( async fn main() { setup_tracing(); let config = config::Config::new(); - let redraw = Arc::new(Redraw::new()); + let redraw = Arc::new(Rerender::new()); let app_data = Arc::new(Mutex::new(AppData::new(config.clone(), &redraw))); - let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw))); + let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw, config.show_logs))); let is_running = Arc::new(AtomicBool::new(true)); let (docker_tx, docker_rx) = tokio::sync::mpsc::channel(32); @@ -159,7 +159,7 @@ mod tests { RunningState, State, StatefulList, }, config::{AppColors, Config, Keymap}, - ui::Redraw, + ui::Rerender, }; /// Default test config, has timestamps turned off @@ -179,6 +179,7 @@ mod tests { timestamp_format: "HH:MM:SS.NNNNN dd-mm-yyyy".to_owned(), show_timestamp: false, use_cli: false, + show_logs: true, timezone: None, } } @@ -207,7 +208,7 @@ mod tests { current_sorted_id: vec![], error: None, sorted_by: None, - redraw: Arc::new(Redraw::new()), + redraw: Arc::new(Rerender::new()), filter: Filter::new(), config: gen_config(), } diff --git a/src/ui/draw_blocks/commands.rs b/src/ui/draw_blocks/commands.rs index 146313d..805ae0f 100644 --- a/src/ui/draw_blocks/commands.rs +++ b/src/ui/draw_blocks/commands.rs @@ -241,7 +241,10 @@ mod tests { } // Control panel now selected, should have a blue border - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal diff --git a/src/ui/draw_blocks/containers.rs b/src/ui/draw_blocks/containers.rs index a5c7395..a63283c 100644 --- a/src/ui/draw_blocks/containers.rs +++ b/src/ui/draw_blocks/containers.rs @@ -157,7 +157,10 @@ mod tests { let mut setup = test_setup(40, 6, true, true); setup.app_data.lock().containers = StatefulList::new(vec![]); - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); let colors = setup.app_data.lock().config.app_colors; @@ -184,7 +187,10 @@ mod tests { } } - setup.gui_state.lock().previous_panel(); + setup + .gui_state + .lock() + .selectable_panel_previous(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup @@ -255,7 +261,10 @@ mod tests { } // Change selected panel, border is now no longer blue - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal diff --git a/src/ui/draw_blocks/help.rs b/src/ui/draw_blocks/help.rs index 35ede46..a28cbbe 100644 --- a/src/ui/draw_blocks/help.rs +++ b/src/ui/draw_blocks/help.rs @@ -85,6 +85,7 @@ impl HelpInfo { } /// Generate the button information span + metadata + #[allow(clippy::too_many_lines)] fn gen_keymap_info(colors: AppColors, zone: Option<&TimeZone>, show_timestamp: bool) -> Self { let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors); let button_desc = |x: &str| Self::text_span(x, colors); @@ -152,6 +153,16 @@ impl HelpInfo { button_item("1 - 9"), button_desc("sort by header - or click header"), ]), + Line::from(vec![ + space(), + button_item("- ="), + button_desc("change log section height"), + ]), + Line::from(vec![ + space(), + button_item("\\"), + button_desc("toggle log section visibility"), + ]), Line::from(vec![ space(), button_item("esc"), @@ -212,15 +223,6 @@ impl HelpInfo { fn custom_text<'a>(colors: AppColors, _keymap: &Keymap, zone: Option<&TimeZone>) -> Line<'a> { let highlighted = |x: &str| Self::highlighted_text_span(x, colors); let text = |x: &str| Self::text_span(x, colors); - - // if keymap != &Keymap::new() { - // op.push(highlighted("customised keymap, ")); - // } - - // if colors != AppColors::new() { - // op.push(highlighted("customised app colors, ")); - // }; - let zone = zone.and_then(|i| i.iana_name()).unwrap_or("Etc/UTC"); Line::from(Vec::from([text("logs timezone: "), highlighted(zone)])).centered() } @@ -295,6 +297,15 @@ impl HelpInfo { or_secondary(km.sort_by_image, "sort containers by image"), or_secondary(km.sort_by_rx, "sort containers by rx"), or_secondary(km.sort_by_tx, "sort containers by tx"), + or_secondary( + km.log_section_height_decrease, + "decrease log section height", + ), + or_secondary( + km.log_section_height_increase, + "increase log section height", + ), + or_secondary(km.log_section_toggle, "toggle log section visibility"), or_secondary(km.clear, "close dialog"), or_secondary(km.quit, "quit at any time"), ]; @@ -426,7 +437,7 @@ mod tests { #[test] /// This will cause issues once the version has more than the current 5 chars (0.5.0) fn test_draw_blocks_help() { - let mut setup = test_setup(87, 33, true, true); + let mut setup = test_setup(87, 35, true, true); let tz = setup.app_data.lock().config.timezone.clone(); setup @@ -448,29 +459,29 @@ mod tests { for (result_cell_index, result_cell) in result_row.iter().enumerate() { match (row_index, result_cell_index) { // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 32, _) | (0..=33, 0 | 86) => { + (0 | 34, _) | (0..=33, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } // border is black on magenta - (1 | 31, _) | (1..=31, 1 | 85) => { + (1 | 32, _) | (1..=31, 1 | 85) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - // oxker logo && description - (2..=10, 2..=85) | (12, 19..=66) - // button in the brackets + // oxker logo && description + (2..=10, 2..=85) + | (12, 19..=66) | (14, 2..=10 | 13..=27) | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) | (16 | 23, 2..=12) - | (17..=20 | 22 | 25, 2..=8) + | (17..=20 | 22 | 25 | 27, 2..=8) | (21, 2..=9 | 12..=18) - | (24, 2..=10) => { + | (24 | 26, 2..=10) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); } // The URL is white and underlined - (28, 25..=60) => { + (30, 25..=60) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); @@ -488,7 +499,7 @@ mod tests { #[test] /// Test that the help panel gets drawn with custom colors fn test_draw_blocks_help_custom_colors() { - let mut setup = test_setup(87, 33, true, true); + let mut setup = test_setup(87, 35, true, true); let mut colors = AppColors::new(); let tz = setup.app_data.lock().config.timezone.clone(); @@ -515,29 +526,29 @@ mod tests { for (result_cell_index, result_cell) in result_row.iter().enumerate() { match (row_index, result_cell_index) { // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 32, _) | (0..=33, 0 | 86) => { + (0 | 34, _) | (0..=33, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } - // border is black on magenta - (1 | 31, _) | (1..=31, 1 | 85) => { + // border is red on black + (1 | 32, _) | (1..=31, 1 | 85) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Red); } - // oxker logo && description - (2..=10, 2..=85) | (12, 19..=66) - // button in the brackets + // oxker logo && description + (2..=10, 2..=85) + | (12, 19..=66) | (14, 2..=10 | 13..=27) | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) | (16 | 23, 2..=12) - | (17..=20 | 22 | 25, 2..=8) + | (17..=20 | 22 | 25 | 27, 2..=8) | (21, 2..=9 | 12..=18) - | (24, 2..=10) => { + | (24 | 26, 2..=10) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); } // The URL is yellow and underlined - (28, 25..=60) => { + (30, 25..=60) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); @@ -562,6 +573,9 @@ mod tests { delete_deny: (KeyCode::Char('c'), None), delete_confirm: (KeyCode::Char('e'), None), exec: (KeyCode::Char('g'), None), + log_section_height_decrease: (KeyCode::Char('z'), None), + log_section_height_increase: (KeyCode::Char('x'), None), + log_section_toggle: (KeyCode::Char('W'), None), filter_mode: (KeyCode::Char('i'), None), quit: (KeyCode::Char('k'), None), save_logs: (KeyCode::Char('m'), None), @@ -607,6 +621,9 @@ mod tests { delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), + log_section_height_decrease: (KeyCode::Char('A'), Some(KeyCode::Char('Z'))), + log_section_height_increase: (KeyCode::Char('B'), Some(KeyCode::Char('X'))), + log_section_toggle: (KeyCode::Char('C'), Some(KeyCode::Char('W'))), filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), @@ -653,6 +670,9 @@ mod tests { delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), exec: (KeyCode::Char('g'), None), filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), + log_section_height_decrease: (KeyCode::Char('A'), Some(KeyCode::Char('Z'))), + log_section_height_increase: (KeyCode::Char('B'), Some(KeyCode::Char('X'))), + log_section_toggle: (KeyCode::Char('C'), Some(KeyCode::Char('W'))), quit: (KeyCode::Char('k'), None), save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), scroll_down_many: (KeyCode::Char('o'), None), @@ -691,7 +711,7 @@ mod tests { #[test] fn test_draw_blocks_help_show_timezone() { - let mut setup = test_setup(87, 35, true, true); + let mut setup = test_setup(87, 37, true, true); setup .terminal diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs index 835b01a..71acd1a 100644 --- a/src/ui/draw_blocks/logs.rs +++ b/src/ui/draw_blocks/logs.rs @@ -124,8 +124,14 @@ mod tests { } } - setup.gui_state.lock().next_panel(); - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); // When selected, has a blue border diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index f729c90..17d6aad 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -128,7 +128,7 @@ pub mod tests { use crate::{ app_data::{AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts}, tests::{gen_appdata, gen_containers}, - ui::{GuiState, Redraw, draw_frame}, + ui::{GuiState, Rerender, draw_frame}, }; use super::FrameData; @@ -152,32 +152,33 @@ pub mod tests { fn from(data: (&Arc>, &Arc>)) -> Self { let (app_data, gui_data) = (data.0.lock(), data.1.lock()); - // set max height for container section, needs +5 to deal with docker commands list and borders - let height = app_data.get_container_len(); - let height = if height < 12 { - u16::try_from(height + 5).unwrap_or_default() - } else { - 12 - }; + // let container_section_height = app_data.get_container_len(); + // let container_section_height = if container_section_height < 12 { + // u16::try_from(container_section_height + 5).unwrap_or_default() + // } else { + // 12 + // }; let (filter_by, filter_term) = app_data.get_filter(); Self { chart_data: app_data.get_chart_data(), - columns: app_data.get_width(), color_logs: app_data.config.color_logs, + columns: app_data.get_width(), + // container_section_height, container_title: app_data.get_container_title(), delete_confirm: gui_data.get_delete_container(), filter_by, filter_term: filter_term.cloned(), has_containers: app_data.get_container_len() > 0, has_error: app_data.get_error(), - height, - ports: app_data.get_selected_ports(), - port_max_lens: app_data.get_longest_port(), + show_logs: gui_data.get_show_logs(), info_text: gui_data.info_box_text.clone(), is_loading: gui_data.is_loading(), loading_icon: gui_data.get_loading().to_string(), + log_height: gui_data.get_log_height(), log_title: app_data.get_log_title(), + port_max_lens: app_data.get_longest_port(), + ports: app_data.get_selected_ports(), selected_panel: gui_data.get_selected_panel(), sorted_by: app_data.get_sorted(), status: gui_data.get_status(), @@ -199,8 +200,8 @@ pub mod tests { app_data.containers_start(); } - let redraw = Arc::new(Redraw::new()); - let gui_state = GuiState::new(&redraw); + let redraw = Arc::new(Rerender::new()); + let gui_state = GuiState::new(&redraw, app_data.config.show_logs); let app_data = Arc::new(Mutex::new(app_data)); let gui_state = Arc::new(Mutex::new(gui_state)); @@ -360,4 +361,65 @@ pub mod tests { assert_snapshot!(setup.terminal.backend()); } + + #[test] + /// Check that the whole layout is drawn correctly when the logs panel is removed + fn test_draw_blocks_whole_layout_no_logs() { + let mut setup = test_setup(160, 30, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + setup.gui_state.lock().log_height_zero(); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } + + #[test] + /// Check that the whole layout is drawn correctly when the logs panel height is ~4 + fn test_draw_blocks_whole_layout_short_height_logs() { + let mut setup = test_setup(160, 30, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + setup.gui_state.lock().log_height_zero(); + + for _ in 0..=3 { + setup.gui_state.lock().log_height_increase(); + } + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } } diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap index 01c97c2..747eec0 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap @@ -1,5 +1,6 @@ --- source: src/ui/draw_blocks/help.rs +assertion_line: 456 expression: setup.terminal.backend() --- " " @@ -26,6 +27,8 @@ expression: setup.terminal.backend() " │ ( F1 ) or ( / ) enter filter mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " +" │ ( - = ) change log section height │ " +" │ ( \ ) toggle log section visibility │ " " │ ( esc ) close dialog │ " " │ ( q ) quit at any time │ " " │ │ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap index 01c97c2..a0f9ea1 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap @@ -26,6 +26,8 @@ expression: setup.terminal.backend() " │ ( F1 ) or ( / ) enter filter mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " +" │ ( - = ) change log section height │ " +" │ ( \ ) toggle log section visibility │ " " │ ( esc ) close dialog │ " " │ ( q ) quit at any time │ " " │ │ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap index 384ea0a..8cebda0 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap @@ -2,7 +2,6 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " " ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────╮ " " │ │ " " │ 88 │ " @@ -40,12 +39,13 @@ expression: setup.terminal.backend() " │ ( , ) sort containers by image │ " " │ ( . ) sort containers by rx │ " " │ ( Insert ) sort containers by tx │ " +" │ ( z ) decrease log section height │ " +" │ ( x ) increase log section height │ " +" │ ( W ) toggle log section visibility │ " " │ ( a ) close dialog │ " " │ ( k ) quit at any time │ " " │ │ " " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰────────────────────────────────────────────────────────────────────────────────────────────╯ " -" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap index 03504ba..818fc99 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap @@ -2,7 +2,6 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " " ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " " │ │ " " │ 88 │ " @@ -40,12 +39,13 @@ expression: setup.terminal.backend() " │ ( , ) or ( \ ) sort containers by image │ " " │ ( . ) or ( ] ) sort containers by rx │ " " │ ( Insert ) or ( Back Tab ) sort containers by tx │ " +" │ ( A ) or ( Z ) decrease log section height │ " +" │ ( B ) or ( X ) increase log section height │ " +" │ ( C ) or ( W ) toggle log section visibility │ " " │ ( a ) or ( b ) close dialog │ " " │ ( k ) or ( l ) quit at any time │ " " │ │ " " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " -" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap index f6dfd9d..1778328 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap @@ -2,7 +2,6 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " " ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " " │ │ " " │ 88 │ " @@ -40,12 +39,13 @@ expression: setup.terminal.backend() " │ ( , ) sort containers by image │ " " │ ( . ) or ( ] ) sort containers by rx │ " " │ ( Insert ) sort containers by tx │ " +" │ ( A ) or ( Z ) decrease log section height │ " +" │ ( B ) or ( X ) increase log section height │ " +" │ ( C ) or ( W ) toggle log section visibility │ " " │ ( a ) or ( b ) close dialog │ " " │ ( k ) quit at any time │ " " │ │ " " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " -" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap index ea369da..291cc9f 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap @@ -28,6 +28,8 @@ expression: setup.terminal.backend() " │ ( F1 ) or ( / ) enter filter mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " +" │ ( - = ) change log section height │ " +" │ ( \ ) toggle log section visibility │ " " │ ( esc ) close dialog │ " " │ ( q ) quit at any time │ " " │ │ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap index f90c683..df648f6 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap @@ -8,8 +8,6 @@ expression: setup.terminal.backend() "│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" "│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" "│ ││ delete │" -"│ ││ │" -"│ ││ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" "╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮" "│ line 1 │" @@ -25,10 +23,12 @@ expression: setup.terminal.backend() "│ │" "│ │" "│ │" +"│ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" "╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •••• ││100.00 kB│ ••• ││ ip private public│" -"│ │ ••• • ││ │ ••• • ││ 8001 │" -"│ │•• ••• ││ │•• ••• ││127.0.0.1 8003 8003│" +"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│" +"│ │ •• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │• •• ││ │• •• ││ │" "│ │ ││ │ ││ │" "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap index 27fe0a0..e0a34fb 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap @@ -8,8 +8,6 @@ expression: setup.terminal.backend() "│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" "│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" "│ ││ delete │" -"│ ││ │" -"│ ││ │" "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰─────────────────╯" "╭ Logs 3/3 - a_long_container_name_for_the_purposes_of_this_test - a_long_image_name_for_the_purposes_of_this_test ──────────────────────────────────────────────────────────────────────────╮" "│ line 1 │" @@ -25,10 +23,12 @@ expression: setup.terminal.backend() "│ │" "│ │" "│ │" +"│ │" "╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" "╭───────────────────────────────── cpu 03.00% ─────────────────────────────────╮╭────────────────────────────── memory 30.00 kB ───────────────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •••• ││100.00 kB│ ••••• ││ ip private public│" -"│ │ •••• • ││ │ ••• • ││ 8001 │" -"│ │••• •••• ││ │••• ••• ││127.0.0.1 8003 8003│" +"│10.00%│ ••• ││100.00 kB│ •••• ││ ip private public│" +"│ │ ••• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │•• ••• ││ │•• •• ││ │" "│ │ ││ │ ││ │" "╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap new file mode 100644 index 0000000..fd83747 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap @@ -0,0 +1,34 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" +"│ ││ delete │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" +"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" +"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│" +"│ │ •• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │• •• ││ │• •• ││ │" +"│ │ ││ │ ││ │" +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap new file mode 100644 index 0000000..046a12e --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap @@ -0,0 +1,34 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" +"│ ││ delete │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" +"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮" +"│ line 2 │" +"│▶ line 3 │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" +"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│" +"│ │ •• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │• •• ││ │• •• ││ │" +"│ │ ││ │ ││ │" +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index 0c9d57a..1ba83c9 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -9,11 +9,11 @@ use tokio::task::JoinHandle; use uuid::Uuid; use crate::{ - app_data::{ContainerId, Header}, + app_data::{AppData, ContainerId, Header}, exec::ExecMode, }; -use super::Redraw; +use super::Rerender; #[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)] pub enum SelectablePanel { @@ -184,13 +184,15 @@ pub struct GuiState { loading_handle: Option>, loading_index: u8, loading_set: HashSet, - redraw: Arc, + log_height: u16, + rerender: Arc, selected_panel: SelectablePanel, + show_logs: bool, status: HashSet, pub info_box_text: Option<(String, Instant)>, } impl GuiState { - pub fn new(redraw: &Arc) -> Self { + pub fn new(redraw: &Arc, show_logs: bool) -> Self { Self { delete_container: None, exec_mode: None, @@ -202,11 +204,61 @@ impl GuiState { loading_handle: None, loading_index: 0, loading_set: HashSet::new(), - redraw: Arc::clone(redraw), + log_height: 75, + rerender: Arc::clone(redraw), selected_panel: SelectablePanel::default(), + show_logs, status: HashSet::new(), } } + /// Increase the height of the log panel, then rerender + pub fn log_height_increase(&mut self) { + if self.show_logs { + if self.log_height <= 75 { + self.log_height = self.log_height.saturating_add(5); + } + } else { + self.log_height = 5; + } + self.show_logs = true; + self.rerender.update(); + } + + /// Reduce the height of the logs panel, then rerender + /// Unselect logs panel if currently selected + pub fn log_height_decrease(&mut self) { + if self.show_logs { + self.log_height = self.log_height.saturating_sub(5); + if self.log_height == 0 && self.selected_panel == SelectablePanel::Logs { + self.show_logs = false; + self.selected_panel = SelectablePanel::Containers; + } + self.rerender.update(); + } + } + + pub const fn get_show_logs(&self) -> bool { + self.show_logs + } + + pub fn toggle_show_logs(&mut self) { + self.show_logs = !self.show_logs; + if !self.show_logs && self.selected_panel == SelectablePanel::Logs { + self.selected_panel = SelectablePanel::Containers; + } + self.rerender.update(); + } + + /// Set the log_height to zero, used if show_logs=false in the config file + pub const fn log_height_zero(&mut self) { + self.log_height = 0; + } + + /// Get the log height, *should* be a u8 between 0 and 80, essentially a percentage + pub const fn get_log_height(&self) -> u16 { + self.log_height + } + /// Clear panels hash map, so on resize can fix the sizes for mouse clicks pub fn clear_area_map(&mut self) { self.intersect_panel.clear(); @@ -227,7 +279,7 @@ impl GuiState { .first() { self.selected_panel = *data.0; - self.redraw.set_true(); + self.rerender.update(); } } @@ -300,7 +352,7 @@ impl GuiState { self.status_del(Status::DeleteConfirm); } self.delete_container = id; - self.redraw.set_true(); + self.rerender.update(); } /// Return a copy of the Status HashSet @@ -321,7 +373,7 @@ impl GuiState { } _ => (), } - self.redraw.set_true(); + self.rerender.update(); } /// Inset the ExecMode into self, and set the Status as exec @@ -330,7 +382,7 @@ impl GuiState { pub fn set_exec_mode(&mut self, mode: ExecMode) { self.exec_mode = Some(mode); self.status.insert(Status::Exec); - self.redraw.set_true(); + self.rerender.update(); } pub fn get_exec_mode(&self) -> Option { @@ -342,20 +394,32 @@ impl GuiState { pub fn status_push(&mut self, status: Status) { if status != Status::Exec { self.status.insert(status); - self.redraw.set_true(); + self.rerender.update(); } } /// Change to next selectable panel - pub fn next_panel(&mut self) { + pub fn selectable_panel_next(&mut self, app_data: &Arc>) { self.selected_panel = self.selected_panel.next(); - self.redraw.set_true(); + if (app_data.lock().get_container_len() == 0 + && self.get_selected_panel() == SelectablePanel::Commands) + || (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs) + { + self.selected_panel = self.selected_panel.next(); + } + self.rerender.update(); } /// Change to previous selectable panel - pub fn previous_panel(&mut self) { + pub fn selectable_panel_previous(&mut self, app_data: &Arc>) { self.selected_panel = self.selected_panel.prev(); - self.redraw.set_true(); + if (app_data.lock().get_container_len() == 0 + && self.get_selected_panel() == SelectablePanel::Commands) + || (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs) + { + self.selected_panel = self.selected_panel.prev(); + } + self.rerender.update(); } /// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array @@ -366,7 +430,7 @@ impl GuiState { self.loading_index += 1; } self.loading_set.insert(uuid); - self.redraw.set_true(); + self.rerender.update(); } pub fn is_loading(&self) -> bool { @@ -399,7 +463,7 @@ impl GuiState { /// Stop the loading_spin function, and reset gui loading status pub fn stop_loading_animation(&mut self, loading_uuid: Uuid) { self.loading_set.remove(&loading_uuid); - self.redraw.set_true(); + self.rerender.update(); if self.loading_set.is_empty() { self.loading_index = 0; if let Some(h) = &self.loading_handle { @@ -412,12 +476,12 @@ impl GuiState { /// Set info box content pub fn set_info_box(&mut self, text: &str) { self.info_box_text = Some((text.to_owned(), std::time::Instant::now())); - self.redraw.set_true(); + self.rerender.update(); } /// Remove info box content pub fn reset_info_box(&mut self) { self.info_box_text = None; - self.redraw.set_true(); + self.rerender.update(); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index cc64199..2141988 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -24,7 +24,7 @@ mod color_match; mod draw_blocks; mod gui_state; mod redraw; -pub use redraw::Redraw; +pub use redraw::Rerender; pub use self::color_match::*; pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status}; @@ -50,7 +50,7 @@ pub struct Ui { input_tx: Sender, is_running: Arc, now: Instant, - redraw: Arc, + redraw: Arc, terminal: Terminal>, } @@ -73,7 +73,7 @@ impl Ui { gui_state: Arc>, input_tx: Sender, is_running: Arc, - redraw: Arc, + redraw: Arc, ) { match Self::setup_terminal() { Ok(mut terminal) => { @@ -205,6 +205,10 @@ impl Ui { let docker_interval_ms = u128::from(self.app_data.lock().config.docker_interval_ms); let mut drawn_at = std::time::Instant::now(); + if !self.app_data.lock().config.show_logs { + self.gui_state.lock().log_height_zero(); + } + while self.is_running.load(Ordering::SeqCst) { if self.should_redraw(&mut drawn_at, docker_interval_ms) { let fd = FrameData::from(&*self); @@ -245,6 +249,8 @@ impl Ui { } } else if let Event::Resize(_, _) = event { self.gui_state.lock().clear_area_map(); + // self.gui_state.lock().set_window_height(row); + self.terminal.autoresize().ok(); } } @@ -267,6 +273,7 @@ impl Ui { /// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here #[derive(Debug, Clone)] +#[allow(clippy::struct_excessive_bools)] pub struct FrameData { chart_data: Option<(CpuTuple, MemTuple)>, color_logs: bool, @@ -276,8 +283,10 @@ pub struct FrameData { filter_by: FilterBy, filter_term: Option, has_containers: bool, + // container_section_height: u16, + log_height: u16, + show_logs: bool, has_error: Option, - height: u16, info_text: Option<(String, Instant)>, is_loading: bool, loading_icon: String, @@ -293,29 +302,36 @@ impl From<&Ui> for FrameData { fn from(ui: &Ui) -> Self { let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock()); + // set a flag if the - or = button has been pressed, and if so use that calc, else use this calc // set max height for container section, needs +5 to deal with docker commands list and borders - let height = app_data.get_container_len(); - let height = if height < 12 { - u16::try_from(height + 5).unwrap_or_default() - } else { - 12 - }; + // TODO fix this + // let container_len = app_data.get_container_len(); + // let container_section_height = if container_len < 12 { + // // u16::try_from(container_len + 5).unwrap_or_default() + // 8 + // } else { + // 12 + // }; + // TODO work out what to do with this + // container_section_height = 8; let (filter_by, filter_term) = app_data.get_filter(); Self { chart_data: app_data.get_chart_data(), color_logs: app_data.config.color_logs, columns: app_data.get_width(), + // container_section_height, container_title: app_data.get_container_title(), delete_confirm: gui_data.get_delete_container(), filter_by, filter_term: filter_term.cloned(), has_containers: app_data.get_container_len() > 0, has_error: app_data.get_error(), - height, info_text: gui_data.info_box_text.clone(), is_loading: gui_data.is_loading(), + show_logs: gui_data.get_show_logs(), loading_icon: gui_data.get_loading().to_string(), + log_height: gui_data.get_log_height(), log_title: app_data.get_log_title(), port_max_lens: app_data.get_longest_port(), ports: app_data.get_selected_ports(), @@ -346,44 +362,61 @@ fn draw_frame( .constraints(whole_constraints) .split(f.area()); - // Split into 3, containers+controls, logs, then graphs + draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap); + + // If required, draw filter bar + if let Some(rect) = whole_layout.get(2) { + draw_blocks::filter::draw(*rect, colors, f, fd); + } + + // What we should do is work out the container+logs size, and then set the percentage to height - 6 + + let container_logs_section_constraints = if fd.show_logs { + vec![Constraint::Min(6), Constraint::Percentage(fd.log_height)] + } else { + vec![Constraint::Percentage(100)] + }; + + let upper_main_constraints = if fd.has_containers { + vec![Constraint::Percentage(75), Constraint::Percentage(25)] + } else { + vec![Constraint::Percentage(100), Constraint::Percentage(0)] + }; let upper_main = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Max(fd.height), Constraint::Min(1)].as_ref()) + .constraints(upper_main_constraints) .split(whole_layout[1]); - let top_split = if fd.has_containers { + let containers_logs_section = Layout::default() + .direction(Direction::Vertical) + .constraints(container_logs_section_constraints) + .split(upper_main[0]); + + // Containers & Command Horizontal split + let container_command_constraints = if fd.has_containers { vec![Constraint::Percentage(90), Constraint::Percentage(10)] } else { vec![Constraint::Percentage(100)] }; + // Containers + docker commands - let top_panel = Layout::default() + let containers_commands = Layout::default() .direction(Direction::Horizontal) - .constraints(top_split) - .split(upper_main[0]); + .constraints(container_command_constraints) + .split(containers_logs_section[0]); - let lower_split = if fd.has_containers { - vec![Constraint::Percentage(70), Constraint::Percentage(30)] - } else { - vec![Constraint::Percentage(100)] - }; + draw_blocks::containers::draw(app_data, containers_commands[0], colors, f, fd, gui_state); - // Split into 2, logs and charts - let lower_main = Layout::default() - .direction(Direction::Vertical) - .constraints(lower_split) - .split(upper_main[1]); - - draw_blocks::containers::draw(app_data, top_panel[0], colors, f, fd, gui_state); - - draw_blocks::logs::draw(app_data, lower_main[0], colors, f, fd, gui_state); - - draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap); - - // Draw filter bar - if let Some(rect) = whole_layout.get(2) { - draw_blocks::filter::draw(*rect, colors, f, fd); + // TODO redraw logs + if fd.show_logs { + draw_blocks::logs::draw( + app_data, + containers_logs_section[1], + colors, + f, + fd, + gui_state, + ); } if let Some(id) = fd.delete_confirm.as_ref() { @@ -400,7 +433,7 @@ fn draw_frame( } // only draw commands + charts if there are containers - if let Some(rect) = top_panel.get(1) { + if let Some(rect) = containers_commands.get(1) { draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state); // Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border) @@ -411,7 +444,7 @@ fn draw_frame( let lower = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Min(1), Constraint::Max(ports_len)]) - .split(lower_main[1]); + .split(upper_main[1]); draw_blocks::charts::draw(lower[0], colors, f, fd); draw_blocks::ports::draw(lower[1], colors, f, fd); diff --git a/src/ui/redraw.rs b/src/ui/redraw.rs index 32e21a1..4fd40d2 100644 --- a/src/ui/redraw.rs +++ b/src/ui/redraw.rs @@ -1,14 +1,14 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[derive(Debug)] -pub struct Redraw(AtomicBool); +pub struct Rerender(AtomicBool); -impl Redraw { +impl Rerender { pub const fn new() -> Self { Self(AtomicBool::new(true)) } - pub fn set_true(&self) { + pub fn update(&self) { self.0.store(true, Ordering::SeqCst); }