feat: use gui_status for various gui states

This commit is contained in:
Jack Wills
2022-10-15 00:23:26 +00:00
parent 90e26c300e
commit 9e9d51559a
7 changed files with 227 additions and 229 deletions
+1 -3
View File
@@ -14,10 +14,8 @@ pub struct AppData {
args: CliArgs, args: CliArgs,
error: Option<AppError>, error: Option<AppError>,
logs_parsed: bool, logs_parsed: bool,
pub containers: StatefulList<ContainerItem>,
// pub init: bool,
// pub show_error: bool,
sorted_by: Option<(Header, SortedOrder)>, sorted_by: Option<(Header, SortedOrder)>,
pub containers: StatefulList<ContainerItem>,
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
+49 -38
View File
@@ -3,7 +3,7 @@ use bollard::{
service::ContainerSummary, service::ContainerSummary,
Docker, Docker,
}; };
use futures_util::StreamExt; use futures_util::{Future, StreamExt};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@@ -318,6 +318,7 @@ impl DockerData {
// Initialize docker container data, before any messages are received // Initialize docker container data, before any messages are received
async fn initialise_container_data(&mut self) { async fn initialise_container_data(&mut self) {
self.gui_state.lock().status_push(Status::Init);
let loading_uuid = Uuid::new_v4(); let loading_uuid = Uuid::new_v4();
let loading_spin = self.loading_spin(loading_uuid).await; 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; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
self.initialised = self.app_data.lock().initialised(&all_ids); self.initialised = self.app_data.lock().initialised(&all_ids);
} }
// self.app_data.lock().init = true; self.gui_state.lock().status_del(Status::Init);
self.gui_state.lock().set_status(Status::Normal);
self.stop_loading_spin(&loading_spin, loading_uuid); self.stop_loading_spin(&loading_spin, loading_uuid);
} }
@@ -346,57 +346,68 @@ impl DockerData {
self.app_data self.app_data
.lock() .lock()
.set_error(AppError::DockerCommand(error)); .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<Output = Result<(), bollard::errors::Error>>,
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 /// Handle incoming messages, container controls & all container information update
async fn message_handler(&mut self) { async fn message_handler(&mut self) {
while let Some(message) = self.receiver.recv().await { while let Some(message) = self.receiver.recv().await {
let docker = Arc::clone(&self.docker);
let loading_uuid = Uuid::new_v4(); let loading_uuid = Uuid::new_v4();
let docker = Arc::clone(&self.docker);
match message { match message {
DockerMessage::Pause(id) => { DockerMessage::Pause(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(
if docker.pause_container(id.get()).await.is_err() { docker.pause_container(id.get()),
self.set_error(DockerControls::Pause); loading_uuid,
}; DockerControls::Pause,
self.stop_loading_spin(&loading_spin, loading_uuid); )
.await;
} }
DockerMessage::Restart(id) => { DockerMessage::Restart(id) => {
// DEBUG self.exec_docker(
// self.set_error(DockerControls::Restart); docker.restart_container(id.get(), None),
// DEBUG loading_uuid,
DockerControls::Restart,
let loading_spin = self.loading_spin(loading_uuid).await; )
if docker.restart_container(id.get(), None).await.is_err() { .await;
self.set_error(DockerControls::Restart);
};
self.stop_loading_spin(&loading_spin, loading_uuid);
} }
DockerMessage::Start(id) => { DockerMessage::Start(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(
if docker docker.start_container(id.get(), None::<StartContainerOptions<String>>),
.start_container(id.get(), None::<StartContainerOptions<String>>) loading_uuid,
.await DockerControls::Start,
.is_err() )
{ .await;
self.set_error(DockerControls::Start);
};
self.stop_loading_spin(&loading_spin, loading_uuid);
} }
DockerMessage::Stop(id) => { DockerMessage::Stop(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(
if docker.stop_container(id.get(), None).await.is_err() { docker.stop_container(id.get(), None),
self.set_error(DockerControls::Stop); loading_uuid,
}; DockerControls::Stop,
self.stop_loading_spin(&loading_spin, loading_uuid); )
.await;
} }
DockerMessage::Unpause(id) => { DockerMessage::Unpause(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(
if docker.unpause_container(id.get()).await.is_err() { docker.unpause_container(id.get()),
self.set_error(DockerControls::Unpause); loading_uuid,
}; DockerControls::Unpause,
self.stop_loading_spin(&loading_spin, loading_uuid); )
.await;
self.update_everything().await; self.update_everything().await;
} }
DockerMessage::Update => self.update_everything().await, DockerMessage::Update => self.update_everything().await,
+127 -129
View File
@@ -64,11 +64,12 @@ impl InputHandler {
match message { match message {
InputMessages::ButtonPress(key_code) => self.button_press(key_code).await, InputMessages::ButtonPress(key_code) => self.button_press(key_code).await,
InputMessages::MouseEvent(mouse_event) => { InputMessages::MouseEvent(mouse_event) => {
// let show_error = self.app_data.lock().show_error; let error_or_help = self
let status = self.gui_state.lock().get_status(); .gui_state
match status { .lock()
Status::Error | Status::Help => (), .status_contains(&[Status::Error, Status::Help]);
_ => self.mouse_press(mouse_event), 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 /// 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) { async fn quit(&self) {
match status { let error_init = self
Status::Error | Status::Init => self.is_running.store(false, Ordering::SeqCst), .gui_state
_ => { .lock()
if self.docker_sender.send(DockerMessage::Quit).await.is_err() { .status_contains(&[Status::Error, Status::Init]);
self.is_running.store(false, Ordering::SeqCst) 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 /// Handle any keyboard button events
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
async fn button_press(&mut self, key_code: KeyCode) { 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 { if contains_error {
Status::Error => match key_code { match key_code {
KeyCode::Char('q' | 'Q') => self.quit(status).await, KeyCode::Char('q' | 'Q') => self.quit().await,
KeyCode::Char('c' | 'C') => { KeyCode::Char('c' | 'C') => {
self.app_data.lock().remove_error(); 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 { } else if contains_help {
KeyCode::Char('q' | 'Q') => self.quit(status).await, match key_code {
KeyCode::Char('h' | 'H') => self.gui_state.lock().set_status(Status::Help), 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(), KeyCode::Char('m' | 'M') => self.m_key(),
_ => (), _ => (),
}, }
_ => { } else {
match key_code { match key_code {
KeyCode::Char('0') => self.app_data.lock().set_sorted(None), KeyCode::Char('0') => self.app_data.lock().set_sorted(None),
KeyCode::Char('1') => self.sort(Header::State), KeyCode::Char('1') => self.sort(Header::State),
KeyCode::Char('2') => self.sort(Header::Status), KeyCode::Char('2') => self.sort(Header::Status),
KeyCode::Char('3') => self.sort(Header::Cpu), KeyCode::Char('3') => self.sort(Header::Cpu),
KeyCode::Char('4') => self.sort(Header::Memory), KeyCode::Char('4') => self.sort(Header::Memory),
KeyCode::Char('5') => self.sort(Header::Id), KeyCode::Char('5') => self.sort(Header::Id),
KeyCode::Char('6') => self.sort(Header::Name), KeyCode::Char('6') => self.sort(Header::Name),
KeyCode::Char('7') => self.sort(Header::Image), KeyCode::Char('7') => self.sort(Header::Image),
KeyCode::Char('8') => self.sort(Header::Rx), KeyCode::Char('8') => self.sort(Header::Rx),
KeyCode::Char('9') => self.sort(Header::Tx), KeyCode::Char('9') => self.sort(Header::Tx),
KeyCode::Char('q' | 'Q') => self.quit(status).await, KeyCode::Char('q' | 'Q') => self.quit().await,
KeyCode::Char('h' | 'H') => self.gui_state.lock().set_status(Status::Help), KeyCode::Char('h' | 'H') => self.gui_state.lock().status_push(Status::Help),
KeyCode::Char('m' | 'M') => self.m_key(), KeyCode::Char('m' | 'M') => self.m_key(),
KeyCode::Tab => { KeyCode::Tab => {
// Skip control panel if no containers, could be refactored // Skip control panel if no containers, could be refactored
let has_containers = self.app_data.lock().get_container_len() == 0; let has_containers = self.app_data.lock().get_container_len() == 0;
let is_containers = let is_containers =
self.gui_state.lock().selected_panel == SelectablePanel::Containers; self.gui_state.lock().selected_panel == SelectablePanel::Containers;
let count = if has_containers && is_containers { let count = if has_containers && is_containers {
2 2
} else { } else {
1 1
}; };
for _ in 0..count { for _ in 0..count {
self.gui_state.lock().next_panel(); self.gui_state.lock().next_panel();
}
} }
KeyCode::BackTab => { }
// Skip control panel if no containers, could be refactored KeyCode::BackTab => {
let has_containers = self.app_data.lock().get_container_len() == 0; // Skip control panel if no containers, could be refactored
let is_containers = let has_containers = self.app_data.lock().get_container_len() == 0;
self.gui_state.lock().selected_panel == SelectablePanel::Logs; let is_containers =
let count = if has_containers && is_containers { self.gui_state.lock().selected_panel == SelectablePanel::Logs;
2 let count = if has_containers && is_containers {
} else { 2
1 } else {
}; 1
for _ in 0..count { };
self.gui_state.lock().previous_panel(); for _ in 0..count {
} self.gui_state.lock().previous_panel();
} }
KeyCode::Home => { }
let mut locked_data = self.app_data.lock(); KeyCode::Home => {
match self.gui_state.lock().selected_panel { let mut locked_data = self.app_data.lock();
SelectablePanel::Containers => locked_data.containers.start(), match self.gui_state.lock().selected_panel {
SelectablePanel::Logs => locked_data.log_start(), SelectablePanel::Containers => locked_data.containers.start(),
SelectablePanel::Commands => locked_data.docker_command_start(), SelectablePanel::Logs => locked_data.log_start(),
} SelectablePanel::Commands => locked_data.docker_command_start(),
} }
KeyCode::End => { }
let mut locked_data = self.app_data.lock(); KeyCode::End => {
match self.gui_state.lock().selected_panel { let mut locked_data = self.app_data.lock();
SelectablePanel::Containers => locked_data.containers.end(), match self.gui_state.lock().selected_panel {
SelectablePanel::Logs => locked_data.log_end(), SelectablePanel::Containers => locked_data.containers.end(),
SelectablePanel::Commands => locked_data.docker_command_end(), SelectablePanel::Logs => locked_data.log_end(),
} SelectablePanel::Commands => locked_data.docker_command_end(),
} }
KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(), }
KeyCode::PageUp => { KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(),
for _ in 0..=6 { KeyCode::PageUp => {
self.previous(); for _ in 0..=6 {
} self.previous();
} }
KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(), }
KeyCode::PageDown => { KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(),
for _ in 0..=6 { KeyCode::PageDown => {
self.next(); 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 KeyCode::Enter => {
let panel = self.gui_state.lock().selected_panel; // This isn't great, just means you can't send docker commands before full initialization of the program
if panel == SelectablePanel::Commands { let panel = self.gui_state.lock().selected_panel;
let option_command = self.app_data.lock().get_docker_command(); if panel == SelectablePanel::Commands {
let option_command = self.app_data.lock().get_docker_command();
if let Some(command) = option_command { if let Some(command) = option_command {
let option_id = self.app_data.lock().get_selected_container_id(); let option_id = self.app_data.lock().get_selected_container_id();
if let Some(id) = option_id { if let Some(id) = option_id {
match command { match command {
DockerControls::Pause => self DockerControls::Pause => self
.docker_sender .docker_sender
.send(DockerMessage::Pause(id)) .send(DockerMessage::Pause(id))
.await .await
.unwrap_or(()), .unwrap_or(()),
DockerControls::Unpause => self DockerControls::Unpause => self
.docker_sender .docker_sender
.send(DockerMessage::Unpause(id)) .send(DockerMessage::Unpause(id))
.await .await
.unwrap_or(()), .unwrap_or(()),
DockerControls::Start => self DockerControls::Start => self
.docker_sender .docker_sender
.send(DockerMessage::Start(id)) .send(DockerMessage::Start(id))
.await .await
.unwrap_or(()), .unwrap_or(()),
DockerControls::Stop => self DockerControls::Stop => self
.docker_sender .docker_sender
.send(DockerMessage::Stop(id)) .send(DockerMessage::Stop(id))
.await .await
.unwrap_or(()), .unwrap_or(()),
DockerControls::Restart => self DockerControls::Restart => self
.docker_sender .docker_sender
.send(DockerMessage::Restart(id)) .send(DockerMessage::Restart(id))
.await .await
.unwrap_or(()), .unwrap_or(()),
}
} }
} }
} }
} }
_ => (),
} }
_ => (),
} }
} }
} }
+2 -2
View File
@@ -61,12 +61,12 @@ async fn main() {
} }
Err(_) => { Err(_) => {
app_data.lock().set_error(AppError::DockerConnect); app_data.lock().set_error(AppError::DockerConnect);
docker_gui_state.lock().set_status(Status::DockerConnect) docker_gui_state.lock().status_push(Status::DockerConnect)
} }
}, },
Err(_) => { Err(_) => {
app_data.lock().set_error(AppError::DockerConnect); 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); let input_app_data = Arc::clone(&app_data);
+35 -47
View File
@@ -216,43 +216,36 @@ pub fn logs<B: Backend>(
loading_icon: &str, loading_icon: &str,
) { ) {
let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs); let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs);
let status = gui_state.lock().get_status(); let contains_init = gui_state.lock().status_contains(&[Status::Init]);
match status { if contains_init {
Status::Init => { let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon))
let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon)) .style(Style::default())
.style(Style::default()) .block(block)
.block(block) .alignment(Alignment::Center);
.alignment(Alignment::Center); f.render_widget(paragraph, area);
f.render_widget(paragraph, area); } else if let Some(index) = index {
} let items = app_data.lock().containers.items[index]
.logs
.items
.iter()
.enumerate()
.map(|i| i.1.clone())
.collect::<Vec<_>>();
_ => { let items = List::new(items)
if let Some(index) = index { .block(block)
let items = app_data.lock().containers.items[index] .highlight_symbol(ARROW)
.logs .highlight_style(Style::default().add_modifier(Modifier::BOLD));
.items f.render_stateful_widget(
.iter() items,
.enumerate() area,
.map(|i| i.1.clone()) &mut app_data.lock().containers.items[index].logs.state,
.collect::<Vec<_>>(); );
} else {
let items = List::new(items) let paragraph = Paragraph::new("no logs found")
.block(block) .block(block)
.highlight_symbol(ARROW) .alignment(Alignment::Center);
.highlight_style(Style::default().add_modifier(Modifier::BOLD)); f.render_widget(paragraph, area);
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<B: Backend>(
gui_state: &Arc<Mutex<GuiState>>, gui_state: &Arc<Mutex<GuiState>>,
) { ) {
let block = |fg: Color| Block::default().style(Style::default().bg(Color::Magenta).fg(fg)); 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); f.render_widget(block(Color::Black), area);
@@ -436,13 +429,8 @@ pub fn heading_bar<B: Backend>(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let suffix = if info_visible { "exit" } else { "show" }; let suffix = if help_visible { "exit" } else { "show" };
let info_text = format!( let info_text = format!("( h ) {} help {}", suffix, MARGIN,);
"( h ) {} help {} {:?}",
suffix,
MARGIN,
gui_state.lock().get_status()
);
let info_width = info_text.chars().count(); let info_width = info_text.chars().count();
let column_width = usize::from(area.width) - info_width; let column_width = usize::from(area.width) - info_width;
@@ -484,10 +472,10 @@ pub fn heading_bar<B: Backend>(
} }
// show/hide help // show/hide help
let color = if info_visible { let color = if help_visible {
Color::White
} else {
Color::Black Color::Black
} else {
Color::White
}; };
let help_paragraph = Paragraph::new(info_text) let help_paragraph = Paragraph::new(info_text)
.block(block(color)) .block(block(color))
+10 -7
View File
@@ -181,7 +181,6 @@ pub enum Status {
Help, Help,
DockerConnect, DockerConnect,
Error, Error,
Normal,
} }
/// Global gui_state, stored in an Arc<Mutex> /// Global gui_state, stored in an Arc<Mutex>
@@ -191,7 +190,7 @@ pub struct GuiState {
heading_map: HashMap<Header, Rect>, heading_map: HashMap<Header, Rect>,
loading_icon: Loading, loading_icon: Loading,
is_loading: HashSet<Uuid>, is_loading: HashSet<Uuid>,
status: Status, status: HashSet<Status>,
pub selected_panel: SelectablePanel, pub selected_panel: SelectablePanel,
pub info_box_text: Option<String>, pub info_box_text: Option<String>,
} }
@@ -205,7 +204,7 @@ impl GuiState {
selected_panel: SelectablePanel::Containers, selected_panel: SelectablePanel::Containers,
is_loading: HashSet::new(), is_loading: HashSet::new(),
info_box_text: None, info_box_text: None,
status: Status::Init, status: HashSet::new(),
} }
} }
@@ -253,12 +252,16 @@ impl GuiState {
}; };
} }
pub fn set_status(&mut self, status: Status) { pub fn status_push(&mut self, status: Status) {
self.status = status self.status.insert(status);
} }
pub fn get_status(&self) -> Status { pub fn status_del(&mut self, status: Status) {
self.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 /// Change to next selectable panel
+3 -3
View File
@@ -85,9 +85,9 @@ async fn run_app<B: Backend + Send>(
let input_poll_rate = std::time::Duration::from_millis(75); let input_poll_rate = std::time::Duration::from_millis(75);
// Check for docker connect errors before attempting to draw the gui // 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; let mut seconds = 5;
loop { loop {
if seconds < 1 { if seconds < 1 {
@@ -159,7 +159,7 @@ fn ui<B: Backend>(
let log_index = app_data.lock().get_selected_log_index(); let log_index = app_data.lock().get_selected_log_index();
let sorted_by = app_data.lock().get_sorted(); 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 info_text = gui_state.lock().info_box_text.clone();
let loading_icon = gui_state.lock().get_loading(); let loading_icon = gui_state.lock().get_loading();