feat: exec mode improvements

Use Bollard library to exec in pure Rust.

`--use-cli` cli arg, will then only try to exec into containers using
Docker.

Only try to exec into a container if the state == Running.
This commit is contained in:
Jack Wills
2023-11-17 02:49:30 +00:00
parent 28b0315dd7
commit 0e5ee143b0
15 changed files with 477 additions and 142 deletions
+3 -2
View File
@@ -279,6 +279,7 @@ pub fn chart(f: &mut Frame, area: Rect, app_data: &Arc<Mutex<AppData>>) {
.data(&mem.0)];
let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1));
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64));
let cpu_chart = make_chart(cpu.2, "cpu", cpu_dataset, &cpu_stats, &cpu.1);
let mem_chart = make_chart(mem.2, "memory", mem_dataset, &mem_stats, &mem.1);
@@ -359,8 +360,8 @@ pub fn heading_bar(
if let Some((a, b)) = data.sorted_by.as_ref() {
if x == a {
match b {
SortedOrder::Asc => suffix = " ",
SortedOrder::Desc => suffix = " ",
SortedOrder::Asc => suffix = " ",
SortedOrder::Desc => suffix = " ",
}
suffix_margin = 2;
color = Color::White;
+33 -4
View File
@@ -7,7 +7,10 @@ use std::{
use tokio::task::JoinHandle;
use uuid::Uuid;
use crate::app_data::{ContainerId, Header};
use crate::{
app_data::{ContainerId, Header},
exec::ExecMode,
};
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
pub enum SelectablePanel {
@@ -174,6 +177,7 @@ pub struct GuiState {
panel_map: HashMap<SelectablePanel, Rect>,
selected_panel: SelectablePanel,
status: HashSet<Status>,
exec_mode: Option<ExecMode>,
pub info_box_text: Option<String>,
}
impl GuiState {
@@ -265,16 +269,41 @@ impl GuiState {
}
/// Remove a gui_status into the current gui_status HashSet
/// Remove exec mode & deleteConfirm is required
pub fn status_del(&mut self, status: Status) {
self.status.remove(&status);
if status == Status::DeleteConfirm {
self.status.remove(&Status::DeleteConfirm);
match status {
Status::DeleteConfirm => {
self.status.remove(&Status::DeleteConfirm);
}
Status::Exec => {
self.exec_mode = None;
}
_ => (),
}
}
/// Inset the ExecMode into self, and set the Status as exec
/// Using StatusPush with Status::Exec won't insert into the hash map
/// To force self.exec_mode to be set
pub fn set_exec_mode(&mut self, mode: ExecMode) {
self.exec_mode = Some(mode);
self.status.insert(Status::Exec);
}
pub fn get_exec_mode(&mut self) -> Option<ExecMode> {
self.exec_mode.clone()
}
/// Insert a gui_status into the current gui_status HashSet
/// If the status is Exec, it won't get inserted, set_exec_mode() should be used instead
pub fn status_push(&mut self, status: Status) {
self.status.insert(status);
match status {
Status::Exec => (),
_ => {
self.status.insert(status);
}
}
}
/// Change to next selectable panel
+23 -31
View File
@@ -1,4 +1,5 @@
use anyhow::Result;
use bollard::Docker;
use crossterm::{
event::{self, DisableMouseCapture, Event},
execute,
@@ -28,20 +29,20 @@ pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
use crate::{
app_data::{AppData, Columns, ContainerId, Header, SortedOrder},
app_error::AppError,
docker_data::DockerMessage,
input_handler::InputMessages,
};
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>>,
input_poll_rate: Duration,
is_running: Arc<AtomicBool>,
now: Instant,
sender: Sender<InputMessages>,
terminal: Terminal<CrosstermBackend<Stdout>>,
cursor_position: (u16, u16),
}
impl Ui {
@@ -60,20 +61,24 @@ impl Ui {
/// Create a new Ui struct, and execute the drawing loop
pub async fn create(
app_data: Arc<Mutex<AppData>>,
docker_sx: Sender<DockerMessage>,
gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>,
sender: Sender<InputMessages>,
) {
if let Ok(terminal) = Self::setup_terminal() {
if let Ok(mut terminal) = Self::setup_terminal() {
// let args = app_data.lock().args.clone();
let cursor_position = terminal.get_cursor().unwrap_or_default();
let mut ui = Self {
app_data,
docker_sx,
gui_state,
input_poll_rate: std::time::Duration::from_millis(100),
is_running,
now: Instant::now(),
sender,
terminal,
cursor_position,
};
if let Err(e) = ui.draw_ui().await {
error!("{e}");
@@ -111,6 +116,9 @@ impl Ui {
DisableMouseCapture
)?;
disable_raw_mode()?;
self.terminal.clear().ok();
self.terminal
.set_cursor(self.cursor_position.0, self.cursor_position.1)?;
Ok(self.terminal.show_cursor()?)
}
@@ -138,24 +146,17 @@ impl Ui {
}
/// Use exeternal docker cli to exec into a container
fn exec(&mut self) {
let id = self.app_data.lock().get_selected_container_id();
async fn exec(&mut self) {
let mut exec_mode = self.gui_state.lock().get_exec_mode();
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)
}
}
if let Some(mode) = exec_mode {
self.reset_terminal().ok();
self.terminal.clear().ok();
if let Err(e) = mode.run(&self.app_data, &self.gui_state).await {
self.app_data
.lock()
.set_error(e, &self.gui_state, Status::Error);
};
}
self.terminal.clear().ok();
self.reset_terminal().ok();
@@ -168,7 +169,7 @@ impl Ui {
while self.is_running.load(Ordering::SeqCst) {
let exec = self.gui_state.lock().status_contains(&[Status::Exec]);
if exec {
self.exec();
self.exec().await;
}
if self
@@ -220,15 +221,6 @@ impl Ui {
}
}
// #[macro_export]
// /// This macro simplifies the definition and evaluation of variables by capturing and immediately evaluating an expression.
// macro_rules! value_capture {
// ($name:ident, $lock_expr:expr) => {
// let $name = || $lock_expr;
// let $name = $name();
// };
// }
#[cfg(not(debug_assertions))]
fn get_wholelayout(f: &Frame) -> std::rc::Rc<[ratatui::layout::Rect]> {
Layout::default()