feat: Docker exec mode, closes #28

This commit is contained in:
Jack Wills
2023-11-14 12:38:15 +00:00
parent e1998c9fca
commit c8077bca0b
13 changed files with 397 additions and 272 deletions
+6 -5
View File
@@ -585,6 +585,11 @@ impl HelpInfo {
space(),
button_item("enter"),
button_desc("to send docker container command"),
]),
Line::from(vec![
space(),
button_item("e"),
button_desc("exec into a container"),
]),
Line::from(vec![
space(),
@@ -724,11 +729,7 @@ pub fn help_box(f: &mut Frame) {
/// Draw the delete confirm box in the centre of the screen
/// take in container id and container name here?
pub fn delete_confirm(
f: &mut Frame,
gui_state: &Arc<Mutex<GuiState>>,
name: &str,
) {
pub fn delete_confirm(f: &mut Frame, gui_state: &Arc<Mutex<GuiState>>, name: &str) {
let block = Block::default()
.title(" Confirm Delete ")
.border_type(BorderType::Rounded)
+32 -5
View File
@@ -1,5 +1,10 @@
use parking_lot::Mutex;
use ratatui::layout::{Constraint, Rect};
use std::collections::{HashMap, HashSet};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use tokio::task::JoinHandle;
use uuid::Uuid;
use crate::app_data::{ContainerId, Header};
@@ -150,11 +155,12 @@ const FRAMES_LEN: u8 = 9;
/// 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,
Exec,
DeleteConfirm,
DockerConnect,
Error,
Help,
Init,
}
/// Global gui_state, stored in an Arc<Mutex>
@@ -296,13 +302,34 @@ impl GuiState {
}
/// Remove a loading_uuid from the is_loading HashSet, if empty, reset loading_index to 0
pub fn remove_loading(&mut self, uuid: Uuid) {
fn remove_loading(&mut self, uuid: Uuid) {
self.is_loading.remove(&uuid);
if self.is_loading.is_empty() {
self.loading_index = 0;
}
}
/// Animate the loading icon in its own Tokio thread
pub fn start_loading_animation(
gui_state: &Arc<Mutex<Self>>,
loading_uuid: Uuid,
) -> JoinHandle<()> {
gui_state.lock().next_loading(loading_uuid);
let gui_state = Arc::clone(gui_state);
tokio::spawn(async move {
loop {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
gui_state.lock().next_loading(loading_uuid);
}
})
}
/// Stop the loading_spin function, and reset gui loading status
pub fn stop_loading_animation(&mut self, handle: &JoinHandle<()>, loading_uuid: Uuid) {
handle.abort();
self.remove_loading(loading_uuid);
}
/// Set info box content
pub fn set_info_box(&mut self, text: &str) {
self.info_box_text = Some(text.to_owned());
+53 -17
View File
@@ -27,10 +27,13 @@ pub use self::color_match::*;
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
use crate::{
app_data::AppData, app_error::AppError, docker_data::DockerMessage,
input_handler::InputMessages,
input_handler::InputMessages, parse_args::CliArgs,
};
pub const DOCKER_COMMAND: &str = "docker";
pub struct Ui {
args: CliArgs,
app_data: Arc<Mutex<AppData>>,
docker_sx: Sender<DockerMessage>,
gui_state: Arc<Mutex<GuiState>>,
@@ -63,7 +66,9 @@ impl Ui {
sender: Sender<InputMessages>,
) {
if let Ok(terminal) = Self::setup_terminal() {
let args = app_data.lock().args.clone();
let mut ui = Self {
args,
app_data,
docker_sx,
gui_state,
@@ -86,19 +91,17 @@ impl Ui {
/// Setup the terminal for full-screen drawing mode, with mouse capture
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
Self::enable_mouse_capture()?;
let stdout = Self::init_terminal()?;
let backend = CrosstermBackend::new(stdout);
Ok(Terminal::new(backend)?)
}
/// This is a fix for mouse-events being printed to screen, read an event and do nothing with it
fn nullify_event_read(&self) {
if crossterm::event::poll(self.input_poll_rate).unwrap_or(true) {
event::read().ok();
}
fn init_terminal() -> Result<Stdout> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
Self::enable_mouse_capture()?;
Ok(stdout)
}
/// reset the terminal back to default settings
@@ -137,12 +140,48 @@ impl Ui {
Ok(())
}
/// Use exeternal docker cli to exec into a container
fn exec(&mut self) {
let id = self.app_data.lock().get_selected_container_id();
if let Some(id) = id {
// if Self::can_exec(&id).is_some() {
if let Ok(mut child) = std::process::Command::new(DOCKER_COMMAND)
.args(["exec", "-it", id.get(), "sh"])
.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.spawn()
{
self.reset_terminal().ok();
child.wait().ok();
if child.kill().is_err() {
std::process::exit(1)
}
// }
}
}
self.terminal.clear().ok();
self.reset_terminal().ok();
Self::init_terminal().ok();
self.gui_state.lock().status_del(Status::Exec);
}
/// The loop for drawing the main UI to the terminal
async fn gui_loop(&mut self) -> Result<(), AppError> {
let update_duration =
std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval));
std::time::Duration::from_millis(u64::from(self.args.docker_interval));
while self.is_running.load(Ordering::SeqCst) {
let exec = self.gui_state.lock().status_contains(&[Status::Exec]);
if exec {
self.exec();
self.docker_sx.send(DockerMessage::Update).await.ok();
continue;
}
if self
.terminal
.draw(|frame| draw_frame(frame, &self.app_data, &self.gui_state))
@@ -150,6 +189,7 @@ impl Ui {
{
return Err(AppError::Terminal);
}
if crossterm::event::poll(self.input_poll_rate).unwrap_or(false) {
if let Ok(event) = event::read() {
if let Event::Key(key) = event {
@@ -173,6 +213,7 @@ impl Ui {
}
}
// Should this be done in the docker thread instead?
if self.now.elapsed() >= update_duration {
self.docker_sx.send(DockerMessage::Update).await.ok();
self.now = Instant::now();
@@ -192,7 +233,6 @@ impl Ui {
} else {
self.gui_loop().await?;
}
self.nullify_event_read();
Ok(())
}
}
@@ -208,11 +248,7 @@ macro_rules! value_capture {
/// Draw the main ui to a frame of the terminal
/// TODO add a single line area for debug message - if not in release mode?
fn draw_frame(
f: &mut Frame,
app_data: &Arc<Mutex<AppData>>,
gui_state: &Arc<Mutex<GuiState>>,
) {
fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mutex<GuiState>>) {
value_capture!(height, app_data.lock().get_container_len());
value_capture!(column_widths, app_data.lock().get_width());
value_capture!(has_containers, app_data.lock().get_container_len() > 0);