diff --git a/.github/demo_01.webp b/.github/demo_01.webp
index 8a7f085..a159342 100644
Binary files a/.github/demo_01.webp and b/.github/demo_01.webp differ
diff --git a/.github/release-body.md b/.github/release-body.md
index fe9b3e0..4bf4712 100644
--- a/.github/release-body.md
+++ b/.github/release-body.md
@@ -1,18 +1,25 @@
-### 2025-09-28
+### 2026-02-23
+
+*BREAKING CHANGES*
++ `log_scroll_forward`, `log_scroll_back` renamed to `scroll_forward`, `scroll_back`
++ Additional KeyMap entry, `inspect` defaults to `i`, enables Inspect mode
++ Docker Host priorities reordered, *should* now be, from high to low order, `--host` cli argument, `config.toml` `host` value, `DOCKER_HOST` env, [Docker library](https://github.com/fussybeaver/bollard) default setting.
++ `config.toml` `host` value is now commented out by default - this should help with invalid Docker connection errors and enable easy Podman support
### Chores
-+ create_release.sh updated, [d4af754ad245540db60177f7b202b3c64519c961]
-+ dependencies updated, [03599b46657d38d0c9f25c2ccfd9510f2b98dd84], [aef0c9503e7045a256856aa887d8c8d7722b9936], [f0771eab5d07d141fe7a8997db650f0f65ffe0a7], [1596de8681ad6c0a7832eb922dd2dc36ab30eb41]
-+ GitHub workflow updated, [66dae5e61ea294ac8ce134a6c32b27c04166b6eb]
-
-### Docs
-+ fix numerous typos, [618a43b501914fdf2659e171172ad180364cf87a]
++ dependencies updated, [4658a8de264698b0c8092e1227f0683527219a0b], [8b5899ca238bcbff32519b376b920cd7b7509809], [bebb687c59f3b408e69b23d2e68fa69f006a3231]
++ GitHub workflow updated, [a0aa7918241ee8f702d6472c620287aa4be7d56c]
### Features
-+ *BREAKING CHANGE* - `scroll_down_many` & `scroll_up_many` removed, `scroll_down_one` `scroll_up_one` renamed `scroll_down`, `scroll_up`, see [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config), [52a04ec1d0b9e4877e304f60a857ebc00f88b4fd]
-+ log search feature, closes #72. Use `#` button, remappable via `log_search_mode`, to enter log search mode. Case-sensitive by default, editable in `config.toml` with `log_search_case_sensitive` entry. Customise colours via `[colors.log_search]` entries, again see see [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config), [96d9469623a7c90b79aa8d82abf587290343ad37], [a2316a9cac270790920a1ebd1be6532d51aba77c]
-+ `term` renamed `filter term`, tests updated, [487c3faf96f4c197c8b82644c02466ea40626a5e]
++ Network chart, closes #79, [99fcb8fedf01599ec346b65d435d4c301a7a8851]
++ Inspect mode & help panel redesign, [ae7f3f4a9472b451c37c0ab97b1756b41a3529f5]
++ set rust-version in Cargo.toml, closes #77, [0763a1024f44d98b8d9d65f57995da538e40963c]
-My 32-bit armhf armv6 hardware no longer seems to be able to run Docker. Future `oxker` releases won't be tested on real hardware but will continue to be built and published for armv6.
+### Fixes
++ Enable quit on Docker connect error screen, [5f942eb2e963660bd7fe9d80fa7ba8a83754803a]
+
+### Refactors
++ dead code removed, [3e31a2a6bc02d6ef75bd6cbc18568e82e60e1ee3]
++ docker data spawns, [cd943f67e465fff9726b40570a089301a4a8f534]
see CHANGELOG.md for more details
diff --git a/.github/screenshot_01.png b/.github/screenshot_01.png
index f39df48..27b91c9 100644
Binary files a/.github/screenshot_01.png and b/.github/screenshot_01.png differ
diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml
index c737331..f8d0b56 100644
--- a/.github/workflows/create_release_and_build.yml
+++ b/.github/workflows/create_release_and_build.yml
@@ -47,11 +47,19 @@ jobs:
- name: install cross
run: cargo install cross --git https://github.com/cross-rs/cross
- # Build binary for arm MacOS using Docker Zigbuild
- name: build
if: matrix.target == 'aarch64-apple-darwin'
run: |
- docker run --rm -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin
+ docker run --rm \
+ -v "$(pwd):/io" \
+ -w /io \
+ ghcr.io/rust-cross/cargo-zigbuild \
+ bash -ec '
+ rustup update stable
+ rustup default stable
+ rustup target add aarch64-apple-darwin
+ cargo zigbuild --release --target aarch64-apple-darwin
+ '
# Build all other targets using Cross
- name: build
@@ -72,7 +80,7 @@ jobs:
# Upload output for release page
- name: Upload Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
if-no-files-found: error
name: ${{ matrix.target }}
@@ -91,7 +99,7 @@ jobs:
uses: actions/checkout@v5
- name: Setup | Artifacts
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v7
- name: Update Release
uses: ncipollo/release-action@v1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 633a832..f6c05f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+# v0.13.0
+### 2026-02-23
+
+*BREAKING CHANGES*
++ `log_scroll_forward`, `log_scroll_back` renamed to `scroll_forward`, `scroll_back`
++ Additional KeyMap entry, `inspect` defaults to `i`, enables Inspect mode
++ Docker Host priorities reordered, *should* now be, from high to low order, `--host` cli argument, `config.toml` `host` value, `DOCKER_HOST` env, [Docker library](https://github.com/fussybeaver/bollard) default setting.
++ `config.toml` `host` value is now commented out by default - this should help with invalid Docker connection errors and enable easy Podman support
+
+### Chores
++ dependencies updated, [4658a8de](https://github.com/mrjackwills/oxker/commit/4658a8de264698b0c8092e1227f0683527219a0b), [8b5899ca](https://github.com/mrjackwills/oxker/commit/8b5899ca238bcbff32519b376b920cd7b7509809), [bebb687c](https://github.com/mrjackwills/oxker/commit/bebb687c59f3b408e69b23d2e68fa69f006a3231)
++ GitHub workflow updated, [a0aa7918](https://github.com/mrjackwills/oxker/commit/a0aa7918241ee8f702d6472c620287aa4be7d56c)
+
+### Features
++ Network chart, closes [#79](https://github.com/mrjackwills/oxker/issues/79), [99fcb8fe](https://github.com/mrjackwills/oxker/commit/99fcb8fedf01599ec346b65d435d4c301a7a8851)
++ Inspect mode & help panel redesign, [ae7f3f4a](https://github.com/mrjackwills/oxker/commit/ae7f3f4a9472b451c37c0ab97b1756b41a3529f5)
++ set rust-version in Cargo.toml, closes [#77](https://github.com/mrjackwills/oxker/issues/77), [0763a102](https://github.com/mrjackwills/oxker/commit/0763a1024f44d98b8d9d65f57995da538e40963c)
+
+### Fixes
++ Enable quit on Docker connect error screen, [5f942eb2](https://github.com/mrjackwills/oxker/commit/5f942eb2e963660bd7fe9d80fa7ba8a83754803a)
+
+### Refactors
++ dead code removed, [3e31a2a6](https://github.com/mrjackwills/oxker/commit/3e31a2a6bc02d6ef75bd6cbc18568e82e60e1ee3)
++ docker data spawns, [cd943f67](https://github.com/mrjackwills/oxker/commit/cd943f67e465fff9726b40570a089301a4a8f534)
+
# v0.12.0
### 2025-09-28
diff --git a/Cargo.lock b/Cargo.lock
index f89e920..3912ed1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,40 +3,25 @@
version = 4
[[package]]
-name = "addr2line"
-version = "0.25.1"
+name = "aho-corasick"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
- "gimli",
+ "memchr",
]
-[[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
-[[package]]
-name = "android_system_properties"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "anstream"
-version = "0.6.20"
+version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -49,9 +34,9 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.11"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
@@ -64,29 +49,38 @@ dependencies = [
[[package]]
name = "anstyle-query"
-version = "1.1.4"
+version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
-version = "3.0.10"
+version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "anyhow"
-version = "1.0.100"
+version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "atomic"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
+dependencies = [
+ "bytemuck",
+]
[[package]]
name = "atomic-waker"
@@ -100,21 +94,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-[[package]]
-name = "backtrace"
-version = "0.3.76"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "windows-link",
-]
-
[[package]]
name = "base64"
version = "0.22.1"
@@ -122,16 +101,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
-name = "bitflags"
-version = "2.9.4"
+name = "bit-set"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
[[package]]
name = "bollard"
-version = "0.19.2"
+version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8796b390a5b4c86f9f2e8173a68c2791f4fa6b038b84e96dbc01c016d1e6722c"
+checksum = "227aa051deec8d16bd9c34605e7aaf153f240e35483dd42f6f78903847934738"
dependencies = [
"base64",
"bollard-stubs",
@@ -150,9 +159,8 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
- "serde_repr",
"serde_urlencoded",
- "thiserror",
+ "thiserror 2.0.18",
"tokio",
"tokio-util",
"tower-service",
@@ -162,27 +170,32 @@ dependencies = [
[[package]]
name = "bollard-stubs"
-version = "1.49.0-rc.28.3.3"
+version = "1.52.1-rc.29.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e7814991259013d5a5bee4ae28657dae0747d843cf06c40f7fc0c2894d6fa38"
+checksum = "0f0a8ca8799131c1837d1282c3f81f31e76ceb0ce426e04a7fe1ccee3287c066"
dependencies = [
"serde",
"serde_json",
"serde_repr",
- "serde_with",
]
[[package]]
name = "bumpalo"
-version = "3.19.0"
+version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "bytes"
-version = "1.10.1"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cansi"
@@ -190,12 +203,6 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bdcae87153686017415ce77e48c53e6818a0a058f0e21b56640d1e944967ef8"
-[[package]]
-name = "cassowary"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
-
[[package]]
name = "castaway"
version = "0.2.4"
@@ -205,39 +212,23 @@ dependencies = [
"rustversion",
]
-[[package]]
-name = "cc"
-version = "1.2.39"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f"
-dependencies = [
- "find-msvc-tools",
- "shlex",
-]
-
[[package]]
name = "cfg-if"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
-name = "chrono"
-version = "0.4.42"
+name = "cfg_aliases"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
-dependencies = [
- "iana-time-zone",
- "num-traits",
- "serde",
- "windows-link",
-]
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
-version = "4.5.48"
+version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
+checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
dependencies = [
"clap_builder",
"clap_derive",
@@ -245,35 +236,35 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.48"
+version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
+checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"unicase",
- "unicode-width 0.2.0",
+ "unicode-width",
]
[[package]]
name = "clap_derive"
-version = "4.5.47"
+version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
+checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "clap_lex"
-version = "0.7.5"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "colorchoice"
@@ -283,9 +274,9 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compact_str"
-version = "0.8.1"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
dependencies = [
"castaway",
"cfg-if",
@@ -309,33 +300,20 @@ dependencies = [
[[package]]
name = "convert_case"
-version = "0.7.1"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
-name = "core-foundation-sys"
-version = "0.8.7"
+name = "cpufeatures"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-
-[[package]]
-name = "crossterm"
-version = "0.28.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
- "bitflags",
- "crossterm_winapi",
- "mio",
- "parking_lot",
- "rustix 0.38.44",
- "signal-hook",
- "signal-hook-mio",
- "winapi",
+ "libc",
]
[[package]]
@@ -344,13 +322,13 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"crossterm_winapi",
"derive_more",
"document-features",
"mio",
"parking_lot",
- "rustix 1.1.2",
+ "rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -366,10 +344,30 @@ dependencies = [
]
[[package]]
-name = "darling"
-version = "0.20.11"
+name = "crypto-common"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "csscolorparser"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf"
+dependencies = [
+ "lab",
+ "phf",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [
"darling_core",
"darling_macro",
@@ -377,58 +375,73 @@ dependencies = [
[[package]]
name = "darling_core"
-version = "0.20.11"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
dependencies = [
- "fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "darling_macro"
-version = "0.20.11"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [
"darling_core",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
-name = "deranged"
-version = "0.5.4"
+name = "deltae"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071"
+checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
- "serde_core",
]
[[package]]
name = "derive_more"
-version = "2.0.1"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
-version = "2.0.1"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
- "syn",
+ "rustc_version",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
]
[[package]]
@@ -449,7 +462,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
- "windows-sys 0.61.1",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -460,24 +473,18 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "document-features"
-version = "0.2.11"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
dependencies = [
"litrs",
]
-[[package]]
-name = "dyn-clone"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
-
[[package]]
name = "either"
version = "1.15.0"
@@ -503,14 +510,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.61.1",
+ "windows-sys 0.61.2",
]
[[package]]
-name = "find-msvc-tools"
-version = "0.1.2"
+name = "euclid"
+version = "0.22.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
+checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
+dependencies = [
+ "bit-set",
+ "regex",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "filedescriptor"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
+dependencies = [
+ "libc",
+ "thiserror 1.0.69",
+ "winapi",
+]
+
+[[package]]
+name = "finl_unicode"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "fnv"
@@ -524,6 +573,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -535,90 +590,100 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-macro"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "futures-sink"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
- "pin-utils",
"slab",
]
[[package]]
-name = "getrandom"
-version = "0.2.16"
+name = "generic-array"
+version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "typenum",
+ "version_check",
]
[[package]]
name = "getrandom"
-version = "0.3.3"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
- "wasi 0.14.7+wasi-0.2.4",
+ "wasip2",
]
[[package]]
-name = "gimli"
-version = "0.32.3"
+name = "getrandom"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
-
-[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasip3",
+]
[[package]]
name = "hashbrown"
@@ -626,16 +691,19 @@ version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "allocator-api2",
- "equivalent",
- "foldhash",
+ "foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
-version = "0.16.0"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
[[package]]
name = "heck"
@@ -651,12 +719,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
-version = "1.3.1"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
- "fnv",
"itoa",
]
@@ -697,9 +764,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "1.7.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
+checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [
"atomic-waker",
"bytes",
@@ -734,13 +801,12 @@ dependencies = [
[[package]]
name = "hyper-util"
-version = "0.1.17"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"bytes",
"futures-channel",
- "futures-core",
"futures-util",
"http",
"http-body",
@@ -768,35 +834,11 @@ dependencies = [
"tower-service",
]
-[[package]]
-name = "iana-time-zone"
-version = "0.1.64"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
-dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
-]
-
[[package]]
name = "icu_collections"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -807,9 +849,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -820,11 +862,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
- "displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -835,42 +876,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
-version = "2.0.1"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
dependencies = [
- "displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
- "potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
-version = "2.0.1"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
[[package]]
name = "icu_provider"
-version = "2.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
- "stable_deref_trait",
- "tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -878,6 +915,12 @@ dependencies = [
"zerovec",
]
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -907,94 +950,76 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "1.9.3"
+version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
-dependencies = [
- "autocfg",
- "hashbrown 0.12.3",
- "serde",
-]
-
-[[package]]
-name = "indexmap"
-version = "2.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
- "hashbrown 0.16.0",
+ "hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "indoc"
-version = "2.0.6"
+version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
+checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
+dependencies = [
+ "rustversion",
+]
[[package]]
name = "insta"
-version = "1.43.2"
+version = "1.46.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
+checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4"
dependencies = [
"console",
"once_cell",
"similar",
+ "tempfile",
]
[[package]]
name = "instability"
-version = "0.3.9"
+version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
+checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d"
dependencies = [
"darling",
"indoc",
"proc-macro2",
"quote",
- "syn",
-]
-
-[[package]]
-name = "io-uring"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
-dependencies = [
- "bitflags",
- "cfg-if",
- "libc",
+ "syn 2.0.117",
]
[[package]]
name = "is_terminal_polyfill"
-version = "1.70.1"
+version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
-version = "0.13.0"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "itoa"
-version = "1.0.15"
+version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "jiff"
-version = "0.2.15"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
+checksum = "b3e3d65f018c6ae946ab16e80944b97096ed73c35b221d1c478a6c81d8f57940"
dependencies = [
"jiff-static",
"jiff-tzdb",
@@ -1002,26 +1027,26 @@ dependencies = [
"log",
"portable-atomic",
"portable-atomic-util",
- "serde",
- "windows-sys 0.59.0",
+ "serde_core",
+ "windows-sys 0.61.2",
]
[[package]]
name = "jiff-static"
-version = "0.2.15"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
+checksum = "a17c2b211d863c7fde02cbea8a3c1a439b98e109286554f2860bdded7ff83818"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "jiff-tzdb"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
+checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2"
[[package]]
name = "jiff-tzdb-platform"
@@ -1034,14 +1059,31 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.81"
+version = "0.3.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
+checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d"
dependencies = [
"once_cell",
"wasm-bindgen",
]
+[[package]]
+name = "kasuari"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b"
+dependencies = [
+ "hashbrown 0.16.1",
+ "portable-atomic",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "lab"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
+
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1049,111 +1091,175 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
-name = "libc"
-version = "0.2.176"
+name = "leb128fmt"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
+name = "libc"
+version = "0.2.182"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libredox"
-version = "0.1.10"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
+checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"libc",
]
[[package]]
-name = "linux-raw-sys"
-version = "0.4.15"
+name = "line-clipping"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a"
+dependencies = [
+ "bitflags 2.11.0",
+]
[[package]]
name = "linux-raw-sys"
-version = "0.11.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "litrs"
-version = "0.4.2"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
-version = "0.4.13"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
- "autocfg",
"scopeguard",
]
[[package]]
name = "log"
-version = "0.4.28"
+version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lru"
-version = "0.12.5"
+version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
- "hashbrown 0.15.5",
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "mac_address"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
+dependencies = [
+ "nix",
+ "winapi",
]
[[package]]
name = "memchr"
-version = "2.7.6"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
-name = "miniz_oxide"
-version = "0.8.9"
+name = "memmem"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
- "adler2",
+ "autocfg",
]
[[package]]
-name = "mio"
-version = "1.0.4"
+name = "minimal-lexical"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"log",
- "wasi 0.11.1+wasi-snapshot-preview1",
- "windows-sys 0.59.0",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.11.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
-version = "0.50.1"
+version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "num-conv"
-version = "0.1.0"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
[[package]]
name = "num-traits"
@@ -1165,12 +1271,12 @@ dependencies = [
]
[[package]]
-name = "object"
-version = "0.37.3"
+name = "num_threads"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
- "memchr",
+ "libc",
]
[[package]]
@@ -1181,9 +1287,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
-version = "1.70.1"
+version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "option-ext"
@@ -1191,15 +1297,24 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+[[package]]
+name = "ordered-float"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "oxker"
-version = "0.12.0"
+version = "0.13.0"
dependencies = [
"anyhow",
"bollard",
"cansi",
"clap",
- "crossterm 0.29.0",
+ "crossterm",
"directories",
"futures-util",
"insta",
@@ -1219,9 +1334,9 @@ dependencies = [
[[package]]
name = "parking_lot"
-version = "0.12.4"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -1229,29 +1344,118 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.11"
+version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
- "windows-targets 0.52.6",
+ "windows-link",
]
-[[package]]
-name = "paste"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+[[package]]
+name = "pest"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
+dependencies = [
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -1266,24 +1470,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
-version = "1.11.1"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "portable-atomic-util"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5"
dependencies = [
"portable-atomic",
]
[[package]]
name = "potential_utf"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -1304,19 +1508,29 @@ dependencies = [
]
[[package]]
-name = "proc-macro2"
-version = "1.0.101"
+name = "prettyplease"
+version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.40"
+version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
@@ -1327,6 +1541,15 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
[[package]]
name = "rand"
version = "0.9.2"
@@ -1334,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
- "rand_core",
+ "rand_core 0.9.5",
]
[[package]]
@@ -1344,46 +1567,116 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.9.5",
]
[[package]]
name = "rand_core"
-version = "0.9.3"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
- "getrandom 0.3.3",
+ "getrandom 0.3.4",
]
[[package]]
name = "ratatui"
-version = "0.29.0"
+version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
+checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc"
dependencies = [
- "bitflags",
- "cassowary",
+ "instability",
+ "ratatui-core",
+ "ratatui-crossterm",
+ "ratatui-macros",
+ "ratatui-termwiz",
+ "ratatui-widgets",
+]
+
+[[package]]
+name = "ratatui-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
+dependencies = [
+ "bitflags 2.11.0",
"compact_str",
- "crossterm 0.28.1",
+ "hashbrown 0.16.1",
+ "indoc",
+ "itertools",
+ "kasuari",
+ "lru",
+ "strum",
+ "thiserror 2.0.18",
+ "unicode-segmentation",
+ "unicode-truncate",
+ "unicode-width",
+]
+
+[[package]]
+name = "ratatui-crossterm"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3"
+dependencies = [
+ "cfg-if",
+ "crossterm",
+ "instability",
+ "ratatui-core",
+]
+
+[[package]]
+name = "ratatui-macros"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4"
+dependencies = [
+ "ratatui-core",
+ "ratatui-widgets",
+]
+
+[[package]]
+name = "ratatui-termwiz"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c"
+dependencies = [
+ "ratatui-core",
+ "termwiz",
+]
+
+[[package]]
+name = "ratatui-widgets"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db"
+dependencies = [
+ "bitflags 2.11.0",
+ "hashbrown 0.16.1",
"indoc",
"instability",
"itertools",
- "lru",
- "paste",
+ "line-clipping",
+ "ratatui-core",
"strum",
+ "time",
"unicode-segmentation",
- "unicode-truncate",
- "unicode-width 0.2.0",
+ "unicode-width",
]
[[package]]
name = "redox_syscall"
-version = "0.5.17"
+version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
]
[[package]]
@@ -1392,61 +1685,60 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
- "getrandom 0.2.16",
+ "getrandom 0.2.17",
"libredox",
- "thiserror",
+ "thiserror 2.0.18",
]
[[package]]
-name = "ref-cast"
-version = "1.0.24"
+name = "regex"
+version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
- "ref-cast-impl",
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
]
[[package]]
-name = "ref-cast-impl"
-version = "1.0.24"
+name = "regex-automata"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
]
[[package]]
-name = "rustc-demangle"
-version = "0.1.26"
+name = "regex-syntax"
+version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]]
-name = "rustix"
-version = "0.38.44"
+name = "rustc_version"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys 0.4.15",
- "windows-sys 0.59.0",
+ "semver",
]
[[package]]
name = "rustix"
-version = "1.1.2"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags",
+ "bitflags 2.11.0",
"errno",
"libc",
- "linux-raw-sys 0.11.0",
- "windows-sys 0.61.1",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1457,33 +1749,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
-version = "1.0.20"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
-
-[[package]]
-name = "schemars"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
-dependencies = [
- "dyn-clone",
- "ref-cast",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "schemars"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
-dependencies = [
- "dyn-clone",
- "ref-cast",
- "serde",
- "serde_json",
-]
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "scopeguard"
@@ -1491,6 +1759,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+
[[package]]
name = "serde"
version = "1.0.228"
@@ -1518,20 +1792,21 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "serde_json"
-version = "1.0.145"
+version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
+ "indexmap",
"itoa",
"memchr",
- "ryu",
"serde",
"serde_core",
+ "zmij",
]
[[package]]
@@ -1553,14 +1828,14 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "serde_spanned"
-version = "1.0.2"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
+checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [
"serde_core",
]
@@ -1578,22 +1853,14 @@ dependencies = [
]
[[package]]
-name = "serde_with"
-version = "3.14.1"
+name = "sha2"
+version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
- "base64",
- "chrono",
- "hex",
- "indexmap 1.9.3",
- "indexmap 2.11.4",
- "schemars 0.9.0",
- "schemars 1.0.4",
- "serde",
- "serde_derive",
- "serde_json",
- "time",
+ "cfg-if",
+ "cpufeatures",
+ "digest",
]
[[package]]
@@ -1605,12 +1872,6 @@ dependencies = [
"lazy_static",
]
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
[[package]]
name = "signal-hook"
version = "0.3.18"
@@ -1623,9 +1884,9 @@ dependencies = [
[[package]]
name = "signal-hook-mio"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
dependencies = [
"libc",
"mio",
@@ -1634,10 +1895,11 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
-version = "1.4.6"
+version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
+ "errno",
"libc",
]
@@ -1648,10 +1910,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
-name = "slab"
-version = "0.4.11"
+name = "siphasher"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
@@ -1661,19 +1929,19 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
-version = "0.6.0"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
dependencies = [
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
]
[[package]]
name = "stable_deref_trait"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
@@ -1689,31 +1957,41 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
-version = "0.26.3"
+version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
-version = "0.26.4"
+version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "rustversion",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "syn"
-version = "2.0.106"
+version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -1728,27 +2006,123 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
+dependencies = [
+ "fastrand",
+ "getrandom 0.4.1",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "terminfo"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
+dependencies = [
+ "fnv",
+ "nom",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "termwiz"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
+dependencies = [
+ "anyhow",
+ "base64",
+ "bitflags 2.11.0",
+ "fancy-regex",
+ "filedescriptor",
+ "finl_unicode",
+ "fixedbitset",
+ "hex",
+ "lazy_static",
+ "libc",
+ "log",
+ "memmem",
+ "nix",
+ "num-derive",
+ "num-traits",
+ "ordered-float",
+ "pest",
+ "pest_derive",
+ "phf",
+ "sha2",
+ "signal-hook",
+ "siphasher",
+ "terminfo",
+ "termios",
+ "thiserror 1.0.69",
+ "ucd-trie",
+ "unicode-segmentation",
+ "vtparse",
+ "wezterm-bidi",
+ "wezterm-blob-leases",
+ "wezterm-color-types",
+ "wezterm-dynamic",
+ "wezterm-input-types",
+ "winapi",
]
[[package]]
name = "thiserror"
-version = "2.0.16"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.16"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
]
[[package]]
@@ -1762,40 +2136,30 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.44"
+version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
- "itoa",
+ "libc",
"num-conv",
+ "num_threads",
"powerfmt",
- "serde",
+ "serde_core",
"time-core",
- "time-macros",
]
[[package]]
name = "time-core"
-version = "0.1.6"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
-
-[[package]]
-name = "time-macros"
-version = "0.2.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
-dependencies = [
- "num-conv",
- "time-core",
-]
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "tinystr"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -1803,40 +2167,37 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.47.1"
+version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
- "backtrace",
"bytes",
- "io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "slab",
"socket2",
"tokio-macros",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "tokio-util"
-version = "0.7.16"
+version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
@@ -1847,9 +2208,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.9.7"
+version = "1.0.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
+checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
dependencies = [
"serde_core",
"serde_spanned",
@@ -1860,18 +2221,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.7.2"
+version = "1.0.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
+checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
-version = "1.0.3"
+version = "1.0.9+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
+checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
dependencies = [
"winnow",
]
@@ -1884,9 +2245,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.41"
+version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@@ -1895,20 +2256,20 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.30"
+version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
name = "tracing-core"
-version = "0.1.34"
+version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@@ -1927,9 +2288,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
-version = "0.3.20"
+version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
+checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"nu-ansi-term",
"sharded-slab",
@@ -1946,16 +2307,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
-name = "unicase"
-version = "2.8.1"
+name = "typenum"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unicase"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
-version = "1.0.19"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-segmentation"
@@ -1965,32 +2338,32 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-truncate"
-version = "1.1.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
+checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
dependencies = [
"itertools",
"unicode-segmentation",
- "unicode-width 0.1.14",
+ "unicode-width",
]
[[package]]
name = "unicode-width"
-version = "0.1.14"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
-name = "unicode-width"
-version = "0.2.0"
+name = "unicode-xid"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "url"
-version = "2.5.7"
+version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
@@ -2012,13 +2385,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.18.1"
+version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
+checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
dependencies = [
- "getrandom 0.3.3",
+ "atomic",
+ "getrandom 0.4.1",
"js-sys",
- "rand",
+ "rand 0.9.2",
"wasm-bindgen",
]
@@ -2028,6 +2402,21 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "vtparse"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0"
+dependencies = [
+ "utf8parse",
+]
+
[[package]]
name = "want"
version = "0.3.1"
@@ -2044,28 +2433,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
-name = "wasi"
-version = "0.14.7+wasi-0.2.4"
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
- "wasip2",
+ "wit-bindgen",
]
[[package]]
-name = "wasip2"
-version = "1.0.1+wasi-0.2.4"
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
-version = "0.2.104"
+version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
+checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac"
dependencies = [
"cfg-if",
"once_cell",
@@ -2074,25 +2463,11 @@ dependencies = [
"wasm-bindgen-shared",
]
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
-dependencies = [
- "bumpalo",
- "log",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.104"
+version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
+checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2100,26 +2475,132 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.104"
+version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
+checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af"
dependencies = [
+ "bumpalo",
"proc-macro2",
"quote",
- "syn",
- "wasm-bindgen-backend",
+ "syn 2.0.117",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.104"
+version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
+checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41"
dependencies = [
"unicode-ident",
]
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.11.0",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "semver",
+]
+
+[[package]]
+name = "wezterm-bidi"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec"
+dependencies = [
+ "log",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-blob-leases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7"
+dependencies = [
+ "getrandom 0.3.4",
+ "mac_address",
+ "sha2",
+ "thiserror 1.0.69",
+ "uuid",
+]
+
+[[package]]
+name = "wezterm-color-types"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
+dependencies = [
+ "csscolorparser",
+ "deltae",
+ "lazy_static",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-dynamic"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac"
+dependencies = [
+ "log",
+ "ordered-float",
+ "strsim",
+ "thiserror 1.0.69",
+ "wezterm-dynamic-derive",
+]
+
+[[package]]
+name = "wezterm-dynamic-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "wezterm-input-types"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e"
+dependencies = [
+ "bitflags 1.3.2",
+ "euclid",
+ "lazy_static",
+ "serde",
+ "wezterm-dynamic",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -2142,73 +2623,11 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-[[package]]
-name = "windows-core"
-version = "0.62.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
-dependencies = [
- "windows-implement",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-implement"
-version = "0.60.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-interface"
-version = "0.59.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "windows-link"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
-
-[[package]]
-name = "windows-result"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets 0.52.6",
-]
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
@@ -2225,14 +2644,14 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
- "windows-targets 0.53.4",
+ "windows-targets 0.53.5",
]
[[package]]
name = "windows-sys"
-version = "0.61.1"
+version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
@@ -2255,19 +2674,19 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.53.4"
+version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
- "windows_aarch64_gnullvm 0.53.0",
- "windows_aarch64_msvc 0.53.0",
- "windows_i686_gnu 0.53.0",
- "windows_i686_gnullvm 0.53.0",
- "windows_i686_msvc 0.53.0",
- "windows_x86_64_gnu 0.53.0",
- "windows_x86_64_gnullvm 0.53.0",
- "windows_x86_64_msvc 0.53.0",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
]
[[package]]
@@ -2278,9 +2697,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
@@ -2290,9 +2709,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
@@ -2302,9 +2721,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
@@ -2314,9 +2733,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
@@ -2326,9 +2745,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
@@ -2338,9 +2757,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -2350,9 +2769,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
@@ -2362,35 +2781,116 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
-version = "0.7.13"
+version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
[[package]]
name = "wit-bindgen"
-version = "0.46.0"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "prettyplease",
+ "syn 2.0.117",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.11.0",
+ "indexmap",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
[[package]]
name = "writeable"
-version = "0.6.1"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
- "serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -2398,34 +2898,34 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"synstructure",
]
[[package]]
name = "zerocopy"
-version = "0.8.27"
+version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
+checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.27"
+version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
+checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -2445,15 +2945,15 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
"synstructure",
]
[[package]]
name = "zerotrie"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -2462,9 +2962,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.4"
+version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -2473,11 +2973,17 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.11.1"
+version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/Cargo.toml b/Cargo.toml
index ed71efa..1125a77 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,13 @@
[package]
name = "oxker"
-version = "0.12.0"
+version = "0.13.0"
edition = "2024"
authors = ["Jack Wills "]
description = "A simple tui to view & control docker containers"
repository = "https://github.com/mrjackwills/oxker"
homepage = "https://github.com/mrjackwills/oxker"
license = "MIT"
+rust-version = "1.90.0"
readme = "README.md"
keywords = ["docker", "tui", "tokio", "terminal", "podman"]
categories = ["command-line-utilities"]
@@ -15,8 +16,6 @@ categories = ["command-line-utilities"]
unsafe_code = "forbid"
[lints.clippy]
-nursery = { level = "warn", priority = -1 }
-pedantic = { level = "warn", priority = -1 }
expect_used = "warn"
todo = "warn"
unused_async = "warn"
@@ -27,7 +26,7 @@ similar_names = "allow"
[dependencies]
anyhow = "1.0"
-bollard = "0.19"
+bollard = "0.20"
cansi = "2.2"
clap = { version = "4.5", features = ["color", "derive", "unicode"] }
crossterm = "0.29"
@@ -35,16 +34,20 @@ directories = "6.0"
futures-util = "0.3"
jiff = { version = "0.2", features = ["tzdb-bundle-always"] }
parking_lot = { version = "0.12" }
-ratatui = "0.29"
+ratatui = "0.30"
serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
+serde_json = { version = "1.0"}
serde_jsonc = "1.0"
-tokio = { version = "1.47", features = ["full"] }
+tokio = { version = "1.49", features = ["full"] }
tokio-util = "0.7"
-toml = { version = "0.9", default-features = false, features = ["parse", "serde"] }
+toml = { version = "1.0", default-features = false, features = ["parse", "serde"] }
tracing = "0.1"
tracing-subscriber = "0.3"
-uuid = { version = "1.18", features = ["fast-rng", "v4"] }
+uuid = { version = "1.21", features = ["fast-rng", "v4"] }
+
+[dev-dependencies]
+insta = "1.46"
+serde_json = { version = "1.0", features = ["preserve_order"]}
[profile.release]
lto = true
@@ -53,5 +56,4 @@ panic = 'abort'
strip = true
debug = false
-[dev-dependencies]
-insta = "1.42.2"
+
diff --git a/README.md b/README.md
index 7aaaa01..394121f 100644
--- a/README.md
+++ b/README.md
@@ -10,10 +10,12 @@
+
+
link to alternative screenshot
@@ -111,7 +113,7 @@ In application controls, these, amongst many other settings, can be customized w
|--|--|
| ```( tab )``` or ```( shift+tab )``` | Change panel, clicking on a panel also changes the selected panel.|
| ```( โ โ )``` or ```( j k )``` or ```( Home End )```| Scroll line in selected panel - mouse wheel will also scroll.|
-| ```( โ โ )``` | When logs panel selected, scroll horizontally across the text of the logs.|
+| ```( โ โ )``` | Scroll horizontally across text.|
| ```( ctrl )``` | Increase scroll speed, used in conjunction with scroll keys.|
| ```( enter )```| Run selected docker command.|
| ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. |
@@ -121,6 +123,7 @@ In application controls, these, amongst many other settings, can be customized w
| ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.|
| ```( \ )``` | Toggle the visibility of the logs panel.|
| ```( e )``` | Exec into the selected container - not available on Windows.|
+| ```( i )``` | Enter container inspect mode. |
| ```( f )``` | Force clear the screen & redraw the gui.|
| ```( h )``` | Toggle help menu.|
| ```( m )``` | Toggle mouse capture - if disabled, text on screen can be selected.|
diff --git a/containerised/DOCKERHUB_README.md b/containerised/DOCKERHUB_README.md
index d073b02..4a0b238 100644
--- a/containerised/DOCKERHUB_README.md
+++ b/containerised/DOCKERHUB_README.md
@@ -8,9 +8,15 @@
-
-
+
+
+
+
+
+ link to alternative screenshot
+
+
## Run
diff --git a/containerised/Dockerfile b/containerised/Dockerfile
index e05760a..0f93266 100644
--- a/containerised/Dockerfile
+++ b/containerised/Dockerfile
@@ -6,7 +6,7 @@ FROM --platform=$BUILDPLATFORM rust:slim AS builder
ARG TARGETARCH
-# These are build platform depandant, but will be ignored if not needed
+# These are build platform dependant, but will be ignored if not needed
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER="aarch64-linux-gnu-gcc"
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-lgcc"
ENV CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER="arm-linux-gnueabihf-ld"
diff --git a/create_release.sh b/create_release.sh
index 8b50e64..393ccbf 100755
--- a/create_release.sh
+++ b/create_release.sh
@@ -232,8 +232,12 @@ cross_build_x86_windows() {
# Build, using zig-build, for Apple silicon
zig_build_aarch64_apple() {
# mkdir /workspace/oxker/target
- echo -e "${YELLOW}docker run --rm -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin${RESET}"
- docker run --rm -v "$(pwd):/io" -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin
+ echo -e "${YELLOW}docker run --rm -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild bash -e -c 'rustup update stable && rustup default stable && rustup target add aarch64-apple-darwin && cargo zigbuild --release --target aarch64-apple-darwin${RESET}"
+
+ docker run --rm -v "$(pwd):/io" -w /io \
+ ghcr.io/rust-cross/cargo-zigbuild \
+ bash -ec 'rustup update stable && rustup default stable && rustup target add aarch64-apple-darwin && cargo zigbuild --release --target aarch64-apple-darwin'
+
if ask_yn "sudo chown $(pwd)/target"; then
echo -e "${YELLOW}sudo chown -R vscode:vscode $(pwd)/target${RESET}"
sudo chown -R vscode:vscode "$(pwd)/target"
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 7c58aac..2ef1418 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -4,7 +4,7 @@ networks:
name: oxker-examaple-net
services:
postgres:
- image: postgres:17-alpine
+ image: postgres:18-alpine
container_name: postgres
environment:
- POSTGRES_PASSWORD=never_use_this_password_in_production
diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc
index 3b1f8fd..db1251f 100644
--- a/example_config/example.config.jsonc
+++ b/example_config/example.config.jsonc
@@ -18,8 +18,8 @@
"show_timestamp": true,
// Don't draw gui - for debugging - mostly pointless
"gui": true,
- // Docker host location
- "host": "/var/run/docker.sock",
+ // Docker host location. Will take priority over a DOCKER_HOST env.
+ // "host": "/var/run/docker.sock",
// Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.01234567
// *Should* accept any valid strftime string up to 32 chars, see https://strftime.org/
"timestamp_format": "%Y-%m-%dT%H:%M:%S.%8f",
@@ -104,10 +104,10 @@
"k"
],
// Horizontal scroll of the logs
- "log_scroll_forward": [
+ "scroll_forward": [
"right"
],
- "log_scroll_back": [
+ "scroll_back": [
"left"
],
// Select next panel
@@ -170,6 +170,10 @@
"log_section_toggle": [
"\\"
],
+ // Toggle to inspect container screen
+ "inspect": [
+ "i"
+ ],
// Force a complete clear & redraw of the screen
"force_redraw": [
"f"
@@ -280,6 +284,27 @@
// Ports & IP listing text
"text": "white"
},
+ // The bandwidth chart
+ "chart_bandwidth": {
+ //Background color of panel
+ "background": "reset",
+ // Border color
+ "border": "white",
+ // Maximum RX value - again paused & stopped colors not yet customizable
+ "max_rx": "#FFE9C1",
+ // # Maximum TX value - again paused & stopped colors not yet customizable
+ "max_tx": "#CD8C8C",
+ // RX points on the chart - again paused & stopped colors not yet customizable
+ "points_rx": "#FFE9C1",
+ // TX points on the chart - again paused & stopped colors not yet customizable
+ "points_tx": "#CD8C8C",
+ // RX title color
+ "title_rx": "#FFE9C1",
+ // TX title color
+ "title_tx": "#CD8C8C",
+ // The charts y-axis
+ "y_axis": "white"
+ },
// The filter panel
"filter": {
// Background color of panel
diff --git a/example_config/example.config.toml b/example_config/example.config.toml
index 8d5f1db..1b1a0c1 100644
--- a/example_config/example.config.toml
+++ b/example_config/example.config.toml
@@ -24,8 +24,8 @@ show_timestamp = true
# Don't draw gui - for debugging - mostly pointless
gui = true
-# Docker host location
-host = "/var/run/docker.sock"
+# Docker host location. Will take priority over a DOCKER_HOST env.
+# host = "/var/run/docker.sock"
# Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC
timezone = "Etc/UTC"
@@ -95,8 +95,8 @@ scroll_start = ["home"]
# scroll up a list by one item
scroll_up = ["up", "k"]
# Horizontal scroll of the logs
-log_scroll_forward = ["right"]
-log_scroll_back = ["left"]
+scroll_forward = ["right"]
+scroll_back = ["left"]
# Select next panel
select_next_panel = ["tab"]
# Select previous panel
@@ -122,6 +122,8 @@ log_section_height_decrease = ["-"]
log_section_height_increase = ["+"]
# Toggle visibility of the log section
log_section_toggle = ["\\"]
+# Toggle to inspect container screen
+inspect = ["i"]
@@ -254,6 +256,27 @@ points = "cyan"
# The charts y-axis
y_axis = "white"
+# The bandwidth chart
+[colors.chart_bandwidth]
+# Background color of panel
+background = "reset"
+# Border color
+border = "white"
+# Maximum RX value - again paused & stopped colors not yet customizable
+max_rx = "#FFE9C1"
+# Maximum TX value - again paused & stopped colors not yet customizable
+max_tx = "#CD8C8C"
+# RX points on the chart - again paused & stopped colors not yet customizable
+points_rx = "#FFE9C1"
+# TX points on the chart - again paused & stopped colors not yet customizable
+points_tx = "#CD8C8C"
+# TX title color
+title_tx = "#FFE9C1"
+# RX title color
+title_rx = "#CD8C8C"
+# The charts y-axis
+y_axis = "white"
+
# The ports chart
[colors.chart_ports]
# Background color of panel
diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs
index 11e39b3..252d982 100644
--- a/src/app_data/container_state.rs
+++ b/src/app_data/container_state.rs
@@ -5,7 +5,7 @@ use std::{
net::IpAddr,
};
-use bollard::service::Port;
+use bollard::secret::{ContainerSummaryHealthStatusEnum, PortSummary};
use jiff::{Timestamp, tz::TimeZone};
use ratatui::{
layout::Size,
@@ -24,8 +24,12 @@ const ONE_GB: f64 = ONE_MB * 1000.0;
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum ScrollDirection {
- Next,
- Previous,
+ // Next,
+ // Previous,
+ Up,
+ Down,
+ Left,
+ Right,
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
@@ -121,8 +125,8 @@ pub struct ContainerPorts {
pub public: Option,
}
-impl From for ContainerPorts {
- fn from(value: Port) -> Self {
+impl From for ContainerPorts {
+ fn from(value: PortSummary) -> Self {
Self {
ip: value.ip.and_then(|i| i.parse::().ok()),
private: value.private_port,
@@ -185,8 +189,10 @@ impl StatefulList {
pub fn scroll(&mut self, scroll: &ScrollDirection) {
match scroll {
- ScrollDirection::Next => self.next(),
- ScrollDirection::Previous => self.previous(),
+ ScrollDirection::Down => self.next(),
+ ScrollDirection::Up => self.previous(),
+ // TODO set offset
+ _ => (),
}
}
@@ -414,7 +420,7 @@ impl fmt::Display for State {
}
/// Items for the container control list
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DockerCommand {
Pause,
Restart,
@@ -570,8 +576,116 @@ impl fmt::Display for ByteStats {
}
}
-pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State);
-pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State);
+#[derive(Debug, Default, Clone, Copy, Eq)]
+pub struct BandwidthStat(u64);
+
+#[cfg(test)]
+impl BandwidthStat {
+ pub fn new(x: u64) -> Self {
+ Self(x)
+ }
+}
+
+impl PartialEq for BandwidthStat {
+ fn eq(&self, other: &Self) -> bool {
+ self.0 == other.0
+ }
+}
+
+impl PartialOrd for BandwidthStat {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for BandwidthStat {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.0.cmp(&other.0)
+ }
+}
+
+#[allow(clippy::cast_precision_loss)]
+impl Stats for BandwidthStat {
+ fn get_value(&self) -> f64 {
+ self.0 as f64
+ }
+}
+
+/// convert from bytes to per second, using 1000 instead of 1024
+impl fmt::Display for BandwidthStat {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let as_f64 = self.get_value();
+ let p = match as_f64 {
+ x if x >= ONE_GB => format!("{y:.2} GB/s", y = as_f64 / ONE_GB),
+ x if x >= ONE_MB => format!("{y:.2} Mb/s", y = as_f64 / ONE_MB),
+ _ => format!("{y:.2} kb/s", y = as_f64 / ONE_KB),
+ };
+ write!(f, "{p:>x$}", x = f.width().unwrap_or(1))
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct NetworkBandwidth(VecDeque);
+
+impl NetworkBandwidth {
+ pub fn new() -> Self {
+ Self(VecDeque::with_capacity(60))
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ /// Find the highest speed recorded in the vecque
+ pub fn max(&self) -> BandwidthStat {
+ self.to_vec_f64()
+ .iter()
+ .map(|(_, speed)| *speed)
+ .max_by(|a, b| a.total_cmp(b))
+ .map(|m| BandwidthStat(m as u64))
+ .unwrap_or(BandwidthStat(0))
+ }
+
+ pub fn push(&mut self, x: u64) {
+ if self.0.len() >= 60 {
+ self.0.pop_front();
+ }
+ self.0.push_back(BandwidthStat(x));
+ }
+
+ /// Get the current total amount of traffic on a given device
+ pub fn current_total(&self) -> ByteStats {
+ self.0
+ .back()
+ .map_or(ByteStats::default(), |i| ByteStats::new(i.0))
+ }
+
+ /// Convert to f64 for use in the network graph
+ pub fn to_vec_f64(&self) -> Vec<(f64, f64)> {
+ self.0
+ .iter()
+ .zip(self.0.iter().skip(1))
+ .enumerate()
+ .map(|(i, (prev, current))| (i as f64, current.0.saturating_sub(prev.0) as f64))
+ .collect()
+ }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct ChartsData {
+ pub memory: ChartSeries,
+ pub cpu: ChartSeries,
+ pub rx: ChartSeries,
+ pub tx: ChartSeries,
+ pub state: State,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct ChartSeries {
+ pub dataset: Vec<(f64, f64)>,
+ pub max: T,
+ pub current: T,
+}
/// Used to make sure that each log entry, for each container, is unique,
/// will only push a log entry into the logs vec if timestamp of said log entry isn't in the hashset
@@ -615,7 +729,8 @@ pub struct Logs {
tz: HashSet,
search_results: Vec,
search_term: Option,
- offset: u16,
+ offset: usize,
+ max_offset: usize,
max_log_len: usize,
adjusted_max_width: usize,
adjust_max_width_text_len: usize,
@@ -629,6 +744,7 @@ impl Default for Logs {
lines,
tz: HashSet::new(),
offset: 0,
+ max_offset: 0,
search_term: None,
search_results: vec![],
adjusted_max_width: 0,
@@ -687,23 +803,26 @@ impl Logs {
.position(|i| i == ¤t_selected)
{
if let Some(new_index) = match sd {
- ScrollDirection::Next => current_position.checked_add(1),
- ScrollDirection::Previous => current_position.checked_sub(1),
- } {
- if let Some(f) = self.search_results.get(new_index) {
- self.lines.state.select(Some(*f));
- return Some(());
- }
+ ScrollDirection::Down => current_position.checked_add(1),
+ ScrollDirection::Up => current_position.checked_sub(1),
+ // TODO set offset
+ _ => None,
+ } && let Some(f) = self.search_results.get(new_index)
+ {
+ self.lines.state.select(Some(*f));
+ return Some(());
}
} else {
let range = match sd {
- ScrollDirection::Previous => (0..=current_selected).rev().collect::>(),
- ScrollDirection::Next => (current_selected
+ ScrollDirection::Up => (0..=current_selected).rev().collect::>(),
+ ScrollDirection::Down => (current_selected
..=self
.search_results
.last()
.map_or_else(|| current_selected, |i| *i))
.collect::>(),
+ // TODO set offset
+ _ => vec![],
};
for i in range {
if self.search_results.contains(&i) {
@@ -821,7 +940,7 @@ impl Logs {
if self.horizontal_scroll_able(width) {
let text_width = self.adjust_max_width_text_len;
let arrow_left = if self.offset > 0 { " โ" } else { " " };
- let arrow_right = if usize::from(self.offset) < self.adjusted_max_width {
+ let arrow_right = if self.offset < self.adjusted_max_width {
"โ "
} else {
" "
@@ -884,10 +1003,10 @@ impl Logs {
pub fn get_visible_logs(&self, size: Size, padding: usize) -> Vec> {
let current_index = self.lines.state.selected().unwrap_or_default();
let height_padding = usize::from(size.height) + padding;
- let char_offset = if usize::from(self.offset) > self.max_log_len {
+ let char_offset = if self.offset > self.max_log_len {
self.max_log_len
} else {
- self.offset.into()
+ self.offset
};
self.lines
@@ -921,11 +1040,12 @@ impl Logs {
/// Add a padding so one char will always be visilbe?
pub fn forward(&mut self, width: u16) {
- let offset = usize::from(self.offset);
- if self.horizontal_scroll_able(width) {
- if self.adjusted_max_width > 0 && offset < self.adjusted_max_width {
- self.offset = self.offset.saturating_add(1);
- }
+ // Need to set a max_offset, instead of using a width each time
+ if self.horizontal_scroll_able(width)
+ && self.adjusted_max_width > 0
+ && self.offset < self.adjusted_max_width
+ {
+ self.offset = self.offset.saturating_add(1);
}
}
@@ -970,6 +1090,7 @@ pub struct ContainerItem {
pub cpu_stats: VecDeque,
pub created: u64,
pub docker_controls: StatefulList,
+ pub health: Option,
pub id: ContainerId,
pub image: ContainerImage,
pub is_oxker: bool,
@@ -979,10 +1100,10 @@ pub struct ContainerItem {
pub mem_stats: VecDeque,
pub name: ContainerName,
pub ports: Vec,
- pub rx: ByteStats,
+ pub rx: NetworkBandwidth,
pub state: State,
pub status: ContainerStatus,
- pub tx: ByteStats,
+ pub tx: NetworkBandwidth,
}
/// Basic display information, for when running in debug mode
@@ -1019,6 +1140,7 @@ impl ContainerItem {
cpu_stats: VecDeque::with_capacity(60),
created,
docker_controls,
+ health: None,
id,
image: image.into(),
is_oxker,
@@ -1028,10 +1150,10 @@ impl ContainerItem {
mem_stats: VecDeque::with_capacity(60),
name: name.into(),
ports,
- rx: ByteStats::default(),
+ rx: NetworkBandwidth::new(),
state,
status,
- tx: ByteStats::default(),
+ tx: NetworkBandwidth::new(),
}
}
@@ -1057,7 +1179,7 @@ impl ContainerItem {
self.cpu_stats
.iter()
.enumerate()
- .map(|i| (i.0 as f64, i.1.0))
+ .map(|(i, v)| (i as f64, v.0))
.collect::>()
}
@@ -1067,24 +1189,65 @@ impl ContainerItem {
self.mem_stats
.iter()
.enumerate()
- .map(|i| (i.0 as f64, i.1.0 as f64))
+ .map(|(i, v)| (i as f64, v.0 as f64))
.collect::>()
}
/// Get all cpu chart data
- fn get_cpu_chart_data(&self) -> CpuTuple {
- (self.get_cpu_dataset(), self.max_cpu_stats(), self.state)
+ fn get_cpu_chart_data(&self) -> ChartSeries {
+ ChartSeries {
+ dataset: self.get_cpu_dataset(),
+ max: self.max_cpu_stats(),
+ current: self
+ .cpu_stats
+ .back()
+ .map_or_else(|| CpuStats::new(0.0), |i| *i),
+ }
}
/// Get all mem chart data
- fn get_mem_chart_data(&self) -> MemTuple {
- (self.get_mem_dataset(), self.max_mem_stats(), self.state)
+ fn get_mem_chart_data(&self) -> ChartSeries {
+ ChartSeries {
+ dataset: self.get_mem_dataset(),
+ max: self.max_mem_stats(),
+ current: self
+ .mem_stats
+ .back()
+ .map_or_else(|| ByteStats::new(0), |i| *i),
+ }
+ }
+
+ /// Get all mem chart data
+ /// Don't understand what we are doing here
+ fn get_bandwidth_chart_tx_data(&self) -> ChartSeries {
+ let data = self.tx.to_vec_f64();
+ ChartSeries {
+ current: BandwidthStat(data.last().map_or(0, |i| i.1 as u64)),
+ dataset: data,
+ max: self.tx.max(),
+ }
+ }
+
+ /// Get all mem chart data
+ fn get_bandwidth_chart_rx_data(&self) -> ChartSeries {
+ let data = self.rx.to_vec_f64();
+ ChartSeries {
+ current: BandwidthStat(data.last().map_or(0, |i| i.1 as u64)),
+ dataset: data,
+ max: self.rx.max(),
+ }
}
/// Get chart info for cpu & memory in one function
/// So only need to call .lock() once
- pub fn get_chart_data(&self) -> (CpuTuple, MemTuple) {
- (self.get_cpu_chart_data(), self.get_mem_chart_data())
+ pub fn get_chart_data(&self) -> ChartsData {
+ ChartsData {
+ memory: self.get_mem_chart_data(),
+ cpu: self.get_cpu_chart_data(),
+ rx: self.get_bandwidth_chart_rx_data(),
+ tx: self.get_bandwidth_chart_tx_data(),
+ state: self.state,
+ }
}
}
diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs
index 1139ad6..9e05986 100644
--- a/src/app_data/mod.rs
+++ b/src/app_data/mod.rs
@@ -1,4 +1,4 @@
-use bollard::models::ContainerSummary;
+use bollard::{models::ContainerSummary, secret::ContainerInspectResponse};
use core::fmt;
use parking_lot::Mutex;
use ratatui::{layout::Size, text::Text, widgets::ListState};
@@ -114,6 +114,45 @@ impl Filter {
}
}
+#[derive(Debug, Clone)]
+pub struct InspectData {
+ pub width: usize,
+ pub height: usize,
+ pub as_string: String,
+ pub name: String,
+ pub id: ContainerId, // pub as_lines: Vec>,
+}
+
+impl From for InspectData {
+ fn from(input: ContainerInspectResponse) -> Self {
+ let as_string = serde_json::to_string_pretty(&input)
+ .unwrap_or_default()
+ .lines()
+ .skip(1)
+ .collect::>()
+ .split_last()
+ .map(|(_, data)| data)
+ .unwrap_or_default()
+ .join("\n");
+
+ let height = as_string.lines().count();
+
+ let mut width = 0;
+ for i in as_string.lines() {
+ width = width.max(i.chars().count());
+ }
+
+ Self {
+ name: input.name.unwrap_or_default(),
+ // TODO maybe make this an Option?
+ id: ContainerId::from(input.id.unwrap_or_default().as_str()),
+ width,
+ height,
+ as_string,
+ }
+ }
+}
+
/// Global app_state, stored in an Arc
#[derive(Debug, Clone)]
#[cfg(not(test))]
@@ -122,6 +161,7 @@ pub struct AppData {
error: Option,
filter: Filter,
hidden_containers: Vec,
+ inspect_data: Option,
rerender: Arc,
sorted_by: Option<(Header, SortedOrder)>,
current_sorted_id: Vec,
@@ -136,6 +176,7 @@ pub struct AppData {
pub error: Option,
pub filter: Filter,
pub hidden_containers: Vec,
+ pub inspect_data: Option,
pub current_sorted_id: Vec,
pub rerender: Arc,
pub sorted_by: Option<(Header, SortedOrder)>,
@@ -151,6 +192,7 @@ impl AppData {
error: None,
filter: Filter::new(),
hidden_containers: vec![],
+ inspect_data: None,
rerender: Arc::clone(redraw),
sorted_by: None,
}
@@ -165,6 +207,18 @@ impl AppData {
.as_secs()
}
+ pub fn clear_inspect_data(&mut self) {
+ self.inspect_data = None;
+ }
+
+ pub fn set_inspect_data(&mut self, data: ContainerInspectResponse) {
+ self.inspect_data = Some(InspectData::from(data))
+ // self.inspect_data = Some(data)
+ }
+
+ pub fn get_inspect_data(&self) -> Option {
+ self.inspect_data.clone()
+ }
/// Filter related methods
/// Get the filterby and filter_term
pub const fn get_filter(&self) -> (FilterBy, Option<&String>) {
@@ -172,10 +226,10 @@ impl AppData {
}
pub fn log_search_scroll(&mut self, np: &ScrollDirection) {
- if let Some(i) = self.get_mut_selected_container() {
- if i.logs.search_scroll(np).is_some() {
- self.rerender.update_draw();
- }
+ if let Some(i) = self.get_mut_selected_container()
+ && i.logs.search_scroll(np).is_some()
+ {
+ self.rerender.update_draw();
}
}
@@ -329,6 +383,7 @@ impl AppData {
.iter()
.position(|i| self.get_selected_container_id().as_ref() == Some(&i.id)),
);
+ self.rerender.update_draw();
}
/// Remove the sorted header & order, and sort by default - created datetime
@@ -340,12 +395,12 @@ impl AppData {
/// Sort containers based on a given header, if headings match, and already ascending, remove sorting
pub fn set_sort_by_header(&mut self, selected_header: Header) {
let mut output = Some((selected_header, SortedOrder::Asc));
- if let Some((current_header, order)) = self.get_sorted() {
- if current_header == selected_header {
- match order {
- SortedOrder::Desc => output = None,
- SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)),
- }
+ if let Some((current_header, order)) = self.get_sorted()
+ && current_header == selected_header
+ {
+ match order {
+ SortedOrder::Desc => output = None,
+ SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)),
}
}
self.set_sorted(output);
@@ -412,12 +467,14 @@ impl AppData {
Header::Rx => item_ord
.0
.rx
- .cmp(&item_ord.1.rx)
+ .current_total()
+ .cmp(&item_ord.1.rx.current_total())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Tx => item_ord
.0
.tx
- .cmp(&item_ord.1.tx)
+ .current_total()
+ .cmp(&item_ord.1.tx.current_total())
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
Header::Name => item_ord
.0
@@ -459,7 +516,6 @@ impl AppData {
/// Get all the ContainerItems
/// Thnk this allow block can be removed with the 1.87 release of Clippy
- #[allow(clippy::missing_const_for_fn)]
pub fn get_container_items(&self) -> &[ContainerItem] {
&self.containers.items
}
@@ -556,10 +612,17 @@ impl AppData {
}
/// Get a mutable container by given id
+ #[cfg(not(test))]
fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
self.containers.items.iter_mut().find(|i| &i.id == id)
}
+ /// As above, but make it public to testing
+ #[cfg(test)]
+ pub fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
+ self.containers.items.iter_mut().find(|i| &i.id == id)
+ }
+
/// Get a mutable container by given id in the tmp_container vec
fn get_hidden_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
self.hidden_containers.iter_mut().find(|i| &i.id == id)
@@ -637,7 +700,7 @@ impl AppData {
.map(|i| &mut i.docker_controls.state)
}
- /// Get mutable Option of the currently selected container DockerConmand items
+ /// Get mutable Option of the currently selected container DockerCommand items
pub fn get_control_items(&mut self) -> Option<&mut Vec> {
self.get_mut_selected_container()
.map(|i| &mut i.docker_controls.items)
@@ -668,19 +731,22 @@ impl AppData {
}
pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) {
+ // Change this to set a max_offset, instead of taking in width each time, then can be combined with the log_scroll beneath
match sd {
- ScrollDirection::Next => {
+ ScrollDirection::Down => {
if let Some(i) = self.get_mut_selected_container() {
i.logs.forward(width);
self.rerender.update_draw();
}
}
- ScrollDirection::Previous => {
+ ScrollDirection::Up => {
if let Some(i) = self.get_mut_selected_container() {
i.logs.back();
self.rerender.update_draw();
}
}
+ // TODO set offset
+ _ => (),
}
}
@@ -688,8 +754,10 @@ impl AppData {
pub fn log_scroll(&mut self, scroll: &ScrollDirection) {
if let Some(i) = self.get_mut_selected_container() {
match scroll {
- ScrollDirection::Next => i.logs.next(),
- ScrollDirection::Previous => i.logs.previous(),
+ ScrollDirection::Down => i.logs.next(),
+ ScrollDirection::Up => i.logs.previous(),
+ // TODO set offset
+ _ => (),
}
self.rerender.update_draw();
}
@@ -731,7 +799,7 @@ impl AppData {
/// Chart data related methods
/// Get mutable Option of the currently selected container chart data
- pub fn get_chart_data(&self) -> Option<(CpuTuple, MemTuple)> {
+ pub fn get_chart_data(&self) -> Option {
self.containers
.state
.selected()
@@ -780,6 +848,7 @@ impl AppData {
for container in [&self.containers.items, &self.hidden_containers] {
for container in container {
+ // TODO refactor these
let cpu_count = container.cpu_stats.back().map_or_else(
|| count(&CpuStats::default().to_string()),
|i| count(&i.to_string()),
@@ -789,14 +858,19 @@ impl AppData {
|| count(&ByteStats::default().to_string()),
|i| count(&i.to_string()),
);
-
columns.cpu.1 = columns.cpu.1.max(cpu_count);
columns.image.1 = columns.image.1.max(count(&container.image.to_string()));
columns.mem.1 = columns.mem.1.max(mem_current_count);
columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string()));
columns.name.1 = columns.name.1.max(count(&container.name.to_string()));
- columns.net_rx.1 = columns.net_rx.1.max(count(&container.rx.to_string()));
- columns.net_tx.1 = columns.net_tx.1.max(count(&container.tx.to_string()));
+ columns.net_rx.1 = columns
+ .net_rx
+ .1
+ .max(count(&container.rx.current_total().to_string()));
+ columns.net_tx.1 = columns
+ .net_tx
+ .1
+ .max(count(&container.tx.current_total().to_string()));
columns.state.1 = columns.state.1.max(count(&container.state.to_string()));
columns.status.1 = columns.status.1.max(count(container.status.get()));
}
@@ -840,8 +914,12 @@ impl AppData {
container.mem_stats.push_back(ByteStats::new(mem));
}
- container.rx.update(rx);
- container.tx.update(tx);
+ // Only insert if alive, or if is empty, need two to create an entry in the bandwidth chart, so instead this fills in the RX/TX total columns
+ if container.rx.is_empty() || container.state.is_alive() {
+ container.rx.push(rx);
+ container.tx.push(tx);
+ }
+
container.mem_limit.update(mem_limit);
}
if self.is_selected_container(id) {
@@ -877,7 +955,7 @@ impl AppData {
// If removed container is currently selected, then change selected to previous
// This will default to 0 in any edge cases
if self.containers.state.selected().is_some() {
- self.containers.scroll(&ScrollDirection::Previous);
+ self.containers.scroll(&ScrollDirection::Up);
}
// Check is some, else can cause out of bounds error, if containers get removed before a docker update
if self.containers.items.get(index).is_some() {
@@ -896,7 +974,7 @@ impl AppData {
if f.starts_with('/') {
f.remove(0);
}
- (*f).to_string()
+ (*f).clone()
})
});
@@ -1277,13 +1355,16 @@ mod tests {
assert_eq!(result, &containers);
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
- i.rx = ByteStats::new(40);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(40);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
- i.rx = ByteStats::new(80);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(80);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
- i.rx = ByteStats::new(2);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(2);
}
// descending
@@ -1314,13 +1395,16 @@ mod tests {
assert_eq!(result, &containers);
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
- i.rx = ByteStats::new(400);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(400);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
- i.rx = ByteStats::new(80);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(80);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
- i.rx = ByteStats::new(83);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(83);
}
// descending
@@ -1378,13 +1462,16 @@ mod tests {
assert_eq!(result, &containers);
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
- i.rx = ByteStats::new(400);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(400);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("2")) {
- i.rx = ByteStats::new(80);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(80);
}
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("3")) {
- i.rx = ByteStats::new(83);
+ i.rx = NetworkBandwidth::new();
+ i.rx.push(83);
}
app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc)));
@@ -1444,7 +1531,7 @@ mod tests {
);
// Calling previous when at start has no effect
- app_data.containers_scroll(&ScrollDirection::Previous);
+ app_data.containers_scroll(&ScrollDirection::Up);
let result = app_data.get_selected_container_id();
assert_eq!(result, Some(ContainerId::from("1")));
let result = app_data.get_selected_container_id_state_name();
@@ -1467,7 +1554,7 @@ mod tests {
// Advance list state by 1
app_data.containers_start();
- app_data.containers.scroll(&ScrollDirection::Next);
+ app_data.containers.scroll(&ScrollDirection::Down);
let result = app_data.get_container_state();
assert_eq!(result.selected(), Some(1));
@@ -1511,7 +1598,7 @@ mod tests {
);
// Calling previous when at end has no effect
- app_data.containers.scroll(&ScrollDirection::Next);
+ app_data.containers.scroll(&ScrollDirection::Down);
let result = app_data.get_selected_container_id();
assert_eq!(result, Some(ContainerId::from("3")));
let result = app_data.get_selected_container_id_state_name();
@@ -1532,7 +1619,7 @@ mod tests {
let mut app_data = gen_appdata(&containers);
app_data.containers_end();
- app_data.containers.scroll(&ScrollDirection::Previous);
+ app_data.containers.scroll(&ScrollDirection::Up);
let result = app_data.get_container_state();
assert_eq!(result.selected(), Some(1));
assert_eq!(result.offset(), 0);
@@ -1548,7 +1635,7 @@ mod tests {
assert_eq!(result, None);
app_data.containers.start();
- app_data.containers.scroll(&ScrollDirection::Next);
+ app_data.containers.scroll(&ScrollDirection::Down);
let result = app_data.get_selected_container();
assert_eq!(result, Some(&containers[1]));
@@ -1635,7 +1722,7 @@ mod tests {
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
app_data.docker_controls_start();
- app_data.docker_controls_scroll(&ScrollDirection::Next);
+ app_data.docker_controls_scroll(&ScrollDirection::Down);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Restart));
@@ -1653,7 +1740,7 @@ mod tests {
assert_eq!(result, Some(DockerCommand::Delete));
// Next has no effect when at end
- app_data.docker_controls_scroll(&ScrollDirection::Next);
+ app_data.docker_controls_scroll(&ScrollDirection::Down);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Delete));
}
@@ -1665,14 +1752,14 @@ mod tests {
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
app_data.docker_controls_end();
- app_data.docker_controls_scroll(&ScrollDirection::Previous);
+ app_data.docker_controls_scroll(&ScrollDirection::Up);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Stop));
// previous has no effect when at start
app_data.docker_controls_start();
- app_data.docker_controls_scroll(&ScrollDirection::Previous);
+ app_data.docker_controls_scroll(&ScrollDirection::Up);
let result = app_data.selected_docker_controls();
assert_eq!(result, Some(DockerCommand::Pause));
}
@@ -1936,7 +2023,7 @@ mod tests {
assert_eq!(result, " 3/3 - container_1 - image_1");
// Change log state to no longer be at the end
- app_data.log_scroll(&ScrollDirection::Previous);
+ app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
}
@@ -1957,7 +2044,7 @@ mod tests {
assert_eq!(result, " - container_1 - image_1");
// change container
- app_data.containers_scroll(&ScrollDirection::Next);
+ app_data.containers_scroll(&ScrollDirection::Down);
let result = app_data.get_log_title();
assert_eq!(result, " - container_2 - image_2");
@@ -1968,7 +2055,7 @@ mod tests {
assert_eq!(result, " 3/3 - container_2 - image_2");
// Change log state to no longer be at the end
- app_data.log_scroll(&ScrollDirection::Previous);
+ app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_2 - image_2");
}
@@ -2075,7 +2162,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 1/3 - container_1 - image_1");
- app_data.log_scroll(&ScrollDirection::Next);
+ app_data.log_scroll(&ScrollDirection::Down);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(1));
@@ -2084,7 +2171,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
- app_data.log_scroll(&ScrollDirection::Next);
+ app_data.log_scroll(&ScrollDirection::Down);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
@@ -2092,7 +2179,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
- app_data.log_scroll(&ScrollDirection::Next);
+ app_data.log_scroll(&ScrollDirection::Down);
let result = app_data.get_log_state();
assert!(result.is_some());
@@ -2123,7 +2210,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 3/3 - container_1 - image_1");
- app_data.log_scroll(&ScrollDirection::Previous);
+ app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_state();
assert!(result.is_some());
@@ -2132,7 +2219,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 2/3 - container_1 - image_1");
- app_data.log_scroll(&ScrollDirection::Previous);
+ app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
@@ -2140,7 +2227,7 @@ mod tests {
let result = app_data.get_log_title();
assert_eq!(result, " 1/3 - container_1 - image_1");
- app_data.log_scroll(&ScrollDirection::Previous);
+ app_data.log_scroll(&ScrollDirection::Up);
let result = app_data.get_log_state();
assert!(result.is_some());
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
@@ -2164,26 +2251,49 @@ mod tests {
app_data.containers_start();
+ let mut rx = NetworkBandwidth::new();
+ rx.push(200);
+ rx.push(100);
+ rx.push(200);
+
+ let mut tx = NetworkBandwidth::new();
+ tx.push(300);
+ tx.push(600);
+ tx.push(900);
+
if let Some(item) = app_data.get_container_by_id(&ContainerId::from("1")) {
- item.cpu_stats = VecDeque::from([CpuStats::new(1.1), CpuStats::new(1.2)]);
+ item.cpu_stats = VecDeque::from([CpuStats::new(1.2), CpuStats::new(1.2)]);
item.mem_stats = VecDeque::from([ByteStats::new(1), ByteStats::new(2)]);
+ item.rx = rx;
+ item.tx = tx;
}
let result = app_data.get_chart_data();
assert_eq!(
result,
- Some((
- (
- vec![(0.0, 1.1), (1.0, 1.2)],
- CpuStats::new(1.2),
- State::Running(RunningState::Healthy),
- ),
- (
- vec![(0.0, 1.0), (1.0, 2.0)],
- ByteStats::new(2),
- State::Running(RunningState::Healthy),
- )
- ))
+ Some(ChartsData {
+ memory: ChartSeries {
+ dataset: vec![(0.0, 1.0), (1.0, 2.0)],
+ max: ByteStats::new(2),
+ current: ByteStats::new(2)
+ },
+ cpu: ChartSeries {
+ dataset: vec![(0.0, 1.2), (1.0, 1.2)],
+ max: CpuStats::new(1.2),
+ current: CpuStats::new(1.2)
+ },
+ rx: ChartSeries {
+ dataset: vec![(0.0, 0.0), (1.0, 100.0)],
+ max: BandwidthStat::new(100),
+ current: BandwidthStat::new(100)
+ },
+ tx: ChartSeries {
+ dataset: vec![(0.0, 300.0), (1.0, 300.0)],
+ max: BandwidthStat::new(300),
+ current: BandwidthStat::new(300)
+ },
+ state: State::Running(RunningState::Healthy)
+ })
);
}
@@ -2333,8 +2443,15 @@ mod tests {
assert_eq!(result[0].cpu_stats, VecDeque::from([CpuStats::new(10.0)]));
assert_eq!(result[0].mem_stats, VecDeque::from([ByteStats::new(10)]));
assert_eq!(result[0].mem_limit, ByteStats::new(10));
- assert_eq!(result[0].rx, ByteStats::new(10));
- assert_eq!(result[0].tx, ByteStats::new(10));
+
+ let mut rx = NetworkBandwidth::new();
+ rx.push(10);
+ let mut tx = NetworkBandwidth::new();
+ tx.push(10);
+ assert_eq!(result[0].rx, rx);
+ // VecDeque::from([ByteStats::new(10)]));
+ assert_eq!(result[0].tx, tx);
+ // VecDeque::from([ByteStats::new(10)]));
}
#[test]
@@ -2434,7 +2551,7 @@ mod tests {
}
for _ in 0..=500 {
- app_data.log_scroll(&ScrollDirection::Next);
+ app_data.log_scroll(&ScrollDirection::Down);
}
let result = app_data.get_logs(
Size {
diff --git a/src/app_error.rs b/src/app_error.rs
index 74d7d3c..ce4b5f6 100644
--- a/src/app_error.rs
+++ b/src/app_error.rs
@@ -2,7 +2,7 @@ use crate::app_data::DockerCommand;
use std::fmt;
/// app errors to set in global state
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum AppError {
DockerCommand(DockerCommand),
DockerExec,
diff --git a/src/config/color_parser.rs b/src/config/color_parser.rs
index 87dacd7..8afded3 100644
--- a/src/config/color_parser.rs
+++ b/src/config/color_parser.rs
@@ -1,5 +1,8 @@
use ratatui::style::Color;
+static COLOR_RX: Color = Color::Rgb(255, 233, 193);
+static COLOR_TX: Color = Color::Rgb(205, 140, 140);
+
/// The macro accepts a list of struct names with key names
/// Returns a struct where every key name is an Option, with the correct derived attributes
macro_rules! optional_config_struct {
@@ -58,7 +61,7 @@ impl From> for AppColors {
);
}
- // Seletable panel borders
+ // Selectable panel borders
if let Some(b) = config_colors.borders {
Self::map_color(b.selected.as_deref(), &mut app_colors.borders.selected);
Self::map_color(b.unselected.as_deref(), &mut app_colors.borders.unselected);
@@ -249,6 +252,8 @@ optional_config_struct!(
ConfigBackgroundText, background, text;
ConfigBackgroundTextHighlight, background, text, text_highlight;
ConfigBorders, selected, unselected;
+ ConfigChartBandwidth, background, border, max_rx, max_tx, title_tx, title_rx, points_rx, points_tx, y_axis;
+
ConfigChartCpu, background, border, order, title, max, points,y_axis;
ConfigChartMemory, background, border, title, max, points, y_axis;
ConfigChartPorts, background, border, title, headings, text;
@@ -265,6 +270,8 @@ config_struct!(
Borders, selected, unselected;
ChartCpu, background, border, title, max, points, y_axis;
ChartMemory, background, border, title, max, points, y_axis;
+ ChartBandwidth, background, border, max_rx, max_tx, title_rx, title_tx, points_rx, points_tx, y_axis;
+
ChartPorts, background, border, title, headings, text;
Commands, background, pause, restart, stop, delete, resume, start;
Containers, background, icon, text, text_rx, text_tx;
@@ -284,6 +291,7 @@ pub struct ConfigColors {
borders: Option,
chart_cpu: Option,
chart_memory: Option,
+ chart_bandwidth: Option,
chart_ports: Option,
commands: Option,
container_state: Option,
@@ -335,7 +343,24 @@ impl Commands {
}
}
-/// Default colours for the help popup
+/// Default colours for the Bandwidth chart
+impl ChartBandwidth {
+ const fn new() -> Self {
+ Self {
+ background: Color::Reset,
+ border: Color::White,
+ max_rx: COLOR_RX,
+ title_rx: COLOR_RX,
+ title_tx: COLOR_TX,
+ max_tx: COLOR_TX,
+ points_rx: COLOR_RX,
+ points_tx: COLOR_TX,
+ y_axis: Color::White,
+ }
+ }
+}
+
+/// Default colours for the CPU chart
impl ChartCpu {
const fn new() -> Self {
Self {
@@ -383,8 +408,8 @@ impl Containers {
background: Color::Reset,
icon: Color::White,
text: Color::Blue,
- text_rx: Color::Rgb(255, 233, 193),
- text_tx: Color::Rgb(205, 140, 140),
+ text_rx: COLOR_RX,
+ text_tx: COLOR_TX,
}
}
}
@@ -487,6 +512,7 @@ pub struct AppColors {
pub borders: Borders,
pub chart_cpu: ChartCpu,
pub chart_memory: ChartMemory,
+ pub chart_bandwidth: ChartBandwidth,
pub chart_ports: ChartPorts,
pub commands: Commands,
pub container_state: ContainerState,
@@ -507,6 +533,7 @@ impl AppColors {
borders: Borders::new(),
chart_cpu: ChartCpu::new(),
chart_memory: ChartMemory::new(),
+ chart_bandwidth: ChartBandwidth::new(),
chart_ports: ChartPorts::new(),
commands: Commands::new(),
container_state: ContainerState::new(),
diff --git a/src/config/config.toml b/src/config/config.toml
index 8d5f1db..f70c116 100644
--- a/src/config/config.toml
+++ b/src/config/config.toml
@@ -24,8 +24,8 @@ show_timestamp = true
# Don't draw gui - for debugging - mostly pointless
gui = true
-# Docker host location
-host = "/var/run/docker.sock"
+# Docker host location. Will take priority over a DOCKER_HOST env.
+# host = "/var/run/docker.sock"
# Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC
timezone = "Etc/UTC"
@@ -95,8 +95,8 @@ scroll_start = ["home"]
# scroll up a list by one item
scroll_up = ["up", "k"]
# Horizontal scroll of the logs
-log_scroll_forward = ["right"]
-log_scroll_back = ["left"]
+scroll_forward = ["right"]
+scroll_back = ["left"]
# Select next panel
select_next_panel = ["tab"]
# Select previous panel
@@ -122,8 +122,8 @@ log_section_height_decrease = ["-"]
log_section_height_increase = ["+"]
# Toggle visibility of the log section
log_section_toggle = ["\\"]
-
-
+# Toggle to inspect container screen
+inspect = ["i"]
# Force a complete clear & redraw of the screen
force_redraw = ["f"]
@@ -254,6 +254,27 @@ points = "cyan"
# The charts y-axis
y_axis = "white"
+# The bandwidth chart
+[colors.chart_bandwidth]
+# Background color of panel
+background = "reset"
+# Border color
+border = "white"
+# Maximum RX value - again paused & stopped colors not yet customizable
+max_rx = "#FFE9C1"
+# Maximum TX value - again paused & stopped colors not yet customizable
+max_tx = "#CD8C8C"
+# RX points on the chart - again paused & stopped colors not yet customizable
+points_rx = "#FFE9C1"
+# TX points on the chart - again paused & stopped colors not yet customizable
+points_tx = "#CD8C8C"
+# TX title color
+title_rx = "#FFE9C1"
+# RX title color
+title_tx = "#CD8C8C"
+# The charts y-axis
+y_axis = "white"
+
# The ports chart
[colors.chart_ports]
# Background color of panel
diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs
index f75ff16..34fde03 100644
--- a/src/config/keymap_parser.rs
+++ b/src/config/keymap_parser.rs
@@ -42,8 +42,9 @@ optional_config_struct!(
exec,
filter_mode,
force_redraw,
- log_scroll_back,
- log_scroll_forward,
+ inspect,
+ scroll_back,
+ scroll_forward,
log_search_mode,
log_section_height_decrease,
log_section_height_increase,
@@ -77,9 +78,10 @@ config_struct!(
delete_deny,
exec,
filter_mode,
+ inspect,
force_redraw,
- log_scroll_back,
- log_scroll_forward,
+ scroll_back,
+ scroll_forward,
log_search_mode,
log_section_height_decrease,
log_section_height_increase,
@@ -113,10 +115,11 @@ impl Keymap {
delete_confirm: (KeyCode::Char('y'), None),
delete_deny: (KeyCode::Char('n'), None),
exec: (KeyCode::Char('e'), None),
+ inspect: (KeyCode::Char('i'), None),
filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))),
force_redraw: (KeyCode::Char('f'), None),
- log_scroll_back: (KeyCode::Left, None),
- log_scroll_forward: (KeyCode::Right, None),
+ scroll_back: (KeyCode::Left, None),
+ scroll_forward: (KeyCode::Right, None),
log_search_mode: (KeyCode::Char('#'), None),
log_section_height_decrease: (KeyCode::Char('-'), None),
log_section_height_increase: (KeyCode::Char('='), None),
@@ -158,20 +161,20 @@ impl From> for Keymap {
|vec_str: Option>,
keymap_field: &mut (KeyCode, Option),
keymap_clash: &mut HashSet| {
- if let Some(vec_str) = vec_str {
- if let Some(vec_keycode) = Self::try_parse_keycode(&vec_str) {
- if let Some(first) = vec_keycode.first() {
- keymap_clash.insert(*first);
- counter += 1;
- keymap_field.0 = *first;
- }
- if let Some(second) = vec_keycode.get(1) {
- keymap_clash.insert(*second);
- counter += 1;
- keymap_field.1 = Some(*second);
- } else {
- keymap_field.1 = None;
- }
+ if let Some(vec_str) = vec_str
+ && let Some(vec_keycode) = Self::try_parse_keycode(&vec_str)
+ {
+ if let Some(first) = vec_keycode.first() {
+ keymap_clash.insert(*first);
+ counter += 1;
+ keymap_field.0 = *first;
+ }
+ if let Some(second) = vec_keycode.get(1) {
+ keymap_clash.insert(*second);
+ counter += 1;
+ keymap_field.1 = Some(*second);
+ } else {
+ keymap_field.1 = None;
}
}
};
@@ -206,12 +209,8 @@ impl From> for Keymap {
update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash);
update_keymap(ck.scroll_up, &mut keymap.scroll_up, &mut clash);
update_keymap(ck.log_search_mode, &mut keymap.log_search_mode, &mut clash);
- update_keymap(
- ck.log_scroll_forward,
- &mut keymap.log_scroll_forward,
- &mut clash,
- );
- update_keymap(ck.log_scroll_back, &mut keymap.log_scroll_back, &mut clash);
+ update_keymap(ck.scroll_forward, &mut keymap.scroll_forward, &mut clash);
+ update_keymap(ck.scroll_back, &mut keymap.scroll_back, &mut clash);
update_keymap(
ck.select_next_panel,
&mut keymap.select_next_panel,
@@ -276,16 +275,16 @@ impl Keymap {
for key in input.iter().take(2) {
if key.chars().count() == 1 {
- if let Some(first_char) = key.chars().next() {
- if let Some(first_char) = match first_char {
+ if let Some(first_char) = key.chars().next()
+ && let Some(first_char) = match first_char {
x if x.is_ascii_alphabetic() || x.is_ascii_digit() => Some(first_char),
'/' | '\\' | ',' | '.' | '#' | '\'' | '[' | ']' | ';' | '=' | '-' => {
Some(first_char)
}
_ => None,
- } {
- output.push(KeyCode::Char(first_char));
}
+ {
+ output.push(KeyCode::Char(first_char));
}
} else {
let keycode = match key.to_lowercase().as_str() {
@@ -327,7 +326,7 @@ impl Keymap {
if output.is_empty() {
None
} else {
- // Remove any duplicates for a single deinition
+ // Remove any duplicates for a single definition
if output.first() == output.get(1) {
output.pop();
}
@@ -395,9 +394,10 @@ mod tests {
exec: None,
filter_mode: None,
force_redraw: None,
- log_scroll_back: None,
+ inspect: None,
+ scroll_back: None,
log_search_mode: None,
- log_scroll_forward: None,
+ scroll_forward: None,
log_section_height_decrease: None,
log_section_height_increase: None,
log_section_toggle: None,
@@ -441,8 +441,9 @@ mod tests {
exec: gen_v(("g", "h")),
filter_mode: gen_v(("i", "j")),
force_redraw: gen_v(("k", "l")),
- log_scroll_back: gen_v(("s", "t")),
- log_scroll_forward: gen_v(("q", "r")),
+ inspect: gen_v(("m", "n")),
+ scroll_back: gen_v(("s", "t")),
+ scroll_forward: gen_v(("q", "r")),
log_search_mode: gen_v(("1", "2")),
log_section_height_decrease: gen_v(("m", "n")),
log_section_height_increase: gen_v(("o", "p")),
@@ -479,8 +480,9 @@ mod tests {
exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
- log_scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))),
- log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))),
+ inspect: (KeyCode::Char('i'), None),
+ scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))),
+ scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))),
log_search_mode: (KeyCode::Char('1'), Some(KeyCode::Char('2'))),
log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))),
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 699768e..db79281 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -13,9 +13,6 @@ pub use {color_parser::AppColors, keymap_parser::Keymap};
mod parse_args;
mod parse_config_file;
-// TODO use a global pub static oncelock for the config
-// static CELL: OnceLock = OnceLock::new();
-
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct Config {
@@ -28,7 +25,8 @@ pub struct Config {
pub keymap: Keymap,
pub log_search_case_sensitive: bool,
pub raw_logs: bool,
- pub save_dir: Option,
+ pub dir_config: Option,
+ pub dir_save: Option,
pub show_logs: bool,
pub show_self: bool,
pub show_std_err: bool,
@@ -50,7 +48,8 @@ impl From<&Args> for Config {
keymap: Keymap::new(),
log_search_case_sensitive: true,
raw_logs: args.raw,
- save_dir: Self::try_get_logs_dir(args.save_dir.as_ref()),
+ dir_save: Self::try_get_logs_dir(args.save_dir.as_ref()),
+ dir_config: args.config_file.as_ref().map(|i| PathBuf::from(&i)),
show_logs: true,
show_self: !args.show_self,
show_std_err: !args.no_std_err,
@@ -62,19 +61,20 @@ impl From<&Args> for Config {
}
}
-impl From for Config {
- fn from(config_file: ConfigFile) -> Self {
+impl From<(ConfigFile, Option)> for Config {
+ fn from((config_file, dir): (ConfigFile, Option)) -> Self {
Self {
app_colors: AppColors::from(config_file.colors),
color_logs: config_file.color_logs.unwrap_or(false),
docker_interval_ms: config_file.docker_interval.unwrap_or(1000),
+ dir_config: dir,
gui: config_file.gui.unwrap_or(true),
host: config_file.host,
in_container: Self::check_if_in_container(),
keymap: Keymap::from(config_file.keymap),
log_search_case_sensitive: config_file.log_search_case_sensitive.unwrap_or(true),
raw_logs: config_file.raw_logs.unwrap_or(false),
- save_dir: Self::try_get_logs_dir(config_file.save_dir.as_ref()),
+ dir_save: Self::try_get_logs_dir(config_file.save_dir.as_ref()),
show_logs: config_file.show_logs.unwrap_or(true),
show_self: config_file.show_self.unwrap_or(false),
show_std_err: config_file.show_std_err.unwrap_or(true),
@@ -185,8 +185,8 @@ impl Config {
self.host = Some(host);
}
- if let Some(x) = config_from_cli.save_dir {
- self.save_dir = Some(x);
+ if let Some(x) = config_from_cli.dir_save {
+ self.dir_save = Some(x);
}
if let Some(tz) = config_from_cli.timezone {
@@ -211,16 +211,16 @@ impl Config {
let args = Args::parse();
let config_from_cli = Self::from(&args);
- if let Some(config_file) = &args.config_file {
- if let Some(config_file) =
- parse_config_file::ConfigFile::try_parse_from_file(config_file)
- {
- return Self::from(config_file).merge_args(config_from_cli);
- }
+ if let Some(dir_config_file) = &args.config_file
+ && let Some(config_file) =
+ parse_config_file::ConfigFile::try_parse_from_file(dir_config_file)
+ {
+ return Self::from((config_file, Some(PathBuf::from(dir_config_file))))
+ .merge_args(config_from_cli);
}
- if let Some(config_file) = parse_config_file::ConfigFile::try_parse(in_container) {
- return Self::from(config_file).merge_args(config_from_cli);
+ if let Some((config_file, dir)) = parse_config_file::ConfigFile::try_parse(in_container) {
+ return Self::from((config_file, Some(dir))).merge_args(config_from_cli);
}
config_from_cli
}
diff --git a/src/config/parse_config_file.rs b/src/config/parse_config_file.rs
index d61991a..e685d57 100644
--- a/src/config/parse_config_file.rs
+++ b/src/config/parse_config_file.rs
@@ -81,7 +81,7 @@ pub struct ConfigFile {
impl ConfigFile {
/// Attempt to create a config.toml file, will attempt to recursively create the directories as well
- fn crate_config_file(in_container: bool) -> Result<(), AppError> {
+ fn create_config_file(in_container: bool) -> Result<(), AppError> {
if in_container {
return Ok(());
}
@@ -146,28 +146,26 @@ impl ConfigFile {
/// Parse a config file using default config_file location
/// This is executed first, then the CLI args are read, and if they contain a "--config-file" entry, then Self::try_parse_from_file() is executed
- pub fn try_parse(in_container: bool) -> Option {
- let mut config = None;
+ pub fn try_parse(in_container: bool) -> Option<(Self, PathBuf)> {
+ let mut output = None;
for file_format in [
ConfigFileFormat::Toml,
ConfigFileFormat::Jsonc,
ConfigFileFormat::JsoncAsJson,
ConfigFileFormat::Json,
] {
- if let Ok(config_file) = Self::parse_config_file(
- file_format,
- &file_format.get_default_path_name(in_container),
- ) {
- config = Some(config_file);
+ let path = file_format.get_default_path_name(in_container);
+ if let Ok(config_file) = Self::parse_config_file(file_format, &path) {
+ output = Some((config_file, path));
break;
}
}
- if config.is_none() {
- Self::crate_config_file(in_container).ok();
+ if output.is_none() {
+ Self::create_config_file(in_container).ok();
}
- config
+ output
}
}
diff --git a/src/docker_data/message.rs b/src/docker_data/message.rs
index b0af01a..6030e29 100644
--- a/src/docker_data/message.rs
+++ b/src/docker_data/message.rs
@@ -9,5 +9,6 @@ pub enum DockerMessage {
ConfirmDelete(ContainerId),
Control((DockerCommand, ContainerId)),
Exec(Sender>),
+ Inspect(ContainerId),
Update,
}
diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs
index 52ecc24..038dc6b 100644
--- a/src/docker_data/mod.rs
+++ b/src/docker_data/mod.rs
@@ -1,8 +1,8 @@
use bollard::{
Docker,
query_parameters::{
- ListContainersOptions, LogsOptions, RemoveContainerOptions, RestartContainerOptions,
- StartContainerOptions, StatsOptions, StopContainerOptions,
+ InspectContainerOptions, ListContainersOptions, LogsOptions, RemoveContainerOptions,
+ RestartContainerOptions, StartContainerOptions, StatsOptions, StopContainerOptions,
},
secret::ContainerStatsResponse,
service::ContainerSummary,
@@ -10,13 +10,10 @@ use bollard::{
use futures_util::StreamExt;
use parking_lot::Mutex;
use std::{
- collections::HashMap,
+ collections::HashSet,
sync::{Arc, atomic::AtomicUsize},
};
-use tokio::{
- sync::mpsc::{Receiver, Sender},
- task::JoinHandle,
-};
+use tokio::sync::mpsc::{Receiver, Sender};
use uuid::Uuid;
use crate::{
@@ -70,7 +67,7 @@ pub struct DockerData {
docker: Arc,
gui_state: Arc>,
receiver: Receiver,
- spawns: Arc>>>,
+ spawns: Arc>>,
}
impl DockerData {
@@ -132,7 +129,7 @@ impl DockerData {
docker: Arc,
state: State,
spawn_id: SpawnId,
- spawns: Arc>>>,
+ spawns: Arc>>,
) {
let id = spawn_id.get_id();
let mut stream = docker
@@ -200,16 +197,13 @@ impl DockerData {
for (state, id) in all_ids {
let spawn_id = SpawnId::Stats((id, self.binate));
- if let std::collections::hash_map::Entry::Vacant(spawns) =
- self.spawns.lock().entry(spawn_id.clone())
- {
- spawns.insert(tokio::spawn(Self::update_container_stat(
- Arc::clone(&self.app_data),
- Arc::clone(&self.docker),
- state,
- spawn_id,
- Arc::clone(&self.spawns),
- )));
+ if !self.spawns.lock().contains(&spawn_id) {
+ let app_data = Arc::clone(&self.app_data);
+ let docker = Arc::clone(&self.docker);
+ let spawns = Arc::clone(&self.spawns);
+ tokio::spawn(Self::update_container_stat(
+ app_data, docker, state, spawn_id, spawns,
+ ));
}
}
self.binate = self.binate.toggle();
@@ -256,7 +250,7 @@ impl DockerData {
docker: Arc,
id: ContainerId,
since: u64,
- spawns: Arc>>>,
+ spawns: Arc>>,
stderr: bool,
) {
let options = Some(LogsOptions {
@@ -290,13 +284,13 @@ impl DockerData {
let spawns = Arc::clone(&self.spawns);
let std_err = self.config.show_std_err;
let init = Arc::clone(&init);
- self.spawns.lock().insert(
- SpawnId::Log(id.clone()),
- tokio::spawn(async move {
- Self::update_log(app_data, docker, id, 0, spawns, std_err).await;
- init.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
- }),
- );
+
+ self.spawns.lock().insert(SpawnId::Log(id.clone()));
+
+ tokio::spawn(async move {
+ Self::update_log(app_data, docker, id, 0, spawns, std_err).await;
+ init.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
+ });
}
init
}
@@ -327,17 +321,16 @@ impl DockerData {
let last_updated = container.last_updated;
let spawn_id = SpawnId::Log(container.id.clone());
// Only spawn if not already spawned with a given id/binate pair
- if let std::collections::hash_map::Entry::Vacant(spawns) =
- self.spawns.lock().entry(spawn_id)
- {
- spawns.insert(tokio::spawn(Self::update_log(
+ if !self.spawns.lock().contains(&spawn_id) {
+ self.spawns.lock().insert(spawn_id.clone());
+ tokio::spawn(Self::update_log(
Arc::clone(&self.app_data),
Arc::clone(&self.docker),
container.id.clone(),
last_updated,
Arc::clone(&self.spawns),
self.config.show_std_err,
- )));
+ ));
}
}
self.update_all_container_stats();
@@ -420,6 +413,18 @@ impl DockerData {
docker_tx.send(Arc::clone(&self.docker)).ok();
}
DockerMessage::Update => self.update_everything().await,
+ DockerMessage::Inspect(id) => {
+ let t = self
+ .docker
+ .inspect_container(id.get(), Some(InspectContainerOptions { size: true }))
+ .await;
+ if let Ok(t) = t {
+ self.app_data.lock().set_inspect_data(t);
+ self.gui_state.lock().status_push(Status::Inspect);
+ } else {
+ // Set error here, can't inspect container
+ }
+ }
}
}
}
@@ -457,7 +462,7 @@ impl DockerData {
docker: Arc::new(docker),
gui_state,
receiver: docker_rx,
- spawns: Arc::new(Mutex::new(HashMap::new())),
+ spawns: Arc::new(Mutex::new(HashSet::new())),
};
inner.initialise_container_data().await;
Self::heartbeat(&inner.config, docker_tx);
@@ -478,6 +483,7 @@ mod tests {
fn gen_stats() -> ContainerStatsResponse {
ContainerStatsResponse {
read: None,
+ os_type: None,
preread: None,
num_procs: Some(1),
pids_stats: None,
diff --git a/src/exec.rs b/src/exec.rs
index a1604b4..bc4444e 100644
--- a/src/exec.rs
+++ b/src/exec.rs
@@ -1,5 +1,5 @@
use std::{
- io::{Read, Stdout, Write},
+ io::{Read, Write},
sync::{Arc, atomic::AtomicBool, mpsc::Sender},
};
@@ -10,7 +10,7 @@ use bollard::{
use crossterm::terminal::enable_raw_mode;
use futures_util::StreamExt;
use parking_lot::Mutex;
-use ratatui::{Terminal, backend::CrosstermBackend};
+use ratatui::layout::Size;
use tokio::{
fs::File,
io::{AsyncReadExt, AsyncWriteExt},
@@ -123,23 +123,29 @@ impl AsyncTTY {
}
}
-/// This is used to set the terminal size when exec via the Internal method
-#[derive(Debug, Clone)]
-pub struct TerminalSize {
- width: u16,
- height: u16,
-}
+// impl TryFrom<&Terminal>> for HWU16 {
+// type Error = None;
+// fn try_from(terminal: &Terminal>) -> Option {
+// terminal.size().map_or(None, |i| {
+// Some(Self {
+// width: i.width,
+// height: i.height,
+// })
+// })
+// }
-impl TerminalSize {
- pub fn new(terminal: &Terminal>) -> Option {
- terminal.size().map_or(None, |i| {
- Some(Self {
- width: i.width,
- height: i.height,
- })
- })
- }
-}
+// }
+
+// impl TerminalSize {
+// pub fn new(terminal: &Terminal>) -> Option {
+// terminal.size().map_or(None, |i| {
+// Some(Self {
+// width: i.width,
+// height: i.height,
+// })
+// })
+// }
+// }
#[derive(Debug, Clone)]
pub enum ExecMode {
@@ -161,51 +167,41 @@ impl ExecMode {
let use_cli = app_data.lock().config.use_cli;
let container = app_data.lock().get_selected_container_id_state_name();
- if let Some((id, state, _)) = container {
- if [
+ if let Some((id, state, _)) = container
+ && [
State::Running(RunningState::Healthy),
State::Running(RunningState::Unhealthy),
]
.contains(&state)
+ {
+ if tty_readable()
+ && !use_cli
+ && let Ok(exec) = docker
+ .create_exec(
+ id.get(),
+ CreateExecOptions {
+ attach_stdout: Some(true),
+ attach_stderr: Some(true),
+ cmd: Some(vec![command::PWD]),
+ ..Default::default()
+ },
+ )
+ .await
+ && let Ok(StartExecResults::Attached { mut output, .. }) =
+ docker.start_exec(&exec.id, None).await
+ && let Some(Ok(msg)) = output.next().await
+ && !msg.to_string().starts_with(OCI_ERROR)
{
- if tty_readable() && !use_cli {
- if let Ok(exec) = docker
- .create_exec(
- id.get(),
- CreateExecOptions {
- attach_stdout: Some(true),
- attach_stderr: Some(true),
- cmd: Some(vec![command::PWD]),
- ..Default::default()
- },
- )
- .await
- {
- if let Ok(StartExecResults::Attached { mut output, .. }) =
- docker.start_exec(&exec.id, None).await
- {
- if let Some(Ok(msg)) = output.next().await {
- if !msg.to_string().starts_with(OCI_ERROR) {
- return Some(Self::Internal((
- Arc::new(id),
- Arc::clone(docker),
- )));
- }
- }
- }
- }
- }
+ return Some(Self::Internal((Arc::new(id), Arc::clone(docker))));
+ }
- if let Ok(output) = std::process::Command::new(command::DOCKER)
- .args([command::EXEC, id.get(), command::PWD])
- .output()
- {
- if let Ok(output) = String::from_utf8(output.stdout) {
- if !output.starts_with(OCI_ERROR) {
- return Some(Self::External(Arc::new(id)));
- }
- }
- }
+ if let Ok(output) = std::process::Command::new(command::DOCKER)
+ .args([command::EXEC, id.get(), command::PWD])
+ .output()
+ && let Ok(output) = String::from_utf8(output.stdout)
+ && !output.starts_with(OCI_ERROR)
+ {
+ return Some(Self::External(Arc::new(id)));
}
}
None
@@ -235,7 +231,7 @@ impl ExecMode {
&self,
id: &ContainerId,
docker: &Arc,
- terminal_size: Option,
+ terminal_size: Option,
) -> Result<(), AppError> {
let cancel_token = CancellationToken::new();
@@ -351,7 +347,7 @@ impl ExecMode {
}
}
- pub async fn run(&self, tty_size: Option) -> Result<(), AppError> {
+ pub async fn run(&self, tty_size: Option) -> Result<(), AppError> {
match self {
Self::External(id) => {
Self::exec_external(id);
diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs
index 000577b..4dd5ae3 100644
--- a/src/input_handler/mod.rs
+++ b/src/input_handler/mod.rs
@@ -119,6 +119,14 @@ impl InputHandler {
self.gui_state.lock().set_delete_container(None);
}
+ async fn inspect_key(&self) {
+ self.app_data.lock().clear_inspect_data();
+ let selected = self.app_data.lock().get_selected_container().cloned();
+ if let Some(g) = selected {
+ self.docker_tx.send(DockerMessage::Inspect(g.id)).await.ok();
+ }
+ }
+
/// Validate that one can exec into a Docker container
async fn exec_key(&self) {
let is_oxker = self.app_data.lock().is_oxker();
@@ -178,58 +186,58 @@ impl InputHandler {
async fn save_logs(&self) -> Result<(), Box> {
let args = self.app_data.lock().config.clone();
let container = self.app_data.lock().get_selected_container_id_state_name();
- if let Some((id, _, name)) = container {
- if let Some(log_path) = args.save_dir {
- let (sx, rx) = tokio::sync::oneshot::channel();
- self.docker_tx.send(DockerMessage::Exec(sx)).await?;
+ if let Some((id, _, name)) = container
+ && let Some(log_path) = args.dir_save
+ {
+ let (sx, rx) = tokio::sync::oneshot::channel();
+ self.docker_tx.send(DockerMessage::Exec(sx)).await?;
- let now = SystemTime::now()
- .duration_since(SystemTime::UNIX_EPOCH)
- .map_or(0, |i| i.as_secs());
+ let now = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .map_or(0, |i| i.as_secs());
- let path = log_path.join(format!("{name}_{now}.log"));
+ let path = log_path.join(format!("{name}_{now}.log"));
- let options = Some(LogsOptions {
- stderr: true,
- stdout: true,
- timestamps: args.show_timestamp,
- since: 0,
- ..Default::default()
- });
- let mut logs = rx.await?.logs(id.get(), options);
- let mut output = vec![];
+ let options = Some(LogsOptions {
+ stderr: true,
+ stdout: true,
+ timestamps: args.show_timestamp,
+ since: 0,
+ ..Default::default()
+ });
+ let mut logs = rx.await?.logs(id.get(), options);
+ let mut output = vec![];
- while let Some(Ok(value)) = logs.next().await {
- let data = value.to_string();
- if !data.trim().is_empty() {
- output.push(
- categorise_text(&data)
- .into_iter()
- .map(|i| i.text)
- .collect::(),
- );
- }
- }
- if !output.is_empty() {
- let mut stream = BufWriter::new(
- OpenOptions::new()
- .read(true)
- .write(true)
- .create(true)
- .truncate(true)
- .open(&path)?,
+ while let Some(Ok(value)) = logs.next().await {
+ let data = value.to_string();
+ if !data.trim().is_empty() {
+ output.push(
+ categorise_text(&data)
+ .into_iter()
+ .map(|i| i.text)
+ .collect::(),
);
-
- for line in &output {
- stream.write_all(line.as_bytes())?;
- }
- stream.flush()?;
-
- self.gui_state
- .lock()
- .set_info_box(&format!("saved to {}", path.display()));
}
}
+ if !output.is_empty() {
+ let mut stream = BufWriter::new(
+ OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(&path)?,
+ );
+
+ for line in &output {
+ stream.write_all(line.as_bytes())?;
+ }
+ stream.flush()?;
+
+ self.gui_state
+ .lock()
+ .set_info_box(&format!("saved to {}", path.display()));
+ }
}
Ok(())
}
@@ -296,6 +304,18 @@ impl InputHandler {
}
}
+ fn inspect_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
+ for _ in 0..self.get_modifier_total(modifier) {
+ self.gui_state.lock().set_inspect_offset(sd);
+ }
+ }
+
+ // fn inspect_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
+ // for _ in 0..self.get_modifier_total(modifier) {
+ // self.gui_state.lock().set_inspect_offset(sd);
+ // }
+ // }
+
fn logs_horizontal_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
let panel = self.gui_state.lock().get_selected_panel();
if panel == SelectablePanel::Logs {
@@ -393,22 +413,22 @@ impl InputHandler {
self.gui_state.lock().status_del(Status::SearchLogs);
}
- _ if self.keymap.log_scroll_back.0 == key_code
- || self.keymap.log_scroll_back.1 == Some(key_code) =>
+ _ if self.keymap.scroll_back.0 == key_code
+ || self.keymap.scroll_back.1 == Some(key_code) =>
{
- self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous);
+ self.logs_horizontal_scroll(modifier, &ScrollDirection::Up);
}
- _ if self.keymap.log_scroll_forward.0 == key_code
- || self.keymap.log_scroll_forward.1 == Some(key_code) =>
+ _ if self.keymap.scroll_forward.0 == key_code
+ || self.keymap.scroll_forward.1 == Some(key_code) =>
{
- self.logs_horizontal_scroll(modifier, &ScrollDirection::Next);
+ self.logs_horizontal_scroll(modifier, &ScrollDirection::Down);
}
_ if self.keymap.scroll_down.0 == key_code => {
self.app_data
.lock()
- .log_search_scroll(&ScrollDirection::Next);
+ .log_search_scroll(&ScrollDirection::Down);
// TODO should only do this is log_search_scroll returns some
// Need to wait til app_data and gui_data is combined
self.gui_state
@@ -418,9 +438,7 @@ impl InputHandler {
}
_ if self.keymap.scroll_up.0 == key_code => {
- self.app_data
- .lock()
- .log_search_scroll(&ScrollDirection::Previous);
+ self.app_data.lock().log_search_scroll(&ScrollDirection::Up);
// TODO should only do this is log_search_scroll returns some
// Need to wait til app_data and gui_data is combined
self.gui_state
@@ -439,6 +457,62 @@ impl InputHandler {
}
}
+ /// Actions to take when Filter status active
+ fn handle_inspect(&mut self, key_code: KeyCode, modifier: KeyModifiers) {
+ match key_code {
+ _ if self.keymap.inspect.0 == key_code
+ || self.keymap.inspect.1 == Some(key_code)
+ || self.keymap.clear.0 == key_code
+ || self.keymap.clear.1 == Some(key_code) =>
+ {
+ self.app_data.lock().clear_inspect_data();
+ self.gui_state.lock().clear_inspect_offset();
+ self.gui_state.lock().status_del(Status::Inspect);
+ }
+
+ _ if self.keymap.scroll_down.0 == key_code
+ || self.keymap.scroll_down.1 == Some(key_code) =>
+ {
+ self.inspect_scroll(modifier, &ScrollDirection::Down);
+ }
+
+ _ if self.keymap.scroll_up.0 == key_code
+ || self.keymap.scroll_up.1 == Some(key_code) =>
+ {
+ self.inspect_scroll(modifier, &ScrollDirection::Up);
+ }
+
+ _ if self.keymap.scroll_forward.0 == key_code
+ || self.keymap.scroll_forward.1 == Some(key_code) =>
+ {
+ self.inspect_scroll(modifier, &ScrollDirection::Right);
+ }
+
+ _ if self.keymap.scroll_back.0 == key_code
+ || self.keymap.scroll_back.1 == Some(key_code) =>
+ {
+ self.inspect_scroll(modifier, &ScrollDirection::Left);
+ }
+
+ _ if self.keymap.toggle_mouse_capture.0 == key_code
+ || self.keymap.toggle_mouse_capture.1 == Some(key_code) =>
+ {
+ self.mouse_capture_key();
+ }
+ _ if self.keymap.scroll_start.0 == key_code
+ || self.keymap.scroll_start.1 == Some(key_code) =>
+ {
+ self.gui_state.lock().clear_inspect_offset();
+ }
+ _ if self.keymap.scroll_end.0 == key_code
+ || self.keymap.scroll_end.1 == Some(key_code) =>
+ {
+ self.gui_state.lock().set_inspect_offset_y_to_max();
+ }
+ _ => (),
+ }
+ }
+
/// Actions to take when Filter status active
fn handle_filter(&self, key_code: KeyCode) {
match key_code {
@@ -596,6 +670,10 @@ impl InputHandler {
self.save_key().await;
}
+ _ if self.keymap.inspect.0 == key_code || self.keymap.inspect.1 == Some(key_code) => {
+ self.inspect_key().await;
+ }
+
_ if self.keymap.select_next_panel.0 == key_code
|| self.keymap.select_next_panel.1 == Some(key_code) =>
{
@@ -623,13 +701,13 @@ impl InputHandler {
_ if self.keymap.scroll_up.0 == key_code
|| self.keymap.scroll_up.1 == Some(key_code) =>
{
- self.scroll(modifier, &ScrollDirection::Previous);
+ self.scroll(modifier, &ScrollDirection::Up);
}
_ if self.keymap.scroll_down.0 == key_code
|| self.keymap.scroll_down.1 == Some(key_code) =>
{
- self.scroll(modifier, &ScrollDirection::Next);
+ self.scroll(modifier, &ScrollDirection::Down);
}
_ if self.keymap.filter_mode.0 == key_code
@@ -648,18 +726,17 @@ impl InputHandler {
self.gui_state.lock().status_push(Status::SearchLogs);
}
- _ if self.keymap.log_scroll_back.0 == key_code
- || self.keymap.log_scroll_back.1 == Some(key_code) =>
+ _ if self.keymap.scroll_back.0 == key_code
+ || self.keymap.scroll_back.1 == Some(key_code) =>
{
- self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous);
+ self.logs_horizontal_scroll(modifier, &ScrollDirection::Up);
// self.logs_back(modifier);
}
- _ if self.keymap.log_scroll_forward.0 == key_code
- || self.keymap.log_scroll_forward.1 == Some(key_code) =>
+ _ if self.keymap.scroll_forward.0 == key_code
+ || self.keymap.scroll_forward.1 == Some(key_code) =>
{
- self.logs_horizontal_scroll(modifier, &ScrollDirection::Next);
- // self.logs_forward(modifier);
+ self.logs_horizontal_scroll(modifier, &ScrollDirection::Down);
}
KeyCode::Enter => self.enter_key().await,
@@ -678,6 +755,7 @@ impl InputHandler {
let contains_filter = contains(Status::Filter);
let contains_delete = contains(Status::DeleteConfirm);
let contains_search_logs = contains(Status::SearchLogs);
+ let contains_inspect = contains(Status::Inspect);
if !contains_exec {
let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1;
@@ -698,6 +776,8 @@ impl InputHandler {
self.handle_search_logs(key_code, key_modifier);
} else if contains_delete {
self.handle_delete(key_code).await;
+ } else if contains_inspect {
+ self.handle_inspect(key_code, key_modifier);
} else {
self.handle_others(key_code, key_modifier).await;
}
@@ -726,7 +806,18 @@ impl InputHandler {
/// Handle mouse button events
fn mouse_press(&self, mouse_event: MouseEvent, modifier: KeyModifiers) {
let status = self.gui_state.lock().get_status();
- if status.contains(&Status::Help) {
+
+ if status.contains(&Status::Inspect) {
+ match mouse_event.kind {
+ MouseEventKind::ScrollDown => self.inspect_scroll(modifier, &ScrollDirection::Down),
+ MouseEventKind::ScrollUp => self.inspect_scroll(modifier, &ScrollDirection::Up),
+ MouseEventKind::ScrollRight => {
+ self.inspect_scroll(modifier, &ScrollDirection::Right)
+ }
+ MouseEventKind::ScrollLeft => self.inspect_scroll(modifier, &ScrollDirection::Left),
+ _ => (),
+ }
+ } else if status.contains(&Status::Help) {
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point);
if help_intersect {
@@ -734,8 +825,9 @@ impl InputHandler {
}
} else {
match mouse_event.kind {
- MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Previous),
- MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Next),
+ MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Up),
+ MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Down),
+ // TODO left and right for log offsets
MouseEventKind::Down(MouseButton::Left) => {
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
let header = self.gui_state.lock().get_intersect_header(mouse_point);
diff --git a/src/main.rs b/src/main.rs
index 30ea787..8ad73f5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,3 @@
-#![allow(clippy::collapsible_if)]
// #![allow(unused)]
// Zigbuild is stuck on 1.87.0, which means Mac builds won't work when using collapsible ifs
@@ -43,12 +42,18 @@ fn setup_tracing() {
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
}
-/// Read the optional docker_host path, the cli args take priority over the DOCKER_HOST env
+/// Read the optional docker_host path
+/// Bollard will use DOCKER_HOST env, so might be pointless here, although it will fix it's priority over any config setting
fn read_docker_host(config: &Config) -> Option {
- config
- .host
- .as_ref()
- .map_or_else(|| std::env::var(DOCKER_HOST).ok(), |x| Some(x.to_string()))
+ if let Some(x) = &config.host {
+ Some(x.to_string())
+ } else if let Ok(env) = std::env::var(DOCKER_HOST)
+ && !env.trim().is_empty()
+ {
+ Some(env)
+ } else {
+ None
+ }
}
/// Create docker daemon handler, and only spawn up the docker data handler if a ping returns non-error
@@ -60,25 +65,27 @@ async fn docker_init(
) {
let host = read_docker_host(&app_data.lock().config);
- let connection = host.map_or_else(Docker::connect_with_socket_defaults, |host| {
- Docker::connect_with_socket(&host, 120, API_DEFAULT_VERSION)
- });
-
- if let Ok(docker) = connection {
- if docker.ping().await.is_ok() {
- tokio::spawn(DockerData::start(
- Arc::clone(app_data),
- docker,
- docker_rx,
- docker_tx,
- Arc::clone(gui_state),
- ));
- return;
- }
+ if let Ok(docker) = host
+ .as_ref()
+ .map_or_else(Docker::connect_with_defaults, |host| {
+ Docker::connect_with_socket(host, 120, API_DEFAULT_VERSION)
+ })
+ && docker.ping().await.is_ok()
+ {
+ tokio::spawn(DockerData::start(
+ Arc::clone(app_data),
+ docker,
+ docker_rx,
+ docker_tx,
+ Arc::clone(gui_state),
+ ));
+ } else {
+ app_data.lock().set_error(
+ AppError::DockerConnect,
+ gui_state,
+ Status::DockerConnect(host),
+ );
}
- app_data
- .lock()
- .set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
}
/// Create data for, and then spawn a tokio thread, for the input handler
@@ -155,7 +162,7 @@ mod tests {
use std::{str::FromStr, sync::Arc};
- use bollard::service::{ContainerSummary, Port};
+ use bollard::service::{ContainerSummary, PortSummary};
use crate::{
app_data::{
@@ -169,23 +176,24 @@ mod tests {
/// Default test config, has timestamps turned off
pub fn gen_config() -> Config {
Config {
+ app_colors: AppColors::new(),
color_logs: false,
+ dir_save: None,
+ dir_config: None,
docker_interval_ms: 1000,
gui: true,
host: None,
- show_std_err: false,
in_container: false,
- save_dir: None,
+ keymap: Keymap::new(),
log_search_case_sensitive: true,
raw_logs: false,
- show_self: false,
- app_colors: AppColors::new(),
- keymap: Keymap::new(),
- timestamp_format: "HH:MM:SS.NNNNN dd-mm-yyyy".to_owned(),
- show_timestamp: false,
- use_cli: false,
show_logs: true,
+ show_self: false,
+ show_std_err: false,
+ show_timestamp: false,
+ timestamp_format: "HH:MM:SS.NNNNN dd-mm-yyyy".to_owned(),
timezone: None,
+ use_cli: false,
}
}
@@ -211,6 +219,7 @@ mod tests {
containers: StatefulList::new(containers.to_vec()),
hidden_containers: vec![],
current_sorted_id: vec![],
+ inspect_data: None,
error: None,
sorted_by: None,
rerender: Arc::new(Rerender::new()),
@@ -234,13 +243,14 @@ mod tests {
pub fn gen_container_summary(index: usize, state: &str) -> ContainerSummary {
ContainerSummary {
image_manifest_descriptor: None,
+ health: None,
id: Some(format!("{index}")),
names: Some(vec![format!("container_{}", index)]),
image: Some(format!("image_{index}")),
image_id: Some(format!("{index}")),
command: None,
created: Some(i64::try_from(index).unwrap()),
- ports: Some(vec![Port {
+ ports: Some(vec![PortSummary {
ip: None,
private_port: u16::try_from(index).unwrap_or(1) + 8000,
public_port: None,
diff --git a/src/ui/draw_blocks/chart_bandwidth.rs b/src/ui/draw_blocks/chart_bandwidth.rs
new file mode 100644
index 0000000..79ffd48
--- /dev/null
+++ b/src/ui/draw_blocks/chart_bandwidth.rs
@@ -0,0 +1,700 @@
+use std::fmt::Display;
+
+use ratatui::{
+ Frame,
+ layout::{Alignment, Rect},
+ style::{Color, Modifier, Style, Stylize},
+ symbols::{self, Marker},
+ text::{Line, Span},
+ widgets::{Axis, Block, BorderType, Borders, Chart, Dataset, GraphType},
+};
+
+use super::FrameData;
+use crate::{
+ app_data::{State, Stats},
+ config::AppColors,
+};
+
+fn make_chart<'a, T: Stats + Display>(
+ state: State,
+ colors: AppColors,
+ dataset: Vec>,
+ current_rx: &'a T,
+ max_rx: &'a T,
+ current_tx: &'a T,
+ max_tx: &'a T,
+) -> Chart<'a> {
+ let gen_color = |state: &State, default: Color| {
+ if state.is_healthy() {
+ default
+ } else {
+ state.get_color(colors)
+ }
+ };
+
+ let mut labels = [
+ Span::raw(""),
+ Span::styled(
+ format!("{max_rx}"),
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(gen_color(&state, colors.chart_bandwidth.max_rx)),
+ ),
+ Span::styled(
+ format!("{max_tx}"),
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(gen_color(&state, colors.chart_bandwidth.max_tx)),
+ ),
+ Span::raw(""),
+ ];
+
+ // Set the order of rx/tx on the y axis, based on which is the highest value
+ if max_rx.get_value() > max_tx.get_value() {
+ labels.reverse();
+ }
+
+ Chart::new(dataset)
+ .bg(colors.chart_bandwidth.background)
+ .block(
+ Block::default()
+ .title_alignment(Alignment::Center)
+ .title(Line::from(vec![
+ Span::styled(
+ format!(" rx: {current_rx}"),
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(gen_color(&state, colors.chart_bandwidth.title_rx)),
+ ),
+ Span::styled(
+ format!(" tx: {current_tx} "),
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(gen_color(&state, colors.chart_bandwidth.title_tx)),
+ ),
+ ]))
+ .borders(Borders::ALL)
+ .border_type(BorderType::Rounded)
+ .border_style(Style::default().fg(colors.chart_bandwidth.border)),
+ )
+ .x_axis(Axis::default().bounds([0.0, 60.0]))
+ .y_axis(
+ Axis::default()
+ .labels(labels)
+ .style(Style::default().fg(colors.chart_bandwidth.y_axis))
+ .bounds([0.0, (max_rx.get_value()).max(max_tx.get_value()) + 0.01]),
+ )
+}
+
+/// Draw bandwidth chart
+pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
+ if let Some(x) = fd.chart_data.as_ref() {
+ let mut dataset = vec![
+ Dataset::default()
+ .marker(symbols::Marker::Dot)
+ .style(Style::default().fg(colors.chart_bandwidth.points_tx))
+ .graph_type(GraphType::Line)
+ .marker(Marker::Dot)
+ .style(Style::default().fg(colors.chart_bandwidth.points_tx))
+ .data(&x.tx.dataset),
+ ];
+ dataset.extend(vec![
+ Dataset::default()
+ .marker(symbols::Marker::Dot)
+ .style(Style::default().fg(colors.chart_bandwidth.points_rx))
+ .marker(Marker::Dot)
+ .style(Style::default().fg(colors.chart_bandwidth.points_rx))
+ .graph_type(GraphType::Line)
+ .data(&x.rx.dataset),
+ ]);
+
+ let chart = make_chart(
+ x.state,
+ colors,
+ dataset,
+ &x.rx.current,
+ &x.rx.max,
+ &x.tx.current,
+ &x.tx.max,
+ );
+
+ f.render_widget(chart, area);
+ }
+}
+
+#[cfg(test)]
+#[allow(clippy::unwrap_used)]
+mod tests {
+ use insta::assert_snapshot;
+ use ratatui::style::Color;
+
+ use crate::{
+ app_data::{ContainerId, NetworkBandwidth, State},
+ config::AppColors,
+ ui::{
+ FrameData,
+ draw_blocks::tests::{COLOR_RX, COLOR_TX, get_result, test_setup},
+ },
+ };
+
+ const TX_DOTS: [(usize, usize); 14] = [
+ (1, 21),
+ (2, 19),
+ (2, 20),
+ (3, 18),
+ (3, 19),
+ (4, 10),
+ (4, 11),
+ (4, 17),
+ (4, 18),
+ (5, 16),
+ (6, 14),
+ (6, 15),
+ (7, 13),
+ (7, 14),
+ ];
+
+ const RX_DOTS: [(usize, usize); 15] = [
+ (1, 21),
+ (2, 19),
+ (2, 20),
+ (3, 18),
+ (3, 19),
+ (4, 10),
+ (4, 11),
+ (4, 17),
+ (4, 18),
+ (5, 16),
+ (6, 16),
+ (6, 15),
+ (7, 13),
+ (7, 14),
+ (8, 13),
+ ];
+
+ const COMBINED_DOTS_RX: [(usize, usize); 15] = [
+ (1, 21),
+ (2, 19),
+ (2, 20),
+ (3, 18),
+ (3, 19),
+ (4, 10),
+ (4, 11),
+ (4, 17),
+ (4, 18),
+ (5, 16),
+ (6, 15),
+ (6, 16),
+ (7, 13),
+ (7, 14),
+ (8, 13),
+ ];
+
+ const COMBINED_DOTS_TX: [(usize, usize); 8] = [
+ (7, 19),
+ (7, 20),
+ (7, 21),
+ (8, 14),
+ (8, 15),
+ (8, 16),
+ (8, 17),
+ (8, 18),
+ ];
+
+ #[test]
+ /// When status is Running, but not data, charts drawn without dots etc, colours correct
+ fn test_draw_blocks_charts_running_none() {
+ let mut setup = test_setup(40, 10, true, true);
+
+ let fd = FrameData::from((&setup.app_data, &setup.gui_state));
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // border
+ (9, _) | (1..=9, 0 | 39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Border first row only
+ (0, 0..=4 | 34..=39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Title RX
+ (0, 5..=18) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ // Title TX
+ (0, 19..=33) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // Y axis
+ (1..=8, 10) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // TX max
+ (4, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // RX max
+ (6, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ _ => {
+ assert_eq!(result_cell.fg, Color::Reset);
+ assert_eq!(result_cell.bg, Color::Reset);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test with TX data
+ fn test_draw_blocks_charts_running_with_data_tx() {
+ let mut setup = test_setup(40, 10, true, true);
+ let mut tx = NetworkBandwidth::new();
+
+ for i in 0..=20 {
+ tx.push(1000 * i * (10 + 5 * i));
+ }
+
+ if let Some(item) = setup
+ .app_data
+ .lock()
+ .get_container_by_id(&ContainerId::from("1"))
+ {
+ item.tx = tx;
+ }
+
+ let fd = FrameData::from((&setup.app_data, &setup.gui_state));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // border
+ (9, _) | (1..=9, 0 | 39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Border first row only
+ (0, 0..=3 | 35..=39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Title RX
+ (0, 4..=17) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ // Title TX
+ (0, 18..=34) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // Y axis
+ (1..=8, 12) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // TX max
+ (4, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // RX max
+ (6, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ // TX dots
+ x if TX_DOTS.contains(&(row_index, result_cell_index)) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ _ => {
+ assert_eq!(result_cell.fg, Color::Reset);
+ assert_eq!(result_cell.bg, Color::Reset);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test with RX data
+ fn test_draw_blocks_charts_running_with_data_rx() {
+ let mut setup = test_setup(40, 10, true, true);
+ let mut rx = NetworkBandwidth::new();
+
+ for i in 0..=20 {
+ rx.push(2000 * i * (10 + 7 * i));
+ }
+
+ if let Some(item) = setup
+ .app_data
+ .lock()
+ .get_container_by_id(&ContainerId::from("1"))
+ {
+ item.rx = rx;
+ }
+
+ let fd = FrameData::from((&setup.app_data, &setup.gui_state));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // border
+ (9, _) | (1..=9, 0 | 39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Border first row only
+ (0, 0..=3 | 35..=39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Title RX
+ (0, 4..=19) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ // Title TX
+ (0, 20..=34) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // Y axis
+ (1..=8, 12) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // RX max
+ (4, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ // TX max
+ (6, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // RX dots
+ x if RX_DOTS.contains(&(row_index, result_cell_index)) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ _ => {
+ assert_eq!(result_cell.fg, Color::Reset);
+ assert_eq!(result_cell.bg, Color::Reset);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test with RX & TX data
+ fn test_draw_blocks_charts_running_with_data_tx_and_rx() {
+ let mut setup = test_setup(40, 10, true, true);
+ let mut rx = NetworkBandwidth::new();
+ let mut tx = NetworkBandwidth::new();
+ for i in 0..=20 {
+ rx.push(2000 * i * (10 + 7 * i));
+ tx.push(200 * i * (10 + 7 * i));
+ }
+
+ if let Some(item) = setup
+ .app_data
+ .lock()
+ .get_container_by_id(&ContainerId::from("1"))
+ {
+ item.rx = rx;
+ item.tx = tx;
+ }
+
+ let fd = FrameData::from((&setup.app_data, &setup.gui_state));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // border
+ (9, _) | (1..=9, 0 | 39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Border first row only
+ (0, 0..=3 | 36..=39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Title RX
+ (0, 4..=19) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ // Title TX
+ (0, 20..=35) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // Y axis
+ (1..=8, 12) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // RX max
+ (4, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ // TX max
+ (6, 1..=10) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // TX dots
+ x if COMBINED_DOTS_TX.contains(&(row_index, result_cell_index)) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_TX);
+ }
+ // RX dots
+ x if COMBINED_DOTS_RX.contains(&(row_index, result_cell_index)) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, COLOR_RX);
+ }
+ _ => {
+ assert_eq!(result_cell.fg, Color::Reset);
+ assert_eq!(result_cell.bg, Color::Reset);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Whens status paused, some text is now Yellow
+ fn test_draw_blocks_charts_paused() {
+ let mut setup = test_setup(40, 10, true, true);
+ setup.app_data.lock().containers.items[0].state = State::Paused;
+
+ let fd = FrameData::from((&setup.app_data, &setup.gui_state));
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // border
+ (9, _) | (1..=9, 0 | 39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Border first row only
+ (0, 0..=4 | 34..=39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Title & y-axis max
+ (0, 5..=33) | (4 | 6, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Yellow);
+ }
+ // Y axis
+ (1..=8, 10) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ _ => {
+ assert_eq!(result_cell.fg, Color::Reset);
+ assert_eq!(result_cell.bg, Color::Reset);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Whens status dead, some text is now red
+ fn test_draw_blocks_charts_dead() {
+ let mut setup = test_setup(40, 10, true, true);
+ setup.app_data.lock().containers.items[0].state = State::Dead;
+
+ let fd = FrameData::from((&setup.app_data, &setup.gui_state));
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // border
+ (9, _) | (1..=9, 0 | 39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Border first row only
+ (0, 0..=4 | 34..=39) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ // Title & y-axis max
+ (0, 5..=33) | (4 | 6, 1..=9) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Red);
+ }
+ // Y axis
+ (1..=8, 10) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ _ => {
+ assert_eq!(result_cell.fg, Color::Reset);
+ assert_eq!(result_cell.bg, Color::Reset);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Custom colours correctly applied to each part of the charts
+ fn test_draw_blocks_charts_custom_colors() {
+ let mut colors = AppColors::new();
+
+ colors.chart_bandwidth.background = Color::White;
+ colors.chart_bandwidth.border = Color::Red;
+ colors.chart_bandwidth.max_rx = Color::Green;
+ colors.chart_bandwidth.max_tx = Color::Magenta;
+ colors.chart_bandwidth.title_rx = Color::LightGreen;
+ colors.chart_bandwidth.title_tx = Color::LightRed;
+ colors.chart_bandwidth.points_rx = Color::Black;
+ colors.chart_bandwidth.points_tx = Color::Blue;
+ colors.chart_bandwidth.y_axis = Color::Yellow;
+
+ let mut setup = test_setup(40, 10, true, true);
+
+ let mut rx = NetworkBandwidth::new();
+ let mut tx = NetworkBandwidth::new();
+ for i in 0..=20 {
+ rx.push(2000 * i * (10 + 7 * i));
+ tx.push(200 * i * (10 + 7 * i));
+ }
+
+ if let Some(item) = setup
+ .app_data
+ .lock()
+ .get_container_by_id(&ContainerId::from("1"))
+ {
+ item.rx = rx;
+ item.tx = tx;
+ }
+
+ let fd = FrameData::from((&setup.app_data, &setup.gui_state));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(setup.area, colors, f, &fd);
+ })
+ .unwrap();
+
+ assert_snapshot!(setup.terminal.backend());
+
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // border
+ (9, _) | (1..=9, 0 | 39) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Red);
+ }
+ // Border first row only
+ (0, 0..=3 | 36..=39) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Red);
+ }
+ // Title RX
+ (0, 4..=19) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::LightGreen);
+ }
+ // Title TX
+ (0, 20..=35) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::LightRed);
+ }
+ // Y axis
+ (1..=8, 12) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Yellow);
+ }
+ // RX max
+ (4, 1..=11) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Green);
+ }
+ // TX max
+ (6, 1..=10) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Magenta);
+ }
+ // TX dots
+ x if COMBINED_DOTS_TX.contains(&(row_index, result_cell_index)) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Blue);
+ }
+ // RX dots
+ x if COMBINED_DOTS_RX.contains(&(row_index, result_cell_index)) => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Black);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::White);
+ assert_eq!(result_cell.fg, Color::Reset);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ui/draw_blocks/charts.rs b/src/ui/draw_blocks/chart_cpu_mem.rs
similarity index 93%
rename from src/ui/draw_blocks/charts.rs
rename to src/ui/draw_blocks/chart_cpu_mem.rs
index 6926213..1cd9952 100644
--- a/src/ui/draw_blocks/charts.rs
+++ b/src/ui/draw_blocks/chart_cpu_mem.rs
@@ -11,7 +11,7 @@ use ratatui::{
use super::{CONSTRAINT_50_50, FrameData};
use crate::{
- app_data::{ByteStats, CpuStats, State, Stats},
+ app_data::{State, Stats},
config::AppColors,
};
@@ -118,7 +118,7 @@ fn make_chart<'a, T: Stats + Display>(
/// Draw the cpu + mem charts
pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
- if let Some((cpu, mem)) = fd.chart_data.as_ref() {
+ if let Some(x) = fd.chart_data.as_ref() {
let area = Layout::default()
.direction(Direction::Horizontal)
.constraints(CONSTRAINT_50_50)
@@ -129,34 +129,34 @@ pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
.marker(symbols::Marker::Dot)
.style(Style::default().fg(colors.chart_cpu.points))
.graph_type(GraphType::Line)
- .data(&cpu.0),
+ .data(&x.cpu.dataset),
];
let mem_dataset = vec![
Dataset::default()
.marker(symbols::Marker::Dot)
.style(Style::default().fg(colors.chart_memory.points))
.graph_type(GraphType::Line)
- .data(&mem.0),
+ .data(&x.memory.dataset),
];
- let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1));
- #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
- let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64));
+ // let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1));
+ // #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+ // let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64));
let cpu_chart = make_chart(
ChartVariant::Cpu,
colors,
- &cpu_stats,
+ &x.cpu.current,
cpu_dataset,
- &cpu.1,
- cpu.2,
+ &x.cpu.max,
+ x.state,
);
let mem_chart = make_chart(
ChartVariant::Memory,
colors,
- &mem_stats,
+ &x.memory.current,
mem_dataset,
- &mem.1,
- mem.2,
+ &x.memory.max,
+ x.state,
);
f.render_widget(cpu_chart, area[0]);
@@ -175,7 +175,7 @@ mod tests {
config::AppColors,
ui::{
FrameData,
- draw_blocks::tests::{COLOR_ORANGE, get_result, insert_chart_data, test_setup},
+ draw_blocks::tests::{COLOR_ORANGE, get_result, insert_all_chart_data, test_setup},
},
};
@@ -194,42 +194,41 @@ mod tests {
];
// co-ordinates of the dots from the cpu chart
- const CPU_XY: [(usize, usize); 15] = [
- (1, 12),
- (2, 11),
+ const CPU_XY: [(usize, usize); 16] = [
+ (1, 13),
(2, 12),
- (3, 10),
+ (2, 13),
(3, 11),
- (3, 12),
- (4, 10),
- (4, 12),
- (5, 9),
+ (3, 13),
+ (4, 11),
+ (4, 13),
+ (5, 10),
(5, 13),
- (5, 14),
- (6, 8),
+ (6, 9),
(6, 13),
+ (6, 14),
(7, 8),
+ (7, 9),
(7, 13),
+ (7, 14),
];
// co-ordinates of the dots from the memory chart
- const MEM_XY: [(usize, usize); 16] = [
- (1, 54),
+ const MEM_XY: [(usize, usize); 14] = [
(1, 55),
(2, 54),
(2, 55),
- (3, 53),
+ (3, 54),
(3, 55),
- (4, 52),
+ (4, 53),
(4, 55),
- (5, 51),
(5, 52),
- (5, 55),
+ (5, 53),
(5, 56),
- (6, 51),
- (6, 55),
+ (6, 52),
+ (6, 56),
(7, 51),
- (7, 55),
+ (7, 56),
];
#[test]
@@ -275,7 +274,7 @@ mod tests {
fn test_draw_blocks_charts_running_some() {
let mut setup = test_setup(80, 10, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
@@ -324,7 +323,7 @@ mod tests {
fn test_draw_blocks_charts_paused() {
let mut setup = test_setup(80, 10, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
setup.app_data.lock().containers.items[0].state = State::Paused;
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
@@ -336,6 +335,7 @@ mod tests {
.unwrap();
assert_snapshot!(setup.terminal.backend());
+ //
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
@@ -369,7 +369,7 @@ mod tests {
/// When dead, text is red
fn test_draw_blocks_charts_dead() {
let mut setup = test_setup(80, 10, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
setup.app_data.lock().containers.items[0].state = State::Dead;
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
@@ -429,7 +429,7 @@ mod tests {
let mut setup = test_setup(80, 10, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
diff --git a/src/ui/draw_blocks/commands.rs b/src/ui/draw_blocks/commands.rs
index d01af86..2ca6221 100644
--- a/src/ui/draw_blocks/commands.rs
+++ b/src/ui/draw_blocks/commands.rs
@@ -1,6 +1,6 @@
use std::sync::Arc;
-use super::RIGHT_ARROW;
+use super::SELECT_ARROW;
use crate::{
app_data::AppData,
config::AppColors,
@@ -44,7 +44,7 @@ pub fn draw(
let items = List::new(items)
.block(block)
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
- .highlight_symbol(RIGHT_ARROW);
+ .highlight_symbol(SELECT_ARROW);
f.render_stateful_widget(items, area, i);
} else {
let paragraph = Paragraph::new("").block(block).alignment(Alignment::Center);
@@ -173,7 +173,7 @@ mod tests {
setup
.app_data
.lock()
- .docker_controls_scroll(&ScrollDirection::Next);
+ .docker_controls_scroll(&ScrollDirection::Down);
setup
.terminal
@@ -370,7 +370,7 @@ mod tests {
setup
.app_data
.lock()
- .docker_controls_scroll(&ScrollDirection::Next);
+ .docker_controls_scroll(&ScrollDirection::Down);
setup
.terminal
diff --git a/src/ui/draw_blocks/containers.rs b/src/ui/draw_blocks/containers.rs
index a63283c..7078d1b 100644
--- a/src/ui/draw_blocks/containers.rs
+++ b/src/ui/draw_blocks/containers.rs
@@ -82,11 +82,19 @@ fn format_containers<'a>(colors: AppColors, i: &ContainerItem, widths: &Columns)
colors.containers.text,
),
Span::styled(
- format!("{:>width$}{MARGIN}", i.rx, width = widths.net_rx.1.into()),
+ format!(
+ "{:>width$}{MARGIN}",
+ i.rx.current_total(),
+ width = widths.net_rx.1.into()
+ ),
Style::default().fg(colors.containers.text_rx),
),
Span::styled(
- format!("{:>width$}{MARGIN}", i.tx, width = widths.net_tx.1.into()),
+ format!(
+ "{:>width$}{MARGIN}",
+ i.tx.current_total(),
+ width = widths.net_tx.1.into()
+ ),
Style::default().fg(colors.containers.text_tx),
),
])
diff --git a/src/ui/draw_blocks/error.rs b/src/ui/draw_blocks/error.rs
index 0d69f12..ddb85ef 100644
--- a/src/ui/draw_blocks/error.rs
+++ b/src/ui/draw_blocks/error.rs
@@ -22,6 +22,7 @@ pub fn draw(
colors: AppColors,
error: &AppError,
f: &mut Frame,
+ host: Option,
keymap: &Keymap,
seconds: Option,
) {
@@ -31,13 +32,21 @@ pub fn draw(
.title_alignment(Alignment::Center)
.borders(Borders::ALL);
- let to_push = if matches!(error, AppError::DockerConnect) {
- format!(
- "\n\n {}::v{} closing in {:02} seconds",
+ let mut text = format!("\n{error}");
+
+ if error == &AppError::DockerConnect {
+ let s = if let Some(host) = host {
+ format!(" @ \"{host}\"")
+ } else {
+ String::new()
+ };
+ text.push_str(&format!(
+ "{}\n\n {}::v{} closing in {:02} seconds",
+ s,
NAME,
VERSION,
- seconds.unwrap_or(5)
- )
+ seconds.unwrap_or(5),
+ ))
} else {
let clear_text = if keymap.clear == Keymap::new().clear {
format!("( {} ) {SUFFIX_CLEAR}", keymap.clear.0)
@@ -46,20 +55,17 @@ pub fn draw(
} else {
format!(" ( {} ) {SUFFIX_CLEAR}", keymap.clear.0)
};
+ text.push_str(&format!("\n\n{clear_text}"));
+ }
- let quit_text = if keymap.quit == Keymap::new().quit {
- format!("( {} ) {SUFFIX_QUIT}", keymap.quit.0)
- } else if let Some(secondary) = keymap.quit.1 {
- format!(" ( {} | {secondary} ) {SUFFIX_QUIT}", keymap.quit.0)
- } else {
- format!(" ( {} ) {SUFFIX_QUIT}", keymap.quit.0)
- };
- format!("\n\n{clear_text}\n\n{quit_text}")
+ let quit_text = if keymap.quit == Keymap::new().quit {
+ format!("( {} ) {SUFFIX_QUIT}", keymap.quit.0)
+ } else if let Some(secondary) = keymap.quit.1 {
+ format!(" ( {} | {secondary} ) {SUFFIX_QUIT}", keymap.quit.0)
+ } else {
+ format!(" ( {} ) {SUFFIX_QUIT}", keymap.quit.0)
};
-
- let mut text = format!("\n{error}");
-
- text.push_str(to_push.as_str());
+ text.push_str(&format!("\n\n{quit_text}"));
// Find the maximum line width & height
let padded_width = max_line_width(&text) + 8;
@@ -106,8 +112,7 @@ mod tests {
#[test]
/// Test that the error popup is centered, red background, white border, white text, and displays the correct text
fn test_draw_blocks_error_docker_connect_error() {
- let mut setup = test_setup(46, 9, true, true);
-
+ let mut setup = test_setup(50, 11, true, true);
setup
.terminal
.draw(|f| {
@@ -115,6 +120,7 @@ mod tests {
AppColors::new(),
&AppError::DockerConnect,
f,
+ None,
&Keymap::new(),
Some(4),
);
@@ -123,12 +129,50 @@ mod tests {
assert_snapshot!(setup.terminal.backend());
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
- if let (0 | 8, _) = (row_index, result_cell_index) {
- assert_eq!(result_cell.bg, Color::Reset);
- assert_eq!(result_cell.fg, Color::Reset);
- } else {
- assert_eq!(result_cell.bg, Color::Red);
- assert_eq!(result_cell.fg, Color::White);
+ match (row_index, result_cell_index) {
+ (0 | 10, _) | (_, 0 | 49) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Reset);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Red);
+ assert_eq!(result_cell.fg, Color::White);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test that the error popup is centered, red background, white border, white text, and displays the correct text with the custom docker host address
+ fn test_draw_blocks_error_docker_connect_error_custom_host() {
+ let mut setup = test_setup(60, 11, true, true);
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ AppColors::new(),
+ &AppError::DockerConnect,
+ f,
+ Some("/test/host.sock".to_owned()),
+ &Keymap::new(),
+ Some(4),
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 10, _) | (_, 0 | 59) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Reset);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Red);
+ assert_eq!(result_cell.fg, Color::White);
+ }
}
}
}
@@ -146,6 +190,7 @@ mod tests {
AppColors::new(),
&AppError::DockerExec,
f,
+ None,
&Keymap::new(),
Some(4),
);
@@ -183,7 +228,14 @@ mod tests {
setup
.terminal
.draw(|f| {
- super::draw(colors, &AppError::DockerExec, f, &Keymap::new(), Some(4));
+ super::draw(
+ colors,
+ &AppError::DockerExec,
+ f,
+ None,
+ &Keymap::new(),
+ Some(4),
+ );
})
.unwrap();
@@ -218,7 +270,14 @@ mod tests {
setup
.terminal
.draw(|f| {
- super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
+ super::draw(
+ AppColors::new(),
+ &AppError::DockerExec,
+ f,
+ None,
+ &keymap,
+ None,
+ );
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
@@ -235,7 +294,14 @@ mod tests {
setup
.terminal
.draw(|f| {
- super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
+ super::draw(
+ AppColors::new(),
+ &AppError::DockerExec,
+ f,
+ None,
+ &keymap,
+ None,
+ );
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
@@ -252,7 +318,14 @@ mod tests {
setup
.terminal
.draw(|f| {
- super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
+ super::draw(
+ AppColors::new(),
+ &AppError::DockerExec,
+ f,
+ None,
+ &keymap,
+ None,
+ );
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
diff --git a/src/ui/draw_blocks/filter.rs b/src/ui/draw_blocks/filter.rs
index 101ce2d..1091cee 100644
--- a/src/ui/draw_blocks/filter.rs
+++ b/src/ui/draw_blocks/filter.rs
@@ -5,7 +5,14 @@ use ratatui::{
text::{Line, Span},
};
-use crate::{app_data::FilterBy, config::AppColors, ui::FrameData};
+use crate::{
+ app_data::FilterBy,
+ config::AppColors,
+ ui::{
+ FrameData,
+ draw_blocks::{LEFT_ARROW, RIGHT_ARROW},
+ },
+};
/// Create the filter_by by spans, coloured dependant on which one is selected
fn filter_by_spans(colors: AppColors, fd: &'_ FrameData) -> [Span<'_>; 4] {
@@ -46,7 +53,7 @@ pub fn draw(area: Rect, colors: AppColors, frame: &mut Frame, fd: &FrameData) {
let mut line = vec![
Span::styled(" Esc ", style_but),
Span::styled(" clear ", style_desc),
- Span::styled(" โ by โ ", style_but),
+ Span::styled(format!(" {LEFT_ARROW} by {RIGHT_ARROW} "), style_but),
Span::from(" "),
];
line.extend_from_slice(&filter_by_spans(colors, fd));
diff --git a/src/ui/draw_blocks/headers.rs b/src/ui/draw_blocks/headers.rs
index 0e24e22..01a0e7d 100644
--- a/src/ui/draw_blocks/headers.rs
+++ b/src/ui/draw_blocks/headers.rs
@@ -39,14 +39,14 @@ fn gen_header<'a>(
fn gen_header_block<'a>(colors: AppColors, fd: &FrameData, header: Header) -> (Color, &'a str) {
let mut color = colors.headers_bar.text;
let mut suffix = "";
- if let Some((a, b)) = &fd.sorted_by {
- if &header == a {
- match b {
- SortedOrder::Asc => suffix = " โฒ",
- SortedOrder::Desc => suffix = " โผ",
- }
- color = colors.headers_bar.text_selected;
+ if let Some((a, b)) = &fd.sorted_by
+ && &header == a
+ {
+ match b {
+ SortedOrder::Asc => suffix = " โฒ",
+ SortedOrder::Desc => suffix = " โผ",
}
+ color = colors.headers_bar.text_selected;
}
(color, suffix)
diff --git a/src/ui/draw_blocks/help.rs b/src/ui/draw_blocks/help.rs
index 7e16abc..1c1fd76 100644
--- a/src/ui/draw_blocks/help.rs
+++ b/src/ui/draw_blocks/help.rs
@@ -1,410 +1,751 @@
-use crossterm::event::KeyCode;
-use jiff::tz::TimeZone;
+use std::sync::LazyLock;
+
use ratatui::{
Frame,
- layout::{Alignment, Constraint, Direction, Layout},
- style::{Color, Modifier, Style},
+ layout::{Alignment, Constraint, Direction, Layout, Rect, Size},
+ style::{Color, Style, Stylize},
text::{Line, Span},
- widgets::{Block, BorderType, Borders, Clear, Paragraph},
+ widgets::{Block, BorderType, Borders, Clear, Padding, Paragraph},
};
use crate::{
- config::{AppColors, Keymap},
+ config::{AppColors, Config, Keymap},
ui::gui_state::BoxLocation,
};
use super::{DESCRIPTION, NAME_TEXT, REPO, VERSION, popup};
-/// Help popup box needs these three pieces of information
-struct HelpInfo {
- lines: Vec>,
- width: usize,
- height: usize,
+macro_rules! to_u16 {
+ ($value:expr) => {
+ u16::try_from($value).unwrap_or_default()
+ };
}
-impl HelpInfo {
- /// Find the max width of a Span in &[Line]
- fn calc_width(lines: &[Line]) -> usize {
- lines
- .iter()
- .map(ratatui::prelude::Line::width)
- .max()
- .unwrap_or(1)
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum KeyDescriptions {
+ Clear,
+ Command,
+ Exec,
+ FilterMode,
+ Help,
+ InspectMode,
+ LogHeight,
+ LogVisibility,
+ MouseCapture,
+ Panel,
+ Quit,
+ Redraw,
+ Save,
+ ScrollEnd,
+ ScrollH,
+ ScrollSpeed,
+ ScrollStart,
+ ScrollV,
+ SearchMode,
+ SortCpu,
+ SortHeader,
+ SortId,
+ SortImage,
+ SortMem,
+ SortName,
+ SortRX,
+ SortState,
+ SortStatus,
+ SortStop,
+ SortTX,
+}
+
+type Column = Vec<(Vec>, KeyDescriptions)>;
+
+#[derive(Debug, Clone, Hash)]
+struct KeymapColumns {
+ left: Column,
+ right: Column,
+}
+
+impl KeymapColumns {
+ fn default(keymap: &Keymap) -> Self {
+ Self {
+ left: vec![
+ (
+ vec![
+ Some(keymap.quit.0.to_string()),
+ keymap.quit.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Quit,
+ ),
+ (
+ vec![
+ Some(keymap.scroll_down.0.to_string()),
+ Some(keymap.scroll_up.0.to_string()),
+ keymap.scroll_down.1.as_ref().map(|i| i.to_string()),
+ keymap.scroll_up.1.as_ref().map(|i| i.to_string()),
+ Some(keymap.scroll_start.0.to_string()),
+ Some(keymap.scroll_end.0.to_string()),
+ keymap.scroll_start.1.as_ref().map(|i| i.to_string()),
+ keymap.scroll_end.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::ScrollV,
+ ),
+ (
+ vec![Some(keymap.scroll_many.to_string())],
+ KeyDescriptions::ScrollSpeed,
+ ),
+ (
+ vec![
+ Some(keymap.exec.0.to_string()),
+ keymap.exec.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Exec,
+ ),
+ (
+ vec![
+ Some(keymap.filter_mode.0.to_string()),
+ keymap.filter_mode.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::FilterMode,
+ ),
+ (
+ vec![
+ Some(keymap.toggle_help.0.to_string()),
+ keymap.toggle_help.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Help,
+ ),
+ (
+ vec![
+ Some(keymap.log_section_height_decrease.0.to_string()),
+ Some(keymap.log_section_height_increase.0.to_string()),
+ keymap
+ .log_section_height_decrease
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ keymap
+ .log_section_height_increase
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::LogHeight,
+ ),
+ (vec![Some("1 ~ 9".to_owned())], KeyDescriptions::SortHeader),
+ (
+ vec![
+ Some(keymap.select_next_panel.0.to_string()),
+ Some(keymap.select_previous_panel.0.to_string()),
+ keymap
+ .select_previous_panel
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ keymap.select_next_panel.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Panel,
+ ),
+ (
+ vec![
+ Some(keymap.save_logs.0.to_string()),
+ keymap.save_logs.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Save,
+ ),
+ ],
+ right: vec![
+ (
+ vec![
+ Some(keymap.clear.0.to_string()),
+ keymap.clear.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Clear,
+ ),
+ (
+ vec![
+ Some(keymap.scroll_back.0.to_string()),
+ Some(keymap.scroll_forward.0.to_string()),
+ keymap.scroll_back.1.as_ref().map(|i| i.to_string()),
+ keymap.scroll_forward.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::ScrollH,
+ ),
+ (vec![Some(String::from("Enter"))], KeyDescriptions::Command),
+ (
+ vec![
+ Some(keymap.inspect.0.to_string()),
+ keymap.inspect.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::InspectMode,
+ ),
+ (
+ vec![
+ Some(keymap.log_search_mode.0.to_string()),
+ keymap.log_search_mode.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SearchMode,
+ ),
+ (
+ vec![
+ Some(keymap.force_redraw.0.to_string()),
+ keymap.force_redraw.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Redraw,
+ ),
+ (
+ vec![
+ Some(keymap.log_section_toggle.0.to_string()),
+ keymap.log_section_toggle.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::LogVisibility,
+ ),
+ (vec![Some("0".to_owned())], KeyDescriptions::SortStop),
+ (
+ vec![
+ Some(keymap.toggle_mouse_capture.0.to_string()),
+ keymap
+ .toggle_mouse_capture
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::MouseCapture,
+ ),
+ ],
+ }
}
+ fn custom(config: &Config) -> Self {
+ Self {
+ left: vec![
+ (
+ vec![
+ Some(config.keymap.quit.0.to_string()),
+ config.keymap.quit.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Quit,
+ ),
+ (
+ vec![
+ Some(config.keymap.scroll_down.0.to_string()),
+ Some(config.keymap.scroll_up.0.to_string()),
+ config.keymap.scroll_down.1.as_ref().map(|i| i.to_string()),
+ config.keymap.scroll_up.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::ScrollV,
+ ),
+ (
+ vec![
+ Some(config.keymap.scroll_start.0.to_string()),
+ config.keymap.scroll_start.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::ScrollStart,
+ ),
+ (
+ vec![Some(config.keymap.scroll_many.to_string())],
+ KeyDescriptions::ScrollSpeed,
+ ),
+ (
+ vec![
+ Some(config.keymap.exec.0.to_string()),
+ config.keymap.exec.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Exec,
+ ),
+ (
+ vec![
+ Some(config.keymap.filter_mode.0.to_string()),
+ config.keymap.filter_mode.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::FilterMode,
+ ),
+ (
+ vec![
+ Some(config.keymap.toggle_help.0.to_string()),
+ config.keymap.toggle_help.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Help,
+ ),
+ (
+ vec![
+ Some(config.keymap.log_section_height_decrease.0.to_string()),
+ Some(config.keymap.log_section_height_increase.0.to_string()),
+ config
+ .keymap
+ .log_section_height_decrease
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ config
+ .keymap
+ .log_section_height_increase
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::LogHeight,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_name.0.to_string()),
+ config.keymap.sort_by_name.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortName,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_status.0.to_string()),
+ config
+ .keymap
+ .sort_by_status
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortStatus,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_memory.0.to_string()),
+ config
+ .keymap
+ .sort_by_memory
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortMem,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_image.0.to_string()),
+ config
+ .keymap
+ .sort_by_image
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortImage,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_tx.0.to_string()),
+ config.keymap.sort_by_tx.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortTX,
+ ),
+ (
+ vec![
+ Some(config.keymap.select_next_panel.0.to_string()),
+ Some(config.keymap.select_previous_panel.0.to_string()),
+ config
+ .keymap
+ .select_previous_panel
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ config
+ .keymap
+ .select_next_panel
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Panel,
+ ),
+ (
+ vec![
+ Some(config.keymap.save_logs.0.to_string()),
+ config.keymap.save_logs.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Save,
+ ),
+ ],
+
+ right: vec![
+ (
+ vec![
+ Some(config.keymap.clear.0.to_string()),
+ config.keymap.clear.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Clear,
+ ),
+ (
+ vec![
+ Some(config.keymap.scroll_back.0.to_string()),
+ Some(config.keymap.scroll_forward.0.to_string()),
+ config.keymap.scroll_back.1.as_ref().map(|i| i.to_string()),
+ config
+ .keymap
+ .scroll_forward
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::ScrollH,
+ ),
+ (
+ vec![
+ Some(config.keymap.scroll_end.0.to_string()),
+ config.keymap.scroll_end.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::ScrollEnd,
+ ),
+ (vec![Some(String::from("Enter"))], KeyDescriptions::Command),
+ (
+ vec![
+ Some(config.keymap.inspect.0.to_string()),
+ config.keymap.inspect.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::InspectMode,
+ ),
+ (
+ vec![
+ Some(config.keymap.log_search_mode.0.to_string()),
+ config
+ .keymap
+ .log_search_mode
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SearchMode,
+ ),
+ (
+ vec![
+ Some(config.keymap.force_redraw.0.to_string()),
+ config.keymap.force_redraw.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::Redraw,
+ ),
+ (
+ vec![
+ Some(config.keymap.log_section_toggle.0.to_string()),
+ config
+ .keymap
+ .log_section_toggle
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::LogVisibility,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_state.0.to_string()),
+ config
+ .keymap
+ .sort_by_state
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortState,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_cpu.0.to_string()),
+ config.keymap.sort_by_cpu.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortCpu,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_id.0.to_string()),
+ config.keymap.sort_by_id.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortId,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_by_rx.0.to_string()),
+ config.keymap.sort_by_rx.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortRX,
+ ),
+ (
+ vec![
+ Some(config.keymap.sort_reset.0.to_string()),
+ config.keymap.sort_reset.1.as_ref().map(|i| i.to_string()),
+ ],
+ KeyDescriptions::SortStop,
+ ),
+ (
+ vec![
+ Some(config.keymap.toggle_mouse_capture.0.to_string()),
+ config
+ .keymap
+ .toggle_mouse_capture
+ .1
+ .as_ref()
+ .map(|i| i.to_string()),
+ ],
+ KeyDescriptions::MouseCapture,
+ ),
+ ],
+ }
+ }
+
+ /// Add 1 to allow spacing between the key and the definition
+ fn longest_line(column: &Column) -> usize {
+ column
+ .iter()
+ .map(|(keys, _)| {
+ keys.iter()
+ .filter_map(|k| k.as_deref())
+ .collect::>()
+ .join(" ")
+ .len()
+ })
+ .max()
+ .unwrap_or(0)
+ .saturating_add(1)
+ }
+
+ fn create_button_line(column: &Column, colors: &AppColors) -> Vec> {
+ let longest_button = Self::longest_line(column);
+ column
+ .iter()
+ .map(|(keys, desc)| HelpInfo::create_button_line(keys, desc, *colors, longest_button))
+ .collect::>()
+ }
+
+ fn to_helpinfo(&self, config: &Config) -> (HelpInfo, HelpInfo) {
+ let left = Self::create_button_line(&self.left, &config.app_colors);
+ let right = Self::create_button_line(&self.right, &config.app_colors);
+
+ let size_left = HelpInfo::calc_size(&left);
+ let size_right = HelpInfo::calc_size(&right);
+ (
+ HelpInfo {
+ lines: left,
+ size: size_left,
+ },
+ HelpInfo {
+ lines: right,
+ size: size_right,
+ },
+ )
+ }
+}
+
+impl KeyDescriptions {
+ fn as_str(&self) -> &'static str {
+ match self {
+ Self::Clear => "close dialog",
+ Self::Command => "send docker command",
+ Self::Exec => "exec into a container",
+ Self::FilterMode => "filter mode",
+ Self::Help => "toggle this panel",
+ Self::InspectMode => "container inspect mode",
+ Self::LogHeight => "change log section height",
+ Self::LogVisibility => "toggle of section visibility",
+ Self::MouseCapture => "toggle mouse capture - allows text selection",
+ Self::Panel => "change panel",
+ Self::Quit => "quit",
+ Self::Redraw => "force clear screen and redraw",
+ Self::Save => "save logs to file",
+ Self::ScrollH => "scroll horizontally",
+ Self::ScrollStart => "scroll to start",
+ Self::ScrollEnd => "scroll to end",
+ Self::ScrollSpeed => "increase scroll speed",
+ Self::ScrollV => "scroll vertically",
+ Self::SearchMode => "log search mode",
+ Self::SortHeader => "sort by header - or click header",
+ Self::SortStop => "stop sort",
+ Self::SortCpu => "sort by CPU",
+ Self::SortId => "sort by ID",
+ Self::SortImage => "sort by Image",
+ Self::SortMem => "sort by memory",
+ Self::SortName => "sort by name",
+ Self::SortRX => "sort by RX",
+ Self::SortState => "sort by state",
+ Self::SortStatus => "sort by status",
+ Self::SortTX => "sort by TX",
+ }
+ }
+}
+
+/// Help popup box needs these three pieces of information
+/// Change this to a trait
+#[derive(Debug, Clone, Hash)]
+struct HelpInfo {
+ lines: Vec>,
+ size: Size,
+}
+
+static DEFAULT_NAME: LazyLock = LazyLock::new(|| {
+ let colors = AppColors::new();
+ HelpInfo::gen_name_description(colors)
+});
+
+static DEFAULT_COLUMNS: LazyLock =
+ LazyLock::new(|| KeymapColumns::default(&Keymap::new()));
+
+impl HelpInfo {
+ /// Find the height and width of an array of lines
+ fn calc_size(lines: &[Line]) -> Size {
+ Size {
+ width: to_u16!(
+ lines
+ .iter()
+ .map(ratatui::prelude::Line::width)
+ .max()
+ .unwrap_or(1)
+ ),
+ height: to_u16!(lines.len()),
+ }
+ }
/// Just an empty span, i.e. a new line
- fn empty_span<'a>() -> Line<'a> {
+ fn empty_line<'a>() -> Line<'a> {
Line::from(String::new())
}
/// generate a span, of given &str and given color
- fn span<'a>(input: &str, color: Color) -> Span<'a> {
- Span::styled(input.to_owned(), Style::default().fg(color))
+ fn span<'a>(input: String, color: Color) -> Span<'a> {
+ Span::styled(input, Style::default().fg(color))
}
/// &str to black text span
- fn text_span<'a>(input: &str, color: AppColors) -> Span<'a> {
+ fn text_span<'a>(input: String, color: AppColors) -> Span<'a> {
Self::span(input, color.popup_help.text)
}
/// &str to white text span
- fn highlighted_text_span<'a>(input: &str, color: AppColors) -> Span<'a> {
+ fn highlighted_text_span<'a>(input: String, color: AppColors) -> Span<'a> {
Self::span(input, color.popup_help.text_highlight)
}
- /// Generate the `oxker` name span + metadata
- fn gen_name(colors: AppColors) -> Self {
+ /// Generate the `oxker` name section
+ fn gen_name_description(colors: AppColors) -> Self {
let mut lines = NAME_TEXT
.lines()
- .map(|i| Line::from(Self::highlighted_text_span(i, colors)))
+ .map(|i| Line::from(Self::highlighted_text_span(i.to_owned(), colors)))
.collect::>();
- lines.insert(0, Self::empty_span());
- let width = Self::calc_width(&lines);
- let height = lines.len();
-
- Self {
- lines,
- width,
- height,
- }
+ lines.extend([
+ Self::empty_line(),
+ Line::from(Self::highlighted_text_span(DESCRIPTION.to_owned(), colors)).centered(),
+ ]);
+ let size = Self::calc_size(&lines);
+ Self { lines, size }
}
- /// Generate the description span + metadata
- fn gen_description(colors: AppColors) -> Self {
- let lines = [
- Self::empty_span(),
- Line::from(Self::highlighted_text_span(DESCRIPTION, colors)),
- Self::empty_span(),
- ];
+ fn create_button<'a>(
+ input: &[Option], // Use a slice for better flexibility
+ color: AppColors,
+ spacing: usize,
+ ) -> Span<'a> {
+ let label = input
+ .iter()
+ .flatten()
+ .map(|s| s.as_str())
+ .collect::>()
+ .join(" ");
- Self {
- lines: lines.to_vec(),
- width: Self::calc_width(&lines),
- height: lines.len(),
- }
+ let padded_text = format!("{label:, show_timestamp: bool) -> Self {
- let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors);
- let button_desc = |x: &str| Self::text_span(x, colors);
- let or = || button_desc("or");
- let space = || button_desc(" ");
+ fn create_button_line<'a>(
+ keys: &[Option],
+ desc: &KeyDescriptions,
+ app_colors: AppColors,
+ longest_button: usize,
+ ) -> Line<'a> {
+ Line::from(vec![
+ Self::create_button(keys, app_colors, longest_button),
+ Span::from(desc.as_str()),
+ ])
+ }
- let descriptions = [
- Line::from(vec![
- space(),
- button_item("tab"),
- or(),
- button_item("shift+tab"),
- button_desc("change panels"),
- ]),
- Line::from(vec![
- space(),
- button_item("โ โ"),
- or(),
- button_item("j k"),
- or(),
- button_item("Home End"),
- button_desc("scroll vertically"),
- ]),
- Line::from(vec![
- space(),
- button_item("โ โ"),
- button_desc("horizontal scroll across logs"),
- ]),
- Line::from(vec![
- space(),
- button_item("ctrl"),
- button_desc("increase scroll speed, used in conjunction scroll keys"),
- ]),
- Line::from(vec![
- space(),
- button_item("enter"),
- button_desc("send docker container command"),
- ]),
- Line::from(vec![
- space(),
- button_item("e"),
- button_desc("exec into a container"),
- #[cfg(target_os = "windows")]
- button_desc(" - not available on Windows"),
- ]),
- Line::from(vec![
- space(),
- button_item("f"),
- button_desc("force clear the screen & redraw the gui"),
- ]),
- Line::from(vec![
- space(),
- button_item("h"),
- button_desc("toggle this help information - or click heading"),
- ]),
- Line::from(vec![
- space(),
- button_item("s"),
- button_desc("save logs to file"),
- ]),
- Line::from(vec![
- space(),
- button_item("m"),
- button_desc(
- "toggle mouse capture - if disabled, text on screen can be selected & copied",
- ),
- ]),
- Line::from(vec![
- space(),
- button_item("F1"),
- or(),
- button_item("/"),
- button_desc("enter filter mode"),
- ]),
- Line::from(vec![
- space(),
- button_item("#"),
- button_desc("enter log search mode"),
- ]),
- Line::from(vec![space(), button_item("0"), button_desc("stop sort")]),
- Line::from(vec![
- space(),
- button_item("1 - 9"),
- button_desc("sort by header - or click header"),
- ]),
- Line::from(vec![
- space(),
- button_item("- ="),
- button_desc("change log section height"),
- ]),
- Line::from(vec![
- space(),
- button_item("\\"),
- button_desc("toggle log section visibility"),
- ]),
- Line::from(vec![
- space(),
- button_item("esc"),
- button_desc("close dialog"),
- ]),
- Line::from(vec![
- space(),
- button_item("q"),
- button_desc("quit at any time"),
- ]),
+ fn gen_keymap_title(style: Style) -> Self {
+ let lines = vec![
+ Self::empty_line(),
+ Line::from(Span::from("Keymap"))
+ .style(style)
+ .centered()
+ .underlined(),
];
+ let size = Self::calc_size(&lines);
+ Self { lines, size }
+ }
- let mut lines = if show_timestamp {
- Vec::from([
- Self::custom_text(colors, &Keymap::new(), zone),
- Self::empty_span(),
- ])
+ fn gen_keymap(config: &Config) -> (Self, Self) {
+ let columns = if config.keymap == Keymap::new() {
+ DEFAULT_COLUMNS.clone()
} else {
- vec![]
+ KeymapColumns::custom(config)
};
-
- lines.extend_from_slice(&descriptions);
- let width = Self::calc_width(&lines);
- let height = lines.len();
-
- Self {
- lines,
- width,
- height,
- }
+ columns.to_helpinfo(config)
}
- /// Generate the final lines, GitHub link etc, + metadata
- fn gen_final(colors: AppColors) -> Self {
- let lines = [
- Self::empty_span(),
- Line::from(vec![Self::text_span(
- "currently an early work in progress, all and any input appreciated",
- colors,
- )]),
- Line::from(vec![Span::styled(
- REPO,
- Style::default()
- .fg(colors.popup_help.text_highlight)
- .add_modifier(Modifier::UNDERLINED),
- )]),
- ];
+ fn gen_locations(config: &Config) -> Self {
+ let mut entries = Vec::new();
- Self {
- lines: lines.to_vec(),
- width: Self::calc_width(&lines),
- height: lines.len(),
+ if let Some(path) = &config.dir_config {
+ entries.push(("config location: ", path.display().to_string()));
}
- }
-
- /// Display timezone in timestamps are visible
- /// Has ability to display if keymap or colors are customized, but currently not in use
- fn custom_text<'a>(colors: AppColors, _keymap: &Keymap, zone: Option<&TimeZone>) -> Line<'a> {
- let highlighted = |x: &str| Self::highlighted_text_span(x, colors);
- let text = |x: &str| Self::text_span(x, colors);
- let zone = zone.and_then(|i| i.iana_name()).unwrap_or("Etc/UTC");
- Line::from(Vec::from([text("logs timezone: "), highlighted(zone)])).centered()
- }
-
- /// Generate the display information when a custom keymap is being used
- fn gen_custom_keymap_info(
- colors: AppColors,
- km: &Keymap,
- zone: Option<&TimeZone>,
- show_timestamp: bool,
- ) -> Self {
- let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors);
- let button_desc = |x: &str| Self::text_span(x, colors);
- let or = || button_desc("or");
- let space = || button_desc(" ");
-
- let or_secondary = |a: (KeyCode, Option), desc: &str| {
- a.1.map_or_else(
- || {
- Line::from(vec![
- space(),
- button_item(&a.0.to_string()),
- button_desc(desc),
- ])
- },
- |secondary| {
- Line::from(vec![
- space(),
- button_item(&a.0.to_string()),
- or(),
- button_item(&secondary.to_string()),
- button_desc(desc),
- ])
- },
- )
- };
- let descriptions = [
- or_secondary(km.select_next_panel, "select next panel"),
- or_secondary(km.select_previous_panel, "select previous panel"),
- or_secondary(km.scroll_down, "scroll list down by one"),
- or_secondary(km.scroll_up, "scroll list up by one"),
- or_secondary(km.scroll_end, "scroll list to end"),
- or_secondary(km.scroll_start, "scroll list to start"),
- or_secondary(km.log_scroll_forward, "horizontal scroll logs right"),
- or_secondary(km.log_scroll_back, "horizontal scroll logs left"),
- Line::from(vec![
- space(),
- button_item(km.scroll_many.to_string().as_str()),
- button_desc("increase scroll speed, used in conjunction scroll keys"),
- ]),
- Line::from(vec![
- space(),
- button_item("enter"),
- button_desc("send docker container command"),
- ]),
- #[cfg(not(target_os = "windows"))]
- or_secondary(km.exec, "exec into a container"),
- #[cfg(target_os = "windows")]
- or_secondary(km.exec, "exec into a container - not available on Windows"),
- or_secondary(km.force_redraw, "force clear the screen & redraw the gui"),
- or_secondary(
- km.toggle_help,
- "toggle this help information - or click heading",
- ),
- or_secondary(km.save_logs, "save logs to file"),
- or_secondary(
- km.toggle_mouse_capture,
- "toggle mouse capture - if disabled, text on screen can be selected & copied",
- ),
- or_secondary(km.filter_mode, "enter filter mode"),
- or_secondary(km.log_search_mode, "enter log search mode"),
- or_secondary(km.sort_reset, "reset container sorting"),
- or_secondary(km.sort_by_name, "sort containers by name"),
- or_secondary(km.sort_by_state, "sort containers by state"),
- or_secondary(km.sort_by_status, "sort containers by status"),
- or_secondary(km.sort_by_cpu, "sort containers by cpu"),
- or_secondary(km.sort_by_memory, "sort containers by memory"),
- or_secondary(km.sort_by_id, "sort containers by id"),
- or_secondary(km.sort_by_image, "sort containers by image"),
- or_secondary(km.sort_by_rx, "sort containers by rx"),
- or_secondary(km.sort_by_tx, "sort containers by tx"),
- or_secondary(
- km.log_section_height_decrease,
- "decrease log section height",
- ),
- or_secondary(
- km.log_section_height_increase,
- "increase log section height",
- ),
- or_secondary(km.log_section_toggle, "toggle log section visibility"),
- or_secondary(km.clear, "close dialog"),
- or_secondary(km.quit, "quit at any time"),
- ];
-
- let mut lines = if show_timestamp {
- Vec::from([Self::custom_text(colors, km, zone), Self::empty_span()])
- } else {
- vec![]
- };
-
- lines.extend_from_slice(&descriptions);
- let width = Self::calc_width(&lines);
- let height = lines.len();
-
- Self {
- lines,
- width,
- height,
+ if let Some(path) = &config.dir_save {
+ entries.push(("export location: ", path.display().to_string()));
}
+ if config.show_timestamp {
+ let tz = config
+ .timezone
+ .as_ref()
+ .and_then(|t| t.iana_name())
+ .unwrap_or("Etc/UTC");
+ entries.push((" logs timezone: ", tz.to_string()));
+ }
+
+ let max_len = entries
+ .iter()
+ .map(|(_, val)| val.chars().count())
+ .max()
+ .unwrap_or_default();
+
+ // 3. Map entries to Lines
+ let mut lines = entries
+ .into_iter()
+ .map(|(label, val)| {
+ let spacing = " ".repeat(max_len.saturating_sub(val.chars().count()));
+ Line::from(vec![
+ Self::text_span(label.to_owned(), config.app_colors),
+ Self::highlighted_text_span(format!("{spacing}{val}"), config.app_colors),
+ ])
+ .right_aligned()
+ })
+ .collect::>();
+
+ lines.extend([
+ Self::empty_line(),
+ Line::from(Self::text_span(
+ "a work in progress, all and any input appreciated".to_owned(),
+ config.app_colors,
+ )),
+ Self::highlighted_text_span(REPO.to_owned(), config.app_colors)
+ .underlined()
+ .into_centered_line(),
+ ]);
+
+ let size = Self::calc_size(&lines);
+ Self { lines, size }
}
}
-/// Draw the help box in the centre of the screen
-pub fn draw(
- colors: AppColors,
+// Draw the oxker name on one half, other half shoe logs location, save location, timezone
+fn draw_top_section(
f: &mut Frame,
- keymap: &Keymap,
- show_timestamp: bool,
- zone: Option<&TimeZone>,
+ area: Rect,
+ colors: AppColors,
+ style: Style,
+ name: HelpInfo,
+ config_dir: HelpInfo,
) {
- let title = format!(" {VERSION} ");
-
- let name_info = HelpInfo::gen_name(colors);
- let description_info = HelpInfo::gen_description(colors);
- let final_info = HelpInfo::gen_final(colors);
-
- let button_info = if keymap == &Keymap::new() {
- HelpInfo::gen_keymap_info(colors, zone, show_timestamp)
- } else {
- HelpInfo::gen_custom_keymap_info(colors, keymap, zone, show_timestamp)
- };
-
- let max_line_width = [
- name_info.width,
- description_info.width,
- button_info.width,
- final_info.width,
- ]
- .into_iter()
- .max()
- .unwrap_or_default()
- + 2;
-
- let max_height =
- name_info.height + description_info.height + button_info.height + final_info.height + 2;
-
- let area = popup::draw(
- max_height,
- max_line_width,
- f.area(),
- BoxLocation::MiddleCentre,
- );
-
- let split_popup = Layout::default()
- .direction(Direction::Vertical)
- .constraints([
- Constraint::Max(name_info.height.try_into().unwrap_or_default()),
- Constraint::Max(description_info.height.try_into().unwrap_or_default()),
- Constraint::Max(button_info.height.try_into().unwrap_or_default()),
- Constraint::Min(final_info.height.try_into().unwrap_or_default()),
- ])
+ let horizontal_split = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(area);
- let name_paragraph = Paragraph::new(name_info.lines)
+ let name_paragraph = Paragraph::new(name.lines)
.style(
Style::default()
.bg(colors.popup_help.background)
@@ -412,71 +753,159 @@ pub fn draw(
)
.alignment(Alignment::Center);
- let style = || {
- Style::default()
- .bg(colors.popup_help.background)
- .fg(colors.popup_help.text)
+ let location_top_padding = name.size.height.saturating_sub(config_dir.size.height);
+
+ let right_vertical_split = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([
+ Constraint::Max(location_top_padding),
+ Constraint::Max(config_dir.size.height),
+ ])
+ .split(horizontal_split[1]);
+
+ let config_paragraph = Paragraph::new(config_dir.lines).style(style);
+
+ f.render_widget(name_paragraph, horizontal_split[0]);
+ f.render_widget(config_paragraph, right_vertical_split[1]);
+}
+
+fn draw_keymap_title(f: &mut Frame, area: Rect, title: HelpInfo) {
+ f.render_widget(Paragraph::new(title.lines), area);
+}
+
+fn draw_keymap(f: &mut Frame, area: Rect, style: Style, columns: (HelpInfo, HelpInfo)) {
+ // Calculate some padding
+ let horizontal_padding = area
+ .width
+ .saturating_sub(columns.0.size.width)
+ .saturating_sub(columns.1.size.width)
+ .saturating_div(2)
+ .saturating_sub(1);
+
+ let horizontal_split = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([
+ Constraint::Max(horizontal_padding),
+ Constraint::Max(columns.0.size.width),
+ Constraint::Max(2),
+ Constraint::Max(columns.1.size.width),
+ Constraint::Max(horizontal_padding),
+ ])
+ .split(area);
+
+ let left_column = Paragraph::new(columns.0.lines).style(style).left_aligned();
+ let right_column = Paragraph::new(columns.1.lines).style(style).left_aligned();
+ f.render_widget(left_column, horizontal_split[1]);
+ f.render_widget(right_column, horizontal_split[3]);
+}
+
+pub fn draw(config: &Config, f: &mut Frame) {
+ let default_colors = config.app_colors == AppColors::new();
+ let title = format!(" {VERSION} ");
+ let style = Style::default()
+ .bg(config.app_colors.popup_help.background)
+ .fg(config.app_colors.popup_help.text);
+
+ let name_info = if default_colors {
+ DEFAULT_NAME.clone()
+ } else {
+ HelpInfo::gen_name_description(config.app_colors)
};
- let description_paragraph = Paragraph::new(description_info.lines)
- .style(style())
- .alignment(Alignment::Center);
+ let locations = HelpInfo::gen_locations(config);
+ let keymap_title = HelpInfo::gen_keymap_title(style);
+ let keymap_columns = HelpInfo::gen_keymap(config);
- let help_paragraph = Paragraph::new(button_info.lines)
- .style(style())
- .alignment(Alignment::Left);
-
- let final_paragraph = Paragraph::new(final_info.lines)
- .style(style())
- .alignment(Alignment::Center);
+ let total_width = name_info
+ .size
+ .width
+ // Account for spacing between the two sections
+ .saturating_add(locations.size.width)
+ .saturating_add(2)
+ .max(
+ keymap_columns
+ .0
+ .size
+ .width
+ .saturating_add(keymap_columns.1.size.width)
+ // Account for the spacing spacing between each column
+ .saturating_add(2),
+ );
+ let top_height = name_info.size.height.max(locations.size.height);
+ let keymap_height = keymap_columns
+ .0
+ .size
+ .height
+ .max(keymap_columns.1.size.height);
+ let total_height = top_height
+ .saturating_add(keymap_title.size.height)
+ .saturating_add(keymap_height);
let block = Block::default()
.title(title)
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
- .border_style(
- Style::default()
- .fg(colors.popup_help.text)
- .bg(colors.popup_help.background),
- );
+ .border_style(style)
+ .style(style)
+ .padding(Padding::horizontal(1));
+
+ let area = popup::draw(
+ (total_height + 2).into(),
+ (total_width + 4).into(),
+ f.area(),
+ BoxLocation::MiddleCentre,
+ );
+
+ let inner_area = block.inner(area);
+
+ let vertical_split = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([
+ Constraint::Length(top_height),
+ Constraint::Length(keymap_title.size.height),
+ Constraint::Length(keymap_height),
+ ])
+ .split(inner_area);
- // Order is important here
f.render_widget(Clear, area);
- f.render_widget(name_paragraph, split_popup[0]);
- f.render_widget(description_paragraph, split_popup[1]);
- f.render_widget(help_paragraph, split_popup[2]);
- f.render_widget(final_paragraph, split_popup[3]);
f.render_widget(block, area);
+ draw_top_section(
+ f,
+ vertical_split[0],
+ config.app_colors,
+ style,
+ name_info,
+ locations,
+ );
+ draw_keymap_title(f, vertical_split[1], keymap_title);
+ draw_keymap(f, vertical_split[2], style, keymap_columns);
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::too_many_lines)]
mod tests {
+ use std::path::PathBuf;
+
use crate::config::{AppColors, Keymap};
use crossterm::event::{KeyCode, KeyModifiers};
use insta::assert_snapshot;
- use jiff::tz::TimeZone;
- use ratatui::style::{Color, Modifier};
+ use ratatui::style::Color;
use crate::ui::draw_blocks::tests::{get_result, test_setup};
#[test]
- /// This will cause issues once the version has more than the current 5 chars (0.5.0)
/// This test is incredibly annoying
/// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg);
fn test_draw_blocks_help() {
- let mut setup = test_setup(87, 39, true, true);
- let tz = setup.app_data.lock().config.timezone.clone();
+ let mut setup = test_setup(118, 25, true, true);
+ setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir"));
+ setup.app_data.lock().config.dir_config =
+ Some(PathBuf::from("/home/user/.config/oxker/config.toml"));
+ setup.app_data.lock().config.show_timestamp = true;
setup
.terminal
.draw(|f| {
- super::draw(
- AppColors::new(),
- f,
- &setup.app_data.lock().config.keymap,
- false,
- tz.as_ref(),
- );
+ super::draw(&setup.app_data.lock().config, f);
})
.unwrap();
@@ -485,100 +914,209 @@ mod tests {
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
- // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
- (0 | 38, _) | (0..=37, 0 | 86) => {
- assert_eq!(result_cell.bg, Color::Reset);
- assert_eq!(result_cell.fg, Color::Reset);
- }
- // Buttons
- (2..=10, 2..=84)
- | (12, 19..=66)
- | (14, 2..=10 | 13..=27)
- | (15, 2..=10 | 13..=21 | 24..=37)
- | (16 | 28 | 30, 2..=10)
- | (19..=26 | 29 | 31, 2..=8)
- | (17, 2..=11)
- | (18 | 27, 2..=12)
- | (24, 2..=9 | 12..=18) => {
- assert_eq!(result_cell.bg, Color::Magenta);
- assert_eq!(result_cell.fg, Color::White);
- }
- // The URL is white on yellow and underlined
- (34, 25..=60) => {
- assert_eq!(result_cell.bg, Color::Magenta);
- assert_eq!(result_cell.fg, Color::White);
- assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
- }
- // The rest is black on magenta
- _ => {
- assert_eq!(result_cell.bg, Color::Magenta);
- assert_eq!(result_cell.fg, Color::Black);
- }
- }
+ // The space around the popup
+ (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)),
+ // The borders
+ (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ // The oxker logo
+ // The description
+ (2..=10, 3..=58)|
+ // Config location
+ (5, 79..=114) |
+ // Export location
+ (6, 79..=114) |
+ // Timezone
+ (7, 79..=114) |
+ //url
+ (10, 69..=104) |
+ // Left column
+ (13..=22, 4..=24) |
+ // Right Column
+ (13..=21,59..=69)
+ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)),
+ _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ };
}
}
}
#[test]
- /// Test that the help panel gets drawn with custom colors
- /// This test is incredibly annoying
- /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg);
- fn test_draw_blocks_help_custom_colors() {
- let mut setup = test_setup(87, 39, true, true);
- let mut colors = AppColors::new();
- let tz = setup.app_data.lock().config.timezone.clone();
-
- colors.popup_help.background = Color::Black;
- colors.popup_help.text = Color::Red;
- colors.popup_help.text_highlight = Color::Yellow;
+ fn test_draw_blocks_help_no_config() {
+ let mut setup = test_setup(116, 25, true, true);
+ setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir"));
+ setup.app_data.lock().config.show_timestamp = true;
setup
.terminal
.draw(|f| {
- super::draw(
- colors,
- f,
- &setup.app_data.lock().config.keymap,
- false,
- tz.as_ref(),
- );
+ super::draw(&setup.app_data.lock().config, f);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
+
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
- // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
- (0 | 38, _) | (0..=37, 0 | 86) => {
- assert_eq!(result_cell.bg, Color::Reset);
- assert_eq!(result_cell.fg, Color::Reset);
- }
- // Buttons
- (2..=10, 2..=84)
- | (12, 19..=66)
- | (14, 2..=10 | 13..=27)
- | (15, 2..=10 | 13..=21 | 24..=37)
- | (16 | 28 | 30, 2..=10)
- | (19..=26 | 29 | 31, 2..=8)
- | (17, 2..=11)
- | (18 | 27, 2..=12)
- | (24, 2..=9 | 12..=18) => {
- assert_eq!(result_cell.bg, Color::Black);
- assert_eq!(result_cell.fg, Color::Yellow);
- }
- // The URL is white on yellow and underlined
- (34, 25..=60) => {
- assert_eq!(result_cell.bg, Color::Black);
- assert_eq!(result_cell.fg, Color::Yellow);
- assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
- }
- // The rest is black on magenta
- _ => {
- assert_eq!(result_cell.bg, Color::Black);
- assert_eq!(result_cell.fg, Color::Red);
- }
- }
+ // The space around the popup
+ (0|24, _) | (_, 0|115) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)),
+ // The borders
+ (1|23, 1..=23) | (_, 1|114) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ // The oxker logo
+ // The description
+ (2..=10, 3..=57)|
+ // Export location
+ (6, 104..=112) |
+ // Timezone
+ (7, 104..=112) |
+ //url
+ (10, 67..=102) |
+ // Left column
+ (13..=22, 3..=23) |
+ // Right Column
+ (13..=21,58..=68)
+ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)),
+ _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ };
+ }
+ }
+ }
+
+ #[test]
+ fn test_draw_blocks_help_no_save() {
+ let mut setup = test_setup(118, 25, true, true);
+ setup.app_data.lock().config.dir_config =
+ Some(PathBuf::from("/home/user/.config/oxker/config.toml"));
+ setup.app_data.lock().config.show_timestamp = true;
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(&setup.app_data.lock().config, f);
+ })
+ .unwrap();
+
+ assert_snapshot!(setup.terminal.backend());
+
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // The space around the popup
+ (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)),
+ // The borders
+ (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ // The oxker logo
+ // The description
+ (2..=10, 3..=58)|
+ // Config location
+ (6, 79..=114) |
+ // Timezone
+ (7, 79..=114) |
+ //url
+ (10, 69..=104) |
+ // Left column
+ (13..=22, 4..=24) |
+ // Right Column
+ (13..=21,59..=69)
+ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)),
+ _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ };
+ }
+ }
+ }
+
+ #[test]
+ fn test_draw_blocks_help_no_timezone() {
+ let mut setup = test_setup(118, 25, true, true);
+ setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir"));
+ setup.app_data.lock().config.dir_config =
+ Some(PathBuf::from("/home/user/.config/oxker/config.toml"));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(&setup.app_data.lock().config, f);
+ })
+ .unwrap();
+
+ assert_snapshot!(setup.terminal.backend());
+
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // The space around the popup
+ (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)),
+ // The borders
+ (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ // The oxker logo
+ // The description
+ (2..=10, 3..=58)|
+ // Config location
+ (6, 79..=114) |
+ // Export location
+ (7, 79..=114) |
+ //url
+ (10, 69..=104) |
+ // Left column
+ (13..=22, 4..=24) |
+ // Right Column
+ (13..=21,59..=69)
+ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::White)),
+ _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Magenta, Color::Black)),
+ };
+ }
+ }
+ }
+
+ #[test]
+ fn test_draw_blocks_help_custom_color() {
+ let mut setup = test_setup(118, 25, true, true);
+ setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir"));
+ setup.app_data.lock().config.dir_config =
+ Some(PathBuf::from("/home/user/.config/oxker/config.toml"));
+ setup.app_data.lock().config.show_timestamp = true;
+
+ let mut colors = AppColors::new();
+ colors.popup_help.background = Color::Black;
+ colors.popup_help.text = Color::Red;
+ colors.popup_help.text_highlight = Color::Yellow;
+
+ setup.app_data.lock().config.app_colors = colors;
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(&setup.app_data.lock().config, f);
+ })
+ .unwrap();
+
+ assert_snapshot!(setup.terminal.backend());
+
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ // The space around the popup
+ (0|24, _) | (_, 0|117) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Reset, Color::Reset)),
+ // The borders
+ (1|23, 1..=23) | (_, 1|116) => assert_eq!((result_cell.bg, result_cell.fg), (Color::Black, Color::Red)),
+ // The oxker logo
+ // The description
+ (2..=10, 3..=58)|
+ // Config location
+ (5, 79..=114) |
+ // Export location
+ (6, 79..=114) |
+ // Timezone
+ (7, 79..=114) |
+ //url
+ (10, 69..=104) |
+ // Left column
+ (13..=22, 4..=24) |
+ // Right Column
+ (13..=21,59..=69)
+ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Black, Color::Yellow)),
+ _ => assert_eq!((result_cell.bg, result_cell.fg), (Color::Black, Color::Red)),
+ };
}
}
}
@@ -586,27 +1124,33 @@ mod tests {
#[test]
/// Help panel will show custom keymap if in use, with one definition for each entry
fn test_draw_blocks_help_custom_keymap_one_definition() {
- let mut setup = test_setup(98, 50, true, true);
+ let mut setup = test_setup(118, 25, true, true);
- let input = Keymap {
+ setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir"));
+ setup.app_data.lock().config.dir_config =
+ Some(PathBuf::from("/home/user/.config/oxker/config.toml"));
+ setup.app_data.lock().config.show_timestamp = true;
+
+ let keymap = Keymap {
clear: (KeyCode::Char('a'), None),
delete_confirm: (KeyCode::Char('b'), None),
delete_deny: (KeyCode::Char('c'), None),
exec: (KeyCode::Char('d'), None),
- filter_mode: (KeyCode::Char('e'), None),
- log_search_mode: (KeyCode::Char('7'), None),
- force_redraw: (KeyCode::Char('f'), None),
- log_scroll_back: (KeyCode::Char('g'), None),
- log_scroll_forward: (KeyCode::Char('h'), None),
- log_section_height_decrease: (KeyCode::Char('i'), None),
- log_section_height_increase: (KeyCode::Char('j'), None),
- log_section_toggle: (KeyCode::Char('k'), None),
- quit: (KeyCode::Char('l'), None),
- save_logs: (KeyCode::Char('m'), None),
- scroll_down: (KeyCode::Char('o'), None),
- scroll_end: (KeyCode::Char('p'), None),
+ inspect: (KeyCode::Char('e'), None),
+ filter_mode: (KeyCode::Char('f'), None),
+ log_search_mode: (KeyCode::Char('g'), None),
+ force_redraw: (KeyCode::Char('h'), None),
+ scroll_back: (KeyCode::Char('i'), None),
+ scroll_forward: (KeyCode::Char('j'), None),
+ log_section_height_decrease: (KeyCode::Char('k'), None),
+ log_section_height_increase: (KeyCode::Char('l'), None),
+ log_section_toggle: (KeyCode::Char('m'), None),
+ quit: (KeyCode::Char('n'), None),
+ save_logs: (KeyCode::Char('o'), None),
+ scroll_down: (KeyCode::Char('p'), None),
+ scroll_end: (KeyCode::Char('q'), None),
scroll_many: KeyModifiers::ALT,
- scroll_start: (KeyCode::Char('q'), None),
+ scroll_start: (KeyCode::Char('r'), None),
scroll_up: (KeyCode::Char('s'), None),
select_next_panel: (KeyCode::Char('t'), None),
select_previous_panel: (KeyCode::Char('u'), None),
@@ -624,10 +1168,12 @@ mod tests {
toggle_mouse_capture: (KeyCode::Char('6'), None),
};
+ setup.app_data.lock().config.keymap = keymap;
+
setup
.terminal
.draw(|f| {
- super::draw(AppColors::new(), f, &input, false, None);
+ super::draw(&setup.app_data.lock().config, f);
})
.unwrap();
@@ -635,50 +1181,58 @@ mod tests {
}
#[test]
- /// Help panel will show custom keymap if in use, with two definition for each entry
- fn test_draw_blocks_help_custom_keymap_two_definitions() {
- let mut setup = test_setup(110, 50, true, true);
+ /// Help panel will show custom keymap if in use, with two definitions for each entry
+ fn test_draw_blocks_help_custom_keymap_two_definition() {
+ let mut setup = test_setup(124, 30, true, true);
+
+ setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir"));
+ setup.app_data.lock().config.dir_config =
+ Some(PathBuf::from("/home/user/.config/oxker/config.toml"));
+ setup.app_data.lock().config.show_timestamp = true;
let keymap = Keymap {
- clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))),
- delete_confirm: (KeyCode::Char('b'), Some(KeyCode::Char('B'))),
- delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))),
- exec: (KeyCode::Char('d'), Some(KeyCode::Char('D'))),
- filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))),
- log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('M'))),
- force_redraw: (KeyCode::Char('f'), Some(KeyCode::Char('F'))),
- log_scroll_back: (KeyCode::Char('f'), Some(KeyCode::Char('F'))),
- log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))),
- log_section_height_decrease: (KeyCode::Char('h'), Some(KeyCode::Char('H'))),
- log_section_height_increase: (KeyCode::Char('i'), Some(KeyCode::Char('I'))),
- log_section_toggle: (KeyCode::Char('j'), Some(KeyCode::Char('J'))),
- quit: (KeyCode::Char('k'), Some(KeyCode::Char('K'))),
- save_logs: (KeyCode::Char('l'), Some(KeyCode::Char('L'))),
- scroll_down: (KeyCode::Char('n'), Some(KeyCode::Char('N'))),
- scroll_end: (KeyCode::Char('o'), Some(KeyCode::Char('O'))),
+ clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
+ delete_confirm: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
+ delete_deny: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
+ exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
+ inspect: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
+ filter_mode: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
+ log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
+ force_redraw: (KeyCode::Char('o'), Some(KeyCode::Char('p'))),
+ scroll_back: (KeyCode::Char('q'), Some(KeyCode::Char('r'))),
+ scroll_forward: (KeyCode::Char('s'), Some(KeyCode::Char('t'))),
+ log_section_height_decrease: (KeyCode::Char('u'), Some(KeyCode::Char('v'))),
+ log_section_height_increase: (KeyCode::Char('w'), Some(KeyCode::Char('x'))),
+ log_section_toggle: (KeyCode::Char('y'), Some(KeyCode::Char('z'))),
+ quit: (KeyCode::Char('0'), Some(KeyCode::Char('1'))),
+ save_logs: (KeyCode::Char('2'), Some(KeyCode::Char('3'))),
+ scroll_down: (KeyCode::Char('4'), Some(KeyCode::Char('5'))),
+ scroll_end: (KeyCode::Char('6'), Some(KeyCode::Char('7'))),
scroll_many: KeyModifiers::ALT,
- scroll_start: (KeyCode::Char('p'), Some(KeyCode::Char('P'))),
- scroll_up: (KeyCode::Char('r'), Some(KeyCode::Char('R'))),
- select_next_panel: (KeyCode::Char('s'), Some(KeyCode::Char('S'))),
- select_previous_panel: (KeyCode::Char('t'), Some(KeyCode::Char('T'))),
- sort_by_cpu: (KeyCode::Char('u'), Some(KeyCode::Char('U'))),
- sort_by_id: (KeyCode::Char('v'), Some(KeyCode::Char('V'))),
- sort_by_image: (KeyCode::Char('w'), Some(KeyCode::Char('W'))),
- sort_by_memory: (KeyCode::Char('x'), Some(KeyCode::Char('X'))),
- sort_by_name: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))),
- sort_by_rx: (KeyCode::Char('z'), Some(KeyCode::Char('Z'))),
- sort_by_state: (KeyCode::Char('0'), Some(KeyCode::Char('9'))),
- sort_by_status: (KeyCode::Char('1'), Some(KeyCode::Char('8'))),
- sort_by_tx: (KeyCode::Char('2'), Some(KeyCode::Char('7'))),
- sort_reset: (KeyCode::Char('3'), Some(KeyCode::Char('6'))),
- toggle_help: (KeyCode::Char('4'), Some(KeyCode::Char('5'))),
- toggle_mouse_capture: (KeyCode::Char('5'), Some(KeyCode::PageDown)),
+ scroll_start: (KeyCode::Char('8'), Some(KeyCode::Char('9'))),
+ scroll_up: (KeyCode::CapsLock, Some(KeyCode::ScrollLock)),
+ select_next_panel: (KeyCode::PrintScreen, Some(KeyCode::Right)),
+ select_previous_panel: (KeyCode::Left, Some(KeyCode::Up)),
+ sort_by_cpu: (KeyCode::Down, Some(KeyCode::Delete)),
+ sort_by_id: (KeyCode::BackTab, Some(KeyCode::Backspace)),
+ sort_by_image: (KeyCode::End, Some(KeyCode::Esc)),
+ sort_by_memory: (KeyCode::Home, Some(KeyCode::Insert)),
+ sort_by_name: (KeyCode::KeypadBegin, Some(KeyCode::Menu)),
+ sort_by_rx: (KeyCode::NumLock, Some(KeyCode::PageDown)),
+ sort_by_state: (KeyCode::PageUp, Some(KeyCode::Pause)),
+ sort_by_status: (KeyCode::PrintScreen, Some(KeyCode::Tab)),
+ sort_by_tx: (KeyCode::F(1), Some(KeyCode::F(2))),
+ sort_reset: (KeyCode::F(3), Some(KeyCode::F(4))),
+ toggle_help: (KeyCode::F(5), Some(KeyCode::F(6))),
+ toggle_mouse_capture: (KeyCode::F(7), Some(KeyCode::F(8))),
};
+ setup.app_data.lock().config.keymap = keymap;
+
setup
.terminal
.draw(|f| {
- super::draw(AppColors::new(), f, &keymap, false, None);
+ super::draw(&setup.app_data.lock().config, f);
})
.unwrap();
@@ -686,90 +1240,61 @@ mod tests {
}
#[test]
- /// Help panel will show custom keymap if in use, with either one or two definition for each entry
- #[allow(clippy::todo)]
- fn test_draw_blocks_help_one_and_two_definitions() {
- let mut setup = test_setup(110, 50, true, true);
+ /// Help panel will show custom keymap if in use, with one or two definitions for each entry
+ fn test_draw_blocks_help_custom_keymap_one_two_definition() {
+ let mut setup = test_setup(124, 30, true, true);
+
+ setup.app_data.lock().config.dir_save = Some(PathBuf::from("/test_dir"));
+ setup.app_data.lock().config.dir_config =
+ Some(PathBuf::from("/home/user/.config/oxker/config.toml"));
+ setup.app_data.lock().config.show_timestamp = true;
let keymap = Keymap {
- clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))),
- delete_confirm: (KeyCode::Char('b'), None),
- delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))),
- exec: (KeyCode::Char('d'), None),
- filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))),
- log_search_mode: (KeyCode::Char('8'), None),
- force_redraw: (KeyCode::Char('f'), None),
- log_scroll_back: (KeyCode::Char('g'), Some(KeyCode::Char('G'))),
- log_scroll_forward: (KeyCode::Char('h'), None),
- log_section_height_decrease: (KeyCode::Char('i'), Some(KeyCode::Char('I'))),
- log_section_height_increase: (KeyCode::Char('j'), None),
- log_section_toggle: (KeyCode::Char('k'), Some(KeyCode::Char('K'))),
- quit: (KeyCode::Char('l'), None),
- save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('M'))),
- scroll_down: (KeyCode::Char('o'), Some(KeyCode::Char('O'))),
- scroll_end: (KeyCode::Char('p'), None),
+ clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
+ delete_confirm: (KeyCode::Char('c'), None),
+ delete_deny: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
+ exec: (KeyCode::Char('g'), None),
+ inspect: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
+ filter_mode: (KeyCode::Char('k'), None),
+ log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
+ force_redraw: (KeyCode::Char('o'), None),
+ scroll_back: (KeyCode::Char('q'), Some(KeyCode::Char('r'))),
+ scroll_forward: (KeyCode::Char('s'), None),
+ log_section_height_decrease: (KeyCode::Char('u'), Some(KeyCode::Char('v'))),
+ log_section_height_increase: (KeyCode::Char('w'), None),
+ log_section_toggle: (KeyCode::Char('y'), Some(KeyCode::Char('z'))),
+ quit: (KeyCode::Char('0'), None),
+ save_logs: (KeyCode::Char('2'), Some(KeyCode::Char('3'))),
+ scroll_down: (KeyCode::Char('4'), None),
+ scroll_end: (KeyCode::Char('6'), Some(KeyCode::Char('7'))),
scroll_many: KeyModifiers::ALT,
- scroll_start: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))),
- scroll_up: (KeyCode::Char('s'), Some(KeyCode::Char('S'))),
- select_next_panel: (KeyCode::Char('t'), None),
- select_previous_panel: (KeyCode::Char('u'), Some(KeyCode::Char('U'))),
- sort_by_cpu: (KeyCode::Char('v'), None),
- sort_by_id: (KeyCode::Char('w'), Some(KeyCode::Char('W'))),
- sort_by_image: (KeyCode::Char('x'), None),
- sort_by_memory: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))),
- sort_by_name: (KeyCode::Char('z'), None),
- sort_by_rx: (KeyCode::Char('0'), Some(KeyCode::Char('9'))),
- sort_by_state: (KeyCode::Char('1'), None),
- sort_by_status: (KeyCode::Char('2'), Some(KeyCode::Char('7'))),
- sort_by_tx: (KeyCode::Char('3'), None),
- sort_reset: (KeyCode::Char('4'), Some(KeyCode::Char('5'))),
- toggle_help: (KeyCode::Char('5'), None),
- toggle_mouse_capture: (KeyCode::Char('6'), Some(KeyCode::Char('#'))),
+ scroll_start: (KeyCode::Char('8'), None),
+ scroll_up: (KeyCode::CapsLock, Some(KeyCode::ScrollLock)),
+ select_next_panel: (KeyCode::PrintScreen, None),
+ select_previous_panel: (KeyCode::Left, Some(KeyCode::Up)),
+ sort_by_cpu: (KeyCode::Down, None),
+ sort_by_id: (KeyCode::BackTab, None),
+ sort_by_image: (KeyCode::End, Some(KeyCode::Esc)),
+ sort_by_memory: (KeyCode::Home, None),
+ sort_by_name: (KeyCode::KeypadBegin, Some(KeyCode::Menu)),
+ sort_by_rx: (KeyCode::NumLock, None),
+ sort_by_state: (KeyCode::PageUp, Some(KeyCode::Pause)),
+ sort_by_status: (KeyCode::PrintScreen, None),
+ sort_by_tx: (KeyCode::F(1), Some(KeyCode::F(2))),
+ sort_reset: (KeyCode::F(3), None),
+ toggle_help: (KeyCode::F(5), Some(KeyCode::F(6))),
+ toggle_mouse_capture: (KeyCode::F(7), None),
};
- let tz = setup.app_data.lock().config.timezone.clone();
+ setup.app_data.lock().config.keymap = keymap;
setup
.terminal
.draw(|f| {
- super::draw(AppColors::new(), f, &keymap, false, tz.as_ref());
+ super::draw(&setup.app_data.lock().config, f);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
}
-
- #[test]
- fn test_draw_blocks_help_show_timezone() {
- let mut setup = test_setup(87, 39, true, true);
-
- setup
- .terminal
- .draw(|f| {
- super::draw(
- AppColors::new(),
- f,
- &Keymap::new(),
- true,
- Some(&TimeZone::get("asia/tokyo").unwrap()),
- );
- })
- .unwrap();
-
- assert_snapshot!(setup.terminal.backend());
-
- for (row_index, result_row) in get_result(&setup) {
- for (result_cell_index, result_cell) in result_row.iter().enumerate() {
- match (row_index, result_cell_index) {
- (13, 31..=45) => {
- assert_eq!(result_cell.fg, AppColors::new().popup_help.text);
- }
- (13, 46..=55) => {
- assert_eq!(result_cell.fg, AppColors::new().popup_help.text_highlight);
- }
- _ => (),
- }
- }
- }
- }
}
diff --git a/src/ui/draw_blocks/inspect.rs b/src/ui/draw_blocks/inspect.rs
new file mode 100644
index 0000000..3c5917d
--- /dev/null
+++ b/src/ui/draw_blocks/inspect.rs
@@ -0,0 +1,777 @@
+use std::sync::Arc;
+
+use parking_lot::Mutex;
+use ratatui::{
+ Frame,
+ layout::Rect,
+ style::{Style, Stylize},
+ text::Line,
+ widgets::{Block, Borders, Paragraph, Wrap},
+};
+
+use crate::{
+ app_data::InspectData,
+ config::{AppColors, Keymap},
+ ui::{
+ GuiState,
+ draw_blocks::{DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, UP_ARROW},
+ gui_state::ScrollOffset,
+ },
+};
+
+/// Create a bordered block with a title.
+fn title_block<'a>(upper_title: &'a str, lower_title: &'a str, colors: &AppColors) -> Block<'a> {
+ Block::default()
+ .borders(Borders::all())
+ .border_type(ratatui::widgets::BorderType::Rounded)
+ .border_style(Style::default().fg(colors.borders.selected))
+ .title(upper_title.bold().into_centered_line())
+ .title_bottom(lower_title.bold().into_centered_line())
+}
+
+/// Create the upper title, with container name, id, and keymap to clear
+fn generate_upper_title(data: &InspectData, keymap: &Keymap) -> String {
+ let mut output = String::from(" inspecting: ");
+ let name = if data.name.starts_with("/") {
+ data.name.replacen('/', "", 1)
+ } else {
+ data.name.clone()
+ };
+
+ output.push_str(&format!("{} {} ", name, data.id.get_short()));
+ let mut inspect_key = keymap.inspect.0.to_string();
+ if let Some(x) = keymap.inspect.1 {
+ inspect_key.push_str(&format!(" or {x}"));
+ }
+ let mut clear_key = keymap.clear.0.to_string();
+ if let Some(x) = keymap.clear.1 {
+ clear_key.push_str(&format!(" or {x}"));
+ }
+ output.push_str(&format!(" - {clear_key} or {inspect_key} to exit"));
+ output.push(' ');
+ output
+}
+
+/// Generate the lower title, with the current scroll and the scrolling limits
+fn generate_lower_title(length: usize, width: usize, offset: ScrollOffset) -> String {
+ let length_width = length
+ .to_string()
+ .chars()
+ .count()
+ .max(offset.y.to_string().chars().count());
+ let width_width = width
+ .to_string()
+ .chars()
+ .count()
+ .max(offset.x.to_string().chars().count());
+
+ let left_arrow = if offset.x == 0 { " " } else { LEFT_ARROW };
+ let right_arrow = if offset.x == width { " " } else { RIGHT_ARROW };
+ let up_arrow = if offset.y == 0 { " " } else { UP_ARROW };
+ let down_arrow = if offset.y == length { " " } else { DOWN_ARROW };
+
+ format!(
+ " {up_arrow} {:>length_width$}/{:>length_width$} {down_arrow} {left_arrow} {:>width_width$}/{:>width_width$} {right_arrow} ",
+ offset.y, length, offset.x, width
+ )
+}
+
+/// Generate the Lines, remove lines & chars based on the offset and viewport
+fn gen_lines<'a>(data_as_str: &'a str, offset: &ScrollOffset, rect: &Rect) -> Vec> {
+ let first_line_index = offset.y.max(0);
+ let first_char_index = offset.x.max(0);
+ let last_char_index = usize::from(rect.width.saturating_sub(2));
+ let take_lines = usize::from(rect.height);
+ //todo see if log scrolling does this - What?
+
+ data_as_str
+ .lines()
+ .skip(first_line_index)
+ .take(take_lines)
+ .map(|line| {
+ Line::from(
+ line.chars()
+ .skip(first_char_index)
+ .take(last_char_index)
+ .collect::(),
+ )
+ })
+ .collect()
+}
+
+/// Draw the InspectContainer widget to the entire screen
+pub fn draw(
+ f: &mut Frame,
+ colors: AppColors,
+ data: InspectData,
+ gui_state: &Arc>,
+ keymap: &Keymap,
+) {
+ let rect = f.area();
+ let offset = gui_state.lock().get_inspect_offset();
+ // +2 to account for the border
+ let height = data
+ .height
+ .saturating_sub(usize::from(rect.height))
+ .saturating_add(2);
+ let width = data
+ .width
+ .saturating_sub(usize::from(rect.width))
+ .saturating_add(2);
+ let upper_title = generate_upper_title(&data, keymap);
+ let lower_title = generate_lower_title(height, width, offset);
+
+ gui_state.lock().set_inspect_offset_max(ScrollOffset {
+ x: width,
+ y: height,
+ });
+
+ let paragraph = Paragraph::new(gen_lines(&data.as_string, &offset, &rect))
+ .block(title_block(&upper_title, &lower_title, &colors))
+ .gray()
+ .left_aligned()
+ .wrap(Wrap { trim: false });
+ f.render_widget(paragraph, rect);
+}
+
+#[cfg(test)]
+#[allow(clippy::unwrap_used)]
+mod tests {
+ use std::{collections::HashMap, sync::LazyLock};
+
+ use crate::{
+ app_data::InspectData,
+ config::{AppColors, Keymap},
+ ui::draw_blocks::tests::{get_result, test_setup},
+ };
+ use bollard::secret::{
+ ContainerConfig, ContainerInspectResponse, ContainerState, ContainerStateStatusEnum,
+ DriverData, EndpointSettings, HostConfig, HostConfigLogConfig, MountPoint,
+ MountPointTypeEnum, NetworkSettings, RestartPolicy, RestartPolicyNameEnum,
+ };
+ use crossterm::event::KeyCode;
+ use insta::assert_snapshot;
+ use ratatui::style::Color;
+
+ static INSPECT_DATA: LazyLock =
+ LazyLock::new(|| InspectData::from(gen_container_inspect_response()));
+
+ #[test]
+ /// Test a inspect container with default settings, keymap, and position
+ fn test_draw_blocks_inspect_default_valid() {
+ let mut setup = test_setup(100, 50, true, true);
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &Keymap::new(),
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ // Assert border colors
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test a inspect container with custom colors
+ fn test_draw_blocks_inspect_custom_color() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ let mut colors = AppColors::new();
+ colors.borders.selected = Color::Red;
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ colors,
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &Keymap::new(),
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ // Assert custom border colors
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Red);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test a inspect container with custom keymap for one clear key
+ fn test_draw_blocks_inspect_custom_keymap_clear_one() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ let mut keymap = Keymap::new();
+
+ keymap.clear.0 = KeyCode::Char('F');
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &keymap,
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ // Assert border colors
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test a inspect container with custom keymap for both clear keys
+ fn test_draw_blocks_inspect_custom_keymap_clear_two() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ let mut keymap = Keymap::new();
+
+ keymap.clear.0 = KeyCode::Char('F');
+ keymap.clear.1 = Some(KeyCode::Char('Z'));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &keymap,
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ // Assert border colors
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test a inspect container with custom keymap for one inspect key
+ fn test_draw_blocks_inspect_custom_keymap_inspect_one() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ let mut keymap = Keymap::new();
+
+ keymap.inspect.0 = KeyCode::Char('4');
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &keymap,
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ // Assert border colors
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test a inspect container with custom keymap for both inspect keys
+ fn test_draw_blocks_inspect_custom_keymap_inspect_two() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ let mut keymap = Keymap::new();
+
+ keymap.inspect.0 = KeyCode::Char('4');
+ keymap.inspect.1 = Some(KeyCode::Char('5'));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &keymap,
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ // Assert border colors
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Test a inspect container with all custom keymaps
+ fn test_draw_blocks_inspect_custom_keymap_all() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ let mut keymap = Keymap::new();
+
+ keymap.clear.0 = KeyCode::Char('F');
+ keymap.clear.1 = Some(KeyCode::Char('Z'));
+ keymap.inspect.0 = KeyCode::Char('4');
+ keymap.inspect.1 = Some(KeyCode::Char('5'));
+
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &keymap,
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ // Assert border colors
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Inspect details are offset 10 in x and y axis
+ fn test_draw_blocks_inspect_offset() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ // Why does one need to draw first, although it *should* be impossible to scroll before an initial drawing
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &Keymap::new(),
+ );
+ })
+ .unwrap();
+
+ {
+ let mut gui_state = setup.gui_state.lock();
+ for _ in 0..=9 {
+ gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Down);
+ gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Right);
+ }
+ }
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &Keymap::new(),
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ /// Inspect details are offset to the maximum allowed
+ fn test_draw_blocks_inspect_offset_max() {
+ let mut setup = test_setup(100, 50, true, true);
+
+ // Why does one need to draw first, although it *should* be impossible to scroll before an initial drawing
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &Keymap::new(),
+ );
+ })
+ .unwrap();
+
+ // Lazy way of getting the max offset
+ {
+ let mut gui_state = setup.gui_state.lock();
+ for _ in 0..=1000 {
+ gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Down);
+ gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Right);
+ }
+ }
+ setup
+ .terminal
+ .draw(|f| {
+ super::draw(
+ f,
+ AppColors::new(),
+ INSPECT_DATA.clone(),
+ &setup.gui_state,
+ &Keymap::new(),
+ );
+ })
+ .unwrap();
+ assert_snapshot!(setup.terminal.backend());
+
+ for (row_index, result_row) in get_result(&setup) {
+ for (result_cell_index, result_cell) in result_row.iter().enumerate() {
+ match (row_index, result_cell_index) {
+ (0 | 49, _) | (_, 0 | 99) => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::LightCyan);
+ }
+ _ => {
+ assert_eq!(result_cell.bg, Color::Reset);
+ assert_eq!(result_cell.fg, Color::Gray);
+ }
+ }
+ }
+ }
+ }
+
+ fn gen_container_inspect_response() -> ContainerInspectResponse {
+ ContainerInspectResponse {
+ id: Some("0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7".to_owned()),
+ created: Some("2026-01-23T22:20:19.927967311Z".to_owned()),
+ path: Some("docker-entrypoint.sh".to_owned()),
+ args: Some(vec!["postgres".to_owned()]),
+ state: Some(ContainerState {
+ status: Some(ContainerStateStatusEnum::RUNNING),
+ running: Some(true),
+ paused: Some(false),
+ restarting: Some(false),
+ oom_killed: Some(false),
+ dead: Some(false),
+ pid: Some(782),
+ exit_code: Some(0),
+ error: Some("".to_owned()),
+ started_at: Some("2026-01-30T08:09:01.574885915Z".to_owned()),
+ finished_at: Some("2026-01-30T08:09:01.180567927Z".to_owned()),
+ health: None,
+ }),
+ image: Some("sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352".to_owned()),
+ resolv_conf_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/resolv.conf".to_owned()),
+ hostname_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/hostname".to_owned()),
+ hosts_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/hosts".to_owned()),
+ log_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7-json.log".to_owned()),
+ name: Some("/postgres".to_owned()),
+ restart_count: Some(0),
+ driver: Some("overlay2".to_owned()),
+ platform: Some("linux".to_owned()),
+ image_manifest_descriptor: None,
+ mount_label: Some("".to_owned()),
+ process_label: Some("".to_owned()),
+ app_armor_profile: Some("".to_owned()),
+ exec_ids: None,
+ host_config: Some(HostConfig {
+ cpu_shares: Some(0),
+ memory: Some(1073741824),
+ cgroup_parent: Some("".to_owned()),
+ blkio_weight: Some(0),
+ blkio_weight_device: None,
+ blkio_device_read_bps: None,
+ blkio_device_write_bps: None,
+ blkio_device_read_iops: None,
+ blkio_device_write_iops: None,
+ cpu_period: Some(0),
+ cpu_quota: Some(0),
+ cpu_realtime_period: Some(0),
+ cpu_realtime_runtime: Some(0),
+ cpuset_cpus: Some("".to_owned()),
+ cpuset_mems: Some("".to_owned()),
+ devices: None,
+ device_cgroup_rules: None,
+ device_requests: None,
+ memory_reservation: Some(0),
+ memory_swap: Some(2147483648),
+ memory_swappiness: None,
+ nano_cpus: Some(0),
+ oom_kill_disable: Some(false),
+ init: None,
+ pids_limit: None,
+ ulimits: None,
+ cpu_count: Some(0),
+ cpu_percent: Some(0),
+ io_maximum_iops: Some(0),
+ io_maximum_bandwidth: Some(0),
+ binds: None,
+ container_id_file: Some("".to_owned()),
+ log_config: Some(HostConfigLogConfig {
+ typ: Some("json-file".to_owned()),
+ config: Some(HashMap::new()),
+ }),
+ network_mode: Some("oxker-examaple-net".to_owned()),
+ port_bindings: Some(HashMap::new()),
+ restart_policy: Some(RestartPolicy {
+ name: Some(RestartPolicyNameEnum::ALWAYS),
+ maximum_retry_count: Some(0),
+ }),
+ auto_remove: Some(false),
+ volume_driver: Some("".to_owned()),
+ volumes_from: None,
+ mounts: None,
+ console_size: Some(vec![0, 0]),
+ annotations: None,
+ cap_add: None,
+ cap_drop: None,
+ cgroupns_mode: Some(bollard::secret::HostConfigCgroupnsModeEnum::HOST),
+ dns: Some(vec![]),
+ dns_options: Some(vec![]),
+ dns_search: Some(vec![]),
+ extra_hosts: Some(vec![]),
+ group_add: None,
+ ipc_mode: Some("private".to_owned()),
+ cgroup: Some("".to_owned()),
+ links: None,
+ oom_score_adj: Some(0),
+ pid_mode: Some("".to_owned()),
+ privileged: Some(false),
+ publish_all_ports: Some(false),
+ readonly_rootfs: Some(false),
+ security_opt: None,
+ storage_opt: None,
+ tmpfs: None,
+ uts_mode: Some("".to_owned()),
+ userns_mode: Some("".to_owned()),
+ shm_size: Some(268435456),
+ sysctls: None,
+ runtime: Some("runc".to_owned()),
+ isolation: Some(bollard::secret::HostConfigIsolationEnum::EMPTY),
+ masked_paths: Some(vec![
+ "/proc/acpi".to_owned(),
+ "/proc/asound".to_owned(),
+ "/proc/interrupts".to_owned(),
+ "/proc/kcore".to_owned(),
+ "/proc/keys".to_owned(),
+ "/proc/latency_stats".to_owned(),
+ "/proc/sched_debug".to_owned(),
+ "/proc/scsi".to_owned(),
+ "/proc/timer_list".to_owned(),
+ "/proc/timer_stats".to_owned(),
+ "/sys/devices/virtual/powercap".to_owned(),
+ "/sys/firmware".to_owned(),
+ ]),
+ readonly_paths: Some(vec![
+ "/proc/bus".to_owned(),
+ "/proc/fs".to_owned(),
+ "/proc/irq".to_owned(),
+ "/proc/sys".to_owned(),
+ "/proc/sysrq-trigger".to_owned(),
+ ]),
+ }),
+ graph_driver: Some(DriverData {
+ name: "overlay2".to_owned(),
+ data: HashMap::from([
+ ("LowerDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea-init/diff:/var/lib/docker/overlay2/51b93846f7ba3e00cb1ed86564e3e1d7c30df2bb1cd5a8469d54625f1e5a2eca/diff:/var/lib/docker/overlay2/c1364ead843d3af87ce286013b6301329d3089422b22b001e156e45d29b5b4dd/diff:/var/lib/docker/overlay2/0e6dc322cad77b1db3906a3a4e5e6d6b80fbffd138437e550d8849fcf4f4c1f2/diff:/var/lib/docker/overlay2/cc0f967a7471cf06e0c9ad3d474650c668a4cf0c02efe20e9c250c436f93033b/diff:/var/lib/docker/overlay2/5c59e0919969987c96a5d0e0a512a0a1a0f67ea747596af9a9c14a9566198d91/diff:/var/lib/docker/overlay2/d7709b7685c9704e1e392c515b6155517270541f6ccde426ef784403e1681fca/diff:/var/lib/docker/overlay2/c891528563fff91bffaf07416e77bcd3bdebb03e5d32ed0e3d4ee1ec5e80e880/diff:/var/lib/docker/overlay2/2b25c179a432c35cc599a082cd709c8c9a1523f8d1959f72fda21fc76e50ad00/diff:/var/lib/docker/overlay2/3b409d2f7a2455578148892302823a7f03c7c36482d08bb68fd6c1aeeec05f05/diff:/var/lib/docker/overlay2/55dbb2fab0ae8bb3bfe8183093cdd576686f7333e2b2c41e6e4178a7b6407554/diff".to_owned()),
+ ("MergedDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/merged".to_owned()),
+ ("WorkDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/work".to_owned()),
+ ("ID".to_owned(), "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7".to_owned()),
+ ("UpperDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/diff".to_owned()),
+ ]),
+ }),
+ storage: None,
+ size_rw: None,
+ size_root_fs: None,
+ mounts: Some(vec![MountPoint {
+ typ: Some(MountPointTypeEnum::VOLUME),
+ name: Some("93bc4e4c8d3823964b58105a99a7b3a7e02c801d5560338bdaf7589966a1b02d".to_owned()),
+ source: Some("/var/lib/docker/volumes/93bc4e4c8d3823964b58105a99a7b3a7e02c801d5560338bdaf7589966a1b02d/_data".to_owned()),
+ destination: Some("/var/lib/postgresql/data".to_owned()),
+ driver: Some("local".to_owned()),
+ mode: Some("".to_owned()),
+ rw: Some(true),
+ propagation: Some("".to_owned()),
+ }]),
+ config: Some(ContainerConfig {
+ hostname: Some("0bdea64212f9".to_owned()),
+ domainname: Some("".to_owned()),
+ user: Some("".to_owned()),
+ attach_stdin: Some(false),
+ attach_stdout: Some(true),
+ attach_stderr: Some(true),
+ exposed_ports: Some(vec!["5432/tcp".to_owned()]),
+ tty: Some(false),
+ open_stdin: Some(false),
+ stdin_once: Some(false),
+ env: Some(vec![
+ "POSTGRES_PASSWORD=never_use_this_password_in_production".to_owned(),
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_owned(),
+ "GOSU_VERSION=1.19".to_owned(),
+ "LANG=en_US.utf8".to_owned(),
+ "PG_MAJOR=17".to_owned(),
+ "PG_VERSION=17.7".to_owned(),
+ "PG_SHA256=ef9e343302eccd33112f1b2f0247be493cb5768313adeb558b02de8797a2e9b5".to_owned(),
+ "DOCKER_PG_LLVM_DEPS=llvm19-dev \t\tclang19".to_owned(),
+ "PGDATA=/var/lib/postgresql/data".to_owned(),
+ ]),
+ cmd: Some(vec!["postgres".to_owned()]),
+ healthcheck: None,
+ args_escaped: None,
+ image: Some("postgres:17-alpine".to_owned()),
+ volumes: Some(vec!["/var/lib/postgresql/data".to_owned()]),
+ working_dir: Some("/".to_owned()),
+ entrypoint: Some(vec!["docker-entrypoint.sh".to_owned()]),
+ network_disabled: None,
+ on_build: None,
+ labels: Some(HashMap::from([
+ ("com.docker.compose.oneoff".to_owned(), "False".to_owned()),
+ ("com.docker.compose.project.config_files".to_owned(), "/workspaces/oxker/docker/docker-compose.yml".to_owned()),
+ ("com.docker.compose.image".to_owned(), "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352".to_owned()),
+ ("com.docker.compose.project.working_dir".to_owned(), "/workspaces/oxker/docker".to_owned()),
+ ("com.docker.compose.service".to_owned(), "postgres".to_owned()),
+ ("com.docker.compose.config-hash".to_owned(), "e06d69ffb3f9b69dd51b356b60c2297df57caf0da16792ccafaabffdb920e443".to_owned()),
+ ("com.docker.compose.depends_on".to_owned(), "".to_owned()),
+ ("com.docker.compose.container-number".to_owned(), "1".to_owned()),
+ ("com.docker.compose.version".to_owned(), "2.40.3".to_owned()),
+ ("com.docker.compose.project".to_owned(), "docker".to_owned()),
+ ])),
+ stop_signal: Some("SIGINT".to_owned()),
+ stop_timeout: None,
+ shell: None,
+ }),
+ network_settings: Some(NetworkSettings {
+ sandbox_id: Some("dab64a66594dd8d06478184e2928c81acdcd9c931f643bd5ca62b7edb6345f8d".to_owned()),
+ sandbox_key: Some("/var/run/docker/netns/dab64a66594d".to_owned()),
+ ports: Some(HashMap::from([("5432/tcp".to_owned(), None)])),
+ networks: Some(HashMap::from([(
+ "oxker-examaple-net".to_owned(),
+ EndpointSettings {
+ ipam_config: None,
+ links: None,
+ mac_address: Some("a2:bd:4e:61:25:c7".to_owned()),
+ aliases: Some(vec!["postgres".to_owned(), "postgres".to_owned()]),
+ driver_opts: None,
+ gw_priority: Some(0),
+ network_id: Some("3cbeb475d81676f89a7aa205d8749ec2ad78d685e45d77b638992956f6dc569a".to_owned()),
+ endpoint_id: Some("31718069b2a3ea77487f3ece36b014d5d1329bc3294568e2621e5c0999071bed".to_owned()),
+ gateway: Some("172.19.0.1".to_owned()),
+ ip_address: Some("172.19.0.4".to_owned()),
+ ip_prefix_len: Some(16),
+ ipv6_gateway: Some("".to_owned()),
+ global_ipv6_address: Some("".to_owned()),
+ global_ipv6_prefix_len: Some(0),
+ dns_names: Some(vec!["postgres".to_owned(), "0bdea64212f9".to_owned()]),
+ },
+ )])),
+ }),
+}
+ }
+}
diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs
index ebf6ec7..98b643f 100644
--- a/src/ui/draw_blocks/logs.rs
+++ b/src/ui/draw_blocks/logs.rs
@@ -14,7 +14,7 @@ use crate::{
ui::{FrameData, GuiState, SelectablePanel, Status},
};
-use super::{RIGHT_ARROW, generate_block};
+use super::{SELECT_ARROW, generate_block};
/// Draw the logs panel
pub fn draw(
@@ -52,7 +52,7 @@ pub fn draw(
} else if fd.color_logs {
let items = List::new(logs)
.block(block)
- .highlight_symbol(RIGHT_ARROW)
+ .highlight_symbol(SELECT_ARROW)
.scroll_padding(padding)
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
// This should always return Some, as logs is not empty
@@ -63,7 +63,7 @@ pub fn draw(
let items = List::new(logs)
.fg(colors.logs.text)
.block(block)
- .highlight_symbol(RIGHT_ARROW)
+ .highlight_symbol(SELECT_ARROW)
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
// This should always return Some, as logs is not empty
if let Some(log_state) = app_data.lock().get_log_state() {
@@ -309,7 +309,7 @@ mod tests {
);
})
.unwrap();
- setup.app_data.lock().log_scroll(&ScrollDirection::Previous);
+ setup.app_data.lock().log_scroll(&ScrollDirection::Up);
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs
index 507a913..147e166 100644
--- a/src/ui/draw_blocks/mod.rs
+++ b/src/ui/draw_blocks/mod.rs
@@ -11,7 +11,8 @@ use crate::config::AppColors;
use super::{FrameData, GuiState, SelectablePanel, Status, gui_state::Region};
-pub mod charts;
+pub mod chart_bandwidth;
+pub mod chart_cpu_mem;
pub mod commands;
pub mod containers;
pub mod delete_confirm;
@@ -20,26 +21,29 @@ pub mod filter;
pub mod headers;
pub mod help;
pub mod info;
+pub mod inspect;
pub mod logs;
pub mod popup;
pub mod ports;
pub mod search_logs;
-pub const NAME_TEXT: &str = r#"
- 88
- 88
- 88
- ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba,
-a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8
-8b d8 )888( 8888[ 8PP""""""" 88
-"8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88
- `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 "#;
+pub const NAME_TEXT: &str = r#" 88
+ 88
+ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba,
+a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8
+8b d8 )888( 8888( 8PP""""""" 88
+"8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88
+ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 "#;
pub const NAME: &str = env!("CARGO_PKG_NAME");
pub const REPO: &str = env!("CARGO_PKG_REPOSITORY");
pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
pub const MARGIN: &str = " ";
-pub const RIGHT_ARROW: &str = "โถ ";
+pub const SELECT_ARROW: &str = "โถ ";
+pub const LEFT_ARROW: &str = "โ";
+pub const RIGHT_ARROW: &str = "โ";
+pub const DOWN_ARROW: &str = "โ";
+pub const UP_ARROW: &str = "โ";
pub const CIRCLE: &str = "โช ";
#[cfg(not(test))]
@@ -103,12 +107,12 @@ fn generate_block<'a>(
.border_type(BorderType::Rounded)
.title(ratatui::text::Line::from(title).left_aligned());
- if panel == SelectablePanel::Logs {
- if let Some(x) = fd.scroll_title.as_ref() {
- block = block
- .title_bottom(x.to_owned())
- .title_alignment(ratatui::layout::Alignment::Right);
- }
+ if panel == SelectablePanel::Logs
+ && let Some(x) = fd.scroll_title.as_ref()
+ {
+ block = block
+ .title_bottom(x.to_owned())
+ .title_alignment(ratatui::layout::Alignment::Right);
}
if !fd.status.contains(&Status::Filter) {
if fd.selected_panel == panel {
@@ -242,7 +246,7 @@ pub mod tests {
#[allow(clippy::cast_precision_loss)]
// Add fixed data to the cpu & mem vecdeques
- pub fn insert_chart_data(setup: &TuiTestSetup) {
+ pub fn insert_all_chart_data(setup: &TuiTestSetup) {
for i in 1..=10 {
setup.app_data.lock().update_stats_by_id(
&setup.ids[0],
@@ -273,7 +277,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout() {
let mut setup = test_setup(160, 30, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
@@ -301,7 +305,7 @@ pub mod tests {
/// Check that the whole layout is drawn correctly
fn test_draw_blocks_whole_layout_with_filter_bar() {
let mut setup = test_setup(160, 30, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[1]
@@ -337,7 +341,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout_long_name() {
let mut setup = test_setup(190, 30, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
@@ -370,7 +374,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout_no_logs() {
let mut setup = test_setup(160, 30, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
@@ -399,7 +403,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout_short_height_logs() {
let mut setup = test_setup(160, 30, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
@@ -431,7 +435,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout_help_panel() {
let mut setup = test_setup(160, 40, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
@@ -461,7 +465,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout_error() {
let mut setup = test_setup(160, 40, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
@@ -495,7 +499,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout_delete() {
let mut setup = test_setup(160, 40, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
@@ -527,7 +531,7 @@ pub mod tests {
fn test_draw_blocks_whole_layout_info_box() {
let mut setup = test_setup(160, 40, true, true);
- insert_chart_data(&setup);
+ insert_all_chart_data(&setup);
insert_logs(&setup);
setup.app_data.lock().containers.items[0]
.ports
diff --git a/src/ui/draw_blocks/search_logs.rs b/src/ui/draw_blocks/search_logs.rs
index 81b1e31..335aa31 100644
--- a/src/ui/draw_blocks/search_logs.rs
+++ b/src/ui/draw_blocks/search_logs.rs
@@ -241,11 +241,11 @@ mod tests {
setup
.app_data
.lock()
- .log_scroll(&crate::app_data::ScrollDirection::Previous);
+ .log_scroll(&crate::app_data::ScrollDirection::Up);
setup
.app_data
.lock()
- .log_scroll(&crate::app_data::ScrollDirection::Previous);
+ .log_scroll(&crate::app_data::ScrollDirection::Up);
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
@@ -298,7 +298,7 @@ mod tests {
setup
.app_data
.lock()
- .log_scroll(&crate::app_data::ScrollDirection::Previous);
+ .log_scroll(&crate::app_data::ScrollDirection::Up);
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
@@ -404,7 +404,7 @@ mod tests {
setup
.app_data
.lock()
- .log_scroll(&crate::app_data::ScrollDirection::Previous);
+ .log_scroll(&crate::app_data::ScrollDirection::Up);
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
setup
@@ -433,7 +433,7 @@ mod tests {
setup
.app_data
.lock()
- .log_scroll(&crate::app_data::ScrollDirection::Previous);
+ .log_scroll(&crate::app_data::ScrollDirection::Up);
let mut colors = AppColors::new();
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_custom_colors.snap
new file mode 100644
index 0000000..699967c
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_custom_colors.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโ rx: 566.00 kb/s tx: 56.60 kb/s โโโโฎ"
+"โ โ โข โ"
+"โ โ โขโข โ"
+"โ โ โขโข โ"
+"โ566.00 kb/sโ โขโข โ"
+"โ โ โข โ"
+"โ56.60 kb/s โ โขโข โ"
+"โ โโขโข โขโขโข โ"
+"โ โโขโขโขโขโขโข โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_dead.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_dead.snap
new file mode 100644
index 0000000..64b250a
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_dead.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโฎ"
+"โ โ โ"
+"โ โ โ"
+"โ โ โ"
+"โ0.00 kb/sโ โ"
+"โ โ โ"
+"โ0.00 kb/sโ โ"
+"โ โ โ"
+"โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_paused.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_paused.snap
new file mode 100644
index 0000000..64b250a
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_paused.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโฎ"
+"โ โ โ"
+"โ โ โ"
+"โ โ โ"
+"โ0.00 kb/sโ โ"
+"โ โ โ"
+"โ0.00 kb/sโ โ"
+"โ โ โ"
+"โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_none.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_none.snap
new file mode 100644
index 0000000..64b250a
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_none.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโฎ"
+"โ โ โ"
+"โ โ โ"
+"โ โ โ"
+"โ0.00 kb/sโ โ"
+"โ โ โ"
+"โ0.00 kb/sโ โ"
+"โ โ โ"
+"โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data.snap
new file mode 100644
index 0000000..f2451e2
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโ rx: 0.00 kb/s tx: 205.00 kb/s โโโโโฎ"
+"โ โ โข โ"
+"โ โ โขโข โ"
+"โ โ โขโข โ"
+"โ205.00 kb/sโ โขโข โ"
+"โ โ โข โ"
+"โ0.00 kb/s โ โขโข โ"
+"โ โโขโข โ"
+"โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_rx.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_rx.snap
new file mode 100644
index 0000000..bff8635
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_rx.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโ rx: 566.00 kb/s tx: 0.00 kb/s โโโโโฎ"
+"โ โ โข โ"
+"โ โ โขโข โ"
+"โ โ โขโข โ"
+"โ566.00 kb/sโ โขโข โ"
+"โ โ โข โ"
+"โ0.00 kb/s โ โขโข โ"
+"โ โโขโข โ"
+"โ โโข โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx.snap
new file mode 100644
index 0000000..f2451e2
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโ rx: 0.00 kb/s tx: 205.00 kb/s โโโโโฎ"
+"โ โ โข โ"
+"โ โ โขโข โ"
+"โ โ โขโข โ"
+"โ205.00 kb/sโ โขโข โ"
+"โ โ โข โ"
+"โ0.00 kb/s โ โขโข โ"
+"โ โโขโข โ"
+"โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx_and_rx.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx_and_rx.snap
new file mode 100644
index 0000000..699967c
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_bandwidth__tests__draw_blocks_charts_running_with_data_tx_and_rx.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_bandwidth.rs
+expression: setup.terminal.backend()
+---
+"โญโโโ rx: 566.00 kb/s tx: 56.60 kb/s โโโโฎ"
+"โ โ โข โ"
+"โ โ โขโข โ"
+"โ โ โขโข โ"
+"โ566.00 kb/sโ โขโข โ"
+"โ โ โข โ"
+"โ56.60 kb/s โ โขโข โ"
+"โ โโขโข โขโขโข โ"
+"โ โโขโขโขโขโขโข โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_custom_colors.snap
new file mode 100644
index 0000000..8a75efc
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_custom_colors.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_cpu_mem.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
+"โ โ โโ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_dead.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_dead.snap
new file mode 100644
index 0000000..8a75efc
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_dead.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_cpu_mem.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
+"โ โ โโ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_paused.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_paused.snap
new file mode 100644
index 0000000..8a75efc
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_paused.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_cpu_mem.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
+"โ โ โโ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_none.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_none.snap
new file mode 100644
index 0000000..8632071
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_none.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_cpu_mem.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโ cpu 00.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโโ memory 0.00 kB โโโโโโโโโโโโฎ"
+"โ00.00%โ โโ0.00 kBโ โ"
+"โ โ โโ โ โ"
+"โ โ โโ โ โ"
+"โ โ โโ โ โ"
+"โ โ โโ โ โ"
+"โ โ โโ โ โ"
+"โ โ โโ โ โ"
+"โ โ โโ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_some.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_some.snap
new file mode 100644
index 0000000..8a75efc
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__chart_cpu_mem__tests__draw_blocks_charts_running_some.snap
@@ -0,0 +1,14 @@
+---
+source: src/ui/draw_blocks/chart_cpu_mem.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
+"โ โ โโ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_custom_colors.snap
index a92f2bd..90160a2 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_custom_colors.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_custom_colors.snap
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
expression: setup.terminal.backend()
---
"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
-"โ10.00%โ โข โโ100.00 kBโ โขโข โ"
-"โ โ โขโข โโ โ โขโข โ"
-"โ โ โขโขโข โโ โ โข โข โ"
-"โ โ โข โข โโ โ โข โข โ"
-"โ โ โข โขโข โโ โโขโข โขโข โ"
-"โ โโข โข โโ โโข โข โ"
-"โ โโข โข โโ โโข โข โ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
"โ โ โโ โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_dead.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_dead.snap
index a92f2bd..90160a2 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_dead.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_dead.snap
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
expression: setup.terminal.backend()
---
"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
-"โ10.00%โ โข โโ100.00 kBโ โขโข โ"
-"โ โ โขโข โโ โ โขโข โ"
-"โ โ โขโขโข โโ โ โข โข โ"
-"โ โ โข โข โโ โ โข โข โ"
-"โ โ โข โขโข โโ โโขโข โขโข โ"
-"โ โโข โข โโ โโข โข โ"
-"โ โโข โข โโ โโข โข โ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
"โ โ โโ โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_paused.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_paused.snap
index a92f2bd..90160a2 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_paused.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_paused.snap
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
expression: setup.terminal.backend()
---
"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
-"โ10.00%โ โข โโ100.00 kBโ โขโข โ"
-"โ โ โขโข โโ โ โขโข โ"
-"โ โ โขโขโข โโ โ โข โข โ"
-"โ โ โข โข โโ โ โข โข โ"
-"โ โ โข โขโข โโ โโขโข โขโข โ"
-"โ โโข โข โโ โโข โข โ"
-"โ โโข โข โโ โโข โข โ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
"โ โ โโ โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_running_some.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_running_some.snap
index a92f2bd..90160a2 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_running_some.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__charts__tests__draw_blocks_charts_running_some.snap
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
expression: setup.terminal.backend()
---
"โญโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโฎโญโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโฎ"
-"โ10.00%โ โข โโ100.00 kBโ โขโข โ"
-"โ โ โขโข โโ โ โขโข โ"
-"โ โ โขโขโข โโ โ โข โข โ"
-"โ โ โข โข โโ โ โข โข โ"
-"โ โ โข โขโข โโ โโขโข โขโข โ"
-"โ โโข โข โโ โโข โข โ"
-"โ โโข โข โโ โโข โข โ"
+"โ10.00%โ โข โโ100.00 kBโ โข โ"
+"โ โ โขโข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โขโข โ"
+"โ โ โข โข โโ โ โข โข โ"
+"โ โ โข โข โโ โ โขโข โข โ"
+"โ โ โข โขโข โโ โ โข โข โ"
+"โ โโขโข โขโข โโ โโข โข โ"
"โ โ โโ โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__error__tests__draw_blocks_error_docker_connect_error.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__error__tests__draw_blocks_error_docker_connect_error.snap
index 629c56a..6bd72ae 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__error__tests__draw_blocks_error_docker_connect_error.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__error__tests__draw_blocks_error_docker_connect_error.snap
@@ -2,12 +2,14 @@
source: src/ui/draw_blocks/error.rs
expression: setup.terminal.backend()
---
-" "
-"โญโโโโโโโโโโโโโโโโโโ Error โโโโโโโโโโโโโโโโโโโโฎ"
-"โ โ"
-"โ Unable to access docker daemon โ"
-"โ โ"
-"โ oxker::v0.00.000 closing in 04 seconds โ"
-"โ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-" "
+" "
+" โญโโโโโโโโโโโโโโโโโโโ Error โโโโโโโโโโโโโโโโโโโโโฎ "
+" โ โ "
+" โ Unable to access docker daemon โ "
+" โ โ "
+" โ oxker::v0.00.000 closing in 04 seconds โ "
+" โ โ "
+" โ ( q ) quit oxker โ "
+" โ โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__error__tests__draw_blocks_error_docker_connect_error_custom_host.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__error__tests__draw_blocks_error_docker_connect_error_custom_host.snap
new file mode 100644
index 0000000..9dabfef
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__error__tests__draw_blocks_error_docker_connect_error_custom_host.snap
@@ -0,0 +1,15 @@
+---
+source: src/ui/draw_blocks/error.rs
+expression: setup.terminal.backend()
+---
+" "
+" โญโโโโโโโโโโโโโโโโโโโโโโโโ Error โโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ โ "
+" โ Unable to access docker daemon @ "/test/host.sock" โ "
+" โ โ "
+" โ oxker::v0.00.000 closing in 04 seconds โ "
+" โ โ "
+" โ ( q ) quit oxker โ "
+" โ โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap
index 9d29eeb..a68dbd3 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap
@@ -2,42 +2,28 @@
source: src/ui/draw_blocks/help.rs
expression: setup.terminal.backend()
---
-" "
-" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
-" โ โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ "
-" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ "
-" โ 8b d8 )888( 8888[ 8PP""""""" 88 โ "
-" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ "
-" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
-" โ โ "
-" โ A simple tui to view & control docker containers โ "
-" โ โ "
-" โ ( tab ) or ( shift+tab ) change panels โ "
-" โ ( โ โ ) or ( j k ) or ( Home End ) scroll vertically โ "
-" โ ( โ โ ) horizontal scroll across logs โ "
-" โ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ "
-" โ ( enter ) send docker container command โ "
-" โ ( e ) exec into a container โ "
-" โ ( f ) force clear the screen & redraw the gui โ "
-" โ ( h ) toggle this help information - or click heading โ "
-" โ ( s ) save logs to file โ "
-" โ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ "
-" โ ( F1 ) or ( / ) enter filter mode โ "
-" โ ( # ) enter log search mode โ "
-" โ ( 0 ) stop sort โ "
-" โ ( 1 - 9 ) sort by header - or click header โ "
-" โ ( - = ) change log section height โ "
-" โ ( \ ) toggle log section visibility โ "
-" โ ( esc ) close dialog โ "
-" โ ( q ) quit at any time โ "
-" โ โ "
-" โ currently an early work in progress, all and any input appreciated โ "
-" โ https://github.com/mrjackwills/oxker โ "
-" โ โ "
-" โ โ "
-" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
-" "
+" "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ "
+" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
+" โ a work in progress, all and any input appreciated โ "
+" โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ "
+" โ โ "
+" โ Keymap โ "
+" โ q quit c Esc close dialog โ "
+" โ Down Up j k Home End scroll vertically Left Right scroll horizontally โ "
+" โ Control increase scroll speed Enter send docker command โ "
+" โ e exec into a container i container inspect mode โ "
+" โ / F1 filter mode # log search mode โ "
+" โ h toggle this panel f force clear screen and redraw โ "
+" โ - = change log section height \ toggle of section visibility โ "
+" โ 1 ~ 9 sort by header - or click header 0 stop sort โ "
+" โ Tab Back Tab change panel m toggle mouse capture - allows text selection โ "
+" โ s save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_color.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_color.snap
new file mode 100644
index 0000000..a68dbd3
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_color.snap
@@ -0,0 +1,29 @@
+---
+source: src/ui/draw_blocks/help.rs
+expression: setup.terminal.backend()
+---
+" "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ "
+" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
+" โ a work in progress, all and any input appreciated โ "
+" โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ "
+" โ โ "
+" โ Keymap โ "
+" โ q quit c Esc close dialog โ "
+" โ Down Up j k Home End scroll vertically Left Right scroll horizontally โ "
+" โ Control increase scroll speed Enter send docker command โ "
+" โ e exec into a container i container inspect mode โ "
+" โ / F1 filter mode # log search mode โ "
+" โ h toggle this panel f force clear screen and redraw โ "
+" โ - = change log section height \ toggle of section visibility โ "
+" โ 1 ~ 9 sort by header - or click header 0 stop sort โ "
+" โ Tab Back Tab change panel m toggle mouse capture - allows text selection โ "
+" โ s save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap
deleted file mode 100644
index 9d29eeb..0000000
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap
+++ /dev/null
@@ -1,43 +0,0 @@
----
-source: src/ui/draw_blocks/help.rs
-expression: setup.terminal.backend()
----
-" "
-" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
-" โ โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ "
-" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ "
-" โ 8b d8 )888( 8888[ 8PP""""""" 88 โ "
-" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ "
-" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
-" โ โ "
-" โ A simple tui to view & control docker containers โ "
-" โ โ "
-" โ ( tab ) or ( shift+tab ) change panels โ "
-" โ ( โ โ ) or ( j k ) or ( Home End ) scroll vertically โ "
-" โ ( โ โ ) horizontal scroll across logs โ "
-" โ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ "
-" โ ( enter ) send docker container command โ "
-" โ ( e ) exec into a container โ "
-" โ ( f ) force clear the screen & redraw the gui โ "
-" โ ( h ) toggle this help information - or click heading โ "
-" โ ( s ) save logs to file โ "
-" โ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ "
-" โ ( F1 ) or ( / ) enter filter mode โ "
-" โ ( # ) enter log search mode โ "
-" โ ( 0 ) stop sort โ "
-" โ ( 1 - 9 ) sort by header - or click header โ "
-" โ ( - = ) change log section height โ "
-" โ ( \ ) toggle log section visibility โ "
-" โ ( esc ) close dialog โ "
-" โ ( q ) quit at any time โ "
-" โ โ "
-" โ currently an early work in progress, all and any input appreciated โ "
-" โ https://github.com/mrjackwills/oxker โ "
-" โ โ "
-" โ โ "
-" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
-" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap
index 97b4d43..a7b1daf 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap
@@ -2,53 +2,28 @@
source: src/ui/draw_blocks/help.rs
expression: setup.terminal.backend()
---
-" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
-" โ โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ "
-" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ "
-" โ 8b d8 )888( 8888[ 8PP""""""" 88 โ "
-" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ "
-" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
-" โ โ "
-" โ A simple tui to view & control docker containers โ "
-" โ โ "
-" โ ( t ) select next panel โ "
-" โ ( u ) select previous panel โ "
-" โ ( o ) scroll list down by one โ "
-" โ ( s ) scroll list up by one โ "
-" โ ( p ) scroll list to end โ "
-" โ ( q ) scroll list to start โ "
-" โ ( h ) horizontal scroll logs right โ "
-" โ ( g ) horizontal scroll logs left โ "
-" โ ( Alt ) increase scroll speed, used in conjunction scroll keys โ "
-" โ ( enter ) send docker container command โ "
-" โ ( d ) exec into a container โ "
-" โ ( f ) force clear the screen & redraw the gui โ "
-" โ ( 5 ) toggle this help information - or click heading โ "
-" โ ( m ) save logs to file โ "
-" โ ( 6 ) toggle mouse capture - if disabled, text on screen can be selected & copied โ "
-" โ ( e ) enter filter mode โ "
-" โ ( 7 ) enter log search mode โ "
-" โ ( 4 ) reset container sorting โ "
-" โ ( z ) sort containers by name โ "
-" โ ( 1 ) sort containers by state โ "
-" โ ( 2 ) sort containers by status โ "
-" โ ( v ) sort containers by cpu โ "
-" โ ( y ) sort containers by memory โ "
-" โ ( w ) sort containers by id โ "
-" โ ( x ) sort containers by image โ "
-" โ ( 0 ) sort containers by rx โ "
-" โ ( 3 ) sort containers by tx โ "
-" โ ( i ) decrease log section height โ "
-" โ ( j ) increase log section height โ "
-" โ ( k ) toggle log section visibility โ "
-" โ ( a ) close dialog โ "
-" โ ( l ) quit at any time โ "
-" โ โ "
-" โ currently an early work in progress, all and any input appreciated โ "
-" โ https://github.com/mrjackwills/oxker โ "
-" โ โ "
-" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ "
+" โ โ "
+" โ Keymap โ "
+" โ n quit a close dialog โ "
+" โ p s scroll vertically i j scroll horizontally โ "
+" โ r scroll to start q scroll to end โ "
+" โ Alt increase scroll speed Enter send docker command โ "
+" โ d exec into a container e container inspect mode โ "
+" โ f filter mode g log search mode โ "
+" โ 5 toggle this panel h force clear screen and redraw โ "
+" โ k l change log section height m toggle of section visibility โ "
+" โ z sort by name 1 sort by state โ "
+" โ 2 sort by status v sort by CPU โ "
+" โ y sort by memory w sort by ID โ "
+" โ x sort by Image 0 sort by RX โ "
+" โ 3 sort by TX 4 stop sort โ "
+" โ t u change panel 6 toggle mouse capture - allows text selection โ "
+" โ o save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_two_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_two_definition.snap
new file mode 100644
index 0000000..ed14a24
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_two_definition.snap
@@ -0,0 +1,34 @@
+---
+source: src/ui/draw_blocks/help.rs
+expression: setup.terminal.backend()
+---
+" "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y config location: /home/user/.config/oxker/config.toml โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ "
+" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
+" โ a work in progress, all and any input appreciated โ "
+" โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ "
+" โ โ "
+" โ Keymap โ "
+" โ 0 quit a b close dialog โ "
+" โ 4 Caps Lock Scroll Lock scroll vertically q s r scroll horizontally โ "
+" โ 8 scroll to start 6 7 scroll to end โ "
+" โ Alt increase scroll speed Enter send docker command โ "
+" โ g exec into a container i j container inspect mode โ "
+" โ k filter mode m n log search mode โ "
+" โ F5 F6 toggle this panel o force clear screen and redraw โ "
+" โ u w v change log section height y z toggle of section visibility โ "
+" โ Begin Menu sort by name Page Up Pause sort by state โ "
+" โ Print Screen sort by status Down sort by CPU โ "
+" โ Home sort by memory Back Tab sort by ID โ "
+" โ End Esc sort by Image Num Lock sort by RX โ "
+" โ F1 F2 sort by TX F3 stop sort โ "
+" โ Print Screen Left Up change panel F7 toggle mouse capture - allows text selection โ "
+" โ 2 3 save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definition.snap
new file mode 100644
index 0000000..1aecc71
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definition.snap
@@ -0,0 +1,34 @@
+---
+source: src/ui/draw_blocks/help.rs
+expression: setup.terminal.backend()
+---
+" "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 config location: /home/user/.config/oxker/config.toml โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ "
+" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
+" โ a work in progress, all and any input appreciated โ "
+" โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ "
+" โ โ "
+" โ Keymap โ "
+" โ 0 1 quit a b close dialog โ "
+" โ 4 Caps Lock 5 Scroll Lock scroll vertically q s r t scroll horizontally โ "
+" โ 8 9 scroll to start 6 7 scroll to end โ "
+" โ Alt increase scroll speed Enter send docker command โ "
+" โ g h exec into a container i j container inspect mode โ "
+" โ k l filter mode m n log search mode โ "
+" โ F5 F6 toggle this panel o p force clear screen and redraw โ "
+" โ u w v x change log section height y z toggle of section visibility โ "
+" โ Begin Menu sort by name Page Up Pause sort by state โ "
+" โ Print Screen Tab sort by status Down Del sort by CPU โ "
+" โ Home Insert sort by memory Back Tab Backspace sort by ID โ "
+" โ End Esc sort by Image Num Lock Page Down sort by RX โ "
+" โ F1 F2 sort by TX F3 F4 stop sort โ "
+" โ Print Screen Left Up Right change panel F7 F8 toggle mouse capture - allows text selection โ "
+" โ 2 3 save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap
deleted file mode 100644
index f884948..0000000
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap
+++ /dev/null
@@ -1,54 +0,0 @@
----
-source: src/ui/draw_blocks/help.rs
-expression: setup.terminal.backend()
----
-" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
-" โ โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ "
-" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ "
-" โ 8b d8 )888( 8888[ 8PP""""""" 88 โ "
-" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ "
-" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
-" โ โ "
-" โ A simple tui to view & control docker containers โ "
-" โ โ "
-" โ ( s ) or ( S ) select next panel โ "
-" โ ( t ) or ( T ) select previous panel โ "
-" โ ( n ) or ( N ) scroll list down by one โ "
-" โ ( r ) or ( R ) scroll list up by one โ "
-" โ ( o ) or ( O ) scroll list to end โ "
-" โ ( p ) or ( P ) scroll list to start โ "
-" โ ( g ) or ( G ) horizontal scroll logs right โ "
-" โ ( f ) or ( F ) horizontal scroll logs left โ "
-" โ ( Alt ) increase scroll speed, used in conjunction scroll keys โ "
-" โ ( enter ) send docker container command โ "
-" โ ( d ) or ( D ) exec into a container โ "
-" โ ( f ) or ( F ) force clear the screen & redraw the gui โ "
-" โ ( 4 ) or ( 5 ) toggle this help information - or click heading โ "
-" โ ( l ) or ( L ) save logs to file โ "
-" โ ( 5 ) or ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied โ "
-" โ ( e ) or ( E ) enter filter mode โ "
-" โ ( m ) or ( M ) enter log search mode โ "
-" โ ( 3 ) or ( 6 ) reset container sorting โ "
-" โ ( y ) or ( Y ) sort containers by name โ "
-" โ ( 0 ) or ( 9 ) sort containers by state โ "
-" โ ( 1 ) or ( 8 ) sort containers by status โ "
-" โ ( u ) or ( U ) sort containers by cpu โ "
-" โ ( x ) or ( X ) sort containers by memory โ "
-" โ ( v ) or ( V ) sort containers by id โ "
-" โ ( w ) or ( W ) sort containers by image โ "
-" โ ( z ) or ( Z ) sort containers by rx โ "
-" โ ( 2 ) or ( 7 ) sort containers by tx โ "
-" โ ( h ) or ( H ) decrease log section height โ "
-" โ ( i ) or ( I ) increase log section height โ "
-" โ ( j ) or ( J ) toggle log section visibility โ "
-" โ ( a ) or ( A ) close dialog โ "
-" โ ( k ) or ( K ) quit at any time โ "
-" โ โ "
-" โ currently an early work in progress, all and any input appreciated โ "
-" โ https://github.com/mrjackwills/oxker โ "
-" โ โ "
-" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_config.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_config.snap
new file mode 100644
index 0000000..b6ae220
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_config.snap
@@ -0,0 +1,29 @@
+---
+source: src/ui/draw_blocks/help.rs
+expression: setup.terminal.backend()
+---
+" "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYb โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' " โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ "
+" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
+" โ a work in progress, all and any input appreciated โ "
+" โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ "
+" โ โ "
+" โ Keymap โ "
+" โ q quit c Esc close dialog โ "
+" โ Down Up j k Home End scroll vertically Left Right scroll horizontally โ "
+" โ Control increase scroll speed Enter send docker command โ "
+" โ e exec into a container i container inspect mode โ "
+" โ / F1 filter mode # log search mode โ "
+" โ h toggle this panel f force clear screen and redraw โ "
+" โ - = change log section height \ toggle of section visibility โ "
+" โ 1 ~ 9 sort by header - or click header 0 stop sort โ "
+" โ Tab Back Tab change panel m toggle mouse capture - allows text selection โ "
+" โ s save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_save.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_save.snap
new file mode 100644
index 0000000..a1240bf
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_save.snap
@@ -0,0 +1,29 @@
+---
+source: src/ui/draw_blocks/help.rs
+expression: setup.terminal.backend()
+---
+" "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 config location: /home/user/.config/oxker/config.toml โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC โ "
+" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
+" โ a work in progress, all and any input appreciated โ "
+" โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ "
+" โ โ "
+" โ Keymap โ "
+" โ q quit c Esc close dialog โ "
+" โ Down Up j k Home End scroll vertically Left Right scroll horizontally โ "
+" โ Control increase scroll speed Enter send docker command โ "
+" โ e exec into a container i container inspect mode โ "
+" โ / F1 filter mode # log search mode โ "
+" โ h toggle this panel f force clear screen and redraw โ "
+" โ - = change log section height \ toggle of section visibility โ "
+" โ 1 ~ 9 sort by header - or click header 0 stop sort โ "
+" โ Tab Back Tab change panel m toggle mouse capture - allows text selection โ "
+" โ s save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_timezone.snap
new file mode 100644
index 0000000..01aa561
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_no_timezone.snap
@@ -0,0 +1,29 @@
+---
+source: src/ui/draw_blocks/help.rs
+expression: setup.terminal.backend()
+---
+" "
+" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
+" โ 88 โ "
+" โ 88 โ "
+" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba โ "
+" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y โ "
+" โ 8b d8 )888( 8888( 8PP""""""" 88 config location: /home/user/.config/oxker/config.toml โ "
+" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 export location: /test_dir โ "
+" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
+" โ a work in progress, all and any input appreciated โ "
+" โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ "
+" โ โ "
+" โ Keymap โ "
+" โ q quit c Esc close dialog โ "
+" โ Down Up j k Home End scroll vertically Left Right scroll horizontally โ "
+" โ Control increase scroll speed Enter send docker command โ "
+" โ e exec into a container i container inspect mode โ "
+" โ / F1 filter mode # log search mode โ "
+" โ h toggle this panel f force clear screen and redraw โ "
+" โ - = change log section height \ toggle of section visibility โ "
+" โ 1 ~ 9 sort by header - or click header 0 stop sort โ "
+" โ Tab Back Tab change panel m toggle mouse capture - allows text selection โ "
+" โ s save logs to file โ "
+" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
+" "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap
deleted file mode 100644
index 6fcfd2e..0000000
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap
+++ /dev/null
@@ -1,54 +0,0 @@
----
-source: src/ui/draw_blocks/help.rs
-expression: setup.terminal.backend()
----
-" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
-" โ โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ "
-" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ "
-" โ 8b d8 )888( 8888[ 8PP""""""" 88 โ "
-" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ "
-" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
-" โ โ "
-" โ A simple tui to view & control docker containers โ "
-" โ โ "
-" โ ( t ) select next panel โ "
-" โ ( u ) or ( U ) select previous panel โ "
-" โ ( o ) or ( O ) scroll list down by one โ "
-" โ ( s ) or ( S ) scroll list up by one โ "
-" โ ( p ) scroll list to end โ "
-" โ ( q ) or ( Q ) scroll list to start โ "
-" โ ( h ) horizontal scroll logs right โ "
-" โ ( g ) or ( G ) horizontal scroll logs left โ "
-" โ ( Alt ) increase scroll speed, used in conjunction scroll keys โ "
-" โ ( enter ) send docker container command โ "
-" โ ( d ) exec into a container โ "
-" โ ( f ) force clear the screen & redraw the gui โ "
-" โ ( 5 ) toggle this help information - or click heading โ "
-" โ ( m ) or ( M ) save logs to file โ "
-" โ ( 6 ) or ( # ) toggle mouse capture - if disabled, text on screen can be selected & copied โ "
-" โ ( e ) or ( E ) enter filter mode โ "
-" โ ( 8 ) enter log search mode โ "
-" โ ( 4 ) or ( 5 ) reset container sorting โ "
-" โ ( z ) sort containers by name โ "
-" โ ( 1 ) sort containers by state โ "
-" โ ( 2 ) or ( 7 ) sort containers by status โ "
-" โ ( v ) sort containers by cpu โ "
-" โ ( y ) or ( Y ) sort containers by memory โ "
-" โ ( w ) or ( W ) sort containers by id โ "
-" โ ( x ) sort containers by image โ "
-" โ ( 0 ) or ( 9 ) sort containers by rx โ "
-" โ ( 3 ) sort containers by tx โ "
-" โ ( i ) or ( I ) decrease log section height โ "
-" โ ( j ) increase log section height โ "
-" โ ( k ) or ( K ) toggle log section visibility โ "
-" โ ( a ) or ( A ) close dialog โ "
-" โ ( l ) quit at any time โ "
-" โ โ "
-" โ currently an early work in progress, all and any input appreciated โ "
-" โ https://github.com/mrjackwills/oxker โ "
-" โ โ "
-" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap
deleted file mode 100644
index 6c8b9ef..0000000
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap
+++ /dev/null
@@ -1,43 +0,0 @@
----
-source: src/ui/draw_blocks/help.rs
-expression: setup.terminal.backend()
----
-" โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ "
-" โ โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ 88 โ "
-" โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โ "
-" โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โ "
-" โ 8b d8 )888( 8888[ 8PP""""""" 88 โ "
-" โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ "
-" โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ "
-" โ โ "
-" โ A simple tui to view & control docker containers โ "
-" โ โ "
-" โ logs timezone: Asia/Tokyo โ "
-" โ โ "
-" โ ( tab ) or ( shift+tab ) change panels โ "
-" โ ( โ โ ) or ( j k ) or ( Home End ) scroll vertically โ "
-" โ ( โ โ ) horizontal scroll across logs โ "
-" โ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ "
-" โ ( enter ) send docker container command โ "
-" โ ( e ) exec into a container โ "
-" โ ( f ) force clear the screen & redraw the gui โ "
-" โ ( h ) toggle this help information - or click heading โ "
-" โ ( s ) save logs to file โ "
-" โ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ "
-" โ ( F1 ) or ( / ) enter filter mode โ "
-" โ ( # ) enter log search mode โ "
-" โ ( 0 ) stop sort โ "
-" โ ( 1 - 9 ) sort by header - or click header โ "
-" โ ( - = ) change log section height โ "
-" โ ( \ ) toggle log section visibility โ "
-" โ ( esc ) close dialog โ "
-" โ ( q ) quit at any time โ "
-" โ โ "
-" โ currently an early work in progress, all and any input appreciated โ "
-" โ https://github.com/mrjackwills/oxker โ "
-" โ โ "
-" โ โ "
-" โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_color.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_color.snap
new file mode 100644
index 0000000..0c5cf65
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_color.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - c or Esc or i to exit โโโโโโโโโโโโโโโโโโโโโโฎ"
+"โ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ"
+"โ "Created": "2026-01-23T22:20:19.927967311Z", โ"
+"โ "Path": "docker-entrypoint.sh", โ"
+"โ "Args": [ โ"
+"โ "postgres" โ"
+"โ ], โ"
+"โ "State": { โ"
+"โ "Status": "running", โ"
+"โ "Running": true, โ"
+"โ "Paused": false, โ"
+"โ "Restarting": false, โ"
+"โ "OOMKilled": false, โ"
+"โ "Dead": false, โ"
+"โ "Pid": 782, โ"
+"โ "ExitCode": 0, โ"
+"โ "Error": "", โ"
+"โ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ }, โ"
+"โ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ"
+"โ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ"
+"โ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ"
+"โ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ"
+"โ "Name": "/postgres", โ"
+"โ "RestartCount": 0, โ"
+"โ "Driver": "overlay2", โ"
+"โ "Platform": "linux", โ"
+"โ "MountLabel": "", โ"
+"โ "ProcessLabel": "", โ"
+"โ "AppArmorProfile": "", โ"
+"โ "HostConfig": { โ"
+"โ "CpuShares": 0, โ"
+"โ "Memory": 1073741824, โ"
+"โ "CgroupParent": "", โ"
+"โ "BlkioWeight": 0, โ"
+"โ "CpuPeriod": 0, โ"
+"โ "CpuQuota": 0, โ"
+"โ "CpuRealtimePeriod": 0, โ"
+"โ "CpuRealtimeRuntime": 0, โ"
+"โ "CpusetCpus": "", โ"
+"โ "CpusetMems": "", โ"
+"โ "MemoryReservation": 0, โ"
+"โ "MemorySwap": 2147483648, โ"
+"โ "NanoCpus": 0, โ"
+"โ "OomKillDisable": false, โ"
+"โ "CpuCount": 0, โ"
+"โ "CpuPercent": 0, โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 0/158 โ 0/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_all.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_all.snap
new file mode 100644
index 0000000..9ab52b8
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_all.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - F or Z or 4 or 5 to exit โโโโโโโโโโโโโโโโโโโโโฎ"
+"โ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ"
+"โ "Created": "2026-01-23T22:20:19.927967311Z", โ"
+"โ "Path": "docker-entrypoint.sh", โ"
+"โ "Args": [ โ"
+"โ "postgres" โ"
+"โ ], โ"
+"โ "State": { โ"
+"โ "Status": "running", โ"
+"โ "Running": true, โ"
+"โ "Paused": false, โ"
+"โ "Restarting": false, โ"
+"โ "OOMKilled": false, โ"
+"โ "Dead": false, โ"
+"โ "Pid": 782, โ"
+"โ "ExitCode": 0, โ"
+"โ "Error": "", โ"
+"โ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ }, โ"
+"โ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ"
+"โ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ"
+"โ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ"
+"โ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ"
+"โ "Name": "/postgres", โ"
+"โ "RestartCount": 0, โ"
+"โ "Driver": "overlay2", โ"
+"โ "Platform": "linux", โ"
+"โ "MountLabel": "", โ"
+"โ "ProcessLabel": "", โ"
+"โ "AppArmorProfile": "", โ"
+"โ "HostConfig": { โ"
+"โ "CpuShares": 0, โ"
+"โ "Memory": 1073741824, โ"
+"โ "CgroupParent": "", โ"
+"โ "BlkioWeight": 0, โ"
+"โ "CpuPeriod": 0, โ"
+"โ "CpuQuota": 0, โ"
+"โ "CpuRealtimePeriod": 0, โ"
+"โ "CpuRealtimeRuntime": 0, โ"
+"โ "CpusetCpus": "", โ"
+"โ "CpusetMems": "", โ"
+"โ "MemoryReservation": 0, โ"
+"โ "MemorySwap": 2147483648, โ"
+"โ "NanoCpus": 0, โ"
+"โ "OomKillDisable": false, โ"
+"โ "CpuCount": 0, โ"
+"โ "CpuPercent": 0, โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 0/158 โ 0/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_one.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_one.snap
new file mode 100644
index 0000000..ed18a89
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_one.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - F or Esc or i to exit โโโโโโโโโโโโโโโโโโโโโโฎ"
+"โ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ"
+"โ "Created": "2026-01-23T22:20:19.927967311Z", โ"
+"โ "Path": "docker-entrypoint.sh", โ"
+"โ "Args": [ โ"
+"โ "postgres" โ"
+"โ ], โ"
+"โ "State": { โ"
+"โ "Status": "running", โ"
+"โ "Running": true, โ"
+"โ "Paused": false, โ"
+"โ "Restarting": false, โ"
+"โ "OOMKilled": false, โ"
+"โ "Dead": false, โ"
+"โ "Pid": 782, โ"
+"โ "ExitCode": 0, โ"
+"โ "Error": "", โ"
+"โ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ }, โ"
+"โ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ"
+"โ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ"
+"โ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ"
+"โ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ"
+"โ "Name": "/postgres", โ"
+"โ "RestartCount": 0, โ"
+"โ "Driver": "overlay2", โ"
+"โ "Platform": "linux", โ"
+"โ "MountLabel": "", โ"
+"โ "ProcessLabel": "", โ"
+"โ "AppArmorProfile": "", โ"
+"โ "HostConfig": { โ"
+"โ "CpuShares": 0, โ"
+"โ "Memory": 1073741824, โ"
+"โ "CgroupParent": "", โ"
+"โ "BlkioWeight": 0, โ"
+"โ "CpuPeriod": 0, โ"
+"โ "CpuQuota": 0, โ"
+"โ "CpuRealtimePeriod": 0, โ"
+"โ "CpuRealtimeRuntime": 0, โ"
+"โ "CpusetCpus": "", โ"
+"โ "CpusetMems": "", โ"
+"โ "MemoryReservation": 0, โ"
+"โ "MemorySwap": 2147483648, โ"
+"โ "NanoCpus": 0, โ"
+"โ "OomKillDisable": false, โ"
+"โ "CpuCount": 0, โ"
+"โ "CpuPercent": 0, โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 0/158 โ 0/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_two.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_two.snap
new file mode 100644
index 0000000..c4b4d46
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_clear_two.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - F or Z or i to exit โโโโโโโโโโโโโโโโโโโโโโโฎ"
+"โ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ"
+"โ "Created": "2026-01-23T22:20:19.927967311Z", โ"
+"โ "Path": "docker-entrypoint.sh", โ"
+"โ "Args": [ โ"
+"โ "postgres" โ"
+"โ ], โ"
+"โ "State": { โ"
+"โ "Status": "running", โ"
+"โ "Running": true, โ"
+"โ "Paused": false, โ"
+"โ "Restarting": false, โ"
+"โ "OOMKilled": false, โ"
+"โ "Dead": false, โ"
+"โ "Pid": 782, โ"
+"โ "ExitCode": 0, โ"
+"โ "Error": "", โ"
+"โ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ }, โ"
+"โ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ"
+"โ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ"
+"โ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ"
+"โ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ"
+"โ "Name": "/postgres", โ"
+"โ "RestartCount": 0, โ"
+"โ "Driver": "overlay2", โ"
+"โ "Platform": "linux", โ"
+"โ "MountLabel": "", โ"
+"โ "ProcessLabel": "", โ"
+"โ "AppArmorProfile": "", โ"
+"โ "HostConfig": { โ"
+"โ "CpuShares": 0, โ"
+"โ "Memory": 1073741824, โ"
+"โ "CgroupParent": "", โ"
+"โ "BlkioWeight": 0, โ"
+"โ "CpuPeriod": 0, โ"
+"โ "CpuQuota": 0, โ"
+"โ "CpuRealtimePeriod": 0, โ"
+"โ "CpuRealtimeRuntime": 0, โ"
+"โ "CpusetCpus": "", โ"
+"โ "CpusetMems": "", โ"
+"โ "MemoryReservation": 0, โ"
+"โ "MemorySwap": 2147483648, โ"
+"โ "NanoCpus": 0, โ"
+"โ "OomKillDisable": false, โ"
+"โ "CpuCount": 0, โ"
+"โ "CpuPercent": 0, โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 0/158 โ 0/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_one.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_one.snap
new file mode 100644
index 0000000..f1144ec
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_one.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - c or Esc or 4 to exit โโโโโโโโโโโโโโโโโโโโโโฎ"
+"โ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ"
+"โ "Created": "2026-01-23T22:20:19.927967311Z", โ"
+"โ "Path": "docker-entrypoint.sh", โ"
+"โ "Args": [ โ"
+"โ "postgres" โ"
+"โ ], โ"
+"โ "State": { โ"
+"โ "Status": "running", โ"
+"โ "Running": true, โ"
+"โ "Paused": false, โ"
+"โ "Restarting": false, โ"
+"โ "OOMKilled": false, โ"
+"โ "Dead": false, โ"
+"โ "Pid": 782, โ"
+"โ "ExitCode": 0, โ"
+"โ "Error": "", โ"
+"โ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ }, โ"
+"โ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ"
+"โ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ"
+"โ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ"
+"โ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ"
+"โ "Name": "/postgres", โ"
+"โ "RestartCount": 0, โ"
+"โ "Driver": "overlay2", โ"
+"โ "Platform": "linux", โ"
+"โ "MountLabel": "", โ"
+"โ "ProcessLabel": "", โ"
+"โ "AppArmorProfile": "", โ"
+"โ "HostConfig": { โ"
+"โ "CpuShares": 0, โ"
+"โ "Memory": 1073741824, โ"
+"โ "CgroupParent": "", โ"
+"โ "BlkioWeight": 0, โ"
+"โ "CpuPeriod": 0, โ"
+"โ "CpuQuota": 0, โ"
+"โ "CpuRealtimePeriod": 0, โ"
+"โ "CpuRealtimeRuntime": 0, โ"
+"โ "CpusetCpus": "", โ"
+"โ "CpusetMems": "", โ"
+"โ "MemoryReservation": 0, โ"
+"โ "MemorySwap": 2147483648, โ"
+"โ "NanoCpus": 0, โ"
+"โ "OomKillDisable": false, โ"
+"โ "CpuCount": 0, โ"
+"โ "CpuPercent": 0, โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 0/158 โ 0/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_two.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_two.snap
new file mode 100644
index 0000000..e2eb4bc
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_custom_keymap_inspect_two.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - c or Esc or 4 or 5 to exit โโโโโโโโโโโโโโโโโโโโฎ"
+"โ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ"
+"โ "Created": "2026-01-23T22:20:19.927967311Z", โ"
+"โ "Path": "docker-entrypoint.sh", โ"
+"โ "Args": [ โ"
+"โ "postgres" โ"
+"โ ], โ"
+"โ "State": { โ"
+"โ "Status": "running", โ"
+"โ "Running": true, โ"
+"โ "Paused": false, โ"
+"โ "Restarting": false, โ"
+"โ "OOMKilled": false, โ"
+"โ "Dead": false, โ"
+"โ "Pid": 782, โ"
+"โ "ExitCode": 0, โ"
+"โ "Error": "", โ"
+"โ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ }, โ"
+"โ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ"
+"โ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ"
+"โ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ"
+"โ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ"
+"โ "Name": "/postgres", โ"
+"โ "RestartCount": 0, โ"
+"โ "Driver": "overlay2", โ"
+"โ "Platform": "linux", โ"
+"โ "MountLabel": "", โ"
+"โ "ProcessLabel": "", โ"
+"โ "AppArmorProfile": "", โ"
+"โ "HostConfig": { โ"
+"โ "CpuShares": 0, โ"
+"โ "Memory": 1073741824, โ"
+"โ "CgroupParent": "", โ"
+"โ "BlkioWeight": 0, โ"
+"โ "CpuPeriod": 0, โ"
+"โ "CpuQuota": 0, โ"
+"โ "CpuRealtimePeriod": 0, โ"
+"โ "CpuRealtimeRuntime": 0, โ"
+"โ "CpusetCpus": "", โ"
+"โ "CpusetMems": "", โ"
+"โ "MemoryReservation": 0, โ"
+"โ "MemorySwap": 2147483648, โ"
+"โ "NanoCpus": 0, โ"
+"โ "OomKillDisable": false, โ"
+"โ "CpuCount": 0, โ"
+"โ "CpuPercent": 0, โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 0/158 โ 0/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_default_valid.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_default_valid.snap
new file mode 100644
index 0000000..0c5cf65
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_default_valid.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - c or Esc or i to exit โโโโโโโโโโโโโโโโโโโโโโฎ"
+"โ "Id": "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7", โ"
+"โ "Created": "2026-01-23T22:20:19.927967311Z", โ"
+"โ "Path": "docker-entrypoint.sh", โ"
+"โ "Args": [ โ"
+"โ "postgres" โ"
+"โ ], โ"
+"โ "State": { โ"
+"โ "Status": "running", โ"
+"โ "Running": true, โ"
+"โ "Paused": false, โ"
+"โ "Restarting": false, โ"
+"โ "OOMKilled": false, โ"
+"โ "Dead": false, โ"
+"โ "Pid": 782, โ"
+"โ "ExitCode": 0, โ"
+"โ "Error": "", โ"
+"โ "StartedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โ "FinishedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ }, โ"
+"โ "Image": "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โ "ResolvConfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c853โ"
+"โ "HostnamePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358โ"
+"โ "HostsPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456โ"
+"โ "LogPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456ccโ"
+"โ "Name": "/postgres", โ"
+"โ "RestartCount": 0, โ"
+"โ "Driver": "overlay2", โ"
+"โ "Platform": "linux", โ"
+"โ "MountLabel": "", โ"
+"โ "ProcessLabel": "", โ"
+"โ "AppArmorProfile": "", โ"
+"โ "HostConfig": { โ"
+"โ "CpuShares": 0, โ"
+"โ "Memory": 1073741824, โ"
+"โ "CgroupParent": "", โ"
+"โ "BlkioWeight": 0, โ"
+"โ "CpuPeriod": 0, โ"
+"โ "CpuQuota": 0, โ"
+"โ "CpuRealtimePeriod": 0, โ"
+"โ "CpuRealtimeRuntime": 0, โ"
+"โ "CpusetCpus": "", โ"
+"โ "CpusetMems": "", โ"
+"โ "MemoryReservation": 0, โ"
+"โ "MemorySwap": 2147483648, โ"
+"โ "NanoCpus": 0, โ"
+"โ "OomKillDisable": false, โ"
+"โ "CpuCount": 0, โ"
+"โ "CpuPercent": 0, โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 0/158 โ 0/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset.snap
new file mode 100644
index 0000000..503c826
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - c or Esc or i to exit โโโโโโโโโโโโโโโโโโโโโโฎ"
+"โrting": false, โ"
+"โlled": false, โ"
+"โ: false, โ"
+"โ 782, โ"
+"โode": 0, โ"
+"โ": "", โ"
+"โedAt": "2026-01-30T08:09:01.574885915Z", โ"
+"โhedAt": "2026-01-30T08:09:01.180567927Z" โ"
+"โ โ"
+"โ "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352", โ"
+"โonfPath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bbโ"
+"โePath": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60โ"
+"โth": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/โ"
+"โ": "/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/0bโ"
+"โ"/postgres", โ"
+"โCount": 0, โ"
+"โ: "overlay2", โ"
+"โm": "linux", โ"
+"โbel": "", โ"
+"โLabel": "", โ"
+"โrProfile": "", โ"
+"โfig": { โ"
+"โares": 0, โ"
+"โy": 1073741824, โ"
+"โpParent": "", โ"
+"โWeight": 0, โ"
+"โriod": 0, โ"
+"โota": 0, โ"
+"โaltimePeriod": 0, โ"
+"โaltimeRuntime": 0, โ"
+"โtCpus": "", โ"
+"โtMems": "", โ"
+"โyReservation": 0, โ"
+"โySwap": 2147483648, โ"
+"โpus": 0, โ"
+"โllDisable": false, โ"
+"โunt": 0, โ"
+"โrcent": 0, โ"
+"โimumIOps": 0, โ"
+"โimumBandwidth": 0, โ"
+"โinerIDFile": "", โ"
+"โnfig": { โ"
+"โe": "json-file", โ"
+"โfig": {} โ"
+"โ โ"
+"โrkMode": "oxker-examaple-net", โ"
+"โindings": {}, โ"
+"โrtPolicy": { โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ 10/158 โ โ 10/972 โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset_max.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset_max.snap
new file mode 100644
index 0000000..b7e3d72
--- /dev/null
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__inspect__tests__draw_blocks_inspect_offset_max.snap
@@ -0,0 +1,54 @@
+---
+source: src/ui/draw_blocks/inspect.rs
+expression: setup.terminal.backend()
+---
+"โญโโโโโโโโโโโโโโโโโโโโโ inspecting: postgres 0bdea642 - c or Esc or i to exit โโโโโโโโโโโโโโโโโโโโโโฎ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ 158/158 โ 972/972 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap
index df648f6..b9fa938 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
"โ โ"
"โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโขโข โโ100.00 kBโ โขโข โโ ip private publicโ"
-"โ โ โขโข โข โโ โ โขโข โข โโ 8001 โ"
-"โ โ โขโขโข โข โข โโ โ โขโขโข โข โข โโ127.0.0.1 8003 8003โ"
-"โ โโข โขโข โโ โโข โขโข โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโฎโญโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โขโข โโ100.00 kBโ โขโข โโ โโขโขโขโขโขโขโข โ โ ip private publicโ"
+"โ โ โขโขโข โโ โ โขโขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โขโข โข โโ โ โขโข โข โโ0.00 kb/sโ โขโข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโข โข โโ โโข โข โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap
index 37af4fd..0f52b1d 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap
@@ -32,13 +32,13 @@ expression: setup.terminal.backend()
"โ โ"
"โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโข โโ100.00 kBโ โขโข โโ ip private publicโ"
-"โ โ โข โข โโ โ โขโข โโ 8001 โ"
-"โ โ โขโข โข โโ โ โขโข โข โโ127.0.0.1 8003 8003โ"
-"โ โ โข โข โโ โ โข โข โโ โ"
-"โ โ โขโข โข โข โโ โ โขโข โข โข โโ โ"
-"โ โโข โขโข โโ โโข โขโข โโ โ"
-"โ โโข โข โโ โโข โข โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโฎโญโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โข โโ โโขโขโขโขโขโขโข โ โ ip private publicโ"
+"โ โ โขโข โโ โ โขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โข โข โโ โ โข โข โโ โ โขโข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โข โโ โ โข โข โโ0.00 kb/sโ โขโข โ โ โ"
+"โ โ โข โข โโ โ โข โข โโ โ โข โ โ โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโขโข โข โโ โโขโข โขโข โโ โ โข โ โ โ"
+"โ โ โโ โ โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap
index 49919e8..90db02f 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap
@@ -32,13 +32,13 @@ expression: setup.terminal.backend()
"โ โ"
"โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโข โโ100.00 kBโ โขโข โโ ip private publicโ"
-"โ โ โข โข โโ โ โขโข โโ 8001 โ"
-"โ โ โขโข โข โโ โ โขโข โข โโ127.0.0.1 8003 8003โ"
-"โ โ โข โข โโ โ โข โข โโ โ"
-"โ โ โขโข โข โข โโ โ โขโข โข โข โโ โ"
-"โ โโข โขโข โโ โโข โขโข โโ โ"
-"โ โโข โข โโ โโข โข โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโฎโญโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โข โโ โโขโขโขโขโขโขโข โ โ ip private publicโ"
+"โ โ โขโข โโ โ โขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โข โข โโ โ โข โข โโ โ โขโข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โข โโ โ โข โข โโ0.00 kb/sโ โขโข โ โ โ"
+"โ โ โข โข โโ โ โข โข โโ โ โข โ โ โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโขโข โข โโ โโขโข โขโข โโ โ โข โ โ โ"
+"โ โ โโ โ โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap
index a69c2d8..706c766 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap
@@ -4,41 +4,41 @@ expression: setup.terminal.backend()
---
" name state status cpu memory/limit id image โ rx โ tx ( h ) exit help "
"โญ Containers 1/3 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโฎ"
-"โโช container_1 โ running Up 1 hoโญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ โโโถ pause โ" Hidden by multi-width symbols: [(2, " ")]
-"โ container_2 โ running Up 2 hoโ โ โโ restart โ"
-"โ container_3 โ running Up 3 hoโ 88 โ โโ stop โ"
-"โ โ 88 โ โโ delete โ"
-"โ โ 88 โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, โโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโฏ"
-"โญ Logs 3/3 - container_1 - image_1 โโโ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ"
-"โ line 1 โ 8b d8 )888( 8888[ 8PP""""""" 88 โ โ"
-"โ line 2 โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ โ"
-"โโถ line 3 โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ โ"
-"โ โ โ โ"
-"โ โ A simple tui to view & control docker containers โ โ"
-"โ โ โ โ"
-"โ โ ( tab ) or ( shift+tab ) change panels โ โ"
-"โ โ ( โ โ ) or ( j k ) or ( Home End ) scroll vertically โ โ"
-"โ โ ( โ โ ) horizontal scroll across logs โ โ"
-"โ โ ( ctrl ) increase scroll speed, used in conjunction scroll keys โ โ"
-"โ โ ( enter ) send docker container command โ โ"
-"โ โ ( e ) exec into a container โ โ"
-"โ โ ( f ) force clear the screen & redraw the gui โ โ"
-"โ โ ( h ) toggle this help information - or click heading โ โ"
-"โ โ ( s ) save logs to file โ โ"
-"โ โ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied โ โ"
-"โ โ ( F1 ) or ( / ) enter filter mode โ โ"
-"โ โ ( # ) enter log search mode โ โ"
-"โ โ ( 0 ) stop sort โ โ"
-"โ โ ( 1 - 9 ) sort by header - or click header โ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ ( - = ) change log section height โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00%โ ( \ ) toggle log section visibility โโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโข โ ( esc ) close dialog โ โโ ip private publicโ"
-"โ โ โข โข โ ( q ) quit at any time โ โโ 8001 โ"
-"โ โ โขโข โข โ โ โโ127.0.0.1 8003 8003โ"
-"โ โ โข โข โ currently an early work in progress, all and any input appreciated โ โโ โ"
-"โ โ โขโข โข โข โ https://github.com/mrjackwills/oxker โ โโ โ"
-"โ โโข โขโข โ โ โโ โ"
-"โ โโข โข โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โโช container_1 โ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB โโโถ pause โ" Hidden by multi-width symbols: [(2, " ")]
+"โ container_2 โ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB โโ restart โ"
+"โ container_3 โ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB โโ stop โ"
+"โ โโ delete โ"
+"โ โโ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโฏ"
+"โญ Logs 3/3 - container_โญ 0.00.000 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎโโโโโโโโโโโโโโโโโโโโโโโฎ"
+"โ line 1 โ 88 โ โ"
+"โ line 2 โ 88 โ โ"
+"โโถ line 3 โ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYb โ โ"
+"โ โ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' " โ โ"
+"โ โ 8b d8 )888( 8888( 8PP""""""" 88 โ โ"
+"โ โ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 โ โ"
+"โ โ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 โ โ"
+"โ โ a work in progress, all and any input appreciated โ โ"
+"โ โ A simple tui to view & control docker containers https://github.com/mrjackwills/oxker โ โ"
+"โ โ โ โ"
+"โ โ Keymap โ โ"
+"โ โ q quit c Esc close dialog โ โ"
+"โ โ Down Up j k Home End scroll vertically Left Right scroll horizontally โ โ"
+"โ โ Control increase scroll speed Enter send docker command โ โ"
+"โ โ e exec into a container i container inspect mode โ โ"
+"โ โ / F1 filter mode # log search mode โ โ"
+"โ โ h toggle this panel f force clear screen and redraw โ โ"
+"โ โ - = change log section height \ toggle of section visibility โ โ"
+"โ โ 1 ~ 9 sort by header - or click header 0 stop sort โ โ"
+"โ โ Tab Back Tab change panel m toggle mouse capture - allows text selection โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโ s save logs to file โโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโ cpu 03.โ โโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ ip private publicโ"
+"โ โ โขโข โโ โ โขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โข โข โโ โ โข โข โโ โ โขโข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โข โโ โ โข โข โโ0.00 kb/sโ โขโข โ โ โ"
+"โ โ โข โข โโ โ โข โข โโ โ โข โ โ โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโขโข โข โโ โโขโข โขโข โโ โ โข โ โ โ"
+"โ โ โโ โ โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap
index a9abbf8..b12a958 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap
@@ -32,13 +32,13 @@ expression: setup.terminal.backend()
"โ โ"
"โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโข โโ100.00 kBโ โขโข โโ ip private publicโ"
-"โ โ โข โข โโ โ โขโข โโ 8001 โ"
-"โ โ โขโข โข โโ โ โขโข โข โโ127.0.0.1 8003 8003โ"
-"โ โ โข โข โโ โ โข โข โโ โ"
-"โ โ โขโข โข โข โโ โ โขโข โข โข โโ โ"
-"โ โโข โขโข โโ โโข โขโข โโ "
-"โ โโข โข โโ โโข โข โโ This is a test "
-"โ โ โโ โ โโ "
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโ "
+"โญโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโฎโญโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โข โโ โโขโขโขโขโขโขโข โ โ ip private publicโ"
+"โ โ โขโข โโ โ โขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โข โข โโ โ โข โข โโ โ โขโข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โข โโ โ โข โข โโ0.00 kb/sโ โขโข โ โ โ"
+"โ โ โข โข โโ โ โข โข โโ โ โข โ โ โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ "
+"โ โโขโข โข โโ โโขโข โขโข โโ โ โข โ โ This is a test "
+"โ โ โโ โ โโ โ โข โ โ "
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโ "
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap
index e0a34fb..e7b9019 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
"โ โ"
"โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโขโข โโ100.00 kBโ โขโขโขโข โโ ip private publicโ"
-"โ โ โขโขโข โข โโ โ โขโข โข โโ 8001 โ"
-"โ โ โขโขโข โข โข โโ โ โขโขโข โข โข โโ127.0.0.1 8003 8003โ"
-"โ โโขโข โขโขโข โโ โโขโข โขโข โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โข โโ100.00 kBโ โขโข โโ โโขโขโขโขโขโข โข โ โ ip private publicโ"
+"โ โ โขโขโข โโ โ โขโขโข โโ โ โข โข โ โ 8001 โ"
+"โ โ โขโขโข โข โโ โ โขโข โข โโ0.00 kb/sโ โข โข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โขโขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโข โข โโ โโข โข โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap
index fd83747..e0131b5 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
"โ โโ โ"
"โ โโ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโขโข โโ100.00 kBโ โขโข โโ ip private publicโ"
-"โ โ โขโข โข โโ โ โขโข โข โโ 8001 โ"
-"โ โ โขโขโข โข โข โโ โ โขโขโข โข โข โโ127.0.0.1 8003 8003โ"
-"โ โโข โขโข โโ โโข โขโข โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโฎโญโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โขโข โโ100.00 kBโ โขโข โโ โโขโขโขโขโขโขโข โ โ ip private publicโ"
+"โ โ โขโขโข โโ โ โขโขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โขโข โข โโ โ โขโข โข โโ0.00 kb/sโ โขโข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโข โข โโ โโข โข โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap
index 046a12e..06bb20f 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
"โ line 2 โ"
"โโถ line 3 โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโขโข โโ100.00 kBโ โขโข โโ ip private publicโ"
-"โ โ โขโข โข โโ โ โขโข โข โโ 8001 โ"
-"โ โ โขโขโข โข โข โโ โ โขโขโข โข โข โโ127.0.0.1 8003 8003โ"
-"โ โโข โขโข โโ โโข โขโข โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโฎโญโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โขโข โโ100.00 kBโ โขโข โโ โโขโขโขโขโขโขโข โ โ ip private publicโ"
+"โ โ โขโขโข โโ โ โขโขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โขโข โข โโ โ โขโข โข โโ0.00 kb/sโ โขโข โ โ127.0.0.1 8003 8003โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโข โข โโ โโข โข โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap
index 4a576be..d3a3751 100644
--- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap
+++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap
@@ -24,11 +24,11 @@ expression: setup.terminal.backend()
"โ โ"
"โ โ"
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
-"โญโโโโโโโโโโโโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโโโโโโโโโโโโฎโญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
-"โ10.00%โ โขโขโข โโ100.00 kBโ โขโข โโ ip private publicโ"
-"โ โ โขโข โข โโ โ โขโข โข โโ 8001 โ"
-"โ โ โขโขโข โข โข โโ โ โขโขโข โข โข โโ โ"
-"โ โโข โขโข โโ โโข โขโข โโ โ"
-"โ โ โโ โ โโ โ"
-"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
+"โญโโโโโโโโโโโโโโ cpu 03.00% โโโโโโโโโโโโโโโโฎโญโโโโโโโโโโโโ memory 30.00 kB โโโโโโโโโโโโโฎโญโโโโโโ rx: 0.00 kb/s tx: 0.00 kb/s โโโโโโโฎ โญโโโโโโโโโโ ports โโโโโโโโโโโโฎ"
+"โ10.00%โ โขโข โโ100.00 kBโ โขโข โโ โโขโขโขโขโขโขโข โ โ ip private publicโ"
+"โ โ โขโขโข โโ โ โขโขโข โโ โ โขโข โ โ 8001 โ"
+"โ โ โขโข โข โโ โ โขโข โข โโ0.00 kb/sโ โขโข โ โ โ"
+"โ โ โข โขโข โโ โ โข โขโข โโ0.00 kb/sโ โข โ โ โ"
+"โ โโข โข โโ โโข โข โโ โ โข โ โ โ"
+"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏโฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
" Esc clear โ by โ Name Image Status All filter term: r_1 "
diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs
index 9b598c4..46ad5f1 100644
--- a/src/ui/gui_state.rs
+++ b/src/ui/gui_state.rs
@@ -9,7 +9,7 @@ use tokio::task::JoinHandle;
use uuid::Uuid;
use crate::{
- app_data::{AppData, ContainerId, Header},
+ app_data::{AppData, ContainerId, Header, ScrollDirection},
exec::ExecMode,
};
@@ -160,19 +160,26 @@ const FRAMES_LEN: u8 = 9;
/// 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)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Status {
DeleteConfirm,
- DockerConnect,
+ DockerConnect(Option),
Error,
Exec,
Filter,
Help,
Init,
+ Inspect,
Logs,
SearchLogs,
}
+#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct ScrollOffset {
+ pub x: usize,
+ pub y: usize,
+}
+
/// Global gui_state, stored in an Arc
#[derive(Debug)]
pub struct GuiState {
@@ -190,6 +197,8 @@ pub struct GuiState {
selected_panel: SelectablePanel,
screen_width: u16,
show_logs: bool,
+ inspect_offset: ScrollOffset,
+ inspect_offset_max: ScrollOffset,
status: HashSet,
pub info_box_text: Option<(String, Instant)>,
}
@@ -203,6 +212,8 @@ impl GuiState {
intersect_heading: HashMap::new(),
intersect_help: None,
intersect_panel: HashMap::new(),
+ inspect_offset: ScrollOffset::default(),
+ inspect_offset_max: ScrollOffset::default(),
loading_handle: None,
loading_index: 0,
loading_set: HashSet::new(),
@@ -235,6 +246,50 @@ impl GuiState {
}
}
+ pub fn set_inspect_offset(&mut self, sd: &ScrollDirection) {
+ match sd {
+ ScrollDirection::Up => self.inspect_offset.y = self.inspect_offset.y.saturating_sub(1),
+ ScrollDirection::Down => {
+ self.inspect_offset.y = self
+ .inspect_offset
+ .y
+ .saturating_add(1)
+ .min(self.inspect_offset_max.y)
+ }
+ ScrollDirection::Left => {
+ self.inspect_offset.x = self.inspect_offset.x.saturating_sub(1)
+ }
+ ScrollDirection::Right => {
+ self.inspect_offset.x = self
+ .inspect_offset
+ .x
+ .saturating_add(1)
+ .min(self.inspect_offset_max.x)
+ }
+ }
+ self.rerender.update_draw();
+ }
+
+ pub fn get_inspect_offset(&self) -> ScrollOffset {
+ self.inspect_offset
+ }
+
+ pub fn set_inspect_offset_max(&mut self, offset: ScrollOffset) {
+ self.inspect_offset_max = offset
+ }
+
+ pub fn set_inspect_offset_y_to_max(&mut self) {
+ self.inspect_offset.y = self.inspect_offset_max.y;
+ self.rerender.update_draw();
+ }
+
+ pub fn clear_inspect_offset(&mut self) {
+ self.inspect_offset.x = 0;
+ self.inspect_offset.y = 0;
+ self.inspect_offset_max = ScrollOffset::default();
+ self.rerender.update_draw();
+ }
+
/// Set the screen width, used for offset char calculations
pub const fn set_screen_width(&mut self, width: u16) {
self.screen_width = width;
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index bb7a458..096191c 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -30,12 +30,11 @@ pub use self::color_match::*;
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
use crate::{
app_data::{
- AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, LogSearch,
- MemTuple, SortedOrder, State,
+ AppData, ChartsData, Columns, ContainerId, ContainerPorts, FilterBy, Header, LogSearch,
+ SortedOrder, State,
},
app_error::AppError,
config::{AppColors, Keymap},
- exec::TerminalSize,
input_handler::InputMessages,
};
@@ -132,12 +131,12 @@ impl Ui {
}
/// Draw the the error message ui, for 5 seconds, with a countdown
- fn err_loop(&mut self) -> Result<(), AppError> {
+ async fn err_loop(&mut self, host: Option) -> Result<(), AppError> {
let mut seconds = 5;
let colors = self.app_data.lock().config.app_colors;
let keymap = self.app_data.lock().config.keymap.clone();
let mut redraw = true;
- loop {
+ while self.is_running.load(Ordering::SeqCst) {
if self.now.elapsed() >= std::time::Duration::from_secs(1) {
seconds -= 1;
self.now = Instant::now();
@@ -155,6 +154,7 @@ impl Ui {
colors,
&AppError::DockerConnect,
f,
+ host.clone(),
&keymap,
Some(seconds),
);
@@ -163,8 +163,18 @@ impl Ui {
{
return Err(AppError::Terminal);
}
+ if crossterm::event::poll(POLL_RATE).unwrap_or(false)
+ && let Ok(event) = event::read()
+ && let Event::Key(key) = event
+ && key.kind == event::KeyEventKind::Press
+ {
+ self.input_tx
+ .send(InputMessages::ButtonPress((key.code, key.modifiers)))
+ .await
+ .ok();
+ }
redraw = false;
- std::thread::sleep(POLL_RATE);
+ // std::thread::sleep(POLL_RATE);
}
Ok(())
}
@@ -183,7 +193,8 @@ impl Ui {
if let Some(mode) = exec_mode {
self.reset_terminal().ok();
self.terminal.clear().ok();
- if let Err(e) = mode.run(TerminalSize::new(&self.terminal)).await {
+
+ if let Err(e) = mode.run(self.terminal.size().ok()).await {
self.app_data
.lock()
.set_error(e, &self.gui_state, Status::Error);
@@ -237,32 +248,32 @@ impl Ui {
}
}
- if crossterm::event::poll(POLL_RATE).unwrap_or(false) {
- if let Ok(event) = event::read() {
- if let Event::Key(key) = event {
- if key.kind == event::KeyEventKind::Press {
+ if crossterm::event::poll(POLL_RATE).unwrap_or(false)
+ && let Ok(event) = event::read()
+ {
+ if let Event::Key(key) = event {
+ if key.kind == event::KeyEventKind::Press {
+ self.input_tx
+ .send(InputMessages::ButtonPress((key.code, key.modifiers)))
+ .await
+ .ok();
+ }
+ } else if let Event::Mouse(m) = event {
+ match m.kind {
+ event::MouseEventKind::Down(_)
+ | event::MouseEventKind::ScrollDown
+ | event::MouseEventKind::ScrollUp => {
self.input_tx
- .send(InputMessages::ButtonPress((key.code, key.modifiers)))
+ .send(InputMessages::MouseEvent((m, m.modifiers)))
.await
.ok();
}
- } else if let Event::Mouse(m) = event {
- match m.kind {
- event::MouseEventKind::Down(_)
- | event::MouseEventKind::ScrollDown
- | event::MouseEventKind::ScrollUp => {
- self.input_tx
- .send(InputMessages::MouseEvent((m, m.modifiers)))
- .await
- .ok();
- }
- _ => (),
- }
- } else if let Event::Resize(width, _) = event {
- self.gui_state.lock().clear_area_map();
- self.terminal.autoresize().ok();
- self.gui_state.lock().set_screen_width(width);
+ _ => (),
}
+ } else if let Event::Resize(width, _) = event {
+ self.gui_state.lock().clear_area_map();
+ self.terminal.autoresize().ok();
+ self.gui_state.lock().set_screen_width(width);
}
}
self.check_clear();
@@ -273,8 +284,11 @@ impl Ui {
/// Draw either the Error, or main oxker ui, to the terminal
async fn draw_ui(&mut self) -> Result<(), AppError> {
let status = self.gui_state.lock().get_status();
- if status.contains(&Status::DockerConnect) {
- self.err_loop()?;
+ if let Some(Status::DockerConnect(msg)) = status
+ .iter()
+ .find(|s| matches!(s, Status::DockerConnect(_)))
+ {
+ self.err_loop(msg.clone()).await?;
} else {
self.gui_loop().await?;
}
@@ -287,7 +301,7 @@ impl Ui {
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct FrameData {
- chart_data: Option<(CpuTuple, MemTuple)>,
+ chart_data: Option,
color_logs: bool,
columns: Columns,
container_title: String,
@@ -355,114 +369,122 @@ fn draw_frame(
let contains_filter = fd.status.contains(&Status::Filter);
let contains_search_logs = fd.status.contains(&Status::SearchLogs);
- let whole_layout = Layout::default()
- .direction(Direction::Vertical)
- .constraints(if contains_filter || contains_search_logs {
- vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)]
- } else {
- vec![Constraint::Max(1), Constraint::Min(1)]
- })
- .split(f.area());
+ let contains_inspect = fd.status.contains(&Status::Inspect);
- draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap);
+ let inspect_data = app_data.lock().get_inspect_data();
+ if contains_inspect && let Some(inspect_data) = inspect_data {
+ draw_blocks::inspect::draw(f, colors, inspect_data, gui_state, keymap);
+ } else {
+ let whole_layout = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(if contains_filter || contains_search_logs {
+ vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)]
+ } else {
+ vec![Constraint::Max(1), Constraint::Min(1)]
+ })
+ .split(f.area());
- if let Some(rect) = whole_layout.get(2) {
- if contains_filter {
- draw_blocks::filter::draw(*rect, colors, f, fd);
- } else {
- draw_blocks::search_logs::draw(*rect, colors, f, fd, keymap);
+ draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap);
+
+ if let Some(rect) = whole_layout.get(2) {
+ if contains_filter {
+ draw_blocks::filter::draw(*rect, colors, f, fd);
+ } else {
+ draw_blocks::search_logs::draw(*rect, colors, f, fd, keymap);
+ }
}
- }
- let upper_main = Layout::default()
- .direction(Direction::Vertical)
- .constraints(if fd.has_containers {
- vec![Constraint::Percentage(75), Constraint::Percentage(25)]
- } else {
- vec![Constraint::Percentage(100), Constraint::Percentage(0)]
- })
- .split(whole_layout[1]);
+ let upper_main = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(if fd.has_containers {
+ vec![Constraint::Percentage(75), Constraint::Percentage(25)]
+ } else {
+ vec![Constraint::Percentage(100), Constraint::Percentage(0)]
+ })
+ .split(whole_layout[1]);
- let containers_logs_section = Layout::default()
- .direction(Direction::Vertical)
- .constraints(if fd.show_logs {
- vec![Constraint::Min(6), Constraint::Percentage(fd.log_height)]
- } else {
- vec![Constraint::Percentage(100)]
- })
- .split(upper_main[0]);
+ let containers_logs_section = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(if fd.show_logs {
+ vec![Constraint::Min(6), Constraint::Percentage(fd.log_height)]
+ } else {
+ vec![Constraint::Percentage(100)]
+ })
+ .split(upper_main[0]);
- // Containers + docker commands
- let containers_commands = Layout::default()
- .direction(Direction::Horizontal)
- .constraints(if fd.has_containers {
- vec![Constraint::Percentage(90), Constraint::Percentage(10)]
- } else {
- vec![Constraint::Percentage(100)]
- })
- .split(containers_logs_section[0]);
-
- draw_blocks::containers::draw(app_data, containers_commands[0], colors, f, fd, gui_state);
-
- if fd.show_logs {
- draw_blocks::logs::draw(
- app_data,
- containers_logs_section[1],
- colors,
- f,
- fd,
- gui_state,
- );
- }
-
- if let Some(id) = fd.delete_confirm.as_ref() {
- app_data.lock().get_container_name_by_id(id).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::draw(colors, f, gui_state, keymap, name);
- },
- );
- }
-
- // only draw commands + charts if there are containers
- if let Some(rect) = containers_commands.get(1) {
- draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state);
-
- // Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border)
- let ports_len =
- u16::try_from(fd.port_max_lens.0 + fd.port_max_lens.1 + fd.port_max_lens.2 + 2)
- .unwrap_or(26);
-
- let lower = Layout::default()
+ // Containers + docker commands
+ let containers_commands = Layout::default()
.direction(Direction::Horizontal)
- .constraints([Constraint::Min(1), Constraint::Max(ports_len)])
- .split(upper_main[1]);
+ .constraints(if fd.has_containers {
+ vec![Constraint::Percentage(90), Constraint::Percentage(10)]
+ } else {
+ vec![Constraint::Percentage(100)]
+ })
+ .split(containers_logs_section[0]);
- draw_blocks::charts::draw(lower[0], colors, f, fd);
- draw_blocks::ports::draw(lower[1], colors, f, fd);
+ draw_blocks::containers::draw(app_data, containers_commands[0], colors, f, fd, gui_state);
+
+ if fd.show_logs {
+ draw_blocks::logs::draw(
+ app_data,
+ containers_logs_section[1],
+ colors,
+ f,
+ fd,
+ gui_state,
+ );
+ }
+
+ if let Some(id) = fd.delete_confirm.as_ref() {
+ app_data.lock().get_container_name_by_id(id).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::draw(colors, f, gui_state, keymap, name);
+ },
+ );
+ }
+
+ // only draw commands + charts if there are containers
+ if let Some(rect) = containers_commands.get(1) {
+ draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state);
+
+ // Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border)
+ let ports_len =
+ u16::try_from(fd.port_max_lens.0 + fd.port_max_lens.1 + fd.port_max_lens.2 + 2)
+ .unwrap_or(26);
+
+ let lower = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([Constraint::Min(1), Constraint::Max(ports_len)])
+ .split(upper_main[1]);
+
+ let charts_rect = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints([Constraint::Percentage(66), Constraint::Percentage(33)])
+ .split(lower[0]);
+
+ draw_blocks::chart_cpu_mem::draw(charts_rect[0], colors, f, fd);
+ draw_blocks::chart_bandwidth::draw(charts_rect[1], colors, f, fd);
+
+ draw_blocks::ports::draw(lower[1], colors, f, fd);
+ }
+
+ // Check if error, and show popup if so
+ if fd.status.contains(&Status::Help) {
+ let config = app_data.lock().config.clone();
+ draw_blocks::help::draw(&config, f);
+ }
}
if let Some((text, instant)) = fd.info_text.as_ref() {
draw_blocks::info::draw(colors, f, gui_state, instant, text.to_owned());
}
- // Check if error, and show popup if so
- if fd.status.contains(&Status::Help) {
- let tz = app_data.lock().config.timezone.clone();
- draw_blocks::help::draw(
- colors,
- f,
- keymap,
- app_data.lock().config.show_timestamp,
- tz.as_ref(),
- );
- }
-
if let Some(error) = fd.has_error.as_ref() {
- draw_blocks::error::draw(colors, error, f, keymap, None);
+ draw_blocks::error::draw(colors, error, f, None, keymap, None);
}
}