chore: merge release-v0.13.0 into main
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 4.1 MiB |
+18
-11
@@ -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
|
### Chores
|
||||||
+ create_release.sh updated, [d4af754ad245540db60177f7b202b3c64519c961]
|
+ dependencies updated, [4658a8de264698b0c8092e1227f0683527219a0b], [8b5899ca238bcbff32519b376b920cd7b7509809], [bebb687c59f3b408e69b23d2e68fa69f006a3231]
|
||||||
+ dependencies updated, [03599b46657d38d0c9f25c2ccfd9510f2b98dd84], [aef0c9503e7045a256856aa887d8c8d7722b9936], [f0771eab5d07d141fe7a8997db650f0f65ffe0a7], [1596de8681ad6c0a7832eb922dd2dc36ab30eb41]
|
+ GitHub workflow updated, [a0aa7918241ee8f702d6472c620287aa4be7d56c]
|
||||||
+ GitHub workflow updated, [66dae5e61ea294ac8ce134a6c32b27c04166b6eb]
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
+ fix numerous typos, [618a43b501914fdf2659e171172ad180364cf87a]
|
|
||||||
|
|
||||||
### Features
|
### 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]
|
+ Network chart, closes #79, [99fcb8fedf01599ec346b65d435d4c301a7a8851]
|
||||||
+ 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]
|
+ Inspect mode & help panel redesign, [ae7f3f4a9472b451c37c0ab97b1756b41a3529f5]
|
||||||
+ `term` renamed `filter term`, tests updated, [487c3faf96f4c197c8b82644c02466ea40626a5e]
|
+ 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 <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details
|
see <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
@@ -47,11 +47,19 @@ jobs:
|
|||||||
- name: install cross
|
- name: install cross
|
||||||
run: cargo install cross --git https://github.com/cross-rs/cross
|
run: cargo install cross --git https://github.com/cross-rs/cross
|
||||||
|
|
||||||
# Build binary for arm MacOS using Docker Zigbuild
|
|
||||||
- name: build
|
- name: build
|
||||||
if: matrix.target == 'aarch64-apple-darwin'
|
if: matrix.target == 'aarch64-apple-darwin'
|
||||||
run: |
|
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
|
# Build all other targets using Cross
|
||||||
- name: build
|
- name: build
|
||||||
@@ -72,7 +80,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload output for release page
|
# Upload output for release page
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
name: ${{ matrix.target }}
|
name: ${{ matrix.target }}
|
||||||
@@ -91,7 +99,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup | Artifacts
|
- name: Setup | Artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v7
|
||||||
|
|
||||||
- name: Update Release
|
- name: Update Release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.13.0'>v0.13.0</a>
|
||||||
|
### 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)
|
||||||
|
|
||||||
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.12.0'>v0.12.0</a>
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.12.0'>v0.12.0</a>
|
||||||
### 2025-09-28
|
### 2025-09-28
|
||||||
|
|
||||||
|
|||||||
Generated
+1203
-697
File diff suppressed because it is too large
Load Diff
+13
-11
@@ -1,12 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oxker"
|
name = "oxker"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["Jack Wills <email@mrjackwills.com>"]
|
authors = ["Jack Wills <email@mrjackwills.com>"]
|
||||||
description = "A simple tui to view & control docker containers"
|
description = "A simple tui to view & control docker containers"
|
||||||
repository = "https://github.com/mrjackwills/oxker"
|
repository = "https://github.com/mrjackwills/oxker"
|
||||||
homepage = "https://github.com/mrjackwills/oxker"
|
homepage = "https://github.com/mrjackwills/oxker"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
rust-version = "1.90.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["docker", "tui", "tokio", "terminal", "podman"]
|
keywords = ["docker", "tui", "tokio", "terminal", "podman"]
|
||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
@@ -15,8 +16,6 @@ categories = ["command-line-utilities"]
|
|||||||
unsafe_code = "forbid"
|
unsafe_code = "forbid"
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
nursery = { level = "warn", priority = -1 }
|
|
||||||
pedantic = { level = "warn", priority = -1 }
|
|
||||||
expect_used = "warn"
|
expect_used = "warn"
|
||||||
todo = "warn"
|
todo = "warn"
|
||||||
unused_async = "warn"
|
unused_async = "warn"
|
||||||
@@ -27,7 +26,7 @@ similar_names = "allow"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bollard = "0.19"
|
bollard = "0.20"
|
||||||
cansi = "2.2"
|
cansi = "2.2"
|
||||||
clap = { version = "4.5", features = ["color", "derive", "unicode"] }
|
clap = { version = "4.5", features = ["color", "derive", "unicode"] }
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
@@ -35,16 +34,20 @@ directories = "6.0"
|
|||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
jiff = { version = "0.2", features = ["tzdb-bundle-always"] }
|
jiff = { version = "0.2", features = ["tzdb-bundle-always"] }
|
||||||
parking_lot = { version = "0.12" }
|
parking_lot = { version = "0.12" }
|
||||||
ratatui = "0.29"
|
ratatui = "0.30"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = { version = "1.0"}
|
||||||
serde_jsonc = "1.0"
|
serde_jsonc = "1.0"
|
||||||
tokio = { version = "1.47", features = ["full"] }
|
tokio = { version = "1.49", features = ["full"] }
|
||||||
tokio-util = "0.7"
|
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 = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
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]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
@@ -53,5 +56,4 @@ panic = 'abort'
|
|||||||
strip = true
|
strip = true
|
||||||
debug = false
|
debug = false
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
insta = "1.42.2"
|
|
||||||
|
|||||||
@@ -10,10 +10,12 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align='center'>
|
<p align='center'>
|
||||||
|
<!-- ffmpeg -i input.mp4 -vf "scale='min(1000,iw)':-1:flags=lanczos,fps=2" -vcodec libwebp -lossless 0 -compression_level 4 -q:v 85 -loop 0 demo_01.webp -->
|
||||||
<a href='https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/demo_01.webp' target='_blank' rel='noopener noreferrer'>
|
<a href='https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/demo_01.webp' target='_blank' rel='noopener noreferrer'>
|
||||||
<img src='./.github/demo_01.webp' width='100%' alt='An animated demo of oxker in operation'/>
|
<img src='./.github/demo_01.webp' width='100%' alt='An animated demo of oxker in operation'/>
|
||||||
</a>
|
</a>
|
||||||
<sub>
|
<sub>
|
||||||
|
<!-- TODO update this -->
|
||||||
<a href="https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png" target='_blank' rel='noopener noreferrer'>
|
<a href="https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png" target='_blank' rel='noopener noreferrer'>
|
||||||
link to alternative screenshot
|
link to alternative screenshot
|
||||||
</a>
|
</a>
|
||||||
@@ -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.|
|
| ```( 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.|
|
| ```( ↑ ↓ )``` 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.|
|
| ```( ctrl )``` | Increase scroll speed, used in conjunction with scroll keys.|
|
||||||
| ```( enter )```| Run selected docker command.|
|
| ```( enter )```| Run selected docker command.|
|
||||||
| ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. |
|
| ```( 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.|
|
| ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.|
|
||||||
| ```( \ )``` | Toggle the visibility of the logs panel.|
|
| ```( \ )``` | Toggle the visibility of the logs panel.|
|
||||||
| ```( e )``` | Exec into the selected container - not available on Windows.|
|
| ```( e )``` | Exec into the selected container - not available on Windows.|
|
||||||
|
| ```( i )``` | Enter container inspect mode. |
|
||||||
| ```( f )``` | Force clear the screen & redraw the gui.|
|
| ```( f )``` | Force clear the screen & redraw the gui.|
|
||||||
| ```( h )``` | Toggle help menu.|
|
| ```( h )``` | Toggle help menu.|
|
||||||
| ```( m )``` | Toggle mouse capture - if disabled, text on screen can be selected.|
|
| ```( m )``` | Toggle mouse capture - if disabled, text on screen can be selected.|
|
||||||
|
|||||||
@@ -8,9 +8,15 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png" target='_blank' rel='noopener noreferrer'>
|
<a href="https://raw.githubusercontent.com/mrjackwills/oxer/main/.github/demo_01.webp" target='_blank' rel='noopener noreferrer'>
|
||||||
<img src='https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png' width='60%'/>
|
<img src='https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/demo_01.webp' width='450px'/>
|
||||||
</a>
|
</a>
|
||||||
|
<br>
|
||||||
|
<sub>
|
||||||
|
<a href="https://raw.githubusercontent.com/mrjackwills/oxker/main/.github/screenshot_01.png" target='_blank' rel='noopener noreferrer'>
|
||||||
|
link to alternative screenshot
|
||||||
|
</a>
|
||||||
|
</sub>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ FROM --platform=$BUILDPLATFORM rust:slim AS builder
|
|||||||
|
|
||||||
ARG TARGETARCH
|
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_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_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"
|
ENV CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER="arm-linux-gnueabihf-ld"
|
||||||
|
|||||||
+6
-2
@@ -232,8 +232,12 @@ cross_build_x86_windows() {
|
|||||||
# Build, using zig-build, for Apple silicon
|
# Build, using zig-build, for Apple silicon
|
||||||
zig_build_aarch64_apple() {
|
zig_build_aarch64_apple() {
|
||||||
# mkdir /workspace/oxker/target
|
# 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}"
|
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 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'
|
||||||
|
|
||||||
if ask_yn "sudo chown $(pwd)/target"; then
|
if ask_yn "sudo chown $(pwd)/target"; then
|
||||||
echo -e "${YELLOW}sudo chown -R vscode:vscode $(pwd)/target${RESET}"
|
echo -e "${YELLOW}sudo chown -R vscode:vscode $(pwd)/target${RESET}"
|
||||||
sudo chown -R vscode:vscode "$(pwd)/target"
|
sudo chown -R vscode:vscode "$(pwd)/target"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ networks:
|
|||||||
name: oxker-examaple-net
|
name: oxker-examaple-net
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17-alpine
|
image: postgres:18-alpine
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=never_use_this_password_in_production
|
- POSTGRES_PASSWORD=never_use_this_password_in_production
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
"show_timestamp": true,
|
"show_timestamp": true,
|
||||||
// Don't draw gui - for debugging - mostly pointless
|
// Don't draw gui - for debugging - mostly pointless
|
||||||
"gui": true,
|
"gui": true,
|
||||||
// Docker host location
|
// Docker host location. Will take priority over a DOCKER_HOST env.
|
||||||
"host": "/var/run/docker.sock",
|
// "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
|
// 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/
|
// *Should* accept any valid strftime string up to 32 chars, see https://strftime.org/
|
||||||
"timestamp_format": "%Y-%m-%dT%H:%M:%S.%8f",
|
"timestamp_format": "%Y-%m-%dT%H:%M:%S.%8f",
|
||||||
@@ -104,10 +104,10 @@
|
|||||||
"k"
|
"k"
|
||||||
],
|
],
|
||||||
// Horizontal scroll of the logs
|
// Horizontal scroll of the logs
|
||||||
"log_scroll_forward": [
|
"scroll_forward": [
|
||||||
"right"
|
"right"
|
||||||
],
|
],
|
||||||
"log_scroll_back": [
|
"scroll_back": [
|
||||||
"left"
|
"left"
|
||||||
],
|
],
|
||||||
// Select next panel
|
// Select next panel
|
||||||
@@ -170,6 +170,10 @@
|
|||||||
"log_section_toggle": [
|
"log_section_toggle": [
|
||||||
"\\"
|
"\\"
|
||||||
],
|
],
|
||||||
|
// Toggle to inspect container screen
|
||||||
|
"inspect": [
|
||||||
|
"i"
|
||||||
|
],
|
||||||
// Force a complete clear & redraw of the screen
|
// Force a complete clear & redraw of the screen
|
||||||
"force_redraw": [
|
"force_redraw": [
|
||||||
"f"
|
"f"
|
||||||
@@ -280,6 +284,27 @@
|
|||||||
// Ports & IP listing text
|
// Ports & IP listing text
|
||||||
"text": "white"
|
"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
|
// The filter panel
|
||||||
"filter": {
|
"filter": {
|
||||||
// Background color of panel
|
// Background color of panel
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ show_timestamp = true
|
|||||||
# Don't draw gui - for debugging - mostly pointless
|
# Don't draw gui - for debugging - mostly pointless
|
||||||
gui = true
|
gui = true
|
||||||
|
|
||||||
# Docker host location
|
# Docker host location. Will take priority over a DOCKER_HOST env.
|
||||||
host = "/var/run/docker.sock"
|
# host = "/var/run/docker.sock"
|
||||||
|
|
||||||
# Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC
|
# Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC
|
||||||
timezone = "Etc/UTC"
|
timezone = "Etc/UTC"
|
||||||
@@ -95,8 +95,8 @@ scroll_start = ["home"]
|
|||||||
# scroll up a list by one item
|
# scroll up a list by one item
|
||||||
scroll_up = ["up", "k"]
|
scroll_up = ["up", "k"]
|
||||||
# Horizontal scroll of the logs
|
# Horizontal scroll of the logs
|
||||||
log_scroll_forward = ["right"]
|
scroll_forward = ["right"]
|
||||||
log_scroll_back = ["left"]
|
scroll_back = ["left"]
|
||||||
# Select next panel
|
# Select next panel
|
||||||
select_next_panel = ["tab"]
|
select_next_panel = ["tab"]
|
||||||
# Select previous panel
|
# Select previous panel
|
||||||
@@ -122,6 +122,8 @@ log_section_height_decrease = ["-"]
|
|||||||
log_section_height_increase = ["+"]
|
log_section_height_increase = ["+"]
|
||||||
# Toggle visibility of the log section
|
# Toggle visibility of the log section
|
||||||
log_section_toggle = ["\\"]
|
log_section_toggle = ["\\"]
|
||||||
|
# Toggle to inspect container screen
|
||||||
|
inspect = ["i"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -254,6 +256,27 @@ points = "cyan"
|
|||||||
# The charts y-axis
|
# The charts y-axis
|
||||||
y_axis = "white"
|
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
|
# The ports chart
|
||||||
[colors.chart_ports]
|
[colors.chart_ports]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
|
|||||||
+200
-37
@@ -5,7 +5,7 @@ use std::{
|
|||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bollard::service::Port;
|
use bollard::secret::{ContainerSummaryHealthStatusEnum, PortSummary};
|
||||||
use jiff::{Timestamp, tz::TimeZone};
|
use jiff::{Timestamp, tz::TimeZone};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::Size,
|
layout::Size,
|
||||||
@@ -24,8 +24,12 @@ const ONE_GB: f64 = ONE_MB * 1000.0;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||||
pub enum ScrollDirection {
|
pub enum ScrollDirection {
|
||||||
Next,
|
// Next,
|
||||||
Previous,
|
// Previous,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||||
@@ -121,8 +125,8 @@ pub struct ContainerPorts {
|
|||||||
pub public: Option<u16>,
|
pub public: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Port> for ContainerPorts {
|
impl From<PortSummary> for ContainerPorts {
|
||||||
fn from(value: Port) -> Self {
|
fn from(value: PortSummary) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ip: value.ip.and_then(|i| i.parse::<IpAddr>().ok()),
|
ip: value.ip.and_then(|i| i.parse::<IpAddr>().ok()),
|
||||||
private: value.private_port,
|
private: value.private_port,
|
||||||
@@ -185,8 +189,10 @@ impl<T> StatefulList<T> {
|
|||||||
|
|
||||||
pub fn scroll(&mut self, scroll: &ScrollDirection) {
|
pub fn scroll(&mut self, scroll: &ScrollDirection) {
|
||||||
match scroll {
|
match scroll {
|
||||||
ScrollDirection::Next => self.next(),
|
ScrollDirection::Down => self.next(),
|
||||||
ScrollDirection::Previous => self.previous(),
|
ScrollDirection::Up => self.previous(),
|
||||||
|
// TODO set offset
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +420,7 @@ impl fmt::Display for State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Items for the container control list
|
/// Items for the container control list
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum DockerCommand {
|
pub enum DockerCommand {
|
||||||
Pause,
|
Pause,
|
||||||
Restart,
|
Restart,
|
||||||
@@ -570,8 +576,116 @@ impl fmt::Display for ByteStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State);
|
#[derive(Debug, Default, Clone, Copy, Eq)]
|
||||||
pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State);
|
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<Ordering> {
|
||||||
|
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<BandwidthStat>);
|
||||||
|
|
||||||
|
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<ByteStats>,
|
||||||
|
pub cpu: ChartSeries<CpuStats>,
|
||||||
|
pub rx: ChartSeries<BandwidthStat>,
|
||||||
|
pub tx: ChartSeries<BandwidthStat>,
|
||||||
|
pub state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct ChartSeries<T: Stats> {
|
||||||
|
pub dataset: Vec<(f64, f64)>,
|
||||||
|
pub max: T,
|
||||||
|
pub current: T,
|
||||||
|
}
|
||||||
|
|
||||||
/// Used to make sure that each log entry, for each container, is unique,
|
/// 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
|
/// 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<LogsTz>,
|
tz: HashSet<LogsTz>,
|
||||||
search_results: Vec<usize>,
|
search_results: Vec<usize>,
|
||||||
search_term: Option<String>,
|
search_term: Option<String>,
|
||||||
offset: u16,
|
offset: usize,
|
||||||
|
max_offset: usize,
|
||||||
max_log_len: usize,
|
max_log_len: usize,
|
||||||
adjusted_max_width: usize,
|
adjusted_max_width: usize,
|
||||||
adjust_max_width_text_len: usize,
|
adjust_max_width_text_len: usize,
|
||||||
@@ -629,6 +744,7 @@ impl Default for Logs {
|
|||||||
lines,
|
lines,
|
||||||
tz: HashSet::new(),
|
tz: HashSet::new(),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
max_offset: 0,
|
||||||
search_term: None,
|
search_term: None,
|
||||||
search_results: vec![],
|
search_results: vec![],
|
||||||
adjusted_max_width: 0,
|
adjusted_max_width: 0,
|
||||||
@@ -687,23 +803,26 @@ impl Logs {
|
|||||||
.position(|i| i == ¤t_selected)
|
.position(|i| i == ¤t_selected)
|
||||||
{
|
{
|
||||||
if let Some(new_index) = match sd {
|
if let Some(new_index) = match sd {
|
||||||
ScrollDirection::Next => current_position.checked_add(1),
|
ScrollDirection::Down => current_position.checked_add(1),
|
||||||
ScrollDirection::Previous => current_position.checked_sub(1),
|
ScrollDirection::Up => current_position.checked_sub(1),
|
||||||
} {
|
// TODO set offset
|
||||||
if let Some(f) = self.search_results.get(new_index) {
|
_ => None,
|
||||||
|
} && let Some(f) = self.search_results.get(new_index)
|
||||||
|
{
|
||||||
self.lines.state.select(Some(*f));
|
self.lines.state.select(Some(*f));
|
||||||
return Some(());
|
return Some(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let range = match sd {
|
let range = match sd {
|
||||||
ScrollDirection::Previous => (0..=current_selected).rev().collect::<Vec<_>>(),
|
ScrollDirection::Up => (0..=current_selected).rev().collect::<Vec<_>>(),
|
||||||
ScrollDirection::Next => (current_selected
|
ScrollDirection::Down => (current_selected
|
||||||
..=self
|
..=self
|
||||||
.search_results
|
.search_results
|
||||||
.last()
|
.last()
|
||||||
.map_or_else(|| current_selected, |i| *i))
|
.map_or_else(|| current_selected, |i| *i))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
// TODO set offset
|
||||||
|
_ => vec![],
|
||||||
};
|
};
|
||||||
for i in range {
|
for i in range {
|
||||||
if self.search_results.contains(&i) {
|
if self.search_results.contains(&i) {
|
||||||
@@ -821,7 +940,7 @@ impl Logs {
|
|||||||
if self.horizontal_scroll_able(width) {
|
if self.horizontal_scroll_able(width) {
|
||||||
let text_width = self.adjust_max_width_text_len;
|
let text_width = self.adjust_max_width_text_len;
|
||||||
let arrow_left = if self.offset > 0 { " ←" } else { " " };
|
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 {
|
} else {
|
||||||
" "
|
" "
|
||||||
@@ -884,10 +1003,10 @@ impl Logs {
|
|||||||
pub fn get_visible_logs(&self, size: Size, padding: usize) -> Vec<Text<'static>> {
|
pub fn get_visible_logs(&self, size: Size, padding: usize) -> Vec<Text<'static>> {
|
||||||
let current_index = self.lines.state.selected().unwrap_or_default();
|
let current_index = self.lines.state.selected().unwrap_or_default();
|
||||||
let height_padding = usize::from(size.height) + padding;
|
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
|
self.max_log_len
|
||||||
} else {
|
} else {
|
||||||
self.offset.into()
|
self.offset
|
||||||
};
|
};
|
||||||
|
|
||||||
self.lines
|
self.lines
|
||||||
@@ -921,13 +1040,14 @@ impl Logs {
|
|||||||
|
|
||||||
/// Add a padding so one char will always be visilbe?
|
/// Add a padding so one char will always be visilbe?
|
||||||
pub fn forward(&mut self, width: u16) {
|
pub fn forward(&mut self, width: u16) {
|
||||||
let offset = usize::from(self.offset);
|
// Need to set a max_offset, instead of using a width each time
|
||||||
if self.horizontal_scroll_able(width) {
|
if self.horizontal_scroll_able(width)
|
||||||
if self.adjusted_max_width > 0 && offset < self.adjusted_max_width {
|
&& self.adjusted_max_width > 0
|
||||||
|
&& self.offset < self.adjusted_max_width
|
||||||
|
{
|
||||||
self.offset = self.offset.saturating_add(1);
|
self.offset = self.offset.saturating_add(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Reduce the char offset
|
/// Reduce the char offset
|
||||||
pub const fn back(&mut self) {
|
pub const fn back(&mut self) {
|
||||||
@@ -970,6 +1090,7 @@ pub struct ContainerItem {
|
|||||||
pub cpu_stats: VecDeque<CpuStats>,
|
pub cpu_stats: VecDeque<CpuStats>,
|
||||||
pub created: u64,
|
pub created: u64,
|
||||||
pub docker_controls: StatefulList<DockerCommand>,
|
pub docker_controls: StatefulList<DockerCommand>,
|
||||||
|
pub health: Option<ContainerSummaryHealthStatusEnum>,
|
||||||
pub id: ContainerId,
|
pub id: ContainerId,
|
||||||
pub image: ContainerImage,
|
pub image: ContainerImage,
|
||||||
pub is_oxker: bool,
|
pub is_oxker: bool,
|
||||||
@@ -979,10 +1100,10 @@ pub struct ContainerItem {
|
|||||||
pub mem_stats: VecDeque<ByteStats>,
|
pub mem_stats: VecDeque<ByteStats>,
|
||||||
pub name: ContainerName,
|
pub name: ContainerName,
|
||||||
pub ports: Vec<ContainerPorts>,
|
pub ports: Vec<ContainerPorts>,
|
||||||
pub rx: ByteStats,
|
pub rx: NetworkBandwidth,
|
||||||
pub state: State,
|
pub state: State,
|
||||||
pub status: ContainerStatus,
|
pub status: ContainerStatus,
|
||||||
pub tx: ByteStats,
|
pub tx: NetworkBandwidth,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Basic display information, for when running in debug mode
|
/// Basic display information, for when running in debug mode
|
||||||
@@ -1019,6 +1140,7 @@ impl ContainerItem {
|
|||||||
cpu_stats: VecDeque::with_capacity(60),
|
cpu_stats: VecDeque::with_capacity(60),
|
||||||
created,
|
created,
|
||||||
docker_controls,
|
docker_controls,
|
||||||
|
health: None,
|
||||||
id,
|
id,
|
||||||
image: image.into(),
|
image: image.into(),
|
||||||
is_oxker,
|
is_oxker,
|
||||||
@@ -1028,10 +1150,10 @@ impl ContainerItem {
|
|||||||
mem_stats: VecDeque::with_capacity(60),
|
mem_stats: VecDeque::with_capacity(60),
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
ports,
|
ports,
|
||||||
rx: ByteStats::default(),
|
rx: NetworkBandwidth::new(),
|
||||||
state,
|
state,
|
||||||
status,
|
status,
|
||||||
tx: ByteStats::default(),
|
tx: NetworkBandwidth::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,7 +1179,7 @@ impl ContainerItem {
|
|||||||
self.cpu_stats
|
self.cpu_stats
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|i| (i.0 as f64, i.1.0))
|
.map(|(i, v)| (i as f64, v.0))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1067,24 +1189,65 @@ impl ContainerItem {
|
|||||||
self.mem_stats
|
self.mem_stats
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|i| (i.0 as f64, i.1.0 as f64))
|
.map(|(i, v)| (i as f64, v.0 as f64))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all cpu chart data
|
/// Get all cpu chart data
|
||||||
fn get_cpu_chart_data(&self) -> CpuTuple {
|
fn get_cpu_chart_data(&self) -> ChartSeries<CpuStats> {
|
||||||
(self.get_cpu_dataset(), self.max_cpu_stats(), self.state)
|
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
|
/// Get all mem chart data
|
||||||
fn get_mem_chart_data(&self) -> MemTuple {
|
fn get_mem_chart_data(&self) -> ChartSeries<ByteStats> {
|
||||||
(self.get_mem_dataset(), self.max_mem_stats(), self.state)
|
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<BandwidthStat> {
|
||||||
|
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<BandwidthStat> {
|
||||||
|
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
|
/// Get chart info for cpu & memory in one function
|
||||||
/// So only need to call .lock() once
|
/// So only need to call .lock() once
|
||||||
pub fn get_chart_data(&self) -> (CpuTuple, MemTuple) {
|
pub fn get_chart_data(&self) -> ChartsData {
|
||||||
(self.get_cpu_chart_data(), self.get_mem_chart_data())
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+183
-66
@@ -1,4 +1,4 @@
|
|||||||
use bollard::models::ContainerSummary;
|
use bollard::{models::ContainerSummary, secret::ContainerInspectResponse};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use ratatui::{layout::Size, text::Text, widgets::ListState};
|
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<Line<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ContainerInspectResponse> for InspectData {
|
||||||
|
fn from(input: ContainerInspectResponse) -> Self {
|
||||||
|
let as_string = serde_json::to_string_pretty(&input)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.lines()
|
||||||
|
.skip(1)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.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>?
|
||||||
|
id: ContainerId::from(input.id.unwrap_or_default().as_str()),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
as_string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Global app_state, stored in an Arc<Mutex>
|
/// Global app_state, stored in an Arc<Mutex>
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
@@ -122,6 +161,7 @@ pub struct AppData {
|
|||||||
error: Option<AppError>,
|
error: Option<AppError>,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
hidden_containers: Vec<ContainerItem>,
|
hidden_containers: Vec<ContainerItem>,
|
||||||
|
inspect_data: Option<InspectData>,
|
||||||
rerender: Arc<Rerender>,
|
rerender: Arc<Rerender>,
|
||||||
sorted_by: Option<(Header, SortedOrder)>,
|
sorted_by: Option<(Header, SortedOrder)>,
|
||||||
current_sorted_id: Vec<ContainerId>,
|
current_sorted_id: Vec<ContainerId>,
|
||||||
@@ -136,6 +176,7 @@ pub struct AppData {
|
|||||||
pub error: Option<AppError>,
|
pub error: Option<AppError>,
|
||||||
pub filter: Filter,
|
pub filter: Filter,
|
||||||
pub hidden_containers: Vec<ContainerItem>,
|
pub hidden_containers: Vec<ContainerItem>,
|
||||||
|
pub inspect_data: Option<InspectData>,
|
||||||
pub current_sorted_id: Vec<ContainerId>,
|
pub current_sorted_id: Vec<ContainerId>,
|
||||||
pub rerender: Arc<Rerender>,
|
pub rerender: Arc<Rerender>,
|
||||||
pub sorted_by: Option<(Header, SortedOrder)>,
|
pub sorted_by: Option<(Header, SortedOrder)>,
|
||||||
@@ -151,6 +192,7 @@ impl AppData {
|
|||||||
error: None,
|
error: None,
|
||||||
filter: Filter::new(),
|
filter: Filter::new(),
|
||||||
hidden_containers: vec![],
|
hidden_containers: vec![],
|
||||||
|
inspect_data: None,
|
||||||
rerender: Arc::clone(redraw),
|
rerender: Arc::clone(redraw),
|
||||||
sorted_by: None,
|
sorted_by: None,
|
||||||
}
|
}
|
||||||
@@ -165,6 +207,18 @@ impl AppData {
|
|||||||
.as_secs()
|
.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<InspectData> {
|
||||||
|
self.inspect_data.clone()
|
||||||
|
}
|
||||||
/// Filter related methods
|
/// Filter related methods
|
||||||
/// Get the filterby and filter_term
|
/// Get the filterby and filter_term
|
||||||
pub const fn get_filter(&self) -> (FilterBy, Option<&String>) {
|
pub const fn get_filter(&self) -> (FilterBy, Option<&String>) {
|
||||||
@@ -172,12 +226,12 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_search_scroll(&mut self, np: &ScrollDirection) {
|
pub fn log_search_scroll(&mut self, np: &ScrollDirection) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container()
|
||||||
if i.logs.search_scroll(np).is_some() {
|
&& i.logs.search_scroll(np).is_some()
|
||||||
|
{
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gen_log_search(&self) -> Option<LogSearch> {
|
pub fn gen_log_search(&self) -> Option<LogSearch> {
|
||||||
self.get_selected_container()
|
self.get_selected_container()
|
||||||
@@ -329,6 +383,7 @@ impl AppData {
|
|||||||
.iter()
|
.iter()
|
||||||
.position(|i| self.get_selected_container_id().as_ref() == Some(&i.id)),
|
.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
|
/// Remove the sorted header & order, and sort by default - created datetime
|
||||||
@@ -340,14 +395,14 @@ impl AppData {
|
|||||||
/// Sort containers based on a given header, if headings match, and already ascending, remove sorting
|
/// 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) {
|
pub fn set_sort_by_header(&mut self, selected_header: Header) {
|
||||||
let mut output = Some((selected_header, SortedOrder::Asc));
|
let mut output = Some((selected_header, SortedOrder::Asc));
|
||||||
if let Some((current_header, order)) = self.get_sorted() {
|
if let Some((current_header, order)) = self.get_sorted()
|
||||||
if current_header == selected_header {
|
&& current_header == selected_header
|
||||||
|
{
|
||||||
match order {
|
match order {
|
||||||
SortedOrder::Desc => output = None,
|
SortedOrder::Desc => output = None,
|
||||||
SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)),
|
SortedOrder::Asc => output = Some((selected_header, SortedOrder::Desc)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.set_sorted(output);
|
self.set_sorted(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,12 +467,14 @@ impl AppData {
|
|||||||
Header::Rx => item_ord
|
Header::Rx => item_ord
|
||||||
.0
|
.0
|
||||||
.rx
|
.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())),
|
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
|
||||||
Header::Tx => item_ord
|
Header::Tx => item_ord
|
||||||
.0
|
.0
|
||||||
.tx
|
.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())),
|
.then_with(|| item_ord.0.name.get().cmp(item_ord.1.name.get())),
|
||||||
Header::Name => item_ord
|
Header::Name => item_ord
|
||||||
.0
|
.0
|
||||||
@@ -459,7 +516,6 @@ impl AppData {
|
|||||||
|
|
||||||
/// Get all the ContainerItems
|
/// Get all the ContainerItems
|
||||||
/// Thnk this allow block can be removed with the 1.87 release of Clippy
|
/// 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] {
|
pub fn get_container_items(&self) -> &[ContainerItem] {
|
||||||
&self.containers.items
|
&self.containers.items
|
||||||
}
|
}
|
||||||
@@ -556,10 +612,17 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable container by given id
|
/// Get a mutable container by given id
|
||||||
|
#[cfg(not(test))]
|
||||||
fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
|
fn get_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
|
||||||
self.containers.items.iter_mut().find(|i| &i.id == id)
|
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
|
/// 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> {
|
fn get_hidden_container_by_id(&mut self, id: &ContainerId) -> Option<&mut ContainerItem> {
|
||||||
self.hidden_containers.iter_mut().find(|i| &i.id == id)
|
self.hidden_containers.iter_mut().find(|i| &i.id == id)
|
||||||
@@ -637,7 +700,7 @@ impl AppData {
|
|||||||
.map(|i| &mut i.docker_controls.state)
|
.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<DockerCommand>> {
|
pub fn get_control_items(&mut self) -> Option<&mut Vec<DockerCommand>> {
|
||||||
self.get_mut_selected_container()
|
self.get_mut_selected_container()
|
||||||
.map(|i| &mut i.docker_controls.items)
|
.map(|i| &mut i.docker_controls.items)
|
||||||
@@ -668,19 +731,22 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) {
|
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 {
|
match sd {
|
||||||
ScrollDirection::Next => {
|
ScrollDirection::Down => {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.forward(width);
|
i.logs.forward(width);
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ScrollDirection::Previous => {
|
ScrollDirection::Up => {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.back();
|
i.logs.back();
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO set offset
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,8 +754,10 @@ impl AppData {
|
|||||||
pub fn log_scroll(&mut self, scroll: &ScrollDirection) {
|
pub fn log_scroll(&mut self, scroll: &ScrollDirection) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
match scroll {
|
match scroll {
|
||||||
ScrollDirection::Next => i.logs.next(),
|
ScrollDirection::Down => i.logs.next(),
|
||||||
ScrollDirection::Previous => i.logs.previous(),
|
ScrollDirection::Up => i.logs.previous(),
|
||||||
|
// TODO set offset
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
@@ -731,7 +799,7 @@ impl AppData {
|
|||||||
|
|
||||||
/// Chart data related methods
|
/// Chart data related methods
|
||||||
/// Get mutable Option of the currently selected container chart data
|
/// 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<ChartsData> {
|
||||||
self.containers
|
self.containers
|
||||||
.state
|
.state
|
||||||
.selected()
|
.selected()
|
||||||
@@ -780,6 +848,7 @@ impl AppData {
|
|||||||
|
|
||||||
for container in [&self.containers.items, &self.hidden_containers] {
|
for container in [&self.containers.items, &self.hidden_containers] {
|
||||||
for container in container {
|
for container in container {
|
||||||
|
// TODO refactor these
|
||||||
let cpu_count = container.cpu_stats.back().map_or_else(
|
let cpu_count = container.cpu_stats.back().map_or_else(
|
||||||
|| count(&CpuStats::default().to_string()),
|
|| count(&CpuStats::default().to_string()),
|
||||||
|i| count(&i.to_string()),
|
|i| count(&i.to_string()),
|
||||||
@@ -789,14 +858,19 @@ impl AppData {
|
|||||||
|| count(&ByteStats::default().to_string()),
|
|| count(&ByteStats::default().to_string()),
|
||||||
|i| count(&i.to_string()),
|
|i| count(&i.to_string()),
|
||||||
);
|
);
|
||||||
|
|
||||||
columns.cpu.1 = columns.cpu.1.max(cpu_count);
|
columns.cpu.1 = columns.cpu.1.max(cpu_count);
|
||||||
columns.image.1 = columns.image.1.max(count(&container.image.to_string()));
|
columns.image.1 = columns.image.1.max(count(&container.image.to_string()));
|
||||||
columns.mem.1 = columns.mem.1.max(mem_current_count);
|
columns.mem.1 = columns.mem.1.max(mem_current_count);
|
||||||
columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string()));
|
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.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_rx.1 = columns
|
||||||
columns.net_tx.1 = columns.net_tx.1.max(count(&container.tx.to_string()));
|
.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.state.1 = columns.state.1.max(count(&container.state.to_string()));
|
||||||
columns.status.1 = columns.status.1.max(count(container.status.get()));
|
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.mem_stats.push_back(ByteStats::new(mem));
|
||||||
}
|
}
|
||||||
|
|
||||||
container.rx.update(rx);
|
// 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
|
||||||
container.tx.update(tx);
|
if container.rx.is_empty() || container.state.is_alive() {
|
||||||
|
container.rx.push(rx);
|
||||||
|
container.tx.push(tx);
|
||||||
|
}
|
||||||
|
|
||||||
container.mem_limit.update(mem_limit);
|
container.mem_limit.update(mem_limit);
|
||||||
}
|
}
|
||||||
if self.is_selected_container(id) {
|
if self.is_selected_container(id) {
|
||||||
@@ -877,7 +955,7 @@ impl AppData {
|
|||||||
// If removed container is currently selected, then change selected to previous
|
// If removed container is currently selected, then change selected to previous
|
||||||
// This will default to 0 in any edge cases
|
// This will default to 0 in any edge cases
|
||||||
if self.containers.state.selected().is_some() {
|
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
|
// 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() {
|
if self.containers.items.get(index).is_some() {
|
||||||
@@ -896,7 +974,7 @@ impl AppData {
|
|||||||
if f.starts_with('/') {
|
if f.starts_with('/') {
|
||||||
f.remove(0);
|
f.remove(0);
|
||||||
}
|
}
|
||||||
(*f).to_string()
|
(*f).clone()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1277,13 +1355,16 @@ mod tests {
|
|||||||
assert_eq!(result, &containers);
|
assert_eq!(result, &containers);
|
||||||
|
|
||||||
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
|
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")) {
|
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")) {
|
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
|
// descending
|
||||||
@@ -1314,13 +1395,16 @@ mod tests {
|
|||||||
assert_eq!(result, &containers);
|
assert_eq!(result, &containers);
|
||||||
|
|
||||||
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
|
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")) {
|
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")) {
|
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
|
// descending
|
||||||
@@ -1378,13 +1462,16 @@ mod tests {
|
|||||||
assert_eq!(result, &containers);
|
assert_eq!(result, &containers);
|
||||||
|
|
||||||
if let Some(i) = app_data.get_container_by_id(&ContainerId::from("1")) {
|
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")) {
|
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")) {
|
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)));
|
app_data.set_sorted(Some((Header::Rx, SortedOrder::Asc)));
|
||||||
@@ -1444,7 +1531,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Calling previous when at start has no effect
|
// 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();
|
let result = app_data.get_selected_container_id();
|
||||||
assert_eq!(result, Some(ContainerId::from("1")));
|
assert_eq!(result, Some(ContainerId::from("1")));
|
||||||
let result = app_data.get_selected_container_id_state_name();
|
let result = app_data.get_selected_container_id_state_name();
|
||||||
@@ -1467,7 +1554,7 @@ mod tests {
|
|||||||
|
|
||||||
// Advance list state by 1
|
// Advance list state by 1
|
||||||
app_data.containers_start();
|
app_data.containers_start();
|
||||||
app_data.containers.scroll(&ScrollDirection::Next);
|
app_data.containers.scroll(&ScrollDirection::Down);
|
||||||
|
|
||||||
let result = app_data.get_container_state();
|
let result = app_data.get_container_state();
|
||||||
assert_eq!(result.selected(), Some(1));
|
assert_eq!(result.selected(), Some(1));
|
||||||
@@ -1511,7 +1598,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Calling previous when at end has no effect
|
// 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();
|
let result = app_data.get_selected_container_id();
|
||||||
assert_eq!(result, Some(ContainerId::from("3")));
|
assert_eq!(result, Some(ContainerId::from("3")));
|
||||||
let result = app_data.get_selected_container_id_state_name();
|
let result = app_data.get_selected_container_id_state_name();
|
||||||
@@ -1532,7 +1619,7 @@ mod tests {
|
|||||||
let mut app_data = gen_appdata(&containers);
|
let mut app_data = gen_appdata(&containers);
|
||||||
|
|
||||||
app_data.containers_end();
|
app_data.containers_end();
|
||||||
app_data.containers.scroll(&ScrollDirection::Previous);
|
app_data.containers.scroll(&ScrollDirection::Up);
|
||||||
let result = app_data.get_container_state();
|
let result = app_data.get_container_state();
|
||||||
assert_eq!(result.selected(), Some(1));
|
assert_eq!(result.selected(), Some(1));
|
||||||
assert_eq!(result.offset(), 0);
|
assert_eq!(result.offset(), 0);
|
||||||
@@ -1548,7 +1635,7 @@ mod tests {
|
|||||||
assert_eq!(result, None);
|
assert_eq!(result, None);
|
||||||
|
|
||||||
app_data.containers.start();
|
app_data.containers.start();
|
||||||
app_data.containers.scroll(&ScrollDirection::Next);
|
app_data.containers.scroll(&ScrollDirection::Down);
|
||||||
|
|
||||||
let result = app_data.get_selected_container();
|
let result = app_data.get_selected_container();
|
||||||
assert_eq!(result, Some(&containers[1]));
|
assert_eq!(result, Some(&containers[1]));
|
||||||
@@ -1635,7 +1722,7 @@ mod tests {
|
|||||||
let mut app_data = gen_appdata(&containers);
|
let mut app_data = gen_appdata(&containers);
|
||||||
app_data.containers_start();
|
app_data.containers_start();
|
||||||
app_data.docker_controls_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();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Restart));
|
assert_eq!(result, Some(DockerCommand::Restart));
|
||||||
@@ -1653,7 +1740,7 @@ mod tests {
|
|||||||
assert_eq!(result, Some(DockerCommand::Delete));
|
assert_eq!(result, Some(DockerCommand::Delete));
|
||||||
|
|
||||||
// Next has no effect when at end
|
// 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();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Delete));
|
assert_eq!(result, Some(DockerCommand::Delete));
|
||||||
}
|
}
|
||||||
@@ -1665,14 +1752,14 @@ mod tests {
|
|||||||
let mut app_data = gen_appdata(&containers);
|
let mut app_data = gen_appdata(&containers);
|
||||||
app_data.containers_start();
|
app_data.containers_start();
|
||||||
app_data.docker_controls_end();
|
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();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Stop));
|
assert_eq!(result, Some(DockerCommand::Stop));
|
||||||
|
|
||||||
// previous has no effect when at start
|
// previous has no effect when at start
|
||||||
app_data.docker_controls_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();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Pause));
|
assert_eq!(result, Some(DockerCommand::Pause));
|
||||||
}
|
}
|
||||||
@@ -1936,7 +2023,7 @@ mod tests {
|
|||||||
assert_eq!(result, " 3/3 - container_1 - image_1");
|
assert_eq!(result, " 3/3 - container_1 - image_1");
|
||||||
|
|
||||||
// Change log state to no longer be at the end
|
// 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();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_1 - image_1");
|
assert_eq!(result, " 2/3 - container_1 - image_1");
|
||||||
}
|
}
|
||||||
@@ -1957,7 +2044,7 @@ mod tests {
|
|||||||
assert_eq!(result, " - container_1 - image_1");
|
assert_eq!(result, " - container_1 - image_1");
|
||||||
|
|
||||||
// change container
|
// change container
|
||||||
app_data.containers_scroll(&ScrollDirection::Next);
|
app_data.containers_scroll(&ScrollDirection::Down);
|
||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " - container_2 - image_2");
|
assert_eq!(result, " - container_2 - image_2");
|
||||||
|
|
||||||
@@ -1968,7 +2055,7 @@ mod tests {
|
|||||||
assert_eq!(result, " 3/3 - container_2 - image_2");
|
assert_eq!(result, " 3/3 - container_2 - image_2");
|
||||||
|
|
||||||
// Change log state to no longer be at the end
|
// 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();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_2 - image_2");
|
assert_eq!(result, " 2/3 - container_2 - image_2");
|
||||||
}
|
}
|
||||||
@@ -2075,7 +2162,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 1/3 - container_1 - image_1");
|
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();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(1));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(1));
|
||||||
@@ -2084,7 +2171,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_1 - image_1");
|
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();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
|
||||||
@@ -2092,7 +2179,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 3/3 - container_1 - image_1");
|
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();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
@@ -2123,7 +2210,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 3/3 - container_1 - image_1");
|
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();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
@@ -2132,7 +2219,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_1 - image_1");
|
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();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
||||||
@@ -2140,7 +2227,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 1/3 - container_1 - image_1");
|
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();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
||||||
@@ -2164,26 +2251,49 @@ mod tests {
|
|||||||
|
|
||||||
app_data.containers_start();
|
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")) {
|
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.mem_stats = VecDeque::from([ByteStats::new(1), ByteStats::new(2)]);
|
||||||
|
item.rx = rx;
|
||||||
|
item.tx = tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = app_data.get_chart_data();
|
let result = app_data.get_chart_data();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Some((
|
Some(ChartsData {
|
||||||
(
|
memory: ChartSeries {
|
||||||
vec![(0.0, 1.1), (1.0, 1.2)],
|
dataset: vec![(0.0, 1.0), (1.0, 2.0)],
|
||||||
CpuStats::new(1.2),
|
max: ByteStats::new(2),
|
||||||
State::Running(RunningState::Healthy),
|
current: ByteStats::new(2)
|
||||||
),
|
},
|
||||||
(
|
cpu: ChartSeries {
|
||||||
vec![(0.0, 1.0), (1.0, 2.0)],
|
dataset: vec![(0.0, 1.2), (1.0, 1.2)],
|
||||||
ByteStats::new(2),
|
max: CpuStats::new(1.2),
|
||||||
State::Running(RunningState::Healthy),
|
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].cpu_stats, VecDeque::from([CpuStats::new(10.0)]));
|
||||||
assert_eq!(result[0].mem_stats, VecDeque::from([ByteStats::new(10)]));
|
assert_eq!(result[0].mem_stats, VecDeque::from([ByteStats::new(10)]));
|
||||||
assert_eq!(result[0].mem_limit, 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]
|
#[test]
|
||||||
@@ -2434,7 +2551,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..=500 {
|
for _ in 0..=500 {
|
||||||
app_data.log_scroll(&ScrollDirection::Next);
|
app_data.log_scroll(&ScrollDirection::Down);
|
||||||
}
|
}
|
||||||
let result = app_data.get_logs(
|
let result = app_data.get_logs(
|
||||||
Size {
|
Size {
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ use crate::app_data::DockerCommand;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// app errors to set in global state
|
/// app errors to set in global state
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum AppError {
|
pub enum AppError {
|
||||||
DockerCommand(DockerCommand),
|
DockerCommand(DockerCommand),
|
||||||
DockerExec,
|
DockerExec,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use ratatui::style::Color;
|
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
|
/// The macro accepts a list of struct names with key names
|
||||||
/// Returns a struct where every key name is an Option<String>, with the correct derived attributes
|
/// Returns a struct where every key name is an Option<String>, with the correct derived attributes
|
||||||
macro_rules! optional_config_struct {
|
macro_rules! optional_config_struct {
|
||||||
@@ -58,7 +61,7 @@ impl From<Option<ConfigColors>> for AppColors {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seletable panel borders
|
// Selectable panel borders
|
||||||
if let Some(b) = config_colors.borders {
|
if let Some(b) = config_colors.borders {
|
||||||
Self::map_color(b.selected.as_deref(), &mut app_colors.borders.selected);
|
Self::map_color(b.selected.as_deref(), &mut app_colors.borders.selected);
|
||||||
Self::map_color(b.unselected.as_deref(), &mut app_colors.borders.unselected);
|
Self::map_color(b.unselected.as_deref(), &mut app_colors.borders.unselected);
|
||||||
@@ -249,6 +252,8 @@ optional_config_struct!(
|
|||||||
ConfigBackgroundText, background, text;
|
ConfigBackgroundText, background, text;
|
||||||
ConfigBackgroundTextHighlight, background, text, text_highlight;
|
ConfigBackgroundTextHighlight, background, text, text_highlight;
|
||||||
ConfigBorders, selected, unselected;
|
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;
|
ConfigChartCpu, background, border, order, title, max, points,y_axis;
|
||||||
ConfigChartMemory, background, border, title, max, points, y_axis;
|
ConfigChartMemory, background, border, title, max, points, y_axis;
|
||||||
ConfigChartPorts, background, border, title, headings, text;
|
ConfigChartPorts, background, border, title, headings, text;
|
||||||
@@ -265,6 +270,8 @@ config_struct!(
|
|||||||
Borders, selected, unselected;
|
Borders, selected, unselected;
|
||||||
ChartCpu, background, border, title, max, points, y_axis;
|
ChartCpu, background, border, title, max, points, y_axis;
|
||||||
ChartMemory, 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;
|
ChartPorts, background, border, title, headings, text;
|
||||||
Commands, background, pause, restart, stop, delete, resume, start;
|
Commands, background, pause, restart, stop, delete, resume, start;
|
||||||
Containers, background, icon, text, text_rx, text_tx;
|
Containers, background, icon, text, text_rx, text_tx;
|
||||||
@@ -284,6 +291,7 @@ pub struct ConfigColors {
|
|||||||
borders: Option<ConfigBorders>,
|
borders: Option<ConfigBorders>,
|
||||||
chart_cpu: Option<ConfigChartCpu>,
|
chart_cpu: Option<ConfigChartCpu>,
|
||||||
chart_memory: Option<ConfigChartMemory>,
|
chart_memory: Option<ConfigChartMemory>,
|
||||||
|
chart_bandwidth: Option<ConfigChartBandwidth>,
|
||||||
chart_ports: Option<ConfigChartPorts>,
|
chart_ports: Option<ConfigChartPorts>,
|
||||||
commands: Option<ConfigCommands>,
|
commands: Option<ConfigCommands>,
|
||||||
container_state: Option<ConfigContainerState>,
|
container_state: Option<ConfigContainerState>,
|
||||||
@@ -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 {
|
impl ChartCpu {
|
||||||
const fn new() -> Self {
|
const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -383,8 +408,8 @@ impl Containers {
|
|||||||
background: Color::Reset,
|
background: Color::Reset,
|
||||||
icon: Color::White,
|
icon: Color::White,
|
||||||
text: Color::Blue,
|
text: Color::Blue,
|
||||||
text_rx: Color::Rgb(255, 233, 193),
|
text_rx: COLOR_RX,
|
||||||
text_tx: Color::Rgb(205, 140, 140),
|
text_tx: COLOR_TX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,6 +512,7 @@ pub struct AppColors {
|
|||||||
pub borders: Borders,
|
pub borders: Borders,
|
||||||
pub chart_cpu: ChartCpu,
|
pub chart_cpu: ChartCpu,
|
||||||
pub chart_memory: ChartMemory,
|
pub chart_memory: ChartMemory,
|
||||||
|
pub chart_bandwidth: ChartBandwidth,
|
||||||
pub chart_ports: ChartPorts,
|
pub chart_ports: ChartPorts,
|
||||||
pub commands: Commands,
|
pub commands: Commands,
|
||||||
pub container_state: ContainerState,
|
pub container_state: ContainerState,
|
||||||
@@ -507,6 +533,7 @@ impl AppColors {
|
|||||||
borders: Borders::new(),
|
borders: Borders::new(),
|
||||||
chart_cpu: ChartCpu::new(),
|
chart_cpu: ChartCpu::new(),
|
||||||
chart_memory: ChartMemory::new(),
|
chart_memory: ChartMemory::new(),
|
||||||
|
chart_bandwidth: ChartBandwidth::new(),
|
||||||
chart_ports: ChartPorts::new(),
|
chart_ports: ChartPorts::new(),
|
||||||
commands: Commands::new(),
|
commands: Commands::new(),
|
||||||
container_state: ContainerState::new(),
|
container_state: ContainerState::new(),
|
||||||
|
|||||||
+27
-6
@@ -24,8 +24,8 @@ show_timestamp = true
|
|||||||
# Don't draw gui - for debugging - mostly pointless
|
# Don't draw gui - for debugging - mostly pointless
|
||||||
gui = true
|
gui = true
|
||||||
|
|
||||||
# Docker host location
|
# Docker host location. Will take priority over a DOCKER_HOST env.
|
||||||
host = "/var/run/docker.sock"
|
# host = "/var/run/docker.sock"
|
||||||
|
|
||||||
# Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC
|
# Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC
|
||||||
timezone = "Etc/UTC"
|
timezone = "Etc/UTC"
|
||||||
@@ -95,8 +95,8 @@ scroll_start = ["home"]
|
|||||||
# scroll up a list by one item
|
# scroll up a list by one item
|
||||||
scroll_up = ["up", "k"]
|
scroll_up = ["up", "k"]
|
||||||
# Horizontal scroll of the logs
|
# Horizontal scroll of the logs
|
||||||
log_scroll_forward = ["right"]
|
scroll_forward = ["right"]
|
||||||
log_scroll_back = ["left"]
|
scroll_back = ["left"]
|
||||||
# Select next panel
|
# Select next panel
|
||||||
select_next_panel = ["tab"]
|
select_next_panel = ["tab"]
|
||||||
# Select previous panel
|
# Select previous panel
|
||||||
@@ -122,8 +122,8 @@ log_section_height_decrease = ["-"]
|
|||||||
log_section_height_increase = ["+"]
|
log_section_height_increase = ["+"]
|
||||||
# Toggle visibility of the log section
|
# Toggle visibility of the log section
|
||||||
log_section_toggle = ["\\"]
|
log_section_toggle = ["\\"]
|
||||||
|
# Toggle to inspect container screen
|
||||||
|
inspect = ["i"]
|
||||||
|
|
||||||
# Force a complete clear & redraw of the screen
|
# Force a complete clear & redraw of the screen
|
||||||
force_redraw = ["f"]
|
force_redraw = ["f"]
|
||||||
@@ -254,6 +254,27 @@ points = "cyan"
|
|||||||
# The charts y-axis
|
# The charts y-axis
|
||||||
y_axis = "white"
|
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
|
# The ports chart
|
||||||
[colors.chart_ports]
|
[colors.chart_ports]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
|
|||||||
+28
-26
@@ -42,8 +42,9 @@ optional_config_struct!(
|
|||||||
exec,
|
exec,
|
||||||
filter_mode,
|
filter_mode,
|
||||||
force_redraw,
|
force_redraw,
|
||||||
log_scroll_back,
|
inspect,
|
||||||
log_scroll_forward,
|
scroll_back,
|
||||||
|
scroll_forward,
|
||||||
log_search_mode,
|
log_search_mode,
|
||||||
log_section_height_decrease,
|
log_section_height_decrease,
|
||||||
log_section_height_increase,
|
log_section_height_increase,
|
||||||
@@ -77,9 +78,10 @@ config_struct!(
|
|||||||
delete_deny,
|
delete_deny,
|
||||||
exec,
|
exec,
|
||||||
filter_mode,
|
filter_mode,
|
||||||
|
inspect,
|
||||||
force_redraw,
|
force_redraw,
|
||||||
log_scroll_back,
|
scroll_back,
|
||||||
log_scroll_forward,
|
scroll_forward,
|
||||||
log_search_mode,
|
log_search_mode,
|
||||||
log_section_height_decrease,
|
log_section_height_decrease,
|
||||||
log_section_height_increase,
|
log_section_height_increase,
|
||||||
@@ -113,10 +115,11 @@ impl Keymap {
|
|||||||
delete_confirm: (KeyCode::Char('y'), None),
|
delete_confirm: (KeyCode::Char('y'), None),
|
||||||
delete_deny: (KeyCode::Char('n'), None),
|
delete_deny: (KeyCode::Char('n'), None),
|
||||||
exec: (KeyCode::Char('e'), None),
|
exec: (KeyCode::Char('e'), None),
|
||||||
|
inspect: (KeyCode::Char('i'), None),
|
||||||
filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))),
|
filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))),
|
||||||
force_redraw: (KeyCode::Char('f'), None),
|
force_redraw: (KeyCode::Char('f'), None),
|
||||||
log_scroll_back: (KeyCode::Left, None),
|
scroll_back: (KeyCode::Left, None),
|
||||||
log_scroll_forward: (KeyCode::Right, None),
|
scroll_forward: (KeyCode::Right, None),
|
||||||
log_search_mode: (KeyCode::Char('#'), None),
|
log_search_mode: (KeyCode::Char('#'), None),
|
||||||
log_section_height_decrease: (KeyCode::Char('-'), None),
|
log_section_height_decrease: (KeyCode::Char('-'), None),
|
||||||
log_section_height_increase: (KeyCode::Char('='), None),
|
log_section_height_increase: (KeyCode::Char('='), None),
|
||||||
@@ -158,8 +161,9 @@ impl From<Option<ConfigKeymap>> for Keymap {
|
|||||||
|vec_str: Option<Vec<String>>,
|
|vec_str: Option<Vec<String>>,
|
||||||
keymap_field: &mut (KeyCode, Option<KeyCode>),
|
keymap_field: &mut (KeyCode, Option<KeyCode>),
|
||||||
keymap_clash: &mut HashSet<KeyCode>| {
|
keymap_clash: &mut HashSet<KeyCode>| {
|
||||||
if let Some(vec_str) = vec_str {
|
if let Some(vec_str) = vec_str
|
||||||
if let Some(vec_keycode) = Self::try_parse_keycode(&vec_str) {
|
&& let Some(vec_keycode) = Self::try_parse_keycode(&vec_str)
|
||||||
|
{
|
||||||
if let Some(first) = vec_keycode.first() {
|
if let Some(first) = vec_keycode.first() {
|
||||||
keymap_clash.insert(*first);
|
keymap_clash.insert(*first);
|
||||||
counter += 1;
|
counter += 1;
|
||||||
@@ -173,7 +177,6 @@ impl From<Option<ConfigKeymap>> for Keymap {
|
|||||||
keymap_field.1 = None;
|
keymap_field.1 = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ck) = value {
|
if let Some(ck) = value {
|
||||||
@@ -206,12 +209,8 @@ impl From<Option<ConfigKeymap>> for Keymap {
|
|||||||
update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash);
|
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.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_search_mode, &mut keymap.log_search_mode, &mut clash);
|
||||||
update_keymap(
|
update_keymap(ck.scroll_forward, &mut keymap.scroll_forward, &mut clash);
|
||||||
ck.log_scroll_forward,
|
update_keymap(ck.scroll_back, &mut keymap.scroll_back, &mut clash);
|
||||||
&mut keymap.log_scroll_forward,
|
|
||||||
&mut clash,
|
|
||||||
);
|
|
||||||
update_keymap(ck.log_scroll_back, &mut keymap.log_scroll_back, &mut clash);
|
|
||||||
update_keymap(
|
update_keymap(
|
||||||
ck.select_next_panel,
|
ck.select_next_panel,
|
||||||
&mut keymap.select_next_panel,
|
&mut keymap.select_next_panel,
|
||||||
@@ -276,16 +275,16 @@ impl Keymap {
|
|||||||
|
|
||||||
for key in input.iter().take(2) {
|
for key in input.iter().take(2) {
|
||||||
if key.chars().count() == 1 {
|
if key.chars().count() == 1 {
|
||||||
if let Some(first_char) = key.chars().next() {
|
if let Some(first_char) = key.chars().next()
|
||||||
if let Some(first_char) = match first_char {
|
&& let Some(first_char) = match first_char {
|
||||||
x if x.is_ascii_alphabetic() || x.is_ascii_digit() => Some(first_char),
|
x if x.is_ascii_alphabetic() || x.is_ascii_digit() => Some(first_char),
|
||||||
'/' | '\\' | ',' | '.' | '#' | '\'' | '[' | ']' | ';' | '=' | '-' => {
|
'/' | '\\' | ',' | '.' | '#' | '\'' | '[' | ']' | ';' | '=' | '-' => {
|
||||||
Some(first_char)
|
Some(first_char)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
} {
|
|
||||||
output.push(KeyCode::Char(first_char));
|
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
output.push(KeyCode::Char(first_char));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let keycode = match key.to_lowercase().as_str() {
|
let keycode = match key.to_lowercase().as_str() {
|
||||||
@@ -327,7 +326,7 @@ impl Keymap {
|
|||||||
if output.is_empty() {
|
if output.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// Remove any duplicates for a single deinition
|
// Remove any duplicates for a single definition
|
||||||
if output.first() == output.get(1) {
|
if output.first() == output.get(1) {
|
||||||
output.pop();
|
output.pop();
|
||||||
}
|
}
|
||||||
@@ -395,9 +394,10 @@ mod tests {
|
|||||||
exec: None,
|
exec: None,
|
||||||
filter_mode: None,
|
filter_mode: None,
|
||||||
force_redraw: None,
|
force_redraw: None,
|
||||||
log_scroll_back: None,
|
inspect: None,
|
||||||
|
scroll_back: None,
|
||||||
log_search_mode: None,
|
log_search_mode: None,
|
||||||
log_scroll_forward: None,
|
scroll_forward: None,
|
||||||
log_section_height_decrease: None,
|
log_section_height_decrease: None,
|
||||||
log_section_height_increase: None,
|
log_section_height_increase: None,
|
||||||
log_section_toggle: None,
|
log_section_toggle: None,
|
||||||
@@ -441,8 +441,9 @@ mod tests {
|
|||||||
exec: gen_v(("g", "h")),
|
exec: gen_v(("g", "h")),
|
||||||
filter_mode: gen_v(("i", "j")),
|
filter_mode: gen_v(("i", "j")),
|
||||||
force_redraw: gen_v(("k", "l")),
|
force_redraw: gen_v(("k", "l")),
|
||||||
log_scroll_back: gen_v(("s", "t")),
|
inspect: gen_v(("m", "n")),
|
||||||
log_scroll_forward: gen_v(("q", "r")),
|
scroll_back: gen_v(("s", "t")),
|
||||||
|
scroll_forward: gen_v(("q", "r")),
|
||||||
log_search_mode: gen_v(("1", "2")),
|
log_search_mode: gen_v(("1", "2")),
|
||||||
log_section_height_decrease: gen_v(("m", "n")),
|
log_section_height_decrease: gen_v(("m", "n")),
|
||||||
log_section_height_increase: gen_v(("o", "p")),
|
log_section_height_increase: gen_v(("o", "p")),
|
||||||
@@ -479,8 +480,9 @@ mod tests {
|
|||||||
exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
|
exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
|
||||||
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
||||||
force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
||||||
log_scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))),
|
inspect: (KeyCode::Char('i'), None),
|
||||||
log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))),
|
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_search_mode: (KeyCode::Char('1'), Some(KeyCode::Char('2'))),
|
||||||
log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
||||||
log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))),
|
log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))),
|
||||||
|
|||||||
+17
-17
@@ -13,9 +13,6 @@ pub use {color_parser::AppColors, keymap_parser::Keymap};
|
|||||||
mod parse_args;
|
mod parse_args;
|
||||||
mod parse_config_file;
|
mod parse_config_file;
|
||||||
|
|
||||||
// TODO use a global pub static oncelock for the config
|
|
||||||
// static CELL: OnceLock<usize> = OnceLock::new();
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -28,7 +25,8 @@ pub struct Config {
|
|||||||
pub keymap: Keymap,
|
pub keymap: Keymap,
|
||||||
pub log_search_case_sensitive: bool,
|
pub log_search_case_sensitive: bool,
|
||||||
pub raw_logs: bool,
|
pub raw_logs: bool,
|
||||||
pub save_dir: Option<PathBuf>,
|
pub dir_config: Option<PathBuf>,
|
||||||
|
pub dir_save: Option<PathBuf>,
|
||||||
pub show_logs: bool,
|
pub show_logs: bool,
|
||||||
pub show_self: bool,
|
pub show_self: bool,
|
||||||
pub show_std_err: bool,
|
pub show_std_err: bool,
|
||||||
@@ -50,7 +48,8 @@ impl From<&Args> for Config {
|
|||||||
keymap: Keymap::new(),
|
keymap: Keymap::new(),
|
||||||
log_search_case_sensitive: true,
|
log_search_case_sensitive: true,
|
||||||
raw_logs: args.raw,
|
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_logs: true,
|
||||||
show_self: !args.show_self,
|
show_self: !args.show_self,
|
||||||
show_std_err: !args.no_std_err,
|
show_std_err: !args.no_std_err,
|
||||||
@@ -62,19 +61,20 @@ impl From<&Args> for Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ConfigFile> for Config {
|
impl From<(ConfigFile, Option<PathBuf>)> for Config {
|
||||||
fn from(config_file: ConfigFile) -> Self {
|
fn from((config_file, dir): (ConfigFile, Option<PathBuf>)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
app_colors: AppColors::from(config_file.colors),
|
app_colors: AppColors::from(config_file.colors),
|
||||||
color_logs: config_file.color_logs.unwrap_or(false),
|
color_logs: config_file.color_logs.unwrap_or(false),
|
||||||
docker_interval_ms: config_file.docker_interval.unwrap_or(1000),
|
docker_interval_ms: config_file.docker_interval.unwrap_or(1000),
|
||||||
|
dir_config: dir,
|
||||||
gui: config_file.gui.unwrap_or(true),
|
gui: config_file.gui.unwrap_or(true),
|
||||||
host: config_file.host,
|
host: config_file.host,
|
||||||
in_container: Self::check_if_in_container(),
|
in_container: Self::check_if_in_container(),
|
||||||
keymap: Keymap::from(config_file.keymap),
|
keymap: Keymap::from(config_file.keymap),
|
||||||
log_search_case_sensitive: config_file.log_search_case_sensitive.unwrap_or(true),
|
log_search_case_sensitive: config_file.log_search_case_sensitive.unwrap_or(true),
|
||||||
raw_logs: config_file.raw_logs.unwrap_or(false),
|
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_logs: config_file.show_logs.unwrap_or(true),
|
||||||
show_self: config_file.show_self.unwrap_or(false),
|
show_self: config_file.show_self.unwrap_or(false),
|
||||||
show_std_err: config_file.show_std_err.unwrap_or(true),
|
show_std_err: config_file.show_std_err.unwrap_or(true),
|
||||||
@@ -185,8 +185,8 @@ impl Config {
|
|||||||
self.host = Some(host);
|
self.host = Some(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(x) = config_from_cli.save_dir {
|
if let Some(x) = config_from_cli.dir_save {
|
||||||
self.save_dir = Some(x);
|
self.dir_save = Some(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tz) = config_from_cli.timezone {
|
if let Some(tz) = config_from_cli.timezone {
|
||||||
@@ -211,16 +211,16 @@ impl Config {
|
|||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let config_from_cli = Self::from(&args);
|
let config_from_cli = Self::from(&args);
|
||||||
|
|
||||||
if let Some(config_file) = &args.config_file {
|
if let Some(dir_config_file) = &args.config_file
|
||||||
if let Some(config_file) =
|
&& let Some(config_file) =
|
||||||
parse_config_file::ConfigFile::try_parse_from_file(config_file)
|
parse_config_file::ConfigFile::try_parse_from_file(dir_config_file)
|
||||||
{
|
{
|
||||||
return Self::from(config_file).merge_args(config_from_cli);
|
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) {
|
if let Some((config_file, dir)) = parse_config_file::ConfigFile::try_parse(in_container) {
|
||||||
return Self::from(config_file).merge_args(config_from_cli);
|
return Self::from((config_file, Some(dir))).merge_args(config_from_cli);
|
||||||
}
|
}
|
||||||
config_from_cli
|
config_from_cli
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ pub struct ConfigFile {
|
|||||||
|
|
||||||
impl ConfigFile {
|
impl ConfigFile {
|
||||||
/// Attempt to create a config.toml file, will attempt to recursively create the directories as well
|
/// 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 {
|
if in_container {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -146,28 +146,26 @@ impl ConfigFile {
|
|||||||
|
|
||||||
/// Parse a config file using default config_file location
|
/// 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
|
/// 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<Self> {
|
pub fn try_parse(in_container: bool) -> Option<(Self, PathBuf)> {
|
||||||
let mut config = None;
|
let mut output = None;
|
||||||
for file_format in [
|
for file_format in [
|
||||||
ConfigFileFormat::Toml,
|
ConfigFileFormat::Toml,
|
||||||
ConfigFileFormat::Jsonc,
|
ConfigFileFormat::Jsonc,
|
||||||
ConfigFileFormat::JsoncAsJson,
|
ConfigFileFormat::JsoncAsJson,
|
||||||
ConfigFileFormat::Json,
|
ConfigFileFormat::Json,
|
||||||
] {
|
] {
|
||||||
if let Ok(config_file) = Self::parse_config_file(
|
let path = file_format.get_default_path_name(in_container);
|
||||||
file_format,
|
if let Ok(config_file) = Self::parse_config_file(file_format, &path) {
|
||||||
&file_format.get_default_path_name(in_container),
|
output = Some((config_file, path));
|
||||||
) {
|
|
||||||
config = Some(config_file);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.is_none() {
|
if output.is_none() {
|
||||||
Self::crate_config_file(in_container).ok();
|
Self::create_config_file(in_container).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
config
|
output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ pub enum DockerMessage {
|
|||||||
ConfirmDelete(ContainerId),
|
ConfirmDelete(ContainerId),
|
||||||
Control((DockerCommand, ContainerId)),
|
Control((DockerCommand, ContainerId)),
|
||||||
Exec(Sender<Arc<Docker>>),
|
Exec(Sender<Arc<Docker>>),
|
||||||
|
Inspect(ContainerId),
|
||||||
Update,
|
Update,
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-30
@@ -1,8 +1,8 @@
|
|||||||
use bollard::{
|
use bollard::{
|
||||||
Docker,
|
Docker,
|
||||||
query_parameters::{
|
query_parameters::{
|
||||||
ListContainersOptions, LogsOptions, RemoveContainerOptions, RestartContainerOptions,
|
InspectContainerOptions, ListContainersOptions, LogsOptions, RemoveContainerOptions,
|
||||||
StartContainerOptions, StatsOptions, StopContainerOptions,
|
RestartContainerOptions, StartContainerOptions, StatsOptions, StopContainerOptions,
|
||||||
},
|
},
|
||||||
secret::ContainerStatsResponse,
|
secret::ContainerStatsResponse,
|
||||||
service::ContainerSummary,
|
service::ContainerSummary,
|
||||||
@@ -10,13 +10,10 @@ use bollard::{
|
|||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashSet,
|
||||||
sync::{Arc, atomic::AtomicUsize},
|
sync::{Arc, atomic::AtomicUsize},
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
sync::mpsc::{Receiver, Sender},
|
|
||||||
task::JoinHandle,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -70,7 +67,7 @@ pub struct DockerData {
|
|||||||
docker: Arc<Docker>,
|
docker: Arc<Docker>,
|
||||||
gui_state: Arc<Mutex<GuiState>>,
|
gui_state: Arc<Mutex<GuiState>>,
|
||||||
receiver: Receiver<DockerMessage>,
|
receiver: Receiver<DockerMessage>,
|
||||||
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
spawns: Arc<Mutex<HashSet<SpawnId>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DockerData {
|
impl DockerData {
|
||||||
@@ -132,7 +129,7 @@ impl DockerData {
|
|||||||
docker: Arc<Docker>,
|
docker: Arc<Docker>,
|
||||||
state: State,
|
state: State,
|
||||||
spawn_id: SpawnId,
|
spawn_id: SpawnId,
|
||||||
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
spawns: Arc<Mutex<HashSet<SpawnId>>>,
|
||||||
) {
|
) {
|
||||||
let id = spawn_id.get_id();
|
let id = spawn_id.get_id();
|
||||||
let mut stream = docker
|
let mut stream = docker
|
||||||
@@ -200,16 +197,13 @@ impl DockerData {
|
|||||||
for (state, id) in all_ids {
|
for (state, id) in all_ids {
|
||||||
let spawn_id = SpawnId::Stats((id, self.binate));
|
let spawn_id = SpawnId::Stats((id, self.binate));
|
||||||
|
|
||||||
if let std::collections::hash_map::Entry::Vacant(spawns) =
|
if !self.spawns.lock().contains(&spawn_id) {
|
||||||
self.spawns.lock().entry(spawn_id.clone())
|
let app_data = Arc::clone(&self.app_data);
|
||||||
{
|
let docker = Arc::clone(&self.docker);
|
||||||
spawns.insert(tokio::spawn(Self::update_container_stat(
|
let spawns = Arc::clone(&self.spawns);
|
||||||
Arc::clone(&self.app_data),
|
tokio::spawn(Self::update_container_stat(
|
||||||
Arc::clone(&self.docker),
|
app_data, docker, state, spawn_id, spawns,
|
||||||
state,
|
));
|
||||||
spawn_id,
|
|
||||||
Arc::clone(&self.spawns),
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.binate = self.binate.toggle();
|
self.binate = self.binate.toggle();
|
||||||
@@ -256,7 +250,7 @@ impl DockerData {
|
|||||||
docker: Arc<Docker>,
|
docker: Arc<Docker>,
|
||||||
id: ContainerId,
|
id: ContainerId,
|
||||||
since: u64,
|
since: u64,
|
||||||
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
spawns: Arc<Mutex<HashSet<SpawnId>>>,
|
||||||
stderr: bool,
|
stderr: bool,
|
||||||
) {
|
) {
|
||||||
let options = Some(LogsOptions {
|
let options = Some(LogsOptions {
|
||||||
@@ -290,13 +284,13 @@ impl DockerData {
|
|||||||
let spawns = Arc::clone(&self.spawns);
|
let spawns = Arc::clone(&self.spawns);
|
||||||
let std_err = self.config.show_std_err;
|
let std_err = self.config.show_std_err;
|
||||||
let init = Arc::clone(&init);
|
let init = Arc::clone(&init);
|
||||||
self.spawns.lock().insert(
|
|
||||||
SpawnId::Log(id.clone()),
|
self.spawns.lock().insert(SpawnId::Log(id.clone()));
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
Self::update_log(app_data, docker, id, 0, spawns, std_err).await;
|
Self::update_log(app_data, docker, id, 0, spawns, std_err).await;
|
||||||
init.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
init.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
init
|
init
|
||||||
}
|
}
|
||||||
@@ -327,17 +321,16 @@ impl DockerData {
|
|||||||
let last_updated = container.last_updated;
|
let last_updated = container.last_updated;
|
||||||
let spawn_id = SpawnId::Log(container.id.clone());
|
let spawn_id = SpawnId::Log(container.id.clone());
|
||||||
// Only spawn if not already spawned with a given id/binate pair
|
// Only spawn if not already spawned with a given id/binate pair
|
||||||
if let std::collections::hash_map::Entry::Vacant(spawns) =
|
if !self.spawns.lock().contains(&spawn_id) {
|
||||||
self.spawns.lock().entry(spawn_id)
|
self.spawns.lock().insert(spawn_id.clone());
|
||||||
{
|
tokio::spawn(Self::update_log(
|
||||||
spawns.insert(tokio::spawn(Self::update_log(
|
|
||||||
Arc::clone(&self.app_data),
|
Arc::clone(&self.app_data),
|
||||||
Arc::clone(&self.docker),
|
Arc::clone(&self.docker),
|
||||||
container.id.clone(),
|
container.id.clone(),
|
||||||
last_updated,
|
last_updated,
|
||||||
Arc::clone(&self.spawns),
|
Arc::clone(&self.spawns),
|
||||||
self.config.show_std_err,
|
self.config.show_std_err,
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.update_all_container_stats();
|
self.update_all_container_stats();
|
||||||
@@ -420,6 +413,18 @@ impl DockerData {
|
|||||||
docker_tx.send(Arc::clone(&self.docker)).ok();
|
docker_tx.send(Arc::clone(&self.docker)).ok();
|
||||||
}
|
}
|
||||||
DockerMessage::Update => self.update_everything().await,
|
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),
|
docker: Arc::new(docker),
|
||||||
gui_state,
|
gui_state,
|
||||||
receiver: docker_rx,
|
receiver: docker_rx,
|
||||||
spawns: Arc::new(Mutex::new(HashMap::new())),
|
spawns: Arc::new(Mutex::new(HashSet::new())),
|
||||||
};
|
};
|
||||||
inner.initialise_container_data().await;
|
inner.initialise_container_data().await;
|
||||||
Self::heartbeat(&inner.config, docker_tx);
|
Self::heartbeat(&inner.config, docker_tx);
|
||||||
@@ -478,6 +483,7 @@ mod tests {
|
|||||||
fn gen_stats() -> ContainerStatsResponse {
|
fn gen_stats() -> ContainerStatsResponse {
|
||||||
ContainerStatsResponse {
|
ContainerStatsResponse {
|
||||||
read: None,
|
read: None,
|
||||||
|
os_type: None,
|
||||||
preread: None,
|
preread: None,
|
||||||
num_procs: Some(1),
|
num_procs: Some(1),
|
||||||
pids_stats: None,
|
pids_stats: None,
|
||||||
|
|||||||
+37
-41
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{Read, Stdout, Write},
|
io::{Read, Write},
|
||||||
sync::{Arc, atomic::AtomicBool, mpsc::Sender},
|
sync::{Arc, atomic::AtomicBool, mpsc::Sender},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ use bollard::{
|
|||||||
use crossterm::terminal::enable_raw_mode;
|
use crossterm::terminal::enable_raw_mode;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use ratatui::{Terminal, backend::CrosstermBackend};
|
use ratatui::layout::Size;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
@@ -123,23 +123,29 @@ impl AsyncTTY {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is used to set the terminal size when exec via the Internal method
|
// impl TryFrom<&Terminal<CrosstermBackend<Stdout>>> for HWU16 {
|
||||||
#[derive(Debug, Clone)]
|
// type Error = None;
|
||||||
pub struct TerminalSize {
|
// fn try_from(terminal: &Terminal<CrosstermBackend<Stdout>>) -> Option<Self> {
|
||||||
width: u16,
|
// terminal.size().map_or(None, |i| {
|
||||||
height: u16,
|
// Some(Self {
|
||||||
}
|
// width: i.width,
|
||||||
|
// height: i.height,
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
impl TerminalSize {
|
// }
|
||||||
pub fn new(terminal: &Terminal<CrosstermBackend<Stdout>>) -> Option<Self> {
|
|
||||||
terminal.size().map_or(None, |i| {
|
// impl TerminalSize {
|
||||||
Some(Self {
|
// pub fn new(terminal: &Terminal<CrosstermBackend<Stdout>>) -> Option<Self> {
|
||||||
width: i.width,
|
// terminal.size().map_or(None, |i| {
|
||||||
height: i.height,
|
// Some(Self {
|
||||||
})
|
// width: i.width,
|
||||||
})
|
// height: i.height,
|
||||||
}
|
// })
|
||||||
}
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ExecMode {
|
pub enum ExecMode {
|
||||||
@@ -161,15 +167,16 @@ impl ExecMode {
|
|||||||
let use_cli = app_data.lock().config.use_cli;
|
let use_cli = app_data.lock().config.use_cli;
|
||||||
let container = app_data.lock().get_selected_container_id_state_name();
|
let container = app_data.lock().get_selected_container_id_state_name();
|
||||||
|
|
||||||
if let Some((id, state, _)) = container {
|
if let Some((id, state, _)) = container
|
||||||
if [
|
&& [
|
||||||
State::Running(RunningState::Healthy),
|
State::Running(RunningState::Healthy),
|
||||||
State::Running(RunningState::Unhealthy),
|
State::Running(RunningState::Unhealthy),
|
||||||
]
|
]
|
||||||
.contains(&state)
|
.contains(&state)
|
||||||
{
|
{
|
||||||
if tty_readable() && !use_cli {
|
if tty_readable()
|
||||||
if let Ok(exec) = docker
|
&& !use_cli
|
||||||
|
&& let Ok(exec) = docker
|
||||||
.create_exec(
|
.create_exec(
|
||||||
id.get(),
|
id.get(),
|
||||||
CreateExecOptions {
|
CreateExecOptions {
|
||||||
@@ -180,34 +187,23 @@ impl ExecMode {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
&& let Ok(StartExecResults::Attached { mut output, .. }) =
|
||||||
if let Ok(StartExecResults::Attached { mut output, .. }) =
|
|
||||||
docker.start_exec(&exec.id, None).await
|
docker.start_exec(&exec.id, None).await
|
||||||
|
&& let Some(Ok(msg)) = output.next().await
|
||||||
|
&& !msg.to_string().starts_with(OCI_ERROR)
|
||||||
{
|
{
|
||||||
if let Some(Ok(msg)) = output.next().await {
|
return Some(Self::Internal((Arc::new(id), Arc::clone(docker))));
|
||||||
if !msg.to_string().starts_with(OCI_ERROR) {
|
|
||||||
return Some(Self::Internal((
|
|
||||||
Arc::new(id),
|
|
||||||
Arc::clone(docker),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(output) = std::process::Command::new(command::DOCKER)
|
if let Ok(output) = std::process::Command::new(command::DOCKER)
|
||||||
.args([command::EXEC, id.get(), command::PWD])
|
.args([command::EXEC, id.get(), command::PWD])
|
||||||
.output()
|
.output()
|
||||||
|
&& let Ok(output) = String::from_utf8(output.stdout)
|
||||||
|
&& !output.starts_with(OCI_ERROR)
|
||||||
{
|
{
|
||||||
if let Ok(output) = String::from_utf8(output.stdout) {
|
|
||||||
if !output.starts_with(OCI_ERROR) {
|
|
||||||
return Some(Self::External(Arc::new(id)));
|
return Some(Self::External(Arc::new(id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +231,7 @@ impl ExecMode {
|
|||||||
&self,
|
&self,
|
||||||
id: &ContainerId,
|
id: &ContainerId,
|
||||||
docker: &Arc<Docker>,
|
docker: &Arc<Docker>,
|
||||||
terminal_size: Option<TerminalSize>,
|
terminal_size: Option<Size>,
|
||||||
) -> Result<(), AppError> {
|
) -> Result<(), AppError> {
|
||||||
let cancel_token = CancellationToken::new();
|
let cancel_token = CancellationToken::new();
|
||||||
|
|
||||||
@@ -351,7 +347,7 @@ impl ExecMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&self, tty_size: Option<TerminalSize>) -> Result<(), AppError> {
|
pub async fn run(&self, tty_size: Option<Size>) -> Result<(), AppError> {
|
||||||
match self {
|
match self {
|
||||||
Self::External(id) => {
|
Self::External(id) => {
|
||||||
Self::exec_external(id);
|
Self::exec_external(id);
|
||||||
|
|||||||
+117
-25
@@ -119,6 +119,14 @@ impl InputHandler {
|
|||||||
self.gui_state.lock().set_delete_container(None);
|
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
|
/// Validate that one can exec into a Docker container
|
||||||
async fn exec_key(&self) {
|
async fn exec_key(&self) {
|
||||||
let is_oxker = self.app_data.lock().is_oxker();
|
let is_oxker = self.app_data.lock().is_oxker();
|
||||||
@@ -178,8 +186,9 @@ impl InputHandler {
|
|||||||
async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args = self.app_data.lock().config.clone();
|
let args = self.app_data.lock().config.clone();
|
||||||
let container = self.app_data.lock().get_selected_container_id_state_name();
|
let container = self.app_data.lock().get_selected_container_id_state_name();
|
||||||
if let Some((id, _, name)) = container {
|
if let Some((id, _, name)) = container
|
||||||
if let Some(log_path) = args.save_dir {
|
&& let Some(log_path) = args.dir_save
|
||||||
|
{
|
||||||
let (sx, rx) = tokio::sync::oneshot::channel();
|
let (sx, rx) = tokio::sync::oneshot::channel();
|
||||||
self.docker_tx.send(DockerMessage::Exec(sx)).await?;
|
self.docker_tx.send(DockerMessage::Exec(sx)).await?;
|
||||||
|
|
||||||
@@ -230,7 +239,6 @@ impl InputHandler {
|
|||||||
.set_info_box(&format!("saved to {}", path.display()));
|
.set_info_box(&format!("saved to {}", path.display()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
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) {
|
fn logs_horizontal_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
|
||||||
let panel = self.gui_state.lock().get_selected_panel();
|
let panel = self.gui_state.lock().get_selected_panel();
|
||||||
if panel == SelectablePanel::Logs {
|
if panel == SelectablePanel::Logs {
|
||||||
@@ -393,22 +413,22 @@ impl InputHandler {
|
|||||||
self.gui_state.lock().status_del(Status::SearchLogs);
|
self.gui_state.lock().status_del(Status::SearchLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if self.keymap.log_scroll_back.0 == key_code
|
_ if self.keymap.scroll_back.0 == key_code
|
||||||
|| self.keymap.log_scroll_back.1 == Some(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
|
_ if self.keymap.scroll_forward.0 == key_code
|
||||||
|| self.keymap.log_scroll_forward.1 == Some(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 => {
|
_ if self.keymap.scroll_down.0 == key_code => {
|
||||||
self.app_data
|
self.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.log_search_scroll(&ScrollDirection::Next);
|
.log_search_scroll(&ScrollDirection::Down);
|
||||||
// TODO should only do this is log_search_scroll returns some
|
// TODO should only do this is log_search_scroll returns some
|
||||||
// Need to wait til app_data and gui_data is combined
|
// Need to wait til app_data and gui_data is combined
|
||||||
self.gui_state
|
self.gui_state
|
||||||
@@ -418,9 +438,7 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ if self.keymap.scroll_up.0 == key_code => {
|
_ if self.keymap.scroll_up.0 == key_code => {
|
||||||
self.app_data
|
self.app_data.lock().log_search_scroll(&ScrollDirection::Up);
|
||||||
.lock()
|
|
||||||
.log_search_scroll(&ScrollDirection::Previous);
|
|
||||||
// TODO should only do this is log_search_scroll returns some
|
// TODO should only do this is log_search_scroll returns some
|
||||||
// Need to wait til app_data and gui_data is combined
|
// Need to wait til app_data and gui_data is combined
|
||||||
self.gui_state
|
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
|
/// Actions to take when Filter status active
|
||||||
fn handle_filter(&self, key_code: KeyCode) {
|
fn handle_filter(&self, key_code: KeyCode) {
|
||||||
match key_code {
|
match key_code {
|
||||||
@@ -596,6 +670,10 @@ impl InputHandler {
|
|||||||
self.save_key().await;
|
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
|
_ if self.keymap.select_next_panel.0 == key_code
|
||||||
|| self.keymap.select_next_panel.1 == Some(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
|
_ if self.keymap.scroll_up.0 == key_code
|
||||||
|| self.keymap.scroll_up.1 == Some(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
|
_ if self.keymap.scroll_down.0 == key_code
|
||||||
|| self.keymap.scroll_down.1 == Some(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
|
_ if self.keymap.filter_mode.0 == key_code
|
||||||
@@ -648,18 +726,17 @@ impl InputHandler {
|
|||||||
self.gui_state.lock().status_push(Status::SearchLogs);
|
self.gui_state.lock().status_push(Status::SearchLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if self.keymap.log_scroll_back.0 == key_code
|
_ if self.keymap.scroll_back.0 == key_code
|
||||||
|| self.keymap.log_scroll_back.1 == Some(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);
|
// self.logs_back(modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if self.keymap.log_scroll_forward.0 == key_code
|
_ if self.keymap.scroll_forward.0 == key_code
|
||||||
|| self.keymap.log_scroll_forward.1 == Some(key_code) =>
|
|| self.keymap.scroll_forward.1 == Some(key_code) =>
|
||||||
{
|
{
|
||||||
self.logs_horizontal_scroll(modifier, &ScrollDirection::Next);
|
self.logs_horizontal_scroll(modifier, &ScrollDirection::Down);
|
||||||
// self.logs_forward(modifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Enter => self.enter_key().await,
|
KeyCode::Enter => self.enter_key().await,
|
||||||
@@ -678,6 +755,7 @@ impl InputHandler {
|
|||||||
let contains_filter = contains(Status::Filter);
|
let contains_filter = contains(Status::Filter);
|
||||||
let contains_delete = contains(Status::DeleteConfirm);
|
let contains_delete = contains(Status::DeleteConfirm);
|
||||||
let contains_search_logs = contains(Status::SearchLogs);
|
let contains_search_logs = contains(Status::SearchLogs);
|
||||||
|
let contains_inspect = contains(Status::Inspect);
|
||||||
|
|
||||||
if !contains_exec {
|
if !contains_exec {
|
||||||
let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1;
|
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);
|
self.handle_search_logs(key_code, key_modifier);
|
||||||
} else if contains_delete {
|
} else if contains_delete {
|
||||||
self.handle_delete(key_code).await;
|
self.handle_delete(key_code).await;
|
||||||
|
} else if contains_inspect {
|
||||||
|
self.handle_inspect(key_code, key_modifier);
|
||||||
} else {
|
} else {
|
||||||
self.handle_others(key_code, key_modifier).await;
|
self.handle_others(key_code, key_modifier).await;
|
||||||
}
|
}
|
||||||
@@ -726,7 +806,18 @@ impl InputHandler {
|
|||||||
/// Handle mouse button events
|
/// Handle mouse button events
|
||||||
fn mouse_press(&self, mouse_event: MouseEvent, modifier: KeyModifiers) {
|
fn mouse_press(&self, mouse_event: MouseEvent, modifier: KeyModifiers) {
|
||||||
let status = self.gui_state.lock().get_status();
|
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 mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
||||||
let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point);
|
let help_intersect = self.gui_state.lock().get_intersect_help(mouse_point);
|
||||||
if help_intersect {
|
if help_intersect {
|
||||||
@@ -734,8 +825,9 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match mouse_event.kind {
|
match mouse_event.kind {
|
||||||
MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Previous),
|
MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Up),
|
||||||
MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Next),
|
MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Down),
|
||||||
|
// TODO left and right for log offsets
|
||||||
MouseEventKind::Down(MouseButton::Left) => {
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
||||||
let header = self.gui_state.lock().get_intersect_header(mouse_point);
|
let header = self.gui_state.lock().get_intersect_header(mouse_point);
|
||||||
|
|||||||
+37
-27
@@ -1,4 +1,3 @@
|
|||||||
#![allow(clippy::collapsible_if)]
|
|
||||||
// #![allow(unused)]
|
// #![allow(unused)]
|
||||||
// Zigbuild is stuck on 1.87.0, which means Mac builds won't work when using collapsible ifs
|
// 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();
|
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<String> {
|
fn read_docker_host(config: &Config) -> Option<String> {
|
||||||
config
|
if let Some(x) = &config.host {
|
||||||
.host
|
Some(x.to_string())
|
||||||
.as_ref()
|
} else if let Ok(env) = std::env::var(DOCKER_HOST)
|
||||||
.map_or_else(|| std::env::var(DOCKER_HOST).ok(), |x| Some(x.to_string()))
|
&& !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
|
/// Create docker daemon handler, and only spawn up the docker data handler if a ping returns non-error
|
||||||
@@ -60,12 +65,13 @@ async fn docker_init(
|
|||||||
) {
|
) {
|
||||||
let host = read_docker_host(&app_data.lock().config);
|
let host = read_docker_host(&app_data.lock().config);
|
||||||
|
|
||||||
let connection = host.map_or_else(Docker::connect_with_socket_defaults, |host| {
|
if let Ok(docker) = host
|
||||||
Docker::connect_with_socket(&host, 120, API_DEFAULT_VERSION)
|
.as_ref()
|
||||||
});
|
.map_or_else(Docker::connect_with_defaults, |host| {
|
||||||
|
Docker::connect_with_socket(host, 120, API_DEFAULT_VERSION)
|
||||||
if let Ok(docker) = connection {
|
})
|
||||||
if docker.ping().await.is_ok() {
|
&& docker.ping().await.is_ok()
|
||||||
|
{
|
||||||
tokio::spawn(DockerData::start(
|
tokio::spawn(DockerData::start(
|
||||||
Arc::clone(app_data),
|
Arc::clone(app_data),
|
||||||
docker,
|
docker,
|
||||||
@@ -73,12 +79,13 @@ async fn docker_init(
|
|||||||
docker_tx,
|
docker_tx,
|
||||||
Arc::clone(gui_state),
|
Arc::clone(gui_state),
|
||||||
));
|
));
|
||||||
return;
|
} 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
|
/// 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 std::{str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use bollard::service::{ContainerSummary, Port};
|
use bollard::service::{ContainerSummary, PortSummary};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{
|
app_data::{
|
||||||
@@ -169,23 +176,24 @@ mod tests {
|
|||||||
/// Default test config, has timestamps turned off
|
/// Default test config, has timestamps turned off
|
||||||
pub fn gen_config() -> Config {
|
pub fn gen_config() -> Config {
|
||||||
Config {
|
Config {
|
||||||
|
app_colors: AppColors::new(),
|
||||||
color_logs: false,
|
color_logs: false,
|
||||||
|
dir_save: None,
|
||||||
|
dir_config: None,
|
||||||
docker_interval_ms: 1000,
|
docker_interval_ms: 1000,
|
||||||
gui: true,
|
gui: true,
|
||||||
host: None,
|
host: None,
|
||||||
show_std_err: false,
|
|
||||||
in_container: false,
|
in_container: false,
|
||||||
save_dir: None,
|
keymap: Keymap::new(),
|
||||||
log_search_case_sensitive: true,
|
log_search_case_sensitive: true,
|
||||||
raw_logs: false,
|
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_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,
|
timezone: None,
|
||||||
|
use_cli: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +219,7 @@ mod tests {
|
|||||||
containers: StatefulList::new(containers.to_vec()),
|
containers: StatefulList::new(containers.to_vec()),
|
||||||
hidden_containers: vec![],
|
hidden_containers: vec![],
|
||||||
current_sorted_id: vec![],
|
current_sorted_id: vec![],
|
||||||
|
inspect_data: None,
|
||||||
error: None,
|
error: None,
|
||||||
sorted_by: None,
|
sorted_by: None,
|
||||||
rerender: Arc::new(Rerender::new()),
|
rerender: Arc::new(Rerender::new()),
|
||||||
@@ -234,13 +243,14 @@ mod tests {
|
|||||||
pub fn gen_container_summary(index: usize, state: &str) -> ContainerSummary {
|
pub fn gen_container_summary(index: usize, state: &str) -> ContainerSummary {
|
||||||
ContainerSummary {
|
ContainerSummary {
|
||||||
image_manifest_descriptor: None,
|
image_manifest_descriptor: None,
|
||||||
|
health: None,
|
||||||
id: Some(format!("{index}")),
|
id: Some(format!("{index}")),
|
||||||
names: Some(vec![format!("container_{}", index)]),
|
names: Some(vec![format!("container_{}", index)]),
|
||||||
image: Some(format!("image_{index}")),
|
image: Some(format!("image_{index}")),
|
||||||
image_id: Some(format!("{index}")),
|
image_id: Some(format!("{index}")),
|
||||||
command: None,
|
command: None,
|
||||||
created: Some(i64::try_from(index).unwrap()),
|
created: Some(i64::try_from(index).unwrap()),
|
||||||
ports: Some(vec![Port {
|
ports: Some(vec![PortSummary {
|
||||||
ip: None,
|
ip: None,
|
||||||
private_port: u16::try_from(index).unwrap_or(1) + 8000,
|
private_port: u16::try_from(index).unwrap_or(1) + 8000,
|
||||||
public_port: None,
|
public_port: None,
|
||||||
|
|||||||
@@ -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<Dataset<'a>>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ use ratatui::{
|
|||||||
|
|
||||||
use super::{CONSTRAINT_50_50, FrameData};
|
use super::{CONSTRAINT_50_50, FrameData};
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{ByteStats, CpuStats, State, Stats},
|
app_data::{State, Stats},
|
||||||
config::AppColors,
|
config::AppColors,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ fn make_chart<'a, T: Stats + Display>(
|
|||||||
|
|
||||||
/// Draw the cpu + mem charts
|
/// Draw the cpu + mem charts
|
||||||
pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
|
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()
|
let area = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(CONSTRAINT_50_50)
|
.constraints(CONSTRAINT_50_50)
|
||||||
@@ -129,34 +129,34 @@ pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
|
|||||||
.marker(symbols::Marker::Dot)
|
.marker(symbols::Marker::Dot)
|
||||||
.style(Style::default().fg(colors.chart_cpu.points))
|
.style(Style::default().fg(colors.chart_cpu.points))
|
||||||
.graph_type(GraphType::Line)
|
.graph_type(GraphType::Line)
|
||||||
.data(&cpu.0),
|
.data(&x.cpu.dataset),
|
||||||
];
|
];
|
||||||
let mem_dataset = vec![
|
let mem_dataset = vec![
|
||||||
Dataset::default()
|
Dataset::default()
|
||||||
.marker(symbols::Marker::Dot)
|
.marker(symbols::Marker::Dot)
|
||||||
.style(Style::default().fg(colors.chart_memory.points))
|
.style(Style::default().fg(colors.chart_memory.points))
|
||||||
.graph_type(GraphType::Line)
|
.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));
|
// let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1));
|
||||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
// #[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 mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64));
|
||||||
let cpu_chart = make_chart(
|
let cpu_chart = make_chart(
|
||||||
ChartVariant::Cpu,
|
ChartVariant::Cpu,
|
||||||
colors,
|
colors,
|
||||||
&cpu_stats,
|
&x.cpu.current,
|
||||||
cpu_dataset,
|
cpu_dataset,
|
||||||
&cpu.1,
|
&x.cpu.max,
|
||||||
cpu.2,
|
x.state,
|
||||||
);
|
);
|
||||||
let mem_chart = make_chart(
|
let mem_chart = make_chart(
|
||||||
ChartVariant::Memory,
|
ChartVariant::Memory,
|
||||||
colors,
|
colors,
|
||||||
&mem_stats,
|
&x.memory.current,
|
||||||
mem_dataset,
|
mem_dataset,
|
||||||
&mem.1,
|
&x.memory.max,
|
||||||
mem.2,
|
x.state,
|
||||||
);
|
);
|
||||||
|
|
||||||
f.render_widget(cpu_chart, area[0]);
|
f.render_widget(cpu_chart, area[0]);
|
||||||
@@ -175,7 +175,7 @@ mod tests {
|
|||||||
config::AppColors,
|
config::AppColors,
|
||||||
ui::{
|
ui::{
|
||||||
FrameData,
|
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
|
// co-ordinates of the dots from the cpu chart
|
||||||
const CPU_XY: [(usize, usize); 15] = [
|
const CPU_XY: [(usize, usize); 16] = [
|
||||||
(1, 12),
|
(1, 13),
|
||||||
(2, 11),
|
|
||||||
(2, 12),
|
(2, 12),
|
||||||
(3, 10),
|
(2, 13),
|
||||||
(3, 11),
|
(3, 11),
|
||||||
(3, 12),
|
(3, 13),
|
||||||
(4, 10),
|
(4, 11),
|
||||||
(4, 12),
|
(4, 13),
|
||||||
(5, 9),
|
(5, 10),
|
||||||
(5, 13),
|
(5, 13),
|
||||||
(5, 14),
|
(6, 9),
|
||||||
(6, 8),
|
|
||||||
(6, 13),
|
(6, 13),
|
||||||
|
(6, 14),
|
||||||
(7, 8),
|
(7, 8),
|
||||||
|
(7, 9),
|
||||||
(7, 13),
|
(7, 13),
|
||||||
|
(7, 14),
|
||||||
];
|
];
|
||||||
|
|
||||||
// co-ordinates of the dots from the memory chart
|
// co-ordinates of the dots from the memory chart
|
||||||
const MEM_XY: [(usize, usize); 16] = [
|
const MEM_XY: [(usize, usize); 14] = [
|
||||||
(1, 54),
|
|
||||||
(1, 55),
|
(1, 55),
|
||||||
(2, 54),
|
(2, 54),
|
||||||
(2, 55),
|
(2, 55),
|
||||||
(3, 53),
|
(3, 54),
|
||||||
(3, 55),
|
(3, 55),
|
||||||
(4, 52),
|
(4, 53),
|
||||||
(4, 55),
|
(4, 55),
|
||||||
(5, 51),
|
|
||||||
(5, 52),
|
(5, 52),
|
||||||
(5, 55),
|
(5, 53),
|
||||||
(5, 56),
|
(5, 56),
|
||||||
(6, 51),
|
(6, 52),
|
||||||
(6, 55),
|
(6, 56),
|
||||||
(7, 51),
|
(7, 51),
|
||||||
(7, 55),
|
(7, 56),
|
||||||
];
|
];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -275,7 +274,7 @@ mod tests {
|
|||||||
fn test_draw_blocks_charts_running_some() {
|
fn test_draw_blocks_charts_running_some() {
|
||||||
let mut setup = test_setup(80, 10, true, true);
|
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));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
setup
|
setup
|
||||||
@@ -324,7 +323,7 @@ mod tests {
|
|||||||
fn test_draw_blocks_charts_paused() {
|
fn test_draw_blocks_charts_paused() {
|
||||||
let mut setup = test_setup(80, 10, true, true);
|
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;
|
setup.app_data.lock().containers.items[0].state = State::Paused;
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
@@ -336,6 +335,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_snapshot!(setup.terminal.backend());
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
//
|
||||||
|
|
||||||
for (row_index, result_row) in get_result(&setup) {
|
for (row_index, result_row) in get_result(&setup) {
|
||||||
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
@@ -369,7 +369,7 @@ mod tests {
|
|||||||
/// When dead, text is red
|
/// When dead, text is red
|
||||||
fn test_draw_blocks_charts_dead() {
|
fn test_draw_blocks_charts_dead() {
|
||||||
let mut setup = test_setup(80, 10, true, true);
|
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;
|
setup.app_data.lock().containers.items[0].state = State::Dead;
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
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);
|
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));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
setup
|
setup
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::RIGHT_ARROW;
|
use super::SELECT_ARROW;
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::AppData,
|
app_data::AppData,
|
||||||
config::AppColors,
|
config::AppColors,
|
||||||
@@ -44,7 +44,7 @@ pub fn draw(
|
|||||||
let items = List::new(items)
|
let items = List::new(items)
|
||||||
.block(block)
|
.block(block)
|
||||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||||
.highlight_symbol(RIGHT_ARROW);
|
.highlight_symbol(SELECT_ARROW);
|
||||||
f.render_stateful_widget(items, area, i);
|
f.render_stateful_widget(items, area, i);
|
||||||
} else {
|
} else {
|
||||||
let paragraph = Paragraph::new("").block(block).alignment(Alignment::Center);
|
let paragraph = Paragraph::new("").block(block).alignment(Alignment::Center);
|
||||||
@@ -173,7 +173,7 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.docker_controls_scroll(&ScrollDirection::Next);
|
.docker_controls_scroll(&ScrollDirection::Down);
|
||||||
|
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
@@ -370,7 +370,7 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.docker_controls_scroll(&ScrollDirection::Next);
|
.docker_controls_scroll(&ScrollDirection::Down);
|
||||||
|
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
|
|||||||
@@ -82,11 +82,19 @@ fn format_containers<'a>(colors: AppColors, i: &ContainerItem, widths: &Columns)
|
|||||||
colors.containers.text,
|
colors.containers.text,
|
||||||
),
|
),
|
||||||
Span::styled(
|
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),
|
Style::default().fg(colors.containers.text_rx),
|
||||||
),
|
),
|
||||||
Span::styled(
|
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),
|
Style::default().fg(colors.containers.text_tx),
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|||||||
+92
-19
@@ -22,6 +22,7 @@ pub fn draw(
|
|||||||
colors: AppColors,
|
colors: AppColors,
|
||||||
error: &AppError,
|
error: &AppError,
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
|
host: Option<String>,
|
||||||
keymap: &Keymap,
|
keymap: &Keymap,
|
||||||
seconds: Option<u8>,
|
seconds: Option<u8>,
|
||||||
) {
|
) {
|
||||||
@@ -31,13 +32,21 @@ pub fn draw(
|
|||||||
.title_alignment(Alignment::Center)
|
.title_alignment(Alignment::Center)
|
||||||
.borders(Borders::ALL);
|
.borders(Borders::ALL);
|
||||||
|
|
||||||
let to_push = if matches!(error, AppError::DockerConnect) {
|
let mut text = format!("\n{error}");
|
||||||
format!(
|
|
||||||
"\n\n {}::v{} closing in {:02} seconds",
|
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,
|
NAME,
|
||||||
VERSION,
|
VERSION,
|
||||||
seconds.unwrap_or(5)
|
seconds.unwrap_or(5),
|
||||||
)
|
))
|
||||||
} else {
|
} else {
|
||||||
let clear_text = if keymap.clear == Keymap::new().clear {
|
let clear_text = if keymap.clear == Keymap::new().clear {
|
||||||
format!("( {} ) {SUFFIX_CLEAR}", keymap.clear.0)
|
format!("( {} ) {SUFFIX_CLEAR}", keymap.clear.0)
|
||||||
@@ -46,6 +55,8 @@ pub fn draw(
|
|||||||
} else {
|
} else {
|
||||||
format!(" ( {} ) {SUFFIX_CLEAR}", keymap.clear.0)
|
format!(" ( {} ) {SUFFIX_CLEAR}", keymap.clear.0)
|
||||||
};
|
};
|
||||||
|
text.push_str(&format!("\n\n{clear_text}"));
|
||||||
|
}
|
||||||
|
|
||||||
let quit_text = if keymap.quit == Keymap::new().quit {
|
let quit_text = if keymap.quit == Keymap::new().quit {
|
||||||
format!("( {} ) {SUFFIX_QUIT}", keymap.quit.0)
|
format!("( {} ) {SUFFIX_QUIT}", keymap.quit.0)
|
||||||
@@ -54,12 +65,7 @@ pub fn draw(
|
|||||||
} else {
|
} else {
|
||||||
format!(" ( {} ) {SUFFIX_QUIT}", keymap.quit.0)
|
format!(" ( {} ) {SUFFIX_QUIT}", keymap.quit.0)
|
||||||
};
|
};
|
||||||
format!("\n\n{clear_text}\n\n{quit_text}")
|
text.push_str(&format!("\n\n{quit_text}"));
|
||||||
};
|
|
||||||
|
|
||||||
let mut text = format!("\n{error}");
|
|
||||||
|
|
||||||
text.push_str(to_push.as_str());
|
|
||||||
|
|
||||||
// Find the maximum line width & height
|
// Find the maximum line width & height
|
||||||
let padded_width = max_line_width(&text) + 8;
|
let padded_width = max_line_width(&text) + 8;
|
||||||
@@ -106,8 +112,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
/// Test that the error popup is centered, red background, white border, white text, and displays the correct text
|
/// 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() {
|
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
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
.draw(|f| {
|
.draw(|f| {
|
||||||
@@ -115,6 +120,7 @@ mod tests {
|
|||||||
AppColors::new(),
|
AppColors::new(),
|
||||||
&AppError::DockerConnect,
|
&AppError::DockerConnect,
|
||||||
f,
|
f,
|
||||||
|
None,
|
||||||
&Keymap::new(),
|
&Keymap::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
);
|
);
|
||||||
@@ -123,16 +129,54 @@ mod tests {
|
|||||||
assert_snapshot!(setup.terminal.backend());
|
assert_snapshot!(setup.terminal.backend());
|
||||||
for (row_index, result_row) in get_result(&setup) {
|
for (row_index, result_row) in get_result(&setup) {
|
||||||
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
if let (0 | 8, _) = (row_index, result_cell_index) {
|
match (row_index, result_cell_index) {
|
||||||
|
(0 | 10, _) | (_, 0 | 49) => {
|
||||||
assert_eq!(result_cell.bg, Color::Reset);
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
assert_eq!(result_cell.fg, Color::Reset);
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
} else {
|
}
|
||||||
|
_ => {
|
||||||
assert_eq!(result_cell.bg, Color::Red);
|
assert_eq!(result_cell.bg, Color::Red);
|
||||||
assert_eq!(result_cell.fg, Color::White);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// Test that the clearable error popup is centered, red background, white border, white text, and displays the correct text
|
/// Test that the clearable error popup is centered, red background, white border, white text, and displays the correct text
|
||||||
@@ -146,6 +190,7 @@ mod tests {
|
|||||||
AppColors::new(),
|
AppColors::new(),
|
||||||
&AppError::DockerExec,
|
&AppError::DockerExec,
|
||||||
f,
|
f,
|
||||||
|
None,
|
||||||
&Keymap::new(),
|
&Keymap::new(),
|
||||||
Some(4),
|
Some(4),
|
||||||
);
|
);
|
||||||
@@ -183,7 +228,14 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
.draw(|f| {
|
.draw(|f| {
|
||||||
super::draw(colors, &AppError::DockerExec, f, &Keymap::new(), Some(4));
|
super::draw(
|
||||||
|
colors,
|
||||||
|
&AppError::DockerExec,
|
||||||
|
f,
|
||||||
|
None,
|
||||||
|
&Keymap::new(),
|
||||||
|
Some(4),
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -218,7 +270,14 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
.draw(|f| {
|
.draw(|f| {
|
||||||
super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
|
super::draw(
|
||||||
|
AppColors::new(),
|
||||||
|
&AppError::DockerExec,
|
||||||
|
f,
|
||||||
|
None,
|
||||||
|
&keymap,
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_snapshot!(setup.terminal.backend());
|
assert_snapshot!(setup.terminal.backend());
|
||||||
@@ -235,7 +294,14 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
.draw(|f| {
|
.draw(|f| {
|
||||||
super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
|
super::draw(
|
||||||
|
AppColors::new(),
|
||||||
|
&AppError::DockerExec,
|
||||||
|
f,
|
||||||
|
None,
|
||||||
|
&keymap,
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_snapshot!(setup.terminal.backend());
|
assert_snapshot!(setup.terminal.backend());
|
||||||
@@ -252,7 +318,14 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
.draw(|f| {
|
.draw(|f| {
|
||||||
super::draw(AppColors::new(), &AppError::DockerExec, f, &keymap, None);
|
super::draw(
|
||||||
|
AppColors::new(),
|
||||||
|
&AppError::DockerExec,
|
||||||
|
f,
|
||||||
|
None,
|
||||||
|
&keymap,
|
||||||
|
None,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_snapshot!(setup.terminal.backend());
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ use ratatui::{
|
|||||||
text::{Line, Span},
|
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
|
/// Create the filter_by by spans, coloured dependant on which one is selected
|
||||||
fn filter_by_spans(colors: AppColors, fd: &'_ FrameData) -> [Span<'_>; 4] {
|
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![
|
let mut line = vec![
|
||||||
Span::styled(" Esc ", style_but),
|
Span::styled(" Esc ", style_but),
|
||||||
Span::styled(" clear ", style_desc),
|
Span::styled(" clear ", style_desc),
|
||||||
Span::styled(" ← by → ", style_but),
|
Span::styled(format!(" {LEFT_ARROW} by {RIGHT_ARROW} "), style_but),
|
||||||
Span::from(" "),
|
Span::from(" "),
|
||||||
];
|
];
|
||||||
line.extend_from_slice(&filter_by_spans(colors, fd));
|
line.extend_from_slice(&filter_by_spans(colors, fd));
|
||||||
|
|||||||
@@ -39,15 +39,15 @@ fn gen_header<'a>(
|
|||||||
fn gen_header_block<'a>(colors: AppColors, fd: &FrameData, header: Header) -> (Color, &'a str) {
|
fn gen_header_block<'a>(colors: AppColors, fd: &FrameData, header: Header) -> (Color, &'a str) {
|
||||||
let mut color = colors.headers_bar.text;
|
let mut color = colors.headers_bar.text;
|
||||||
let mut suffix = "";
|
let mut suffix = "";
|
||||||
if let Some((a, b)) = &fd.sorted_by {
|
if let Some((a, b)) = &fd.sorted_by
|
||||||
if &header == a {
|
&& &header == a
|
||||||
|
{
|
||||||
match b {
|
match b {
|
||||||
SortedOrder::Asc => suffix = " ▲",
|
SortedOrder::Asc => suffix = " ▲",
|
||||||
SortedOrder::Desc => suffix = " ▼",
|
SortedOrder::Desc => suffix = " ▼",
|
||||||
}
|
}
|
||||||
color = colors.headers_bar.text_selected;
|
color = colors.headers_bar.text_selected;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(color, suffix)
|
(color, suffix)
|
||||||
}
|
}
|
||||||
|
|||||||
+1102
-577
File diff suppressed because it is too large
Load Diff
@@ -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<Line<'a>> {
|
||||||
|
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::<String>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the InspectContainer widget to the entire screen
|
||||||
|
pub fn draw(
|
||||||
|
f: &mut Frame,
|
||||||
|
colors: AppColors,
|
||||||
|
data: InspectData,
|
||||||
|
gui_state: &Arc<Mutex<GuiState>>,
|
||||||
|
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<InspectData> =
|
||||||
|
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()]),
|
||||||
|
},
|
||||||
|
)])),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
ui::{FrameData, GuiState, SelectablePanel, Status},
|
ui::{FrameData, GuiState, SelectablePanel, Status},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{RIGHT_ARROW, generate_block};
|
use super::{SELECT_ARROW, generate_block};
|
||||||
|
|
||||||
/// Draw the logs panel
|
/// Draw the logs panel
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
@@ -52,7 +52,7 @@ pub fn draw(
|
|||||||
} else if fd.color_logs {
|
} else if fd.color_logs {
|
||||||
let items = List::new(logs)
|
let items = List::new(logs)
|
||||||
.block(block)
|
.block(block)
|
||||||
.highlight_symbol(RIGHT_ARROW)
|
.highlight_symbol(SELECT_ARROW)
|
||||||
.scroll_padding(padding)
|
.scroll_padding(padding)
|
||||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
||||||
// This should always return Some, as logs is not empty
|
// This should always return Some, as logs is not empty
|
||||||
@@ -63,7 +63,7 @@ pub fn draw(
|
|||||||
let items = List::new(logs)
|
let items = List::new(logs)
|
||||||
.fg(colors.logs.text)
|
.fg(colors.logs.text)
|
||||||
.block(block)
|
.block(block)
|
||||||
.highlight_symbol(RIGHT_ARROW)
|
.highlight_symbol(SELECT_ARROW)
|
||||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
||||||
// This should always return Some, as logs is not empty
|
// This should always return Some, as logs is not empty
|
||||||
if let Some(log_state) = app_data.lock().get_log_state() {
|
if let Some(log_state) = app_data.lock().get_log_state() {
|
||||||
@@ -309,7 +309,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.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));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
setup
|
setup
|
||||||
|
|||||||
+23
-19
@@ -11,7 +11,8 @@ use crate::config::AppColors;
|
|||||||
|
|
||||||
use super::{FrameData, GuiState, SelectablePanel, Status, gui_state::Region};
|
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 commands;
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
pub mod delete_confirm;
|
pub mod delete_confirm;
|
||||||
@@ -20,18 +21,17 @@ pub mod filter;
|
|||||||
pub mod headers;
|
pub mod headers;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
|
pub mod inspect;
|
||||||
pub mod logs;
|
pub mod logs;
|
||||||
pub mod popup;
|
pub mod popup;
|
||||||
pub mod ports;
|
pub mod ports;
|
||||||
pub mod search_logs;
|
pub mod search_logs;
|
||||||
|
|
||||||
pub const NAME_TEXT: &str = r#"
|
pub const NAME_TEXT: &str = r#" 88
|
||||||
88
|
|
||||||
88
|
|
||||||
88
|
88
|
||||||
,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba,
|
,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba,
|
||||||
a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8
|
a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8
|
||||||
8b d8 )888( 8888[ 8PP""""""" 88
|
8b d8 )888( 8888( 8PP""""""" 88
|
||||||
"8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88
|
"8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88
|
||||||
`"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 "#;
|
`"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 "#;
|
||||||
|
|
||||||
@@ -39,7 +39,11 @@ pub const NAME: &str = env!("CARGO_PKG_NAME");
|
|||||||
pub const REPO: &str = env!("CARGO_PKG_REPOSITORY");
|
pub const REPO: &str = env!("CARGO_PKG_REPOSITORY");
|
||||||
pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
|
pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
|
||||||
pub const MARGIN: &str = " ";
|
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 = "⚪ ";
|
pub const CIRCLE: &str = "⚪ ";
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
@@ -103,13 +107,13 @@ fn generate_block<'a>(
|
|||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.title(ratatui::text::Line::from(title).left_aligned());
|
.title(ratatui::text::Line::from(title).left_aligned());
|
||||||
|
|
||||||
if panel == SelectablePanel::Logs {
|
if panel == SelectablePanel::Logs
|
||||||
if let Some(x) = fd.scroll_title.as_ref() {
|
&& let Some(x) = fd.scroll_title.as_ref()
|
||||||
|
{
|
||||||
block = block
|
block = block
|
||||||
.title_bottom(x.to_owned())
|
.title_bottom(x.to_owned())
|
||||||
.title_alignment(ratatui::layout::Alignment::Right);
|
.title_alignment(ratatui::layout::Alignment::Right);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !fd.status.contains(&Status::Filter) {
|
if !fd.status.contains(&Status::Filter) {
|
||||||
if fd.selected_panel == panel {
|
if fd.selected_panel == panel {
|
||||||
block = block.border_style(Style::default().fg(colors.borders.selected));
|
block = block.border_style(Style::default().fg(colors.borders.selected));
|
||||||
@@ -242,7 +246,7 @@ pub mod tests {
|
|||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
// Add fixed data to the cpu & mem vecdeques
|
// 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 {
|
for i in 1..=10 {
|
||||||
setup.app_data.lock().update_stats_by_id(
|
setup.app_data.lock().update_stats_by_id(
|
||||||
&setup.ids[0],
|
&setup.ids[0],
|
||||||
@@ -273,7 +277,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout() {
|
fn test_draw_blocks_whole_layout() {
|
||||||
let mut setup = test_setup(160, 30, true, true);
|
let mut setup = test_setup(160, 30, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
@@ -301,7 +305,7 @@ pub mod tests {
|
|||||||
/// Check that the whole layout is drawn correctly
|
/// Check that the whole layout is drawn correctly
|
||||||
fn test_draw_blocks_whole_layout_with_filter_bar() {
|
fn test_draw_blocks_whole_layout_with_filter_bar() {
|
||||||
let mut setup = test_setup(160, 30, true, true);
|
let mut setup = test_setup(160, 30, true, true);
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
|
|
||||||
setup.app_data.lock().containers.items[1]
|
setup.app_data.lock().containers.items[1]
|
||||||
@@ -337,7 +341,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout_long_name() {
|
fn test_draw_blocks_whole_layout_long_name() {
|
||||||
let mut setup = test_setup(190, 30, true, true);
|
let mut setup = test_setup(190, 30, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
@@ -370,7 +374,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout_no_logs() {
|
fn test_draw_blocks_whole_layout_no_logs() {
|
||||||
let mut setup = test_setup(160, 30, true, true);
|
let mut setup = test_setup(160, 30, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
@@ -399,7 +403,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout_short_height_logs() {
|
fn test_draw_blocks_whole_layout_short_height_logs() {
|
||||||
let mut setup = test_setup(160, 30, true, true);
|
let mut setup = test_setup(160, 30, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
@@ -431,7 +435,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout_help_panel() {
|
fn test_draw_blocks_whole_layout_help_panel() {
|
||||||
let mut setup = test_setup(160, 40, true, true);
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
@@ -461,7 +465,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout_error() {
|
fn test_draw_blocks_whole_layout_error() {
|
||||||
let mut setup = test_setup(160, 40, true, true);
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
@@ -495,7 +499,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout_delete() {
|
fn test_draw_blocks_whole_layout_delete() {
|
||||||
let mut setup = test_setup(160, 40, true, true);
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
@@ -527,7 +531,7 @@ pub mod tests {
|
|||||||
fn test_draw_blocks_whole_layout_info_box() {
|
fn test_draw_blocks_whole_layout_info_box() {
|
||||||
let mut setup = test_setup(160, 40, true, true);
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
insert_chart_data(&setup);
|
insert_all_chart_data(&setup);
|
||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
setup.app_data.lock().containers.items[0]
|
setup.app_data.lock().containers.items[0]
|
||||||
.ports
|
.ports
|
||||||
|
|||||||
@@ -241,11 +241,11 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.log_scroll(&crate::app_data::ScrollDirection::Previous);
|
.log_scroll(&crate::app_data::ScrollDirection::Up);
|
||||||
setup
|
setup
|
||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.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));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
setup
|
setup
|
||||||
@@ -298,7 +298,7 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.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));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
setup
|
setup
|
||||||
@@ -404,7 +404,7 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.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));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
setup
|
setup
|
||||||
@@ -433,7 +433,7 @@ mod tests {
|
|||||||
setup
|
setup
|
||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.log_scroll(&crate::app_data::ScrollDirection::Previous);
|
.log_scroll(&crate::app_data::ScrollDirection::Up);
|
||||||
|
|
||||||
let mut colors = AppColors::new();
|
let mut colors = AppColors::new();
|
||||||
|
|
||||||
|
|||||||
+14
@@ -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 │ •• │"
|
||||||
|
"│ │•• ••• │"
|
||||||
|
"│ │•••••• │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ │"
|
||||||
|
"│ │ │"
|
||||||
|
"│ │ │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ │"
|
||||||
|
"│ │ │"
|
||||||
|
"│ │ │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ │"
|
||||||
|
"│ │ │"
|
||||||
|
"│ │ │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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 │ •• │"
|
||||||
|
"│ │•• │"
|
||||||
|
"│ │ │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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 │ •• │"
|
||||||
|
"│ │•• │"
|
||||||
|
"│ │• │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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 │ •• │"
|
||||||
|
"│ │•• │"
|
||||||
|
"│ │ │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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 │ •• │"
|
||||||
|
"│ │•• ••• │"
|
||||||
|
"│ │•••••• │"
|
||||||
|
"╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ • │"
|
||||||
|
"│ │ •• ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ • • │"
|
||||||
|
"│ │ • • ││ │ •• • │"
|
||||||
|
"│ │ • •• ││ │ • • │"
|
||||||
|
"│ │•• •• ││ │• • │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ • │"
|
||||||
|
"│ │ •• ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ • • │"
|
||||||
|
"│ │ • • ││ │ •• • │"
|
||||||
|
"│ │ • •• ││ │ • • │"
|
||||||
|
"│ │•• •• ││ │• • │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ • │"
|
||||||
|
"│ │ •• ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ • • │"
|
||||||
|
"│ │ • • ││ │ •• • │"
|
||||||
|
"│ │ • •• ││ │ • • │"
|
||||||
|
"│ │•• •• ││ │• • │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
+14
@@ -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│ • │"
|
||||||
|
"│ │ •• ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ •• │"
|
||||||
|
"│ │ • • ││ │ • • │"
|
||||||
|
"│ │ • • ││ │ •• • │"
|
||||||
|
"│ │ • •• ││ │ • • │"
|
||||||
|
"│ │•• •• ││ │• • │"
|
||||||
|
"│ │ ││ │ │"
|
||||||
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
+5
-5
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
|
|||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
||||||
"│10.00%│ • ││100.00 kB│ •• │"
|
"│10.00%│ • ││100.00 kB│ • │"
|
||||||
"│ │ •• ││ │ •• │"
|
"│ │ •• ││ │ •• │"
|
||||||
"│ │ ••• ││ │ • • │"
|
"│ │ • • ││ │ •• │"
|
||||||
"│ │ • • ││ │ • • │"
|
"│ │ • • ││ │ • • │"
|
||||||
"│ │ • •• ││ │•• •• │"
|
"│ │ • • ││ │ •• • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │ • •• ││ │ • • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │•• •• ││ │• • │"
|
||||||
"│ │ ││ │ │"
|
"│ │ ││ │ │"
|
||||||
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
|
|||||||
+5
-5
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
|
|||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
||||||
"│10.00%│ • ││100.00 kB│ •• │"
|
"│10.00%│ • ││100.00 kB│ • │"
|
||||||
"│ │ •• ││ │ •• │"
|
"│ │ •• ││ │ •• │"
|
||||||
"│ │ ••• ││ │ • • │"
|
"│ │ • • ││ │ •• │"
|
||||||
"│ │ • • ││ │ • • │"
|
"│ │ • • ││ │ • • │"
|
||||||
"│ │ • •• ││ │•• •• │"
|
"│ │ • • ││ │ •• • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │ • •• ││ │ • • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │•• •• ││ │• • │"
|
||||||
"│ │ ││ │ │"
|
"│ │ ││ │ │"
|
||||||
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
|
|||||||
+5
-5
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
|
|||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
||||||
"│10.00%│ • ││100.00 kB│ •• │"
|
"│10.00%│ • ││100.00 kB│ • │"
|
||||||
"│ │ •• ││ │ •• │"
|
"│ │ •• ││ │ •• │"
|
||||||
"│ │ ••• ││ │ • • │"
|
"│ │ • • ││ │ •• │"
|
||||||
"│ │ • • ││ │ • • │"
|
"│ │ • • ││ │ • • │"
|
||||||
"│ │ • •• ││ │•• •• │"
|
"│ │ • • ││ │ •• • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │ • •• ││ │ • • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │•• •• ││ │• • │"
|
||||||
"│ │ ││ │ │"
|
"│ │ ││ │ │"
|
||||||
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
|
|||||||
+5
-5
@@ -3,12 +3,12 @@ source: src/ui/draw_blocks/charts.rs
|
|||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
"╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮"
|
||||||
"│10.00%│ • ││100.00 kB│ •• │"
|
"│10.00%│ • ││100.00 kB│ • │"
|
||||||
"│ │ •• ││ │ •• │"
|
"│ │ •• ││ │ •• │"
|
||||||
"│ │ ••• ││ │ • • │"
|
"│ │ • • ││ │ •• │"
|
||||||
"│ │ • • ││ │ • • │"
|
"│ │ • • ││ │ • • │"
|
||||||
"│ │ • •• ││ │•• •• │"
|
"│ │ • • ││ │ •• • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │ • •• ││ │ • • │"
|
||||||
"│ │• • ││ │• • │"
|
"│ │•• •• ││ │• • │"
|
||||||
"│ │ ││ │ │"
|
"│ │ ││ │ │"
|
||||||
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
"╰──────────────────────────────────────╯╰──────────────────────────────────────╯"
|
||||||
|
|||||||
+9
-7
@@ -3,11 +3,13 @@ source: src/ui/draw_blocks/error.rs
|
|||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
"╭────────────────── Error ───────────────────╮"
|
" ╭─────────────────── Error ────────────────────╮ "
|
||||||
"│ │"
|
" │ │ "
|
||||||
"│ Unable to access docker daemon │"
|
" │ Unable to access docker daemon │ "
|
||||||
"│ │"
|
" │ │ "
|
||||||
"│ oxker::v0.00.000 closing in 04 seconds │"
|
" │ oxker::v0.00.000 closing in 04 seconds │ "
|
||||||
"│ │"
|
" │ │ "
|
||||||
"╰────────────────────────────────────────────╯"
|
" │ ( q ) quit oxker │ "
|
||||||
|
" │ │ "
|
||||||
|
" ╰──────────────────────────────────────────────╯ "
|
||||||
" "
|
" "
|
||||||
|
|||||||
+15
@@ -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 │ "
|
||||||
|
" │ │ "
|
||||||
|
" ╰────────────────────────────────────────────────────────╯ "
|
||||||
|
" "
|
||||||
+19
-33
@@ -3,41 +3,27 @@ source: src/ui/draw_blocks/help.rs
|
|||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
" ╭ 0.00.000 ─────────────────────────────────────────────────────────────────────────╮ "
|
" ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────────────╮ "
|
||||||
" │ │ "
|
|
||||||
" │ 88 │ "
|
" │ 88 │ "
|
||||||
" │ 88 │ "
|
" │ 88 │ "
|
||||||
" │ 88 │ "
|
" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba │ "
|
||||||
" │ ,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 │ "
|
||||||
" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ "
|
" │ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir │ "
|
||||||
" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ "
|
" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC │ "
|
||||||
" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ "
|
|
||||||
" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 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 │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ A simple tui to view & control docker containers │ "
|
" │ Keymap │ "
|
||||||
" │ │ "
|
" │ q quit c Esc close dialog │ "
|
||||||
" │ ( tab ) or ( shift+tab ) change panels │ "
|
" │ Down Up j k Home End scroll vertically Left Right scroll horizontally │ "
|
||||||
" │ ( ↑ ↓ ) or ( j k ) or ( Home End ) scroll vertically │ "
|
" │ Control increase scroll speed Enter send docker command │ "
|
||||||
" │ ( ← → ) horizontal scroll across logs │ "
|
" │ e exec into a container i container inspect mode │ "
|
||||||
" │ ( ctrl ) increase scroll speed, used in conjunction scroll keys │ "
|
" │ / F1 filter mode # log search mode │ "
|
||||||
" │ ( enter ) send docker container command │ "
|
" │ h toggle this panel f force clear screen and redraw │ "
|
||||||
" │ ( e ) exec into a container │ "
|
" │ - = change log section height \ toggle of section visibility │ "
|
||||||
" │ ( f ) force clear the screen & redraw the gui │ "
|
" │ 1 ~ 9 sort by header - or click header 0 stop sort │ "
|
||||||
" │ ( h ) toggle this help information - or click heading │ "
|
" │ Tab Back Tab change panel m toggle mouse capture - allows text selection │ "
|
||||||
" │ ( s ) save logs to file │ "
|
" │ 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 │ "
|
|
||||||
" │ │ "
|
|
||||||
" │ │ "
|
|
||||||
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
|
||||||
" "
|
" "
|
||||||
|
|||||||
+29
@@ -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 │ "
|
||||||
|
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
" "
|
||||||
-43
@@ -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 │ "
|
|
||||||
" │ │ "
|
|
||||||
" │ │ "
|
|
||||||
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
|
||||||
" "
|
|
||||||
+22
-47
@@ -2,53 +2,28 @@
|
|||||||
source: src/ui/draw_blocks/help.rs
|
source: src/ui/draw_blocks/help.rs
|
||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ "
|
" ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────────────╮ "
|
||||||
" │ │ "
|
|
||||||
" │ 88 │ "
|
" │ 88 │ "
|
||||||
" │ 88 │ "
|
" │ 88 │ "
|
||||||
" │ 88 │ "
|
" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba │ "
|
||||||
" │ ,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 │ "
|
||||||
" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ "
|
" │ 8b d8 )888( 8888( 8PP""""""" 88 export location: /test_dir │ "
|
||||||
" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ "
|
" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 logs timezone: Etc/UTC │ "
|
||||||
" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ "
|
|
||||||
" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ "
|
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ A simple tui to view & control docker containers │ "
|
" │ Keymap │ "
|
||||||
" │ │ "
|
" │ n quit a close dialog │ "
|
||||||
" │ ( t ) select next panel │ "
|
" │ p s scroll vertically i j scroll horizontally │ "
|
||||||
" │ ( u ) select previous panel │ "
|
" │ r scroll to start q scroll to end │ "
|
||||||
" │ ( o ) scroll list down by one │ "
|
" │ Alt increase scroll speed Enter send docker command │ "
|
||||||
" │ ( s ) scroll list up by one │ "
|
" │ d exec into a container e container inspect mode │ "
|
||||||
" │ ( p ) scroll list to end │ "
|
" │ f filter mode g log search mode │ "
|
||||||
" │ ( q ) scroll list to start │ "
|
" │ 5 toggle this panel h force clear screen and redraw │ "
|
||||||
" │ ( h ) horizontal scroll logs right │ "
|
" │ k l change log section height m toggle of section visibility │ "
|
||||||
" │ ( g ) horizontal scroll logs left │ "
|
" │ z sort by name 1 sort by state │ "
|
||||||
" │ ( Alt ) increase scroll speed, used in conjunction scroll keys │ "
|
" │ 2 sort by status v sort by CPU │ "
|
||||||
" │ ( enter ) send docker container command │ "
|
" │ y sort by memory w sort by ID │ "
|
||||||
" │ ( d ) exec into a container │ "
|
" │ x sort by Image 0 sort by RX │ "
|
||||||
" │ ( f ) force clear the screen & redraw the gui │ "
|
" │ 3 sort by TX 4 stop sort │ "
|
||||||
" │ ( 5 ) toggle this help information - or click heading │ "
|
" │ t u change panel 6 toggle mouse capture - allows text selection │ "
|
||||||
" │ ( m ) save logs to file │ "
|
" │ o 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 │ "
|
|
||||||
" │ │ "
|
|
||||||
" ╰────────────────────────────────────────────────────────────────────────────────────╯ "
|
|
||||||
|
|||||||
+34
@@ -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 │ "
|
||||||
|
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
" "
|
||||||
+34
@@ -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 │ "
|
||||||
|
" ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
" "
|
||||||
-54
@@ -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 │ "
|
|
||||||
" │ │ "
|
|
||||||
" ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
|
||||||
+29
@@ -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 │ "
|
||||||
|
" ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
" "
|
||||||
+29
@@ -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 │ "
|
||||||
|
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
" "
|
||||||
+29
@@ -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 │ "
|
||||||
|
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
" "
|
||||||
-54
@@ -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 │ "
|
|
||||||
" │ │ "
|
|
||||||
" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
|
||||||
-43
@@ -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 │ "
|
|
||||||
" │ │ "
|
|
||||||
" │ │ "
|
|
||||||
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 → ────────────────────────────────────╯"
|
||||||
+54
@@ -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 ────────────────────────────────────╯"
|
||||||
+7
-7
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭────────────── 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│"
|
"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│"
|
||||||
"│ │ •• • ││ │ •• • ││ 8001 │"
|
"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│"
|
"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │127.0.0.1 8003 8003│"
|
||||||
"│ │• •• ││ │• •• ││ │"
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │• • ││ │• • ││ │ • │ │ │"
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯"
|
||||||
|
|||||||
+10
-10
@@ -32,13 +32,13 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭────────────── 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│"
|
"│10.00%│ • ││100.00 kB│ • ││ │••••••• │ │ ip private public│"
|
||||||
"│ │ • • ││ │ •• ││ 8001 │"
|
"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│"
|
"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│"
|
||||||
"│ │ • • ││ │ • • ││ │"
|
"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │"
|
||||||
"│ │ •• • • ││ │ •• • • ││ │"
|
"│ │ • • ││ │ • • ││ │ • │ │ │"
|
||||||
"│ │• •• ││ │• •• ││ │"
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│ │• • ││ │• • ││ │"
|
"│ │•• • ││ │•• •• ││ │ • │ │ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │ ││ │ ││ │ • │ │ │"
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯"
|
||||||
|
|||||||
+10
-10
@@ -32,13 +32,13 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭────────────── 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│"
|
"│10.00%│ • ││100.00 kB│ • ││ │••••••• │ │ ip private public│"
|
||||||
"│ │ • • ││ │ •• ││ 8001 │"
|
"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│"
|
"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│"
|
||||||
"│ │ • • ││ │ • • ││ │"
|
"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │"
|
||||||
"│ │ •• • • ││ │ •• • • ││ │"
|
"│ │ • • ││ │ • • ││ │ • │ │ │"
|
||||||
"│ │• •• ││ │• •• ││ │"
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│ │• • ││ │• • ││ │"
|
"│ │•• • ││ │•• •• ││ │ • │ │ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │ ││ │ ││ │ • │ │ │"
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯"
|
||||||
|
|||||||
+37
-37
@@ -4,41 +4,41 @@ expression: setup.terminal.backend()
|
|||||||
---
|
---
|
||||||
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) exit help "
|
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) exit help "
|
||||||
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
||||||
"│⚪ container_1 ✓ running Up 1 ho╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ ││▶ pause │" Hidden by multi-width symbols: [(2, " ")]
|
"│⚪ 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 ho│ │ ││ restart │"
|
"│ 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 ho│ 88 │ ││ stop │"
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
"│ │ 88 │ ││ delete │"
|
"│ ││ 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 │────────────────────────────────────╮"
|
"╭ Logs 3/3 - container_╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────────────────────────╮──────────────────────╮"
|
||||||
"│ line 1 │ 8b d8 )888( 8888[ 8PP""""""" 88 │ │"
|
"│ line 1 │ 88 │ │"
|
||||||
"│ line 2 │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ │"
|
"│ line 2 │ 88 │ │"
|
||||||
"│▶ line 3 │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 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 │ │"
|
||||||
"│ │ │ │"
|
"│ │ │ │"
|
||||||
"│ │ A simple tui to view & control docker containers │ │"
|
"│ │ Keymap │ │"
|
||||||
"│ │ │ │"
|
"│ │ q quit c Esc close dialog │ │"
|
||||||
"│ │ ( tab ) or ( shift+tab ) change panels │ │"
|
"│ │ Down Up j k Home End scroll vertically Left Right scroll horizontally │ │"
|
||||||
"│ │ ( ↑ ↓ ) or ( j k ) or ( Home End ) scroll vertically │ │"
|
"│ │ Control increase scroll speed Enter send docker command │ │"
|
||||||
"│ │ ( ← → ) horizontal scroll across logs │ │"
|
"│ │ e exec into a container i container inspect mode │ │"
|
||||||
"│ │ ( ctrl ) increase scroll speed, used in conjunction scroll keys │ │"
|
"│ │ / F1 filter mode # log search mode │ │"
|
||||||
"│ │ ( enter ) send docker container command │ │"
|
"│ │ h toggle this panel f force clear screen and redraw │ │"
|
||||||
"│ │ ( e ) exec into a container │ │"
|
"│ │ - = change log section height \ toggle of section visibility │ │"
|
||||||
"│ │ ( f ) force clear the screen & redraw the gui │ │"
|
"│ │ 1 ~ 9 sort by header - or click header 0 stop sort │ │"
|
||||||
"│ │ ( h ) toggle this help information - or click heading │ │"
|
"│ │ Tab Back Tab change panel m toggle mouse capture - allows text selection │ │"
|
||||||
"│ │ ( s ) save logs to file │ │"
|
"╰──────────────────────│ s save logs to file │──────────────────────╯"
|
||||||
"│ │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ │"
|
"╭────────────── cpu 03.│ │──── ports ───────────╮"
|
||||||
"│ │ ( F1 ) or ( / ) enter filter mode │ │"
|
"│10.00%│ • ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ip private public│"
|
||||||
"│ │ ( # ) enter log search mode │ │"
|
"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ ( 0 ) stop sort │ │"
|
"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│"
|
||||||
"│ │ ( 1 - 9 ) sort by header - or click header │ │"
|
"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │"
|
||||||
"╰────────────────────────────────────│ ( - = ) change log section height │────────────────────────────────────╯"
|
"│ │ • • ││ │ • • ││ │ • │ │ │"
|
||||||
"╭───────────────────────── cpu 03.00%│ ( \ ) toggle log section visibility │──────╮╭────────── ports ───────────╮"
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│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 │ ││ │"
|
|
||||||
"│ │• •• │ │ ││ │"
|
|
||||||
"│ │• • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │"
|
|
||||||
"│ │ ││ │ ││ │"
|
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
|
||||||
|
|||||||
+10
-10
@@ -32,13 +32,13 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭────────────── 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│"
|
"│10.00%│ • ││100.00 kB│ • ││ │••••••• │ │ ip private public│"
|
||||||
"│ │ • • ││ │ •• ││ 8001 │"
|
"│ │ •• ││ │ •• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│"
|
"│ │ • • ││ │ • • ││ │ •• │ │127.0.0.1 8003 8003│"
|
||||||
"│ │ • • ││ │ • • ││ │"
|
"│ │ • • ││ │ • • ││0.00 kb/s│ •• │ │ │"
|
||||||
"│ │ •• • • ││ │ •• • • ││ │"
|
"│ │ • • ││ │ • • ││ │ • │ │ │"
|
||||||
"│ │• •• ││ │• •• ││ "
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ "
|
||||||
"│ │• • ││ │• • ││ This is a test "
|
"│ │•• • ││ │•• •• ││ │ • │ │ This is a test "
|
||||||
"│ │ ││ │ ││ "
|
"│ │ ││ │ ││ │ • │ │ "
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰─────── "
|
"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰─────── "
|
||||||
|
|||||||
+7
-7
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────────────── cpu 03.00% ─────────────────────────────────╮╭────────────────────────────── memory 30.00 kB ───────────────────────────────╮╭────────── ports ───────────╮"
|
"╭─────────────────── 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│"
|
"│10.00%│ • ││100.00 kB│ •• ││ │•••••• • │ │ ip private public│"
|
||||||
"│ │ ••• • ││ │ •• • ││ 8001 │"
|
"│ │ ••• ││ │ ••• ││ │ • • │ │ 8001 │"
|
||||||
"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│"
|
"│ │ ••• • ││ │ •• • ││0.00 kb/s│ • • │ │127.0.0.1 8003 8003│"
|
||||||
"│ │•• ••• ││ │•• •• ││ │"
|
"│ │ • ••• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │• • ││ │• • ││ │ • │ │ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰───────────────────────────────────────────────────╯╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯ ╰────────────────────────────╯"
|
||||||
|
|||||||
+7
-7
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
|
|||||||
"│ ││ │"
|
"│ ││ │"
|
||||||
"│ ││ │"
|
"│ ││ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭────────────── 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│"
|
"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│"
|
||||||
"│ │ •• • ││ │ •• • ││ 8001 │"
|
"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│"
|
"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │127.0.0.1 8003 8003│"
|
||||||
"│ │• •• ││ │• •• ││ │"
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │• • ││ │• • ││ │ • │ │ │"
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯"
|
||||||
|
|||||||
+7
-7
@@ -25,10 +25,10 @@ expression: setup.terminal.backend()
|
|||||||
"│ line 2 │"
|
"│ line 2 │"
|
||||||
"│▶ line 3 │"
|
"│▶ line 3 │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭────────────── 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│"
|
"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│"
|
||||||
"│ │ •• • ││ │ •• • ││ 8001 │"
|
"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│"
|
"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │127.0.0.1 8003 8003│"
|
||||||
"│ │• •• ││ │• •• ││ │"
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │• • ││ │• • ││ │ • │ │ │"
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯"
|
||||||
|
|||||||
+7
-7
@@ -24,11 +24,11 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭────────────── 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│"
|
"│10.00%│ •• ││100.00 kB│ •• ││ │••••••• │ │ ip private public│"
|
||||||
"│ │ •• • ││ │ •• • ││ 8001 │"
|
"│ │ ••• ││ │ ••• ││ │ •• │ │ 8001 │"
|
||||||
"│ │ ••• • • ││ │ ••• • • ││ │"
|
"│ │ •• • ││ │ •• • ││0.00 kb/s│ •• │ │ │"
|
||||||
"│ │• •• ││ │• •• ││ │"
|
"│ │ • •• ││ │ • •• ││0.00 kb/s│ • │ │ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │• • ││ │• • ││ │ • │ │ │"
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯╰─────────────────────────────────────────╯ ╰────────────────────────────╯"
|
||||||
" Esc clear ← by → Name Image Status All filter term: r_1 "
|
" Esc clear ← by → Name Image Status All filter term: r_1 "
|
||||||
|
|||||||
+58
-3
@@ -9,7 +9,7 @@ use tokio::task::JoinHandle;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{AppData, ContainerId, Header},
|
app_data::{AppData, ContainerId, Header, ScrollDirection},
|
||||||
exec::ExecMode,
|
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
|
/// The application gui state can be in multiple of these four states at the same time
|
||||||
/// Various functions (e.g input handler), operate differently depending upon current Status
|
/// Various functions (e.g input handler), operate differently depending upon current Status
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
DeleteConfirm,
|
DeleteConfirm,
|
||||||
DockerConnect,
|
DockerConnect(Option<String>),
|
||||||
Error,
|
Error,
|
||||||
Exec,
|
Exec,
|
||||||
Filter,
|
Filter,
|
||||||
Help,
|
Help,
|
||||||
Init,
|
Init,
|
||||||
|
Inspect,
|
||||||
Logs,
|
Logs,
|
||||||
SearchLogs,
|
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<Mutex>
|
/// Global gui_state, stored in an Arc<Mutex>
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GuiState {
|
pub struct GuiState {
|
||||||
@@ -190,6 +197,8 @@ pub struct GuiState {
|
|||||||
selected_panel: SelectablePanel,
|
selected_panel: SelectablePanel,
|
||||||
screen_width: u16,
|
screen_width: u16,
|
||||||
show_logs: bool,
|
show_logs: bool,
|
||||||
|
inspect_offset: ScrollOffset,
|
||||||
|
inspect_offset_max: ScrollOffset,
|
||||||
status: HashSet<Status>,
|
status: HashSet<Status>,
|
||||||
pub info_box_text: Option<(String, Instant)>,
|
pub info_box_text: Option<(String, Instant)>,
|
||||||
}
|
}
|
||||||
@@ -203,6 +212,8 @@ impl GuiState {
|
|||||||
intersect_heading: HashMap::new(),
|
intersect_heading: HashMap::new(),
|
||||||
intersect_help: None,
|
intersect_help: None,
|
||||||
intersect_panel: HashMap::new(),
|
intersect_panel: HashMap::new(),
|
||||||
|
inspect_offset: ScrollOffset::default(),
|
||||||
|
inspect_offset_max: ScrollOffset::default(),
|
||||||
loading_handle: None,
|
loading_handle: None,
|
||||||
loading_index: 0,
|
loading_index: 0,
|
||||||
loading_set: HashSet::new(),
|
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
|
/// Set the screen width, used for offset char calculations
|
||||||
pub const fn set_screen_width(&mut self, width: u16) {
|
pub const fn set_screen_width(&mut self, width: u16) {
|
||||||
self.screen_width = width;
|
self.screen_width = width;
|
||||||
|
|||||||
+49
-27
@@ -30,12 +30,11 @@ pub use self::color_match::*;
|
|||||||
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{
|
app_data::{
|
||||||
AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, LogSearch,
|
AppData, ChartsData, Columns, ContainerId, ContainerPorts, FilterBy, Header, LogSearch,
|
||||||
MemTuple, SortedOrder, State,
|
SortedOrder, State,
|
||||||
},
|
},
|
||||||
app_error::AppError,
|
app_error::AppError,
|
||||||
config::{AppColors, Keymap},
|
config::{AppColors, Keymap},
|
||||||
exec::TerminalSize,
|
|
||||||
input_handler::InputMessages,
|
input_handler::InputMessages,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -132,12 +131,12 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the the error message ui, for 5 seconds, with a countdown
|
/// 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<String>) -> Result<(), AppError> {
|
||||||
let mut seconds = 5;
|
let mut seconds = 5;
|
||||||
let colors = self.app_data.lock().config.app_colors;
|
let colors = self.app_data.lock().config.app_colors;
|
||||||
let keymap = self.app_data.lock().config.keymap.clone();
|
let keymap = self.app_data.lock().config.keymap.clone();
|
||||||
let mut redraw = true;
|
let mut redraw = true;
|
||||||
loop {
|
while self.is_running.load(Ordering::SeqCst) {
|
||||||
if self.now.elapsed() >= std::time::Duration::from_secs(1) {
|
if self.now.elapsed() >= std::time::Duration::from_secs(1) {
|
||||||
seconds -= 1;
|
seconds -= 1;
|
||||||
self.now = Instant::now();
|
self.now = Instant::now();
|
||||||
@@ -155,6 +154,7 @@ impl Ui {
|
|||||||
colors,
|
colors,
|
||||||
&AppError::DockerConnect,
|
&AppError::DockerConnect,
|
||||||
f,
|
f,
|
||||||
|
host.clone(),
|
||||||
&keymap,
|
&keymap,
|
||||||
Some(seconds),
|
Some(seconds),
|
||||||
);
|
);
|
||||||
@@ -163,8 +163,18 @@ impl Ui {
|
|||||||
{
|
{
|
||||||
return Err(AppError::Terminal);
|
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;
|
redraw = false;
|
||||||
std::thread::sleep(POLL_RATE);
|
// std::thread::sleep(POLL_RATE);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -183,7 +193,8 @@ impl Ui {
|
|||||||
if let Some(mode) = exec_mode {
|
if let Some(mode) = exec_mode {
|
||||||
self.reset_terminal().ok();
|
self.reset_terminal().ok();
|
||||||
self.terminal.clear().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
|
self.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.set_error(e, &self.gui_state, Status::Error);
|
.set_error(e, &self.gui_state, Status::Error);
|
||||||
@@ -237,8 +248,9 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if crossterm::event::poll(POLL_RATE).unwrap_or(false) {
|
if crossterm::event::poll(POLL_RATE).unwrap_or(false)
|
||||||
if let Ok(event) = event::read() {
|
&& let Ok(event) = event::read()
|
||||||
|
{
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
if key.kind == event::KeyEventKind::Press {
|
if key.kind == event::KeyEventKind::Press {
|
||||||
self.input_tx
|
self.input_tx
|
||||||
@@ -264,7 +276,6 @@ impl Ui {
|
|||||||
self.gui_state.lock().set_screen_width(width);
|
self.gui_state.lock().set_screen_width(width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.check_clear();
|
self.check_clear();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -273,8 +284,11 @@ impl Ui {
|
|||||||
/// Draw either the Error, or main oxker ui, to the terminal
|
/// Draw either the Error, or main oxker ui, to the terminal
|
||||||
async fn draw_ui(&mut self) -> Result<(), AppError> {
|
async fn draw_ui(&mut self) -> Result<(), AppError> {
|
||||||
let status = self.gui_state.lock().get_status();
|
let status = self.gui_state.lock().get_status();
|
||||||
if status.contains(&Status::DockerConnect) {
|
if let Some(Status::DockerConnect(msg)) = status
|
||||||
self.err_loop()?;
|
.iter()
|
||||||
|
.find(|s| matches!(s, Status::DockerConnect(_)))
|
||||||
|
{
|
||||||
|
self.err_loop(msg.clone()).await?;
|
||||||
} else {
|
} else {
|
||||||
self.gui_loop().await?;
|
self.gui_loop().await?;
|
||||||
}
|
}
|
||||||
@@ -287,7 +301,7 @@ impl Ui {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct FrameData {
|
pub struct FrameData {
|
||||||
chart_data: Option<(CpuTuple, MemTuple)>,
|
chart_data: Option<ChartsData>,
|
||||||
color_logs: bool,
|
color_logs: bool,
|
||||||
columns: Columns,
|
columns: Columns,
|
||||||
container_title: String,
|
container_title: String,
|
||||||
@@ -355,6 +369,12 @@ fn draw_frame(
|
|||||||
let contains_filter = fd.status.contains(&Status::Filter);
|
let contains_filter = fd.status.contains(&Status::Filter);
|
||||||
let contains_search_logs = fd.status.contains(&Status::SearchLogs);
|
let contains_search_logs = fd.status.contains(&Status::SearchLogs);
|
||||||
|
|
||||||
|
let contains_inspect = fd.status.contains(&Status::Inspect);
|
||||||
|
|
||||||
|
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()
|
let whole_layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(if contains_filter || contains_search_logs {
|
.constraints(if contains_filter || contains_search_logs {
|
||||||
@@ -442,27 +462,29 @@ fn draw_frame(
|
|||||||
.constraints([Constraint::Min(1), Constraint::Max(ports_len)])
|
.constraints([Constraint::Min(1), Constraint::Max(ports_len)])
|
||||||
.split(upper_main[1]);
|
.split(upper_main[1]);
|
||||||
|
|
||||||
draw_blocks::charts::draw(lower[0], colors, f, fd);
|
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);
|
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() {
|
if let Some((text, instant)) = fd.info_text.as_ref() {
|
||||||
draw_blocks::info::draw(colors, f, gui_state, instant, text.to_owned());
|
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() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user