Merge branch 'feat/docker_recv' into dev
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DockerMessage {
|
||||||
|
Update,
|
||||||
|
Start(String),
|
||||||
|
Restart(String),
|
||||||
|
Pause(String),
|
||||||
|
Unpause(String),
|
||||||
|
Stop(String),
|
||||||
|
}
|
||||||
+114
-49
@@ -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,
|
let gui_state = Arc::clone(&self.gui_state);
|
||||||
app_data: Arc<Mutex<AppData>>,
|
tokio::spawn(async move {
|
||||||
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);
|
|
||||||
// could also just loop while init is false, would need to move an arc mutex into here
|
|
||||||
// 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,23 +249,94 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests, use redis-test container, check logs exists, and selector of logs, and that it increases, and matches end, when you run restart on the docker containers
|
// tests, use redis-test container, check logs exists, and selector of logs, and that it increases, and matches end, when you run restart on the docker containers
|
||||||
|
|||||||
+39
-82
@@ -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(
|
.await
|
||||||
AppError::DockerCommand(
|
.unwrap(),
|
||||||
DockerControls::Pause,
|
DockerControls::Unpause => self
|
||||||
),
|
.docker_sender
|
||||||
)
|
.send(DockerMessage::Unpause(id))
|
||||||
},
|
.await
|
||||||
);
|
.unwrap(),
|
||||||
});
|
DockerControls::Start => self
|
||||||
}
|
.docker_sender
|
||||||
DockerControls::Unpause => {
|
.send(DockerMessage::Start(id))
|
||||||
tokio::spawn(async move {
|
.await
|
||||||
docker.unpause_container(&id).await.unwrap_or_else(
|
.unwrap(),
|
||||||
|_| {
|
DockerControls::Stop => self
|
||||||
app_data.lock().set_error(
|
.docker_sender
|
||||||
AppError::DockerCommand(
|
.send(DockerMessage::Stop(id))
|
||||||
DockerControls::Unpause,
|
.await
|
||||||
),
|
.unwrap(),
|
||||||
)
|
DockerControls::Restart => self
|
||||||
},
|
.docker_sender
|
||||||
);
|
.send(DockerMessage::Restart(id))
|
||||||
});
|
.await
|
||||||
}
|
.unwrap(),
|
||||||
DockerControls::Start => {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
docker
|
|
||||||
.start_container(
|
|
||||||
&id,
|
|
||||||
None::<StartContainerOptions<String>>,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
app_data.lock().set_error(
|
|
||||||
AppError::DockerCommand(
|
|
||||||
DockerControls::Start,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
app_data.lock().set_error(
|
|
||||||
AppError::DockerCommand(
|
|
||||||
DockerControls::Restart,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-8
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -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,16 +368,22 @@ 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$}",
|
||||||
MARGIN,
|
MARGIN,
|
||||||
columns.status.0,
|
columns.status.0,
|
||||||
width = columns.status.1
|
width = columns.status.1
|
||||||
@@ -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
@@ -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
@@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user