From 90e26c300ee0614a7e33d00ce4c3b2c1bb7b4dc8 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Fri, 14 Oct 2022 21:26:20 +0000 Subject: [PATCH 1/2] wip: gui_status, should use a hashset? --- src/app_data/mod.rs | 6 +- src/docker_data/mod.rs | 38 +++--- src/input_handler/mod.rs | 264 ++++++++++++++++++++------------------- src/main.rs | 12 +- src/ui/draw_blocks.rs | 76 ++++++----- src/ui/gui_state.rs | 23 +++- src/ui/mod.rs | 13 +- 7 files changed, 240 insertions(+), 192 deletions(-) diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index abb6f8b..1f4591e 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -15,8 +15,8 @@ pub struct AppData { error: Option, logs_parsed: bool, pub containers: StatefulList, - pub init: bool, - pub show_error: bool, + // pub init: bool, + // pub show_error: bool, sorted_by: Option<(Header, SortedOrder)>, } @@ -79,9 +79,7 @@ impl AppData { args, containers: StatefulList::new(vec![]), error: None, - init: false, logs_parsed: false, - show_error: false, sorted_by: None, } } diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index d0a3e47..635c8dd 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -19,7 +19,7 @@ use crate::{ app_data::{AppData, ContainerId, DockerControls}, app_error::AppError, parse_args::CliArgs, - ui::GuiState, + ui::{GuiState, Status}, }; mod message; pub use message::DockerMessage; @@ -336,32 +336,40 @@ impl DockerData { tokio::time::sleep(std::time::Duration::from_millis(100)).await; self.initialised = self.app_data.lock().initialised(&all_ids); } - self.app_data.lock().init = true; + // self.app_data.lock().init = true; + self.gui_state.lock().set_status(Status::Normal); self.stop_loading_spin(&loading_spin, loading_uuid); } + /// Set the global error as the dockererror, and set gui_state to errro + fn set_error(&mut self, error: DockerControls) { + self.app_data + .lock() + .set_error(AppError::DockerCommand(error)); + self.gui_state.lock().set_status(Status::Error); + } + /// Handle incoming messages, container controls & all container information update async fn message_handler(&mut self) { while let Some(message) = self.receiver.recv().await { let docker = Arc::clone(&self.docker); - let app_data = Arc::clone(&self.app_data); let loading_uuid = Uuid::new_v4(); match message { DockerMessage::Pause(id) => { let loading_spin = self.loading_spin(loading_uuid).await; if docker.pause_container(id.get()).await.is_err() { - app_data - .lock() - .set_error(AppError::DockerCommand(DockerControls::Pause)); + self.set_error(DockerControls::Pause); }; self.stop_loading_spin(&loading_spin, loading_uuid); } DockerMessage::Restart(id) => { + // DEBUG + // self.set_error(DockerControls::Restart); + // DEBUG + let loading_spin = self.loading_spin(loading_uuid).await; if docker.restart_container(id.get(), None).await.is_err() { - app_data - .lock() - .set_error(AppError::DockerCommand(DockerControls::Restart)); + self.set_error(DockerControls::Restart); }; self.stop_loading_spin(&loading_spin, loading_uuid); } @@ -372,27 +380,21 @@ impl DockerData { .await .is_err() { - app_data - .lock() - .set_error(AppError::DockerCommand(DockerControls::Start)); + self.set_error(DockerControls::Start); }; self.stop_loading_spin(&loading_spin, loading_uuid); } DockerMessage::Stop(id) => { let loading_spin = self.loading_spin(loading_uuid).await; if docker.stop_container(id.get(), None).await.is_err() { - app_data - .lock() - .set_error(AppError::DockerCommand(DockerControls::Stop)); + self.set_error(DockerControls::Stop); }; self.stop_loading_spin(&loading_spin, loading_uuid); } DockerMessage::Unpause(id) => { let loading_spin = self.loading_spin(loading_uuid).await; if docker.unpause_container(id.get()).await.is_err() { - app_data - .lock() - .set_error(AppError::DockerCommand(DockerControls::Unpause)); + self.set_error(DockerControls::Unpause); }; self.stop_loading_spin(&loading_spin, loading_uuid); self.update_everything().await; diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 91f2418..beb01ad 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -21,7 +21,7 @@ use crate::{ app_data::{AppData, DockerControls, Header, SortedOrder}, app_error::AppError, docker_data::DockerMessage, - ui::{GuiState, SelectablePanel}, + ui::{GuiState, SelectablePanel, Status}, }; pub use message::InputMessages; @@ -64,10 +64,11 @@ impl InputHandler { match message { InputMessages::ButtonPress(key_code) => self.button_press(key_code).await, InputMessages::MouseEvent(mouse_event) => { - let show_error = self.app_data.lock().show_error; - let show_info = self.gui_state.lock().show_help; - if !show_error && !show_info { - self.mouse_press(mouse_event); + // let show_error = self.app_data.lock().show_error; + let status = self.gui_state.lock().get_status(); + match status { + Status::Error | Status::Help => (), + _ => self.mouse_press(mouse_event), } } } @@ -85,10 +86,11 @@ impl InputHandler { .gui_state .lock() .set_info_box("✖ mouse capture disabled".to_owned()), - Err(_) => self - .app_data - .lock() - .set_error(AppError::MouseCapture(false)), + Err(_) => { + self.app_data + .lock() + .set_error(AppError::MouseCapture(false)); + } } } else { match execute!(std::io::stdout(), EnableMouseCapture) { @@ -96,7 +98,9 @@ impl InputHandler { .gui_state .lock() .set_info_box("✓ mouse capture enabled".to_owned()), - Err(_) => self.app_data.lock().set_error(AppError::MouseCapture(true)), + Err(_) => { + self.app_data.lock().set_error(AppError::MouseCapture(true)); + } } }; @@ -129,147 +133,155 @@ impl InputHandler { } /// Send a quit message to docker, to abort all spawns, if an error is return, set is_running to false here instead - async fn quit(&self) { - match self.docker_sender.send(DockerMessage::Quit).await { - Ok(_) => (), - Err(_) => self.is_running.store(false, Ordering::SeqCst), + async fn quit(&self, status: Status) { + match status { + Status::Error | Status::Init => self.is_running.store(false, Ordering::SeqCst), + _ => { + if self.docker_sender.send(DockerMessage::Quit).await.is_err() { + self.is_running.store(false, Ordering::SeqCst) + } + } } + + // match self.docker_sender.send(DockerMessage::Quit).await { + // Ok(_) => (), + // Err(_) => self.is_running.store(false, Ordering::SeqCst), + // } } /// Handle any keyboard button events #[allow(clippy::too_many_lines)] async fn button_press(&mut self, key_code: KeyCode) { - let show_error = self.app_data.lock().show_error; - let show_info = self.gui_state.lock().show_help; + let status = self.gui_state.lock().get_status(); - if show_error { - match key_code { - KeyCode::Char('q' | 'Q') => self.quit().await, + match status { + Status::Error => match key_code { + KeyCode::Char('q' | 'Q') => self.quit(status).await, KeyCode::Char('c' | 'C') => { - self.app_data.lock().show_error = false; self.app_data.lock().remove_error(); + self.gui_state.lock().set_status(Status::Normal); } _ => (), - } - } else if show_info { - match key_code { - KeyCode::Char('q' | 'Q') => self.quit().await, - KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = false, + }, + Status::Help => match key_code { + KeyCode::Char('q' | 'Q') => self.quit(status).await, + KeyCode::Char('h' | 'H') => self.gui_state.lock().set_status(Status::Help), KeyCode::Char('m' | 'M') => self.m_key(), _ => (), - } - } else { - match key_code { - KeyCode::Char('0') => self.app_data.lock().set_sorted(None), - KeyCode::Char('1') => self.sort(Header::State), - KeyCode::Char('2') => self.sort(Header::Status), - KeyCode::Char('3') => self.sort(Header::Cpu), - KeyCode::Char('4') => self.sort(Header::Memory), - KeyCode::Char('5') => self.sort(Header::Id), - KeyCode::Char('6') => self.sort(Header::Name), - KeyCode::Char('7') => self.sort(Header::Image), - KeyCode::Char('8') => self.sort(Header::Rx), - KeyCode::Char('9') => self.sort(Header::Tx), - KeyCode::Char('q' | 'Q') => self.quit().await, - KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = true, - KeyCode::Char('m' | 'M') => self.m_key(), - KeyCode::Tab => { - // Skip control panel if no containers, could be refactored - let has_containers = self.app_data.lock().get_container_len() == 0; - let is_containers = - self.gui_state.lock().selected_panel == SelectablePanel::Containers; - let count = if has_containers && is_containers { - 2 - } else { - 1 - }; - for _ in 0..count { - self.gui_state.lock().next_panel(); + }, + _ => { + match key_code { + KeyCode::Char('0') => self.app_data.lock().set_sorted(None), + KeyCode::Char('1') => self.sort(Header::State), + KeyCode::Char('2') => self.sort(Header::Status), + KeyCode::Char('3') => self.sort(Header::Cpu), + KeyCode::Char('4') => self.sort(Header::Memory), + KeyCode::Char('5') => self.sort(Header::Id), + KeyCode::Char('6') => self.sort(Header::Name), + KeyCode::Char('7') => self.sort(Header::Image), + KeyCode::Char('8') => self.sort(Header::Rx), + KeyCode::Char('9') => self.sort(Header::Tx), + KeyCode::Char('q' | 'Q') => self.quit(status).await, + KeyCode::Char('h' | 'H') => self.gui_state.lock().set_status(Status::Help), + KeyCode::Char('m' | 'M') => self.m_key(), + KeyCode::Tab => { + // Skip control panel if no containers, could be refactored + let has_containers = self.app_data.lock().get_container_len() == 0; + let is_containers = + self.gui_state.lock().selected_panel == SelectablePanel::Containers; + let count = if has_containers && is_containers { + 2 + } else { + 1 + }; + for _ in 0..count { + self.gui_state.lock().next_panel(); + } } - } - KeyCode::BackTab => { - // Skip control panel if no containers, could be refactored - let has_containers = self.app_data.lock().get_container_len() == 0; - let is_containers = - self.gui_state.lock().selected_panel == SelectablePanel::Logs; - let count = if has_containers && is_containers { - 2 - } else { - 1 - }; - for _ in 0..count { - self.gui_state.lock().previous_panel(); + KeyCode::BackTab => { + // Skip control panel if no containers, could be refactored + let has_containers = self.app_data.lock().get_container_len() == 0; + let is_containers = + self.gui_state.lock().selected_panel == SelectablePanel::Logs; + let count = if has_containers && is_containers { + 2 + } else { + 1 + }; + for _ in 0..count { + self.gui_state.lock().previous_panel(); + } } - } - KeyCode::Home => { - let mut locked_data = self.app_data.lock(); - match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.start(), - SelectablePanel::Logs => locked_data.log_start(), - SelectablePanel::Commands => locked_data.docker_command_start(), + KeyCode::Home => { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.start(), + SelectablePanel::Logs => locked_data.log_start(), + SelectablePanel::Commands => locked_data.docker_command_start(), + } } - } - KeyCode::End => { - let mut locked_data = self.app_data.lock(); - match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.end(), - SelectablePanel::Logs => locked_data.log_end(), - SelectablePanel::Commands => locked_data.docker_command_end(), + KeyCode::End => { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.end(), + SelectablePanel::Logs => locked_data.log_end(), + SelectablePanel::Commands => locked_data.docker_command_end(), + } } - } - KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(), - KeyCode::PageUp => { - for _ in 0..=6 { - self.previous(); + KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(), + KeyCode::PageUp => { + for _ in 0..=6 { + self.previous(); + } } - } - KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(), - KeyCode::PageDown => { - for _ in 0..=6 { - self.next(); + KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(), + KeyCode::PageDown => { + for _ in 0..=6 { + self.next(); + } } - } - KeyCode::Enter => { - // This isn't great, just means you can't send docker commands before full initialization of the program - let panel = self.gui_state.lock().selected_panel; - if panel == SelectablePanel::Commands { - let option_command = self.app_data.lock().get_docker_command(); + KeyCode::Enter => { + // This isn't great, just means you can't send docker commands before full initialization of the program + let panel = self.gui_state.lock().selected_panel; + if panel == SelectablePanel::Commands { + let option_command = self.app_data.lock().get_docker_command(); - if let Some(command) = option_command { - let option_id = self.app_data.lock().get_selected_container_id(); - if let Some(id) = option_id { - match command { - DockerControls::Pause => self - .docker_sender - .send(DockerMessage::Pause(id)) - .await - .unwrap_or(()), - DockerControls::Unpause => self - .docker_sender - .send(DockerMessage::Unpause(id)) - .await - .unwrap_or(()), - DockerControls::Start => self - .docker_sender - .send(DockerMessage::Start(id)) - .await - .unwrap_or(()), - DockerControls::Stop => self - .docker_sender - .send(DockerMessage::Stop(id)) - .await - .unwrap_or(()), - DockerControls::Restart => self - .docker_sender - .send(DockerMessage::Restart(id)) - .await - .unwrap_or(()), + if let Some(command) = option_command { + let option_id = self.app_data.lock().get_selected_container_id(); + if let Some(id) = option_id { + match command { + DockerControls::Pause => self + .docker_sender + .send(DockerMessage::Pause(id)) + .await + .unwrap_or(()), + DockerControls::Unpause => self + .docker_sender + .send(DockerMessage::Unpause(id)) + .await + .unwrap_or(()), + DockerControls::Start => self + .docker_sender + .send(DockerMessage::Start(id)) + .await + .unwrap_or(()), + DockerControls::Stop => self + .docker_sender + .send(DockerMessage::Stop(id)) + .await + .unwrap_or(()), + DockerControls::Restart => self + .docker_sender + .send(DockerMessage::Restart(id)) + .await + .unwrap_or(()), + } } } } } + _ => (), } - _ => (), } } } diff --git a/src/main.rs b/src/main.rs index ae69b15..cc97e7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ mod input_handler; mod parse_args; mod ui; -use ui::{create_ui, GuiState}; +use ui::{create_ui, GuiState, Status}; fn setup_tracing() { tracing_subscriber::fmt().with_max_level(Level::INFO).init(); @@ -59,9 +59,15 @@ async fn main() { is_running, )); } - Err(_) => app_data.lock().set_error(AppError::DockerConnect), + Err(_) => { + app_data.lock().set_error(AppError::DockerConnect); + docker_gui_state.lock().set_status(Status::DockerConnect) + } }, - Err(_) => app_data.lock().set_error(AppError::DockerConnect), + Err(_) => { + app_data.lock().set_error(AppError::DockerConnect); + docker_gui_state.lock().set_status(Status::DockerConnect) + } } let input_app_data = Arc::clone(&app_data); diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index 086dad4..4f855d5 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -15,6 +15,7 @@ use tui::{ }; use crate::app_data::{Header, SortedOrder}; +use crate::ui::Status; use crate::{ app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats}, app_error::AppError, @@ -215,37 +216,43 @@ pub fn logs( loading_icon: &str, ) { let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs); + let status = gui_state.lock().get_status(); + match status { + Status::Init => { + let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon)) + .style(Style::default()) + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area); + } - let init = app_data.lock().init; - if !init { - let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon)) - .style(Style::default()) - .block(block) - .alignment(Alignment::Center); - f.render_widget(paragraph, area); - } else if let Some(index) = index { - let items = app_data.lock().containers.items[index] - .logs - .items - .iter() - .enumerate() - .map(|i| i.1.clone()) - .collect::>(); + _ => { + if let Some(index) = index { + let items = app_data.lock().containers.items[index] + .logs + .items + .iter() + .enumerate() + .map(|i| i.1.clone()) + .collect::>(); - let items = List::new(items) - .block(block) - .highlight_symbol(ARROW) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)); - f.render_stateful_widget( - items, - area, - &mut app_data.lock().containers.items[index].logs.state, - ); - } else { - let paragraph = Paragraph::new("no logs found") - .block(block) - .alignment(Alignment::Center); - f.render_widget(paragraph, area); + let items = List::new(items) + .block(block) + .highlight_symbol(ARROW) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)); + f.render_stateful_widget( + items, + area, + &mut app_data.lock().containers.items[index].logs.state, + ); + } else { + let paragraph = Paragraph::new("no logs found") + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area); + // } + } + } } } @@ -349,7 +356,7 @@ pub fn heading_bar( gui_state: &Arc>, ) { let block = |fg: Color| Block::default().style(Style::default().bg(Color::Magenta).fg(fg)); - let info_visible = gui_state.lock().show_help; + let info_visible = gui_state.lock().get_status() == Status::Help; f.render_widget(block(Color::Black), area); @@ -430,7 +437,12 @@ pub fn heading_bar( .collect::>(); let suffix = if info_visible { "exit" } else { "show" }; - let info_text = format!("( h ) {} help {}", suffix, MARGIN); + let info_text = format!( + "( h ) {} help {} {:?}", + suffix, + MARGIN, + gui_state.lock().get_status() + ); let info_width = info_text.chars().count(); let column_width = usize::from(area.width) - info_width; @@ -457,8 +469,6 @@ pub fn heading_bar( .alignment(Alignment::Center); f.render_widget(loading_paragraph, split_bar[0]); - - let container_splits = header_data.iter().map(|i| i.2).collect::>(); let headers_section = Layout::default() .direction(Direction::Horizontal) diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index 9279d19..e504095 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -173,6 +173,17 @@ impl fmt::Display for Loading { } } +/// The application can be in these four states +/// Various functions (e.g input handler), operate differently depending upon current Status +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum Status { + Init, + Help, + DockerConnect, + Error, + Normal, +} + /// Global gui_state, stored in an Arc #[derive(Debug, Clone)] pub struct GuiState { @@ -180,8 +191,8 @@ pub struct GuiState { heading_map: HashMap, loading_icon: Loading, is_loading: HashSet, + status: Status, pub selected_panel: SelectablePanel, - pub show_help: bool, pub info_box_text: Option, } impl GuiState { @@ -192,9 +203,9 @@ impl GuiState { heading_map: HashMap::new(), loading_icon: Loading::One, selected_panel: SelectablePanel::Containers, - show_help: false, is_loading: HashSet::new(), info_box_text: None, + status: Status::Init, } } @@ -242,6 +253,14 @@ impl GuiState { }; } + pub fn set_status(&mut self, status: Status) { + self.status = status + } + + pub fn get_status(&self) -> Status { + self.status + } + /// Change to next selectable panel pub fn next_panel(&mut self) { self.selected_panel = self.selected_panel.next(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ff54d2d..8b414ac 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -25,7 +25,7 @@ mod draw_blocks; mod gui_state; pub use self::color_match::*; -pub use self::gui_state::{GuiState, SelectablePanel}; +pub use self::gui_state::{GuiState, SelectablePanel, Status}; use crate::{ app_data::AppData, app_error::AppError, docker_data::DockerMessage, input_handler::InputMessages, @@ -56,7 +56,7 @@ pub async fn create_ui( update_duration, ) .await; - terminal.clear()?; + terminal.clear()?; disable_raw_mode()?; execute!( @@ -85,8 +85,9 @@ async fn run_app( let input_poll_rate = std::time::Duration::from_millis(75); // Check for docker connect errors before attempting to draw the gui - let e = app_data.lock().get_error(); - if let Some(AppError::DockerConnect) = e { + let status = gui_state.lock().get_status(); + + if status == Status::DockerConnect { let mut seconds = 5; loop { if seconds < 1 { @@ -138,6 +139,7 @@ async fn run_app( break; } } + // } } Ok(()) } @@ -157,7 +159,7 @@ fn ui( let log_index = app_data.lock().get_selected_log_index(); let sorted_by = app_data.lock().get_sorted(); - let show_help = gui_state.lock().show_help; + let show_help = gui_state.lock().get_status() == Status::Help; let info_text = gui_state.lock().info_box_text.clone(); let loading_icon = gui_state.lock().get_loading(); @@ -241,7 +243,6 @@ fn ui( } if let Some(error) = has_error { - app_data.lock().show_error = true; draw_blocks::error(f, error, None); } } From 9e9d51559a13944622abf4fcbd3bd63766d11467 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:23:26 +0000 Subject: [PATCH 2/2] feat: use gui_status for various gui states --- src/app_data/mod.rs | 4 +- src/docker_data/mod.rs | 87 +++++++------ src/input_handler/mod.rs | 256 +++++++++++++++++++-------------------- src/main.rs | 4 +- src/ui/draw_blocks.rs | 82 ++++++------- src/ui/gui_state.rs | 17 +-- src/ui/mod.rs | 6 +- 7 files changed, 227 insertions(+), 229 deletions(-) diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 1f4591e..3821f99 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -14,10 +14,8 @@ pub struct AppData { args: CliArgs, error: Option, logs_parsed: bool, - pub containers: StatefulList, - // pub init: bool, - // pub show_error: bool, sorted_by: Option<(Header, SortedOrder)>, + pub containers: StatefulList, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 635c8dd..ed1d4d5 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -3,7 +3,7 @@ use bollard::{ service::ContainerSummary, Docker, }; -use futures_util::StreamExt; +use futures_util::{Future, StreamExt}; use parking_lot::Mutex; use std::{ collections::HashMap, @@ -318,6 +318,7 @@ impl DockerData { // Initialize docker container data, before any messages are received async fn initialise_container_data(&mut self) { + self.gui_state.lock().status_push(Status::Init); let loading_uuid = Uuid::new_v4(); let loading_spin = self.loading_spin(loading_uuid).await; @@ -336,8 +337,7 @@ impl DockerData { tokio::time::sleep(std::time::Duration::from_millis(100)).await; self.initialised = self.app_data.lock().initialised(&all_ids); } - // self.app_data.lock().init = true; - self.gui_state.lock().set_status(Status::Normal); + self.gui_state.lock().status_del(Status::Init); self.stop_loading_spin(&loading_spin, loading_uuid); } @@ -346,57 +346,68 @@ impl DockerData { self.app_data .lock() .set_error(AppError::DockerCommand(error)); - self.gui_state.lock().set_status(Status::Error); + self.gui_state.lock().status_push(Status::Error); + } + + /// Execute docker commands, will start and stop the loading spinner + async fn exec_docker( + &mut self, + docker_fn: impl Future>, + uuid: Uuid, + control: DockerControls, + ) { + let loading_spin = self.loading_spin(uuid).await; + if docker_fn.await.is_err() { + self.set_error(control); + }; + self.stop_loading_spin(&loading_spin, uuid); } /// Handle incoming messages, container controls & all container information update async fn message_handler(&mut self) { while let Some(message) = self.receiver.recv().await { - let docker = Arc::clone(&self.docker); let loading_uuid = Uuid::new_v4(); + let docker = Arc::clone(&self.docker); match message { DockerMessage::Pause(id) => { - let loading_spin = self.loading_spin(loading_uuid).await; - if docker.pause_container(id.get()).await.is_err() { - self.set_error(DockerControls::Pause); - }; - self.stop_loading_spin(&loading_spin, loading_uuid); + self.exec_docker( + docker.pause_container(id.get()), + loading_uuid, + DockerControls::Pause, + ) + .await; } DockerMessage::Restart(id) => { - // DEBUG - // self.set_error(DockerControls::Restart); - // DEBUG - - let loading_spin = self.loading_spin(loading_uuid).await; - if docker.restart_container(id.get(), None).await.is_err() { - self.set_error(DockerControls::Restart); - }; - self.stop_loading_spin(&loading_spin, loading_uuid); + self.exec_docker( + docker.restart_container(id.get(), None), + loading_uuid, + DockerControls::Restart, + ) + .await; } DockerMessage::Start(id) => { - let loading_spin = self.loading_spin(loading_uuid).await; - if docker - .start_container(id.get(), None::>) - .await - .is_err() - { - self.set_error(DockerControls::Start); - }; - self.stop_loading_spin(&loading_spin, loading_uuid); + self.exec_docker( + docker.start_container(id.get(), None::>), + loading_uuid, + DockerControls::Start, + ) + .await; } DockerMessage::Stop(id) => { - let loading_spin = self.loading_spin(loading_uuid).await; - if docker.stop_container(id.get(), None).await.is_err() { - self.set_error(DockerControls::Stop); - }; - self.stop_loading_spin(&loading_spin, loading_uuid); + self.exec_docker( + docker.stop_container(id.get(), None), + loading_uuid, + DockerControls::Stop, + ) + .await; } DockerMessage::Unpause(id) => { - let loading_spin = self.loading_spin(loading_uuid).await; - if docker.unpause_container(id.get()).await.is_err() { - self.set_error(DockerControls::Unpause); - }; - self.stop_loading_spin(&loading_spin, loading_uuid); + self.exec_docker( + docker.unpause_container(id.get()), + loading_uuid, + DockerControls::Unpause, + ) + .await; self.update_everything().await; } DockerMessage::Update => self.update_everything().await, diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index beb01ad..9cc6208 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -64,11 +64,12 @@ impl InputHandler { match message { InputMessages::ButtonPress(key_code) => self.button_press(key_code).await, InputMessages::MouseEvent(mouse_event) => { - // let show_error = self.app_data.lock().show_error; - let status = self.gui_state.lock().get_status(); - match status { - Status::Error | Status::Help => (), - _ => self.mouse_press(mouse_event), + let error_or_help = self + .gui_state + .lock() + .status_contains(&[Status::Error, Status::Help]); + if !error_or_help { + self.mouse_press(mouse_event) } } } @@ -133,155 +134,152 @@ impl InputHandler { } /// Send a quit message to docker, to abort all spawns, if an error is return, set is_running to false here instead - async fn quit(&self, status: Status) { - match status { - Status::Error | Status::Init => self.is_running.store(false, Ordering::SeqCst), - _ => { - if self.docker_sender.send(DockerMessage::Quit).await.is_err() { - self.is_running.store(false, Ordering::SeqCst) - } - } + async fn quit(&self) { + let error_init = self + .gui_state + .lock() + .status_contains(&[Status::Error, Status::Init]); + if error_init { + self.is_running.store(false, Ordering::SeqCst); + } else if self.docker_sender.send(DockerMessage::Quit).await.is_err() { + self.is_running.store(false, Ordering::SeqCst) } - - // match self.docker_sender.send(DockerMessage::Quit).await { - // Ok(_) => (), - // Err(_) => self.is_running.store(false, Ordering::SeqCst), - // } } /// Handle any keyboard button events #[allow(clippy::too_many_lines)] async fn button_press(&mut self, key_code: KeyCode) { - let status = self.gui_state.lock().get_status(); + let contains_error = self.gui_state.lock().status_contains(&[Status::Error]); + let contains_help = self.gui_state.lock().status_contains(&[Status::Help]); - match status { - Status::Error => match key_code { - KeyCode::Char('q' | 'Q') => self.quit(status).await, + if contains_error { + match key_code { + KeyCode::Char('q' | 'Q') => self.quit().await, KeyCode::Char('c' | 'C') => { self.app_data.lock().remove_error(); - self.gui_state.lock().set_status(Status::Normal); + self.gui_state.lock().status_del(Status::Error); } _ => (), - }, - Status::Help => match key_code { - KeyCode::Char('q' | 'Q') => self.quit(status).await, - KeyCode::Char('h' | 'H') => self.gui_state.lock().set_status(Status::Help), + } + } else if contains_help { + match key_code { + KeyCode::Char('q' | 'Q') => self.quit().await, + KeyCode::Char('h' | 'H') => self.gui_state.lock().status_del(Status::Help), KeyCode::Char('m' | 'M') => self.m_key(), _ => (), - }, - _ => { - match key_code { - KeyCode::Char('0') => self.app_data.lock().set_sorted(None), - KeyCode::Char('1') => self.sort(Header::State), - KeyCode::Char('2') => self.sort(Header::Status), - KeyCode::Char('3') => self.sort(Header::Cpu), - KeyCode::Char('4') => self.sort(Header::Memory), - KeyCode::Char('5') => self.sort(Header::Id), - KeyCode::Char('6') => self.sort(Header::Name), - KeyCode::Char('7') => self.sort(Header::Image), - KeyCode::Char('8') => self.sort(Header::Rx), - KeyCode::Char('9') => self.sort(Header::Tx), - KeyCode::Char('q' | 'Q') => self.quit(status).await, - KeyCode::Char('h' | 'H') => self.gui_state.lock().set_status(Status::Help), - KeyCode::Char('m' | 'M') => self.m_key(), - KeyCode::Tab => { - // Skip control panel if no containers, could be refactored - let has_containers = self.app_data.lock().get_container_len() == 0; - let is_containers = - self.gui_state.lock().selected_panel == SelectablePanel::Containers; - let count = if has_containers && is_containers { - 2 - } else { - 1 - }; - for _ in 0..count { - self.gui_state.lock().next_panel(); - } + } + } else { + match key_code { + KeyCode::Char('0') => self.app_data.lock().set_sorted(None), + KeyCode::Char('1') => self.sort(Header::State), + KeyCode::Char('2') => self.sort(Header::Status), + KeyCode::Char('3') => self.sort(Header::Cpu), + KeyCode::Char('4') => self.sort(Header::Memory), + KeyCode::Char('5') => self.sort(Header::Id), + KeyCode::Char('6') => self.sort(Header::Name), + KeyCode::Char('7') => self.sort(Header::Image), + KeyCode::Char('8') => self.sort(Header::Rx), + KeyCode::Char('9') => self.sort(Header::Tx), + KeyCode::Char('q' | 'Q') => self.quit().await, + KeyCode::Char('h' | 'H') => self.gui_state.lock().status_push(Status::Help), + KeyCode::Char('m' | 'M') => self.m_key(), + KeyCode::Tab => { + // Skip control panel if no containers, could be refactored + let has_containers = self.app_data.lock().get_container_len() == 0; + let is_containers = + self.gui_state.lock().selected_panel == SelectablePanel::Containers; + let count = if has_containers && is_containers { + 2 + } else { + 1 + }; + for _ in 0..count { + self.gui_state.lock().next_panel(); } - KeyCode::BackTab => { - // Skip control panel if no containers, could be refactored - let has_containers = self.app_data.lock().get_container_len() == 0; - let is_containers = - self.gui_state.lock().selected_panel == SelectablePanel::Logs; - let count = if has_containers && is_containers { - 2 - } else { - 1 - }; - for _ in 0..count { - self.gui_state.lock().previous_panel(); - } + } + KeyCode::BackTab => { + // Skip control panel if no containers, could be refactored + let has_containers = self.app_data.lock().get_container_len() == 0; + let is_containers = + self.gui_state.lock().selected_panel == SelectablePanel::Logs; + let count = if has_containers && is_containers { + 2 + } else { + 1 + }; + for _ in 0..count { + self.gui_state.lock().previous_panel(); } - KeyCode::Home => { - let mut locked_data = self.app_data.lock(); - match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.start(), - SelectablePanel::Logs => locked_data.log_start(), - SelectablePanel::Commands => locked_data.docker_command_start(), - } + } + KeyCode::Home => { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.start(), + SelectablePanel::Logs => locked_data.log_start(), + SelectablePanel::Commands => locked_data.docker_command_start(), } - KeyCode::End => { - let mut locked_data = self.app_data.lock(); - match self.gui_state.lock().selected_panel { - SelectablePanel::Containers => locked_data.containers.end(), - SelectablePanel::Logs => locked_data.log_end(), - SelectablePanel::Commands => locked_data.docker_command_end(), - } + } + KeyCode::End => { + let mut locked_data = self.app_data.lock(); + match self.gui_state.lock().selected_panel { + SelectablePanel::Containers => locked_data.containers.end(), + SelectablePanel::Logs => locked_data.log_end(), + SelectablePanel::Commands => locked_data.docker_command_end(), } - KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(), - KeyCode::PageUp => { - for _ in 0..=6 { - self.previous(); - } + } + KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(), + KeyCode::PageUp => { + for _ in 0..=6 { + self.previous(); } - KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(), - KeyCode::PageDown => { - for _ in 0..=6 { - self.next(); - } + } + KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(), + KeyCode::PageDown => { + for _ in 0..=6 { + self.next(); } - KeyCode::Enter => { - // This isn't great, just means you can't send docker commands before full initialization of the program - let panel = self.gui_state.lock().selected_panel; - if panel == SelectablePanel::Commands { - let option_command = self.app_data.lock().get_docker_command(); + } + KeyCode::Enter => { + // This isn't great, just means you can't send docker commands before full initialization of the program + let panel = self.gui_state.lock().selected_panel; + if panel == SelectablePanel::Commands { + let option_command = self.app_data.lock().get_docker_command(); - if let Some(command) = option_command { - let option_id = self.app_data.lock().get_selected_container_id(); - if let Some(id) = option_id { - match command { - DockerControls::Pause => self - .docker_sender - .send(DockerMessage::Pause(id)) - .await - .unwrap_or(()), - DockerControls::Unpause => self - .docker_sender - .send(DockerMessage::Unpause(id)) - .await - .unwrap_or(()), - DockerControls::Start => self - .docker_sender - .send(DockerMessage::Start(id)) - .await - .unwrap_or(()), - DockerControls::Stop => self - .docker_sender - .send(DockerMessage::Stop(id)) - .await - .unwrap_or(()), - DockerControls::Restart => self - .docker_sender - .send(DockerMessage::Restart(id)) - .await - .unwrap_or(()), - } + if let Some(command) = option_command { + let option_id = self.app_data.lock().get_selected_container_id(); + if let Some(id) = option_id { + match command { + DockerControls::Pause => self + .docker_sender + .send(DockerMessage::Pause(id)) + .await + .unwrap_or(()), + DockerControls::Unpause => self + .docker_sender + .send(DockerMessage::Unpause(id)) + .await + .unwrap_or(()), + DockerControls::Start => self + .docker_sender + .send(DockerMessage::Start(id)) + .await + .unwrap_or(()), + DockerControls::Stop => self + .docker_sender + .send(DockerMessage::Stop(id)) + .await + .unwrap_or(()), + DockerControls::Restart => self + .docker_sender + .send(DockerMessage::Restart(id)) + .await + .unwrap_or(()), } } } } - _ => (), } + _ => (), } } } diff --git a/src/main.rs b/src/main.rs index cc97e7c..9d03204 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,12 +61,12 @@ async fn main() { } Err(_) => { app_data.lock().set_error(AppError::DockerConnect); - docker_gui_state.lock().set_status(Status::DockerConnect) + docker_gui_state.lock().status_push(Status::DockerConnect) } }, Err(_) => { app_data.lock().set_error(AppError::DockerConnect); - docker_gui_state.lock().set_status(Status::DockerConnect) + docker_gui_state.lock().status_push(Status::DockerConnect) } } let input_app_data = Arc::clone(&app_data); diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index 4f855d5..e827cdb 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -216,43 +216,36 @@ pub fn logs( loading_icon: &str, ) { let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs); - let status = gui_state.lock().get_status(); - match status { - Status::Init => { - let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon)) - .style(Style::default()) - .block(block) - .alignment(Alignment::Center); - f.render_widget(paragraph, area); - } + let contains_init = gui_state.lock().status_contains(&[Status::Init]); + if contains_init { + let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon)) + .style(Style::default()) + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area); + } else if let Some(index) = index { + let items = app_data.lock().containers.items[index] + .logs + .items + .iter() + .enumerate() + .map(|i| i.1.clone()) + .collect::>(); - _ => { - if let Some(index) = index { - let items = app_data.lock().containers.items[index] - .logs - .items - .iter() - .enumerate() - .map(|i| i.1.clone()) - .collect::>(); - - let items = List::new(items) - .block(block) - .highlight_symbol(ARROW) - .highlight_style(Style::default().add_modifier(Modifier::BOLD)); - f.render_stateful_widget( - items, - area, - &mut app_data.lock().containers.items[index].logs.state, - ); - } else { - let paragraph = Paragraph::new("no logs found") - .block(block) - .alignment(Alignment::Center); - f.render_widget(paragraph, area); - // } - } - } + let items = List::new(items) + .block(block) + .highlight_symbol(ARROW) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)); + f.render_stateful_widget( + items, + area, + &mut app_data.lock().containers.items[index].logs.state, + ); + } else { + let paragraph = Paragraph::new("no logs found") + .block(block) + .alignment(Alignment::Center); + f.render_widget(paragraph, area); } } @@ -356,7 +349,7 @@ pub fn heading_bar( gui_state: &Arc>, ) { let block = |fg: Color| Block::default().style(Style::default().bg(Color::Magenta).fg(fg)); - let info_visible = gui_state.lock().get_status() == Status::Help; + let help_visible = gui_state.lock().status_contains(&[Status::Help]); f.render_widget(block(Color::Black), area); @@ -436,13 +429,8 @@ pub fn heading_bar( }) .collect::>(); - let suffix = if info_visible { "exit" } else { "show" }; - let info_text = format!( - "( h ) {} help {} {:?}", - suffix, - MARGIN, - gui_state.lock().get_status() - ); + let suffix = if help_visible { "exit" } else { "show" }; + let info_text = format!("( h ) {} help {}", suffix, MARGIN,); let info_width = info_text.chars().count(); let column_width = usize::from(area.width) - info_width; @@ -484,10 +472,10 @@ pub fn heading_bar( } // show/hide help - let color = if info_visible { - Color::White - } else { + let color = if help_visible { Color::Black + } else { + Color::White }; let help_paragraph = Paragraph::new(info_text) .block(block(color)) diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index e504095..8a9bc93 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -181,7 +181,6 @@ pub enum Status { Help, DockerConnect, Error, - Normal, } /// Global gui_state, stored in an Arc @@ -191,7 +190,7 @@ pub struct GuiState { heading_map: HashMap, loading_icon: Loading, is_loading: HashSet, - status: Status, + status: HashSet, pub selected_panel: SelectablePanel, pub info_box_text: Option, } @@ -205,7 +204,7 @@ impl GuiState { selected_panel: SelectablePanel::Containers, is_loading: HashSet::new(), info_box_text: None, - status: Status::Init, + status: HashSet::new(), } } @@ -253,12 +252,16 @@ impl GuiState { }; } - pub fn set_status(&mut self, status: Status) { - self.status = status + pub fn status_push(&mut self, status: Status) { + self.status.insert(status); } - pub fn get_status(&self) -> Status { - self.status + pub fn status_del(&mut self, status: Status) { + self.status.remove(&status); + } + + pub fn status_contains(&self, status: &[Status]) -> bool { + status.iter().any(|i| self.status.contains(i)) } /// Change to next selectable panel diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 8b414ac..e93a671 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -85,9 +85,9 @@ async fn run_app( let input_poll_rate = std::time::Duration::from_millis(75); // Check for docker connect errors before attempting to draw the gui - let status = gui_state.lock().get_status(); + let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]); - if status == Status::DockerConnect { + if status_dockerconnect { let mut seconds = 5; loop { if seconds < 1 { @@ -159,7 +159,7 @@ fn ui( let log_index = app_data.lock().get_selected_log_index(); let sorted_by = app_data.lock().get_sorted(); - let show_help = gui_state.lock().get_status() == Status::Help; + let show_help = gui_state.lock().status_contains(&[Status::Help]); let info_text = gui_state.lock().info_box_text.clone(); let loading_icon = gui_state.lock().get_loading();