chore: merge release-v0.3.2 into main
This commit is contained in:
@@ -1,13 +1,15 @@
|
|||||||
### 2023-06-04
|
### 2023-08-28
|
||||||
|
|
||||||
### Chores
|
### Chores
|
||||||
+ github workflow ubuntu latest, build for x86 musl, [4fa841e6e74e3e10e3d3e82eac1a1ca1338814cf]
|
+ dependencies updated, [8ce5a1877a8c56d9bbab560c97e2596ea87cc4c0], [94a20584e6ef0701c9f36838b0dfbcd911698dbe], [29e02e0d1faae4a836c7e5cfd0d791338ff586e3], [8e4c2e686761df56920df2267b765ab1297c9972]
|
||||||
+ dependencies updated, [0caa92f6a4728d50d8b2d8f15d96a21112732ec5], [1fd1dfc75d6fa4e84451ebc845b9e1c730381f41]
|
+ `_typos.toml` added, [84ba1020939606abf4a287cbd1de1f3a10d3f0c0]
|
||||||
+ `Spans` -> `Line`, ratatui 0.21 update, [4679ddc885a9b35c901f3600b63fd9e86118264c], [0d37ac55018038363e5f92dc4215996f8cff7b2e]
|
|
||||||
+ `create_release.sh` updated, [7dec5f14a381d237c5e72fbf9551bcf398f93f3e]
|
|
||||||
|
|
||||||
### Fixes
|
### Features
|
||||||
+ workflow additional image fix, closes #29, [47cda44b8213cfb8c3807df6c43e3f5dc2452b57]
|
+ Custom hostname. `oxker` will use `$DOCKER_HOST` env if set, or one can use the cli argument `--host`, which takes priority over the `$DOCKER_HOST`, closes #30, [10950787649d2b66fc1e8cd8b85526df51479857]
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
+ `set_error()` takes `gui_state` and error enum, to make sure app_data & gui_state is in sync [62c78dfaa50a8d8c084f7fbf7e203b50aaa731ae]
|
||||||
|
+ `fn loading_spin` doesn't need to be async, [2e27462d1b3f0bdb27d7646511e36d0c9af07f3e]
|
||||||
|
|
||||||
|
|
||||||
see <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details
|
see <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.3.2'>v0.3.2</a>
|
||||||
|
### 2023-08-28
|
||||||
|
|
||||||
|
### Chores
|
||||||
|
+ dependencies updated, [8ce5a187](https://github.com/mrjackwills/oxker/commit/8ce5a1877a8c56d9bbab560c97e2596ea87cc4c0), [94a20584](https://github.com/mrjackwills/oxker/commit/94a20584e6ef0701c9f36838b0dfbcd911698dbe), [29e02e0d](https://github.com/mrjackwills/oxker/commit/29e02e0d1faae4a836c7e5cfd0d791338ff586e3), [8e4c2e68](https://github.com/mrjackwills/oxker/commit/8e4c2e686761df56920df2267b765ab1297c9972)
|
||||||
|
+ `_typos.toml` added, [84ba1020](https://github.com/mrjackwills/oxker/commit/84ba1020939606abf4a287cbd1de1f3a10d3f0c0)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
+ Custom hostname. `oxker` will use `$DOCKER_HOST` env if set, or one can use the cli argument `--host`, which takes priority over the `$DOCKER_HOST`, closes [#30](https://github.com/mrjackwills/oxker/issues/30), [10950787](https://github.com/mrjackwills/oxker/commit/10950787649d2b66fc1e8cd8b85526df51479857)
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
+ `set_error()` takes `gui_state` and error enum, to make sure app_data & gui_state is in sync [62c78dfa](https://github.com/mrjackwills/oxker/commit/62c78dfaa50a8d8c084f7fbf7e203b50aaa731ae)
|
||||||
|
+ `fn loading_spin` doesn't need to be async, [2e27462d](https://github.com/mrjackwills/oxker/commit/2e27462d1b3f0bdb27d7646511e36d0c9af07f3e)
|
||||||
|
|
||||||
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.3.1'>v0.3.1</a>
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.3.1'>v0.3.1</a>
|
||||||
### 2023-06-04
|
### 2023-06-04
|
||||||
|
|
||||||
|
|||||||
Generated
+306
-294
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oxker"
|
name = "oxker"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Jack Wills <email@mrjackwills.com>"]
|
authors = ["Jack Wills <email@mrjackwills.com>"]
|
||||||
description = "A simple tui to view & control docker containers"
|
description = "A simple tui to view & control docker containers"
|
||||||
@@ -15,15 +15,15 @@ categories = ["command-line-utilities"]
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bollard = "0.14"
|
bollard = "0.14"
|
||||||
cansi = "2.2"
|
cansi = "2.2"
|
||||||
clap={version="4.3", features = ["derive", "unicode", "color"] }
|
clap = { version = "4.3", features = ["derive", "unicode", "color"] }
|
||||||
crossterm = "0.26"
|
crossterm = "0.27"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
parking_lot = {version= "0.12"}
|
parking_lot = { version= "0.12" }
|
||||||
tokio = {version = "1.28", features=["full"]}
|
tokio = { version = "1.32", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
ratatui = "0.21"
|
ratatui = "0.23"
|
||||||
uuid = {version = "1.3", features = ["v4", "fast-rng"]}
|
uuid = { version = "1.4", features = ["v4", "fast-rng"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ Available command line arguments
|
|||||||
| argument|result|
|
| argument|result|
|
||||||
|--|--|
|
|--|--|
|
||||||
|```-d [number > 0]```| set the minimum update interval for docker information, in ms, defaults to 1000 (1 second) |
|
|```-d [number > 0]```| set the minimum update interval for docker information, in ms, defaults to 1000 (1 second) |
|
||||||
|
|```--host [hostname]```| connect to Docker with a custom hostname, defaults to `/var/run/docker.sock`, will use `$DOCKER_HOST` env if set |
|
||||||
|```-r```| show raw logs, by default oxker will remove ANSI formatting (conflicts with -c) |
|
|```-r```| show raw logs, by default oxker will remove ANSI formatting (conflicts with -c) |
|
||||||
|```-c```| attempt to color the logs (conflicts with -r) |
|
|```-c```| attempt to color the logs (conflicts with -r) |
|
||||||
|```-t```| remove timestamps from each log entry |
|
|```-t```| remove timestamps from each log entry |
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[default.extend-words]
|
||||||
|
ratatui = "ratatui"
|
||||||
+3
-5
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# rust create_release
|
# rust create_release
|
||||||
# v0.2.2
|
# v0.3.0
|
||||||
|
|
||||||
STAR_LINE='****************************************'
|
STAR_LINE='****************************************'
|
||||||
CWD=$(pwd)
|
CWD=$(pwd)
|
||||||
@@ -50,9 +50,7 @@ update_patch () {
|
|||||||
|
|
||||||
# Get the url of the github repo, strip .git from the end of it
|
# Get the url of the github repo, strip .git from the end of it
|
||||||
get_git_remote_url() {
|
get_git_remote_url() {
|
||||||
REMOTE_ORIGIN=$(git config --get remote.origin.url)
|
GIT_REPO_URL="$(git config --get remote.origin.url | sed 's/\.git$//')"
|
||||||
TO_REMOVE=".git"
|
|
||||||
GIT_REPO_URL="${REMOTE_ORIGIN//$TO_REMOVE}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that git status is clean
|
# Check that git status is clean
|
||||||
@@ -121,7 +119,7 @@ update_version_number_in_files () {
|
|||||||
# create new semver version based on user input
|
# create new semver version based on user input
|
||||||
# Set MAJOR MINOR PATCH
|
# Set MAJOR MINOR PATCH
|
||||||
check_tag () {
|
check_tag () {
|
||||||
LATEST_TAG=$(git describe --tags --abbrev=0 --always)
|
LATEST_TAG=$(git describe --tags "$(git rev-list --tags --max-count=1)")
|
||||||
echo -e "\nCurrent tag: ${PURPLE}${LATEST_TAG}${RESET}\n"
|
echo -e "\nCurrent tag: ${PURPLE}${LATEST_TAG}${RESET}\n"
|
||||||
echo -e "${YELLOW}Choose new tag version:${RESET}\n"
|
echo -e "${YELLOW}Choose new tag version:${RESET}\n"
|
||||||
if [[ $LATEST_TAG =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]
|
if [[ $LATEST_TAG =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]
|
||||||
|
|||||||
+13
-3
@@ -1,11 +1,20 @@
|
|||||||
use bollard::models::ContainerSummary;
|
use bollard::models::ContainerSummary;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use ratatui::widgets::{ListItem, ListState};
|
use ratatui::widgets::{ListItem, ListState};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::{
|
||||||
|
sync::Arc,
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
mod container_state;
|
mod container_state;
|
||||||
|
|
||||||
use crate::{app_error::AppError, parse_args::CliArgs, ui::log_sanitizer, ENTRY_POINT};
|
use crate::{
|
||||||
|
app_error::AppError,
|
||||||
|
parse_args::CliArgs,
|
||||||
|
ui::{log_sanitizer, GuiState, Status},
|
||||||
|
ENTRY_POINT,
|
||||||
|
};
|
||||||
pub use container_state::*;
|
pub use container_state::*;
|
||||||
|
|
||||||
/// Global app_state, stored in an Arc<Mutex>
|
/// Global app_state, stored in an Arc<Mutex>
|
||||||
@@ -389,7 +398,8 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// insert single app_state error
|
/// insert single app_state error
|
||||||
pub fn set_error(&mut self, error: AppError) {
|
pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) {
|
||||||
|
gui_state.lock().status_push(status);
|
||||||
self.error = Some(error);
|
self.error = Some(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-12
@@ -287,7 +287,7 @@ impl DockerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Animate the loading icon
|
/// Animate the loading icon
|
||||||
async fn loading_spin(loading_uuid: Uuid, gui_state: &Arc<Mutex<GuiState>>) -> JoinHandle<()> {
|
fn loading_spin(loading_uuid: Uuid, gui_state: &Arc<Mutex<GuiState>>) -> JoinHandle<()> {
|
||||||
let gui_state = Arc::clone(gui_state);
|
let gui_state = Arc::clone(gui_state);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
@@ -311,7 +311,7 @@ impl DockerData {
|
|||||||
async fn initialise_container_data(&mut self) {
|
async fn initialise_container_data(&mut self) {
|
||||||
self.gui_state.lock().status_push(Status::Init);
|
self.gui_state.lock().status_push(Status::Init);
|
||||||
let loading_uuid = Uuid::new_v4();
|
let loading_uuid = Uuid::new_v4();
|
||||||
let loading_spin = Self::loading_spin(loading_uuid, &Arc::clone(&self.gui_state)).await;
|
let loading_spin = Self::loading_spin(loading_uuid, &Arc::clone(&self.gui_state));
|
||||||
|
|
||||||
let all_ids = self.update_all_containers().await;
|
let all_ids = self.update_all_containers().await;
|
||||||
|
|
||||||
@@ -323,8 +323,8 @@ impl DockerData {
|
|||||||
while !self.app_data.lock().initialised(&all_ids) {
|
while !self.app_data.lock().initialised(&all_ids) {
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
self.gui_state.lock().status_del(Status::Init);
|
|
||||||
Self::stop_loading_spin(&self.gui_state, &loading_spin, loading_uuid);
|
Self::stop_loading_spin(&self.gui_state, &loading_spin, loading_uuid);
|
||||||
|
self.gui_state.lock().status_del(Status::Init);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the global error as the docker error, and set gui_state to error
|
/// Set the global error as the docker error, and set gui_state to error
|
||||||
@@ -333,8 +333,9 @@ impl DockerData {
|
|||||||
error: DockerControls,
|
error: DockerControls,
|
||||||
gui_state: &Arc<Mutex<GuiState>>,
|
gui_state: &Arc<Mutex<GuiState>>,
|
||||||
) {
|
) {
|
||||||
app_data.lock().set_error(AppError::DockerCommand(error));
|
app_data
|
||||||
gui_state.lock().status_push(Status::Error);
|
.lock()
|
||||||
|
.set_error(AppError::DockerCommand(error), gui_state, Status::Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle incoming messages, container controls & all container information update
|
/// Handle incoming messages, container controls & all container information update
|
||||||
@@ -349,7 +350,7 @@ impl DockerData {
|
|||||||
match message {
|
match message {
|
||||||
DockerMessage::Pause(id) => {
|
DockerMessage::Pause(id) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let loading_spin = Self::loading_spin(uuid, &gui_state).await;
|
let loading_spin = Self::loading_spin(uuid, &gui_state);
|
||||||
if docker.pause_container(id.get()).await.is_err() {
|
if docker.pause_container(id.get()).await.is_err() {
|
||||||
Self::set_error(&app_data, DockerControls::Pause, &gui_state);
|
Self::set_error(&app_data, DockerControls::Pause, &gui_state);
|
||||||
}
|
}
|
||||||
@@ -359,7 +360,7 @@ impl DockerData {
|
|||||||
}
|
}
|
||||||
DockerMessage::Restart(id) => {
|
DockerMessage::Restart(id) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let loading_spin = Self::loading_spin(uuid, &gui_state).await;
|
let loading_spin = Self::loading_spin(uuid, &gui_state);
|
||||||
if docker.restart_container(id.get(), None).await.is_err() {
|
if docker.restart_container(id.get(), None).await.is_err() {
|
||||||
Self::set_error(&app_data, DockerControls::Restart, &gui_state);
|
Self::set_error(&app_data, DockerControls::Restart, &gui_state);
|
||||||
}
|
}
|
||||||
@@ -369,7 +370,7 @@ impl DockerData {
|
|||||||
}
|
}
|
||||||
DockerMessage::Start(id) => {
|
DockerMessage::Start(id) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let loading_spin = Self::loading_spin(uuid, &gui_state).await;
|
let loading_spin = Self::loading_spin(uuid, &gui_state);
|
||||||
if docker
|
if docker
|
||||||
.start_container(id.get(), None::<StartContainerOptions<String>>)
|
.start_container(id.get(), None::<StartContainerOptions<String>>)
|
||||||
.await
|
.await
|
||||||
@@ -383,7 +384,7 @@ impl DockerData {
|
|||||||
}
|
}
|
||||||
DockerMessage::Stop(id) => {
|
DockerMessage::Stop(id) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let loading_spin = Self::loading_spin(uuid, &gui_state).await;
|
let loading_spin = Self::loading_spin(uuid, &gui_state);
|
||||||
if docker.stop_container(id.get(), None).await.is_err() {
|
if docker.stop_container(id.get(), None).await.is_err() {
|
||||||
Self::set_error(&app_data, DockerControls::Stop, &gui_state);
|
Self::set_error(&app_data, DockerControls::Stop, &gui_state);
|
||||||
}
|
}
|
||||||
@@ -393,7 +394,7 @@ impl DockerData {
|
|||||||
}
|
}
|
||||||
DockerMessage::Unpause(id) => {
|
DockerMessage::Unpause(id) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let loading_spin = Self::loading_spin(uuid, &gui_state).await;
|
let loading_spin = Self::loading_spin(uuid, &gui_state);
|
||||||
if docker.unpause_container(id.get()).await.is_err() {
|
if docker.unpause_container(id.get()).await.is_err() {
|
||||||
Self::set_error(&app_data, DockerControls::Unpause, &gui_state);
|
Self::set_error(&app_data, DockerControls::Unpause, &gui_state);
|
||||||
}
|
}
|
||||||
@@ -403,7 +404,7 @@ impl DockerData {
|
|||||||
}
|
}
|
||||||
DockerMessage::Delete(id) => {
|
DockerMessage::Delete(id) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let loading_spin = Self::loading_spin(uuid, &gui_state).await;
|
let loading_spin = Self::loading_spin(uuid, &gui_state);
|
||||||
if docker
|
if docker
|
||||||
.remove_container(
|
.remove_container(
|
||||||
id.get(),
|
id.get(),
|
||||||
@@ -448,7 +449,7 @@ impl DockerData {
|
|||||||
gui_state: Arc<Mutex<GuiState>>,
|
gui_state: Arc<Mutex<GuiState>>,
|
||||||
is_running: Arc<AtomicBool>,
|
is_running: Arc<AtomicBool>,
|
||||||
) {
|
) {
|
||||||
let args = app_data.lock().args;
|
let args = app_data.lock().args.clone();
|
||||||
if app_data.lock().get_error().is_none() {
|
if app_data.lock().get_error().is_none() {
|
||||||
let mut inner = Self {
|
let mut inner = Self {
|
||||||
app_data,
|
app_data,
|
||||||
|
|||||||
+37
-23
@@ -20,6 +20,7 @@ use crate::{
|
|||||||
app_error::AppError,
|
app_error::AppError,
|
||||||
docker_data::DockerMessage,
|
docker_data::DockerMessage,
|
||||||
ui::{DeleteButton, GuiState, SelectablePanel, Status, Ui},
|
ui::{DeleteButton, GuiState, SelectablePanel, Status, Ui},
|
||||||
|
value_capture,
|
||||||
};
|
};
|
||||||
pub use message::InputMessages;
|
pub use message::InputMessages;
|
||||||
|
|
||||||
@@ -62,12 +63,11 @@ impl InputHandler {
|
|||||||
match message {
|
match message {
|
||||||
InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await,
|
InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await,
|
||||||
InputMessages::MouseEvent(mouse_event) => {
|
InputMessages::MouseEvent(mouse_event) => {
|
||||||
let error_or_help = self.gui_state.lock().status_contains(&[
|
if !self.gui_state.lock().status_contains(&[
|
||||||
Status::Error,
|
Status::Error,
|
||||||
Status::Help,
|
Status::Help,
|
||||||
Status::DeleteConfirm,
|
Status::DeleteConfirm,
|
||||||
]);
|
]) {
|
||||||
if !error_or_help {
|
|
||||||
self.mouse_press(mouse_event);
|
self.mouse_press(mouse_event);
|
||||||
}
|
}
|
||||||
let delete_confirm = self
|
let delete_confirm = self
|
||||||
@@ -93,18 +93,22 @@ impl InputHandler {
|
|||||||
.lock()
|
.lock()
|
||||||
.set_info_box("✖ mouse capture disabled".to_owned());
|
.set_info_box("✖ mouse capture disabled".to_owned());
|
||||||
} else {
|
} else {
|
||||||
self.app_data
|
self.app_data.lock().set_error(
|
||||||
.lock()
|
AppError::MouseCapture(false),
|
||||||
.set_error(AppError::MouseCapture(false));
|
&self.gui_state,
|
||||||
self.gui_state.lock().status_push(Status::Error);
|
Status::Error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if Ui::enable_mouse_capture().is_ok() {
|
} else if Ui::enable_mouse_capture().is_ok() {
|
||||||
self.gui_state
|
self.gui_state
|
||||||
.lock()
|
.lock()
|
||||||
.set_info_box("✓ mouse capture enabled".to_owned());
|
.set_info_box("✓ mouse capture enabled".to_owned());
|
||||||
} else {
|
} else {
|
||||||
self.app_data.lock().set_error(AppError::MouseCapture(true));
|
self.app_data.lock().set_error(
|
||||||
self.gui_state.lock().status_push(Status::Error);
|
AppError::MouseCapture(true),
|
||||||
|
&self.gui_state,
|
||||||
|
Status::Error,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the info box sleep handle is currently being executed, as in 'm' is pressed twice within a 4000ms window
|
// If the info box sleep handle is currently being executed, as in 'm' is pressed twice within a 4000ms window
|
||||||
@@ -160,13 +164,21 @@ impl InputHandler {
|
|||||||
/// Handle any keyboard button events
|
/// Handle any keyboard button events
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
async fn button_press(&mut self, key_code: KeyCode, key_modififer: KeyModifiers) {
|
async fn button_press(&mut self, key_code: KeyCode, key_modififer: KeyModifiers) {
|
||||||
// TODO - refactor this to a single call, maybe return Error, Help or Normal
|
value_capture!(
|
||||||
let contains_error = self.gui_state.lock().status_contains(&[Status::Error]);
|
contains_delete,
|
||||||
let contains_help = self.gui_state.lock().status_contains(&[Status::Help]);
|
self.gui_state
|
||||||
let contains_delete = self
|
|
||||||
.gui_state
|
|
||||||
.lock()
|
.lock()
|
||||||
.status_contains(&[Status::DeleteConfirm]);
|
.status_contains(&[Status::DeleteConfirm])
|
||||||
|
);
|
||||||
|
|
||||||
|
value_capture!(
|
||||||
|
contains_error,
|
||||||
|
self.gui_state.lock().status_contains(&[Status::Error])
|
||||||
|
);
|
||||||
|
value_capture!(
|
||||||
|
contains_help,
|
||||||
|
self.gui_state.lock().status_contains(&[Status::Help])
|
||||||
|
);
|
||||||
|
|
||||||
// Always just quit on Ctrl + c/C or q/Q
|
// Always just quit on Ctrl + c/C or q/Q
|
||||||
let is_c = || key_code == KeyCode::Char('c') || key_code == KeyCode::Char('C');
|
let is_c = || key_code == KeyCode::Char('c') || key_code == KeyCode::Char('C');
|
||||||
@@ -208,10 +220,9 @@ impl InputHandler {
|
|||||||
KeyCode::Char('m' | 'M') => self.m_key(),
|
KeyCode::Char('m' | 'M') => self.m_key(),
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
// Skip control panel if no containers, could be refactored
|
// Skip control panel if no containers, could be refactored
|
||||||
let has_containers = self.app_data.lock().get_container_len() == 0;
|
|
||||||
let is_containers =
|
let is_containers =
|
||||||
self.gui_state.lock().selected_panel == SelectablePanel::Containers;
|
self.gui_state.lock().selected_panel == SelectablePanel::Containers;
|
||||||
let count = if has_containers && is_containers {
|
let count = if self.app_data.lock().get_container_len() == 0 && is_containers {
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
@@ -222,10 +233,9 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
KeyCode::BackTab => {
|
KeyCode::BackTab => {
|
||||||
// Skip control panel if no containers, could be refactored
|
// Skip control panel if no containers, could be refactored
|
||||||
let has_containers = self.app_data.lock().get_container_len() == 0;
|
|
||||||
let is_containers =
|
let is_containers =
|
||||||
self.gui_state.lock().selected_panel == SelectablePanel::Logs;
|
self.gui_state.lock().selected_panel == SelectablePanel::Logs;
|
||||||
let count = if has_containers && is_containers {
|
let count = if self.app_data.lock().get_container_len() == 0 && is_containers {
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
@@ -236,7 +246,8 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
KeyCode::Home => {
|
KeyCode::Home => {
|
||||||
let mut locked_data = self.app_data.lock();
|
let mut locked_data = self.app_data.lock();
|
||||||
match self.gui_state.lock().selected_panel {
|
let selected_panel = self.gui_state.lock().selected_panel;
|
||||||
|
match selected_panel {
|
||||||
SelectablePanel::Containers => locked_data.containers_start(),
|
SelectablePanel::Containers => locked_data.containers_start(),
|
||||||
SelectablePanel::Logs => locked_data.log_start(),
|
SelectablePanel::Logs => locked_data.log_start(),
|
||||||
SelectablePanel::Commands => locked_data.docker_command_start(),
|
SelectablePanel::Commands => locked_data.docker_command_start(),
|
||||||
@@ -244,7 +255,8 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
KeyCode::End => {
|
KeyCode::End => {
|
||||||
let mut locked_data = self.app_data.lock();
|
let mut locked_data = self.app_data.lock();
|
||||||
match self.gui_state.lock().selected_panel {
|
let selected_panel = self.gui_state.lock().selected_panel;
|
||||||
|
match selected_panel {
|
||||||
SelectablePanel::Containers => locked_data.containers_end(),
|
SelectablePanel::Containers => locked_data.containers_end(),
|
||||||
SelectablePanel::Logs => locked_data.log_end(),
|
SelectablePanel::Logs => locked_data.log_end(),
|
||||||
SelectablePanel::Commands => locked_data.docker_command_end(),
|
SelectablePanel::Commands => locked_data.docker_command_end(),
|
||||||
@@ -358,7 +370,8 @@ impl InputHandler {
|
|||||||
/// Change state to next, depending which panel is currently in focus
|
/// Change state to next, depending which panel is currently in focus
|
||||||
fn next(&mut self) {
|
fn next(&mut self) {
|
||||||
let mut locked_data = self.app_data.lock();
|
let mut locked_data = self.app_data.lock();
|
||||||
match self.gui_state.lock().selected_panel {
|
let selected_panel = self.gui_state.lock().selected_panel;
|
||||||
|
match selected_panel {
|
||||||
SelectablePanel::Containers => locked_data.containers_next(),
|
SelectablePanel::Containers => locked_data.containers_next(),
|
||||||
SelectablePanel::Logs => locked_data.log_next(),
|
SelectablePanel::Logs => locked_data.log_next(),
|
||||||
SelectablePanel::Commands => locked_data.docker_command_next(),
|
SelectablePanel::Commands => locked_data.docker_command_next(),
|
||||||
@@ -368,7 +381,8 @@ impl InputHandler {
|
|||||||
/// Change state to previous, depending which panel is currently in focus
|
/// Change state to previous, depending which panel is currently in focus
|
||||||
fn previous(&mut self) {
|
fn previous(&mut self) {
|
||||||
let mut locked_data = self.app_data.lock();
|
let mut locked_data = self.app_data.lock();
|
||||||
match self.gui_state.lock().selected_panel {
|
let selected_panel = self.gui_state.lock().selected_panel;
|
||||||
|
match selected_panel {
|
||||||
SelectablePanel::Containers => locked_data.containers_previous(),
|
SelectablePanel::Containers => locked_data.containers_previous(),
|
||||||
SelectablePanel::Logs => locked_data.log_previous(),
|
SelectablePanel::Logs => locked_data.log_previous(),
|
||||||
SelectablePanel::Commands => locked_data.docker_command_previous(),
|
SelectablePanel::Commands => locked_data.docker_command_previous(),
|
||||||
|
|||||||
+50
-14
@@ -17,17 +17,20 @@
|
|||||||
|
|
||||||
use app_data::AppData;
|
use app_data::AppData;
|
||||||
use app_error::AppError;
|
use app_error::AppError;
|
||||||
use bollard::Docker;
|
use bollard::{Docker, API_DEFAULT_VERSION};
|
||||||
use docker_data::DockerData;
|
use docker_data::DockerData;
|
||||||
use input_handler::InputMessages;
|
use input_handler::InputMessages;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use parse_args::CliArgs;
|
use parse_args::CliArgs;
|
||||||
use std::sync::{
|
use std::{
|
||||||
|
process,
|
||||||
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{info, Level};
|
use tracing::{error, info, Level};
|
||||||
|
|
||||||
mod app_data;
|
mod app_data;
|
||||||
mod app_error;
|
mod app_error;
|
||||||
@@ -44,6 +47,7 @@ use crate::docker_data::DockerMessage;
|
|||||||
const ENTRY_POINT: &str = "/app/oxker";
|
const ENTRY_POINT: &str = "/app/oxker";
|
||||||
const ENV_KEY: &str = "OXKER_RUNTIME";
|
const ENV_KEY: &str = "OXKER_RUNTIME";
|
||||||
const ENV_VALUE: &str = "container";
|
const ENV_VALUE: &str = "container";
|
||||||
|
const DOCKER_HOST: &str = "DOCKER_HOST";
|
||||||
|
|
||||||
/// Enable tracing, only really used in debug mode, for now
|
/// Enable tracing, only really used in debug mode, for now
|
||||||
/// write to file if `-g` is set?
|
/// write to file if `-g` is set?
|
||||||
@@ -62,6 +66,18 @@ fn check_if_containerised() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the optional docker_host path, the cli args take priority over the DOCKER_HOST env
|
||||||
|
fn read_docker_host(args: &CliArgs) -> Option<String> {
|
||||||
|
args.host.as_ref().map_or_else(
|
||||||
|
|| {
|
||||||
|
std::env::vars()
|
||||||
|
.find(|x| x.0 == DOCKER_HOST)
|
||||||
|
.map(|(_, val)| val)
|
||||||
|
},
|
||||||
|
|x| Some(x.to_string()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create docker daemon handler, and only spawn up the docker data handler if a ping returns non-error
|
/// Create docker daemon handler, and only spawn up the docker data handler if a ping returns non-error
|
||||||
async fn docker_init(
|
async fn docker_init(
|
||||||
app_data: &Arc<Mutex<AppData>>,
|
app_data: &Arc<Mutex<AppData>>,
|
||||||
@@ -69,8 +85,13 @@ async fn docker_init(
|
|||||||
docker_rx: Receiver<DockerMessage>,
|
docker_rx: Receiver<DockerMessage>,
|
||||||
gui_state: &Arc<Mutex<GuiState>>,
|
gui_state: &Arc<Mutex<GuiState>>,
|
||||||
is_running: &Arc<AtomicBool>,
|
is_running: &Arc<AtomicBool>,
|
||||||
|
host: Option<String>,
|
||||||
) {
|
) {
|
||||||
if let Ok(docker) = Docker::connect_with_socket_defaults() {
|
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() {
|
if docker.ping().await.is_ok() {
|
||||||
let app_data = Arc::clone(app_data);
|
let app_data = Arc::clone(app_data);
|
||||||
let gui_state = Arc::clone(gui_state);
|
let gui_state = Arc::clone(gui_state);
|
||||||
@@ -84,12 +105,14 @@ async fn docker_init(
|
|||||||
is_running,
|
is_running,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
app_data.lock().set_error(AppError::DockerConnect);
|
app_data
|
||||||
gui_state.lock().status_push(Status::DockerConnect);
|
.lock()
|
||||||
|
.set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
app_data.lock().set_error(AppError::DockerConnect);
|
app_data
|
||||||
gui_state.lock().status_push(Status::DockerConnect);
|
.lock()
|
||||||
|
.set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,23 +143,36 @@ async fn main() {
|
|||||||
setup_tracing();
|
setup_tracing();
|
||||||
|
|
||||||
let args = CliArgs::new();
|
let args = CliArgs::new();
|
||||||
let app_data = Arc::new(Mutex::new(AppData::default(args)));
|
let host = read_docker_host(&args);
|
||||||
|
|
||||||
|
let app_data = Arc::new(Mutex::new(AppData::default(args.clone())));
|
||||||
let gui_state = Arc::new(Mutex::new(GuiState::default()));
|
let gui_state = Arc::new(Mutex::new(GuiState::default()));
|
||||||
let is_running = Arc::new(AtomicBool::new(true));
|
let is_running = Arc::new(AtomicBool::new(true));
|
||||||
let (docker_sx, docker_rx) = tokio::sync::mpsc::channel(32);
|
let (docker_sx, docker_rx) = tokio::sync::mpsc::channel(32);
|
||||||
let (input_sx, input_rx) = tokio::sync::mpsc::channel(32);
|
|
||||||
|
|
||||||
docker_init(&app_data, containerised, docker_rx, &gui_state, &is_running).await;
|
docker_init(
|
||||||
|
&app_data,
|
||||||
handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running);
|
containerised,
|
||||||
|
docker_rx,
|
||||||
|
&gui_state,
|
||||||
|
&is_running,
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
if args.gui {
|
if args.gui {
|
||||||
|
let (input_sx, input_rx) = tokio::sync::mpsc::channel(32);
|
||||||
|
handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running);
|
||||||
Ui::create(app_data, docker_sx, gui_state, is_running, input_sx).await;
|
Ui::create(app_data, docker_sx, gui_state, is_running, input_sx).await;
|
||||||
} else {
|
} else {
|
||||||
info!("in debug mode");
|
info!("in debug mode");
|
||||||
while is_running.load(Ordering::SeqCst) {
|
|
||||||
// Debug mode for testing, mostly pointless, doesn't take terminal
|
// Debug mode for testing, mostly pointless, doesn't take terminal
|
||||||
|
while is_running.load(Ordering::SeqCst) {
|
||||||
loop {
|
loop {
|
||||||
|
if let Some(err) = app_data.lock().get_error() {
|
||||||
|
error!("{}", err);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
docker_sx.send(DockerMessage::Update).await.ok();
|
docker_sx.send(DockerMessage::Update).await.ok();
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(u64::from(
|
tokio::time::sleep(std::time::Duration::from_millis(u64::from(
|
||||||
args.docker_interval,
|
args.docker_interval,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::process;
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
#[derive(Parser, Debug, Clone, Copy)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[command(version, about)]
|
#[command(version, about)]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
@@ -19,6 +19,10 @@ pub struct CliArgs {
|
|||||||
#[clap(short = 'c', conflicts_with = "raw")]
|
#[clap(short = 'c', conflicts_with = "raw")]
|
||||||
pub color: bool,
|
pub color: bool,
|
||||||
|
|
||||||
|
/// Docker host, defaults to `/var/run/docker.sock`
|
||||||
|
#[clap(long, short = None)]
|
||||||
|
pub host: Option<String>,
|
||||||
|
|
||||||
/// Show raw logs, default is to remove ansi formatting, conflicts with "-c"
|
/// Show raw logs, default is to remove ansi formatting, conflicts with "-c"
|
||||||
#[clap(short = 'r', conflicts_with = "color")]
|
#[clap(short = 'r', conflicts_with = "color")]
|
||||||
pub raw: bool,
|
pub raw: bool,
|
||||||
@@ -46,6 +50,7 @@ impl CliArgs {
|
|||||||
Self {
|
Self {
|
||||||
color: args.color,
|
color: args.color,
|
||||||
docker_interval: args.docker_interval,
|
docker_interval: args.docker_interval,
|
||||||
|
host: args.host,
|
||||||
gui: !args.gui,
|
gui: !args.gui,
|
||||||
show_self: !args.show_self,
|
show_self: !args.show_self,
|
||||||
raw: args.raw,
|
raw: args.raw,
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ fn generate_block<'a>(
|
|||||||
gui_state
|
gui_state
|
||||||
.lock()
|
.lock()
|
||||||
.update_region_map(Region::Panel(panel), area);
|
.update_region_map(Region::Panel(panel), area);
|
||||||
let current_selected_panel = gui_state.lock().selected_panel;
|
|
||||||
let mut title = match panel {
|
let mut title = match panel {
|
||||||
SelectablePanel::Containers => {
|
SelectablePanel::Containers => {
|
||||||
format!("{} {}", panel.title(), app_data.lock().container_title())
|
format!("{} {}", panel.title(), app_data.lock().container_title())
|
||||||
@@ -79,7 +78,7 @@ fn generate_block<'a>(
|
|||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.title(title);
|
.title(title);
|
||||||
if current_selected_panel == panel {
|
if gui_state.lock().selected_panel == panel {
|
||||||
block = block.border_style(Style::default().fg(Color::LightCyan));
|
block = block.border_style(Style::default().fg(Color::LightCyan));
|
||||||
}
|
}
|
||||||
block
|
block
|
||||||
@@ -819,6 +818,11 @@ pub fn delete_confirm<B: Backend>(
|
|||||||
let no_area = split_buttons[1];
|
let no_area = split_buttons[1];
|
||||||
let yes_area = split_buttons[3];
|
let yes_area = split_buttons[3];
|
||||||
|
|
||||||
|
f.render_widget(Clear, area);
|
||||||
|
f.render_widget(block, area);
|
||||||
|
f.render_widget(confirm_para, split_popup[1]);
|
||||||
|
f.render_widget(no_para, no_area);
|
||||||
|
f.render_widget(yes_para, yes_area);
|
||||||
// Insert button areas into region map, so can interact with them on click
|
// Insert button areas into region map, so can interact with them on click
|
||||||
gui_state
|
gui_state
|
||||||
.lock()
|
.lock()
|
||||||
@@ -827,12 +831,6 @@ pub fn delete_confirm<B: Backend>(
|
|||||||
gui_state
|
gui_state
|
||||||
.lock()
|
.lock()
|
||||||
.update_region_map(Region::Delete(DeleteButton::Yes), yes_area);
|
.update_region_map(Region::Delete(DeleteButton::Yes), yes_area);
|
||||||
|
|
||||||
f.render_widget(Clear, area);
|
|
||||||
f.render_widget(block, area);
|
|
||||||
f.render_widget(confirm_para, split_popup[1]);
|
|
||||||
f.render_widget(no_para, no_area);
|
|
||||||
f.render_widget(yes_para, yes_area);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw an error popup over whole screen
|
/// Draw an error popup over whole screen
|
||||||
|
|||||||
+19
-63
@@ -1,8 +1,5 @@
|
|||||||
use ratatui::layout::{Constraint, Rect};
|
use ratatui::layout::{Constraint, Rect};
|
||||||
use std::{
|
use std::collections::{HashMap, HashSet};
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
fmt,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::app_data::{ContainerId, Header};
|
use crate::app_data::{ContainerId, Header};
|
||||||
@@ -145,60 +142,12 @@ impl BoxLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State for the loading animation
|
// loading animation frames
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
const FRAMES: [char; 10] = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||||
pub enum Loading {
|
const FRAMES_LEN: u8 = 9;
|
||||||
#[default]
|
|
||||||
One,
|
|
||||||
Two,
|
|
||||||
Three,
|
|
||||||
Four,
|
|
||||||
Five,
|
|
||||||
Six,
|
|
||||||
Seven,
|
|
||||||
Eight,
|
|
||||||
Nine,
|
|
||||||
Ten,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Loading {
|
|
||||||
pub const fn next(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::One => Self::Two,
|
|
||||||
Self::Two => Self::Three,
|
|
||||||
Self::Three => Self::Four,
|
|
||||||
Self::Four => Self::Five,
|
|
||||||
Self::Five => Self::Six,
|
|
||||||
Self::Six => Self::Seven,
|
|
||||||
Self::Seven => Self::Eight,
|
|
||||||
Self::Eight => Self::Nine,
|
|
||||||
Self::Nine => Self::Ten,
|
|
||||||
Self::Ten => Self::One,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Loading {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let disp = match self {
|
|
||||||
Self::One => '⠋',
|
|
||||||
Self::Two => '⠙',
|
|
||||||
Self::Three => '⠹',
|
|
||||||
Self::Four => '⠸',
|
|
||||||
Self::Five => '⠼',
|
|
||||||
Self::Six => '⠴',
|
|
||||||
Self::Seven => '⠦',
|
|
||||||
Self::Eight => '⠧',
|
|
||||||
Self::Nine => '⠇',
|
|
||||||
Self::Ten => '⠏',
|
|
||||||
};
|
|
||||||
write!(f, "{disp}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The application gui state can be in multiple of these four states at the same time
|
/// The application gui state can be in multiple of these four states at the same time
|
||||||
/// Various functions (e.g input handler), operate differently depending upon current Status
|
/// Various functions (e.g input handler), operate differently depending upon current Status
|
||||||
// Copy
|
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Init,
|
Init,
|
||||||
@@ -213,7 +162,7 @@ pub enum Status {
|
|||||||
pub struct GuiState {
|
pub struct GuiState {
|
||||||
heading_map: HashMap<Header, Rect>,
|
heading_map: HashMap<Header, Rect>,
|
||||||
is_loading: HashSet<Uuid>,
|
is_loading: HashSet<Uuid>,
|
||||||
loading_icon: Loading,
|
loading_index: u8,
|
||||||
panel_map: HashMap<SelectablePanel, Rect>,
|
panel_map: HashMap<SelectablePanel, Rect>,
|
||||||
delete_map: HashMap<DeleteButton, Rect>,
|
delete_map: HashMap<DeleteButton, Rect>,
|
||||||
status: HashSet<Status>,
|
status: HashSet<Status>,
|
||||||
@@ -327,24 +276,31 @@ impl GuiState {
|
|||||||
self.selected_panel = self.selected_panel.prev();
|
self.selected_panel = self.selected_panel.prev();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new loading_uuid into HashSet, and advance the animation by one frame
|
/// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array
|
||||||
pub fn next_loading(&mut self, uuid: Uuid) {
|
pub fn next_loading(&mut self, uuid: Uuid) {
|
||||||
self.loading_icon = self.loading_icon.next();
|
if self.loading_index == FRAMES_LEN {
|
||||||
|
self.loading_index = 0;
|
||||||
|
} else {
|
||||||
|
self.loading_index += 1;
|
||||||
|
}
|
||||||
self.is_loading.insert(uuid);
|
self.is_loading.insert(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If is_loading has any entries, return the current loading_icon, else an empty string, which needs to take up the same space, hence ' '
|
/// If is_loading has any entries, return the char at FRAMES[index], else an empty char, which needs to take up the same space, hence ' '
|
||||||
pub fn get_loading(&mut self) -> String {
|
pub fn get_loading(&mut self) -> char {
|
||||||
if self.is_loading.is_empty() {
|
if self.is_loading.is_empty() {
|
||||||
String::from(" ")
|
' '
|
||||||
} else {
|
} else {
|
||||||
self.loading_icon.to_string()
|
FRAMES[usize::from(self.loading_index)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a loading_uuid from the is_loading HashSet
|
/// Remove a loading_uuid from the is_loading HashSet, if empty, reset loading_index to 0
|
||||||
pub fn remove_loading(&mut self, uuid: Uuid) {
|
pub fn remove_loading(&mut self, uuid: Uuid) {
|
||||||
self.is_loading.remove(&uuid);
|
self.is_loading.remove(&uuid);
|
||||||
|
if self.is_loading.is_empty() {
|
||||||
|
self.loading_index = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set info box content
|
/// Set info box content
|
||||||
|
|||||||
+22
-19
@@ -197,28 +197,34 @@ 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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw the main ui to a frame of the terminal
|
/// Draw the main ui to a frame of the terminal
|
||||||
/// TODO add a single line area for debug message - if not in release mode, maybe with #[cfg(debug_assertions)] ?
|
/// TODO add a single line area for debug message - if not in release mode?
|
||||||
fn draw_frame<B: Backend>(
|
fn draw_frame<B: Backend>(
|
||||||
f: &mut Frame<'_, B>,
|
f: &mut Frame<'_, B>,
|
||||||
app_data: &Arc<Mutex<AppData>>,
|
app_data: &Arc<Mutex<AppData>>,
|
||||||
gui_state: &Arc<Mutex<GuiState>>,
|
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);
|
||||||
|
value_capture!(sorted_by, app_data.lock().get_sorted());
|
||||||
|
value_capture!(delete_confirm, gui_state.lock().get_delete_container());
|
||||||
|
value_capture!(has_error, app_data.lock().get_error());
|
||||||
|
value_capture!(info_text, gui_state.lock().info_box_text.clone());
|
||||||
|
value_capture!(loading_icon, gui_state.lock().get_loading().to_string());
|
||||||
|
|
||||||
// set max height for container section, needs +5 to deal with docker commands list and borders
|
// set max height for container section, needs +5 to deal with docker commands list and borders
|
||||||
let height = app_data.lock().get_container_len();
|
|
||||||
let height = if height < 12 { height + 5 } else { 12 };
|
let height = if height < 12 { height + 5 } else { 12 };
|
||||||
|
|
||||||
let column_widths = app_data.lock().get_width();
|
|
||||||
let has_containers = app_data.lock().get_container_len() > 0;
|
|
||||||
let has_error = app_data.lock().get_error();
|
|
||||||
let sorted_by = app_data.lock().get_sorted();
|
|
||||||
|
|
||||||
let delete_confirm = gui_state.lock().get_delete_container();
|
|
||||||
|
|
||||||
let show_help = gui_state.lock().status_contains(&[Status::Help]);
|
|
||||||
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)
|
||||||
.constraints([Constraint::Min(1), Constraint::Min(100)].as_ref())
|
.constraints([Constraint::Min(1), Constraint::Min(100)].as_ref())
|
||||||
@@ -261,10 +267,6 @@ fn draw_frame<B: Backend>(
|
|||||||
|
|
||||||
draw_blocks::containers(app_data, top_panel[0], f, gui_state, &column_widths);
|
draw_blocks::containers(app_data, top_panel[0], f, gui_state, &column_widths);
|
||||||
|
|
||||||
if has_containers {
|
|
||||||
draw_blocks::commands(app_data, top_panel[1], f, gui_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_blocks::logs(app_data, lower_main[0], f, gui_state, &loading_icon);
|
draw_blocks::logs(app_data, lower_main[0], f, gui_state, &loading_icon);
|
||||||
|
|
||||||
draw_blocks::heading_bar(
|
draw_blocks::heading_bar(
|
||||||
@@ -290,8 +292,9 @@ fn draw_frame<B: Backend>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// only draw charts if there are containers
|
// only draw commands + charts if there are containers
|
||||||
if has_containers {
|
if has_containers {
|
||||||
|
draw_blocks::commands(app_data, top_panel[1], f, gui_state);
|
||||||
draw_blocks::chart(f, lower_main[1], app_data);
|
draw_blocks::chart(f, lower_main[1], app_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +303,7 @@ fn draw_frame<B: Backend>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if error, and show popup if so
|
// Check if error, and show popup if so
|
||||||
if show_help {
|
if gui_state.lock().status_contains(&[Status::Help]) {
|
||||||
draw_blocks::help_box(f);
|
draw_blocks::help_box(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user