Merge branch 'feat/docker_recv' into dev

This commit is contained in:
Jack Wills
2022-04-29 17:04:11 +00:00
7 changed files with 260 additions and 166 deletions
+9
View File
@@ -0,0 +1,9 @@
#[derive(Debug, Clone)]
pub enum DockerMessage {
Update,
Start(String),
Restart(String),
Pause(String),
Unpause(String),
Stop(String),
}
+113 -48
View File
@@ -1,22 +1,27 @@
use bollard::{ use bollard::{
container::{ListContainersOptions, LogsOptions, Stats, StatsOptions}, container::{ListContainersOptions, LogsOptions, StartContainerOptions, Stats, StatsOptions},
Docker, Docker,
}; };
use futures_util::{future::join_all, StreamExt}; use futures_util::{future::join_all, StreamExt};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::sync::Arc;
sync::Arc, use tokio::{sync::mpsc::Receiver, task::JoinHandle};
time::{Duration, Instant},
};
use crate::{app_data::AppData, parse_args::CliArgs, ui::GuiState}; use crate::{
app_data::{AppData, DockerControls},
app_error::AppError,
parse_args::CliArgs,
ui::GuiState,
};
mod message;
pub use message::DockerMessage;
pub struct DockerData { pub struct DockerData {
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker: Arc<Docker>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
initialised: bool, initialised: bool,
sleep_duration: Duration, receiver: Receiver<DockerMessage>,
timestamps: bool, timestamps: bool,
} }
@@ -178,8 +183,7 @@ impl DockerData {
} }
/// Update all logs, spawn each container into own tokio::spawn thread /// Update all logs, spawn each container into own tokio::spawn thread
// rename init all logs, as only gets run once async fn init_all_logs(&mut self, all_ids: &[(bool, String)]) {
async fn update_all_logs(&mut self, all_ids: &[(bool, String)]) {
let mut handles = vec![]; let mut handles = vec![];
for (_, id) in all_ids.iter() { for (_, id) in all_ids.iter() {
@@ -207,43 +211,33 @@ impl DockerData {
self.update_all_container_stats(&all_ids).await; self.update_all_container_stats(&all_ids).await;
} }
/// Initialise self, and start the updated loop /// Animate the loading icon
pub async fn init( async fn loading_spin(&mut self ) -> JoinHandle<()> {
args: CliArgs,
app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>,
gui_state: Arc<Mutex<GuiState>>,
) {
if app_data.lock().get_error().is_none() {
let mut inner = Self {
app_data,
docker,
gui_state,
initialised: false,
sleep_duration: Duration::from_millis(args.docker_interval as u64),
timestamps: args.timestamp,
};
inner.initialise_container_data().await;
inner.update_loop().await;
}
}
async fn initialise_container_data(&mut self) {
let gui_state = Arc::clone(&self.gui_state); let gui_state = Arc::clone(&self.gui_state);
// could also just loop while init is false, would need to move an arc mutex into here tokio::spawn(async move {
// so instead just abort at end of function
let loading_spin = tokio::spawn(async move {
loop { loop {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
gui_state.lock().next_loading(); gui_state.lock().next_loading();
} }
}); })
}
/// Stop the loading_spin fn, and reset gui loading status
fn stop_loading_spin(&mut self, handle: JoinHandle<()>) {
handle.abort();
self.gui_state.lock().reset_loading();
}
// Initialize docker container data, before any messages are received
async fn initialise_container_data(&mut self) {
let loading_spin = self.loading_spin().await;
let all_ids = self.update_all_containers().await; let all_ids = self.update_all_containers().await;
self.update_all_container_stats(&all_ids).await; self.update_all_container_stats(&all_ids).await;
// Maybe only do a single one at first? // Maybe only do a single one at first?
self.update_all_logs(&all_ids).await; self.init_all_logs(&all_ids).await;
if all_ids.is_empty() { if all_ids.is_empty() {
self.initialised = true; self.initialised = true;
@@ -255,21 +249,92 @@ impl DockerData {
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.app_data.lock().init = true;
loading_spin.abort(); self.stop_loading_spin(loading_spin);
self.gui_state.lock().reset_loading();
} }
/// Update all items, wait until all complete /// Handle incoming messages, container controls & all container information update
/// sleep for CliArgs.docker ms before updating next async fn message_handler(&mut self) {
async fn update_loop(&mut self) { while let Some(message) = self.receiver.recv().await {
loop { let docker = Arc::clone(&self.docker);
let start = Instant::now(); let app_data = Arc::clone(&self.app_data);
self.update_everything().await; match message {
DockerMessage::Pause(id) => {
let elapsed = start.elapsed(); let loading_spin =self.loading_spin().await;
if elapsed < self.sleep_duration { docker.pause_container(&id).await.unwrap_or_else(|_| {
tokio::time::sleep(self.sleep_duration - elapsed).await; app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Pause))
});
self.stop_loading_spin(loading_spin);
} }
DockerMessage::Restart(id) => {
let loading_spin =self.loading_spin().await;
docker
.restart_container(&id, None)
.await
.unwrap_or_else(|_| {
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Restart))
});
self.stop_loading_spin(loading_spin);
}
DockerMessage::Start(id) => {
let loading_spin =self.loading_spin().await;
docker
.start_container(&id, None::<StartContainerOptions<String>>)
.await
.unwrap_or_else(|_| {
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Start))
});
self.stop_loading_spin(loading_spin);
}
DockerMessage::Stop(id) => {
let loading_spin =self.loading_spin().await;
docker.stop_container(&id, None).await.unwrap_or_else(|_| {
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Stop))
});
self.stop_loading_spin(loading_spin);
}
DockerMessage::Unpause(id) => {
let loading_spin =self.loading_spin().await;
docker.unpause_container(&id).await.unwrap_or_else(|_| {
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Unpause))
});
self.stop_loading_spin(loading_spin);
self.update_everything().await
}
DockerMessage::Update => self.update_everything().await,
}
}
}
/// Initialise self, and start the message receiving loop
pub async fn init(
args: CliArgs,
app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>,
gui_state: Arc<Mutex<GuiState>>,
receiver: Receiver<DockerMessage>,
) {
if app_data.lock().get_error().is_none() {
let mut inner = Self {
app_data,
docker,
gui_state,
initialised: false,
receiver,
timestamps: args.timestamp,
};
inner.initialise_container_data().await;
inner.message_handler().await;
} }
} }
} }
+37 -80
View File
@@ -3,7 +3,6 @@ use std::sync::{
Arc, Arc,
}; };
use bollard::{container::StartContainerOptions, Docker};
use crossterm::{ use crossterm::{
event::{ event::{
DisableMouseCapture, EnableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind, DisableMouseCapture, EnableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind,
@@ -11,13 +10,17 @@ use crossterm::{
execute, execute,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use tokio::{sync::broadcast::Receiver, task::JoinHandle}; use tokio::{
sync::mpsc::{Receiver, Sender},
task::JoinHandle,
};
use tui::layout::Rect; use tui::layout::Rect;
mod message; mod message;
use crate::{ use crate::{
app_data::{AppData, DockerControls}, app_data::{AppData, DockerControls},
app_error::AppError, app_error::AppError,
docker_data::DockerMessage,
ui::{GuiState, SelectablePanel}, ui::{GuiState, SelectablePanel},
}; };
pub use message::InputMessages; pub use message::InputMessages;
@@ -26,12 +29,12 @@ pub use message::InputMessages;
#[derive(Debug)] #[derive(Debug)]
pub struct InputHandler { pub struct InputHandler {
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker_sender: Sender<DockerMessage>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>,
rec: Receiver<InputMessages>,
mouse_capture: bool,
info_sleep: Option<JoinHandle<()>>, info_sleep: Option<JoinHandle<()>>,
is_running: Arc<AtomicBool>,
mouse_capture: bool,
rec: Receiver<InputMessages>,
} }
impl InputHandler { impl InputHandler {
@@ -39,13 +42,13 @@ impl InputHandler {
pub async fn init( pub async fn init(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
rec: Receiver<InputMessages>, rec: Receiver<InputMessages>,
docker: Arc<Docker>, docker_sender: Sender<DockerMessage>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
) { ) {
let mut inner = Self { let mut inner = Self {
app_data, app_data,
docker, docker_sender,
gui_state, gui_state,
is_running, is_running,
rec, rec,
@@ -57,7 +60,7 @@ impl InputHandler {
/// check for incoming messages /// check for incoming messages
async fn start(&mut self) { async fn start(&mut self) {
while let Ok(message) = self.rec.recv().await { while let Some(message) = self.rec.recv().await {
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) => {
@@ -168,90 +171,44 @@ impl InputHandler {
} }
} }
KeyCode::Enter => { KeyCode::Enter => {
// Does is matter though?
// This isn't great, just means you can't send docker commands before full initialization of the program // This isn't great, just means you can't send docker commands before full initialization of the program
// could change to to if loading = true, although at the moment don't have a loading bool // could change to to if loading = true, although at the moment don't have a loading bool
// Does is matter though?
let panel = self.gui_state.lock().selected_panel; let panel = self.gui_state.lock().selected_panel;
if panel == SelectablePanel::Commands { if panel == SelectablePanel::Commands {
let command = self.app_data.lock().get_docker_command(); let command = self.app_data.lock().get_docker_command();
if command.is_some() { if command.is_some() {
let id = self.app_data.lock().get_selected_container_id(); let id = self.app_data.lock().get_selected_container_id();
let app_data = Arc::clone(&self.app_data);
let docker = Arc::clone(&self.docker);
if id.is_some() { if id.is_some() {
let id = id.unwrap(); let id = id.unwrap();
match command.unwrap() { match command.unwrap() {
DockerControls::Pause => { // TODO handle theses errors?
tokio::spawn(async move { DockerControls::Pause => self
docker.pause_container(&id).await.unwrap_or_else( .docker_sender
|_| { .send(DockerMessage::Pause(id))
app_data.lock().set_error(
AppError::DockerCommand(
DockerControls::Pause,
),
)
},
);
});
}
DockerControls::Unpause => {
tokio::spawn(async move {
docker.unpause_container(&id).await.unwrap_or_else(
|_| {
app_data.lock().set_error(
AppError::DockerCommand(
DockerControls::Unpause,
),
)
},
);
});
}
DockerControls::Start => {
tokio::spawn(async move {
docker
.start_container(
&id,
None::<StartContainerOptions<String>>,
)
.await .await
.unwrap_or_else(|_| { .unwrap(),
app_data.lock().set_error( DockerControls::Unpause => self
AppError::DockerCommand( .docker_sender
DockerControls::Start, .send(DockerMessage::Unpause(id))
),
)
});
});
}
DockerControls::Stop => {
tokio::spawn(async move {
docker.stop_container(&id, None).await.unwrap_or_else(
|_| {
app_data.lock().set_error(
AppError::DockerCommand(
DockerControls::Stop,
),
)
},
);
});
}
DockerControls::Restart => {
tokio::spawn(async move {
docker
.restart_container(&id, None)
.await .await
.unwrap_or_else(|_| { .unwrap(),
app_data.lock().set_error( DockerControls::Start => self
AppError::DockerCommand( .docker_sender
DockerControls::Restart, .send(DockerMessage::Start(id))
), .await
) .unwrap(),
}); DockerControls::Stop => self
}); .docker_sender
} .send(DockerMessage::Stop(id))
.await
.unwrap(),
DockerControls::Restart => self
.docker_sender
.send(DockerMessage::Restart(id))
.await
.unwrap(),
} }
} }
} }
+25 -8
View File
@@ -1,5 +1,3 @@
#![allow(unused)]
use app_data::AppData; use app_data::AppData;
use app_error::AppError; use app_error::AppError;
use bollard::Docker; use bollard::Docker;
@@ -33,13 +31,21 @@ async fn main() {
let docker_app_data = Arc::clone(&app_data); let docker_app_data = Arc::clone(&app_data);
let docker_gui_state = Arc::clone(&gui_state); let docker_gui_state = Arc::clone(&gui_state);
let (docker_sx, docker_rx) = tokio::sync::mpsc::channel(16);
// Create docker daemon handler, and only spawn up the docker data handler if ping returns non-error // Create docker daemon handler, and only spawn up the docker data handler if ping returns non-error
let docker = Arc::new(Docker::connect_with_socket_defaults().unwrap()); let docker = Arc::new(Docker::connect_with_socket_defaults().unwrap());
match docker.ping().await { match docker.ping().await {
Ok(_) => { Ok(_) => {
let docker = Arc::clone(&docker); let docker = Arc::clone(&docker);
tokio::spawn(async move { tokio::spawn(async move {
DockerData::init(docker_args, docker_app_data, docker, docker_gui_state).await; DockerData::init(
docker_args,
docker_app_data,
docker,
docker_gui_state,
docker_rx,
)
.await;
}); });
} }
Err(_) => app_data.lock().set_error(AppError::DockerConnect), Err(_) => app_data.lock().set_error(AppError::DockerConnect),
@@ -47,19 +53,20 @@ async fn main() {
let input_app_data = Arc::clone(&app_data); let input_app_data = Arc::clone(&app_data);
let (s, r) = tokio::sync::broadcast::channel(16); let (input_sx, input_rx) = tokio::sync::mpsc::channel(16);
let input_docker = Arc::clone(&docker); // let input_docker = Arc::clone(&docker);
let is_running = Arc::new(AtomicBool::new(true)); let is_running = Arc::new(AtomicBool::new(true));
let input_is_running = Arc::clone(&is_running); let input_is_running = Arc::clone(&is_running);
let input_gui_state = Arc::clone(&gui_state); let input_gui_state = Arc::clone(&gui_state);
let input_docker_sender = docker_sx.clone();
// Spawn input handling into own tokio thread // Spawn input handling into own tokio thread
tokio::spawn(async { tokio::spawn(async {
input_handler::InputHandler::init( input_handler::InputHandler::init(
input_app_data, input_app_data,
r, input_rx,
input_docker, input_docker_sender,
input_gui_state, input_gui_state,
input_is_running, input_is_running,
) )
@@ -73,6 +80,16 @@ async fn main() {
tokio::time::sleep(std::time::Duration::from_millis(5000)).await; tokio::time::sleep(std::time::Duration::from_millis(5000)).await;
} }
} else { } else {
create_ui(app_data, s, is_running, gui_state).await.unwrap(); let update_duration = std::time::Duration::from_millis(args.docker_interval as u64);
create_ui(
app_data,
input_sx,
is_running,
gui_state,
docker_sx,
update_duration,
)
.await
.unwrap();
} }
} }
+13 -5
View File
@@ -223,6 +223,7 @@ pub fn draw_logs<B: Backend>(
f: &mut Frame<'_, B>, f: &mut Frame<'_, B>,
gui_state: &Arc<Mutex<GuiState>>, gui_state: &Arc<Mutex<GuiState>>,
index: Option<usize>, index: Option<usize>,
loading_icon: String,
selected_panel: &SelectablePanel, selected_panel: &SelectablePanel,
) { ) {
let panel = SelectablePanel::Logs; let panel = SelectablePanel::Logs;
@@ -233,8 +234,8 @@ pub fn draw_logs<B: Backend>(
let init = app_data.lock().init; let init = app_data.lock().init;
if !init { if !init {
let icon = gui_state.lock().get_loading(); // let icon = gui_state.lock().get_loading();
let parsing_logs = format!("parsing logs {}", icon); let parsing_logs = format!("parsing logs {}", loading_icon);
let paragraph = Paragraph::new(parsing_logs) let paragraph = Paragraph::new(parsing_logs)
.style(Style::default()) .style(Style::default())
.block(block) .block(block)
@@ -367,13 +368,19 @@ pub fn draw_heading_bar<B: Backend>(
columns: &Columns, columns: &Columns,
f: &mut Frame<'_, B>, f: &mut Frame<'_, B>,
has_containers: bool, has_containers: bool,
loading_icon: String,
info_visible: bool, info_visible: bool,
) { ) {
let block = || Block::default().style(Style::default().bg(Color::Magenta).fg(Color::Black)); let block = || Block::default().style(Style::default().bg(Color::Magenta).fg(Color::Black));
f.render_widget(block(), area); f.render_widget(block(), area);
let mut column_headings = format!(" {:>width$}", columns.state.0, width = columns.state.1); let mut column_headings = format!(
" {}{:>width$}",
loading_icon,
columns.state.0,
width = columns.state.1
);
column_headings.push_str( column_headings.push_str(
format!( format!(
"{} {:>width$}", "{} {:>width$}",
@@ -471,7 +478,9 @@ pub fn draw_help_box<B: Backend>(f: &mut Frame<'_, B>) {
help_text.push_str("\n ( ↑ ↓ ← → ) to change selected line"); help_text.push_str("\n ( ↑ ↓ ← → ) to change selected line");
help_text.push_str("\n ( enter ) to send docker container commands"); help_text.push_str("\n ( enter ) to send docker container commands");
help_text.push_str("\n ( h ) to toggle this help information"); help_text.push_str("\n ( h ) to toggle this help information");
help_text.push_str("\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied"); help_text.push_str(
"\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied",
);
help_text.push_str("\n ( q ) to quit at any time"); help_text.push_str("\n ( q ) to quit at any time");
help_text.push_str("\n mouse scrolling & clicking also available"); help_text.push_str("\n mouse scrolling & clicking also available");
help_text.push_str("\n\n currenty an early work in progress, all and any input appreciated"); help_text.push_str("\n\n currenty an early work in progress, all and any input appreciated");
@@ -602,7 +611,6 @@ pub fn draw_info<B: Backend>(f: &mut Frame<'_, B>, text: String) {
.title_alignment(Alignment::Center) .title_alignment(Alignment::Center)
.borders(Borders::NONE); .borders(Borders::NONE);
let mut max_line_width = 0; let mut max_line_width = 0;
text.lines().into_iter().for_each(|line| { text.lines().into_iter().for_each(|line| {
let width = line.chars().count(); let width = line.chars().count();
+20 -13
View File
@@ -8,6 +8,7 @@ pub enum SelectablePanel {
Logs, Logs,
} }
#[allow(unused)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum BoxLocation { pub enum BoxLocation {
TopLeft, TopLeft,
@@ -165,10 +166,12 @@ pub struct GuiState {
// Is an issue if two panels are in the same space, sush as a smaller panel embedded, yet infront of, a larger panel // Is an issue if two panels are in the same space, sush as a smaller panel embedded, yet infront of, a larger panel
// If a BMapTree think it would mean have to implement ordering for SelectablePanel // If a BMapTree think it would mean have to implement ordering for SelectablePanel
area_map: HashMap<SelectablePanel, Rect>, area_map: HashMap<SelectablePanel, Rect>,
loading: Loading, loading_icon: Loading,
// Should be a vec, each time loading add a new to the vec, and reset remove from vec
// for for if is_loading just check if vec is empty or not
is_loading: bool,
pub selected_panel: SelectablePanel, pub selected_panel: SelectablePanel,
pub show_help: bool, pub show_help: bool,
// show_info_panel: bool,
pub info_box_text: Option<String>, pub info_box_text: Option<String>,
} }
@@ -177,10 +180,10 @@ impl GuiState {
pub fn default() -> Self { pub fn default() -> Self {
Self { Self {
area_map: HashMap::new(), area_map: HashMap::new(),
loading: Loading::One, loading_icon: Loading::One,
selected_panel: SelectablePanel::Containers, selected_panel: SelectablePanel::Containers,
show_help: false, show_help: false,
// show_info_panel: false, is_loading: false,
info_box_text: None, info_box_text: None,
} }
} }
@@ -218,29 +221,33 @@ impl GuiState {
self.selected_panel = self.selected_panel.prev(); self.selected_panel = self.selected_panel.prev();
} }
/// Advance loading animation
pub fn next_loading(&mut self) { pub fn next_loading(&mut self) {
self.loading = self.loading.next() self.loading_icon = self.loading_icon.next();
self.is_loading = true;
} }
/// if is_loading, return loading animation frame, else single space
pub fn get_loading(&mut self) -> String { pub fn get_loading(&mut self) -> String {
self.loading.to_string() if self.is_loading {
self.loading_icon.to_string()
} else {
String::from(" ")
}
} }
/// set is_loading to false, but keep animation frame at same state
pub fn reset_loading(&mut self) { pub fn reset_loading(&mut self) {
self.loading = Loading::One; self.is_loading = false;
} }
/// Set info box content
pub fn set_info_box(&mut self, text: String) { pub fn set_info_box(&mut self, text: String) {
self.info_box_text = Some(text); self.info_box_text = Some(text);
// self.show_info_panel = true;
// Should spawn and after 10 seconds close?
// Need to copy whatever we're doing with parsing logs icon
} }
/// Remove info box content
pub fn reset_info_box(&mut self) { pub fn reset_info_box(&mut self) {
// self.loading = Loading::One;
self.info_box_text = None; self.info_box_text = None;
// self.show_info_panel = false;
} }
} }
+39 -8
View File
@@ -5,13 +5,15 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
use std::{ use std::{
io, io,
sync::{atomic::Ordering, Arc}, sync::{atomic::Ordering, Arc},
}; };
use tokio::sync::broadcast::Sender; use std::{
use tracing::error; sync::atomic::AtomicBool,
time::{Duration, Instant},
};
use tokio::sync::mpsc::Sender;
use tui::{ use tui::{
backend::{Backend, CrosstermBackend}, backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
@@ -24,7 +26,10 @@ mod gui_state;
pub use self::color_match::*; pub use self::color_match::*;
pub use self::gui_state::{GuiState, SelectablePanel}; pub use self::gui_state::{GuiState, SelectablePanel};
use crate::{app_data::AppData, app_error::AppError, input_handler::InputMessages}; use crate::{
app_data::AppData, app_error::AppError, docker_data::DockerMessage,
input_handler::InputMessages,
};
use draw_blocks::*; use draw_blocks::*;
/// Take control of the terminal in order to draw gui /// Take control of the terminal in order to draw gui
@@ -33,6 +38,8 @@ pub async fn create_ui(
sender: Sender<InputMessages>, sender: Sender<InputMessages>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
docker_sx: Sender<DockerMessage>,
update_duration: Duration,
) -> Result<()> { ) -> Result<()> {
enable_raw_mode()?; enable_raw_mode()?;
let mut stdout = io::stdout(); let mut stdout = io::stdout();
@@ -40,7 +47,16 @@ pub async fn create_ui(
let backend = CrosstermBackend::new(stdout); let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
let res = run_app(&mut terminal, app_data, sender, is_running, gui_state).await; let res = run_app(
&mut terminal,
app_data,
sender,
is_running,
gui_state,
docker_sx,
update_duration,
)
.await;
disable_raw_mode().unwrap(); disable_raw_mode().unwrap();
execute!( execute!(
@@ -51,7 +67,7 @@ pub async fn create_ui(
terminal.show_cursor().unwrap(); terminal.show_cursor().unwrap();
if let Err(err) = res { if let Err(err) = res {
error!(%err); println!("{}", err);
} }
Ok(()) Ok(())
} }
@@ -63,6 +79,8 @@ async fn run_app<B: Backend>(
sender: Sender<InputMessages>, sender: Sender<InputMessages>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
docker_sx: Sender<DockerMessage>,
update_duration: Duration,
) -> Result<(), AppError> { ) -> Result<(), AppError> {
let input_poll_rate = std::time::Duration::from_millis(75); let input_poll_rate = std::time::Duration::from_millis(75);
@@ -84,6 +102,7 @@ async fn run_app<B: Backend>(
} }
} }
} else { } else {
let mut now = Instant::now();
loop { loop {
terminal.draw(|f| ui(f, &app_data, &gui_state)).unwrap(); terminal.draw(|f| ui(f, &app_data, &gui_state)).unwrap();
if crossterm::event::poll(input_poll_rate).unwrap() { if crossterm::event::poll(input_poll_rate).unwrap() {
@@ -91,15 +110,24 @@ async fn run_app<B: Backend>(
if let Event::Key(key) = event { if let Event::Key(key) = event {
sender sender
.send(InputMessages::ButtonPress(key.code)) .send(InputMessages::ButtonPress(key.code))
.unwrap_or(0); .await
.unwrap_or(());
} else if let Event::Mouse(m) = event { } else if let Event::Mouse(m) = event {
sender.send(InputMessages::MouseEvent(m)).unwrap_or(0); sender
.send(InputMessages::MouseEvent(m))
.await
.unwrap_or(());
} else if let Event::Resize(_, _) = event { } else if let Event::Resize(_, _) = event {
gui_state.lock().clear_area_map(); gui_state.lock().clear_area_map();
terminal.autoresize().unwrap_or(()); terminal.autoresize().unwrap_or(());
} }
} }
if now.elapsed() >= update_duration {
docker_sx.send(DockerMessage::Update).await.unwrap();
now = Instant::now();
}
if !is_running.load(Ordering::SeqCst) { if !is_running.load(Ordering::SeqCst) {
break; break;
} }
@@ -128,6 +156,7 @@ fn ui<B: Backend>(
let selected_panel = gui_state.lock().selected_panel; let selected_panel = gui_state.lock().selected_panel;
let show_help = gui_state.lock().show_help; let show_help = gui_state.lock().show_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 whole_layout = Layout::default() let whole_layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@@ -189,6 +218,7 @@ fn ui<B: Backend>(
f, f,
gui_state, gui_state,
log_index, log_index,
loading_icon.to_owned(),
&selected_panel, &selected_panel,
); );
@@ -197,6 +227,7 @@ fn ui<B: Backend>(
&column_widths, &column_widths,
f, f,
has_containers, has_containers,
loading_icon,
show_help, show_help,
); );