chore: merge release-v0.1.6 into main

This commit is contained in:
Jack Wills
2022-10-16 02:15:16 +00:00
14 changed files with 297 additions and 200 deletions
+10 -11
View File
@@ -1,21 +1,20 @@
### 2022-10-07 ### 2022-10-16
### Chores ### Chores
+ Update clap to v4, [15597dbe6942ec053541398ce0e9dedc10a4d3ea] + Cargo update, [c3e72ae7369a25d903f39e55a4349cb005671dd4]
+ create_release.sh v0.1.0, [3c8d59c666bd4cda9ca54989b2f1b48bba17bc57]
+ uuid updated to version 1.2, [438ad770f4a5ecb5f4bbc308066ad9e808f66514]
### Docs ### Fixes
+ readme.md updated, [a05bf561cc6d96237f683ab0b3c782d6841974d9] + loading icon shifting error fix, also make icon white, closes #15, [59797685dffa29752a48c98e6cf465884d6d9df6]
### Features ### Features
+ use newtype construct for container id, [41cbb84f2896f8be2c37eba87e390d998aff7382] + Show container name in log panel title, closes #16, [9cb0c414afc284947fc2b8494504387e4e7edd87]
+ use gui_state HashSet to keep track of application gui state, [9e9d51559a13944622abf4fcbd3bd63766d11467]
+ terminal.clear() after run_app finished, [67c49575682cb271fac0998ff377a6504cd0bc86]
### Refactors ### Refactors
+ Impl Copy where able to, [e76878f424d72b943713ef84e95e25fada77d79e] + CpuStats & MemStats use tuple struct, [a060d032586a0707ac91cb13d922aae0850449c5]
+ replace async fn with just fn, [17dc604befac75cb9dc0311a0e43f9927fe0ca30]
+ remove pointless clone()'s & variable declarations, [6731002ee42c9460042c2c38aff5101b1bcebbe6]
+ replace String::from("") with String::new(), [62fb22478697cc9a7ab9fb562a724965b437233a]
+ replace map_or_else with map_or, [3e26f292c7dc5e13af4580952767ebe821aa5183], [5660b34d5149dce27706ff6daa90b854e6f84e14]
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
+23 -5
View File
@@ -1,7 +1,25 @@
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.6'>v0.1.6</a>
### 2022-10-16
### Chores
+ Cargo update, [c3e72ae7](https://github.com/mrjackwills/oxker/commit/c3e72ae7369a25d903f39e55a4349cb005671dd4),
+ create_release.sh v0.1.0, [3c8d59c6](https://github.com/mrjackwills/oxker/commit/3c8d59c666bd4cda9ca54989b2f1b48bba17bc57),
+ uuid updated to version 1.2, [438ad770](https://github.com/mrjackwills/oxker/commit/438ad770f4a5ecb5f4bbc308066ad9e808f66514),
### Fixes
+ loading icon shifting error fix, also make icon white, closes [#15](https://github.com/mrjackwills/oxker/issues/15), [59797685](https://github.com/mrjackwills/oxker/commit/59797685dffa29752a48c98e6cf465884d6d9df6),
### Features
+ Show container name in log panel title, closes [#16](https://github.com/mrjackwills/oxker/issues/16), [9cb0c414](https://github.com/mrjackwills/oxker/commit/9cb0c414afc284947fc2b8494504387e4e7edd87),
+ use gui_state HashSet to keep track of application gui state, [9e9d5155](https://github.com/mrjackwills/oxker/commit/9e9d51559a13944622abf4fcbd3bd63766d11467),
+ terminal.clear() after run_app finished, [67c49575](https://github.com/mrjackwills/oxker/commit/67c49575682cb271fac0998ff377a6504cd0bc86),
### Refactors
+ CpuStats & MemStats use tuple struct, [a060d032](https://github.com/mrjackwills/oxker/commit/a060d032586a0707ac91cb13d922aae0850449c5),
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.5'>v0.1.5</a> # <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.5'>v0.1.5</a>
### 2022-10-07 ### 2022-10-07
### Chores ### Chores
+ Update clap to v4, [15597dbe](https://github.com/mrjackwills/oxker/commit/15597dbe6942ec053541398ce0e9dedc10a4d3ea), + Update clap to v4, [15597dbe](https://github.com/mrjackwills/oxker/commit/15597dbe6942ec053541398ce0e9dedc10a4d3ea),
@@ -16,13 +34,13 @@
+ replace async fn with just fn, [17dc604b](https://github.com/mrjackwills/oxker/commit/17dc604befac75cb9dc0311a0e43f9927fe0ca30), + replace async fn with just fn, [17dc604b](https://github.com/mrjackwills/oxker/commit/17dc604befac75cb9dc0311a0e43f9927fe0ca30),
+ remove pointless clone()'s & variable declarations, [6731002e](https://github.com/mrjackwills/oxker/commit/6731002ee42c9460042c2c38aff5101b1bcebbe6), + remove pointless clone()'s & variable declarations, [6731002e](https://github.com/mrjackwills/oxker/commit/6731002ee42c9460042c2c38aff5101b1bcebbe6),
+ replace String::from("") with String::new(), [62fb2247](https://github.com/mrjackwills/oxker/commit/62fb22478697cc9a7ab9fb562a724965b437233a), + replace String::from("") with String::new(), [62fb2247](https://github.com/mrjackwills/oxker/commit/62fb22478697cc9a7ab9fb562a724965b437233a),
+ replace map_or_else with map_or, [3e26f292](https://github.com/mrjackwills/oxker/commit/3e26f292c7dc5e13af4580952767ebe821aa5183),, [5660b34d](https://github.com/mrjackwills/oxker/commit/5660b34d5149dce27706ff6daa90b854e6f84e14), + replace map_or_else with map_or, [3e26f292](https://github.com/mrjackwills/oxker/commit/3e26f292c7dc5e13af4580952767ebe821aa5183), [5660b34d](https://github.com/mrjackwills/oxker/commit/5660b34d5149dce27706ff6daa90b854e6f84e14),
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.4'>v0.1.4</a> # <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.4'>v0.1.4</a>
### 2022-09-07 ### 2022-09-07
### Chores ### Chores
+ dependencies updated, [a3168daa](https://github.com/mrjackwills/oxker/commit/a3168daa3f769a6747dfbe61103073a7e80a1485),, [78e59160](https://github.com/mrjackwills/oxker/commit/78e59160bb6a978ee80e3a99eb72f051fb64e737), + dependencies updated, [a3168daa](https://github.com/mrjackwills/oxker/commit/a3168daa3f769a6747dfbe61103073a7e80a1485),[78e59160](https://github.com/mrjackwills/oxker/commit/78e59160bb6a978ee80e3a99eb72f051fb64e737),
### Features ### Features
+ containerize self, github action to build and push to [Docker Hub](https://hub.docker.com/r/mrjackwills/oxker), [07f97202](https://github.com/mrjackwills/oxker/commit/07f972022a69f22bac57925e6ad84234381f7890), + containerize self, github action to build and push to [Docker Hub](https://hub.docker.com/r/mrjackwills/oxker), [07f97202](https://github.com/mrjackwills/oxker/commit/07f972022a69f22bac57925e6ad84234381f7890),
@@ -138,9 +156,9 @@
### 2022-04-29 ### 2022-04-29
### Features ### Features
+ allow toggling of mouse caputre, to select & copy text with mouse, closes #2, [aec184ea](https://github.com/mrjackwills/oxker/commit/aec184ea22b289e91942a4c3e6a415685884bc47), + allow toggling of mouse caputre, to select & copy text with mouse, closes [#2](https://github.com/mrjackwills/oxker/issues/2), [aec184ea](https://github.com/mrjackwills/oxker/commit/aec184ea22b289e91942a4c3e6a415685884bc47),
+ show id column, [b10f9274](https://github.com/mrjackwills/oxker/commit/b10f927481c9e38a48c1d4b94e744ec48e8b6ba6), + show id column, [b10f9274](https://github.com/mrjackwills/oxker/commit/b10f927481c9e38a48c1d4b94e744ec48e8b6ba6),
+ draw_popup, using enum to draw in one of 9 areas, closes #6, [1017850a](https://github.com/mrjackwills/oxker/commit/1017850a6cc91328abc1127bdb117495f5e909d8), + draw_popup, using enum to draw in one of 9 areas, closes [#6](https://github.com/mrjackwills/oxker/issues/6), [1017850a](https://github.com/mrjackwills/oxker/commit/1017850a6cc91328abc1127bdb117495f5e909d8),
+ use a message rx/sx for all docker commands, remove update loop, wait for update message from gui instead, [9b70fdfa](https://github.com/mrjackwills/oxker/commit/9b70fdfad7b38361ebee301bdc2545d3f0dfcf9e), + use a message rx/sx for all docker commands, remove update loop, wait for update message from gui instead, [9b70fdfa](https://github.com/mrjackwills/oxker/commit/9b70fdfad7b38361ebee301bdc2545d3f0dfcf9e),
### Fixes ### Fixes
Generated
+15 -15
View File
@@ -101,9 +101,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.0.10" version = "4.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b1a0a4208c6c483b952ad35c6eed505fc13b46f08f631b81e828084a9318d74" checksum = "6bf8832993da70a4c6d13c581f4463c2bdda27b9bf1c5498dc4365543abe6d6f"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
@@ -118,9 +118,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.0.10" version = "4.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db342ce9fda24fb191e2ed4e102055a4d381c1086a06630174cd8da8d5d917ce" checksum = "c42f169caba89a7d512b5418b09864543eeb4d497416c917d7137863bd2076ad"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error", "proc-macro-error",
@@ -433,9 +433,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.134" version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -514,7 +514,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "oxker" name = "oxker"
version = "0.1.5" version = "0.1.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bollard", "bollard",
@@ -623,9 +623,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.46" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -712,9 +712,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.85" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@@ -1034,9 +1034,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@@ -1072,9 +1072,9 @@ dependencies = [
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.1.2" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"rand", "rand",
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "oxker" name = "oxker"
version = "0.1.5" version = "0.1.6"
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"
@@ -23,7 +23,7 @@ tokio = {version = "1.21", features=["full"]}
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
tui = "0.19" tui = "0.19"
uuid = {version = "1.1", features = ["v4", "fast-rng"]} uuid = {version = "1.2", features = ["v4", "fast-rng"]}
[dev-dependencies] [dev-dependencies]
+9 -9
View File
@@ -4,15 +4,15 @@
case $TARGETARCH in case $TARGETARCH in
"amd64") "amd64")
echo "x86_64-unknown-linux-musl" > /.platform echo "x86_64-unknown-linux-musl" > /.platform
echo "" > /.compiler echo "" > /.compiler
;; ;;
"arm64") "arm64")
echo "aarch64-unknown-linux-musl" > /.platform echo "aarch64-unknown-linux-musl" > /.platform
echo "gcc-aarch64-linux-gnu" > /.compiler echo "gcc-aarch64-linux-gnu" > /.compiler
;; ;;
"arm") "arm")
echo "arm-unknown-linux-musleabihf" > /.platform echo "arm-unknown-linux-musleabihf" > /.platform
echo "gcc-arm-linux-gnueabihf" > /.compiler echo "gcc-arm-linux-gnueabihf" > /.compiler
;; ;;
esac esac
+41 -15
View File
@@ -1,9 +1,8 @@
#!/bin/bash #!/bin/bash
# rust create_release # rust create_release
# v0.0.15 # v0.1.2
PACKAGE_NAME='oxker'
STAR_LINE='****************************************' STAR_LINE='****************************************'
CWD=$(pwd) CWD=$(pwd)
@@ -20,11 +19,6 @@ error_close() {
exit 1 exit 1
} }
if [ -z "$PACKAGE_NAME" ]
then
error_close "No package name"
fi
# $1 string - question to ask # $1 string - question to ask
ask_yn () { ask_yn () {
printf "%b%s? [y/N]:%b " "${GREEN}" "$1" "${RESET}" printf "%b%s? [y/N]:%b " "${GREEN}" "$1" "${RESET}"
@@ -114,8 +108,8 @@ update_release_body_and_changelog () {
sed -i -E "s=(\s)\[([0-9a-f]{8})([0-9a-f]{32})\]= [\2](${GIT_REPO_URL}/commit/\2\3),=g" ./CHANGELOG.md sed -i -E "s=(\s)\[([0-9a-f]{8})([0-9a-f]{32})\]= [\2](${GIT_REPO_URL}/commit/\2\3),=g" ./CHANGELOG.md
# Update changelog to add links to closed issues - comma included! # Update changelog to add links to closed issues - comma included!
# "closes [#1]," -> "closes [#1](https:/www.../issues/1),"" # "closes #1," -> "closes [#1](https:/www.../issues/1),""
sed -i -r -E "s=closes \[#([0-9]+)\],=closes [#\1](${GIT_REPO_URL}/issues/\1),=g" ./CHANGELOG.md sed -i -r -E "s=closes \#([0-9]+)\,=closes [#\1](${GIT_REPO_URL}/issues/\1),=g" ./CHANGELOG.md
} }
# update version in cargo.toml, to match selected current version # update version in cargo.toml, to match selected current version
@@ -193,34 +187,66 @@ cargo_build () {
ask_continue ask_continue
} }
# $1 text to colourise
release_continue () {
echo -e "\n${PURPLE}$1${RESET}"
ask_continue
}
# Full flow to create a new release # Full flow to create a new release
release_flow() { release_flow() {
check_git check_git
get_git_remote_url get_git_remote_url
cargo_test cargo_test
cargo_build cargo_build
cd "${CWD}" || error_close "Can't find ${CWD}" cd "${CWD}" || error_close "Can't find ${CWD}"
check_tag check_tag
NEW_TAG_WITH_V="v${MAJOR}.${MINOR}.${PATCH}" NEW_TAG_WITH_V="v${MAJOR}.${MINOR}.${PATCH}"
printf "\nnew tag chosen: %s\n\n" "${NEW_TAG_WITH_V}" printf "\nnew tag chosen: %s\n\n" "${NEW_TAG_WITH_V}"
RELEASE_BRANCH=release-$NEW_TAG_WITH_V RELEASE_BRANCH=release-$NEW_TAG_WITH_V
echo -e echo -e
ask_changelog_update ask_changelog_update
release_continue "checkout ${RELEASE_BRANCH}"
git checkout -b "$RELEASE_BRANCH" git checkout -b "$RELEASE_BRANCH"
update_version_number_in_files
cargo fmt
git add .
git commit -m "chore: release $NEW_TAG_WITH_V"
release_continue "update_version_number_in_files"
update_version_number_in_files
echo -e "\ncargo fmt"
cargo fmt
release_continue "git add ."
git add .
release_continue "git commit -m \"chore: release \"${NEW_TAG_WITH_V}\""
git commit -m "chore: release ${NEW_TAG_WITH_V}"
release_continue "git checkout main"
git checkout main git checkout main
release_continue "git merge --no-ff \"${RELEASE_BRANCH}\" -m \"chore: merge ${RELEASE_BRANCH} into main\""
git merge --no-ff "$RELEASE_BRANCH" -m "chore: merge ${RELEASE_BRANCH} into main" git merge --no-ff "$RELEASE_BRANCH" -m "chore: merge ${RELEASE_BRANCH} into main"
release_continue "git tag -am \"${RELEASE_BRANCH}\" \"$NEW_TAG_WITH_V\""
git tag -am "${RELEASE_BRANCH}" "$NEW_TAG_WITH_V" git tag -am "${RELEASE_BRANCH}" "$NEW_TAG_WITH_V"
echo "git tag -am \"${RELEASE_BRANCH}\" \"$NEW_TAG_WITH_V\""
release_continue "git push --atomic origin main \"$NEW_TAG_WITH_V\""
git push --atomic origin main "$NEW_TAG_WITH_V" git push --atomic origin main "$NEW_TAG_WITH_V"
release_continue "git checkout dev"
git checkout dev git checkout dev
git merge --no-ff main -m 'chore: merge main into dev'
release_continue "git merge --no-ff main -m \"chore: merge main into dev\""
git merge --no-ff main -m "chore: merge main into dev"
release_continue "git push origin dev"
git push origin dev git push origin dev
release_continue "git branch -d \"$RELEASE_BRANCH\""
git branch -d "$RELEASE_BRANCH" git branch -d "$RELEASE_BRANCH"
} }
+19 -22
View File
@@ -107,6 +107,7 @@ impl<T> StatefulList<T> {
} }
} }
/// Return the current status of the select list, e.g. 2/5,
pub fn get_state_title(&self) -> String { pub fn get_state_title(&self) -> String {
if self.items.is_empty() { if self.items.is_empty() {
String::new() String::new()
@@ -254,13 +255,11 @@ pub trait Stats {
/// So can use custom display formatter /// So can use custom display formatter
/// Use trait Stats for use as generic in draw_chart function /// Use trait Stats for use as generic in draw_chart function
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct CpuStats { pub struct CpuStats(f64);
value: f64,
}
impl CpuStats { impl CpuStats {
pub const fn new(value: f64) -> Self { pub const fn new(value: f64) -> Self {
Self { value } Self(value)
} }
} }
@@ -268,21 +267,21 @@ impl Eq for CpuStats {}
impl PartialEq for CpuStats { impl PartialEq for CpuStats {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.value == other.value self.0 == other.0
} }
} }
impl PartialOrd for CpuStats { impl PartialOrd for CpuStats {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.value.partial_cmp(&other.value) self.0.partial_cmp(&other.0)
} }
} }
impl Ord for CpuStats { impl Ord for CpuStats {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
if self.value > other.value { if self.0 > other.0 {
Ordering::Greater Ordering::Greater
} else if (self.value - other.value).abs() < 0.01 { } else if (self.0 - other.0).abs() < 0.01 {
Ordering::Equal Ordering::Equal
} else { } else {
Ordering::Less Ordering::Less
@@ -292,13 +291,13 @@ impl Ord for CpuStats {
impl Stats for CpuStats { impl Stats for CpuStats {
fn get_value(&self) -> f64 { fn get_value(&self) -> f64 {
self.value self.0
} }
} }
impl fmt::Display for CpuStats { impl fmt::Display for CpuStats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disp = format!("{:05.2}%", self.value); let disp = format!("{:05.2}%", self.0);
write!(f, "{:>x$}", disp, x = f.width().unwrap_or(1)) write!(f, "{:>x$}", disp, x = f.width().unwrap_or(1))
} }
} }
@@ -307,41 +306,39 @@ impl fmt::Display for CpuStats {
/// So can use custom display formatter /// So can use custom display formatter
/// Use trait Stats for use as generic in draw_chart function /// Use trait Stats for use as generic in draw_chart function
#[derive(Debug, Default, Clone, Copy, Eq)] #[derive(Debug, Default, Clone, Copy, Eq)]
pub struct ByteStats { pub struct ByteStats(u64);
value: u64,
}
impl PartialEq for ByteStats { impl PartialEq for ByteStats {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.value == other.value self.0 == other.0
} }
} }
impl PartialOrd for ByteStats { impl PartialOrd for ByteStats {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.value.partial_cmp(&other.value) self.0.partial_cmp(&other.0)
} }
} }
impl Ord for ByteStats { impl Ord for ByteStats {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value) self.0.cmp(&other.0)
} }
} }
impl ByteStats { impl ByteStats {
pub const fn new(value: u64) -> Self { pub const fn new(value: u64) -> Self {
Self { value } Self(value)
} }
pub fn update(&mut self, value: u64) { pub fn update(&mut self, value: u64) {
self.value = value; self.0 = value;
} }
} }
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
impl Stats for ByteStats { impl Stats for ByteStats {
fn get_value(&self) -> f64 { fn get_value(&self) -> f64 {
self.value as f64 self.0 as f64
} }
} }
@@ -353,7 +350,7 @@ impl fmt::Display for ByteStats {
x if x >= ONE_GB => format!("{y:.2} GB", y = as_f64 / ONE_GB), x if x >= ONE_GB => format!("{y:.2} GB", y = as_f64 / ONE_GB),
x if x >= ONE_MB => format!("{y:.2} MB", y = as_f64 / ONE_MB), x if x >= ONE_MB => format!("{y:.2} MB", y = as_f64 / ONE_MB),
x if x >= ONE_KB => format!("{y:.2} kB", y = as_f64 / ONE_KB), x if x >= ONE_KB => format!("{y:.2} kB", y = as_f64 / ONE_KB),
_ => format!("{} B", self.value), _ => format!("{} B", self.0),
}; };
write!(f, "{:>x$}", p, x = f.width().unwrap_or(1)) write!(f, "{:>x$}", p, x = f.width().unwrap_or(1))
} }
@@ -426,7 +423,7 @@ impl ContainerItem {
self.cpu_stats self.cpu_stats
.iter() .iter()
.enumerate() .enumerate()
.map(|i| (i.0 as f64, i.1.value as f64)) .map(|i| (i.0 as f64, i.1 .0 as f64))
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
@@ -436,7 +433,7 @@ impl ContainerItem {
self.mem_stats self.mem_stats
.iter() .iter()
.enumerate() .enumerate()
.map(|i| (i.0 as f64, i.1.value as f64)) .map(|i| (i.0 as f64, i.1 .0 as f64))
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
+12 -7
View File
@@ -14,10 +14,8 @@ pub struct AppData {
args: CliArgs, args: CliArgs,
error: Option<AppError>, error: Option<AppError>,
logs_parsed: bool, logs_parsed: bool,
pub containers: StatefulList<ContainerItem>,
pub init: bool,
pub show_error: bool,
sorted_by: Option<(Header, SortedOrder)>, sorted_by: Option<(Header, SortedOrder)>,
pub containers: StatefulList<ContainerItem>,
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
@@ -79,9 +77,7 @@ impl AppData {
args, args,
containers: StatefulList::new(vec![]), containers: StatefulList::new(vec![]),
error: None, error: None,
init: false,
logs_parsed: false, logs_parsed: false,
show_error: false,
sorted_by: None, sorted_by: None,
} }
} }
@@ -257,11 +253,20 @@ impl AppData {
} }
/// Get the title for log panel for selected container /// Get the title for log panel for selected container
/// will be "logs x/x" /// will be either
/// 1) "logs x/x - container_name" where container_name is 32 chars max
/// 2) "logs - container_name" when no logs found, again 32 chars max
pub fn get_log_title(&self) -> String { pub fn get_log_title(&self) -> String {
self.get_selected_log_index() self.get_selected_log_index()
.map_or("".to_owned(), |index| { .map_or("".to_owned(), |index| {
self.containers.items[index].logs.get_state_title() let logs_len = self.containers.items[index].logs.get_state_title();
let mut name = self.containers.items[index].name.clone();
name.truncate(32);
if logs_len.is_empty() {
format!("- {} ", name)
} else {
format!("{} - {}", logs_len, name)
}
}) })
} }
+43 -45
View File
@@ -3,7 +3,7 @@ use bollard::{
service::ContainerSummary, service::ContainerSummary,
Docker, Docker,
}; };
use futures_util::StreamExt; use futures_util::{Future, StreamExt};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@@ -19,7 +19,7 @@ use crate::{
app_data::{AppData, ContainerId, DockerControls}, app_data::{AppData, ContainerId, DockerControls},
app_error::AppError, app_error::AppError,
parse_args::CliArgs, parse_args::CliArgs,
ui::GuiState, ui::{GuiState, Status},
}; };
mod message; mod message;
pub use message::DockerMessage; pub use message::DockerMessage;
@@ -316,8 +316,9 @@ impl DockerData {
self.gui_state.lock().remove_loading(loading_uuid); self.gui_state.lock().remove_loading(loading_uuid);
} }
// Initialize docker container data, before any messages are received /// Initialize docker container data, before any messages are received
async fn initialise_container_data(&mut self) { async fn initialise_container_data(&mut self) {
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).await; let loading_spin = self.loading_spin(loading_uuid).await;
@@ -336,65 +337,62 @@ impl DockerData {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
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.gui_state.lock().status_del(Status::Init);
self.stop_loading_spin(&loading_spin, loading_uuid); self.stop_loading_spin(&loading_spin, loading_uuid);
} }
/// Set the global error as the docker error, and set gui_state to error
fn set_error(&mut self, error: DockerControls) {
self.app_data
.lock()
.set_error(AppError::DockerCommand(error));
self.gui_state.lock().status_push(Status::Error);
}
/// Execute a docker command, will start and stop the loading spinner, and set correct error
async fn exec_docker(
&mut self,
docker_fn: impl Future<Output = Result<(), bollard::errors::Error>> + Send,
control: DockerControls,
) {
let uuid = Uuid::new_v4();
let loading_spin = self.loading_spin(uuid).await;
if docker_fn.await.is_err() {
self.set_error(control);
};
self.stop_loading_spin(&loading_spin, uuid);
}
/// Handle incoming messages, container controls & all container information update /// Handle incoming messages, container controls & all container information update
async fn message_handler(&mut self) { async fn message_handler(&mut self) {
while let Some(message) = self.receiver.recv().await { while let Some(message) = self.receiver.recv().await {
let docker = Arc::clone(&self.docker); let docker = Arc::clone(&self.docker);
let app_data = Arc::clone(&self.app_data);
let loading_uuid = Uuid::new_v4();
match message { match message {
DockerMessage::Pause(id) => { DockerMessage::Pause(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(docker.pause_container(id.get()), DockerControls::Pause)
if docker.pause_container(id.get()).await.is_err() { .await;
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Pause));
};
self.stop_loading_spin(&loading_spin, loading_uuid);
} }
DockerMessage::Restart(id) => { DockerMessage::Restart(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(
if docker.restart_container(id.get(), None).await.is_err() { docker.restart_container(id.get(), None),
app_data DockerControls::Restart,
.lock() )
.set_error(AppError::DockerCommand(DockerControls::Restart)); .await;
};
self.stop_loading_spin(&loading_spin, loading_uuid);
} }
DockerMessage::Start(id) => { DockerMessage::Start(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(
if docker docker.start_container(id.get(), None::<StartContainerOptions<String>>),
.start_container(id.get(), None::<StartContainerOptions<String>>) DockerControls::Start,
.await )
.is_err() .await;
{
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Start));
};
self.stop_loading_spin(&loading_spin, loading_uuid);
} }
DockerMessage::Stop(id) => { DockerMessage::Stop(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(docker.stop_container(id.get(), None), DockerControls::Stop)
if docker.stop_container(id.get(), None).await.is_err() { .await;
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Stop));
};
self.stop_loading_spin(&loading_spin, loading_uuid);
} }
DockerMessage::Unpause(id) => { DockerMessage::Unpause(id) => {
let loading_spin = self.loading_spin(loading_uuid).await; self.exec_docker(docker.unpause_container(id.get()), DockerControls::Unpause)
if docker.unpause_container(id.get()).await.is_err() { .await;
app_data
.lock()
.set_error(AppError::DockerCommand(DockerControls::Unpause));
};
self.stop_loading_spin(&loading_spin, loading_uuid);
self.update_everything().await; self.update_everything().await;
} }
DockerMessage::Update => self.update_everything().await, DockerMessage::Update => self.update_everything().await,
+30 -20
View File
@@ -21,7 +21,7 @@ use crate::{
app_data::{AppData, DockerControls, Header, SortedOrder}, app_data::{AppData, DockerControls, Header, SortedOrder},
app_error::AppError, app_error::AppError,
docker_data::DockerMessage, docker_data::DockerMessage,
ui::{GuiState, SelectablePanel}, ui::{GuiState, SelectablePanel, Status},
}; };
pub use message::InputMessages; pub use message::InputMessages;
@@ -64,9 +64,11 @@ impl InputHandler {
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) => {
let show_error = self.app_data.lock().show_error; let error_or_help = self
let show_info = self.gui_state.lock().show_help; .gui_state
if !show_error && !show_info { .lock()
.status_contains(&[Status::Error, Status::Help]);
if !error_or_help {
self.mouse_press(mouse_event); self.mouse_press(mouse_event);
} }
} }
@@ -85,10 +87,11 @@ impl InputHandler {
.gui_state .gui_state
.lock() .lock()
.set_info_box("✖ mouse capture disabled".to_owned()), .set_info_box("✖ mouse capture disabled".to_owned()),
Err(_) => self Err(_) => {
.app_data self.app_data
.lock() .lock()
.set_error(AppError::MouseCapture(false)), .set_error(AppError::MouseCapture(false));
}
} }
} else { } else {
match execute!(std::io::stdout(), EnableMouseCapture) { match execute!(std::io::stdout(), EnableMouseCapture) {
@@ -96,11 +99,13 @@ impl InputHandler {
.gui_state .gui_state
.lock() .lock()
.set_info_box("✓ mouse capture enabled".to_owned()), .set_info_box("✓ mouse capture enabled".to_owned()),
Err(_) => self.app_data.lock().set_error(AppError::MouseCapture(true)), Err(_) => {
self.app_data.lock().set_error(AppError::MouseCapture(true));
}
} }
}; };
// 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
// then cancel the first handle, as a new handle will be invoked // then cancel the first handle, as a new handle will be invoked
if let Some(info_sleep_timer) = self.info_sleep.as_ref() { if let Some(info_sleep_timer) = self.info_sleep.as_ref() {
info_sleep_timer.abort(); info_sleep_timer.abort();
@@ -129,32 +134,37 @@ impl InputHandler {
} }
/// Send a quit message to docker, to abort all spawns, if an error is return, set is_running to false here instead /// Send a quit message to docker, to abort all spawns, if an error is return, set is_running to false here instead
/// If gui_status is Error or Init, then just set the is_running to false immediately, for a quicker exit
async fn quit(&self) { async fn quit(&self) {
match self.docker_sender.send(DockerMessage::Quit).await { let error_init = self
Ok(_) => (), .gui_state
Err(_) => self.is_running.store(false, Ordering::SeqCst), .lock()
.status_contains(&[Status::Error, Status::Init]);
if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() {
self.is_running.store(false, Ordering::SeqCst);
} }
} }
/// 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) { async fn button_press(&mut self, key_code: KeyCode) {
let show_error = self.app_data.lock().show_error; // TODO - refactor this to a single call, maybe return Error, Help or Normal
let show_info = self.gui_state.lock().show_help; let contains_error = self.gui_state.lock().status_contains(&[Status::Error]);
let contains_help = self.gui_state.lock().status_contains(&[Status::Help]);
if show_error { if contains_error {
match key_code { match key_code {
KeyCode::Char('q' | 'Q') => self.quit().await, KeyCode::Char('q' | 'Q') => self.quit().await,
KeyCode::Char('c' | 'C') => { KeyCode::Char('c' | 'C') => {
self.app_data.lock().show_error = false;
self.app_data.lock().remove_error(); self.app_data.lock().remove_error();
self.gui_state.lock().status_del(Status::Error);
} }
_ => (), _ => (),
} }
} else if show_info { } else if contains_help {
match key_code { match key_code {
KeyCode::Char('q' | 'Q') => self.quit().await, KeyCode::Char('q' | 'Q') => self.quit().await,
KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = false, KeyCode::Char('h' | 'H') => self.gui_state.lock().status_del(Status::Help),
KeyCode::Char('m' | 'M') => self.m_key(), KeyCode::Char('m' | 'M') => self.m_key(),
_ => (), _ => (),
} }
@@ -171,7 +181,7 @@ impl InputHandler {
KeyCode::Char('8') => self.sort(Header::Rx), KeyCode::Char('8') => self.sort(Header::Rx),
KeyCode::Char('9') => self.sort(Header::Tx), KeyCode::Char('9') => self.sort(Header::Tx),
KeyCode::Char('q' | 'Q') => self.quit().await, KeyCode::Char('q' | 'Q') => self.quit().await,
KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = true, KeyCode::Char('h' | 'H') => self.gui_state.lock().status_push(Status::Help),
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
+20 -18
View File
@@ -24,7 +24,7 @@ mod input_handler;
mod parse_args; mod parse_args;
mod ui; mod ui;
use ui::{create_ui, GuiState}; use ui::{create_ui, GuiState, Status};
fn setup_tracing() { fn setup_tracing() {
tracing_subscriber::fmt().with_max_level(Level::INFO).init(); tracing_subscriber::fmt().with_max_level(Level::INFO).init();
@@ -45,23 +45,25 @@ async fn main() {
let (docker_sx, docker_rx) = tokio::sync::mpsc::channel(16); 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
match Docker::connect_with_socket_defaults() { if let Ok(docker) = Docker::connect_with_socket_defaults() {
Ok(docker) => match docker.ping().await { if docker.ping().await.is_ok() {
Ok(_) => { let docker = Arc::new(docker);
let docker = Arc::new(docker); let is_running = Arc::clone(&is_running);
let is_running = Arc::clone(&is_running); tokio::spawn(DockerData::init(
tokio::spawn(DockerData::init( args,
args, docker_app_data,
docker_app_data, docker,
docker, docker_gui_state,
docker_gui_state, docker_rx,
docker_rx, is_running,
is_running, ));
)); } else {
} app_data.lock().set_error(AppError::DockerConnect);
Err(_) => app_data.lock().set_error(AppError::DockerConnect), docker_gui_state.lock().status_push(Status::DockerConnect);
}, }
Err(_) => app_data.lock().set_error(AppError::DockerConnect), } else {
app_data.lock().set_error(AppError::DockerConnect);
docker_gui_state.lock().status_push(Status::DockerConnect);
} }
let input_app_data = Arc::clone(&app_data); let input_app_data = Arc::clone(&app_data);
+38 -20
View File
@@ -15,6 +15,7 @@ use tui::{
}; };
use crate::app_data::{Header, SortedOrder}; use crate::app_data::{Header, SortedOrder};
use crate::ui::Status;
use crate::{ use crate::{
app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats}, app_data::{AppData, ByteStats, Columns, CpuStats, State, Stats},
app_error::AppError, app_error::AppError,
@@ -50,7 +51,9 @@ fn generate_block<'a>(
gui_state: &Arc<Mutex<GuiState>>, gui_state: &Arc<Mutex<GuiState>>,
panel: SelectablePanel, panel: SelectablePanel,
) -> Block<'a> { ) -> Block<'a> {
gui_state.lock().update_map(Region::Panel(panel), area); gui_state
.lock()
.update_heading_map(Region::Panel(panel), area);
let current_selected_panel = gui_state.lock().selected_panel; let current_selected_panel = gui_state.lock().selected_panel;
let title = match panel { let title = match panel {
SelectablePanel::Containers => { SelectablePanel::Containers => {
@@ -215,9 +218,8 @@ pub fn logs<B: Backend>(
loading_icon: &str, loading_icon: &str,
) { ) {
let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs); let block = generate_block(app_data, area, gui_state, SelectablePanel::Logs);
let contains_init = gui_state.lock().status_contains(&[Status::Init]);
let init = app_data.lock().init; if contains_init {
if !init {
let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon)) let paragraph = Paragraph::new(format!("parsing logs {}", loading_icon))
.style(Style::default()) .style(Style::default())
.block(block) .block(block)
@@ -337,6 +339,7 @@ fn make_chart<'a, T: Stats + Display>(
} }
/// Draw heading bar at top of program, always visible /// Draw heading bar at top of program, always visible
/// TODO Should seperate into loading icon/headers/help functions
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn heading_bar<B: Backend>( pub fn heading_bar<B: Backend>(
area: Rect, area: Rect,
@@ -347,10 +350,10 @@ pub fn heading_bar<B: Backend>(
sorted_by: Option<(Header, SortedOrder)>, sorted_by: Option<(Header, SortedOrder)>,
gui_state: &Arc<Mutex<GuiState>>, gui_state: &Arc<Mutex<GuiState>>,
) { ) {
let block = || Block::default().style(Style::default().bg(Color::Magenta).fg(Color::Black)); let block = |fg: Color| Block::default().style(Style::default().bg(Color::Magenta).fg(fg));
let info_visible = gui_state.lock().show_help; let help_visible = gui_state.lock().status_contains(&[Status::Help]);
f.render_widget(block(), area); f.render_widget(block(Color::Black), area);
// Generate a bloack for the header, if the header is currently being used to sort a column, then highlight it white // Generate a bloack for the header, if the header is currently being used to sort a column, then highlight it white
let header_block = |x: &Header| { let header_block = |x: &Header| {
@@ -380,8 +383,7 @@ pub fn heading_bar<B: Backend>(
let block = header_block(header); let block = header_block(header);
let text = match header { let text = match header {
Header::State => format!( Header::State => format!(
" {}{:>width$}{ic}", "{:>width$}{ic}",
loading_icon,
header, header,
ic = block.1, ic = block.1,
width = width - block.2, width = width - block.2,
@@ -393,7 +395,6 @@ pub fn heading_bar<B: Backend>(
ic = block.1, ic = block.1,
width = width - block.2 width = width - block.2
), ),
_ => format!( _ => format!(
"{}{:>width$}{ic}", "{}{:>width$}{ic}",
MARGIN, MARGIN,
@@ -430,14 +431,15 @@ pub fn heading_bar<B: Backend>(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let suffix = if info_visible { "exit" } else { "show" }; let suffix = if help_visible { "exit" } else { "show" };
let info_text = format!("( h ) {} help {}", suffix, MARGIN); let info_text = format!("( h ) {} help {}", suffix, MARGIN,);
let info_width = info_text.chars().count(); let info_width = info_text.chars().count();
let column_width = usize::from(area.width) - info_width; let column_width = usize::from(area.width) - info_width;
let column_width = if column_width > 0 { column_width } else { 1 }; let column_width = if column_width > 0 { column_width } else { 1 };
let splits = if has_containers { let splits = if has_containers {
vec![ vec![
Constraint::Min(2),
Constraint::Min(column_width.try_into().unwrap_or_default()), Constraint::Min(column_width.try_into().unwrap_or_default()),
Constraint::Min(info_width.try_into().unwrap_or_default()), Constraint::Min(info_width.try_into().unwrap_or_default()),
] ]
@@ -450,28 +452,44 @@ pub fn heading_bar<B: Backend>(
.constraints(splits.as_ref()) .constraints(splits.as_ref())
.split(area); .split(area);
if has_containers { if has_containers {
let container_splits = header_data.iter().map(|i| i.2).collect::<Vec<_>>(); // Draw loading icon, or not, and a prefix with a single space
let loading_icon = format!("{:>2}", loading_icon);
let loading_paragraph = Paragraph::new(loading_icon)
.block(block(Color::White))
.alignment(Alignment::Center);
f.render_widget(loading_paragraph, split_bar[0]);
let container_splits = header_data.iter().map(|i| i.2).collect::<Vec<_>>();
let headers_section = Layout::default() let headers_section = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints(container_splits.as_ref()) .constraints(container_splits.as_ref())
.split(split_bar[0]); .split(split_bar[1]);
// draw the actual header blocks // draw the actual header blocks
for (index, (paragraph, header, _)) in header_data.into_iter().enumerate() { for (index, (paragraph, header, _)) in header_data.into_iter().enumerate() {
let rect = headers_section[index]; let rect = headers_section[index];
gui_state.lock().update_map(Region::Header(header), rect); gui_state
.lock()
.update_heading_map(Region::Header(header), rect);
f.render_widget(paragraph, rect); f.render_widget(paragraph, rect);
} }
} }
let paragraph = Paragraph::new(info_text) // show/hide help
.block(block()) let color = if help_visible {
Color::Black
} else {
Color::White
};
let help_paragraph = Paragraph::new(info_text)
.block(block(color))
.alignment(Alignment::Right); .alignment(Alignment::Right);
// If no containers, don't display the headers, could maybe do this first? // If no containers, don't display the headers, could maybe do this first?
let index = if has_containers { 1 } else { 0 }; let help_index = if has_containers { 2 } else { 0 };
f.render_widget(paragraph, split_bar[index]); // render help info
f.render_widget(help_paragraph, split_bar[help_index]);
} }
/// From a given &str, return the maximum number of chars on a single line /// From a given &str, return the maximum number of chars on a single line
@@ -487,7 +505,7 @@ fn max_line_width(text: &str) -> usize {
} }
/// Draw the help box in the centre of the screen /// Draw the help box in the centre of the screen
/// TODO this is message, should make every line it's own renderable span /// TODO should make every line it's own renderable span
pub fn help_box<B: Backend>(f: &mut Frame<'_, B>) { pub fn help_box<B: Backend>(f: &mut Frame<'_, B>) {
let title = format!(" {} ", VERSION); let title = format!(" {} ", VERSION);
+30 -4
View File
@@ -59,6 +59,7 @@ pub enum BoxLocation {
} }
impl BoxLocation { impl BoxLocation {
/// Screen is divided into 3x3 sections
pub const fn get_indexes(self) -> (usize, usize) { pub const fn get_indexes(self) -> (usize, usize) {
match self { match self {
Self::TopLeft => (0, 0), Self::TopLeft => (0, 0),
@@ -172,6 +173,16 @@ impl fmt::Display for Loading {
} }
} }
/// 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
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Status {
Init,
Help,
DockerConnect,
Error,
}
/// Global gui_state, stored in an Arc<Mutex> /// Global gui_state, stored in an Arc<Mutex>
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GuiState { pub struct GuiState {
@@ -179,8 +190,8 @@ pub struct GuiState {
heading_map: HashMap<Header, Rect>, heading_map: HashMap<Header, Rect>,
loading_icon: Loading, loading_icon: Loading,
is_loading: HashSet<Uuid>, is_loading: HashSet<Uuid>,
status: HashSet<Status>,
pub selected_panel: SelectablePanel, pub selected_panel: SelectablePanel,
pub show_help: bool,
pub info_box_text: Option<String>, pub info_box_text: Option<String>,
} }
impl GuiState { impl GuiState {
@@ -191,9 +202,9 @@ impl GuiState {
heading_map: HashMap::new(), heading_map: HashMap::new(),
loading_icon: Loading::One, loading_icon: Loading::One,
selected_panel: SelectablePanel::Containers, selected_panel: SelectablePanel::Containers,
show_help: false,
is_loading: HashSet::new(), is_loading: HashSet::new(),
info_box_text: None, info_box_text: None,
status: HashSet::new(),
} }
} }
@@ -226,7 +237,7 @@ impl GuiState {
} }
/// Insert, or updates header area panel into heading_map /// Insert, or updates header area panel into heading_map
pub fn update_map(&mut self, region: Region, area: Rect) { pub fn update_heading_map(&mut self, region: Region, area: Rect) {
match region { match region {
Region::Header(header) => self Region::Header(header) => self
.heading_map .heading_map
@@ -241,6 +252,21 @@ impl GuiState {
}; };
} }
/// Check if the current gui_status contains any of the given status'
pub fn status_contains(&self, status: &[Status]) -> bool {
status.iter().any(|i| self.status.contains(i))
}
/// Remove a gui_status into the current gui_status hashset
pub fn status_del(&mut self, status: Status) {
self.status.remove(&status);
}
/// Insert a gui_status into the current gui_status hashset
pub fn status_push(&mut self, status: Status) {
self.status.insert(status);
}
/// Change to next selectable panel /// Change to next selectable panel
pub fn next_panel(&mut self) { pub fn next_panel(&mut self) {
self.selected_panel = self.selected_panel.next(); self.selected_panel = self.selected_panel.next();
@@ -260,7 +286,7 @@ impl GuiState {
/// If is_loading has any entries, return the current loading_icon, else an emtpy string /// If is_loading has any entries, return the current loading_icon, else an emtpy string
pub fn get_loading(&mut self) -> String { pub fn get_loading(&mut self) -> String {
if self.is_loading.is_empty() { if self.is_loading.is_empty() {
String::new() String::from(" ")
} else { } else {
self.loading_icon.to_string() self.loading_icon.to_string()
} }
+5 -7
View File
@@ -25,7 +25,7 @@ mod draw_blocks;
mod gui_state; 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, Status};
use crate::{ use crate::{
app_data::AppData, app_error::AppError, docker_data::DockerMessage, app_data::AppData, app_error::AppError, docker_data::DockerMessage,
input_handler::InputMessages, input_handler::InputMessages,
@@ -56,6 +56,7 @@ pub async fn create_ui(
update_duration, update_duration,
) )
.await; .await;
terminal.clear()?;
disable_raw_mode()?; disable_raw_mode()?;
execute!( execute!(
@@ -82,10 +83,8 @@ async fn run_app<B: Backend + Send>(
update_duration: Duration, 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);
let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]);
// Check for docker connect errors before attempting to draw the gui if status_dockerconnect {
let e = app_data.lock().get_error();
if let Some(AppError::DockerConnect) = e {
let mut seconds = 5; let mut seconds = 5;
loop { loop {
if seconds < 1 { if seconds < 1 {
@@ -156,7 +155,7 @@ fn ui<B: Backend>(
let log_index = app_data.lock().get_selected_log_index(); let log_index = app_data.lock().get_selected_log_index();
let sorted_by = app_data.lock().get_sorted(); let sorted_by = app_data.lock().get_sorted();
let show_help = gui_state.lock().show_help; let show_help = gui_state.lock().status_contains(&[Status::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 loading_icon = gui_state.lock().get_loading();
@@ -240,7 +239,6 @@ fn ui<B: Backend>(
} }
if let Some(error) = has_error { if let Some(error) = has_error {
app_data.lock().show_error = true;
draw_blocks::error(f, error, None); draw_blocks::error(f, error, None);
} }
} }