refactor: input_handler
This commit is contained in:
+23
-23
@@ -196,7 +196,7 @@ impl DockerData {
|
||||
/// Get all current containers, handle into ContainerItem in the app_data struct rather than here
|
||||
/// Just make sure that items sent are guaranteed to have an id
|
||||
/// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set
|
||||
pub async fn update_all_containers(&self) -> Vec<(State, ContainerId)> {
|
||||
async fn update_all_containers(&self) -> Vec<(State, ContainerId)> {
|
||||
let containers = self
|
||||
.docker
|
||||
.list_containers(Some(ListContainersOptions::<String> {
|
||||
@@ -293,6 +293,26 @@ 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();
|
||||
GuiState::start_loading_animation(&self.gui_state, loading_uuid);
|
||||
let all_ids = self.update_all_containers().await;
|
||||
|
||||
self.update_all_container_stats(&all_ids);
|
||||
|
||||
let init = Arc::new(AtomicUsize::new(0));
|
||||
self.init_all_logs(&all_ids, &Some(Arc::clone(&init)));
|
||||
|
||||
while init.load(std::sync::atomic::Ordering::SeqCst) != all_ids.len() {
|
||||
self.app_data.lock().sort_containers();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
self.gui_state.lock().stop_loading_animation(loading_uuid);
|
||||
self.gui_state.lock().status_del(Status::Init);
|
||||
}
|
||||
|
||||
/// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed)
|
||||
async fn update_everything(&mut self) {
|
||||
let all_ids = self.update_all_containers().await;
|
||||
@@ -314,27 +334,6 @@ impl DockerData {
|
||||
};
|
||||
self.update_all_container_stats(&all_ids);
|
||||
self.app_data.lock().sort_containers();
|
||||
self.gui_state.lock().stop_loading_animation(Uuid::nil());
|
||||
}
|
||||
|
||||
/// 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();
|
||||
GuiState::start_loading_animation(&self.gui_state, loading_uuid);
|
||||
let all_ids = self.update_all_containers().await;
|
||||
|
||||
self.update_all_container_stats(&all_ids);
|
||||
|
||||
let init = Arc::new(AtomicUsize::new(0));
|
||||
self.init_all_logs(&all_ids, &Some(Arc::clone(&init)));
|
||||
|
||||
while init.load(std::sync::atomic::Ordering::SeqCst) != all_ids.len() {
|
||||
self.app_data.lock().sort_containers();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
self.gui_state.lock().stop_loading_animation(loading_uuid);
|
||||
self.gui_state.lock().status_del(Status::Init);
|
||||
}
|
||||
|
||||
/// Set the global error as the docker error, and set gui_state to error
|
||||
@@ -387,6 +386,7 @@ impl DockerData {
|
||||
}
|
||||
gui_state.lock().stop_loading_animation(uuid);
|
||||
});
|
||||
|
||||
self.update_everything().await;
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ impl DockerData {
|
||||
}
|
||||
|
||||
/// Initialise self, and start the message receiving loop
|
||||
pub async fn init(
|
||||
pub async fn start(
|
||||
app_data: Arc<Mutex<AppData>>,
|
||||
docker: Docker,
|
||||
docker_rx: Receiver<DockerMessage>,
|
||||
|
||||
+46
-65
@@ -5,7 +5,7 @@ use std::{
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use bollard::{container::LogsOptions, Docker};
|
||||
use bollard::container::LogsOptions;
|
||||
use cansi::v3::categorise_text;
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind},
|
||||
@@ -40,7 +40,7 @@ pub struct InputHandler {
|
||||
|
||||
impl InputHandler {
|
||||
/// Initialize self, and running the message handling loop
|
||||
pub async fn init(
|
||||
pub async fn start(
|
||||
app_data: Arc<Mutex<AppData>>,
|
||||
rec: Receiver<InputMessages>,
|
||||
docker_tx: Sender<DockerMessage>,
|
||||
@@ -55,11 +55,11 @@ impl InputHandler {
|
||||
rec,
|
||||
mouse_capture: true,
|
||||
};
|
||||
inner.start().await;
|
||||
inner.message_handler().await;
|
||||
}
|
||||
|
||||
/// check for incoming messages
|
||||
async fn start(&mut self) {
|
||||
async fn message_handler(&mut self) {
|
||||
while let Some(message) = self.rec.recv().await {
|
||||
match message {
|
||||
InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await,
|
||||
@@ -124,7 +124,7 @@ impl InputHandler {
|
||||
if !is_oxker && tty_readable() {
|
||||
let uuid = Uuid::new_v4();
|
||||
GuiState::start_loading_animation(&self.gui_state, uuid);
|
||||
let (sx, rx) = tokio::sync::oneshot::channel::<Arc<Docker>>();
|
||||
let (sx, rx) = tokio::sync::oneshot::channel();
|
||||
self.docker_tx.send(DockerMessage::Exec(sx)).await.ok();
|
||||
|
||||
if let Ok(docker) = rx.await {
|
||||
@@ -147,47 +147,40 @@ impl InputHandler {
|
||||
|
||||
/// Toggle the mouse capture (via input of the 'm' key)
|
||||
fn m_key(&mut self) {
|
||||
let err = || {
|
||||
self.app_data.lock().set_error(
|
||||
AppError::MouseCapture(!self.mouse_capture),
|
||||
&self.gui_state,
|
||||
Status::Error,
|
||||
);
|
||||
};
|
||||
if self.mouse_capture {
|
||||
if execute!(std::io::stdout(), DisableMouseCapture).is_ok() {
|
||||
self.gui_state
|
||||
.lock()
|
||||
.set_info_box("✖ mouse capture disabled");
|
||||
} else {
|
||||
self.app_data.lock().set_error(
|
||||
AppError::MouseCapture(false),
|
||||
&self.gui_state,
|
||||
Status::Error,
|
||||
);
|
||||
err();
|
||||
}
|
||||
} else if Ui::enable_mouse_capture().is_ok() {
|
||||
self.gui_state
|
||||
.lock()
|
||||
.set_info_box("✓ mouse capture enabled");
|
||||
} else {
|
||||
self.app_data.lock().set_error(
|
||||
AppError::MouseCapture(true),
|
||||
&self.gui_state,
|
||||
Status::Error,
|
||||
);
|
||||
err();
|
||||
};
|
||||
|
||||
self.mouse_capture = !self.mouse_capture;
|
||||
}
|
||||
|
||||
/// Save the currently selected containers logs into a `[container_name]_[timestamp].log` file
|
||||
async fn s_key(&self) {
|
||||
/// This is the inner workings, *inlined* here to return a Result
|
||||
async fn save_logs(
|
||||
app_data: &Arc<Mutex<AppData>>,
|
||||
gui_state: &Arc<Mutex<GuiState>>,
|
||||
docker_tx: &Sender<DockerMessage>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = app_data.lock().args.clone();
|
||||
let container = app_data.lock().get_selected_container_id_state_name();
|
||||
async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = self.app_data.lock().args.clone();
|
||||
let container = self.app_data.lock().get_selected_container_id_state_name();
|
||||
if let Some((id, _, name)) = container {
|
||||
if let Some(log_path) = args.save_dir {
|
||||
let (sx, rx) = tokio::sync::oneshot::channel::<Arc<Docker>>();
|
||||
docker_tx.send(DockerMessage::Exec(sx)).await?;
|
||||
let (sx, rx) = tokio::sync::oneshot::channel();
|
||||
self.docker_tx.send(DockerMessage::Exec(sx)).await?;
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
@@ -195,7 +188,6 @@ impl InputHandler {
|
||||
|
||||
let path = log_path.join(format!("{name}_{now}.log"));
|
||||
|
||||
let docker = rx.await?;
|
||||
let options = Some(LogsOptions::<String> {
|
||||
stderr: true,
|
||||
stdout: true,
|
||||
@@ -203,7 +195,7 @@ impl InputHandler {
|
||||
since: 0,
|
||||
..Default::default()
|
||||
});
|
||||
let mut logs = docker.logs(id.get(), options);
|
||||
let mut logs = rx.await?.logs(id.get(), options);
|
||||
let mut output = vec![];
|
||||
|
||||
while let Some(Ok(value)) = logs.next().await {
|
||||
@@ -232,7 +224,7 @@ impl InputHandler {
|
||||
}
|
||||
stream.flush()?;
|
||||
|
||||
gui_state
|
||||
self.gui_state
|
||||
.lock()
|
||||
.set_info_box(&format!("saved to {}", path.display()));
|
||||
}
|
||||
@@ -241,17 +233,15 @@ impl InputHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt to save the currently selected container logs to a file
|
||||
async fn s_key(&self) {
|
||||
let log_status = Status::Logs;
|
||||
let status = self.gui_state.lock().status_contains(&[log_status]);
|
||||
if !status {
|
||||
self.gui_state.lock().status_push(log_status);
|
||||
|
||||
let uuid = Uuid::new_v4();
|
||||
GuiState::start_loading_animation(&self.gui_state, uuid);
|
||||
if save_logs(&self.app_data, &self.gui_state, &self.docker_tx)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
if self.save_logs().await.is_err() {
|
||||
self.app_data.lock().set_error(
|
||||
AppError::DockerLogs,
|
||||
&self.gui_state,
|
||||
@@ -296,50 +286,43 @@ impl InputHandler {
|
||||
}
|
||||
|
||||
/// Change the the "next" selectable panel
|
||||
/// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state
|
||||
fn tab_key(&self) {
|
||||
let is_containers =
|
||||
self.gui_state.lock().get_selected_panel() == SelectablePanel::Containers;
|
||||
let count = if self.app_data.lock().get_container_len() == 0 && is_containers {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
for _ in 0..count {
|
||||
self.gui_state.lock().next_panel();
|
||||
if self.app_data.lock().get_container_len() == 0
|
||||
&& self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
|
||||
{
|
||||
self.gui_state.lock().next_panel();
|
||||
}
|
||||
}
|
||||
|
||||
/// Change to previously selected panel
|
||||
/// Need to skip the commands planel if there no are current containers running
|
||||
fn back_tab_key(&self) {
|
||||
let is_containers = self.gui_state.lock().get_selected_panel() == SelectablePanel::Logs;
|
||||
let count = if self.app_data.lock().get_container_len() == 0 && is_containers {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
for _ in 0..count {
|
||||
self.gui_state.lock().previous_panel();
|
||||
if self.app_data.lock().get_container_len() == 0
|
||||
&& self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
|
||||
{
|
||||
self.gui_state.lock().previous_panel();
|
||||
}
|
||||
}
|
||||
|
||||
fn home_key(&self) {
|
||||
let mut locked_data = self.app_data.lock();
|
||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
||||
match selected_panel {
|
||||
SelectablePanel::Containers => locked_data.containers_start(),
|
||||
SelectablePanel::Logs => locked_data.log_start(),
|
||||
SelectablePanel::Commands => locked_data.docker_controls_start(),
|
||||
SelectablePanel::Containers => self.app_data.lock().containers_start(),
|
||||
SelectablePanel::Logs => self.app_data.lock().log_start(),
|
||||
SelectablePanel::Commands => self.app_data.lock().docker_controls_start(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Go to end of the list of the currently selected panel
|
||||
fn end_key(&self) {
|
||||
let mut locked_data = self.app_data.lock();
|
||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
||||
match selected_panel {
|
||||
SelectablePanel::Containers => locked_data.containers_end(),
|
||||
SelectablePanel::Logs => locked_data.log_end(),
|
||||
SelectablePanel::Commands => locked_data.docker_controls_end(),
|
||||
SelectablePanel::Containers => self.app_data.lock().containers_end(),
|
||||
SelectablePanel::Logs => self.app_data.lock().log_end(),
|
||||
SelectablePanel::Commands => self.app_data.lock().docker_controls_end(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,23 +508,21 @@ impl InputHandler {
|
||||
|
||||
/// Change state to next, depending which panel is currently in focus
|
||||
fn next(&self) {
|
||||
let mut locked_data = self.app_data.lock();
|
||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
||||
match selected_panel {
|
||||
SelectablePanel::Containers => locked_data.containers_next(),
|
||||
SelectablePanel::Logs => locked_data.log_next(),
|
||||
SelectablePanel::Commands => locked_data.docker_controls_next(),
|
||||
SelectablePanel::Containers => self.app_data.lock().containers_next(),
|
||||
SelectablePanel::Logs => self.app_data.lock().log_next(),
|
||||
SelectablePanel::Commands => self.app_data.lock().docker_controls_next(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Change state to previous, depending which panel is currently in focus
|
||||
fn previous(&self) {
|
||||
let mut locked_data = self.app_data.lock();
|
||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
||||
match selected_panel {
|
||||
SelectablePanel::Containers => locked_data.containers_previous(),
|
||||
SelectablePanel::Logs => locked_data.log_previous(),
|
||||
SelectablePanel::Commands => locked_data.docker_controls_previous(),
|
||||
SelectablePanel::Containers => self.app_data.lock().containers_previous(),
|
||||
SelectablePanel::Logs => self.app_data.lock().log_previous(),
|
||||
SelectablePanel::Commands => self.app_data.lock().docker_controls_previous(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-19
@@ -54,29 +54,27 @@ async fn docker_init(
|
||||
gui_state: &Arc<Mutex<GuiState>>,
|
||||
host: Option<String>,
|
||||
) {
|
||||
// let err = ||
|
||||
let connection = host.map_or_else(Docker::connect_with_socket_defaults, |host| {
|
||||
Docker::connect_with_socket(&host, 120, API_DEFAULT_VERSION)
|
||||
});
|
||||
|
||||
if let Ok(docker) = connection {
|
||||
if docker.ping().await.is_ok() {
|
||||
let app_data = Arc::clone(app_data);
|
||||
let gui_state = Arc::clone(gui_state);
|
||||
|
||||
tokio::spawn(DockerData::init(
|
||||
app_data, docker, docker_rx, docker_tx, gui_state,
|
||||
tokio::spawn(DockerData::start(
|
||||
Arc::clone(app_data),
|
||||
docker,
|
||||
docker_rx,
|
||||
docker_tx,
|
||||
Arc::clone(gui_state),
|
||||
));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
app_data
|
||||
.lock()
|
||||
.set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
|
||||
}
|
||||
} else {
|
||||
app_data
|
||||
.lock()
|
||||
.set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create data for, and then spawn a tokio thread, for the input handler
|
||||
fn handler_init(
|
||||
@@ -86,15 +84,12 @@ fn handler_init(
|
||||
input_rx: Receiver<InputMessages>,
|
||||
is_running: &Arc<AtomicBool>,
|
||||
) {
|
||||
let app_data = Arc::clone(app_data);
|
||||
let gui_state = Arc::clone(gui_state);
|
||||
let is_running = Arc::clone(is_running);
|
||||
tokio::spawn(input_handler::InputHandler::init(
|
||||
app_data,
|
||||
tokio::spawn(input_handler::InputHandler::start(
|
||||
Arc::clone(app_data),
|
||||
input_rx,
|
||||
docker_sx.clone(),
|
||||
gui_state,
|
||||
is_running,
|
||||
Arc::clone(gui_state),
|
||||
Arc::clone(is_running),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user