From d431f850219b28af2bc45f3b6917377604596a40 Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Sun, 19 Mar 2023 21:08:33 +0000
Subject: [PATCH 01/20] feat: use ratatui
---
Cargo.lock | 170 ++++++++++++++++----------------
Cargo.toml | 2 +-
README.md | 2 +-
src/app_data/container_state.rs | 2 +-
src/app_data/mod.rs | 2 +-
src/input_handler/mod.rs | 2 +-
src/ui/color_match.rs | 2 +-
src/ui/draw_blocks.rs | 4 +-
src/ui/gui_state.rs | 2 +-
src/ui/mod.rs | 2 +-
10 files changed, 96 insertions(+), 94 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 048dded..7a46926 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.69"
+version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
+checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "autocfg"
@@ -41,6 +41,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+[[package]]
+name = "bitflags"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
+
[[package]]
name = "bollard"
version = "0.14.0"
@@ -131,11 +137,11 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.1.8"
+version = "4.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
+checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098"
dependencies = [
- "bitflags",
+ "bitflags 2.0.2",
"clap_derive",
"clap_lex",
"is-terminal",
@@ -148,22 +154,22 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.1.8"
+version = "4.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
+checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
name = "clap_lex"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
+checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
dependencies = [
"os_str_bytes",
]
@@ -184,29 +190,13 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
-[[package]]
-name = "crossterm"
-version = "0.25.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
-dependencies = [
- "bitflags",
- "crossterm_winapi",
- "libc",
- "mio",
- "parking_lot",
- "signal-hook",
- "signal-hook-mio",
- "winapi",
-]
-
[[package]]
name = "crossterm"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
@@ -227,9 +217,9 @@ dependencies = [
[[package]]
name = "cxx"
-version = "1.0.92"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
+checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -239,9 +229,9 @@ dependencies = [
[[package]]
name = "cxx-build"
-version = "1.0.92"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
+checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca"
dependencies = [
"cc",
"codespan-reporting",
@@ -249,24 +239,24 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
- "syn",
+ "syn 2.0.2",
]
[[package]]
name = "cxxbridge-flags"
-version = "1.0.92"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
+checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31"
[[package]]
name = "cxxbridge-macro"
-version = "1.0.92"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
+checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.2",
]
[[package]]
@@ -328,7 +318,7 @@ checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ -538,19 +528,20 @@ dependencies = [
[[package]]
name = "io-lifetimes"
-version = "1.0.6"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
+checksum = "0dd6da19f25979c7270e70fa95ab371ec3b701cd0eefc47667a09785b3c59155"
dependencies = [
+ "hermit-abi 0.3.1",
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
-version = "0.4.4"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
+checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
@@ -684,9 +675,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "os_str_bytes"
-version = "6.4.1"
+version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
+checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
[[package]]
name = "overload"
@@ -702,13 +693,13 @@ dependencies = [
"bollard",
"cansi",
"clap",
- "crossterm 0.26.1",
+ "crossterm",
"futures-util",
"parking_lot",
+ "ratatui",
"tokio",
"tracing",
"tracing-subscriber",
- "tui",
"uuid",
]
@@ -758,7 +749,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ -788,7 +779,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
"version_check",
]
@@ -851,22 +842,35 @@ dependencies = [
"getrandom",
]
+[[package]]
+name = "ratatui"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d690717aac4aca6e901da642fafcceff63ded0ab4c65c18ceff39c9a27f21508"
+dependencies = [
+ "bitflags 1.3.2",
+ "cassowary",
+ "crossterm",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
]
[[package]]
name = "rustix"
-version = "0.36.9"
+version = "0.36.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
+checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
@@ -894,22 +898,22 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serde"
-version = "1.0.155"
+version = "1.0.157"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8"
+checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.155"
+version = "1.0.157"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630"
+checksum = "78997f4555c22a7971214540c4a661291970619afd56de19f77e0de86296e1e5"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.2",
]
[[package]]
@@ -925,13 +929,13 @@ dependencies = [
[[package]]
name = "serde_repr"
-version = "0.1.11"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "395627de918015623b32e7669714206363a7fc00382bf477e72c1f7533e8eafc"
+checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.2",
]
[[package]]
@@ -1042,6 +1046,17 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "syn"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59d3276aee1fa0c33612917969b5172b5be2db051232a6e4826f1a1a9191b045"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "termcolor"
version = "1.2.0"
@@ -1053,22 +1068,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.39"
+version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.39"
+version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.2",
]
[[package]]
@@ -1151,7 +1166,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ -1194,7 +1209,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
]
[[package]]
@@ -1238,19 +1253,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
-[[package]]
-name = "tui"
-version = "0.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
-dependencies = [
- "bitflags",
- "cassowary",
- "crossterm 0.25.0",
- "unicode-segmentation",
- "unicode-width",
-]
-
[[package]]
name = "unicase"
version = "2.6.0"
@@ -1262,9 +1264,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
-version = "0.3.11"
+version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
+checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b"
[[package]]
name = "unicode-ident"
@@ -1363,7 +1365,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
"wasm-bindgen-shared",
]
@@ -1385,7 +1387,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
diff --git a/Cargo.toml b/Cargo.toml
index 02a5f28..e83f8ab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,7 +22,7 @@ parking_lot = {version= "0.12"}
tokio = {version = "1.26", features=["full"]}
tracing = "0.1"
tracing-subscriber = "0.3"
-tui = "0.19"
+ratatui = "0.20"
uuid = {version = "1.3", features = ["v4", "fast-rng"]}
[dev-dependencies]
diff --git a/README.md b/README.md
index aa4ee99..db1d0d2 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
- Built in Rust, making heavy use of tui-rs & Bollard
+ Built in Rust, making heavy use of ratatui & Bollard
diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs
index b1e1ba6..8e3fa92 100644
--- a/src/app_data/container_state.rs
+++ b/src/app_data/container_state.rs
@@ -4,7 +4,7 @@ use std::{
fmt,
};
-use tui::{
+use ratatui::{
style::Color,
widgets::{ListItem, ListState},
};
diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs
index 93d5769..9c662fc 100644
--- a/src/app_data/mod.rs
+++ b/src/app_data/mod.rs
@@ -1,7 +1,7 @@
use bollard::models::ContainerSummary;
use core::fmt;
use std::time::{SystemTime, UNIX_EPOCH};
-use tui::widgets::{ListItem, ListState};
+use ratatui::widgets::{ListItem, ListState};
mod container_state;
diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs
index bb60bdd..72bc374 100644
--- a/src/input_handler/mod.rs
+++ b/src/input_handler/mod.rs
@@ -12,7 +12,7 @@ use tokio::{
sync::mpsc::{Receiver, Sender},
task::JoinHandle,
};
-use tui::layout::Rect;
+use ratatui::layout::Rect;
mod message;
use crate::{
diff --git a/src/ui/color_match.rs b/src/ui/color_match.rs
index d77a40e..2e349f6 100644
--- a/src/ui/color_match.rs
+++ b/src/ui/color_match.rs
@@ -1,7 +1,7 @@
pub mod log_sanitizer {
use cansi::{v3::categorise_text, Color as CansiColor, Intensity};
- use tui::{
+ use ratatui::{
style::{Color, Modifier, Style},
text::{Span, Spans},
};
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index d09105b..17b64b2 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -1,7 +1,7 @@
use parking_lot::Mutex;
use std::default::Default;
use std::{fmt::Display, sync::Arc};
-use tui::{
+use ratatui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
@@ -500,7 +500,7 @@ impl HelpInfo {
spans
.iter()
.flat_map(|x| x.0.iter())
- .map(tui::text::Span::width)
+ .map(ratatui::text::Span::width)
.max()
.unwrap_or(1)
}
diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs
index 134c112..79b5e23 100644
--- a/src/ui/gui_state.rs
+++ b/src/ui/gui_state.rs
@@ -2,7 +2,7 @@ use std::{
collections::{HashMap, HashSet},
fmt,
};
-use tui::layout::{Constraint, Rect};
+use ratatui::layout::{Constraint, Rect};
use uuid::Uuid;
use crate::app_data::Header;
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index 5f9ee65..11a0d0e 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -13,7 +13,7 @@ use std::{
use std::{sync::atomic::AtomicBool, time::Instant};
use tokio::sync::mpsc::Sender;
use tracing::error;
-use tui::{
+use ratatui::{
backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout},
Frame, Terminal,
From f23dac9bc8e66fe1384ec279d0ac9dcb5aaae5e2 Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Mon, 20 Mar 2023 00:06:21 +0000
Subject: [PATCH 02/20] fix: alter the help box link underlining
---
src/ui/draw_blocks.rs | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index 17b64b2..fc79722 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -1,6 +1,4 @@
use parking_lot::Mutex;
-use std::default::Default;
-use std::{fmt::Display, sync::Arc};
use ratatui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
@@ -13,6 +11,8 @@ use ratatui::{
},
Frame,
};
+use std::default::Default;
+use std::{fmt::Display, sync::Arc};
use crate::app_data::{Header, SortedOrder};
use crate::ui::Status;
@@ -628,6 +628,7 @@ impl HelpInfo {
/// Generate the final lines, GitHub link etc, + metadata
fn gen_final() -> Self {
+ let top_bar = (0..REPO.chars().count()).map(|_|"▔").collect::();
let spans = [
Self::empty_span(),
Spans::from(vec![Self::black_span(
@@ -635,11 +636,9 @@ impl HelpInfo {
)]),
Spans::from(vec![Span::styled(
REPO.to_owned(),
- Style::default()
- .bg(Color::Magenta)
- .fg(Color::Black)
- .add_modifier(Modifier::UNDERLINED),
+ Style::default().fg(Color::White), // .add_modifier(Modifier::BOLD),
)]),
+ Spans::from(vec![Self::white_span(&top_bar)]),
];
let height = spans.len();
let width = Self::calc_width(&spans);
From 7a9bdc9699594532e17a33e044ca0678693c8d3f Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Wed, 29 Mar 2023 17:19:47 +0000
Subject: [PATCH 03/20] chore: dependencies updated
---
Cargo.lock | 252 ++++++++++++++++++++++++++++++-----------------------
Cargo.toml | 4 +-
2 files changed, 144 insertions(+), 112 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7a46926..0c9adac 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,6 +11,46 @@ dependencies = [
"libc",
]
+[[package]]
+name = "anstream"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-wincon",
+ "concolor-override",
+ "concolor-query",
+ "is-terminal",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
[[package]]
name = "anyhow"
version = "1.0.70"
@@ -41,12 +81,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-[[package]]
-name = "bitflags"
-version = "2.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
-
[[package]]
name = "bollard"
version = "0.14.0"
@@ -137,42 +171,47 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.1.11"
+version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098"
+checksum = "6efb5f0a41b5ef5b50c5da28c07609c20091df0c1fc33d418fa2a7e693c2b624"
dependencies = [
- "bitflags 2.0.2",
+ "clap_builder",
"clap_derive",
- "clap_lex",
- "is-terminal",
"once_cell",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "671fcaa5debda4b9a84aa7fde49c907c8986c0e6ab927e04217c9cb74e7c8bc9"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "bitflags",
+ "clap_lex",
"strsim",
- "termcolor",
"unicase",
"unicode-width",
]
[[package]]
name = "clap_derive"
-version = "4.1.9"
+version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644"
+checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
dependencies = [
"heck",
- "proc-macro-error",
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.11",
]
[[package]]
name = "clap_lex"
-version = "0.3.3"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
-dependencies = [
- "os_str_bytes",
-]
+checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
[[package]]
name = "codespan-reporting"
@@ -184,6 +223,21 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "concolor-override"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f"
+
+[[package]]
+name = "concolor-query"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
+dependencies = [
+ "windows-sys",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
@@ -196,7 +250,7 @@ version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"crossterm_winapi",
"libc",
"mio",
@@ -217,9 +271,9 @@ dependencies = [
[[package]]
name = "cxx"
-version = "1.0.93"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038"
+checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -229,9 +283,9 @@ dependencies = [
[[package]]
name = "cxx-build"
-version = "1.0.93"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca"
+checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
dependencies = [
"cc",
"codespan-reporting",
@@ -239,35 +293,35 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
- "syn 2.0.2",
+ "syn 2.0.11",
]
[[package]]
name = "cxxbridge-flags"
-version = "1.0.93"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31"
+checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
[[package]]
name = "cxxbridge-macro"
-version = "1.0.93"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f"
+checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.2",
+ "syn 2.0.11",
]
[[package]]
name = "errno"
-version = "0.2.8"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
dependencies = [
"errno-dragonfly",
"libc",
- "winapi",
+ "windows-sys",
]
[[package]]
@@ -483,16 +537,16 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.53"
+version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
- "winapi",
+ "windows",
]
[[package]]
@@ -517,9 +571,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "1.9.2"
+version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
@@ -528,9 +582,9 @@ dependencies = [
[[package]]
name = "io-lifetimes"
-version = "1.0.8"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dd6da19f25979c7270e70fa95ab371ec3b701cd0eefc47667a09785b3c59155"
+checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
dependencies = [
"hermit-abi 0.3.1",
"libc",
@@ -539,9 +593,9 @@ dependencies = [
[[package]]
name = "is-terminal"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e"
+checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
@@ -587,9 +641,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
-version = "0.1.4"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d"
[[package]]
name = "lock_api"
@@ -610,12 +664,6 @@ dependencies = [
"cfg-if",
]
-[[package]]
-name = "memchr"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
-
[[package]]
name = "mio"
version = "0.8.6"
@@ -673,12 +721,6 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
-[[package]]
-name = "os_str_bytes"
-version = "6.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
-
[[package]]
name = "overload"
version = "0.1.1"
@@ -770,35 +812,11 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2",
- "quote",
- "version_check",
-]
-
[[package]]
name = "proc-macro2"
-version = "1.0.52"
+version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
+checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
dependencies = [
"unicode-ident",
]
@@ -844,11 +862,11 @@ dependencies = [
[[package]]
name = "ratatui"
-version = "0.20.0"
+version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d690717aac4aca6e901da642fafcceff63ded0ab4c65c18ceff39c9a27f21508"
+checksum = "dcc0d032bccba900ee32151ec0265667535c230169f5a011154cdcd984e16829"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"cassowary",
"crossterm",
"unicode-segmentation",
@@ -861,16 +879,16 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
]
[[package]]
name = "rustix"
-version = "0.36.10"
+version = "0.37.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778"
+checksum = "c348b5dc624ecee40108aa2922fed8bad89d7fcc2b9f8cb18f632898ac4a37f9"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"errno",
"io-lifetimes",
"libc",
@@ -898,29 +916,29 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serde"
-version = "1.0.157"
+version = "1.0.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca"
+checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.157"
+version = "1.0.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78997f4555c22a7971214540c4a661291970619afd56de19f77e0de86296e1e5"
+checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.2",
+ "syn 2.0.11",
]
[[package]]
name = "serde_json"
-version = "1.0.94"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
+checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
dependencies = [
"itoa",
"ryu",
@@ -935,7 +953,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.2",
+ "syn 2.0.11",
]
[[package]]
@@ -1048,9 +1066,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.2"
+version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59d3276aee1fa0c33612917969b5172b5be2db051232a6e4826f1a1a9191b045"
+checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40"
dependencies = [
"proc-macro2",
"quote",
@@ -1083,7 +1101,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.2",
+ "syn 2.0.11",
]
[[package]]
@@ -1140,14 +1158,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.26.0"
+version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
+checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
dependencies = [
"autocfg",
"bytes",
"libc",
- "memchr",
"mio",
"num_cpus",
"parking_lot",
@@ -1160,13 +1177,13 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "1.8.2"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.11",
]
[[package]]
@@ -1264,9 +1281,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
-version = "0.3.12"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
@@ -1306,6 +1323,12 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
[[package]]
name = "uuid"
version = "1.3.0"
@@ -1429,6 +1452,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[package]]
+name = "windows"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
+dependencies = [
+ "windows-targets",
+]
+
[[package]]
name = "windows-sys"
version = "0.45.0"
diff --git a/Cargo.toml b/Cargo.toml
index e83f8ab..7ba44aa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,11 +15,11 @@ categories = ["command-line-utilities"]
anyhow = "1.0"
bollard = "0.14"
cansi = "2.2"
-clap={version="4.1", features = ["derive", "unicode", "color"] }
+clap={version="4.2", features = ["derive", "unicode", "color"] }
crossterm = "0.26"
futures-util = "0.3"
parking_lot = {version= "0.12"}
-tokio = {version = "1.26", features=["full"]}
+tokio = {version = "1.27", features=["full"]}
tracing = "0.1"
tracing-subscriber = "0.3"
ratatui = "0.20"
From f90679978247974e999b3cc3b808e64350cf5c8c Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Wed, 29 Mar 2023 17:20:20 +0000
Subject: [PATCH 04/20] fix: alter the help box link underlining
---
src/app_data/mod.rs | 2 +-
src/input_handler/mod.rs | 2 +-
src/ui/draw_blocks.rs | 4 +---
src/ui/gui_state.rs | 2 +-
src/ui/mod.rs | 10 +++++-----
5 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs
index 9c662fc..dc80aeb 100644
--- a/src/app_data/mod.rs
+++ b/src/app_data/mod.rs
@@ -1,7 +1,7 @@
use bollard::models::ContainerSummary;
use core::fmt;
-use std::time::{SystemTime, UNIX_EPOCH};
use ratatui::widgets::{ListItem, ListState};
+use std::time::{SystemTime, UNIX_EPOCH};
mod container_state;
diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs
index 72bc374..696e3c9 100644
--- a/src/input_handler/mod.rs
+++ b/src/input_handler/mod.rs
@@ -8,11 +8,11 @@ use crossterm::{
execute,
};
use parking_lot::Mutex;
+use ratatui::layout::Rect;
use tokio::{
sync::mpsc::{Receiver, Sender},
task::JoinHandle,
};
-use ratatui::layout::Rect;
mod message;
use crate::{
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index fc79722..56286c5 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -628,7 +628,6 @@ impl HelpInfo {
/// Generate the final lines, GitHub link etc, + metadata
fn gen_final() -> Self {
- let top_bar = (0..REPO.chars().count()).map(|_|"▔").collect::();
let spans = [
Self::empty_span(),
Spans::from(vec![Self::black_span(
@@ -636,9 +635,8 @@ impl HelpInfo {
)]),
Spans::from(vec![Span::styled(
REPO.to_owned(),
- Style::default().fg(Color::White), // .add_modifier(Modifier::BOLD),
+ Style::default().fg(Color::White).add_modifier(Modifier::UNDERLINED),
)]),
- Spans::from(vec![Self::white_span(&top_bar)]),
];
let height = spans.len();
let width = Self::calc_width(&spans);
diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs
index 79b5e23..cb8571b 100644
--- a/src/ui/gui_state.rs
+++ b/src/ui/gui_state.rs
@@ -1,8 +1,8 @@
+use ratatui::layout::{Constraint, Rect};
use std::{
collections::{HashMap, HashSet},
fmt,
};
-use ratatui::layout::{Constraint, Rect};
use uuid::Uuid;
use crate::app_data::Header;
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index 11a0d0e..ddee0c2 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -5,6 +5,11 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use parking_lot::Mutex;
+use ratatui::{
+ backend::{Backend, CrosstermBackend},
+ layout::{Constraint, Direction, Layout},
+ Frame, Terminal,
+};
use std::{
io::{self, Stdout, Write},
sync::{atomic::Ordering, Arc},
@@ -13,11 +18,6 @@ use std::{
use std::{sync::atomic::AtomicBool, time::Instant};
use tokio::sync::mpsc::Sender;
use tracing::error;
-use ratatui::{
- backend::{Backend, CrosstermBackend},
- layout::{Constraint, Direction, Layout},
- Frame, Terminal,
-};
mod color_match;
mod draw_blocks;
From e0b49be84062abdfcb636418f57043fad37d06ec Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Wed, 29 Mar 2023 17:29:19 +0000
Subject: [PATCH 05/20] fix: "-d" arg error text updated
---
src/parse_args/mod.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/parse_args/mod.rs b/src/parse_args/mod.rs
index 2a881ba..d6c823d 100644
--- a/src/parse_args/mod.rs
+++ b/src/parse_args/mod.rs
@@ -40,7 +40,7 @@ impl CliArgs {
// Quit the program if the docker update argument is 0
// Should maybe change it to check if less than 100
if args.docker_interval == 0 {
- error!("docker args needs to be greater than 0");
+ error!("\"-d\" argument needs to be greater than 0");
process::exit(1)
}
Self {
From cb1271cf7f21c898020481ad85914a3dcc83ec93 Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Wed, 29 Mar 2023 17:30:14 +0000
Subject: [PATCH 06/20] feat: github workflow create image for ghcr.io
---
.../workflows/create_release_and_build.yml | 25 ++++++++++++++++---
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml
index bd9eb27..df4c890 100644
--- a/.github/workflows/create_release_and_build.yml
+++ b/.github/workflows/create_release_and_build.yml
@@ -5,6 +5,7 @@ on:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
deploy:
+ # Change this to latest - or ubuntu 20.04?
runs-on: ubuntu-18.04
steps:
- name: Checkout
@@ -64,23 +65,39 @@ jobs:
- name: compress windows_x86_64 binary
run: zip -j ./oxker_windows_x86_64.zip target/x86_64-pc-windows-gnu/release/oxker.exe
- ###############################
- ## Build images for Dockerhub #
- ###############################
+ #########################################
+ ## Build images for Dockerhub & ghcr.io #
+ #########################################
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Write release version to env
+ run: |
+ CURRENT_SEMVER=${GITHUB_REF_NAME#v}
+ echo "CURRENT_SEMVER=$CURRENT_SEMVER" >> $GITHUB_ENV
+
- uses: docker/setup-buildx-action@v2
id: buildx
with:
install: true
- - name: Build for Docker Hub
+ - name: Build for Dockerhub & ghcr.io
run: |
docker build --platform linux/arm/v6,linux/arm64,linux/amd64 \
-t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:latest \
+ -t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:${{env.CURRENT_SEMVER}} \
+ -t ghcr.io/${{ github.repository_owner }}/oxker:latest \
+ -t ghcr.io/${{ github.repository_owner }}/oxker:${{env.CURRENT_SEMVER}} \
--push \
-f containerised/Dockerfile .
From 7c92ffef7da20143a31706a310b5e6f2c3e0554f Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Wed, 29 Mar 2023 18:00:31 +0000
Subject: [PATCH 07/20] refactor: button_item() include brackets
---
src/ui/draw_blocks.rs | 34 ++++++++++++++++++----------------
1 file changed, 18 insertions(+), 16 deletions(-)
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index 56286c5..da6cf7a 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -515,12 +515,12 @@ impl HelpInfo {
Span::styled(input.to_owned(), Style::default().fg(color))
}
- /// Span to black text span
+ /// &str to black text span
fn black_span<'a>(input: &str) -> Span<'a> {
Self::span(input, Color::Black)
}
- /// Span to white text span
+ /// &str to white text span
fn white_span<'a>(input: &str) -> Span<'a> {
Self::span(input, Color::White)
}
@@ -559,7 +559,7 @@ impl HelpInfo {
/// Generate the button information span + metadata
fn gen_button() -> Self {
- let button_item = |x: &str| Self::white_span(&format!(" {x} "));
+ let button_item = |x: &str| Self::white_span(&format!(" ( {x} ) "));
let button_desc = |x: &str| Self::black_span(x);
let or = || button_desc("or");
let space = || button_desc(" ");
@@ -567,52 +567,52 @@ impl HelpInfo {
let spans = [
Spans::from(vec![
space(),
- button_item("( tab )"),
+ button_item("tab"),
or(),
- button_item("( shift+tab )"),
+ button_item("shift+tab"),
button_desc("to change panels"),
]),
Spans::from(vec![
space(),
- button_item("( ↑ ↓ )"),
+ button_item("↑ ↓"),
or(),
- button_item("( j k )"),
+ button_item("j k"),
or(),
- button_item("( PgUp PgDown )"),
+ button_item("PgUp PgDown"),
or(),
- button_item("( Home End )"),
+ button_item("Home End"),
button_desc("to change selected line"),
]),
Spans::from(vec![
space(),
- button_item("( enter )"),
+ button_item("enter"),
button_desc("to send docker container command"),
]),
Spans::from(vec![
space(),
- button_item("( h )"),
+ button_item("h"),
button_desc("to toggle this help information"),
]),
Spans::from(vec![
space(),
- button_item("( 0 )"),
+ button_item("0"),
button_desc("to stop sort"),
]),
Spans::from(vec![
space(),
- button_item("( 1 - 9 )"),
+ button_item("1 - 9"),
button_desc("sort by header - or click header"),
]),
Spans::from(vec![
space(),
- button_item("( m )"),
+ button_item("m"),
button_desc(
"to toggle mouse capture - if disabled, text on screen can be selected & copied",
),
]),
Spans::from(vec![
space(),
- button_item("( q )"),
+ button_item("q"),
button_desc("to quit at any time"),
]),
];
@@ -635,7 +635,9 @@ impl HelpInfo {
)]),
Spans::from(vec![Span::styled(
REPO.to_owned(),
- Style::default().fg(Color::White).add_modifier(Modifier::UNDERLINED),
+ Style::default()
+ .fg(Color::White)
+ .add_modifier(Modifier::UNDERLINED),
)]),
];
let height = spans.len();
From 937202fe34d1692693c62dd1a7ad19db37651233 Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Thu, 30 Mar 2023 02:12:03 +0000
Subject: [PATCH 08/20] feat: delete container, closes #27
Enable a user to delete a container. A dialog will pop up to ask the user to confirm the deletion. A user can then click on either button, or press N/Y to make a selection
---
src/app_data/container_state.rs | 13 ++--
src/app_data/mod.rs | 10 +++
src/docker_data/message.rs | 12 +--
src/docker_data/mod.rs | 33 +++++++-
src/input_handler/mod.rs | 89 ++++++++++++++++-----
src/main.rs | 2 +-
src/ui/draw_blocks.rs | 132 ++++++++++++++++++++++++++++----
src/ui/gui_state.rs | 49 +++++++++++-
src/ui/mod.rs | 23 +++++-
9 files changed, 309 insertions(+), 54 deletions(-)
diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs
index 8e3fa92..974356b 100644
--- a/src/app_data/container_state.rs
+++ b/src/app_data/container_state.rs
@@ -207,6 +207,7 @@ pub enum DockerControls {
Start,
Stop,
Unpause,
+ Delete,
}
impl DockerControls {
@@ -216,6 +217,7 @@ impl DockerControls {
Self::Restart => Color::Magenta,
Self::Start => Color::Green,
Self::Stop => Color::Red,
+ Self::Delete => Color::Gray,
Self::Unpause => Color::Blue,
}
}
@@ -223,11 +225,11 @@ impl DockerControls {
/// Docker commands available depending on the containers state
pub fn gen_vec(state: State) -> Vec {
match state {
- State::Dead | State::Exited => vec![Self::Start, Self::Restart],
- State::Paused => vec![Self::Unpause, Self::Stop],
- State::Restarting => vec![Self::Stop],
- State::Running => vec![Self::Pause, Self::Restart, Self::Stop],
- _ => vec![],
+ State::Dead | State::Exited => vec![Self::Start, Self::Restart, Self::Delete],
+ State::Paused => vec![Self::Unpause, Self::Stop, Self::Delete],
+ State::Restarting => vec![Self::Stop, Self::Delete],
+ State::Running => vec![Self::Pause, Self::Restart, Self::Stop, Self::Delete],
+ _ => vec![Self::Delete],
}
}
}
@@ -236,6 +238,7 @@ impl fmt::Display for DockerControls {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disp = match self {
Self::Pause => "pause",
+ Self::Delete => "delete",
Self::Restart => "restart",
Self::Start => "start",
Self::Stop => "stop",
diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs
index dc80aeb..fda6cbb 100644
--- a/src/app_data/mod.rs
+++ b/src/app_data/mod.rs
@@ -456,6 +456,15 @@ impl AppData {
self.containers.items.iter_mut().find(|i| &i.id == id)
}
+ /// return a mutable container by given id
+ pub fn get_container_name_by_id(&mut self, id: &ContainerId) -> Option {
+ self.containers
+ .items
+ .iter_mut()
+ .find(|i| &i.id == id)
+ .map(|i| i.name.clone())
+ }
+
/// Find the id of the currently selected container.
/// If any containers on system, will always return a ContainerId
/// Only returns None when no containers found.
@@ -532,6 +541,7 @@ impl AppData {
}
}
}
+
// Trim a &String and return String
let trim_owned = |x: &String| x.trim().to_owned();
diff --git a/src/docker_data/message.rs b/src/docker_data/message.rs
index f7de6f5..0a6b67e 100644
--- a/src/docker_data/message.rs
+++ b/src/docker_data/message.rs
@@ -2,11 +2,13 @@ use crate::app_data::ContainerId;
#[derive(Debug, Clone)]
pub enum DockerMessage {
- Update,
- Start(ContainerId),
- Restart(ContainerId),
+ Delete(ContainerId),
+ ConfirmDelete(ContainerId),
Pause(ContainerId),
- Unpause(ContainerId),
- Stop(ContainerId),
Quit,
+ Restart(ContainerId),
+ Start(ContainerId),
+ Stop(ContainerId),
+ Unpause(ContainerId),
+ Update,
}
diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs
index 8104ccc..391b3fc 100644
--- a/src/docker_data/mod.rs
+++ b/src/docker_data/mod.rs
@@ -1,5 +1,8 @@
use bollard::{
- container::{ListContainersOptions, LogsOptions, StartContainerOptions, Stats, StatsOptions},
+ container::{
+ ListContainersOptions, LogsOptions, RemoveContainerOptions, StartContainerOptions, Stats,
+ StatsOptions,
+ },
service::ContainerSummary,
Docker,
};
@@ -335,13 +338,14 @@ impl DockerData {
}
/// Handle incoming messages, container controls & all container information update
- /// Spawn dowcker commands off into own thread
+ /// Spawn Docker commands off into own thread
async fn message_handler(&mut self) {
while let Some(message) = self.receiver.recv().await {
let docker = Arc::clone(&self.docker);
let gui_state = Arc::clone(&self.gui_state);
let app_data = Arc::clone(&self.app_data);
let uuid = Uuid::new_v4();
+ // TODO need to refactor these
match message {
DockerMessage::Pause(id) => {
tokio::spawn(async move {
@@ -397,6 +401,31 @@ impl DockerData {
});
self.update_everything().await;
}
+ DockerMessage::Delete(id) => {
+ tokio::spawn(async move {
+ let loading_spin = Self::loading_spin(uuid, &gui_state).await;
+ if docker
+ .remove_container(
+ id.get(),
+ Some(RemoveContainerOptions {
+ v: false,
+ force: true,
+ link: false,
+ }),
+ )
+ .await
+ .is_err()
+ {
+ Self::set_error(&app_data, DockerControls::Stop, &gui_state);
+ }
+ Self::stop_loading_spin(&gui_state, &loading_spin, uuid);
+ });
+ self.update_everything().await;
+ self.gui_state.lock().set_delete_container(None);
+ }
+ DockerMessage::ConfirmDelete(id) => {
+ self.gui_state.lock().set_delete_container(Some(id))
+ }
DockerMessage::Update => self.update_everything().await,
DockerMessage::Quit => {
self.spawns
diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs
index 696e3c9..421f86a 100644
--- a/src/input_handler/mod.rs
+++ b/src/input_handler/mod.rs
@@ -19,7 +19,7 @@ use crate::{
app_data::{AppData, DockerControls, Header},
app_error::AppError,
docker_data::DockerMessage,
- ui::{GuiState, SelectablePanel, Status, Ui},
+ ui::{DeleteButton, GuiState, SelectablePanel, Status, Ui},
};
pub use message::InputMessages;
@@ -62,13 +62,21 @@ impl InputHandler {
match message {
InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await,
InputMessages::MouseEvent(mouse_event) => {
- let error_or_help = self
- .gui_state
- .lock()
- .status_contains(&[Status::Error, Status::Help]);
+ let error_or_help = self.gui_state.lock().status_contains(&[
+ Status::Error,
+ Status::Help,
+ Status::DeleteConfirm,
+ ]);
if !error_or_help {
self.mouse_press(mouse_event);
}
+ let delete_confirm = self
+ .gui_state
+ .lock()
+ .status_contains(&[Status::DeleteConfirm]);
+ if delete_confirm {
+ self.button_intersect(mouse_event).await;
+ }
}
}
if !self.is_running.load(Ordering::SeqCst) {
@@ -133,41 +141,59 @@ impl InputHandler {
}
}
+ /// This is executed from the Delete Confirm dialog, and will send an internal message to actually remove the given container
+ async fn confirm_delete(&self) {
+ let id = self.gui_state.lock().get_delete_container();
+ if let Some(id) = id {
+ self.docker_sender
+ .send(DockerMessage::Delete(id))
+ .await
+ .ok();
+ }
+ }
+
+ /// This is executed from the Delete Confirm dialog, and will clear the delete_container information (removes id and closes panel)
+ fn clear_delete(&self) {
+ self.gui_state.lock().set_delete_container(None);
+ }
+
/// Handle any keyboard button events
#[allow(clippy::too_many_lines)]
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
let contains_error = self.gui_state.lock().status_contains(&[Status::Error]);
let contains_help = self.gui_state.lock().status_contains(&[Status::Help]);
+ let contains_delete = self
+ .gui_state
+ .lock()
+ .status_contains(&[Status::DeleteConfirm]);
- // Quit on Ctrl + c/C
+ // Always just quit on Ctrl + c/C or q/Q
let is_c = || key_code == KeyCode::Char('c') || key_code == KeyCode::Char('C');
- if key_modififer == KeyModifiers::CONTROL && is_c() {
+ let is_q = || key_code == KeyCode::Char('q') || key_code == KeyCode::Char('Q');
+ if key_modififer == KeyModifiers::CONTROL && is_c() || is_q() {
self.quit().await;
}
if contains_error {
- match key_code {
- KeyCode::Char('q' | 'Q') => self.quit().await,
- KeyCode::Char('c' | 'C') => {
- self.app_data.lock().remove_error();
- self.gui_state.lock().status_del(Status::Error);
- }
- _ => (),
+ if let KeyCode::Char('c' | 'C') = key_code {
+ self.app_data.lock().remove_error();
+ self.gui_state.lock().status_del(Status::Error);
}
} else if contains_help {
match key_code {
- KeyCode::Char('q' | 'Q') => self.quit().await,
KeyCode::Char('h' | 'H') => self.gui_state.lock().status_del(Status::Help),
KeyCode::Char('m' | 'M') => self.m_key(),
_ => (),
}
+ } else if contains_delete {
+ match key_code {
+ KeyCode::Char('y' | 'Y') => self.confirm_delete().await,
+ KeyCode::Char('n' | 'N') => self.clear_delete(),
+ _ => (),
+ }
} else {
- // let abc = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::Ctrl);
match key_code {
- // KeyCode::Ctrl('c') => {
- // self.quit().await;
- // }
KeyCode::Char('0') => self.app_data.lock().reset_sorted(),
KeyCode::Char('1') => self.sort(Header::State),
KeyCode::Char('2') => self.sort(Header::Status),
@@ -178,7 +204,6 @@ impl InputHandler {
KeyCode::Char('7') => self.sort(Header::Image),
KeyCode::Char('8') => self.sort(Header::Rx),
KeyCode::Char('9') => self.sort(Header::Tx),
- KeyCode::Char('q' | 'Q') => self.quit().await,
KeyCode::Char('h' | 'H') => self.gui_state.lock().status_push(Status::Help),
KeyCode::Char('m' | 'M') => self.m_key(),
KeyCode::Tab => {
@@ -251,6 +276,11 @@ impl InputHandler {
};
if let Some(id) = option_id {
match command {
+ DockerControls::Delete => self
+ .docker_sender
+ .send(DockerMessage::ConfirmDelete(id))
+ .await
+ .ok(),
DockerControls::Pause => {
self.docker_sender.send(DockerMessage::Pause(id)).await.ok()
}
@@ -280,6 +310,25 @@ impl InputHandler {
}
}
+ /// Check if a button press interacts with either the yes or no buttons in the delete container confirm window
+ async fn button_intersect(&mut self, mouse_event: MouseEvent) {
+ if mouse_event.kind == MouseEventKind::Down(MouseButton::Left) {
+ let intersect = self.gui_state.lock().button_intersect(Rect::new(
+ mouse_event.column,
+ mouse_event.row,
+ 1,
+ 1,
+ ));
+
+ if let Some(button) = intersect {
+ match button {
+ DeleteButton::Yes => self.confirm_delete().await,
+ DeleteButton::No => self.clear_delete(),
+ }
+ }
+ }
+ }
+
/// Handle mouse button events
fn mouse_press(&mut self, mouse_event: MouseEvent) {
match mouse_event.kind {
diff --git a/src/main.rs b/src/main.rs
index fd99297..29db59b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -40,7 +40,7 @@ use ui::{GuiState, Status, Ui};
use crate::docker_data::DockerMessage;
-// this is the entry point when running as a Docker Container, and is used, in conjunction with the `CONTAINER_ENV` ENV, to check if we are running as a Docker Container
+/// This is the entry point when running as a Docker Container, and is used, in conjunction with the `CONTAINER_ENV` ENV, to check if we are running as a Docker Container
const ENTRY_POINT: &str = "/app/oxker";
const ENV_KEY: &str = "OXKER_RUNTIME";
const ENV_VALUE: &str = "container";
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index da6cf7a..c6834d1 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -21,7 +21,7 @@ use crate::{
app_error::AppError,
};
-use super::gui_state::{BoxLocation, Region};
+use super::gui_state::{BoxLocation, DeleteButton, Region};
use super::{GuiState, SelectablePanel};
const NAME_TEXT: &str = r#"
@@ -43,6 +43,14 @@ const MARGIN: &str = " ";
const ARROW: &str = "▶ ";
const CIRCLE: &str = "⚪ ";
+/// From a given &str, return the maximum number of chars on a single line
+fn max_line_width(text: &str) -> usize {
+ text.lines()
+ .map(|i| i.chars().count())
+ .max()
+ .unwrap_or_default()
+}
+
/// Generate block, add a border if is the selected panel,
/// add custom title based on state of each panel
fn generate_block<'a>(
@@ -53,7 +61,7 @@ fn generate_block<'a>(
) -> Block<'a> {
gui_state
.lock()
- .update_heading_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 {
SelectablePanel::Containers => {
@@ -459,7 +467,7 @@ pub fn heading_bar(
let rect = headers_section[index];
gui_state
.lock()
- .update_heading_map(Region::Header(header), rect);
+ .update_region_map(Region::Header(header), rect);
f.render_widget(paragraph, rect);
}
}
@@ -479,14 +487,6 @@ pub fn heading_bar(
f.render_widget(help_paragraph, split_bar[help_index]);
}
-/// From a given &str, return the maximum number of chars on a single line
-fn max_line_width(text: &str) -> usize {
- text.lines()
- .map(|i| i.chars().count())
- .max()
- .unwrap_or_default()
-}
-
/// Help popup box needs these three pieces of information
struct HelpInfo {
spans: Vec>,
@@ -593,11 +593,7 @@ impl HelpInfo {
button_item("h"),
button_desc("to toggle this help information"),
]),
- Spans::from(vec![
- space(),
- button_item("0"),
- button_desc("to stop sort"),
- ]),
+ Spans::from(vec![space(), button_item("0"), button_desc("to stop sort")]),
Spans::from(vec![
space(),
button_item("1 - 9"),
@@ -728,6 +724,110 @@ pub fn help_box(f: &mut Frame<'_, B>) {
f.render_widget(block, area);
}
+/// Draw the delete confirm box in the centre of the screen
+/// take in container id and container name here?
+pub fn delete_confirm(
+ f: &mut Frame<'_, B>,
+ gui_state: &Arc>,
+ name: &str,
+) {
+ let block = Block::default()
+ .title(" Confirm Delete ")
+ .border_type(BorderType::Rounded)
+ .style(Style::default().bg(Color::White).fg(Color::Black))
+ .title_alignment(Alignment::Center)
+ .borders(Borders::ALL);
+
+ let confirm = Spans::from(vec![
+ Span::from("Are you sure you want to delete container: "),
+ Span::styled(
+ name,
+ Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
+ ),
+ ]);
+
+ let yes_text = " (Y)es ";
+ let no_text = " (N)o ";
+
+ // Find the maximum line width & height, and add some padding
+ let max_line_width = u16::try_from(confirm.width()).unwrap_or(64) + 12;
+ let lines = 8;
+
+ let confirm_para = Paragraph::new(confirm).alignment(Alignment::Center);
+
+ let button_block = || {
+ Block::default()
+ .border_type(BorderType::Rounded)
+ .borders(Borders::ALL)
+ };
+
+ let yes_para = Paragraph::new(yes_text)
+ .alignment(Alignment::Center)
+ .block(button_block());
+ // Need to add some padding for the borders
+ let yes_chars = u16::try_from(yes_text.chars().count() + 2).unwrap_or(9);
+
+ let no_para = Paragraph::new(no_text)
+ .alignment(Alignment::Center)
+ .block(button_block());
+ // Need to add some padding for the borders
+ let no_chars = u16::try_from(no_text.chars().count() + 2).unwrap_or(8);
+
+ let area = popup(
+ lines,
+ max_line_width.into(),
+ f.size(),
+ BoxLocation::MiddleCentre,
+ );
+
+ let split_popup = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(
+ [
+ Constraint::Min(2),
+ Constraint::Max(1),
+ Constraint::Max(1),
+ Constraint::Max(3),
+ Constraint::Min(1),
+ ]
+ .as_ref(),
+ )
+ .split(area);
+
+ let button_spacing = (max_line_width - no_chars - yes_chars) / 3;
+ let split_buttons = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints(
+ [
+ Constraint::Min(button_spacing),
+ Constraint::Max(no_chars),
+ Constraint::Min(button_spacing),
+ Constraint::Max(yes_chars),
+ Constraint::Min(button_spacing),
+ ]
+ .as_ref(),
+ )
+ .split(split_popup[3]);
+
+ let no_area = split_buttons[1];
+ let yes_area = split_buttons[3];
+
+ // Insert button areas into region map, so can interact with them on click
+ gui_state
+ .lock()
+ .update_region_map(Region::Delete(DeleteButton::No), no_area);
+
+ gui_state
+ .lock()
+ .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
pub fn error(f: &mut Frame<'_, B>, error: AppError, seconds: Option) {
let block = Block::default()
diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs
index cb8571b..baa575a 100644
--- a/src/ui/gui_state.rs
+++ b/src/ui/gui_state.rs
@@ -5,7 +5,7 @@ use std::{
};
use uuid::Uuid;
-use crate::app_data::Header;
+use crate::app_data::{ContainerId, Header};
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
pub enum SelectablePanel {
@@ -43,6 +43,13 @@ impl SelectablePanel {
pub enum Region {
Panel(SelectablePanel),
Header(Header),
+ Delete(DeleteButton),
+}
+
+#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
+pub enum DeleteButton {
+ Yes,
+ No,
}
#[allow(unused)]
@@ -191,11 +198,13 @@ 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
+// Copy
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Status {
Init,
Help,
DockerConnect,
+ DeleteConfirm,
Error,
}
@@ -206,7 +215,9 @@ pub struct GuiState {
is_loading: HashSet,
loading_icon: Loading,
panel_map: HashMap,
+ delete_map: HashMap,
status: HashSet,
+ delete_container: Option,
pub info_box_text: Option,
pub selected_panel: SelectablePanel,
}
@@ -229,6 +240,16 @@ impl GuiState {
}
}
+ /// Check if a given Rect (a clicked area of 1x1), interacts with any known delete button
+ pub fn button_intersect(&mut self, rect: Rect) -> Option {
+ self.delete_map
+ .iter()
+ .filter(|i| i.1.intersects(rect))
+ .collect::>()
+ .get(0)
+ .map(|data| *data.0)
+ }
+
/// Check if a given Rect (a clicked area of 1x1), interacts with any known panels
pub fn header_intersect(&mut self, rect: Rect) -> Option {
self.heading_map
@@ -240,7 +261,7 @@ impl GuiState {
}
/// Insert, or updates header area panel into heading_map
- pub fn update_heading_map(&mut self, region: Region, area: Rect) {
+ pub fn update_region_map(&mut self, region: Region, area: Rect) {
match region {
Region::Header(header) => self
.heading_map
@@ -252,9 +273,30 @@ impl GuiState {
.entry(panel)
.and_modify(|w| *w = area)
.or_insert(area),
+ Region::Delete(button) => self
+ .delete_map
+ .entry(button)
+ .and_modify(|w| *w = area)
+ .or_insert(area),
};
}
+ /// Check if an ContainerId is set in the delete_container field
+ pub fn get_delete_container(&self) -> Option {
+ self.delete_container.clone()
+ }
+
+ /// Set either a ContainerId, or None, to the delete_container field
+ /// If Some, will also insert the DeleteConfirm status into self.status
+ pub fn set_delete_container(&mut self, id: Option) {
+ if id.is_some() {
+ self.status.insert(Status::DeleteConfirm);
+ } else {
+ self.status.remove(&Status::DeleteConfirm);
+ }
+ self.delete_container = id;
+ }
+
/// Check if the current gui_status contains any of the given status'
/// Don't really like this methodology for gui state, needs a re-think
pub fn status_contains(&self, status: &[Status]) -> bool {
@@ -264,6 +306,9 @@ impl GuiState {
/// Remove a gui_status into the current gui_status HashSet
pub fn status_del(&mut self, status: Status) {
self.status.remove(&status);
+ if status == Status::DeleteConfirm {
+ self.status.remove(&Status::DeleteConfirm);
+ }
}
/// Insert a gui_status into the current gui_status HashSet
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index ddee0c2..53ec6a1 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -24,7 +24,7 @@ mod draw_blocks;
mod gui_state;
pub use self::color_match::*;
-pub use self::gui_state::{GuiState, SelectablePanel, Status};
+pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
use crate::{
app_data::AppData, app_error::AppError, docker_data::DockerMessage,
input_handler::InputMessages,
@@ -198,20 +198,23 @@ impl Ui {
}
/// 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)] ?
fn draw_frame(
f: &mut Frame<'_, B>,
app_data: &Arc>,
gui_state: &Arc>,
) {
- // set max height for container section, needs +4 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 + 4 } 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();
@@ -274,6 +277,20 @@ fn draw_frame(
gui_state,
);
+ if let Some(id) = delete_confirm {
+ let name = app_data.lock().get_container_name_by_id(&id);
+ name.map_or_else(
+ || {
+ // If a container is deleted outside of oxker but whilst the Delete Confirm dialog is open, it can get caught in kind of a dead lock situation
+ // so if in that unique situation, just clear the delete_container id
+ gui_state.lock().set_delete_container(None);
+ },
+ |name| {
+ draw_blocks::delete_confirm(f, gui_state, &name);
+ },
+ );
+ }
+
// only draw charts if there are containers
if has_containers {
draw_blocks::chart(f, lower_main[1], app_data);
From b9c125da46fe0eb4aae15c354d87ac824e9cb83a Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Thu, 30 Mar 2023 02:22:47 +0000
Subject: [PATCH 09/20] fix: out of bound bug in heading_bar
use a `saturating_sub` to calculate the column_width
---
src/docker_data/mod.rs | 2 +-
src/ui/draw_blocks.rs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs
index 391b3fc..92fd28c 100644
--- a/src/docker_data/mod.rs
+++ b/src/docker_data/mod.rs
@@ -424,7 +424,7 @@ impl DockerData {
self.gui_state.lock().set_delete_container(None);
}
DockerMessage::ConfirmDelete(id) => {
- self.gui_state.lock().set_delete_container(Some(id))
+ self.gui_state.lock().set_delete_container(Some(id));
}
DockerMessage::Update => self.update_everything().await,
DockerMessage::Quit => {
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index c6834d1..341b33b 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -432,7 +432,7 @@ pub fn heading_bar(
let info_text = format!("( h ) {suffix} help {MARGIN}",);
let info_width = info_text.chars().count();
- let column_width = usize::from(area.width) - info_width;
+ let column_width = usize::from(area.width).saturating_sub(info_width);
let column_width = if column_width > 0 { column_width } else { 1 };
let splits = if has_containers {
vec![
From d628e8029942916053b3b7e72d363b1290fc5711 Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Thu, 30 Mar 2023 02:28:53 +0000
Subject: [PATCH 10/20] refactor: popup() use `saturating_x()` rather than
`checked_x()`
---
src/ui/draw_blocks.rs | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index 341b33b..25ed869 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -11,7 +11,7 @@ use ratatui::{
},
Frame,
};
-use std::default::Default;
+use std::{default::Default, ops::Sub};
use std::{fmt::Display, sync::Arc};
use crate::app_data::{Header, SortedOrder};
@@ -898,9 +898,7 @@ pub fn info(f: &mut Frame<'_, B>, text: String) {
fn popup(text_lines: usize, text_width: usize, r: Rect, box_location: BoxLocation) -> Rect {
// Make sure blank_space can't be an negative, as will crash
let calc = |x: u16, y: usize| {
- (usize::from(x).checked_sub(y).map_or(1usize, |f| f))
- .checked_div(2)
- .map_or(1usize, |f| f)
+ usize::from(x).saturating_sub(y).saturating_div(2)
};
let blank_vertical = calc(r.height, text_lines);
From 73ab7580c61dd59c59f10872629111360afb9033 Mon Sep 17 00:00:00 2001
From: Jack Wills <32690432+mrjackwills@users.noreply.github.com>
Date: Thu, 30 Mar 2023 02:58:44 +0000
Subject: [PATCH 11/20] docs: README.md and screenshot updated
---
.github/screenshot_01.png | Bin 125418 -> 110799 bytes
README.md | 13 +++++++++++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/.github/screenshot_01.png b/.github/screenshot_01.png
index d3b2c72524cca2fa417b65c776062223d6e94ffd..ac502aba86afebe8ad7ffd2d3a5587193bf2d49d 100644
GIT binary patch
literal 110799
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