chore: merge release-v0.10.4 into main
This commit is contained in:
@@ -38,8 +38,7 @@
|
|||||||
// VS Code don't watch files under ./target
|
// VS Code don't watch files under ./target
|
||||||
"files.watcherExclude": {
|
"files.watcherExclude": {
|
||||||
"**/target/**": true
|
"**/target/**": true
|
||||||
},
|
}
|
||||||
"rust-analyzer.checkOnSave.command": "clippy"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+20
-3
@@ -1,9 +1,26 @@
|
|||||||
### 2025-04-22
|
### 2025-06-18
|
||||||
|
|
||||||
### Chores
|
### Chores
|
||||||
+ dependencies updated, [bbfd2462a1f45008587b488e8c6049ee76da72f2]
|
+ .devcontainer updated, [324f8268278081504d5357f2ed89b78ca2c25d04]
|
||||||
|
+ dependencies updated, [0ace9dd662144a589341779a64d7fcd8de7d9978], [a636007547280b3b3db69374601dbece4bc21eef]
|
||||||
|
+ Rust 1.87.0 linting, [395b1aa7e997a528e4f21e66f5f859001c1c3ec1], [67e5888e008cfd504c10e47f678f9351c838be99]
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
+ example config files updated, [63ab7de72897de460f31181c5a42befbee2f91d3], [8fb5ac4a945b75f3fcd118c53be1202ccbc43c59]
|
||||||
|
+ README.md updated, link to directories crate, closes #65, [c2bfe3296563daf4b7f077469f3eeff6895720b0]
|
||||||
|
|
||||||
|
### Features
|
||||||
|
+ log panel size configurable, closes #50, use the `-` or `=` keys to change the height of the logs panel, or `\` to toggle visibility. Automatically hide the logs panel using a new config item `show_logs`, see `example_config/*` files for more details, [6edf99e0846bb4134d8ee5b646065b8cda8074d7]
|
||||||
|
+ build release binaries for aarch64-apple-darwin, closes #62, personally untested on MacOS - but others suggest it works as expected, [e7114d2f5e0ed8935943be64726fc2d90464a777], [2e8500902a515a246f9d9a503b4350849d634978]
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
+ merge args color/raw fix, [d198398795698a21d81d3fd20231c482cc346ab5]
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
+ reduce cloning of the logs text items, can expect 40-50% reduction in CPU and memory usage in certain common situations, [ecefa302b9ef5320ad4cce0b606aca70a7b459e2]
|
||||||
|
+ dead code removed, [b40b6b197e4e5fbdab083bc918d1a5d2750597f3]
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
+ fix tests for MacOS, closes #61, [cfc2decd8d237f1ac3f0bdb2b3d5581684064448]
|
+ add more whole layout tests, [4b81c6caaf12028d7527c3f23cd2de6d1503e223]
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ jobs:
|
|||||||
- target: arm-unknown-linux-musleabihf
|
- target: arm-unknown-linux-musleabihf
|
||||||
output_name: linux_armv6.tar.gz
|
output_name: linux_armv6.tar.gz
|
||||||
|
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
output_name: apple_darwin_aarch64.tar.gz
|
||||||
|
|
||||||
- target: x86_64-pc-windows-gnu
|
- target: x86_64-pc-windows-gnu
|
||||||
output_name: windows_x86_64.zip
|
output_name: windows_x86_64.zip
|
||||||
|
|
||||||
@@ -38,8 +41,15 @@ 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
|
# Build binary for arm MacOS using Docker Zigbuild
|
||||||
- name: build
|
- name: build
|
||||||
|
if: matrix.target == 'aarch64-apple-darwin'
|
||||||
|
run: |
|
||||||
|
docker run --rm -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin
|
||||||
|
|
||||||
|
# Build all other targets using Cross
|
||||||
|
- name: build
|
||||||
|
if: matrix.target != 'aarch64-apple-darwin'
|
||||||
run: cross build --target ${{ matrix.target }} --release
|
run: cross build --target ${{ matrix.target }} --release
|
||||||
|
|
||||||
# Compress the output
|
# Compress the output
|
||||||
@@ -126,8 +136,8 @@ jobs:
|
|||||||
docker build --platform linux/arm/v6,linux/arm64,linux/amd64 \
|
docker build --platform linux/arm/v6,linux/arm64,linux/amd64 \
|
||||||
-t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:latest \
|
-t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:latest \
|
||||||
-t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:${{env.CURRENT_SEMVER}} \
|
-t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:${{env.CURRENT_SEMVER}} \
|
||||||
-t ghcr.io/${{ github.repository_owner }}/oxker:latest \
|
-t ghcr.io/${{ github.repository_owner }}/${{ github.ref_name }}:latest \
|
||||||
-t ghcr.io/${{ github.repository_owner }}/oxker:${{env.CURRENT_SEMVER}} \
|
-t ghcr.io/${{ github.repository_owner }}/${{ github.ref_name }}:${{env.CURRENT_SEMVER}} \
|
||||||
--provenance=false --sbom=false \
|
--provenance=false --sbom=false \
|
||||||
--push \
|
--push \
|
||||||
-f containerised/Dockerfile .
|
-f containerised/Dockerfile .
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
/releases
|
/releases
|
||||||
|
# Used in the zigbuild for aarch64-apple-darwin
|
||||||
|
.intentionally-empty-file.o
|
||||||
@@ -1,3 +1,29 @@
|
|||||||
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.10.4'>v0.10.4</a>
|
||||||
|
### 2025-06-18
|
||||||
|
|
||||||
|
### Chores
|
||||||
|
+ .devcontainer updated, [324f8268](https://github.com/mrjackwills/oxker/commit/324f8268278081504d5357f2ed89b78ca2c25d04)
|
||||||
|
+ dependencies updated, [0ace9dd6](https://github.com/mrjackwills/oxker/commit/0ace9dd662144a589341779a64d7fcd8de7d9978), [a6360075](https://github.com/mrjackwills/oxker/commit/a636007547280b3b3db69374601dbece4bc21eef)
|
||||||
|
+ Rust 1.87.0 linting, [395b1aa7](https://github.com/mrjackwills/oxker/commit/395b1aa7e997a528e4f21e66f5f859001c1c3ec1), [67e5888e](https://github.com/mrjackwills/oxker/commit/67e5888e008cfd504c10e47f678f9351c838be99)
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
+ example config files updated, [63ab7de7](https://github.com/mrjackwills/oxker/commit/63ab7de72897de460f31181c5a42befbee2f91d3), [8fb5ac4a](https://github.com/mrjackwills/oxker/commit/8fb5ac4a945b75f3fcd118c53be1202ccbc43c59)
|
||||||
|
+ README.md updated, link to directories crate, closes [#65](https://github.com/mrjackwills/oxker/issues/65), [c2bfe329](https://github.com/mrjackwills/oxker/commit/c2bfe3296563daf4b7f077469f3eeff6895720b0)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
+ log panel size configurable, closes [#50](https://github.com/mrjackwills/oxker/issues/50), use the `-` or `=` keys to change the height of the logs panel, or `\` to toggle visibility. Automatically hide the logs panel using a new config item `show_logs`, see `example_config/*` files for more details, [6edf99e0](https://github.com/mrjackwills/oxker/commit/6edf99e0846bb4134d8ee5b646065b8cda8074d7)
|
||||||
|
+ build release binaries for aarch64-apple-darwin, closes [#62](https://github.com/mrjackwills/oxker/issues/62), personally untested on MacOS - but others suggest it works as expected, [e7114d2f](https://github.com/mrjackwills/oxker/commit/e7114d2f5e0ed8935943be64726fc2d90464a777), [2e850090](https://github.com/mrjackwills/oxker/commit/2e8500902a515a246f9d9a503b4350849d634978)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
+ merge args color/raw fix, [d1983987](https://github.com/mrjackwills/oxker/commit/d198398795698a21d81d3fd20231c482cc346ab5)
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
+ reduce cloning of the logs text items, can expect 40-50% reduction in CPU and memory usage in certain common situations, [ecefa302](https://github.com/mrjackwills/oxker/commit/ecefa302b9ef5320ad4cce0b606aca70a7b459e2)
|
||||||
|
+ dead code removed, [b40b6b19](https://github.com/mrjackwills/oxker/commit/b40b6b197e4e5fbdab083bc918d1a5d2750597f3)
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
+ add more whole layout tests, [4b81c6ca](https://github.com/mrjackwills/oxker/commit/4b81c6caaf12028d7527c3f23cd2de6d1503e223)
|
||||||
|
|
||||||
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.10.3'>v0.10.3</a>
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.10.3'>v0.10.3</a>
|
||||||
### 2025-04-22
|
### 2025-04-22
|
||||||
|
|
||||||
|
|||||||
Generated
+336
-270
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oxker"
|
name = "oxker"
|
||||||
version = "0.10.3"
|
version = "0.10.4"
|
||||||
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"
|
||||||
@@ -27,7 +27,7 @@ similar_names = "allow"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bollard = "0.18"
|
bollard = "0.19"
|
||||||
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"
|
||||||
@@ -39,12 +39,12 @@ ratatui = "0.29"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_jsonc = "1.0"
|
serde_jsonc = "1.0"
|
||||||
tokio = { version = "1.44", features = ["full"] }
|
tokio = { version = "1.45", features = ["full"] }
|
||||||
tokio-util = "0.7"
|
tokio-util = "0.7"
|
||||||
toml = { version = "0.8", default-features = false, features = ["parse"] }
|
toml = { version = "0.8", default-features = false, features = ["parse"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
uuid = { version = "1.16", features = ["fast-rng", "v4"] }
|
uuid = { version = "1.17", features = ["fast-rng", "v4"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ In application controls, these, amongst many other settings, can be customized w
|
|||||||
| ```( 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. |
|
||||||
| ```( 0 )``` | Stop sorting.|
|
| ```( 0 )``` | Stop sorting.|
|
||||||
| ```( F1 )``` or ```( / )``` | Enter filter mode. |
|
| ```( F1 )``` or ```( / )``` | Enter filter mode. |
|
||||||
|
| ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.|
|
||||||
|
| ```( \ )``` | Toggle the visibility of the logs panel.|
|
||||||
| ```( e )``` | Exec into the selected container - not available on Windows.|
|
| ```( e )``` | Exec into the selected container - not available on Windows.|
|
||||||
| ```( 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.|
|
||||||
@@ -143,7 +145,10 @@ A config file enables the user to persist settings, create a custom keymap, set
|
|||||||
Examples of the config file, alsong with explanations of each value, can be found in the [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config) directory. `oxker` supports `.toml`,`.json`, and `.jsonc` file formats.
|
Examples of the config file, alsong with explanations of each value, can be found in the [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config) directory. `oxker` supports `.toml`,`.json`, and `.jsonc` file formats.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
If not config file is found, `oxker` will create a `config.toml` in the user's local config directory. Command line arguments will take priority over values from the config file.
|
If no config file is found, a `config.toml` file will be created in an `oxker` directory in the user's local config directory, as found by the [directories crate](https://docs.rs/directories/6.0.0/directories/struct.BaseDirs.html#method.config_local_dir).
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
Command line arguments will take priority over values from the config file.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
If running an `oxker` container, the default config location will be `/` rather than the automatically detected platform-specific local config directory, and can be mounted as follows;
|
If running an `oxker` container, the default config location will be `/` rather than the automatically detected platform-specific local config directory, and can be mounted as follows;
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
[default.extend-words]
|
[default.extend-words]
|
||||||
ratatui = "ratatui"
|
ratatui = "ratatui"
|
||||||
|
|
||||||
|
[default]
|
||||||
|
extend-ignore-words-re = ["[(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{40})]"]
|
||||||
|
|||||||
+25
-9
@@ -230,6 +230,18 @@ cross_build_x86_windows() {
|
|||||||
cross build --target x86_64-pc-windows-gnu --release
|
cross build --target x86_64-pc-windows-gnu --release
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Build, using zig-build, for Apple silicon
|
||||||
|
zig_build_aarch64_apple() {
|
||||||
|
# mkdir /workspace/oxker/target
|
||||||
|
echo -e "${YELLOW}docker run --rm -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin${RESET}"
|
||||||
|
docker run --rm -v "$(pwd):/io" -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin
|
||||||
|
if ask_yn "sudo chown $(pwd)/target"; then
|
||||||
|
echo -e "${YELLOW}sudo chown -R vscode:vscode $(pwd)/target${RESET}"
|
||||||
|
sudo chown -R vscode:vscode "$(pwd)/target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
# Build all releases that GitHub workflow would
|
# Build all releases that GitHub workflow would
|
||||||
# This will download GB's of docker images
|
# This will download GB's of docker images
|
||||||
cross_build_all() {
|
cross_build_all() {
|
||||||
@@ -242,6 +254,8 @@ cross_build_all() {
|
|||||||
ask_continue
|
ask_continue
|
||||||
cross_build_x86_windows
|
cross_build_x86_windows
|
||||||
ask_continue
|
ask_continue
|
||||||
|
zig_build_aarch64_apple
|
||||||
|
ask_continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1 text to colourise
|
# $1 text to colourise
|
||||||
@@ -371,17 +385,17 @@ release_flow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build_choice() {
|
build_choice() {
|
||||||
cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16)
|
cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16)
|
||||||
options=(
|
options=(
|
||||||
1 "x86 musl linux" off
|
1 "x86 musl linux" off
|
||||||
2 "aarch64 musl linux" off
|
2 "aarch64 musl linux" off
|
||||||
3 "armv6 musl linux" off
|
3 "armv6 musl linux" off
|
||||||
4 "x86 windows" off
|
4 "aarch64 apple" off
|
||||||
5 "all" off
|
5 "x86 windows" off
|
||||||
|
6 "all" off
|
||||||
)
|
)
|
||||||
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
|
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
|
||||||
exitStatus=$?
|
exitStatus=$?
|
||||||
clear
|
|
||||||
if [ $exitStatus -ne 0 ]; then
|
if [ $exitStatus -ne 0 ]; then
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
@@ -403,10 +417,14 @@ build_choice() {
|
|||||||
exit
|
exit
|
||||||
;;
|
;;
|
||||||
4)
|
4)
|
||||||
cross_build_x86_windows
|
zig_build_aarch64_apple
|
||||||
exit
|
exit
|
||||||
;;
|
;;
|
||||||
5)
|
5)
|
||||||
|
cross_build_x86_windows
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
6)
|
||||||
cross_build_all
|
cross_build_all
|
||||||
exit
|
exit
|
||||||
;;
|
;;
|
||||||
@@ -415,7 +433,7 @@ build_choice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build_container_choice() {
|
build_container_choice() {
|
||||||
cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16)
|
cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16)
|
||||||
options=(
|
options=(
|
||||||
1 "x86 " off
|
1 "x86 " off
|
||||||
2 "aarch64" off
|
2 "aarch64" off
|
||||||
@@ -424,7 +442,6 @@ build_container_choice() {
|
|||||||
)
|
)
|
||||||
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
|
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
|
||||||
exitStatus=$?
|
exitStatus=$?
|
||||||
clear
|
|
||||||
if [ $exitStatus -ne 0 ]; then
|
if [ $exitStatus -ne 0 ]; then
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
@@ -455,7 +472,7 @@ build_container_choice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16)
|
cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16)
|
||||||
options=(
|
options=(
|
||||||
1 "test" off
|
1 "test" off
|
||||||
2 "release" off
|
2 "release" off
|
||||||
@@ -464,7 +481,6 @@ main() {
|
|||||||
)
|
)
|
||||||
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
|
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
|
||||||
exitStatus=$?
|
exitStatus=$?
|
||||||
clear
|
|
||||||
if [ $exitStatus -ne 0 ]; then
|
if [ $exitStatus -ne 0 ]; then
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -29,12 +29,15 @@
|
|||||||
// "save_dir": "$HOME",
|
// "save_dir": "$HOME",
|
||||||
// Force use of docker cli when execing into containers, honestly mostly pointless
|
// Force use of docker cli when execing into containers, honestly mostly pointless
|
||||||
"use_cli": false,
|
"use_cli": false,
|
||||||
|
// Show the logs section - this can be changed during operation with the log_section_toggle key
|
||||||
|
"show_logs": true,
|
||||||
//////////////////
|
//////////////////
|
||||||
// Custom Keymap //
|
// Custom Keymap //
|
||||||
//////////////////
|
//////////////////
|
||||||
// Available keys are;
|
// Available keys are;
|
||||||
// 1) a-z and A-Z
|
// 1) a-z and A-Z
|
||||||
// 2) 0-9
|
// 2) 0-9
|
||||||
|
// WARNING if using the \ key, it needs to be escaped, e.g. "log_section_toggle": ["\\"]
|
||||||
// 3) / \ , . # ' [ ] ; = -
|
// 3) / \ , . # ' [ ] ; = -
|
||||||
// 3) F1-F12
|
// 3) F1-F12
|
||||||
// 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down
|
// 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down
|
||||||
@@ -144,6 +147,18 @@
|
|||||||
// Toggle mouse capture
|
// Toggle mouse capture
|
||||||
"toggle_mouse_capture": [
|
"toggle_mouse_capture": [
|
||||||
"m"
|
"m"
|
||||||
|
],
|
||||||
|
// Reduce the height of the logs list section
|
||||||
|
"log_section_height_decrease": [
|
||||||
|
"-"
|
||||||
|
],
|
||||||
|
// Increase the height of the logs list section
|
||||||
|
"log_section_height_increase": [
|
||||||
|
"+"
|
||||||
|
],
|
||||||
|
// Toggle visibility of the log section
|
||||||
|
"log_section_toggle": [
|
||||||
|
"\\"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ timezone = "Etc/UTC"
|
|||||||
|
|
||||||
# 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.012345678Z
|
# 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.012345678Z
|
||||||
# *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"
|
||||||
|
|
||||||
# Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows
|
# Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows
|
||||||
# save_dir = "$HOME"
|
# save_dir = "$HOME"
|
||||||
@@ -40,6 +40,9 @@ timestamp_format="%Y-%m-%dT%H:%M:%S.%8f"
|
|||||||
# Force use of docker cli when execing into containers, honestly mostly pointless
|
# Force use of docker cli when execing into containers, honestly mostly pointless
|
||||||
use_cli = false
|
use_cli = false
|
||||||
|
|
||||||
|
# Show the logs section - this can be changed during operation with the log_section_toggle key
|
||||||
|
show_logs = true
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Custom Keymap #
|
# Custom Keymap #
|
||||||
#################
|
#################
|
||||||
@@ -47,61 +50,68 @@ use_cli = false
|
|||||||
# Available keys are;
|
# Available keys are;
|
||||||
# 1) a-z and A-Z
|
# 1) a-z and A-Z
|
||||||
# 2) 0-9
|
# 2) 0-9
|
||||||
|
# WARNING if using the \ key, it needs to be escaped, e.g. log_section_toggle = ["\\"]
|
||||||
# 3) / \ , . # ' [ ] ; = -
|
# 3) / \ , . # ' [ ] ; = -
|
||||||
# 3) F1-F12
|
# 3) F1-F12
|
||||||
# 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down
|
# 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down
|
||||||
|
|
||||||
|
|
||||||
# Each definition can have two keys associated with it
|
# Each definition can have two keys associated with it
|
||||||
# If any key clashes are found, oxker will revert to it's default keymap
|
# If any key clashes are found, oxker will revert to it's default keymap
|
||||||
|
|
||||||
[keymap]
|
[keymap]
|
||||||
# Clear any popup boxes, filter panel, or help panel
|
# Clear any popup boxes, filter panel, or help panel
|
||||||
clear=["c", "esc"]
|
clear = ["c", "esc"]
|
||||||
# Cancel delete - clear also works here
|
# Cancel delete - clear also works here
|
||||||
delete_deny=["n"]
|
delete_deny = ["n"]
|
||||||
# Confirm Delete
|
# Confirm Delete
|
||||||
delete_confirm=["y"]
|
delete_confirm = ["y"]
|
||||||
# Exec into the selected container
|
# Exec into the selected container
|
||||||
exec=["e"]
|
exec = ["e"]
|
||||||
# Enter filter mode
|
# Enter filter mode
|
||||||
filter_mode=["/", "F1"]
|
filter_mode = ["/", "F1"]
|
||||||
# Quit at anytime
|
# Quit at anytime
|
||||||
quit = ["q"]
|
quit = ["q"]
|
||||||
# Save logs of selected container to file on disk
|
# Save logs of selected container to file on disk
|
||||||
save_logs=["s"]
|
save_logs = ["s"]
|
||||||
# scroll down a list by many
|
# scroll down a list by many
|
||||||
scroll_down_many=["pagedown"]
|
scroll_down_many = ["pagedown"]
|
||||||
# scroll down a list by one item
|
# scroll down a list by one item
|
||||||
scroll_down_one=["down", "j"]
|
scroll_down_one = ["down", "j"]
|
||||||
# scroll down to the end of a list
|
# scroll down to the end of a list
|
||||||
scroll_end=["end"]
|
scroll_end = ["end"]
|
||||||
# scroll up to the start of a list
|
# scroll up to the start of a list
|
||||||
scroll_start=["home"]
|
scroll_start = ["home"]
|
||||||
# scroll up a list by many
|
# scroll up a list by many
|
||||||
scroll_up_many=["pageup"]
|
scroll_up_many = ["pageup"]
|
||||||
# scroll up a list by one item
|
# scroll up a list by one item
|
||||||
scroll_up_one=["up", "k"]
|
scroll_up_one = ["up", "k"]
|
||||||
# Select next panel
|
# Select next panel
|
||||||
select_next_panel=["tab"]
|
select_next_panel = ["tab"]
|
||||||
# Select previous panel
|
# Select previous panel
|
||||||
select_previous_panel=["backtab"]
|
select_previous_panel = ["backtab"]
|
||||||
# Sort the containers based on specific column
|
# Sort the containers based on specific column
|
||||||
sort_by_name=["1"]
|
sort_by_name = ["1"]
|
||||||
sort_by_state=["2"]
|
sort_by_state = ["2"]
|
||||||
sort_by_status=["3"]
|
sort_by_status = ["3"]
|
||||||
sort_by_cpu=["4"]
|
sort_by_cpu = ["4"]
|
||||||
sort_by_memory=["5"]
|
sort_by_memory = ["5"]
|
||||||
sort_by_id=["6"]
|
sort_by_id = ["6"]
|
||||||
sort_by_image=["7"]
|
sort_by_image = ["7"]
|
||||||
sort_by_rx=["8"]
|
sort_by_rx = ["8"]
|
||||||
sort_by_tx=["9"]
|
sort_by_tx = ["9"]
|
||||||
# Reset the sorted containers
|
# Reset the sorted containers
|
||||||
sort_reset=["0"]
|
sort_reset = ["0"]
|
||||||
# Toggle the help panel
|
# Toggle the help panel
|
||||||
toggle_help=["h"]
|
toggle_help = ["h"]
|
||||||
# Toggle mouse capture
|
# Toggle mouse capture
|
||||||
toggle_mouse_capture=["m"]
|
toggle_mouse_capture = ["m"]
|
||||||
|
# Reduce the height of the logs list section
|
||||||
|
log_section_height_decrease = ["-"]
|
||||||
|
# Increase the height of the logs list section
|
||||||
|
log_section_height_increase = ["+"]
|
||||||
|
# Toggle visibility of the log section
|
||||||
|
log_section_toggle = ["\\"]
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Custom Colors #
|
# Custom Colors #
|
||||||
@@ -119,7 +129,7 @@ toggle_mouse_capture=["m"]
|
|||||||
# Background color of the entire line
|
# Background color of the entire line
|
||||||
background = "magenta"
|
background = "magenta"
|
||||||
# Animated loading icon at the start of the bar
|
# Animated loading icon at the start of the bar
|
||||||
loading_spinner="white"
|
loading_spinner = "white"
|
||||||
# Text color
|
# Text color
|
||||||
text = "black"
|
text = "black"
|
||||||
# Text color of a selected header
|
# Text color of a selected header
|
||||||
@@ -128,137 +138,136 @@ text_selected = "gray"
|
|||||||
# The borders around the selectable panels - Containers, Commands, Logs
|
# The borders around the selectable panels - Containers, Commands, Logs
|
||||||
[colors.borders]
|
[colors.borders]
|
||||||
# Border when selected
|
# Border when selected
|
||||||
selected="lightcyan"
|
selected = "lightcyan"
|
||||||
# Border when not selected
|
# Border when not selected
|
||||||
unselected="grey"
|
unselected = "grey"
|
||||||
|
|
||||||
# The containers sections, in the future more color customization options should be made available in this section
|
# The containers sections, in the future more color customization options should be made available in this section
|
||||||
[colors.containers]
|
[colors.containers]
|
||||||
# The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol
|
# The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol
|
||||||
icon="white"
|
icon = "white"
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# At the moment, this will only change the color of the name, id, and image columns
|
# At the moment, this will only change the color of the name, id, and image columns
|
||||||
text="blue"
|
text = "blue"
|
||||||
# Text color of the RX column
|
# Text color of the RX column
|
||||||
text_rx="#FFE9C1"
|
text_rx = "#FFE9C1"
|
||||||
# Text color of the TX column
|
# Text color of the TX column
|
||||||
text_tx="#CD8C8C"
|
text_tx = "#CD8C8C"
|
||||||
|
|
||||||
# The logs panel, will only be applied if color_logs is false
|
# The logs panel, will only be applied if color_logs is false
|
||||||
[colors.logs]
|
[colors.logs]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# text color
|
# text color
|
||||||
text="reset"
|
text = "reset"
|
||||||
|
|
||||||
# Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section
|
# Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section
|
||||||
[colors.container_state]
|
[colors.container_state]
|
||||||
dead="red"
|
dead = "red"
|
||||||
exited="red"
|
exited = "red"
|
||||||
paused = "yellow"
|
paused = "yellow"
|
||||||
removing ="lightred"
|
removing = "lightred"
|
||||||
restarting ="lightgreen"
|
restarting = "lightgreen"
|
||||||
running_healthy ="green"
|
running_healthy = "green"
|
||||||
running_unhealthy="#FFB224"
|
running_unhealthy = "#FFB224"
|
||||||
unknown="red"
|
unknown = "red"
|
||||||
|
|
||||||
# The filter panel
|
# The filter panel
|
||||||
[colors.filter]
|
[colors.filter]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# color of text
|
# color of text
|
||||||
text="gray"
|
text = "gray"
|
||||||
# background color of the selected filter by item (Name/Image/Status/All)
|
# background color of the selected filter by item (Name/Image/Status/All)
|
||||||
selected_filter_background="gray"
|
selected_filter_background = "gray"
|
||||||
# text color of the selected filter by item (Name/Image/Status/All)
|
# text color of the selected filter by item (Name/Image/Status/All)
|
||||||
selected_filter_text="black"
|
selected_filter_text = "black"
|
||||||
# Highlighted text color
|
# Highlighted text color
|
||||||
highlight="magenta"
|
highlight = "magenta"
|
||||||
|
|
||||||
|
|
||||||
# The color the of Docker commands available for each container
|
# The color the of Docker commands available for each container
|
||||||
[colors.commands]
|
[colors.commands]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
pause="yellow"
|
pause = "yellow"
|
||||||
restart="magenta"
|
restart = "magenta"
|
||||||
stop = "red"
|
stop = "red"
|
||||||
delete ="gray"
|
delete = "gray"
|
||||||
resume ="blue"
|
resume = "blue"
|
||||||
start ="green"
|
start = "green"
|
||||||
|
|
||||||
# The cpu chart
|
# The cpu chart
|
||||||
[colors.chart_cpu]
|
[colors.chart_cpu]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# Border color
|
# Border color
|
||||||
border="white"
|
border = "white"
|
||||||
# Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color?
|
# Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color?
|
||||||
title="green"
|
title = "green"
|
||||||
# Maximum CPU percentage - again paused & stopped colors not yet customizable
|
# Maximum CPU percentage - again paused & stopped colors not yet customizable
|
||||||
max="#FFB224"
|
max = "#FFB224"
|
||||||
# Points on the chart - again paused & stopped colors not yet customizable
|
# Points on the chart - again paused & stopped colors not yet customizable
|
||||||
points="magenta"
|
points = "magenta"
|
||||||
# The charts y-axis
|
# The charts y-axis
|
||||||
y_axis="white"
|
y_axis = "white"
|
||||||
|
|
||||||
# The memory chart
|
# The memory chart
|
||||||
[colors.chart_memory]
|
[colors.chart_memory]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# Border color
|
# Border color
|
||||||
border="white"
|
border = "white"
|
||||||
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
||||||
title="green"
|
title = "green"
|
||||||
# Maximum memory use - again paused & stopped will use colors.container_state
|
# Maximum memory use - again paused & stopped will use colors.container_state
|
||||||
max="#FFB224"
|
max = "#FFB224"
|
||||||
# Points on the chart - again paused & stopped will use colors.container_state
|
# Points on the chart - again paused & stopped will use colors.container_state
|
||||||
points="cyan"
|
points = "cyan"
|
||||||
# The charts y-axis
|
# The charts y-axis
|
||||||
y_axis="white"
|
y_axis = "white"
|
||||||
|
|
||||||
# The ports chart
|
# The ports chart
|
||||||
[colors.chart_ports]
|
[colors.chart_ports]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# Border color
|
# Border color
|
||||||
border="white"
|
border = "white"
|
||||||
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
||||||
title="green"
|
title = "green"
|
||||||
# Private/Public/IP headings
|
# Private/Public/IP headings
|
||||||
headings="yellow"
|
headings = "yellow"
|
||||||
# Ports & IP listing text
|
# Ports & IP listing text
|
||||||
text="white"
|
text = "white"
|
||||||
|
|
||||||
# The help popup
|
# The help popup
|
||||||
[colors.popup_help]
|
[colors.popup_help]
|
||||||
# Background color
|
# Background color
|
||||||
background="magenta"
|
background = "magenta"
|
||||||
# Text color
|
# Text color
|
||||||
text="black"
|
text = "black"
|
||||||
# Highlighted text color
|
# Highlighted text color
|
||||||
text_highlight="white"
|
text_highlight = "white"
|
||||||
|
|
||||||
# The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings
|
# The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings
|
||||||
[colors.popup_info]
|
[colors.popup_info]
|
||||||
# Background color
|
# Background color
|
||||||
background="blue"
|
background = "blue"
|
||||||
# Text color
|
# Text color
|
||||||
text="white"
|
text = "white"
|
||||||
|
|
||||||
# The delete popup - used to display a confirmation warning when about to delete a container
|
# The delete popup - used to display a confirmation warning when about to delete a container
|
||||||
[colors.popup_delete]
|
[colors.popup_delete]
|
||||||
# Background color
|
# Background color
|
||||||
background="white"
|
background = "white"
|
||||||
# Text color
|
# Text color
|
||||||
text="black"
|
text = "black"
|
||||||
# Highlighted text color
|
# Highlighted text color
|
||||||
text_highlight="red"
|
text_highlight = "red"
|
||||||
|
|
||||||
# The error popup - hopefully you'll never have to see this
|
# The error popup - hopefully you'll never have to see this
|
||||||
[colors.popup_error]
|
[colors.popup_error]
|
||||||
# Background color
|
# Background color
|
||||||
background="red"
|
background = "red"
|
||||||
# Text color
|
# Text color
|
||||||
text="white"
|
text = "white"
|
||||||
|
|||||||
@@ -30,10 +30,18 @@ impl From<&str> for ContainerId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerId {
|
impl ContainerId {
|
||||||
|
// TODO remove this once zigbuild uses Rust v1.87.0
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[allow(clippy::missing_const_for_fn)]
|
||||||
pub fn get(&self) -> &str {
|
pub fn get(&self) -> &str {
|
||||||
self.0.as_str()
|
self.0.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
pub const fn get(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
/// Only return first 8 chars of id, is usually more than enough for uniqueness
|
/// Only return first 8 chars of id, is usually more than enough for uniqueness
|
||||||
/// need to update tests to use real ids, or atleast strings of the correct-ish length
|
/// need to update tests to use real ids, or atleast strings of the correct-ish length
|
||||||
pub fn get_short(&self) -> String {
|
pub fn get_short(&self) -> String {
|
||||||
@@ -76,10 +84,18 @@ macro_rules! unit_struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[allow(clippy::missing_const_for_fn)]
|
||||||
|
// TODO remove this once zigbuild uses Rust v1.87.0
|
||||||
pub fn get(&self) -> &str {
|
pub fn get(&self) -> &str {
|
||||||
self.0.as_str()
|
self.0.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
pub const fn get(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set(&mut self, value: String) {
|
pub fn set(&mut self, value: String) {
|
||||||
self.0 = value;
|
self.0 = value;
|
||||||
}
|
}
|
||||||
@@ -326,6 +342,54 @@ impl From<(&str, &ContainerStatus)> for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Need status, to check if container is unhealthy or not
|
||||||
|
impl
|
||||||
|
From<(
|
||||||
|
&bollard::secret::ContainerSummaryStateEnum,
|
||||||
|
&ContainerStatus,
|
||||||
|
)> for State
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
(input, status): (
|
||||||
|
&bollard::secret::ContainerSummaryStateEnum,
|
||||||
|
&ContainerStatus,
|
||||||
|
),
|
||||||
|
) -> Self {
|
||||||
|
match input {
|
||||||
|
bollard::secret::ContainerSummaryStateEnum::DEAD => Self::Dead,
|
||||||
|
bollard::secret::ContainerSummaryStateEnum::EXITED => Self::Exited,
|
||||||
|
bollard::secret::ContainerSummaryStateEnum::PAUSED => Self::Paused,
|
||||||
|
bollard::secret::ContainerSummaryStateEnum::REMOVING => Self::Removing,
|
||||||
|
bollard::secret::ContainerSummaryStateEnum::RESTARTING => Self::Restarting,
|
||||||
|
bollard::secret::ContainerSummaryStateEnum::RUNNING => {
|
||||||
|
if status.unhealthy() {
|
||||||
|
Self::Running(RunningState::Unhealthy)
|
||||||
|
} else {
|
||||||
|
Self::Running(RunningState::Healthy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Self::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Again, need status, to check if container is unhealthy or not
|
||||||
|
impl
|
||||||
|
From<(
|
||||||
|
Option<&bollard::secret::ContainerSummaryStateEnum>,
|
||||||
|
&ContainerStatus,
|
||||||
|
)> for State
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
(input, status): (
|
||||||
|
Option<&bollard::secret::ContainerSummaryStateEnum>,
|
||||||
|
&ContainerStatus,
|
||||||
|
),
|
||||||
|
) -> Self {
|
||||||
|
input.map_or(Self::Unknown, |input| Self::from((input, status)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Again, need status, to check if container is unhealthy or not
|
/// Again, need status, to check if container is unhealthy or not
|
||||||
impl From<(Option<String>, &ContainerStatus)> for State {
|
impl From<(Option<String>, &ContainerStatus)> for State {
|
||||||
fn from((input, status): (Option<String>, &ContainerStatus)) -> Self {
|
fn from((input, status): (Option<String>, &ContainerStatus)) -> Self {
|
||||||
@@ -544,7 +608,7 @@ impl LogsTz {
|
|||||||
|
|
||||||
/// Store the logs alongside a HashSet, each log *should* generate a unique timestamp,
|
/// Store the logs alongside a HashSet, each log *should* generate a unique timestamp,
|
||||||
/// so if we store the timestamp separately in a HashSet, we can then check if we should insert a log line into the
|
/// so if we store the timestamp separately in a HashSet, we can then check if we should insert a log line into the
|
||||||
/// stateful list dependent on whethere the timestamp is in the HashSet or not
|
/// stateful list dependent on whether the timestamp is in the HashSet or not
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Logs {
|
pub struct Logs {
|
||||||
logs: StatefulList<ListItem<'static>>,
|
logs: StatefulList<ListItem<'static>>,
|
||||||
@@ -570,10 +634,24 @@ impl Logs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Vec<ListItem<'static>> {
|
/// Get the logs vec, but instead of cloning to whole vec, only clone items with x of the currently selected index
|
||||||
self.logs.items.clone()
|
/// Where x is the abs different of the index plus the panel height & a padding
|
||||||
|
/// The rest can be just empty list items
|
||||||
|
pub fn to_vec(&self, height: usize, padding: usize) -> Vec<ListItem<'static>> {
|
||||||
|
let current_index = self.logs.state.selected().unwrap_or_default();
|
||||||
|
self.logs
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, item)| {
|
||||||
|
if current_index.abs_diff(index) <= height + padding {
|
||||||
|
item.clone()
|
||||||
|
} else {
|
||||||
|
ListItem::from("")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The rest of the methods are basically forwarding from the underlying StatefulList
|
/// The rest of the methods are basically forwarding from the underlying StatefulList
|
||||||
pub fn get_state_title(&self) -> String {
|
pub fn get_state_title(&self) -> String {
|
||||||
self.logs.get_state_title()
|
self.logs.get_state_title()
|
||||||
@@ -594,10 +672,18 @@ impl Logs {
|
|||||||
self.logs.start();
|
self.logs.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO remove this once zigbuild uses Rust v1.87.0
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[allow(clippy::missing_const_for_fn)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.logs.items.len()
|
self.logs.items.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
pub const fn len(&self) -> usize {
|
||||||
|
self.logs.items.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn state(&mut self) -> &mut ListState {
|
pub const fn state(&mut self) -> &mut ListState {
|
||||||
&mut self.logs.state
|
&mut self.logs.state
|
||||||
}
|
}
|
||||||
|
|||||||
+99
-29
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
ENTRY_POINT,
|
ENTRY_POINT,
|
||||||
app_error::AppError,
|
app_error::AppError,
|
||||||
config::Config,
|
config::Config,
|
||||||
ui::{GuiState, Redraw, Status, log_sanitizer},
|
ui::{GuiState, Rerender, Status, log_sanitizer},
|
||||||
};
|
};
|
||||||
pub use container_state::*;
|
pub use container_state::*;
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ pub struct AppData {
|
|||||||
error: Option<AppError>,
|
error: Option<AppError>,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
hidden_containers: Vec<ContainerItem>,
|
hidden_containers: Vec<ContainerItem>,
|
||||||
redraw: Arc<Redraw>,
|
redraw: Arc<Rerender>,
|
||||||
sorted_by: Option<(Header, SortedOrder)>,
|
sorted_by: Option<(Header, SortedOrder)>,
|
||||||
current_sorted_id: Vec<ContainerId>,
|
current_sorted_id: Vec<ContainerId>,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
@@ -137,13 +137,13 @@ pub struct AppData {
|
|||||||
pub filter: Filter,
|
pub filter: Filter,
|
||||||
pub hidden_containers: Vec<ContainerItem>,
|
pub hidden_containers: Vec<ContainerItem>,
|
||||||
pub current_sorted_id: Vec<ContainerId>,
|
pub current_sorted_id: Vec<ContainerId>,
|
||||||
pub redraw: Arc<Redraw>,
|
pub redraw: Arc<Rerender>,
|
||||||
pub sorted_by: Option<(Header, SortedOrder)>,
|
pub sorted_by: Option<(Header, SortedOrder)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppData {
|
impl AppData {
|
||||||
/// Generate a default app_state
|
/// Generate a default app_state
|
||||||
pub fn new(config: Config, redraw: &Arc<Redraw>) -> Self {
|
pub fn new(config: Config, redraw: &Arc<Rerender>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
containers: StatefulList::new(vec![]),
|
containers: StatefulList::new(vec![]),
|
||||||
@@ -192,7 +192,7 @@ impl AppData {
|
|||||||
/// sets the state to start if any filtering has occurred
|
/// sets the state to start if any filtering has occurred
|
||||||
/// Also search in the "hidden" vec for items and insert back into the main containers vec
|
/// Also search in the "hidden" vec for items and insert back into the main containers vec
|
||||||
fn filter_containers(&mut self) {
|
fn filter_containers(&mut self) {
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
let pre_len = self.get_container_len();
|
let pre_len = self.get_container_len();
|
||||||
|
|
||||||
if !self.hidden_containers.is_empty() {
|
if !self.hidden_containers.is_empty() {
|
||||||
@@ -296,7 +296,7 @@ impl AppData {
|
|||||||
/// Remove the sorted header & order, and sort by default - created datetime
|
/// Remove the sorted header & order, and sort by default - created datetime
|
||||||
pub fn reset_sorted(&mut self) {
|
pub fn reset_sorted(&mut self) {
|
||||||
self.set_sorted(None);
|
self.set_sorted(None);
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
@@ -392,7 +392,7 @@ impl AppData {
|
|||||||
|
|
||||||
self.containers.items.sort_by(sort_closure);
|
self.containers.items.sort_by(sort_closure);
|
||||||
if pre_order != self.get_current_ids() {
|
if pre_order != self.get_current_ids() {
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
} else if self.current_sorted_id != self.get_current_ids() {
|
} else if self.current_sorted_id != self.get_current_ids() {
|
||||||
self.containers.items.sort_by(|a, b| {
|
self.containers.items.sort_by(|a, b| {
|
||||||
@@ -400,17 +400,24 @@ impl AppData {
|
|||||||
.cmp(&b.created)
|
.cmp(&b.created)
|
||||||
.then_with(|| a.name.get().cmp(b.name.get()))
|
.then_with(|| a.name.get().cmp(b.name.get()))
|
||||||
});
|
});
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
self.current_sorted_id = self.get_current_ids();
|
self.current_sorted_id = self.get_current_ids();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Container state methods
|
/// Container state methods
|
||||||
/// Get the total number of none "hidden" containers
|
/// Get the total number of none "hidden" containers
|
||||||
|
// TODO remove this once zigbuild uses Rust v1.87.0
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
pub fn get_container_len(&self) -> usize {
|
pub fn get_container_len(&self) -> usize {
|
||||||
self.containers.items.len()
|
self.containers.items.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
pub const fn get_container_len(&self) -> usize {
|
||||||
|
self.containers.items.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_all_id_state(&self) -> Vec<(State, ContainerId)> {
|
pub fn get_all_id_state(&self) -> Vec<(State, ContainerId)> {
|
||||||
self.containers
|
self.containers
|
||||||
.items
|
.items
|
||||||
@@ -439,25 +446,25 @@ impl AppData {
|
|||||||
/// Select the first container
|
/// Select the first container
|
||||||
pub fn containers_start(&mut self) {
|
pub fn containers_start(&mut self) {
|
||||||
self.containers.start();
|
self.containers.start();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// select the last container
|
/// select the last container
|
||||||
pub fn containers_end(&mut self) {
|
pub fn containers_end(&mut self) {
|
||||||
self.containers.end();
|
self.containers.end();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select the next container
|
/// Select the next container
|
||||||
pub fn containers_next(&mut self) {
|
pub fn containers_next(&mut self) {
|
||||||
self.containers.next();
|
self.containers.next();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// select the previous container
|
/// select the previous container
|
||||||
pub fn containers_previous(&mut self) {
|
pub fn containers_previous(&mut self) {
|
||||||
self.containers.previous();
|
self.containers.previous();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get ListState of containers
|
/// Get ListState of containers
|
||||||
@@ -579,7 +586,7 @@ impl AppData {
|
|||||||
pub fn docker_controls_next(&mut self) {
|
pub fn docker_controls_next(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.docker_controls.next();
|
i.docker_controls.next();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +594,7 @@ impl AppData {
|
|||||||
pub fn docker_controls_previous(&mut self) {
|
pub fn docker_controls_previous(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.docker_controls.previous();
|
i.docker_controls.previous();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,7 +602,7 @@ impl AppData {
|
|||||||
pub fn docker_controls_start(&mut self) {
|
pub fn docker_controls_start(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.docker_controls.start();
|
i.docker_controls.start();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,7 +610,7 @@ impl AppData {
|
|||||||
pub fn docker_controls_end(&mut self) {
|
pub fn docker_controls_end(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.docker_controls.end();
|
i.docker_controls.end();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,7 +648,7 @@ impl AppData {
|
|||||||
pub fn log_next(&mut self) {
|
pub fn log_next(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.next();
|
i.logs.next();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,7 +656,7 @@ impl AppData {
|
|||||||
pub fn log_previous(&mut self) {
|
pub fn log_previous(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.previous();
|
i.logs.previous();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,7 +664,7 @@ impl AppData {
|
|||||||
pub fn log_end(&mut self) {
|
pub fn log_end(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.end();
|
i.logs.end();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,17 +672,17 @@ impl AppData {
|
|||||||
pub fn log_start(&mut self) {
|
pub fn log_start(&mut self) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.start();
|
i.logs.start();
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable Vec of current containers logs
|
/// Get mutable Vec of current containers logs
|
||||||
pub fn get_logs(&self) -> Vec<ListItem<'static>> {
|
pub fn get_logs(&self, height: u16, padding: usize) -> Vec<ListItem<'static>> {
|
||||||
self.containers
|
self.containers
|
||||||
.state
|
.state
|
||||||
.selected()
|
.selected()
|
||||||
.and_then(|i| self.containers.items.get(i))
|
.and_then(|i| self.containers.items.get(i))
|
||||||
.map_or(vec![], |i| i.logs.to_vec())
|
.map_or(vec![], |i| i.logs.to_vec(height.into(), padding))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable Option of the currently selected container Logs state
|
/// Get mutable Option of the currently selected container Logs state
|
||||||
@@ -706,14 +713,14 @@ impl AppData {
|
|||||||
/// Remove single app_state error
|
/// Remove single app_state error
|
||||||
pub fn remove_error(&mut self) {
|
pub fn remove_error(&mut self) {
|
||||||
self.error = None;
|
self.error = None;
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert single app_state error
|
/// Insert single app_state error
|
||||||
pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) {
|
pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) {
|
||||||
gui_state.lock().status_push(status);
|
gui_state.lock().status_push(status);
|
||||||
self.error = Some(error);
|
self.error = Some(error);
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the selected container is a dockerised version of oxker
|
/// Check if the selected container is a dockerised version of oxker
|
||||||
@@ -803,7 +810,7 @@ impl AppData {
|
|||||||
container.mem_limit.update(mem_limit);
|
container.mem_limit.update(mem_limit);
|
||||||
}
|
}
|
||||||
if self.is_selected_container(id) {
|
if self.is_selected_container(id) {
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
self.sort_containers();
|
self.sort_containers();
|
||||||
}
|
}
|
||||||
@@ -841,7 +848,7 @@ impl AppData {
|
|||||||
if self.containers.items.get(index).is_some() {
|
if self.containers.items.get(index).is_some() {
|
||||||
self.containers.items.remove(index);
|
self.containers.items.remove(index);
|
||||||
if self.is_selected_container(id) {
|
if self.is_selected_container(id) {
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -875,7 +882,12 @@ impl AppData {
|
|||||||
.map_or(String::new(), std::clone::Clone::clone),
|
.map_or(String::new(), std::clone::Clone::clone),
|
||||||
);
|
);
|
||||||
|
|
||||||
let state = State::from((i.state.as_ref().map_or("dead", |z| z), &status));
|
let state = State::from((
|
||||||
|
i.state
|
||||||
|
.as_ref()
|
||||||
|
.map_or(&bollard::secret::ContainerSummaryStateEnum::DEAD, |z| z),
|
||||||
|
&status,
|
||||||
|
));
|
||||||
let image = i
|
let image = i
|
||||||
.image
|
.image
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -971,7 +983,7 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.is_selected_container(id) {
|
if self.is_selected_container(id) {
|
||||||
self.redraw.set_true();
|
self.redraw.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1946,7 +1958,7 @@ mod tests {
|
|||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
|
||||||
assert_eq!(result.unwrap().offset(), 0);
|
assert_eq!(result.unwrap().offset(), 0);
|
||||||
|
|
||||||
let result = app_data.get_logs();
|
let result = app_data.get_logs(4, 1);
|
||||||
assert_eq!(result.len(), 3);
|
assert_eq!(result.len(), 3);
|
||||||
|
|
||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
@@ -2317,4 +2329,62 @@ mod tests {
|
|||||||
let result = app_data.get_log_state();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *************** //
|
||||||
|
// Get logs method //
|
||||||
|
// *************** //
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// get_logs() returns vec of item, but the items are empty unless they are in the *visible" zone, based on height, index, and padding
|
||||||
|
fn test_app_data_update_get_logs() {
|
||||||
|
let (ids, containers) = gen_containers();
|
||||||
|
|
||||||
|
let mut app_data = gen_appdata(&containers);
|
||||||
|
|
||||||
|
app_data.containers_start();
|
||||||
|
let logs = (0..=999).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
app_data.update_log_by_id(logs, &ids[0]);
|
||||||
|
|
||||||
|
let result = app_data.get_logs(10, 10);
|
||||||
|
for (index, item) in result.iter().enumerate() {
|
||||||
|
if index < 979 {
|
||||||
|
assert_eq!(item, &ListItem::new(""));
|
||||||
|
} else {
|
||||||
|
assert_eq!(item, &ListItem::new(format!("{index}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = app_data.get_logs(100, 20);
|
||||||
|
for (index, item) in result.iter().enumerate() {
|
||||||
|
if index < 879 {
|
||||||
|
assert_eq!(item, &ListItem::new(""));
|
||||||
|
} else {
|
||||||
|
assert_eq!(item, &ListItem::new(format!("{index}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app_data.log_start();
|
||||||
|
let result = app_data.get_logs(10, 10);
|
||||||
|
for (index, item) in result.iter().enumerate() {
|
||||||
|
if index > 20 {
|
||||||
|
assert_eq!(item, &ListItem::new(""));
|
||||||
|
} else {
|
||||||
|
assert_eq!(item, &ListItem::new(format!("{index}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..=500 {
|
||||||
|
app_data.log_next();
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = app_data.get_logs(10, 10);
|
||||||
|
for (index, item) in result.iter().enumerate() {
|
||||||
|
if (481..=521).contains(&index) {
|
||||||
|
assert_eq!(item, &ListItem::new(format!("{index}")));
|
||||||
|
} else {
|
||||||
|
assert_eq!(item, &ListItem::new(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-76
@@ -32,7 +32,7 @@ timezone = "Etc/UTC"
|
|||||||
|
|
||||||
# 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.012345678Z
|
# 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.012345678Z
|
||||||
# *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"
|
||||||
|
|
||||||
# Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows
|
# Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows
|
||||||
# save_dir = "$HOME"
|
# save_dir = "$HOME"
|
||||||
@@ -40,6 +40,9 @@ timestamp_format="%Y-%m-%dT%H:%M:%S.%8f"
|
|||||||
# Force use of docker cli when execing into containers, honestly mostly pointless
|
# Force use of docker cli when execing into containers, honestly mostly pointless
|
||||||
use_cli = false
|
use_cli = false
|
||||||
|
|
||||||
|
# Show the logs section - this can be changed during operation with the log_section_toggle key
|
||||||
|
show_logs = true
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Custom Keymap #
|
# Custom Keymap #
|
||||||
#################
|
#################
|
||||||
@@ -47,6 +50,7 @@ use_cli = false
|
|||||||
# Available keys are;
|
# Available keys are;
|
||||||
# 1) a-z and A-Z
|
# 1) a-z and A-Z
|
||||||
# 2) 0-9
|
# 2) 0-9
|
||||||
|
# WARNING if using the \ key, it needs to be escaped, e.g. log_section_toggle = ["\\"]
|
||||||
# 3) / \ , . # ' [ ] ; = -
|
# 3) / \ , . # ' [ ] ; = -
|
||||||
# 3) F1-F12
|
# 3) F1-F12
|
||||||
# 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down
|
# 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down
|
||||||
@@ -56,52 +60,56 @@ use_cli = false
|
|||||||
|
|
||||||
[keymap]
|
[keymap]
|
||||||
# Clear any popup boxes, filter panel, or help panel
|
# Clear any popup boxes, filter panel, or help panel
|
||||||
clear=["c", "esc"]
|
clear = ["c", "esc"]
|
||||||
# Cancel delete - clear also works here
|
# Cancel delete - clear also works here
|
||||||
delete_deny=["n"]
|
delete_deny = ["n"]
|
||||||
# Confirm Delete
|
# Confirm Delete
|
||||||
delete_confirm=["y"]
|
delete_confirm = ["y"]
|
||||||
# Exec into the selected container
|
# Exec into the selected container
|
||||||
exec=["e"]
|
exec = ["e"]
|
||||||
# Enter filter mode
|
# Enter filter mode
|
||||||
filter_mode=["/", "F1"]
|
filter_mode = ["/", "F1"]
|
||||||
# Quit at anytime
|
# Quit at anytime
|
||||||
quit = ["q"]
|
quit = ["q"]
|
||||||
# Save logs of selected container to file on disk
|
# Save logs of selected container to file on disk
|
||||||
save_logs=["s"]
|
save_logs = ["s"]
|
||||||
# scroll down a list by many
|
# scroll down a list by many
|
||||||
scroll_down_many=["pagedown"]
|
scroll_down_many = ["pagedown"]
|
||||||
# scroll down a list by one item
|
# scroll down a list by one item
|
||||||
scroll_down_one=["down", "j"]
|
scroll_down_one = ["down", "j"]
|
||||||
# scroll down to the end of a list
|
# scroll down to the end of a list
|
||||||
scroll_end=["end"]
|
scroll_end = ["end"]
|
||||||
# scroll up to the start of a list
|
# scroll up to the start of a list
|
||||||
scroll_start=["home"]
|
scroll_start = ["home"]
|
||||||
# scroll up a list by many
|
# scroll up a list by many
|
||||||
scroll_up_many=["pageup"]
|
scroll_up_many = ["pageup"]
|
||||||
# scroll up a list by one item
|
# scroll up a list by one item
|
||||||
scroll_up_one=["up", "k"]
|
scroll_up_one = ["up", "k"]
|
||||||
# Select next panel
|
# Select next panel
|
||||||
select_next_panel=["tab"]
|
select_next_panel = ["tab"]
|
||||||
# Select previous panel
|
# Select previous panel
|
||||||
select_previous_panel=["backtab"]
|
select_previous_panel = ["backtab"]
|
||||||
# Sort the containers based on specific column
|
# Sort the containers based on specific column
|
||||||
sort_by_name=["1"]
|
sort_by_name = ["1"]
|
||||||
sort_by_state=["2"]
|
sort_by_state = ["2"]
|
||||||
sort_by_status=["3"]
|
sort_by_status = ["3"]
|
||||||
sort_by_cpu=["4"]
|
sort_by_cpu = ["4"]
|
||||||
sort_by_memory=["5"]
|
sort_by_memory = ["5"]
|
||||||
sort_by_id=["6"]
|
sort_by_id = ["6"]
|
||||||
sort_by_image=["7"]
|
sort_by_image = ["7"]
|
||||||
sort_by_rx=["8"]
|
sort_by_rx = ["8"]
|
||||||
sort_by_tx=["9"]
|
sort_by_tx = ["9"]
|
||||||
# Reset the sorted containers
|
# Reset the sorted containers
|
||||||
sort_reset=["0"]
|
sort_reset = ["0"]
|
||||||
# Toggle the help panel
|
# Toggle the help panel
|
||||||
toggle_help=["h"]
|
toggle_help = ["h"]
|
||||||
# Toggle mouse capture
|
# Toggle mouse capture
|
||||||
toggle_mouse_capture=["m"]
|
toggle_mouse_capture = ["m"]
|
||||||
|
# Reduce the height of the logs list section
|
||||||
|
log_section_height_decrease = ["-"]
|
||||||
|
log_section_height_increase = ["+"]
|
||||||
|
# Toggle visibility of the log section
|
||||||
|
log_section_toggle = ["\\"]
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Custom Colors #
|
# Custom Colors #
|
||||||
@@ -119,7 +127,7 @@ toggle_mouse_capture=["m"]
|
|||||||
# Background color of the entire line
|
# Background color of the entire line
|
||||||
background = "magenta"
|
background = "magenta"
|
||||||
# Animated loading icon at the start of the bar
|
# Animated loading icon at the start of the bar
|
||||||
loading_spinner="white"
|
loading_spinner = "white"
|
||||||
# Text color
|
# Text color
|
||||||
text = "black"
|
text = "black"
|
||||||
# Text color of a selected header
|
# Text color of a selected header
|
||||||
@@ -128,137 +136,137 @@ text_selected = "gray"
|
|||||||
# The borders around the selectable panels - Containers, Commands, Logs
|
# The borders around the selectable panels - Containers, Commands, Logs
|
||||||
[colors.borders]
|
[colors.borders]
|
||||||
# Border when selected
|
# Border when selected
|
||||||
selected="lightcyan"
|
selected = "lightcyan"
|
||||||
# Border when not selected
|
# Border when not selected
|
||||||
unselected="grey"
|
unselected = "grey"
|
||||||
|
|
||||||
# The containers sections, in the future more color customization options should be made available in this section
|
# The containers sections, in the future more color customization options should be made available in this section
|
||||||
[colors.containers]
|
[colors.containers]
|
||||||
# The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol
|
# The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol
|
||||||
icon="white"
|
icon = "white"
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# At the moment, this will only change the color of the name, id, and image columns
|
# At the moment, this will only change the color of the name, id, and image columns
|
||||||
text="blue"
|
text = "blue"
|
||||||
# Text color of the RX column
|
# Text color of the RX column
|
||||||
text_rx="#FFE9C1"
|
text_rx = "#FFE9C1"
|
||||||
# Text color of the TX column
|
# Text color of the TX column
|
||||||
text_tx="#CD8C8C"
|
text_tx = "#CD8C8C"
|
||||||
|
|
||||||
# The logs panel, will only be applied if color_logs is false
|
# The logs panel, will only be applied if color_logs is false
|
||||||
[colors.logs]
|
[colors.logs]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# text color
|
# text color
|
||||||
text="reset"
|
text = "reset"
|
||||||
|
|
||||||
# Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section
|
# Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section
|
||||||
[colors.container_state]
|
[colors.container_state]
|
||||||
dead="red"
|
dead = "red"
|
||||||
exited="red"
|
exited = "red"
|
||||||
paused = "yellow"
|
paused = "yellow"
|
||||||
removing ="lightred"
|
removing = "lightred"
|
||||||
restarting ="lightgreen"
|
restarting = "lightgreen"
|
||||||
running_healthy ="green"
|
running_healthy = "green"
|
||||||
running_unhealthy="#FFB224"
|
running_unhealthy = "#FFB224"
|
||||||
unknown="red"
|
unknown = "red"
|
||||||
|
|
||||||
# The filter panel
|
# The filter panel
|
||||||
[colors.filter]
|
[colors.filter]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# color of text
|
# color of text
|
||||||
text="gray"
|
text = "gray"
|
||||||
# background color of the selected filter by item (Name/Image/Status/All)
|
# background color of the selected filter by item (Name/Image/Status/All)
|
||||||
selected_filter_background="gray"
|
selected_filter_background = "gray"
|
||||||
# text color of the selected filter by item (Name/Image/Status/All)
|
# text color of the selected filter by item (Name/Image/Status/All)
|
||||||
selected_filter_text="black"
|
selected_filter_text = "black"
|
||||||
# Highlighted text color
|
# Highlighted text color
|
||||||
highlight="magenta"
|
highlight = "magenta"
|
||||||
|
|
||||||
|
|
||||||
# The color the of Docker commands available for each container
|
# The color the of Docker commands available for each container
|
||||||
[colors.commands]
|
[colors.commands]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
pause="yellow"
|
pause = "yellow"
|
||||||
restart="magenta"
|
restart = "magenta"
|
||||||
stop = "red"
|
stop = "red"
|
||||||
delete ="gray"
|
delete = "gray"
|
||||||
resume ="blue"
|
resume = "blue"
|
||||||
start ="green"
|
start = "green"
|
||||||
|
|
||||||
# The cpu chart
|
# The cpu chart
|
||||||
[colors.chart_cpu]
|
[colors.chart_cpu]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# Border color
|
# Border color
|
||||||
border="white"
|
border = "white"
|
||||||
# Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color?
|
# Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color?
|
||||||
title="green"
|
title = "green"
|
||||||
# Maximum CPU percentage - again paused & stopped colors not yet customizable
|
# Maximum CPU percentage - again paused & stopped colors not yet customizable
|
||||||
max="#FFB224"
|
max = "#FFB224"
|
||||||
# Points on the chart - again paused & stopped colors not yet customizable
|
# Points on the chart - again paused & stopped colors not yet customizable
|
||||||
points="magenta"
|
points = "magenta"
|
||||||
# The charts y-axis
|
# The charts y-axis
|
||||||
y_axis="white"
|
y_axis = "white"
|
||||||
|
|
||||||
# The memory chart
|
# The memory chart
|
||||||
[colors.chart_memory]
|
[colors.chart_memory]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# Border color
|
# Border color
|
||||||
border="white"
|
border = "white"
|
||||||
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
||||||
title="green"
|
title = "green"
|
||||||
# Maximum memory use - again paused & stopped will use colors.container_state
|
# Maximum memory use - again paused & stopped will use colors.container_state
|
||||||
max="#FFB224"
|
max = "#FFB224"
|
||||||
# Points on the chart - again paused & stopped will use colors.container_state
|
# Points on the chart - again paused & stopped will use colors.container_state
|
||||||
points="cyan"
|
points = "cyan"
|
||||||
# The charts y-axis
|
# The charts y-axis
|
||||||
y_axis="white"
|
y_axis = "white"
|
||||||
|
|
||||||
# The ports chart
|
# The ports chart
|
||||||
[colors.chart_ports]
|
[colors.chart_ports]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
background = "reset"
|
background = "reset"
|
||||||
# Border color
|
# Border color
|
||||||
border="white"
|
border = "white"
|
||||||
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
# Chart title - only whilst container is running, paused & stopped will use colors.container_state
|
||||||
title="green"
|
title = "green"
|
||||||
# Private/Public/IP headings
|
# Private/Public/IP headings
|
||||||
headings="yellow"
|
headings = "yellow"
|
||||||
# Ports & IP listing text
|
# Ports & IP listing text
|
||||||
text="white"
|
text = "white"
|
||||||
|
|
||||||
# The help popup
|
# The help popup
|
||||||
[colors.popup_help]
|
[colors.popup_help]
|
||||||
# Background color
|
# Background color
|
||||||
background="magenta"
|
background = "magenta"
|
||||||
# Text color
|
# Text color
|
||||||
text="black"
|
text = "black"
|
||||||
# Highlighted text color
|
# Highlighted text color
|
||||||
text_highlight="white"
|
text_highlight = "white"
|
||||||
|
|
||||||
# The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings
|
# The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings
|
||||||
[colors.popup_info]
|
[colors.popup_info]
|
||||||
# Background color
|
# Background color
|
||||||
background="blue"
|
background = "blue"
|
||||||
# Text color
|
# Text color
|
||||||
text="white"
|
text = "white"
|
||||||
|
|
||||||
# The delete popup - used to display a confirmation warning when about to delete a container
|
# The delete popup - used to display a confirmation warning when about to delete a container
|
||||||
[colors.popup_delete]
|
[colors.popup_delete]
|
||||||
# Background color
|
# Background color
|
||||||
background="white"
|
background = "white"
|
||||||
# Text color
|
# Text color
|
||||||
text="black"
|
text = "black"
|
||||||
# Highlighted text color
|
# Highlighted text color
|
||||||
text_highlight="red"
|
text_highlight = "red"
|
||||||
|
|
||||||
# The error popup - hopefully you'll never have to see this
|
# The error popup - hopefully you'll never have to see this
|
||||||
[colors.popup_error]
|
[colors.popup_error]
|
||||||
# Background color
|
# Background color
|
||||||
background="red"
|
background = "red"
|
||||||
# Text color
|
# Text color
|
||||||
text="white"
|
text = "white"
|
||||||
|
|||||||
+44
-10
@@ -39,6 +39,9 @@ optional_config_struct!(
|
|||||||
delete_confirm,
|
delete_confirm,
|
||||||
exec,
|
exec,
|
||||||
filter_mode,
|
filter_mode,
|
||||||
|
log_section_height_increase,
|
||||||
|
log_section_height_decrease,
|
||||||
|
log_section_toggle,
|
||||||
quit,
|
quit,
|
||||||
save_logs,
|
save_logs,
|
||||||
scroll_down_many,
|
scroll_down_many,
|
||||||
@@ -70,6 +73,9 @@ config_struct!(
|
|||||||
delete_confirm,
|
delete_confirm,
|
||||||
exec,
|
exec,
|
||||||
filter_mode,
|
filter_mode,
|
||||||
|
log_section_height_increase,
|
||||||
|
log_section_height_decrease,
|
||||||
|
log_section_toggle,
|
||||||
quit,
|
quit,
|
||||||
save_logs,
|
save_logs,
|
||||||
scroll_down_many,
|
scroll_down_many,
|
||||||
@@ -98,10 +104,13 @@ impl Keymap {
|
|||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
clear: (KeyCode::Char('c'), Some(KeyCode::Esc)),
|
clear: (KeyCode::Char('c'), Some(KeyCode::Esc)),
|
||||||
delete_deny: (KeyCode::Char('n'), None),
|
|
||||||
delete_confirm: (KeyCode::Char('y'), None),
|
delete_confirm: (KeyCode::Char('y'), None),
|
||||||
|
delete_deny: (KeyCode::Char('n'), None),
|
||||||
exec: (KeyCode::Char('e'), None),
|
exec: (KeyCode::Char('e'), None),
|
||||||
filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))),
|
filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))),
|
||||||
|
log_section_height_decrease: (KeyCode::Char('-'), None),
|
||||||
|
log_section_height_increase: (KeyCode::Char('='), None),
|
||||||
|
log_section_toggle: (KeyCode::Char('\\'), None),
|
||||||
quit: (KeyCode::Char('q'), None),
|
quit: (KeyCode::Char('q'), None),
|
||||||
save_logs: (KeyCode::Char('s'), None),
|
save_logs: (KeyCode::Char('s'), None),
|
||||||
scroll_down_many: (KeyCode::PageDown, None),
|
scroll_down_many: (KeyCode::PageDown, None),
|
||||||
@@ -112,14 +121,14 @@ impl Keymap {
|
|||||||
scroll_up_one: (KeyCode::Up, Some(KeyCode::Char('k'))),
|
scroll_up_one: (KeyCode::Up, Some(KeyCode::Char('k'))),
|
||||||
select_next_panel: (KeyCode::Tab, None),
|
select_next_panel: (KeyCode::Tab, None),
|
||||||
select_previous_panel: (KeyCode::BackTab, None),
|
select_previous_panel: (KeyCode::BackTab, None),
|
||||||
sort_by_name: (KeyCode::Char('1'), None),
|
|
||||||
sort_by_state: (KeyCode::Char('2'), None),
|
|
||||||
sort_by_status: (KeyCode::Char('3'), None),
|
|
||||||
sort_by_cpu: (KeyCode::Char('4'), None),
|
sort_by_cpu: (KeyCode::Char('4'), None),
|
||||||
sort_by_memory: (KeyCode::Char('5'), None),
|
|
||||||
sort_by_id: (KeyCode::Char('6'), None),
|
sort_by_id: (KeyCode::Char('6'), None),
|
||||||
sort_by_image: (KeyCode::Char('7'), None),
|
sort_by_image: (KeyCode::Char('7'), None),
|
||||||
|
sort_by_memory: (KeyCode::Char('5'), None),
|
||||||
|
sort_by_name: (KeyCode::Char('1'), None),
|
||||||
sort_by_rx: (KeyCode::Char('8'), None),
|
sort_by_rx: (KeyCode::Char('8'), None),
|
||||||
|
sort_by_state: (KeyCode::Char('2'), None),
|
||||||
|
sort_by_status: (KeyCode::Char('3'), None),
|
||||||
sort_by_tx: (KeyCode::Char('9'), None),
|
sort_by_tx: (KeyCode::Char('9'), None),
|
||||||
sort_reset: (KeyCode::Char('0'), None),
|
sort_reset: (KeyCode::Char('0'), None),
|
||||||
toggle_help: (KeyCode::Char('h'), None),
|
toggle_help: (KeyCode::Char('h'), None),
|
||||||
@@ -162,6 +171,22 @@ impl From<Option<ConfigKeymap>> for Keymap {
|
|||||||
update_keymap(ck.clear, &mut keymap.clear, &mut clash);
|
update_keymap(ck.clear, &mut keymap.clear, &mut clash);
|
||||||
update_keymap(ck.delete_deny, &mut keymap.delete_deny, &mut clash);
|
update_keymap(ck.delete_deny, &mut keymap.delete_deny, &mut clash);
|
||||||
update_keymap(ck.delete_confirm, &mut keymap.delete_confirm, &mut clash);
|
update_keymap(ck.delete_confirm, &mut keymap.delete_confirm, &mut clash);
|
||||||
|
update_keymap(
|
||||||
|
ck.log_section_height_decrease,
|
||||||
|
&mut keymap.log_section_height_decrease,
|
||||||
|
&mut clash,
|
||||||
|
);
|
||||||
|
update_keymap(
|
||||||
|
ck.log_section_height_increase,
|
||||||
|
&mut keymap.log_section_height_increase,
|
||||||
|
&mut clash,
|
||||||
|
);
|
||||||
|
update_keymap(
|
||||||
|
ck.log_section_toggle,
|
||||||
|
&mut keymap.log_section_toggle,
|
||||||
|
&mut clash,
|
||||||
|
);
|
||||||
|
|
||||||
update_keymap(ck.exec, &mut keymap.exec, &mut clash);
|
update_keymap(ck.exec, &mut keymap.exec, &mut clash);
|
||||||
update_keymap(ck.filter_mode, &mut keymap.filter_mode, &mut clash);
|
update_keymap(ck.filter_mode, &mut keymap.filter_mode, &mut clash);
|
||||||
update_keymap(ck.quit, &mut keymap.quit, &mut clash);
|
update_keymap(ck.quit, &mut keymap.quit, &mut clash);
|
||||||
@@ -339,6 +364,8 @@ mod tests {
|
|||||||
delete_deny: Some(vec!["s".to_owned()]),
|
delete_deny: Some(vec!["s".to_owned()]),
|
||||||
delete_confirm: None,
|
delete_confirm: None,
|
||||||
exec: None,
|
exec: None,
|
||||||
|
log_section_height_decrease: None,
|
||||||
|
log_section_height_increase: None,
|
||||||
filter_mode: None,
|
filter_mode: None,
|
||||||
quit: None,
|
quit: None,
|
||||||
save_logs: None,
|
save_logs: None,
|
||||||
@@ -349,6 +376,7 @@ mod tests {
|
|||||||
scroll_up_many: None,
|
scroll_up_many: None,
|
||||||
scroll_up_one: None,
|
scroll_up_one: None,
|
||||||
select_next_panel: None,
|
select_next_panel: None,
|
||||||
|
log_section_toggle: None,
|
||||||
select_previous_panel: None,
|
select_previous_panel: None,
|
||||||
sort_by_name: None,
|
sort_by_name: None,
|
||||||
sort_by_state: None,
|
sort_by_state: None,
|
||||||
@@ -376,10 +404,13 @@ mod tests {
|
|||||||
|
|
||||||
let input = ConfigKeymap {
|
let input = ConfigKeymap {
|
||||||
clear: gen_v(("a", "b")),
|
clear: gen_v(("a", "b")),
|
||||||
delete_deny: gen_v(("c", "d")),
|
|
||||||
delete_confirm: gen_v(("e", "f")),
|
delete_confirm: gen_v(("e", "f")),
|
||||||
|
delete_deny: gen_v(("c", "d")),
|
||||||
exec: gen_v(("g", "h")),
|
exec: gen_v(("g", "h")),
|
||||||
filter_mode: gen_v(("i", "j")),
|
filter_mode: gen_v(("i", "j")),
|
||||||
|
log_section_height_decrease: gen_v(("-", "Z")),
|
||||||
|
log_section_height_increase: gen_v(("=", "X")),
|
||||||
|
log_section_toggle: gen_v(("Y", "W")),
|
||||||
quit: gen_v(("k", "l")),
|
quit: gen_v(("k", "l")),
|
||||||
save_logs: gen_v(("m", "n")),
|
save_logs: gen_v(("m", "n")),
|
||||||
scroll_down_many: gen_v(("o", "p")),
|
scroll_down_many: gen_v(("o", "p")),
|
||||||
@@ -390,14 +421,14 @@ mod tests {
|
|||||||
scroll_up_one: gen_v(("y", "z")),
|
scroll_up_one: gen_v(("y", "z")),
|
||||||
select_next_panel: gen_v(("0", "1")),
|
select_next_panel: gen_v(("0", "1")),
|
||||||
select_previous_panel: gen_v(("2", "3")),
|
select_previous_panel: gen_v(("2", "3")),
|
||||||
sort_by_name: gen_v(("4", "5")),
|
|
||||||
sort_by_state: gen_v(("6", "7")),
|
|
||||||
sort_by_status: gen_v(("8", "9")),
|
|
||||||
sort_by_cpu: gen_v(("F1", "F12")),
|
sort_by_cpu: gen_v(("F1", "F12")),
|
||||||
sort_by_memory: gen_v(("/", "\\")),
|
|
||||||
sort_by_id: gen_v(("[", "]")),
|
sort_by_id: gen_v(("[", "]")),
|
||||||
sort_by_image: gen_v(("A", "B")),
|
sort_by_image: gen_v(("A", "B")),
|
||||||
|
sort_by_memory: gen_v(("/", "\\")),
|
||||||
|
sort_by_name: gen_v(("4", "5")),
|
||||||
sort_by_rx: gen_v(("C", "D")),
|
sort_by_rx: gen_v(("C", "D")),
|
||||||
|
sort_by_state: gen_v(("6", "7")),
|
||||||
|
sort_by_status: gen_v(("8", "9")),
|
||||||
sort_by_tx: gen_v(("insert", "TAB")),
|
sort_by_tx: gen_v(("insert", "TAB")),
|
||||||
sort_reset: gen_v(("up", "down")),
|
sort_reset: gen_v(("up", "down")),
|
||||||
toggle_help: gen_v(("home", "end")),
|
toggle_help: gen_v(("home", "end")),
|
||||||
@@ -410,6 +441,9 @@ mod tests {
|
|||||||
clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
|
clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
|
||||||
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
|
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
|
||||||
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
|
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
|
||||||
|
log_section_height_decrease: (KeyCode::Char('-'), Some(KeyCode::Char('Z'))),
|
||||||
|
log_section_height_increase: (KeyCode::Char('='), Some(KeyCode::Char('X'))),
|
||||||
|
log_section_toggle: (KeyCode::Char('Y'), Some(KeyCode::Char('W'))),
|
||||||
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'))),
|
||||||
quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub struct Config {
|
|||||||
pub show_timestamp: bool,
|
pub show_timestamp: bool,
|
||||||
pub timezone: Option<TimeZone>,
|
pub timezone: Option<TimeZone>,
|
||||||
pub timestamp_format: String,
|
pub timestamp_format: String,
|
||||||
|
pub show_logs: bool,
|
||||||
pub use_cli: bool,
|
pub use_cli: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ impl From<&Args> for Config {
|
|||||||
timezone: Self::parse_timezone(args.timezone.clone()),
|
timezone: Self::parse_timezone(args.timezone.clone()),
|
||||||
timestamp_format: Self::parse_timestamp_format(None),
|
timestamp_format: Self::parse_timestamp_format(None),
|
||||||
use_cli: args.use_cli,
|
use_cli: args.use_cli,
|
||||||
|
show_logs: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,6 +75,7 @@ impl From<ConfigFile> for Config {
|
|||||||
timezone: Self::parse_timezone(config_file.timezone),
|
timezone: Self::parse_timezone(config_file.timezone),
|
||||||
timestamp_format: Self::parse_timestamp_format(config_file.timestamp_format),
|
timestamp_format: Self::parse_timestamp_format(config_file.timestamp_format),
|
||||||
use_cli: config_file.use_cli.unwrap_or(false),
|
use_cli: config_file.use_cli.unwrap_or(false),
|
||||||
|
show_logs: config_file.show_logs.unwrap_or(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +135,12 @@ impl Config {
|
|||||||
|
|
||||||
if config_from_cli.color_logs != default_args.color {
|
if config_from_cli.color_logs != default_args.color {
|
||||||
self.color_logs = config_from_cli.color_logs;
|
self.color_logs = config_from_cli.color_logs;
|
||||||
|
self.raw_logs = !self.color_logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if config_from_cli.raw_logs != default_args.raw {
|
||||||
|
self.raw_logs = config_from_cli.raw_logs;
|
||||||
|
self.color_logs = !self.raw_logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if config_from_cli.gui != default_args.gui {
|
if config_from_cli.gui != default_args.gui {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ pub struct ConfigFile {
|
|||||||
pub timestamp_format: Option<String>,
|
pub timestamp_format: Option<String>,
|
||||||
pub timezone: Option<String>,
|
pub timezone: Option<String>,
|
||||||
pub use_cli: Option<bool>,
|
pub use_cli: Option<bool>,
|
||||||
|
pub show_logs: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigFile {
|
impl ConfigFile {
|
||||||
|
|||||||
+191
-244
@@ -1,9 +1,7 @@
|
|||||||
use bollard::{
|
use bollard::{
|
||||||
Docker,
|
Docker,
|
||||||
container::{
|
query_parameters::{ListContainersOptions, LogsOptions, RemoveContainerOptions, StatsOptions},
|
||||||
ListContainersOptions, LogsOptions, MemoryStatsStats, RemoveContainerOptions,
|
secret::ContainerStatsResponse,
|
||||||
StartContainerOptions, Stats, StatsOptions,
|
|
||||||
},
|
|
||||||
service::ContainerSummary,
|
service::ContainerSummary,
|
||||||
};
|
};
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
@@ -75,31 +73,44 @@ pub struct DockerData {
|
|||||||
impl DockerData {
|
impl DockerData {
|
||||||
/// Use docker stats to calculate current cpu usage
|
/// Use docker stats to calculate current cpu usage
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn calculate_usage(stats: &Stats) -> f64 {
|
fn calculate_usage(stats: &ContainerStatsResponse) -> f64 {
|
||||||
let mut cpu_percentage = 0.0;
|
let mut cpu_percentage = 0.0;
|
||||||
let cpu_delta = stats
|
|
||||||
.cpu_stats
|
|
||||||
.cpu_usage
|
|
||||||
.total_usage
|
|
||||||
.saturating_sub(stats.precpu_stats.cpu_usage.total_usage)
|
|
||||||
as f64;
|
|
||||||
|
|
||||||
if let (Some(cpu_stats_usage), Some(precpu_stats_usage)) = (
|
let total_usage = stats.precpu_stats.as_ref().map_or(0, |i| {
|
||||||
stats.cpu_stats.system_cpu_usage,
|
i.cpu_usage
|
||||||
stats.precpu_stats.system_cpu_usage,
|
.as_ref()
|
||||||
|
.map_or(0, |i| i.total_usage.unwrap_or_default())
|
||||||
|
});
|
||||||
|
|
||||||
|
let cpu_delta = stats.cpu_stats.as_ref().map_or(0, |i| {
|
||||||
|
i.cpu_usage.as_ref().map_or(0, |i| {
|
||||||
|
i.total_usage
|
||||||
|
.unwrap_or_default()
|
||||||
|
.saturating_sub(total_usage)
|
||||||
|
})
|
||||||
|
}) as f64;
|
||||||
|
|
||||||
|
if let (Some(Some(cpu_stats_usage)), Some(Some(precpu_stats_usage))) = (
|
||||||
|
stats.cpu_stats.as_ref().map(|i| i.system_cpu_usage),
|
||||||
|
stats.precpu_stats.as_ref().map(|i| i.system_cpu_usage),
|
||||||
) {
|
) {
|
||||||
let system_delta = cpu_stats_usage.saturating_sub(precpu_stats_usage) as f64;
|
let system_delta = cpu_stats_usage.saturating_sub(precpu_stats_usage) as f64;
|
||||||
let online_cpus = stats.cpu_stats.online_cpus.unwrap_or_else(|| {
|
let online_cpus = f64::from(stats.cpu_stats.as_ref().map_or(0, |i| {
|
||||||
u64::try_from(
|
i.online_cpus.unwrap_or_else(|| {
|
||||||
stats
|
u32::try_from(
|
||||||
.cpu_stats
|
stats
|
||||||
.cpu_usage
|
.cpu_stats
|
||||||
.percpu_usage
|
.clone()
|
||||||
.as_ref()
|
.unwrap_or_default()
|
||||||
.map_or(0, std::vec::Vec::len),
|
.cpu_usage
|
||||||
)
|
.unwrap_or_default()
|
||||||
.unwrap_or_default()
|
.percpu_usage
|
||||||
}) as f64;
|
.as_ref()
|
||||||
|
.map_or(0, std::vec::Vec::len),
|
||||||
|
)
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
}));
|
||||||
if system_delta > 0.0 && cpu_delta > 0.0 {
|
if system_delta > 0.0 && cpu_delta > 0.0 {
|
||||||
cpu_percentage = (cpu_delta / system_delta) * online_cpus * 100.0;
|
cpu_percentage = (cpu_delta / system_delta) * online_cpus * 100.0;
|
||||||
}
|
}
|
||||||
@@ -107,6 +118,9 @@ impl DockerData {
|
|||||||
cpu_percentage
|
cpu_percentage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a single docker stat in order to update mem and cpu usage
|
||||||
|
/// don't take &self, so that can tokio::spawn into it's own thread
|
||||||
|
/// remove if from spawns hashmap when complete
|
||||||
/// Get a single docker stat in order to update mem and cpu usage
|
/// Get a single docker stat in order to update mem and cpu usage
|
||||||
/// don't take &self, so that can tokio::spawn into it's own thread
|
/// don't take &self, so that can tokio::spawn into it's own thread
|
||||||
/// remove if from spawns hashmap when complete
|
/// remove if from spawns hashmap when complete
|
||||||
@@ -128,20 +142,23 @@ impl DockerData {
|
|||||||
)
|
)
|
||||||
.take(1);
|
.take(1);
|
||||||
|
|
||||||
|
// some err here
|
||||||
while let Some(Ok(stats)) = stream.next().await {
|
while let Some(Ok(stats)) = stream.next().await {
|
||||||
// Memory stats are only collected if the container is alive - is this the behaviour we want?
|
// Memory stats are only collected if the container is alive - is this the behaviour we want?
|
||||||
|
|
||||||
let (mem_stat, cpu_stats) = if state.is_alive() {
|
let (mem_stat, cpu_stats) = if state.is_alive() {
|
||||||
let mem_cache = stats.memory_stats.stats.map_or(0, |i| match i {
|
let mem_cache = stats.memory_stats.as_ref().map_or(&0, |i| {
|
||||||
MemoryStatsStats::V1(x) => x.inactive_file,
|
i.stats
|
||||||
MemoryStatsStats::V2(x) => x.inactive_file,
|
.as_ref()
|
||||||
|
.map_or(&0, |i| i.get("inactive_file").unwrap_or(&0))
|
||||||
});
|
});
|
||||||
(
|
(
|
||||||
Some(
|
Some(
|
||||||
stats
|
stats
|
||||||
.memory_stats
|
.memory_stats
|
||||||
.usage
|
.as_ref()
|
||||||
.unwrap_or_default()
|
.map_or(0, |i| i.usage.unwrap_or_default())
|
||||||
.saturating_sub(mem_cache),
|
.saturating_sub(*mem_cache),
|
||||||
),
|
),
|
||||||
Some(Self::calculate_usage(&stats)),
|
Some(Self::calculate_usage(&stats)),
|
||||||
)
|
)
|
||||||
@@ -149,26 +166,22 @@ impl DockerData {
|
|||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
let op_key = stats
|
let (rx, tx) = stats.networks.as_ref().map_or((0, 0), |i| {
|
||||||
.networks
|
(
|
||||||
.as_ref()
|
i.rx_bytes.unwrap_or_default(),
|
||||||
.and_then(|networks| networks.keys().next().cloned());
|
i.tx_bytes.unwrap_or_default(),
|
||||||
|
)
|
||||||
let (rx, tx) = if let Some(key) = op_key {
|
});
|
||||||
stats
|
|
||||||
.networks
|
|
||||||
.unwrap_or_default()
|
|
||||||
.get(&key)
|
|
||||||
.map_or((0, 0), |f| (f.rx_bytes, f.tx_bytes))
|
|
||||||
} else {
|
|
||||||
(0, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
app_data.lock().update_stats_by_id(
|
app_data.lock().update_stats_by_id(
|
||||||
id,
|
id,
|
||||||
cpu_stats,
|
cpu_stats,
|
||||||
mem_stat,
|
mem_stat,
|
||||||
stats.memory_stats.limit.unwrap_or_default(),
|
stats
|
||||||
|
.memory_stats
|
||||||
|
.unwrap_or_default()
|
||||||
|
.limit
|
||||||
|
.unwrap_or_default(),
|
||||||
rx,
|
rx,
|
||||||
tx,
|
tx,
|
||||||
);
|
);
|
||||||
@@ -203,7 +216,7 @@ impl DockerData {
|
|||||||
async fn update_all_containers(&self) {
|
async fn update_all_containers(&self) {
|
||||||
let containers = self
|
let containers = self
|
||||||
.docker
|
.docker
|
||||||
.list_containers(Some(ListContainersOptions::<String> {
|
.list_containers(Some(ListContainersOptions {
|
||||||
all: true,
|
all: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
@@ -242,11 +255,11 @@ impl DockerData {
|
|||||||
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
|
||||||
stderr: bool,
|
stderr: bool,
|
||||||
) {
|
) {
|
||||||
let options = Some(LogsOptions::<String> {
|
let options = Some(LogsOptions {
|
||||||
stdout: true,
|
stdout: true,
|
||||||
stderr,
|
stderr,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
since: i64::try_from(since).unwrap_or_default(),
|
since: i32::try_from(since).unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -363,14 +376,31 @@ impl DockerData {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
DockerCommand::Pause => docker.pause_container(id.get()).await,
|
DockerCommand::Pause => docker.pause_container(id.get()).await,
|
||||||
DockerCommand::Restart => docker.restart_container(id.get(), None).await,
|
DockerCommand::Restart => {
|
||||||
|
docker
|
||||||
|
.restart_container(
|
||||||
|
id.get(),
|
||||||
|
None::<bollard::query_parameters::RestartContainerOptions>,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
DockerCommand::Resume => docker.unpause_container(id.get()).await,
|
DockerCommand::Resume => docker.unpause_container(id.get()).await,
|
||||||
DockerCommand::Start => {
|
DockerCommand::Start => {
|
||||||
docker
|
docker
|
||||||
.start_container(id.get(), None::<StartContainerOptions<String>>)
|
.start_container(
|
||||||
|
id.get(),
|
||||||
|
None::<bollard::query_parameters::StartContainerOptions>,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
DockerCommand::Stop => {
|
||||||
|
docker
|
||||||
|
.stop_container(
|
||||||
|
id.get(),
|
||||||
|
None::<bollard::query_parameters::StopContainerOptions>,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
DockerCommand::Stop => docker.stop_container(id.get(), None).await,
|
|
||||||
}
|
}
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
@@ -445,119 +475,73 @@ impl DockerData {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::float_cmp)]
|
#[allow(clippy::float_cmp)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bollard::container::{
|
|
||||||
BlkioStats, CPUStats, CPUUsage, MemoryStats, PidsStats, Stats, StorageStats, ThrottlingData,
|
use bollard::secret::{ContainerCpuStats, ContainerCpuUsage};
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn gen_stats() -> Stats {
|
fn gen_stats() -> ContainerStatsResponse {
|
||||||
Stats {
|
ContainerStatsResponse {
|
||||||
read: String::new(),
|
read: None,
|
||||||
preread: String::new(),
|
preread: None,
|
||||||
num_procs: 1,
|
num_procs: Some(1),
|
||||||
pids_stats: PidsStats {
|
pids_stats: None,
|
||||||
current: None,
|
|
||||||
limit: None,
|
|
||||||
},
|
|
||||||
network: None,
|
|
||||||
networks: None,
|
networks: None,
|
||||||
memory_stats: MemoryStats {
|
memory_stats: None,
|
||||||
stats: None,
|
blkio_stats: None,
|
||||||
max_usage: None,
|
cpu_stats: Some(ContainerCpuStats {
|
||||||
usage: None,
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
failcnt: None,
|
|
||||||
limit: None,
|
|
||||||
commit: None,
|
|
||||||
commit_peak: None,
|
|
||||||
commitbytes: None,
|
|
||||||
commitpeakbytes: None,
|
|
||||||
privateworkingset: None,
|
|
||||||
},
|
|
||||||
blkio_stats: BlkioStats {
|
|
||||||
io_service_bytes_recursive: None,
|
|
||||||
io_serviced_recursive: None,
|
|
||||||
io_queue_recursive: None,
|
|
||||||
io_service_time_recursive: None,
|
|
||||||
io_wait_time_recursive: None,
|
|
||||||
io_merged_recursive: None,
|
|
||||||
io_time_recursive: None,
|
|
||||||
sectors_recursive: None,
|
|
||||||
},
|
|
||||||
cpu_stats: CPUStats {
|
|
||||||
cpu_usage: CPUUsage {
|
|
||||||
percpu_usage: Some(vec![50]),
|
percpu_usage: Some(vec![50]),
|
||||||
usage_in_usermode: 10,
|
usage_in_usermode: Some(10),
|
||||||
total_usage: 100,
|
total_usage: Some(100),
|
||||||
usage_in_kernelmode: 20,
|
usage_in_kernelmode: Some(20),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(400),
|
system_cpu_usage: Some(400),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
}),
|
||||||
throttled_periods: 0,
|
precpu_stats: Some(ContainerCpuStats {
|
||||||
throttled_time: 0,
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
},
|
|
||||||
},
|
|
||||||
precpu_stats: CPUStats {
|
|
||||||
cpu_usage: CPUUsage {
|
|
||||||
percpu_usage: Some(vec![50]),
|
percpu_usage: Some(vec![50]),
|
||||||
usage_in_usermode: 10,
|
usage_in_usermode: Some(10),
|
||||||
total_usage: 100,
|
total_usage: Some(100),
|
||||||
usage_in_kernelmode: 20,
|
usage_in_kernelmode: Some(20),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(400),
|
system_cpu_usage: Some(400),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
}),
|
||||||
throttled_periods: 0,
|
storage_stats: None,
|
||||||
throttled_time: 0,
|
name: None,
|
||||||
},
|
id: None,
|
||||||
},
|
|
||||||
storage_stats: StorageStats {
|
|
||||||
read_count_normalized: None,
|
|
||||||
read_size_bytes: None,
|
|
||||||
write_count_normalized: None,
|
|
||||||
write_size_bytes: None,
|
|
||||||
},
|
|
||||||
name: String::new(),
|
|
||||||
id: String::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_usage_50() {
|
fn test_calculate_usage_50() {
|
||||||
let mut stats = gen_stats();
|
let mut stats = gen_stats();
|
||||||
stats.precpu_stats = CPUStats {
|
stats.precpu_stats = Some(ContainerCpuStats {
|
||||||
cpu_usage: CPUUsage {
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
percpu_usage: Some(vec![50]),
|
percpu_usage: Some(vec![50]),
|
||||||
usage_in_usermode: 10,
|
usage_in_usermode: Some(10),
|
||||||
total_usage: 100,
|
total_usage: Some(100),
|
||||||
usage_in_kernelmode: 20,
|
usage_in_kernelmode: Some(20),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(400),
|
system_cpu_usage: Some(400),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
stats.cpu_stats = Some(ContainerCpuStats {
|
||||||
throttled_time: 0,
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
},
|
|
||||||
};
|
|
||||||
stats.cpu_stats = CPUStats {
|
|
||||||
cpu_usage: CPUUsage {
|
|
||||||
percpu_usage: Some(vec![150]),
|
percpu_usage: Some(vec![150]),
|
||||||
usage_in_usermode: 20,
|
usage_in_usermode: Some(20),
|
||||||
total_usage: 150,
|
total_usage: Some(150),
|
||||||
usage_in_kernelmode: 30,
|
usage_in_kernelmode: Some(30),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(500),
|
system_cpu_usage: Some(500),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
|
||||||
throttled_time: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let cpu_percentage = DockerData::calculate_usage(&stats);
|
let cpu_percentage = DockerData::calculate_usage(&stats);
|
||||||
assert_eq!(50.0, cpu_percentage);
|
assert_eq!(50.0, cpu_percentage);
|
||||||
}
|
}
|
||||||
@@ -565,37 +549,28 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_usage_25() {
|
fn test_calculate_usage_25() {
|
||||||
let mut stats = gen_stats();
|
let mut stats = gen_stats();
|
||||||
stats.precpu_stats = CPUStats {
|
stats.precpu_stats = Some(ContainerCpuStats {
|
||||||
cpu_usage: CPUUsage {
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
percpu_usage: Some(vec![50]),
|
percpu_usage: Some(vec![50]),
|
||||||
usage_in_usermode: 10,
|
usage_in_usermode: Some(10),
|
||||||
total_usage: 100,
|
total_usage: Some(100),
|
||||||
usage_in_kernelmode: 20,
|
usage_in_kernelmode: Some(20),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(400),
|
system_cpu_usage: Some(400),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
stats.cpu_stats = Some(ContainerCpuStats {
|
||||||
throttled_time: 0,
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
},
|
|
||||||
};
|
|
||||||
stats.cpu_stats = CPUStats {
|
|
||||||
cpu_usage: CPUUsage {
|
|
||||||
percpu_usage: Some(vec![75]),
|
percpu_usage: Some(vec![75]),
|
||||||
usage_in_usermode: 20,
|
usage_in_usermode: Some(20),
|
||||||
total_usage: 125,
|
total_usage: Some(125),
|
||||||
usage_in_kernelmode: 30,
|
usage_in_kernelmode: Some(30),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(500),
|
system_cpu_usage: Some(500),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
|
||||||
throttled_time: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let cpu_percentage = DockerData::calculate_usage(&stats);
|
let cpu_percentage = DockerData::calculate_usage(&stats);
|
||||||
assert_eq!(25.0, cpu_percentage);
|
assert_eq!(25.0, cpu_percentage);
|
||||||
}
|
}
|
||||||
@@ -603,38 +578,28 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_usage_75() {
|
fn test_calculate_usage_75() {
|
||||||
let mut stats = gen_stats();
|
let mut stats = gen_stats();
|
||||||
stats.precpu_stats = CPUStats {
|
stats.precpu_stats = Some(ContainerCpuStats {
|
||||||
cpu_usage: CPUUsage {
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
percpu_usage: Some(vec![50]),
|
percpu_usage: Some(vec![50]),
|
||||||
usage_in_usermode: 10,
|
usage_in_usermode: Some(10),
|
||||||
total_usage: 100,
|
total_usage: Some(100),
|
||||||
usage_in_kernelmode: 20,
|
usage_in_kernelmode: Some(20),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(400),
|
system_cpu_usage: Some(400),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
stats.cpu_stats = Some(ContainerCpuStats {
|
||||||
throttled_time: 0,
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
stats.cpu_stats = CPUStats {
|
|
||||||
cpu_usage: CPUUsage {
|
|
||||||
percpu_usage: Some(vec![175]),
|
percpu_usage: Some(vec![175]),
|
||||||
usage_in_usermode: 20,
|
usage_in_usermode: Some(20),
|
||||||
total_usage: 175,
|
total_usage: Some(175),
|
||||||
usage_in_kernelmode: 30,
|
usage_in_kernelmode: Some(30),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(500),
|
system_cpu_usage: Some(500),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
|
||||||
throttled_time: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let cpu_percentage = DockerData::calculate_usage(&stats);
|
let cpu_percentage = DockerData::calculate_usage(&stats);
|
||||||
assert_eq!(75.0, cpu_percentage);
|
assert_eq!(75.0, cpu_percentage);
|
||||||
}
|
}
|
||||||
@@ -642,36 +607,28 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_usage_100() {
|
fn test_calculate_usage_100() {
|
||||||
let mut stats = gen_stats();
|
let mut stats = gen_stats();
|
||||||
stats.precpu_stats = CPUStats {
|
stats.precpu_stats = Some(ContainerCpuStats {
|
||||||
cpu_usage: CPUUsage {
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
percpu_usage: Some(vec![50]),
|
percpu_usage: Some(vec![50]),
|
||||||
usage_in_usermode: 10,
|
usage_in_usermode: Some(10),
|
||||||
total_usage: 100,
|
total_usage: Some(100),
|
||||||
usage_in_kernelmode: 20,
|
usage_in_kernelmode: Some(20),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(400),
|
system_cpu_usage: Some(400),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
stats.cpu_stats = Some(ContainerCpuStats {
|
||||||
throttled_time: 0,
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
},
|
|
||||||
};
|
|
||||||
stats.cpu_stats = CPUStats {
|
|
||||||
cpu_usage: CPUUsage {
|
|
||||||
percpu_usage: Some(vec![200]),
|
percpu_usage: Some(vec![200]),
|
||||||
usage_in_usermode: 20,
|
usage_in_usermode: Some(20),
|
||||||
total_usage: 200,
|
total_usage: Some(200),
|
||||||
usage_in_kernelmode: 30,
|
usage_in_kernelmode: Some(30),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(500),
|
system_cpu_usage: Some(500),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
|
||||||
throttled_time: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let cpu_percentage = DockerData::calculate_usage(&stats);
|
let cpu_percentage = DockerData::calculate_usage(&stats);
|
||||||
assert_eq!(100.0, cpu_percentage);
|
assert_eq!(100.0, cpu_percentage);
|
||||||
}
|
}
|
||||||
@@ -679,38 +636,28 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_usage_175() {
|
fn test_calculate_usage_175() {
|
||||||
let mut stats = gen_stats();
|
let mut stats = gen_stats();
|
||||||
stats.precpu_stats = CPUStats {
|
stats.precpu_stats = Some(ContainerCpuStats {
|
||||||
cpu_usage: CPUUsage {
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
percpu_usage: Some(vec![50]),
|
percpu_usage: Some(vec![50]),
|
||||||
usage_in_usermode: 10,
|
usage_in_usermode: Some(10),
|
||||||
total_usage: 100,
|
total_usage: Some(100),
|
||||||
usage_in_kernelmode: 20,
|
usage_in_kernelmode: Some(20),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(400),
|
system_cpu_usage: Some(400),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
stats.cpu_stats = Some(ContainerCpuStats {
|
||||||
throttled_time: 0,
|
cpu_usage: Some(ContainerCpuUsage {
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
stats.cpu_stats = CPUStats {
|
|
||||||
cpu_usage: CPUUsage {
|
|
||||||
percpu_usage: Some(vec![275]),
|
percpu_usage: Some(vec![275]),
|
||||||
usage_in_usermode: 20,
|
usage_in_usermode: Some(20),
|
||||||
total_usage: 275,
|
total_usage: Some(275),
|
||||||
usage_in_kernelmode: 30,
|
usage_in_kernelmode: Some(30),
|
||||||
},
|
}),
|
||||||
system_cpu_usage: Some(500),
|
system_cpu_usage: Some(500),
|
||||||
online_cpus: Some(1),
|
online_cpus: Some(1),
|
||||||
throttling_data: ThrottlingData {
|
throttling_data: None,
|
||||||
periods: 0,
|
});
|
||||||
throttled_periods: 0,
|
|
||||||
throttled_time: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let cpu_percentage = DockerData::calculate_usage(&stats);
|
let cpu_percentage = DockerData::calculate_usage(&stats);
|
||||||
assert_eq!(175.0, cpu_percentage);
|
assert_eq!(175.0, cpu_percentage);
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-14
@@ -5,7 +5,8 @@ use std::{
|
|||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bollard::container::LogsOptions;
|
use bollard::query_parameters::LogsOptions;
|
||||||
|
// use bollard::container::LogsOptions;
|
||||||
use cansi::v3::categorise_text;
|
use cansi::v3::categorise_text;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind},
|
event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind},
|
||||||
@@ -187,7 +188,7 @@ impl InputHandler {
|
|||||||
|
|
||||||
let path = log_path.join(format!("{name}_{now}.log"));
|
let path = log_path.join(format!("{name}_{now}.log"));
|
||||||
|
|
||||||
let options = Some(LogsOptions::<String> {
|
let options = Some(LogsOptions {
|
||||||
stderr: true,
|
stderr: true,
|
||||||
stdout: true,
|
stdout: true,
|
||||||
timestamps: args.show_timestamp,
|
timestamps: args.show_timestamp,
|
||||||
@@ -288,23 +289,15 @@ impl InputHandler {
|
|||||||
/// Change the the "next" selectable panel
|
/// Change the the "next" selectable panel
|
||||||
/// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state
|
/// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state
|
||||||
fn next_panel_key(&self) {
|
fn next_panel_key(&self) {
|
||||||
self.gui_state.lock().next_panel();
|
self.gui_state.lock().selectable_panel_next(&self.app_data);
|
||||||
if self.app_data.lock().get_container_len() == 0
|
|
||||||
&& self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
|
|
||||||
{
|
|
||||||
self.gui_state.lock().next_panel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change to previously selected panel
|
/// Change to previously selected panel
|
||||||
/// Need to skip the commands planel if there no are current containers running
|
/// Need to skip the commands planel if there no are current containers running
|
||||||
fn previous_panel_key(&self) {
|
fn previous_panel_key(&self) {
|
||||||
self.gui_state.lock().previous_panel();
|
self.gui_state
|
||||||
if self.app_data.lock().get_container_len() == 0
|
.lock()
|
||||||
&& self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
|
.selectable_panel_previous(&self.app_data);
|
||||||
{
|
|
||||||
self.gui_state.lock().previous_panel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_start_key(&self) {
|
fn scroll_start_key(&self) {
|
||||||
@@ -458,9 +451,25 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increase the log panel height
|
||||||
|
fn log_panel_height_increase(&self) {
|
||||||
|
self.gui_state.lock().log_height_increase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease the log panel height
|
||||||
|
fn log_panel_height_decrease(&self) {
|
||||||
|
self.gui_state.lock().log_height_decrease();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle visibility of the log panel
|
||||||
|
fn log_panel_toggle(&self) {
|
||||||
|
self.gui_state.lock().toggle_show_logs();
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle button presses in all other scenarios
|
/// Handle button presses in all other scenarios
|
||||||
async fn handle_others(&mut self, key_code: KeyCode) {
|
async fn handle_others(&mut self, key_code: KeyCode) {
|
||||||
self.handle_sort(key_code);
|
self.handle_sort(key_code);
|
||||||
|
// shift key plus arrows
|
||||||
match key_code {
|
match key_code {
|
||||||
_ if self.keymap.exec.0 == key_code || self.keymap.exec.1 == Some(key_code) => {
|
_ if self.keymap.exec.0 == key_code || self.keymap.exec.1 == Some(key_code) => {
|
||||||
self.exec_key().await;
|
self.exec_key().await;
|
||||||
@@ -477,6 +486,23 @@ impl InputHandler {
|
|||||||
{
|
{
|
||||||
self.mouse_capture_key();
|
self.mouse_capture_key();
|
||||||
}
|
}
|
||||||
|
_ if self.keymap.log_section_height_decrease.0 == key_code
|
||||||
|
|| self.keymap.log_section_height_decrease.1 == Some(key_code) =>
|
||||||
|
{
|
||||||
|
self.log_panel_height_decrease();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ if self.keymap.log_section_height_increase.0 == key_code
|
||||||
|
|| self.keymap.log_section_height_increase.1 == Some(key_code) =>
|
||||||
|
{
|
||||||
|
self.log_panel_height_increase();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ if self.keymap.log_section_toggle.0 == key_code
|
||||||
|
|| self.keymap.log_section_toggle.1 == Some(key_code) =>
|
||||||
|
{
|
||||||
|
self.log_panel_toggle();
|
||||||
|
}
|
||||||
|
|
||||||
_ if self.keymap.save_logs.0 == key_code
|
_ if self.keymap.save_logs.0 == key_code
|
||||||
|| self.keymap.save_logs.1 == Some(key_code) =>
|
|| self.keymap.save_logs.1 == Some(key_code) =>
|
||||||
|
|||||||
+9
-7
@@ -23,7 +23,7 @@ mod exec;
|
|||||||
mod input_handler;
|
mod input_handler;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use ui::{GuiState, Redraw, Status, Ui};
|
use ui::{GuiState, Rerender, Status, Ui};
|
||||||
|
|
||||||
use crate::docker_data::DockerMessage;
|
use crate::docker_data::DockerMessage;
|
||||||
|
|
||||||
@@ -98,10 +98,10 @@ fn handler_init(
|
|||||||
async fn main() {
|
async fn main() {
|
||||||
setup_tracing();
|
setup_tracing();
|
||||||
let config = config::Config::new();
|
let config = config::Config::new();
|
||||||
let redraw = Arc::new(Redraw::new());
|
let redraw = Arc::new(Rerender::new());
|
||||||
|
|
||||||
let app_data = Arc::new(Mutex::new(AppData::new(config.clone(), &redraw)));
|
let app_data = Arc::new(Mutex::new(AppData::new(config.clone(), &redraw)));
|
||||||
let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw)));
|
let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw, config.show_logs)));
|
||||||
let is_running = Arc::new(AtomicBool::new(true));
|
let is_running = Arc::new(AtomicBool::new(true));
|
||||||
let (docker_tx, docker_rx) = tokio::sync::mpsc::channel(32);
|
let (docker_tx, docker_rx) = tokio::sync::mpsc::channel(32);
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ async fn main() {
|
|||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use bollard::service::{ContainerSummary, Port};
|
use bollard::service::{ContainerSummary, Port};
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ mod tests {
|
|||||||
RunningState, State, StatefulList,
|
RunningState, State, StatefulList,
|
||||||
},
|
},
|
||||||
config::{AppColors, Config, Keymap},
|
config::{AppColors, Config, Keymap},
|
||||||
ui::Redraw,
|
ui::Rerender,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Default test config, has timestamps turned off
|
/// Default test config, has timestamps turned off
|
||||||
@@ -179,6 +179,7 @@ mod tests {
|
|||||||
timestamp_format: "HH:MM:SS.NNNNN dd-mm-yyyy".to_owned(),
|
timestamp_format: "HH:MM:SS.NNNNN dd-mm-yyyy".to_owned(),
|
||||||
show_timestamp: false,
|
show_timestamp: false,
|
||||||
use_cli: false,
|
use_cli: false,
|
||||||
|
show_logs: true,
|
||||||
timezone: None,
|
timezone: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +208,7 @@ mod tests {
|
|||||||
current_sorted_id: vec![],
|
current_sorted_id: vec![],
|
||||||
error: None,
|
error: None,
|
||||||
sorted_by: None,
|
sorted_by: None,
|
||||||
redraw: Arc::new(Redraw::new()),
|
redraw: Arc::new(Rerender::new()),
|
||||||
filter: Filter::new(),
|
filter: Filter::new(),
|
||||||
config: gen_config(),
|
config: gen_config(),
|
||||||
}
|
}
|
||||||
@@ -227,6 +228,7 @@ 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,
|
||||||
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}")),
|
||||||
@@ -242,7 +244,7 @@ mod tests {
|
|||||||
size_rw: None,
|
size_rw: None,
|
||||||
size_root_fs: None,
|
size_root_fs: None,
|
||||||
labels: None,
|
labels: None,
|
||||||
state: Some(state.to_owned()),
|
state: Some(bollard::secret::ContainerSummaryStateEnum::from_str(state).unwrap()),
|
||||||
status: Some(format!("Up {index} hour")),
|
status: Some(format!("Up {index} hour")),
|
||||||
host_config: None,
|
host_config: None,
|
||||||
network_settings: None,
|
network_settings: None,
|
||||||
|
|||||||
@@ -241,7 +241,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Control panel now selected, should have a blue border
|
// Control panel now selected, should have a blue border
|
||||||
setup.gui_state.lock().next_panel();
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.selectable_panel_next(&setup.app_data);
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
|
|||||||
@@ -157,7 +157,10 @@ mod tests {
|
|||||||
let mut setup = test_setup(40, 6, true, true);
|
let mut setup = test_setup(40, 6, true, true);
|
||||||
setup.app_data.lock().containers = StatefulList::new(vec![]);
|
setup.app_data.lock().containers = StatefulList::new(vec![]);
|
||||||
|
|
||||||
setup.gui_state.lock().next_panel();
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.selectable_panel_next(&setup.app_data);
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
let colors = setup.app_data.lock().config.app_colors;
|
let colors = setup.app_data.lock().config.app_colors;
|
||||||
|
|
||||||
@@ -184,7 +187,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setup.gui_state.lock().previous_panel();
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.selectable_panel_previous(&setup.app_data);
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
setup
|
setup
|
||||||
@@ -255,7 +261,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Change selected panel, border is now no longer blue
|
// Change selected panel, border is now no longer blue
|
||||||
setup.gui_state.lock().next_panel();
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.selectable_panel_next(&setup.app_data);
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
|
|||||||
@@ -154,10 +154,9 @@ fn draw_columns(
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let container_splits = header_data.iter().map(|i| i.2).collect::<Vec<_>>();
|
|
||||||
let headers_section = Layout::default()
|
let headers_section = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(container_splits)
|
.constraints(header_data.iter().map(|i| i.2))
|
||||||
.split(split_bar[1]);
|
.split(split_bar[1]);
|
||||||
|
|
||||||
for (index, (paragraph, header, _)) in header_data.into_iter().enumerate() {
|
for (index, (paragraph, header, _)) in header_data.into_iter().enumerate() {
|
||||||
@@ -196,19 +195,18 @@ pub fn draw(
|
|||||||
|
|
||||||
let column_width = usize::from(area.width).saturating_sub(help_width);
|
let column_width = usize::from(area.width).saturating_sub(help_width);
|
||||||
let column_width = if column_width > 0 { column_width } else { 1 };
|
let column_width = if column_width > 0 { column_width } else { 1 };
|
||||||
let splits = if fd.has_containers {
|
|
||||||
vec![
|
|
||||||
Constraint::Max(4),
|
|
||||||
Constraint::Max(column_width.try_into().unwrap_or_default()),
|
|
||||||
Constraint::Max(help_width.try_into().unwrap_or_default()),
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
CONSTRAINT_100.to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
let split_bar = Layout::default()
|
let split_bar = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(splits)
|
.constraints(if fd.has_containers {
|
||||||
|
vec![
|
||||||
|
Constraint::Max(4),
|
||||||
|
Constraint::Max(column_width.try_into().unwrap_or_default()),
|
||||||
|
Constraint::Max(help_width.try_into().unwrap_or_default()),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
CONSTRAINT_100.to_vec()
|
||||||
|
})
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
draw_loading_spinner(colors, f, fd, split_bar[0]);
|
draw_loading_spinner(colors, f, fd, split_bar[0]);
|
||||||
|
|||||||
+49
-29
@@ -85,6 +85,7 @@ impl HelpInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the button information span + metadata
|
/// Generate the button information span + metadata
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn gen_keymap_info(colors: AppColors, zone: Option<&TimeZone>, show_timestamp: bool) -> Self {
|
fn gen_keymap_info(colors: AppColors, zone: Option<&TimeZone>, show_timestamp: bool) -> Self {
|
||||||
let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors);
|
let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors);
|
||||||
let button_desc = |x: &str| Self::text_span(x, colors);
|
let button_desc = |x: &str| Self::text_span(x, colors);
|
||||||
@@ -152,6 +153,16 @@ impl HelpInfo {
|
|||||||
button_item("1 - 9"),
|
button_item("1 - 9"),
|
||||||
button_desc("sort by header - or click header"),
|
button_desc("sort by header - or click header"),
|
||||||
]),
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("- ="),
|
||||||
|
button_desc("change log section height"),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("\\"),
|
||||||
|
button_desc("toggle log section visibility"),
|
||||||
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
space(),
|
space(),
|
||||||
button_item("esc"),
|
button_item("esc"),
|
||||||
@@ -212,15 +223,6 @@ impl HelpInfo {
|
|||||||
fn custom_text<'a>(colors: AppColors, _keymap: &Keymap, zone: Option<&TimeZone>) -> Line<'a> {
|
fn custom_text<'a>(colors: AppColors, _keymap: &Keymap, zone: Option<&TimeZone>) -> Line<'a> {
|
||||||
let highlighted = |x: &str| Self::highlighted_text_span(x, colors);
|
let highlighted = |x: &str| Self::highlighted_text_span(x, colors);
|
||||||
let text = |x: &str| Self::text_span(x, colors);
|
let text = |x: &str| Self::text_span(x, colors);
|
||||||
|
|
||||||
// if keymap != &Keymap::new() {
|
|
||||||
// op.push(highlighted("customised keymap, "));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if colors != AppColors::new() {
|
|
||||||
// op.push(highlighted("customised app colors, "));
|
|
||||||
// };
|
|
||||||
|
|
||||||
let zone = zone.and_then(|i| i.iana_name()).unwrap_or("Etc/UTC");
|
let zone = zone.and_then(|i| i.iana_name()).unwrap_or("Etc/UTC");
|
||||||
Line::from(Vec::from([text("logs timezone: "), highlighted(zone)])).centered()
|
Line::from(Vec::from([text("logs timezone: "), highlighted(zone)])).centered()
|
||||||
}
|
}
|
||||||
@@ -295,6 +297,15 @@ impl HelpInfo {
|
|||||||
or_secondary(km.sort_by_image, "sort containers by image"),
|
or_secondary(km.sort_by_image, "sort containers by image"),
|
||||||
or_secondary(km.sort_by_rx, "sort containers by rx"),
|
or_secondary(km.sort_by_rx, "sort containers by rx"),
|
||||||
or_secondary(km.sort_by_tx, "sort containers by tx"),
|
or_secondary(km.sort_by_tx, "sort containers by tx"),
|
||||||
|
or_secondary(
|
||||||
|
km.log_section_height_decrease,
|
||||||
|
"decrease log section height",
|
||||||
|
),
|
||||||
|
or_secondary(
|
||||||
|
km.log_section_height_increase,
|
||||||
|
"increase log section height",
|
||||||
|
),
|
||||||
|
or_secondary(km.log_section_toggle, "toggle log section visibility"),
|
||||||
or_secondary(km.clear, "close dialog"),
|
or_secondary(km.clear, "close dialog"),
|
||||||
or_secondary(km.quit, "quit at any time"),
|
or_secondary(km.quit, "quit at any time"),
|
||||||
];
|
];
|
||||||
@@ -426,7 +437,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
/// This will cause issues once the version has more than the current 5 chars (0.5.0)
|
/// This will cause issues once the version has more than the current 5 chars (0.5.0)
|
||||||
fn test_draw_blocks_help() {
|
fn test_draw_blocks_help() {
|
||||||
let mut setup = test_setup(87, 33, true, true);
|
let mut setup = test_setup(87, 35, true, true);
|
||||||
let tz = setup.app_data.lock().config.timezone.clone();
|
let tz = setup.app_data.lock().config.timezone.clone();
|
||||||
|
|
||||||
setup
|
setup
|
||||||
@@ -448,29 +459,29 @@ mod tests {
|
|||||||
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
match (row_index, result_cell_index) {
|
match (row_index, result_cell_index) {
|
||||||
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
||||||
(0 | 32, _) | (0..=33, 0 | 86) => {
|
(0 | 34, _) | (0..=33, 0 | 86) => {
|
||||||
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);
|
||||||
}
|
}
|
||||||
// border is black on magenta
|
// border is black on magenta
|
||||||
(1 | 31, _) | (1..=31, 1 | 85) => {
|
(1 | 32, _) | (1..=31, 1 | 85) => {
|
||||||
assert_eq!(result_cell.bg, Color::Magenta);
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
assert_eq!(result_cell.fg, Color::Black);
|
assert_eq!(result_cell.fg, Color::Black);
|
||||||
}
|
}
|
||||||
// oxker logo && description
|
// oxker logo && description
|
||||||
(2..=10, 2..=85) | (12, 19..=66)
|
(2..=10, 2..=85)
|
||||||
// button in the brackets
|
| (12, 19..=66)
|
||||||
| (14, 2..=10 | 13..=27)
|
| (14, 2..=10 | 13..=27)
|
||||||
| (15, 2..=10 | 13..=21 | 24..=40 | 43..=56)
|
| (15, 2..=10 | 13..=21 | 24..=40 | 43..=56)
|
||||||
| (16 | 23, 2..=12)
|
| (16 | 23, 2..=12)
|
||||||
| (17..=20 | 22 | 25, 2..=8)
|
| (17..=20 | 22 | 25 | 27, 2..=8)
|
||||||
| (21, 2..=9 | 12..=18)
|
| (21, 2..=9 | 12..=18)
|
||||||
| (24, 2..=10) => {
|
| (24 | 26, 2..=10) => {
|
||||||
assert_eq!(result_cell.bg, Color::Magenta);
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
assert_eq!(result_cell.fg, Color::White);
|
assert_eq!(result_cell.fg, Color::White);
|
||||||
}
|
}
|
||||||
// The URL is white and underlined
|
// The URL is white and underlined
|
||||||
(28, 25..=60) => {
|
(30, 25..=60) => {
|
||||||
assert_eq!(result_cell.bg, Color::Magenta);
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
assert_eq!(result_cell.fg, Color::White);
|
assert_eq!(result_cell.fg, Color::White);
|
||||||
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
||||||
@@ -488,7 +499,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
/// Test that the help panel gets drawn with custom colors
|
/// Test that the help panel gets drawn with custom colors
|
||||||
fn test_draw_blocks_help_custom_colors() {
|
fn test_draw_blocks_help_custom_colors() {
|
||||||
let mut setup = test_setup(87, 33, true, true);
|
let mut setup = test_setup(87, 35, true, true);
|
||||||
let mut colors = AppColors::new();
|
let mut colors = AppColors::new();
|
||||||
let tz = setup.app_data.lock().config.timezone.clone();
|
let tz = setup.app_data.lock().config.timezone.clone();
|
||||||
|
|
||||||
@@ -515,29 +526,29 @@ mod tests {
|
|||||||
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
match (row_index, result_cell_index) {
|
match (row_index, result_cell_index) {
|
||||||
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
||||||
(0 | 32, _) | (0..=33, 0 | 86) => {
|
(0 | 34, _) | (0..=33, 0 | 86) => {
|
||||||
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);
|
||||||
}
|
}
|
||||||
// border is black on magenta
|
// border is red on black
|
||||||
(1 | 31, _) | (1..=31, 1 | 85) => {
|
(1 | 32, _) | (1..=31, 1 | 85) => {
|
||||||
assert_eq!(result_cell.bg, Color::Black);
|
assert_eq!(result_cell.bg, Color::Black);
|
||||||
assert_eq!(result_cell.fg, Color::Red);
|
assert_eq!(result_cell.fg, Color::Red);
|
||||||
}
|
}
|
||||||
// oxker logo && description
|
// oxker logo && description
|
||||||
(2..=10, 2..=85) | (12, 19..=66)
|
(2..=10, 2..=85)
|
||||||
// button in the brackets
|
| (12, 19..=66)
|
||||||
| (14, 2..=10 | 13..=27)
|
| (14, 2..=10 | 13..=27)
|
||||||
| (15, 2..=10 | 13..=21 | 24..=40 | 43..=56)
|
| (15, 2..=10 | 13..=21 | 24..=40 | 43..=56)
|
||||||
| (16 | 23, 2..=12)
|
| (16 | 23, 2..=12)
|
||||||
| (17..=20 | 22 | 25, 2..=8)
|
| (17..=20 | 22 | 25 | 27, 2..=8)
|
||||||
| (21, 2..=9 | 12..=18)
|
| (21, 2..=9 | 12..=18)
|
||||||
| (24, 2..=10) => {
|
| (24 | 26, 2..=10) => {
|
||||||
assert_eq!(result_cell.bg, Color::Black);
|
assert_eq!(result_cell.bg, Color::Black);
|
||||||
assert_eq!(result_cell.fg, Color::Yellow);
|
assert_eq!(result_cell.fg, Color::Yellow);
|
||||||
}
|
}
|
||||||
// The URL is yellow and underlined
|
// The URL is yellow and underlined
|
||||||
(28, 25..=60) => {
|
(30, 25..=60) => {
|
||||||
assert_eq!(result_cell.bg, Color::Black);
|
assert_eq!(result_cell.bg, Color::Black);
|
||||||
assert_eq!(result_cell.fg, Color::Yellow);
|
assert_eq!(result_cell.fg, Color::Yellow);
|
||||||
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
||||||
@@ -562,6 +573,9 @@ mod tests {
|
|||||||
delete_deny: (KeyCode::Char('c'), None),
|
delete_deny: (KeyCode::Char('c'), None),
|
||||||
delete_confirm: (KeyCode::Char('e'), None),
|
delete_confirm: (KeyCode::Char('e'), None),
|
||||||
exec: (KeyCode::Char('g'), None),
|
exec: (KeyCode::Char('g'), None),
|
||||||
|
log_section_height_decrease: (KeyCode::Char('z'), None),
|
||||||
|
log_section_height_increase: (KeyCode::Char('x'), None),
|
||||||
|
log_section_toggle: (KeyCode::Char('W'), None),
|
||||||
filter_mode: (KeyCode::Char('i'), None),
|
filter_mode: (KeyCode::Char('i'), None),
|
||||||
quit: (KeyCode::Char('k'), None),
|
quit: (KeyCode::Char('k'), None),
|
||||||
save_logs: (KeyCode::Char('m'), None),
|
save_logs: (KeyCode::Char('m'), None),
|
||||||
@@ -607,6 +621,9 @@ mod tests {
|
|||||||
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
|
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
|
||||||
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
|
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
|
||||||
exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
|
exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
|
||||||
|
log_section_height_decrease: (KeyCode::Char('A'), Some(KeyCode::Char('Z'))),
|
||||||
|
log_section_height_increase: (KeyCode::Char('B'), Some(KeyCode::Char('X'))),
|
||||||
|
log_section_toggle: (KeyCode::Char('C'), Some(KeyCode::Char('W'))),
|
||||||
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
||||||
quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
||||||
save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
||||||
@@ -653,6 +670,9 @@ mod tests {
|
|||||||
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
|
delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
|
||||||
exec: (KeyCode::Char('g'), None),
|
exec: (KeyCode::Char('g'), None),
|
||||||
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
||||||
|
log_section_height_decrease: (KeyCode::Char('A'), Some(KeyCode::Char('Z'))),
|
||||||
|
log_section_height_increase: (KeyCode::Char('B'), Some(KeyCode::Char('X'))),
|
||||||
|
log_section_toggle: (KeyCode::Char('C'), Some(KeyCode::Char('W'))),
|
||||||
quit: (KeyCode::Char('k'), None),
|
quit: (KeyCode::Char('k'), None),
|
||||||
save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
||||||
scroll_down_many: (KeyCode::Char('o'), None),
|
scroll_down_many: (KeyCode::Char('o'), None),
|
||||||
@@ -691,7 +711,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_draw_blocks_help_show_timezone() {
|
fn test_draw_blocks_help_show_timezone() {
|
||||||
let mut setup = test_setup(87, 35, true, true);
|
let mut setup = test_setup(87, 37, true, true);
|
||||||
|
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ pub fn draw(
|
|||||||
}
|
}
|
||||||
f.render_widget(paragraph, area);
|
f.render_widget(paragraph, area);
|
||||||
} else {
|
} else {
|
||||||
let logs = app_data.lock().get_logs();
|
let padding = usize::from(area.height / 5);
|
||||||
|
let logs = app_data.lock().get_logs(area.height, padding);
|
||||||
if logs.is_empty() {
|
if logs.is_empty() {
|
||||||
let mut paragraph = Paragraph::new("no logs found")
|
let mut paragraph = Paragraph::new("no logs found")
|
||||||
.block(block)
|
.block(block)
|
||||||
@@ -52,6 +53,7 @@ pub fn draw(
|
|||||||
let items = List::new(logs)
|
let items = List::new(logs)
|
||||||
.block(block)
|
.block(block)
|
||||||
.highlight_symbol(RIGHT_ARROW)
|
.highlight_symbol(RIGHT_ARROW)
|
||||||
|
.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
|
||||||
if let Some(log_state) = app_data.lock().get_log_state() {
|
if let Some(log_state) = app_data.lock().get_log_state() {
|
||||||
@@ -124,8 +126,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setup.gui_state.lock().next_panel();
|
setup
|
||||||
setup.gui_state.lock().next_panel();
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.selectable_panel_next(&setup.app_data);
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.selectable_panel_next(&setup.app_data);
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
// When selected, has a blue border
|
// When selected, has a blue border
|
||||||
|
|||||||
+201
-14
@@ -127,8 +127,9 @@ pub mod tests {
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts},
|
app_data::{AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts},
|
||||||
|
app_error::AppError,
|
||||||
tests::{gen_appdata, gen_containers},
|
tests::{gen_appdata, gen_containers},
|
||||||
ui::{GuiState, Redraw, draw_frame},
|
ui::{GuiState, Rerender, Status, draw_frame},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::FrameData;
|
use super::FrameData;
|
||||||
@@ -152,32 +153,33 @@ pub mod tests {
|
|||||||
fn from(data: (&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)) -> Self {
|
fn from(data: (&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)) -> Self {
|
||||||
let (app_data, gui_data) = (data.0.lock(), data.1.lock());
|
let (app_data, gui_data) = (data.0.lock(), data.1.lock());
|
||||||
|
|
||||||
// set max height for container section, needs +5 to deal with docker commands list and borders
|
// let container_section_height = app_data.get_container_len();
|
||||||
let height = app_data.get_container_len();
|
// let container_section_height = if container_section_height < 12 {
|
||||||
let height = if height < 12 {
|
// u16::try_from(container_section_height + 5).unwrap_or_default()
|
||||||
u16::try_from(height + 5).unwrap_or_default()
|
// } else {
|
||||||
} else {
|
// 12
|
||||||
12
|
// };
|
||||||
};
|
|
||||||
|
|
||||||
let (filter_by, filter_term) = app_data.get_filter();
|
let (filter_by, filter_term) = app_data.get_filter();
|
||||||
Self {
|
Self {
|
||||||
chart_data: app_data.get_chart_data(),
|
chart_data: app_data.get_chart_data(),
|
||||||
columns: app_data.get_width(),
|
|
||||||
color_logs: app_data.config.color_logs,
|
color_logs: app_data.config.color_logs,
|
||||||
|
columns: app_data.get_width(),
|
||||||
|
// container_section_height,
|
||||||
container_title: app_data.get_container_title(),
|
container_title: app_data.get_container_title(),
|
||||||
delete_confirm: gui_data.get_delete_container(),
|
delete_confirm: gui_data.get_delete_container(),
|
||||||
filter_by,
|
filter_by,
|
||||||
filter_term: filter_term.cloned(),
|
filter_term: filter_term.cloned(),
|
||||||
has_containers: app_data.get_container_len() > 0,
|
has_containers: app_data.get_container_len() > 0,
|
||||||
has_error: app_data.get_error(),
|
has_error: app_data.get_error(),
|
||||||
height,
|
show_logs: gui_data.get_show_logs(),
|
||||||
ports: app_data.get_selected_ports(),
|
|
||||||
port_max_lens: app_data.get_longest_port(),
|
|
||||||
info_text: gui_data.info_box_text.clone(),
|
info_text: gui_data.info_box_text.clone(),
|
||||||
is_loading: gui_data.is_loading(),
|
is_loading: gui_data.is_loading(),
|
||||||
loading_icon: gui_data.get_loading().to_string(),
|
loading_icon: gui_data.get_loading().to_string(),
|
||||||
|
log_height: gui_data.get_log_height(),
|
||||||
log_title: app_data.get_log_title(),
|
log_title: app_data.get_log_title(),
|
||||||
|
port_max_lens: app_data.get_longest_port(),
|
||||||
|
ports: app_data.get_selected_ports(),
|
||||||
selected_panel: gui_data.get_selected_panel(),
|
selected_panel: gui_data.get_selected_panel(),
|
||||||
sorted_by: app_data.get_sorted(),
|
sorted_by: app_data.get_sorted(),
|
||||||
status: gui_data.get_status(),
|
status: gui_data.get_status(),
|
||||||
@@ -199,8 +201,8 @@ pub mod tests {
|
|||||||
app_data.containers_start();
|
app_data.containers_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
let redraw = Arc::new(Redraw::new());
|
let redraw = Arc::new(Rerender::new());
|
||||||
let gui_state = GuiState::new(&redraw);
|
let gui_state = GuiState::new(&redraw, app_data.config.show_logs);
|
||||||
|
|
||||||
let app_data = Arc::new(Mutex::new(app_data));
|
let app_data = Arc::new(Mutex::new(app_data));
|
||||||
let gui_state = Arc::new(Mutex::new(gui_state));
|
let gui_state = Arc::new(Mutex::new(gui_state));
|
||||||
@@ -360,4 +362,189 @@ pub mod tests {
|
|||||||
|
|
||||||
assert_snapshot!(setup.terminal.backend());
|
assert_snapshot!(setup.terminal.backend());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Check that the whole layout is drawn correctly when the logs panel is removed
|
||||||
|
fn test_draw_blocks_whole_layout_no_logs() {
|
||||||
|
let mut setup = test_setup(160, 30, true, true);
|
||||||
|
|
||||||
|
insert_chart_data(&setup);
|
||||||
|
insert_logs(&setup);
|
||||||
|
setup.app_data.lock().containers.items[0]
|
||||||
|
.ports
|
||||||
|
.push(ContainerPorts {
|
||||||
|
ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
|
||||||
|
private: 8003,
|
||||||
|
public: Some(8003),
|
||||||
|
});
|
||||||
|
let colors = setup.app_data.lock().config.app_colors;
|
||||||
|
let keymap = setup.app_data.lock().config.keymap.clone();
|
||||||
|
setup.gui_state.lock().log_height_zero();
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Check that the whole layout is drawn correctly when the logs panel height is ~4
|
||||||
|
fn test_draw_blocks_whole_layout_short_height_logs() {
|
||||||
|
let mut setup = test_setup(160, 30, true, true);
|
||||||
|
|
||||||
|
insert_chart_data(&setup);
|
||||||
|
insert_logs(&setup);
|
||||||
|
setup.app_data.lock().containers.items[0]
|
||||||
|
.ports
|
||||||
|
.push(ContainerPorts {
|
||||||
|
ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
|
||||||
|
private: 8003,
|
||||||
|
public: Some(8003),
|
||||||
|
});
|
||||||
|
let colors = setup.app_data.lock().config.app_colors;
|
||||||
|
let keymap = setup.app_data.lock().config.keymap.clone();
|
||||||
|
setup.gui_state.lock().log_height_zero();
|
||||||
|
|
||||||
|
for _ in 0..=3 {
|
||||||
|
setup.gui_state.lock().log_height_increase();
|
||||||
|
}
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Check that the whole layout is drawn with the help panel visible
|
||||||
|
fn test_draw_blocks_whole_layout_help_panel() {
|
||||||
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
|
insert_chart_data(&setup);
|
||||||
|
insert_logs(&setup);
|
||||||
|
setup.app_data.lock().containers.items[0]
|
||||||
|
.ports
|
||||||
|
.push(ContainerPorts {
|
||||||
|
ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
|
||||||
|
private: 8003,
|
||||||
|
public: Some(8003),
|
||||||
|
});
|
||||||
|
let colors = setup.app_data.lock().config.app_colors;
|
||||||
|
let keymap = setup.app_data.lock().config.keymap.clone();
|
||||||
|
|
||||||
|
setup.gui_state.lock().status_push(Status::Help);
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Check that the whole layout is drawn with the error box is visible
|
||||||
|
fn test_draw_blocks_whole_layout_error() {
|
||||||
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
|
insert_chart_data(&setup);
|
||||||
|
insert_logs(&setup);
|
||||||
|
setup.app_data.lock().containers.items[0]
|
||||||
|
.ports
|
||||||
|
.push(ContainerPorts {
|
||||||
|
ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
|
||||||
|
private: 8003,
|
||||||
|
public: Some(8003),
|
||||||
|
});
|
||||||
|
let colors = setup.app_data.lock().config.app_colors;
|
||||||
|
let keymap = setup.app_data.lock().config.keymap.clone();
|
||||||
|
|
||||||
|
setup.app_data.lock().set_error(
|
||||||
|
AppError::DockerCommand(crate::app_data::DockerCommand::Pause),
|
||||||
|
&setup.gui_state,
|
||||||
|
Status::Error,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Check that the whole layout is drawn with the delete box is visible
|
||||||
|
fn test_draw_blocks_whole_layout_delete() {
|
||||||
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
|
insert_chart_data(&setup);
|
||||||
|
insert_logs(&setup);
|
||||||
|
setup.app_data.lock().containers.items[0]
|
||||||
|
.ports
|
||||||
|
.push(ContainerPorts {
|
||||||
|
ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
|
||||||
|
private: 8003,
|
||||||
|
public: Some(8003),
|
||||||
|
});
|
||||||
|
let colors = setup.app_data.lock().config.app_colors;
|
||||||
|
let keymap = setup.app_data.lock().config.keymap.clone();
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.set_delete_container(setup.app_data.lock().get_selected_container_id());
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Check that the whole layout is drawn with the info box is visible
|
||||||
|
fn test_draw_blocks_whole_layout_info_box() {
|
||||||
|
let mut setup = test_setup(160, 40, true, true);
|
||||||
|
|
||||||
|
insert_chart_data(&setup);
|
||||||
|
insert_logs(&setup);
|
||||||
|
setup.app_data.lock().containers.items[0]
|
||||||
|
.ports
|
||||||
|
.push(ContainerPorts {
|
||||||
|
ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
|
||||||
|
private: 8003,
|
||||||
|
public: Some(8003),
|
||||||
|
});
|
||||||
|
let colors = setup.app_data.lock().config.app_colors;
|
||||||
|
let keymap = setup.app_data.lock().config.keymap.clone();
|
||||||
|
setup.gui_state.lock().set_info_box("This is a test");
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: src/ui/draw_blocks/help.rs
|
source: src/ui/draw_blocks/help.rs
|
||||||
|
assertion_line: 456
|
||||||
expression: setup.terminal.backend()
|
expression: setup.terminal.backend()
|
||||||
---
|
---
|
||||||
" "
|
" "
|
||||||
@@ -26,6 +27,8 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( F1 ) or ( / ) enter filter mode │ "
|
" │ ( F1 ) or ( / ) enter filter mode │ "
|
||||||
" │ ( 0 ) stop sort │ "
|
" │ ( 0 ) stop sort │ "
|
||||||
" │ ( 1 - 9 ) sort by header - or click header │ "
|
" │ ( 1 - 9 ) sort by header - or click header │ "
|
||||||
|
" │ ( - = ) change log section height │ "
|
||||||
|
" │ ( \ ) toggle log section visibility │ "
|
||||||
" │ ( esc ) close dialog │ "
|
" │ ( esc ) close dialog │ "
|
||||||
" │ ( q ) quit at any time │ "
|
" │ ( q ) quit at any time │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
|
|||||||
+2
@@ -26,6 +26,8 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( F1 ) or ( / ) enter filter mode │ "
|
" │ ( F1 ) or ( / ) enter filter mode │ "
|
||||||
" │ ( 0 ) stop sort │ "
|
" │ ( 0 ) stop sort │ "
|
||||||
" │ ( 1 - 9 ) sort by header - or click header │ "
|
" │ ( 1 - 9 ) sort by header - or click header │ "
|
||||||
|
" │ ( - = ) change log section height │ "
|
||||||
|
" │ ( \ ) toggle log section visibility │ "
|
||||||
" │ ( esc ) close dialog │ "
|
" │ ( esc ) close dialog │ "
|
||||||
" │ ( q ) quit at any time │ "
|
" │ ( q ) quit at any time │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,6 @@
|
|||||||
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 │ "
|
||||||
@@ -40,12 +39,13 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( , ) sort containers by image │ "
|
" │ ( , ) sort containers by image │ "
|
||||||
" │ ( . ) sort containers by rx │ "
|
" │ ( . ) sort containers by rx │ "
|
||||||
" │ ( Insert ) sort containers by tx │ "
|
" │ ( Insert ) sort containers by tx │ "
|
||||||
|
" │ ( z ) decrease log section height │ "
|
||||||
|
" │ ( x ) increase log section height │ "
|
||||||
|
" │ ( W ) toggle log section visibility │ "
|
||||||
" │ ( a ) close dialog │ "
|
" │ ( a ) close dialog │ "
|
||||||
" │ ( k ) quit at any time │ "
|
" │ ( k ) quit at any time │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ │ "
|
|
||||||
" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
" "
|
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,6 @@
|
|||||||
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 │ "
|
||||||
@@ -40,12 +39,13 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( , ) or ( \ ) sort containers by image │ "
|
" │ ( , ) or ( \ ) sort containers by image │ "
|
||||||
" │ ( . ) or ( ] ) sort containers by rx │ "
|
" │ ( . ) or ( ] ) sort containers by rx │ "
|
||||||
" │ ( Insert ) or ( Back Tab ) sort containers by tx │ "
|
" │ ( Insert ) or ( Back Tab ) sort containers by tx │ "
|
||||||
|
" │ ( A ) or ( Z ) decrease log section height │ "
|
||||||
|
" │ ( B ) or ( X ) increase log section height │ "
|
||||||
|
" │ ( C ) or ( W ) toggle log section visibility │ "
|
||||||
" │ ( a ) or ( b ) close dialog │ "
|
" │ ( a ) or ( b ) close dialog │ "
|
||||||
" │ ( k ) or ( l ) quit at any time │ "
|
" │ ( k ) or ( l ) quit at any time │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ │ "
|
|
||||||
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
" "
|
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,6 @@
|
|||||||
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 │ "
|
||||||
@@ -40,12 +39,13 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( , ) sort containers by image │ "
|
" │ ( , ) sort containers by image │ "
|
||||||
" │ ( . ) or ( ] ) sort containers by rx │ "
|
" │ ( . ) or ( ] ) sort containers by rx │ "
|
||||||
" │ ( Insert ) sort containers by tx │ "
|
" │ ( Insert ) sort containers by tx │ "
|
||||||
|
" │ ( A ) or ( Z ) decrease log section height │ "
|
||||||
|
" │ ( B ) or ( X ) increase log section height │ "
|
||||||
|
" │ ( C ) or ( W ) toggle log section visibility │ "
|
||||||
" │ ( a ) or ( b ) close dialog │ "
|
" │ ( a ) or ( b ) close dialog │ "
|
||||||
" │ ( k ) quit at any time │ "
|
" │ ( k ) quit at any time │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ │ "
|
|
||||||
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
" "
|
|
||||||
|
|||||||
+2
@@ -28,6 +28,8 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( F1 ) or ( / ) enter filter mode │ "
|
" │ ( F1 ) or ( / ) enter filter mode │ "
|
||||||
" │ ( 0 ) stop sort │ "
|
" │ ( 0 ) stop sort │ "
|
||||||
" │ ( 1 - 9 ) sort by header - or click header │ "
|
" │ ( 1 - 9 ) sort by header - or click header │ "
|
||||||
|
" │ ( - = ) change log section height │ "
|
||||||
|
" │ ( \ ) toggle log section visibility │ "
|
||||||
" │ ( esc ) close dialog │ "
|
" │ ( esc ) close dialog │ "
|
||||||
" │ ( q ) quit at any time │ "
|
" │ ( q ) quit at any time │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
|
|||||||
+5
-5
@@ -8,8 +8,6 @@ expression: setup.terminal.backend()
|
|||||||
"│ 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_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
"│ ││ delete │"
|
"│ ││ delete │"
|
||||||
"│ ││ │"
|
|
||||||
"│ ││ │"
|
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
||||||
"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||||
"│ line 1 │"
|
"│ line 1 │"
|
||||||
@@ -25,10 +23,12 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
|
"│ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── 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│"
|
||||||
|
"│ │• •• ││ │• •• ││ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │ ││ │ ││ │"
|
||||||
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/mod.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help "
|
||||||
|
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
||||||
|
"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")]
|
||||||
|
"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
|
"│ ││ delete │"
|
||||||
|
"│ ││ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
||||||
|
"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||||
|
"│ line 1 │"
|
||||||
|
"│ line 2 │"
|
||||||
|
"│▶ line 3 │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ ╭──────────────────────── Confirm Delete ────────────────────────╮ │"
|
||||||
|
"│ │ │ │"
|
||||||
|
"│ │ Are you sure you want to delete container: container_1 │ │"
|
||||||
|
"│ │ │ │"
|
||||||
|
"│ │ ╭─────────────────────╮ ╭─────────────────────╮ │ │"
|
||||||
|
"│ │ │ ( n ) no │ │ ( y ) yes │ │ │"
|
||||||
|
"│ │ ╰─────────────────────╯ ╰─────────────────────╯ │ │"
|
||||||
|
"│ ╰────────────────────────────────────────────────────────────────╯ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
|
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
||||||
|
"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│"
|
||||||
|
"│ │ • • ││ │ •• ││ 8001 │"
|
||||||
|
"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│"
|
||||||
|
"│ │ • • ││ │ • • ││ │"
|
||||||
|
"│ │ •• • • ││ │ •• • • ││ │"
|
||||||
|
"│ │• •• ││ │• •• ││ │"
|
||||||
|
"│ │• • ││ │• • ││ │"
|
||||||
|
"│ │ ││ │ ││ │"
|
||||||
|
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/mod.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help "
|
||||||
|
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
||||||
|
"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")]
|
||||||
|
"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
|
"│ ││ delete │"
|
||||||
|
"│ ││ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
||||||
|
"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||||
|
"│ line 1 │"
|
||||||
|
"│ line 2 │"
|
||||||
|
"│▶ line 3 │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ ╭──────────── Error ─────────────╮ │"
|
||||||
|
"│ │ │ │"
|
||||||
|
"│ │ Unable to pause container │ │"
|
||||||
|
"│ │ │ │"
|
||||||
|
"│ │ ( c ) clear error │ │"
|
||||||
|
"│ │ │ │"
|
||||||
|
"│ │ ( q ) quit oxker │ │"
|
||||||
|
"│ │ │ │"
|
||||||
|
"│ │ │ │"
|
||||||
|
"│ ╰────────────────────────────────╯ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
|
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
||||||
|
"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│"
|
||||||
|
"│ │ • • ││ │ •• ││ 8001 │"
|
||||||
|
"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│"
|
||||||
|
"│ │ • • ││ │ • • ││ │"
|
||||||
|
"│ │ •• • • ││ │ •• • • ││ │"
|
||||||
|
"│ │• •• ││ │• •• ││ │"
|
||||||
|
"│ │• • ││ │• • ││ │"
|
||||||
|
"│ │ ││ │ ││ │"
|
||||||
|
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/mod.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) exit help "
|
||||||
|
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
||||||
|
"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")]
|
||||||
|
"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
|
"│ container_3 ✓ running Up 3 ho╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ ││ stop │"
|
||||||
|
"│ │ │ ││ delete │"
|
||||||
|
"│ │ 88 │ ││ │"
|
||||||
|
"╰────────────────────────────────────│ 88 │────────────────────╯╰──────────────╯"
|
||||||
|
"╭ Logs 3/3 - container_1 - image_1 ──│ 88 │────────────────────────────────────╮"
|
||||||
|
"│ line 1 │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ │"
|
||||||
|
"│ line 2 │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ │"
|
||||||
|
"│▶ line 3 │ 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 ( PgUp PgDown ) or ( Home End ) change selected line │ │"
|
||||||
|
"│ │ ( enter ) send docker container command │ │"
|
||||||
|
"│ │ ( e ) exec into a container │ │"
|
||||||
|
"│ │ ( 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 │ │"
|
||||||
|
"│ │ ( 0 ) stop sort │ │"
|
||||||
|
"│ │ ( 1 - 9 ) sort by header - or click header │ │"
|
||||||
|
"│ │ ( - = ) change log section height │ │"
|
||||||
|
"│ │ ( \ ) toggle log section visibility │ │"
|
||||||
|
"╰────────────────────────────────────│ ( esc ) close dialog │────────────────────────────────────╯"
|
||||||
|
"╭───────────────────────── cpu 03.00%│ ( q ) quit at any time │──────╮╭────────── ports ───────────╮"
|
||||||
|
"│10.00%│ •• │ │ ││ ip private public│"
|
||||||
|
"│ │ • • │ currently an early work in progress, all and any input appreciated │ ││ 8001 │"
|
||||||
|
"│ │ •• • │ https://github.com/mrjackwills/oxker │ ││127.0.0.1 8003 8003│"
|
||||||
|
"│ │ • • │ │ ││ │"
|
||||||
|
"│ │ •• • • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │"
|
||||||
|
"│ │• •• ││ │• •• ││ │"
|
||||||
|
"│ │• • ││ │• • ││ │"
|
||||||
|
"│ │ ││ │ ││ │"
|
||||||
|
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/mod.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help "
|
||||||
|
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
||||||
|
"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")]
|
||||||
|
"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
|
"│ ││ delete │"
|
||||||
|
"│ ││ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
||||||
|
"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||||
|
"│ line 1 │"
|
||||||
|
"│ line 2 │"
|
||||||
|
"│▶ line 3 │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"│ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
|
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
||||||
|
"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│"
|
||||||
|
"│ │ • • ││ │ •• ││ 8001 │"
|
||||||
|
"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│"
|
||||||
|
"│ │ • • ││ │ • • ││ │"
|
||||||
|
"│ │ •• • • ││ │ •• • • ││ │"
|
||||||
|
"│ │• •• ││ │• •• ││ "
|
||||||
|
"│ │• • ││ │• • ││ This is a test "
|
||||||
|
"│ │ ││ │ ││ "
|
||||||
|
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰─────── "
|
||||||
+5
-5
@@ -8,8 +8,6 @@ expression: setup.terminal.backend()
|
|||||||
"│ 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_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
"│ ││ delete │"
|
"│ ││ delete │"
|
||||||
"│ ││ │"
|
|
||||||
"│ ││ │"
|
|
||||||
"╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰─────────────────╯"
|
"╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰─────────────────╯"
|
||||||
"╭ Logs 3/3 - a_long_container_name_for_the_purposes_of_this_test - a_long_image_name_for_the_purposes_of_this_test ──────────────────────────────────────────────────────────────────────────╮"
|
"╭ Logs 3/3 - a_long_container_name_for_the_purposes_of_this_test - a_long_image_name_for_the_purposes_of_this_test ──────────────────────────────────────────────────────────────────────────╮"
|
||||||
"│ line 1 │"
|
"│ line 1 │"
|
||||||
@@ -25,10 +23,12 @@ expression: setup.terminal.backend()
|
|||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
"│ │"
|
"│ │"
|
||||||
|
"│ │"
|
||||||
"╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
"╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
"╭───────────────────────────────── cpu 03.00% ─────────────────────────────────╮╭────────────────────────────── memory 30.00 kB ───────────────────────────────╮╭────────── ports ───────────╮"
|
"╭───────────────────────────────── cpu 03.00% ─────────────────────────────────╮╭────────────────────────────── memory 30.00 kB ───────────────────────────────╮╭────────── 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│"
|
||||||
|
"│ │•• ••• ││ │•• •• ││ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │ ││ │ ││ │"
|
||||||
"╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
"╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/mod.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help "
|
||||||
|
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
||||||
|
"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")]
|
||||||
|
"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
|
"│ ││ delete │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
||||||
|
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
||||||
|
"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│"
|
||||||
|
"│ │ •• • ││ │ •• • ││ 8001 │"
|
||||||
|
"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│"
|
||||||
|
"│ │• •• ││ │• •• ││ │"
|
||||||
|
"│ │ ││ │ ││ │"
|
||||||
|
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/mod.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help "
|
||||||
|
"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮"
|
||||||
|
"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")]
|
||||||
|
"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │"
|
||||||
|
"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │"
|
||||||
|
"│ ││ delete │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"│ ││ │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯"
|
||||||
|
"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||||
|
"│ line 2 │"
|
||||||
|
"│▶ line 3 │"
|
||||||
|
"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||||
|
"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮"
|
||||||
|
"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│"
|
||||||
|
"│ │ •• • ││ │ •• • ││ 8001 │"
|
||||||
|
"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│"
|
||||||
|
"│ │• •• ││ │• •• ││ │"
|
||||||
|
"│ │ ││ │ ││ │"
|
||||||
|
"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯"
|
||||||
+82
-22
@@ -9,11 +9,11 @@ use tokio::task::JoinHandle;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{ContainerId, Header},
|
app_data::{AppData, ContainerId, Header},
|
||||||
exec::ExecMode,
|
exec::ExecMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Redraw;
|
use super::Rerender;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
|
||||||
pub enum SelectablePanel {
|
pub enum SelectablePanel {
|
||||||
@@ -175,7 +175,7 @@ pub enum Status {
|
|||||||
/// 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 {
|
||||||
delete_container: Option<ContainerId>,
|
delete_container_id: Option<ContainerId>,
|
||||||
exec_mode: Option<ExecMode>,
|
exec_mode: Option<ExecMode>,
|
||||||
intersect_delete: HashMap<DeleteButton, Rect>,
|
intersect_delete: HashMap<DeleteButton, Rect>,
|
||||||
intersect_heading: HashMap<Header, Rect>,
|
intersect_heading: HashMap<Header, Rect>,
|
||||||
@@ -184,15 +184,17 @@ pub struct GuiState {
|
|||||||
loading_handle: Option<JoinHandle<()>>,
|
loading_handle: Option<JoinHandle<()>>,
|
||||||
loading_index: u8,
|
loading_index: u8,
|
||||||
loading_set: HashSet<Uuid>,
|
loading_set: HashSet<Uuid>,
|
||||||
redraw: Arc<Redraw>,
|
log_height: u16,
|
||||||
|
rerender: Arc<Rerender>,
|
||||||
selected_panel: SelectablePanel,
|
selected_panel: SelectablePanel,
|
||||||
|
show_logs: bool,
|
||||||
status: HashSet<Status>,
|
status: HashSet<Status>,
|
||||||
pub info_box_text: Option<(String, Instant)>,
|
pub info_box_text: Option<(String, Instant)>,
|
||||||
}
|
}
|
||||||
impl GuiState {
|
impl GuiState {
|
||||||
pub fn new(redraw: &Arc<Redraw>) -> Self {
|
pub fn new(redraw: &Arc<Rerender>, show_logs: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
delete_container: None,
|
delete_container_id: None,
|
||||||
exec_mode: None,
|
exec_mode: None,
|
||||||
info_box_text: None,
|
info_box_text: None,
|
||||||
intersect_delete: HashMap::new(),
|
intersect_delete: HashMap::new(),
|
||||||
@@ -202,11 +204,57 @@ impl GuiState {
|
|||||||
loading_handle: None,
|
loading_handle: None,
|
||||||
loading_index: 0,
|
loading_index: 0,
|
||||||
loading_set: HashSet::new(),
|
loading_set: HashSet::new(),
|
||||||
redraw: Arc::clone(redraw),
|
log_height: 75,
|
||||||
|
rerender: Arc::clone(redraw),
|
||||||
selected_panel: SelectablePanel::default(),
|
selected_panel: SelectablePanel::default(),
|
||||||
|
show_logs,
|
||||||
status: HashSet::new(),
|
status: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Increase the height of the log panel, then rerender
|
||||||
|
pub fn log_height_increase(&mut self) {
|
||||||
|
if self.show_logs && self.log_height <= 75 {
|
||||||
|
self.log_height = self.log_height.saturating_add(5);
|
||||||
|
self.rerender.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduce the height of the logs panel, then rerender
|
||||||
|
/// Unselect logs panel if currently selected
|
||||||
|
pub fn log_height_decrease(&mut self) {
|
||||||
|
if self.show_logs {
|
||||||
|
self.log_height = self.log_height.saturating_sub(5);
|
||||||
|
if self.log_height == 0 && self.selected_panel == SelectablePanel::Logs {
|
||||||
|
self.show_logs = false;
|
||||||
|
self.selected_panel = SelectablePanel::Containers;
|
||||||
|
}
|
||||||
|
self.rerender.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_show_logs(&self) -> bool {
|
||||||
|
self.show_logs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_show_logs(&mut self) {
|
||||||
|
self.show_logs = !self.show_logs;
|
||||||
|
if !self.show_logs && self.selected_panel == SelectablePanel::Logs {
|
||||||
|
self.selected_panel = SelectablePanel::Containers;
|
||||||
|
}
|
||||||
|
self.rerender.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the log_height to zero, for now only used by tests
|
||||||
|
#[cfg(test)]
|
||||||
|
pub const fn log_height_zero(&mut self) {
|
||||||
|
self.log_height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the log height, *should* be a u8 between 0 and 80, essentially a percentage
|
||||||
|
pub const fn get_log_height(&self) -> u16 {
|
||||||
|
self.log_height
|
||||||
|
}
|
||||||
|
|
||||||
/// Clear panels hash map, so on resize can fix the sizes for mouse clicks
|
/// Clear panels hash map, so on resize can fix the sizes for mouse clicks
|
||||||
pub fn clear_area_map(&mut self) {
|
pub fn clear_area_map(&mut self) {
|
||||||
self.intersect_panel.clear();
|
self.intersect_panel.clear();
|
||||||
@@ -227,7 +275,7 @@ impl GuiState {
|
|||||||
.first()
|
.first()
|
||||||
{
|
{
|
||||||
self.selected_panel = *data.0;
|
self.selected_panel = *data.0;
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +335,7 @@ impl GuiState {
|
|||||||
|
|
||||||
/// Check if an ContainerId is set in the delete_container field
|
/// Check if an ContainerId is set in the delete_container field
|
||||||
pub fn get_delete_container(&self) -> Option<ContainerId> {
|
pub fn get_delete_container(&self) -> Option<ContainerId> {
|
||||||
self.delete_container.clone()
|
self.delete_container_id.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set either a ContainerId, or None, to the delete_container field
|
/// Set either a ContainerId, or None, to the delete_container field
|
||||||
@@ -299,8 +347,8 @@ impl GuiState {
|
|||||||
self.intersect_delete.clear();
|
self.intersect_delete.clear();
|
||||||
self.status_del(Status::DeleteConfirm);
|
self.status_del(Status::DeleteConfirm);
|
||||||
}
|
}
|
||||||
self.delete_container = id;
|
self.delete_container_id = id;
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a copy of the Status HashSet
|
/// Return a copy of the Status HashSet
|
||||||
@@ -321,7 +369,7 @@ impl GuiState {
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inset the ExecMode into self, and set the Status as exec
|
/// Inset the ExecMode into self, and set the Status as exec
|
||||||
@@ -330,7 +378,7 @@ impl GuiState {
|
|||||||
pub fn set_exec_mode(&mut self, mode: ExecMode) {
|
pub fn set_exec_mode(&mut self, mode: ExecMode) {
|
||||||
self.exec_mode = Some(mode);
|
self.exec_mode = Some(mode);
|
||||||
self.status.insert(Status::Exec);
|
self.status.insert(Status::Exec);
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_exec_mode(&self) -> Option<ExecMode> {
|
pub fn get_exec_mode(&self) -> Option<ExecMode> {
|
||||||
@@ -342,20 +390,32 @@ impl GuiState {
|
|||||||
pub fn status_push(&mut self, status: Status) {
|
pub fn status_push(&mut self, status: Status) {
|
||||||
if status != Status::Exec {
|
if status != Status::Exec {
|
||||||
self.status.insert(status);
|
self.status.insert(status);
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change to next selectable panel
|
/// Change to next selectable panel
|
||||||
pub fn next_panel(&mut self) {
|
pub fn selectable_panel_next(&mut self, app_data: &Arc<Mutex<AppData>>) {
|
||||||
self.selected_panel = self.selected_panel.next();
|
self.selected_panel = self.selected_panel.next();
|
||||||
self.redraw.set_true();
|
if (app_data.lock().get_container_len() == 0
|
||||||
|
&& self.get_selected_panel() == SelectablePanel::Commands)
|
||||||
|
|| (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs)
|
||||||
|
{
|
||||||
|
self.selected_panel = self.selected_panel.next();
|
||||||
|
}
|
||||||
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change to previous selectable panel
|
/// Change to previous selectable panel
|
||||||
pub fn previous_panel(&mut self) {
|
pub fn selectable_panel_previous(&mut self, app_data: &Arc<Mutex<AppData>>) {
|
||||||
self.selected_panel = self.selected_panel.prev();
|
self.selected_panel = self.selected_panel.prev();
|
||||||
self.redraw.set_true();
|
if (app_data.lock().get_container_len() == 0
|
||||||
|
&& self.get_selected_panel() == SelectablePanel::Commands)
|
||||||
|
|| (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs)
|
||||||
|
{
|
||||||
|
self.selected_panel = self.selected_panel.prev();
|
||||||
|
}
|
||||||
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array
|
/// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array
|
||||||
@@ -366,7 +426,7 @@ impl GuiState {
|
|||||||
self.loading_index += 1;
|
self.loading_index += 1;
|
||||||
}
|
}
|
||||||
self.loading_set.insert(uuid);
|
self.loading_set.insert(uuid);
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_loading(&self) -> bool {
|
pub fn is_loading(&self) -> bool {
|
||||||
@@ -399,7 +459,7 @@ impl GuiState {
|
|||||||
/// Stop the loading_spin function, and reset gui loading status
|
/// Stop the loading_spin function, and reset gui loading status
|
||||||
pub fn stop_loading_animation(&mut self, loading_uuid: Uuid) {
|
pub fn stop_loading_animation(&mut self, loading_uuid: Uuid) {
|
||||||
self.loading_set.remove(&loading_uuid);
|
self.loading_set.remove(&loading_uuid);
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
if self.loading_set.is_empty() {
|
if self.loading_set.is_empty() {
|
||||||
self.loading_index = 0;
|
self.loading_index = 0;
|
||||||
if let Some(h) = &self.loading_handle {
|
if let Some(h) = &self.loading_handle {
|
||||||
@@ -412,12 +472,12 @@ impl GuiState {
|
|||||||
/// Set info box content
|
/// Set info box content
|
||||||
pub fn set_info_box(&mut self, text: &str) {
|
pub fn set_info_box(&mut self, text: &str) {
|
||||||
self.info_box_text = Some((text.to_owned(), std::time::Instant::now()));
|
self.info_box_text = Some((text.to_owned(), std::time::Instant::now()));
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove info box content
|
/// Remove info box content
|
||||||
pub fn reset_info_box(&mut self) {
|
pub fn reset_info_box(&mut self) {
|
||||||
self.info_box_text = None;
|
self.info_box_text = None;
|
||||||
self.redraw.set_true();
|
self.rerender.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-56
@@ -24,7 +24,7 @@ mod color_match;
|
|||||||
mod draw_blocks;
|
mod draw_blocks;
|
||||||
mod gui_state;
|
mod gui_state;
|
||||||
mod redraw;
|
mod redraw;
|
||||||
pub use redraw::Redraw;
|
pub use redraw::Rerender;
|
||||||
|
|
||||||
pub use self::color_match::*;
|
pub use self::color_match::*;
|
||||||
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
||||||
@@ -50,7 +50,7 @@ pub struct Ui {
|
|||||||
input_tx: Sender<InputMessages>,
|
input_tx: Sender<InputMessages>,
|
||||||
is_running: Arc<AtomicBool>,
|
is_running: Arc<AtomicBool>,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
redraw: Arc<Redraw>,
|
redraw: Arc<Rerender>,
|
||||||
terminal: Terminal<CrosstermBackend<Stdout>>,
|
terminal: Terminal<CrosstermBackend<Stdout>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ impl Ui {
|
|||||||
gui_state: Arc<Mutex<GuiState>>,
|
gui_state: Arc<Mutex<GuiState>>,
|
||||||
input_tx: Sender<InputMessages>,
|
input_tx: Sender<InputMessages>,
|
||||||
is_running: Arc<AtomicBool>,
|
is_running: Arc<AtomicBool>,
|
||||||
redraw: Arc<Redraw>,
|
redraw: Arc<Rerender>,
|
||||||
) {
|
) {
|
||||||
match Self::setup_terminal() {
|
match Self::setup_terminal() {
|
||||||
Ok(mut terminal) => {
|
Ok(mut terminal) => {
|
||||||
@@ -245,6 +245,8 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
} else if let Event::Resize(_, _) = event {
|
} else if let Event::Resize(_, _) = event {
|
||||||
self.gui_state.lock().clear_area_map();
|
self.gui_state.lock().clear_area_map();
|
||||||
|
// self.gui_state.lock().set_window_height(row);
|
||||||
|
|
||||||
self.terminal.autoresize().ok();
|
self.terminal.autoresize().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,6 +269,7 @@ impl Ui {
|
|||||||
|
|
||||||
/// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here
|
/// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct FrameData {
|
pub struct FrameData {
|
||||||
chart_data: Option<(CpuTuple, MemTuple)>,
|
chart_data: Option<(CpuTuple, MemTuple)>,
|
||||||
color_logs: bool,
|
color_logs: bool,
|
||||||
@@ -276,8 +279,10 @@ pub struct FrameData {
|
|||||||
filter_by: FilterBy,
|
filter_by: FilterBy,
|
||||||
filter_term: Option<String>,
|
filter_term: Option<String>,
|
||||||
has_containers: bool,
|
has_containers: bool,
|
||||||
|
// container_section_height: u16,
|
||||||
|
log_height: u16,
|
||||||
|
show_logs: bool,
|
||||||
has_error: Option<AppError>,
|
has_error: Option<AppError>,
|
||||||
height: u16,
|
|
||||||
info_text: Option<(String, Instant)>,
|
info_text: Option<(String, Instant)>,
|
||||||
is_loading: bool,
|
is_loading: bool,
|
||||||
loading_icon: String,
|
loading_icon: String,
|
||||||
@@ -293,14 +298,6 @@ impl From<&Ui> for FrameData {
|
|||||||
fn from(ui: &Ui) -> Self {
|
fn from(ui: &Ui) -> Self {
|
||||||
let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock());
|
let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock());
|
||||||
|
|
||||||
// set max height for container section, needs +5 to deal with docker commands list and borders
|
|
||||||
let height = app_data.get_container_len();
|
|
||||||
let height = if height < 12 {
|
|
||||||
u16::try_from(height + 5).unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
12
|
|
||||||
};
|
|
||||||
|
|
||||||
let (filter_by, filter_term) = app_data.get_filter();
|
let (filter_by, filter_term) = app_data.get_filter();
|
||||||
Self {
|
Self {
|
||||||
chart_data: app_data.get_chart_data(),
|
chart_data: app_data.get_chart_data(),
|
||||||
@@ -312,10 +309,11 @@ impl From<&Ui> for FrameData {
|
|||||||
filter_term: filter_term.cloned(),
|
filter_term: filter_term.cloned(),
|
||||||
has_containers: app_data.get_container_len() > 0,
|
has_containers: app_data.get_container_len() > 0,
|
||||||
has_error: app_data.get_error(),
|
has_error: app_data.get_error(),
|
||||||
height,
|
|
||||||
info_text: gui_data.info_box_text.clone(),
|
info_text: gui_data.info_box_text.clone(),
|
||||||
is_loading: gui_data.is_loading(),
|
is_loading: gui_data.is_loading(),
|
||||||
|
show_logs: gui_data.get_show_logs(),
|
||||||
loading_icon: gui_data.get_loading().to_string(),
|
loading_icon: gui_data.get_loading().to_string(),
|
||||||
|
log_height: gui_data.get_log_height(),
|
||||||
log_title: app_data.get_log_title(),
|
log_title: app_data.get_log_title(),
|
||||||
port_max_lens: app_data.get_longest_port(),
|
port_max_lens: app_data.get_longest_port(),
|
||||||
ports: app_data.get_selected_ports(),
|
ports: app_data.get_selected_ports(),
|
||||||
@@ -335,57 +333,63 @@ fn draw_frame(
|
|||||||
fd: &FrameData,
|
fd: &FrameData,
|
||||||
gui_state: &Arc<Mutex<GuiState>>,
|
gui_state: &Arc<Mutex<GuiState>>,
|
||||||
) {
|
) {
|
||||||
let whole_constraints = if fd.status.contains(&Status::Filter) {
|
|
||||||
vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)]
|
|
||||||
} else {
|
|
||||||
vec![Constraint::Max(1), Constraint::Min(1)]
|
|
||||||
};
|
|
||||||
|
|
||||||
let whole_layout = Layout::default()
|
let whole_layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(whole_constraints)
|
.constraints(if fd.status.contains(&Status::Filter) {
|
||||||
|
vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)]
|
||||||
|
} else {
|
||||||
|
vec![Constraint::Max(1), Constraint::Min(1)]
|
||||||
|
})
|
||||||
.split(f.area());
|
.split(f.area());
|
||||||
|
|
||||||
// Split into 3, containers+controls, logs, then graphs
|
|
||||||
let upper_main = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Max(fd.height), Constraint::Min(1)].as_ref())
|
|
||||||
.split(whole_layout[1]);
|
|
||||||
|
|
||||||
let top_split = if fd.has_containers {
|
|
||||||
vec![Constraint::Percentage(90), Constraint::Percentage(10)]
|
|
||||||
} else {
|
|
||||||
vec![Constraint::Percentage(100)]
|
|
||||||
};
|
|
||||||
// Containers + docker commands
|
|
||||||
let top_panel = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints(top_split)
|
|
||||||
.split(upper_main[0]);
|
|
||||||
|
|
||||||
let lower_split = if fd.has_containers {
|
|
||||||
vec![Constraint::Percentage(70), Constraint::Percentage(30)]
|
|
||||||
} else {
|
|
||||||
vec![Constraint::Percentage(100)]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Split into 2, logs and charts
|
|
||||||
let lower_main = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints(lower_split)
|
|
||||||
.split(upper_main[1]);
|
|
||||||
|
|
||||||
draw_blocks::containers::draw(app_data, top_panel[0], colors, f, fd, gui_state);
|
|
||||||
|
|
||||||
draw_blocks::logs::draw(app_data, lower_main[0], colors, f, fd, gui_state);
|
|
||||||
|
|
||||||
draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap);
|
draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap);
|
||||||
|
|
||||||
// Draw filter bar
|
// If required, draw filter bar
|
||||||
if let Some(rect) = whole_layout.get(2) {
|
if let Some(rect) = whole_layout.get(2) {
|
||||||
draw_blocks::filter::draw(*rect, colors, f, fd);
|
draw_blocks::filter::draw(*rect, colors, f, fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let upper_main = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(if fd.has_containers {
|
||||||
|
vec![Constraint::Percentage(75), Constraint::Percentage(25)]
|
||||||
|
} else {
|
||||||
|
vec![Constraint::Percentage(100), Constraint::Percentage(0)]
|
||||||
|
})
|
||||||
|
.split(whole_layout[1]);
|
||||||
|
|
||||||
|
let containers_logs_section = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(if fd.show_logs {
|
||||||
|
vec![Constraint::Min(6), Constraint::Percentage(fd.log_height)]
|
||||||
|
} else {
|
||||||
|
vec![Constraint::Percentage(100)]
|
||||||
|
})
|
||||||
|
.split(upper_main[0]);
|
||||||
|
|
||||||
|
// Containers + docker commands
|
||||||
|
let containers_commands = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(if fd.has_containers {
|
||||||
|
vec![Constraint::Percentage(90), Constraint::Percentage(10)]
|
||||||
|
} else {
|
||||||
|
vec![Constraint::Percentage(100)]
|
||||||
|
})
|
||||||
|
.split(containers_logs_section[0]);
|
||||||
|
|
||||||
|
draw_blocks::containers::draw(app_data, containers_commands[0], colors, f, fd, gui_state);
|
||||||
|
|
||||||
|
if fd.show_logs {
|
||||||
|
draw_blocks::logs::draw(
|
||||||
|
app_data,
|
||||||
|
containers_logs_section[1],
|
||||||
|
colors,
|
||||||
|
f,
|
||||||
|
fd,
|
||||||
|
gui_state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(id) = fd.delete_confirm.as_ref() {
|
if let Some(id) = fd.delete_confirm.as_ref() {
|
||||||
app_data.lock().get_container_name_by_id(id).map_or_else(
|
app_data.lock().get_container_name_by_id(id).map_or_else(
|
||||||
|| {
|
|| {
|
||||||
@@ -400,7 +404,7 @@ fn draw_frame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only draw commands + charts if there are containers
|
// only draw commands + charts if there are containers
|
||||||
if let Some(rect) = top_panel.get(1) {
|
if let Some(rect) = containers_commands.get(1) {
|
||||||
draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state);
|
draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state);
|
||||||
|
|
||||||
// Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border)
|
// Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border)
|
||||||
@@ -411,7 +415,7 @@ fn draw_frame(
|
|||||||
let lower = Layout::default()
|
let lower = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Min(1), Constraint::Max(ports_len)])
|
.constraints([Constraint::Min(1), Constraint::Max(ports_len)])
|
||||||
.split(lower_main[1]);
|
.split(upper_main[1]);
|
||||||
|
|
||||||
draw_blocks::charts::draw(lower[0], colors, f, fd);
|
draw_blocks::charts::draw(lower[0], colors, f, fd);
|
||||||
draw_blocks::ports::draw(lower[1], colors, f, fd);
|
draw_blocks::ports::draw(lower[1], colors, f, fd);
|
||||||
|
|||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Redraw(AtomicBool);
|
pub struct Rerender(AtomicBool);
|
||||||
|
|
||||||
impl Redraw {
|
impl Rerender {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self(AtomicBool::new(true))
|
Self(AtomicBool::new(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_true(&self) {
|
pub fn update(&self) {
|
||||||
self.0.store(true, Ordering::SeqCst);
|
self.0.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user