diff --git a/.github/release-body.md b/.github/release-body.md index c3b48a8..f17bc3a 100644 --- a/.github/release-body.md +++ b/.github/release-body.md @@ -1,6 +1,16 @@ -### 2025-06-19 +### 2025-08-21 -### Reverts -+ Bollard update rolled back, closes #66, [aac9c6b598ce6c23b14f5a8b0116e662b18074d2] +### Chores ++ Dependencies updated, [ced885e0128b6d5d3a3c7cb97d7e53bc2da64893], [f9b40ea03d0e70e235c28646ff3f9ebb468a904d] ++ Rust 1.89 linting, [79d19ceeb81ae60bc5562683e405d6e74e6f2578] ++ GitHub workflow updated, [08384200558fa1b9d378ea62ea832708caebaa91], [6573af1ed7d382a81c1305397e904066bb8395a8] + +### Features ++ Horizontally scroll across logs. By default use `←` & `→` keys to traverse horizontally across the lines when logs panel selected. Updated `config.toml` with `log_scroll_forward` and `log_scroll_back` [c190f0206cc55b8e45b8373f9be954e828c18b3b], [8939ac0345326633e794cc10a981a1f3c5c07549] ++ Force clear screen & redraw of UI. By default uses `f` key, `config.toml` updated with `force_redraw` [50edbc0cc09db864835fe81a03cba8eadafe548b] ++ Increase scroll speed using the `ctrl` key in conjuction with a scroll key, `config.toml` updated with `scroll_modifier`. The next release will remove `scroll_down_many` & `scroll_down_up` keys, [c5bbffdb5f9e800951e4060aa6aee8e00db589aa] + +### Refactors ++ remove macos cfg none-const functions, Zigbuild now uses Rust 1.87.0, [eb686e2c952e04da74b3e12c0bfa015ec4615e1d] see CHANGELOG.md for more details diff --git a/.github/screenshot_01.png b/.github/screenshot_01.png index 567dd3a..f39df48 100644 Binary files a/.github/screenshot_01.png and b/.github/screenshot_01.png differ diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml index c3e89ca..7b03691 100644 --- a/.github/workflows/create_release_and_build.yml +++ b/.github/workflows/create_release_and_build.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Install stable rust, and associated tools - name: install rust @@ -82,10 +82,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup | Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 - name: Update Release uses: ncipollo/release-action@v1 @@ -107,7 +107,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Login to GitHub Container Registry uses: docker/login-action@v3 @@ -121,26 +121,25 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Write release version to env - run: | - CURRENT_SEMVER=${GITHUB_REF_NAME#v} - echo "CURRENT_SEMVER=$CURRENT_SEMVER" >> $GITHUB_ENV - - - uses: docker/setup-buildx-action@v3 - id: buildx + - name: Build and push Docker image + uses: docker/build-push-action@v6 with: - install: true - - name: Build for Dockerhub & ghcr.io - run: | - docker build --platform linux/arm/v6,linux/arm64,linux/amd64 \ - -t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:latest \ - -t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:${{env.CURRENT_SEMVER}} \ - -t ghcr.io/${{ github.repository_owner }}/${{ github.ref_name }}:latest \ - -t ghcr.io/${{ github.repository_owner }}/${{ github.ref_name }}:${{env.CURRENT_SEMVER}} \ - --provenance=false --sbom=false \ - --push \ - -f containerised/Dockerfile . + context: . + file: ./containerised/Dockerfile + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/{{ github.event.repository.name }}:latest + ${{ secrets.DOCKERHUB_USERNAME }}/{{ github.event.repository.name }}:${{env.CURRENT_SEMVER}} + ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest + ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.CURRENT_SEMVER }} + platforms: linux/arm/v6,linux/arm64,linux/amd64 + provenance: false + sbom: false + ######################## # Publish to crates.io # ######################## @@ -152,7 +151,7 @@ jobs: steps: - name: update rust stable run: rustup update stable - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Publish Dry Run run: cargo publish --dry-run @@ -165,7 +164,7 @@ jobs: steps: - name: update rust stable run: rustup update stable - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Publish run: cargo publish diff --git a/.gitignore b/.gitignore index 5388e4b..ee5b384 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target /releases +/binaries # Used in the zigbuild for aarch64-apple-darwin .intentionally-empty-file.o \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8a280..d17d0fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# v0.11.0 +### 2025-08-21 + +### Chores ++ Dependencies updated, [ced885e0](https://github.com/mrjackwills/oxker/commit/ced885e0128b6d5d3a3c7cb97d7e53bc2da64893), [f9b40ea0](https://github.com/mrjackwills/oxker/commit/f9b40ea03d0e70e235c28646ff3f9ebb468a904d) ++ Rust 1.89 linting, [79d19cee](https://github.com/mrjackwills/oxker/commit/79d19ceeb81ae60bc5562683e405d6e74e6f2578) ++ GitHub workflow updated, [08384200](https://github.com/mrjackwills/oxker/commit/08384200558fa1b9d378ea62ea832708caebaa91), [6573af1e](https://github.com/mrjackwills/oxker/commit/6573af1ed7d382a81c1305397e904066bb8395a8) + +### Features ++ Horizontally scroll across logs. By default use `←` & `→` keys to traverse horizontally across the lines when logs panel selected. Updated `config.toml` with `log_scroll_forward` and `log_scroll_back` [c190f020](https://github.com/mrjackwills/oxker/commit/c190f0206cc55b8e45b8373f9be954e828c18b3b), [8939ac03](https://github.com/mrjackwills/oxker/commit/8939ac0345326633e794cc10a981a1f3c5c07549) ++ Force clear screen & redraw of UI. By default uses `f` key, `config.toml` updated with `force_redraw` [50edbc0c](https://github.com/mrjackwills/oxker/commit/50edbc0cc09db864835fe81a03cba8eadafe548b) ++ Increase scroll speed using the `ctrl` key in conjuction with a scroll key, `config.toml` updated with `scroll_modifier`. The next release will remove `scroll_down_many` & `scroll_down_up` keys, [c5bbffdb](https://github.com/mrjackwills/oxker/commit/c5bbffdb5f9e800951e4060aa6aee8e00db589aa) + +### Refactors ++ remove macos cfg none-const functions, Zigbuild now uses Rust 1.87.0, [eb686e2c](https://github.com/mrjackwills/oxker/commit/eb686e2c952e04da74b3e12c0bfa015ec4615e1d) + # v0.10.5 ### 2025-06-19 @@ -11,7 +27,7 @@ + .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) - +back ### 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) diff --git a/Cargo.lock b/Cargo.lock index bc3647a..7d2cf58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -70,35 +70,41 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -123,15 +129,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" [[package]] name = "bollard" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" +checksum = "8796b390a5b4c86f9f2e8173a68c2791f4fa6b038b84e96dbc01c016d1e6722c" dependencies = [ "base64", "bollard-stubs", @@ -162,20 +168,21 @@ dependencies = [ [[package]] name = "bollard-stubs" -version = "1.47.1-rc.27.3.1" +version = "1.49.0-rc.28.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" +checksum = "2e7814991259013d5a5bee4ae28657dae0747d843cf06c40f7fc0c2894d6fa38" dependencies = [ "serde", + "serde_json", "serde_repr", "serde_with", ] [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -197,27 +204,27 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.2.27" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "chrono" @@ -234,9 +241,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", "clap_derive", @@ -244,9 +251,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -258,9 +265,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck", "proc-macro2", @@ -349,7 +356,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.0.7", + "rustix 1.0.8", "signal-hook", "signal-hook-mio", "winapi", @@ -473,9 +480,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" @@ -497,12 +504,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -519,9 +526,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -615,9 +622,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -684,19 +691,21 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -719,9 +728,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "bytes", "futures-channel", @@ -871,9 +880,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -903,12 +912,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "serde", ] @@ -931,9 +940,9 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ "darling", "indoc", @@ -942,6 +951,17 @@ dependencies = [ "syn", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1023,15 +1043,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags", "libc", @@ -1057,9 +1077,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -1083,7 +1103,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -1173,7 +1193,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "oxker" -version = "0.10.5" +version = "0.11.0" dependencies = [ "anyhow", "bollard", @@ -1228,9 +1248,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -1285,9 +1305,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1309,9 +1329,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", @@ -1359,18 +1379,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -1399,9 +1419,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" @@ -1418,22 +1438,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1453,6 +1473,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1481,9 +1513,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -1515,9 +1547,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -1536,16 +1568,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", - "schemars", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -1590,9 +1623,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -1605,9 +1638,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -1617,12 +1650,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1667,9 +1700,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1689,18 +1722,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -1759,20 +1792,22 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1788,9 +1823,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -1801,35 +1836,32 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ - "indexmap 2.9.0", - "serde", - "serde_spanned", - "toml_datetime", "winnow", ] @@ -1945,9 +1977,9 @@ checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "url" -version = "2.5.4" +version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "137a3c834eaf7139b73688502f3f1141a0337c5d8e4d9b536f9b8c796e26a7c4" dependencies = [ "form_urlencoded", "idna", @@ -1968,9 +2000,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -2147,15 +2179,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -2171,7 +2194,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -2192,10 +2215,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2304,12 +2328,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" -dependencies = [ - "memchr", -] +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" [[package]] name = "wit-bindgen-rt" @@ -2352,18 +2373,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -2404,9 +2425,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 3d0ea62..83e4ff0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxker" -version = "0.10.5" +version = "0.11.0" edition = "2024" authors = ["Jack Wills "] description = "A simple tui to view & control docker containers" @@ -27,7 +27,7 @@ similar_names = "allow" [dependencies] anyhow = "1.0" -bollard = "0.18" +bollard = "0.19" cansi = "2.2" clap = { version = "4.5", features = ["color", "derive", "unicode"] } crossterm = "0.29" @@ -39,12 +39,12 @@ ratatui = "0.29" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_jsonc = "1.0" -tokio = { version = "1.45", features = ["full"] } +tokio = { version = "1.47", features = ["full"] } tokio-util = "0.7" -toml = { version = "0.8", default-features = false, features = ["parse"] } +toml = { version = "0.9", default-features = false, features = ["parse", "serde"] } tracing = "0.1" tracing-subscriber = "0.3" -uuid = { version = "1.17", features = ["fast-rng", "v4"] } +uuid = { version = "1.18", features = ["fast-rng", "v4"] } [profile.release] lto = true diff --git a/README.md b/README.md index 83837ae..1c85e30 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,9 @@ In application controls, these, amongst many other settings, can be customized w | button| result| |--|--| | ```( tab )``` or ```( shift+tab )``` | Change panel, clicking on a panel also changes the selected panel.| -| ```( ↑ ↓ )``` or ```( j k )``` or ```( PgUp PgDown )``` or ```( Home End )```| Change selected line in selected panel, mouse scroll also changes selected line.| +| ```( ↑ ↓ )``` or ```( j k )``` or ```( PgUp PgDown )``` or ```( Home End )```| Scroll line in selected panel - mouse wheel will also scroll.| +| ```( ← → )``` | When logs panel selected, scroll horizontally across the text of the logs.| +| ```( ctrl )``` | Increase scroll speed, used in conjuction scroll keys.| | ```( enter )```| Run selected docker command.| | ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. | | ```( 0 )``` | Stop sorting.| @@ -113,6 +115,7 @@ In application controls, these, amongst many other settings, can be customized w | ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.| | ```( \ )``` | Toggle the visibility of the logs panel.| | ```( e )``` | Exec into the selected container - not available on Windows.| +| ```( f )``` | Force clear the screen & redraw the gui.| | ```( h )``` | Toggle help menu.| | ```( m )``` | Toggle mouse capture - if disabled, text on screen can be selected.| | ```( q )``` | Quit.| diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7af255b..7c58aac 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -4,7 +4,7 @@ networks: name: oxker-examaple-net services: postgres: - image: postgres:alpine3.21 + image: postgres:17-alpine container_name: postgres environment: - POSTGRES_PASSWORD=never_use_this_password_in_production @@ -18,7 +18,7 @@ services: limits: memory: 1024M redis: - image: redis:alpine3.21 + image: redis:latest container_name: redis ipc: private restart: always diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index 230e982..8abb678 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -42,6 +42,7 @@ // 3) F1-F12 // 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down // Each definition can have two keys associated with it + // WARNING "scroll_many" only accepts control, alt, shift, with no secondary option // If any key clashes are found, oxker will revert to it's default keymap "keymap": { // Clear any popup boxes, filter panel, or help panel @@ -74,10 +75,12 @@ "save_logs": [ "s" ], + // TODO "scroll_down_many" will be removed in the next release // Scroll down a list by many "scroll_down_many": [ "pagedown" ], + // TODO rename in next release // Scroll down a list by one item "scroll_down_one": [ "down", @@ -87,19 +90,30 @@ "scroll_end": [ "end" ], + // Modifier to scroll by 10 lines isntead of one, used in conjunction with scroll_up_x/scroll_down_x + "scroll_many": ["control"], // Scroll up to the start of a list "scroll_start": [ "home" ], + // TODO "scroll_up_many" will be removed in the next release // Scroll up a list by many "scroll_up_many": [ "pageup" ], + // TODO rename in next release // Scroll up a list by one item "scroll_up_one": [ "up", "k" ], + // Horizontal scroll of the logs + "log_scroll_forward": [ + "right" + ], + "log_scroll_back": [ + "left" + ], // Select next panel "select_next_panel": [ "tab" @@ -159,6 +173,10 @@ // Toggle visibility of the log section "log_section_toggle": [ "\\" + ], + // Force a complete clear & redraw of the screen + "force_redraw": [ + "f" ] }, //////////////////// diff --git a/example_config/example.config.toml b/example_config/example.config.toml index 3c71386..182e7c5 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -55,8 +55,10 @@ show_logs = true # 3) F1-F12 # 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down - # Each definition can have two keys associated with it + +# WARNING "scroll_many" only accepts control, alt, shift, with no secondary option + # If any key clashes are found, oxker will revert to it's default keymap [keymap] @@ -74,18 +76,28 @@ filter_mode = ["/", "F1"] quit = ["q"] # Save logs of selected container to file on disk save_logs = ["s"] +# TODO "scroll_down_many" will be removed in the next release # scroll down a list by many scroll_down_many = ["pagedown"] +# TODO rename in next release # scroll down a list by one item scroll_down_one = ["down", "j"] + # scroll down to the end of a list scroll_end = ["end"] +# Modifier to scroll by 10 lines isntead of one, used in conjunction with scroll_up_x/scroll_down_x +scroll_many = ["control"] # scroll up to the start of a list scroll_start = ["home"] +# TODO "scroll_up_many" will be removed in the next release # scroll up a list by many scroll_up_many = ["pageup"] +# TODO rename in next release # scroll up a list by one item scroll_up_one = ["up", "k"] +# Horizontal scroll of the logs +log_scroll_forward = ["right"] +log_scroll_back = ["left"] # Select next panel select_next_panel = ["tab"] # Select previous panel @@ -108,10 +120,11 @@ toggle_help = ["h"] 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 = ["\\"] +# Force a complete clear & redraw of the screen +force_redraw = ["f"] ################# # Custom Colors # @@ -186,6 +199,7 @@ selected_filter_text = "black" # Highlighted text color highlight = "magenta" + # The color the of Docker commands available for each container [colors.commands] # Background color of panel diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index de27aac..d31550e 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -8,8 +8,10 @@ use std::{ use bollard::service::Port; use jiff::{Timestamp, tz::TimeZone}; use ratatui::{ + layout::Size, style::Color, - widgets::{ListItem, ListState}, + text::{Line, Text}, + widgets::ListState, }; use crate::config::AppColors; @@ -30,14 +32,6 @@ impl From<&str> for 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 { - self.0.as_str() - } - - #[cfg(not(target_os = "macos"))] pub const fn get(&self) -> &str { self.0.as_str() } @@ -84,14 +78,6 @@ macro_rules! unit_struct { } 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 { - self.0.as_str() - } - - #[cfg(not(target_os = "macos"))] pub const fn get(&self) -> &str { self.0.as_str() } @@ -215,6 +201,7 @@ impl StatefulList { } /// Return the current status of the select list, e.g. 2/5, + /// MAYBE add up down arrows, check if at start or end etc pub fn get_state_title(&self) -> String { if self.items.is_empty() { String::new() @@ -342,6 +329,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 impl From<(Option, &ContainerStatus)> for State { fn from((input, status): (Option, &ContainerStatus)) -> Self { @@ -563,81 +598,177 @@ impl LogsTz { /// stateful list dependent on whether the timestamp is in the HashSet or not #[derive(Debug, Clone, PartialEq, Eq)] pub struct Logs { - logs: StatefulList>, + lines: StatefulList>, tz: HashSet, + offset: u16, + max_log_len: usize, + adjusted_max_width: usize, + adjust_max_width_text_len: usize, } impl Default for Logs { fn default() -> Self { - let mut logs = StatefulList::new(vec![]); - logs.end(); + let mut lines = StatefulList::new(vec![]); + lines.end(); Self { - logs, + lines, tz: HashSet::new(), + offset: 0, + adjusted_max_width: 0, + adjust_max_width_text_len: 0, + max_log_len: 0, } } } impl Logs { /// Only allow a new log line to be inserted if the log timestamp isn't in the tz HashSet - pub fn insert(&mut self, line: ListItem<'static>, tz: LogsTz) { + pub fn insert(&mut self, line: Text<'static>, tz: LogsTz) { if self.tz.insert(tz) { - self.logs.items.push(line); + self.max_log_len = self.max_log_len.max(line.width()); + self.lines.items.push(line); } } - /// Get the logs vec, but instead of cloning to whole vec, only clone items with x of the currently selected index + /// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y` + pub fn get_scroll_title(&mut self, width: u16) -> Option { + if self.horizontal_scroll_able(width) { + let text_width = self.adjust_max_width_text_len; + let arrow_left = if self.offset > 0 { " ←" } else { " " }; + let arrow_right = if usize::from(self.offset) < self.adjusted_max_width { + "→ " + } else { + " " + }; + Some(format!( + "{left} {offset:>text_width$}/{adjusted_max_width} {right}", + offset = self.offset, + adjusted_max_width = self.adjusted_max_width, + left = arrow_left, + right = arrow_right, + )) + } else { + None + } + } + + /// Format a log lone. Only return screen width amount of chars + /// If offset set, remove `char_offset` number of chars from a Text + /// `text` *should* only be a single line, so just use the .first() method rather than trying to iterate + fn format_log_line(text: &Text<'static>, char_offset: usize, width: u16) -> Text<'static> { + let mut skipped = 0; + text.lines.first().map_or_else(Text::default, |line| { + Text::from(Line::from( + line.spans + .iter() + .filter_map(|span| { + if skipped >= char_offset { + Some(ratatui::text::Span::styled( + span.content.chars().take(width.into()).collect::(), + span.style, + )) + } else { + let span_len = span.content.chars().count(); + if skipped + span_len <= char_offset { + skipped += span_len; + None + } else { + let start_index = char_offset - skipped; + skipped = char_offset; + Some(ratatui::text::Span::styled( + span.content + .chars() + .skip(start_index) + .take(width.into()) + .collect::(), + span.style, + )) + } + } + }) + .collect::>(), + )) + }) + } + + /// Get the logs vec, but instead of cloning to whole vec, only clone items within x of the currently selected index, as well as only the current screen widths number of chars /// Where x is the abs different of the index plus the panel height & a padding + /// Take into account the char offset, so that can scroll a line /// The rest can be just empty list items - pub fn to_vec(&self, height: usize, padding: usize) -> Vec> { - let current_index = self.logs.state.selected().unwrap_or_default(); - self.logs + pub fn get_visible_logs(&self, size: Size, padding: usize) -> Vec> { + let current_index = self.lines.state.selected().unwrap_or_default(); + let height_padding = usize::from(size.height) + padding; + let char_offset = if usize::from(self.offset) > self.max_log_len { + self.max_log_len + } else { + self.offset.into() + }; + + self.lines .items .iter() .enumerate() .map(|(index, item)| { - if current_index.abs_diff(index) <= height + padding { - item.clone() + if current_index.abs_diff(index) <= height_padding { + Self::format_log_line(item, char_offset, size.width) } else { - ListItem::from("") + Text::from("") } }) .collect() } + /// The rest of the methods are basically forwarding from the underlying StatefulList pub fn get_state_title(&self) -> String { - self.logs.get_state_title() + self.lines.get_state_title() + } + + /// Return true it currently selected cotnainer logs are wide enough to horizontally scroll + pub fn horizontal_scroll_able(&mut self, width: u16) -> bool { + if self.lines.items.is_empty() { + return false; + } + self.adjusted_max_width = self.max_log_len.saturating_sub(width.into()) + 4; + self.adjust_max_width_text_len = self.adjusted_max_width.to_string().chars().count(); + self.max_log_len + 4 > usize::from(width) + } + + /// Add a padding so one char will always be visilbe? + pub fn forward(&mut self, width: u16) { + let offset = usize::from(self.offset); + if self.horizontal_scroll_able(width) { + if self.adjusted_max_width > 0 && offset < self.adjusted_max_width { + self.offset = self.offset.saturating_add(1); + } + } + } + + /// Reduce the char offset + pub const fn back(&mut self) { + self.offset = self.offset.saturating_sub(1); } pub fn next(&mut self) { - self.logs.next(); + self.lines.next(); } pub fn previous(&mut self) { - self.logs.previous(); + self.lines.previous(); } pub fn end(&mut self) { - self.logs.end(); + self.lines.end(); } pub fn start(&mut self) { - self.logs.start(); + self.lines.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 { - self.logs.items.len() - } - - #[cfg(not(target_os = "macos"))] pub const fn len(&self) -> usize { - self.logs.items.len() + self.lines.items.len() } pub const fn state(&mut self) -> &mut ListState { - &mut self.logs.state + &mut self.lines.state } } @@ -801,7 +932,10 @@ impl Columns { mod tests { use jiff::tz::TimeZone; - use ratatui::widgets::ListItem; + use ratatui::{ + layout::Size, + text::{Line, Text}, + }; use crate::{ app_data::{ContainerImage, Logs, LogsTz, RunningState}, @@ -941,21 +1075,21 @@ mod tests { let mut logs = Logs::default(); let line = log_sanitizer::remove_ansi(input); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line), tz); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line), tz); - assert_eq!(logs.logs.items.len(), 1); + assert_eq!(logs.lines.items.len(), 1); let input = "2023-01-15T19:13:30.783138328Z Lorem ipsum dolor sit amet"; let (tz, _) = LogsTz::splitter(input); let line = log_sanitizer::remove_ansi(input); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line), tz); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line), tz); - assert_eq!(logs.logs.items.len(), 2); + assert_eq!(logs.lines.items.len(), 2); } #[test] @@ -1008,4 +1142,73 @@ mod tests { let input = State::from(("oxker", &healthy)); assert_eq!(input, State::Unknown); } + + #[test] + /// Test the format_log_line methods, should ideally check colours are being correct kept as well + fn test_to_vec() { + let mut logs = Logs::default(); + + let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + let input = "2023-01-14T19:13:31.783138328Z Hello world some line".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + let input = "2023-01-14T19:13:32.783138328Z Hello world".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + logs.offset = 43; + let result = logs.get_visible_logs( + Size { + width: 14, + height: 10, + }, + 10, + ); + assert_eq!( + vec![ + Text::from(Line::from("some long line")), + Text::from(Line::from("some line")), + Text::from(Line::default()) + ], + result + ); + } + + #[test] + /// Test the get_scroll_title methods + fn test_scroll_title() { + let mut logs = Logs::default(); + + let result = logs.get_scroll_title(10); + assert!(result.is_none()); + + let input = "short".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + let result = logs.get_scroll_title(10); + assert!(result.is_none()); + + let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + let result = logs.get_scroll_title(10); + assert_eq!(result, Some(" 0/51 → ".to_owned())); + + logs.forward(10); + + let result = logs.get_scroll_title(10); + assert_eq!(result, Some(" ← 1/51 → ".to_owned())); + + for _ in 0..=49 { + logs.forward(10); + } + let result = logs.get_scroll_title(10); + assert_eq!(result, Some(" ← 51/51 ".to_owned())); + } } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 3301476..ab7993e 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -1,7 +1,7 @@ use bollard::models::ContainerSummary; use core::fmt; use parking_lot::Mutex; -use ratatui::widgets::{ListItem, ListState}; +use ratatui::{layout::Size, text::Text, widgets::ListState}; use std::{ hash::Hash, sync::Arc, @@ -122,7 +122,7 @@ pub struct AppData { error: Option, filter: Filter, hidden_containers: Vec, - redraw: Arc, + rerender: Arc, sorted_by: Option<(Header, SortedOrder)>, current_sorted_id: Vec, pub config: Config, @@ -137,7 +137,7 @@ pub struct AppData { pub filter: Filter, pub hidden_containers: Vec, pub current_sorted_id: Vec, - pub redraw: Arc, + pub rerender: Arc, pub sorted_by: Option<(Header, SortedOrder)>, } @@ -151,7 +151,7 @@ impl AppData { error: None, filter: Filter::new(), hidden_containers: vec![], - redraw: Arc::clone(redraw), + rerender: Arc::clone(redraw), sorted_by: None, } } @@ -192,7 +192,7 @@ impl AppData { /// 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 fn filter_containers(&mut self) { - self.redraw.update(); + self.rerender.update_draw(); let pre_len = self.get_container_len(); if !self.hidden_containers.is_empty() { @@ -296,7 +296,7 @@ impl AppData { /// Remove the sorted header & order, and sort by default - created datetime pub fn reset_sorted(&mut self) { self.set_sorted(None); - self.redraw.update(); + self.rerender.update_draw(); } /// 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); if pre_order != self.get_current_ids() { - self.redraw.update(); + self.rerender.update_draw(); } } else if self.current_sorted_id != self.get_current_ids() { self.containers.items.sort_by(|a, b| { @@ -400,20 +400,13 @@ impl AppData { .cmp(&b.created) .then_with(|| a.name.get().cmp(b.name.get())) }); - self.redraw.update(); + self.rerender.update_draw(); self.current_sorted_id = self.get_current_ids(); } } /// Container state methods /// 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 { - self.containers.items.len() - } - - #[cfg(not(target_os = "macos"))] pub const fn get_container_len(&self) -> usize { self.containers.items.len() } @@ -446,25 +439,25 @@ impl AppData { /// Select the first container pub fn containers_start(&mut self) { self.containers.start(); - self.redraw.update(); + self.rerender.update_draw(); } /// select the last container pub fn containers_end(&mut self) { self.containers.end(); - self.redraw.update(); + self.rerender.update_draw(); } /// Select the next container pub fn containers_next(&mut self) { self.containers.next(); - self.redraw.update(); + self.rerender.update_draw(); } /// select the previous container pub fn containers_previous(&mut self) { self.containers.previous(); - self.redraw.update(); + self.rerender.update_draw(); } /// Get ListState of containers @@ -586,7 +579,7 @@ impl AppData { pub fn docker_controls_next(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.next(); - self.redraw.update(); + self.rerender.update_draw(); } } @@ -594,7 +587,7 @@ impl AppData { pub fn docker_controls_previous(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.previous(); - self.redraw.update(); + self.rerender.update_draw(); } } @@ -602,7 +595,7 @@ impl AppData { pub fn docker_controls_start(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.start(); - self.redraw.update(); + self.rerender.update_draw(); } } @@ -610,7 +603,7 @@ impl AppData { pub fn docker_controls_end(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.docker_controls.end(); - self.redraw.update(); + self.rerender.update_draw(); } } @@ -644,11 +637,33 @@ impl AppData { }) } + /// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y` + pub fn get_scroll_title(&mut self, width: u16) -> Option { + self.get_mut_selected_container() + .and_then(|i| i.logs.get_scroll_title(width)) + } + + /// Increase the logs offset, basically moving an invisible cursor back + pub fn log_back(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.logs.back(); + self.rerender.update_draw(); + } + } + + /// Increase the logs offset, basically moving an invisible cursor forward + pub fn log_forward(&mut self, width: u16) { + if let Some(i) = self.get_mut_selected_container() { + i.logs.forward(width); + self.rerender.update_draw(); + } + } + /// select next selected log line pub fn log_next(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.next(); - self.redraw.update(); + self.rerender.update_draw(); } } @@ -656,7 +671,7 @@ impl AppData { pub fn log_previous(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.previous(); - self.redraw.update(); + self.rerender.update_draw(); } } @@ -664,7 +679,7 @@ impl AppData { pub fn log_end(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.end(); - self.redraw.update(); + self.rerender.update_draw(); } } @@ -672,17 +687,17 @@ impl AppData { pub fn log_start(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.start(); - self.redraw.update(); + self.rerender.update_draw(); } } /// Get mutable Vec of current containers logs - pub fn get_logs(&self, height: u16, padding: usize) -> Vec> { + pub fn get_logs(&self, size: Size, padding: usize) -> Vec> { self.containers .state .selected() .and_then(|i| self.containers.items.get(i)) - .map_or(vec![], |i| i.logs.to_vec(height.into(), padding)) + .map_or(vec![], |i| i.logs.get_visible_logs(size, padding)) } /// Get mutable Option of the currently selected container Logs state @@ -713,14 +728,14 @@ impl AppData { /// Remove single app_state error pub fn remove_error(&mut self) { self.error = None; - self.redraw.update(); + self.rerender.update_draw(); } /// Insert single app_state error pub fn set_error(&mut self, error: AppError, gui_state: &Arc>, status: Status) { gui_state.lock().status_push(status); self.error = Some(error); - self.redraw.update(); + self.rerender.update_draw(); } /// Check if the selected container is a dockerised version of oxker @@ -810,7 +825,7 @@ impl AppData { container.mem_limit.update(mem_limit); } if self.is_selected_container(id) { - self.redraw.update(); + self.rerender.update_draw(); } self.sort_containers(); } @@ -848,7 +863,7 @@ impl AppData { if self.containers.items.get(index).is_some() { self.containers.items.remove(index); if self.is_selected_container(id) { - self.redraw.update(); + self.rerender.update_draw(); } } } @@ -881,7 +896,12 @@ impl AppData { .as_ref() .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 .image .as_ref() @@ -965,7 +985,7 @@ impl AppData { } else { log_sanitizer::remove_ansi(&i) }; - container.logs.insert(ListItem::new(lines), log_tz); + container.logs.insert(Text::from(lines), log_tz); } // Set the logs selected row for each container @@ -977,7 +997,7 @@ impl AppData { } } if self.is_selected_container(id) { - self.redraw.update(); + self.rerender.update_draw(); } } } @@ -1945,14 +1965,19 @@ mod tests { let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); app_data.update_log_by_id(logs, &ids[0]); - // app_data.log_start(); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(2)); assert_eq!(result.unwrap().offset(), 0); - let result = app_data.get_logs(4, 1); + let result = app_data.get_logs( + Size { + width: 20, + height: 4, + }, + 1, + ); assert_eq!(result.len(), 3); let result = app_data.get_log_title(); @@ -2340,44 +2365,68 @@ mod tests { app_data.update_log_by_id(logs, &ids[0]); - let result = app_data.get_logs(10, 10); + let result = app_data.get_logs( + Size { + width: 20, + height: 10, + }, + 10, + ); for (index, item) in result.iter().enumerate() { if index < 979 { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } else { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } } - let result = app_data.get_logs(100, 20); + let result = app_data.get_logs( + Size { + width: 20, + height: 100, + }, + 20, + ); for (index, item) in result.iter().enumerate() { if index < 879 { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } else { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } } app_data.log_start(); - let result = app_data.get_logs(10, 10); + + let result = app_data.get_logs( + Size { + width: 20, + height: 10, + }, + 10, + ); for (index, item) in result.iter().enumerate() { if index > 20 { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } else { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } } for _ in 0..=500 { app_data.log_next(); } - - let result = app_data.get_logs(10, 10); + let result = app_data.get_logs( + Size { + width: 20, + height: 10, + }, + 10, + ); for (index, item) in result.iter().enumerate() { if (481..=521).contains(&index) { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } else { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } } } diff --git a/src/config/config.toml b/src/config/config.toml index 457064a..182e7c5 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -56,6 +56,9 @@ show_logs = true # 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down # Each definition can have two keys associated with it + +# WARNING "scroll_many" only accepts control, alt, shift, with no secondary option + # If any key clashes are found, oxker will revert to it's default keymap [keymap] @@ -73,18 +76,28 @@ filter_mode = ["/", "F1"] quit = ["q"] # Save logs of selected container to file on disk save_logs = ["s"] +# TODO "scroll_down_many" will be removed in the next release # scroll down a list by many scroll_down_many = ["pagedown"] +# TODO rename in next release # scroll down a list by one item scroll_down_one = ["down", "j"] + # scroll down to the end of a list scroll_end = ["end"] +# Modifier to scroll by 10 lines isntead of one, used in conjunction with scroll_up_x/scroll_down_x +scroll_many = ["control"] # scroll up to the start of a list scroll_start = ["home"] +# TODO "scroll_up_many" will be removed in the next release # scroll up a list by many scroll_up_many = ["pageup"] +# TODO rename in next release # scroll up a list by one item scroll_up_one = ["up", "k"] +# Horizontal scroll of the logs +log_scroll_forward = ["right"] +log_scroll_back = ["left"] # Select next panel select_next_panel = ["tab"] # Select previous panel @@ -110,6 +123,8 @@ log_section_height_decrease = ["-"] log_section_height_increase = ["+"] # Toggle visibility of the log section log_section_toggle = ["\\"] +# Force a complete clear & redraw of the screen +force_redraw = ["f"] ################# # Custom Colors # diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs index 9847800..3f62e44 100644 --- a/src/config/keymap_parser.rs +++ b/src/config/keymap_parser.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use crossterm::event::KeyCode; +use crossterm::event::{KeyCode, KeyModifiers}; /// The macro accepts a list of struct names with key names /// Returns a struct where every key name is an Option, with the correct derived attributes @@ -12,6 +12,7 @@ macro_rules! optional_config_struct { $( $key_name: Option>, )* + pub scroll_many: Option>, } )* }; @@ -24,9 +25,10 @@ macro_rules! config_struct { $( #[derive(Debug, Clone, PartialEq, Eq)] pub struct $struct_name { - $( + $( pub $key_name: (KeyCode, Option), )* + pub scroll_many: KeyModifiers, } )* }; @@ -35,6 +37,7 @@ macro_rules! config_struct { optional_config_struct!( ConfigKeymap, clear, + force_redraw, delete_deny, delete_confirm, exec, @@ -42,13 +45,19 @@ optional_config_struct!( log_section_height_increase, log_section_height_decrease, log_section_toggle, + log_scroll_forward, + log_scroll_back, quit, save_logs, + // TODO remove in next release scroll_down_many, + // TODO rename in next release scroll_down_one, scroll_end, scroll_start, + // TODO remove in next release scroll_up_many, + // TODO rename in next release scroll_up_one, select_next_panel, select_previous_panel, @@ -73,16 +82,23 @@ config_struct!( delete_confirm, exec, filter_mode, + force_redraw, log_section_height_increase, log_section_height_decrease, log_section_toggle, + log_scroll_forward, + log_scroll_back, quit, save_logs, + // TODO remove in next release scroll_down_many, + // TODO rename in next release scroll_down_one, scroll_end, scroll_start, + // TODO remove in next release scroll_up_many, + // TODO rename in next release scroll_up_one, select_next_panel, select_previous_panel, @@ -108,16 +124,24 @@ impl Keymap { delete_deny: (KeyCode::Char('n'), None), exec: (KeyCode::Char('e'), None), filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))), + force_redraw: (KeyCode::Char('f'), None), log_section_height_decrease: (KeyCode::Char('-'), None), log_section_height_increase: (KeyCode::Char('='), None), log_section_toggle: (KeyCode::Char('\\'), None), + log_scroll_back: (KeyCode::Left, None), + log_scroll_forward: (KeyCode::Right, None), quit: (KeyCode::Char('q'), None), save_logs: (KeyCode::Char('s'), None), + // TODO remove in next release scroll_down_many: (KeyCode::PageDown, None), + // TODO rename in next release scroll_down_one: (KeyCode::Down, Some(KeyCode::Char('j'))), scroll_end: (KeyCode::End, None), scroll_start: (KeyCode::Home, None), + scroll_many: KeyModifiers::CONTROL, + // TODO remove in next release scroll_up_many: (KeyCode::PageUp, None), + // TODO rename in next release scroll_up_one: (KeyCode::Up, Some(KeyCode::Char('k'))), select_next_panel: (KeyCode::Tab, None), select_previous_panel: (KeyCode::BackTab, None), @@ -189,6 +213,7 @@ impl From> for Keymap { update_keymap(ck.exec, &mut keymap.exec, &mut clash); update_keymap(ck.filter_mode, &mut keymap.filter_mode, &mut clash); + update_keymap(ck.force_redraw, &mut keymap.force_redraw, &mut clash); update_keymap(ck.quit, &mut keymap.quit, &mut clash); update_keymap(ck.save_logs, &mut keymap.save_logs, &mut clash); update_keymap( @@ -201,6 +226,12 @@ impl From> for Keymap { update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash); update_keymap(ck.scroll_up_many, &mut keymap.scroll_up_many, &mut clash); update_keymap(ck.scroll_up_one, &mut keymap.scroll_up_one, &mut clash); + update_keymap( + ck.log_scroll_forward, + &mut keymap.log_scroll_forward, + &mut clash, + ); + update_keymap(ck.log_scroll_back, &mut keymap.log_scroll_back, &mut clash); update_keymap( ck.select_next_panel, &mut keymap.select_next_panel, @@ -227,6 +258,10 @@ impl From> for Keymap { &mut keymap.toggle_mouse_capture, &mut clash, ); + // TODO need to check for clashes when using additional modifiers + if let Some(scroll_many) = Self::try_parse_modifier(ck.scroll_many) { + keymap.scroll_many = scroll_many; + } } // A very basic clash check, every key has been inserted into a hashset, and a counter has been increased // if the counter and hashet length don't match, then there's a clash, and we just return the default keymap @@ -239,6 +274,20 @@ impl From> for Keymap { } impl Keymap { + // Allowable key modifiers are only `shift`, `control`, `alt` + fn try_parse_modifier(input: Option>) -> Option { + input.and_then(|input| { + input + .first() + .and_then(|input| match input.to_lowercase().trim() { + "control" => Some(KeyModifiers::CONTROL), + "alt" => Some(KeyModifiers::ALT), + "shift" => Some(KeyModifiers::SHIFT), + _ => None, + }) + }) + } + /// Try to parse a &[String] into a Vec of keycodes, at most the output will have 2 entries /// This might fail on MacOS due to Backspace and Delete working in a different manner as to how they work on Linux & Windows /// I think that on MacOS `Del` becomes `Fwd Del`, and `Backspace` becomes `Delete` @@ -310,7 +359,7 @@ impl Keymap { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use crossterm::event::KeyCode; + use crossterm::event::{KeyCode, KeyModifiers}; use crate::config::keymap_parser::ConfigKeymap; @@ -364,28 +413,32 @@ mod tests { delete_deny: Some(vec!["s".to_owned()]), delete_confirm: None, exec: None, + filter_mode: None, + force_redraw: None, + log_scroll_back: None, + log_scroll_forward: None, log_section_height_decrease: None, log_section_height_increase: None, - filter_mode: None, + log_section_toggle: None, quit: None, save_logs: None, scroll_down_many: None, scroll_down_one: None, scroll_end: None, scroll_start: None, + scroll_many: None, scroll_up_many: None, scroll_up_one: None, select_next_panel: None, - log_section_toggle: None, select_previous_panel: None, - sort_by_name: None, - sort_by_state: None, - sort_by_status: None, sort_by_cpu: None, - sort_by_memory: None, sort_by_id: None, sort_by_image: None, + sort_by_memory: None, + sort_by_name: None, sort_by_rx: None, + sort_by_state: None, + sort_by_status: None, sort_by_tx: None, sort_reset: None, toggle_help: None, @@ -404,72 +457,79 @@ mod tests { let input = ConfigKeymap { clear: gen_v(("a", "b")), - delete_confirm: gen_v(("e", "f")), - delete_deny: gen_v(("c", "d")), + delete_confirm: gen_v(("c", "d")), + delete_deny: gen_v(("e", "fd")), exec: gen_v(("g", "h")), 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")), - save_logs: gen_v(("m", "n")), - scroll_down_many: gen_v(("o", "p")), - scroll_down_one: gen_v(("q", "r")), - scroll_end: gen_v(("s", "t")), - scroll_start: gen_v(("u", "v")), - scroll_up_many: gen_v(("w", "x")), - scroll_up_one: gen_v(("y", "z")), - select_next_panel: gen_v(("0", "1")), - select_previous_panel: gen_v(("2", "3")), - sort_by_cpu: gen_v(("F1", "F12")), - sort_by_id: gen_v(("[", "]")), - 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_state: gen_v(("6", "7")), - sort_by_status: gen_v(("8", "9")), - sort_by_tx: gen_v(("insert", "TAB")), - sort_reset: gen_v(("up", "down")), - toggle_help: gen_v(("home", "end")), - toggle_mouse_capture: gen_v(("pagedown", "PAGEUP")), + force_redraw: gen_v(("k", "l")), + log_section_height_decrease: gen_v(("m", "n")), + log_section_height_increase: gen_v(("o", "p")), + log_scroll_forward: gen_v(("q", "r")), + log_scroll_back: gen_v(("s", "t")), + log_section_toggle: gen_v(("u", "v")), + quit: gen_v(("w", "x")), + save_logs: gen_v(("y", "z")), + scroll_down_many: gen_v(("1", "2")), + scroll_down_one: gen_v(("3", "4")), + scroll_end: gen_v(("5", "6")), + scroll_many: Some(vec!["alt".to_owned()]), + scroll_start: gen_v(("7", "8")), + scroll_up_many: gen_v(("9", "0")), + scroll_up_one: gen_v(("F1", "F2")), + select_next_panel: gen_v(("F3", "F4")), + select_previous_panel: gen_v(("F5", "F6")), + sort_by_cpu: gen_v(("F7", "F8")), + sort_by_id: gen_v(("F9", "F10")), + sort_by_image: gen_v(("F11", "F12")), + sort_by_memory: gen_v(("HOME", "END")), + sort_by_name: gen_v(("UP", "DOWN")), + sort_by_rx: gen_v(("LEFT", "RIGHT")), + sort_by_state: gen_v(("[", "]")), + sort_by_status: gen_v(("INSERTt", "TAB")), + sort_by_tx: gen_v(("PAGEDOWN", "PAGEUP")), + sort_reset: gen_v((",", ".")), + toggle_help: gen_v(("-", "=")), + toggle_mouse_capture: gen_v(("\\", "/")), }; let result = Keymap::from(Some(input)); let expected = Keymap { clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), - delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), - 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'))), + delete_deny: (KeyCode::Char('e'), None), + delete_confirm: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), - quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), - save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), - scroll_down_many: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), - scroll_down_one: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), - scroll_end: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), - scroll_start: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), - scroll_up_many: (KeyCode::Char('w'), Some(KeyCode::Char('x'))), - scroll_up_one: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), - select_next_panel: (KeyCode::Char('0'), Some(KeyCode::Char('1'))), - select_previous_panel: (KeyCode::Char('2'), Some(KeyCode::Char('3'))), - sort_by_name: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), - sort_by_state: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), - sort_by_status: (KeyCode::Char('8'), Some(KeyCode::Char('9'))), - sort_by_cpu: (KeyCode::F(1), Some(KeyCode::F(12))), - sort_by_memory: (KeyCode::Char('/'), Some(KeyCode::Char('\\'))), - sort_by_id: (KeyCode::Char('['), Some(KeyCode::Char(']'))), - sort_by_image: (KeyCode::Char('A'), Some(KeyCode::Char('B'))), - sort_by_rx: (KeyCode::Char('C'), Some(KeyCode::Char('D'))), - sort_by_tx: (KeyCode::Insert, Some(KeyCode::Tab)), - sort_reset: (KeyCode::Up, Some(KeyCode::Down)), - toggle_help: (KeyCode::Home, Some(KeyCode::End)), - toggle_mouse_capture: (KeyCode::PageDown, Some(KeyCode::PageUp)), + force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), + log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), + log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), + log_section_toggle: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), + log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), + log_scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), + quit: (KeyCode::Char('w'), Some(KeyCode::Char('x'))), + save_logs: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), + scroll_down_many: (KeyCode::Char('1'), Some(KeyCode::Char('2'))), + scroll_down_one: (KeyCode::Char('3'), Some(KeyCode::Char('4'))), + scroll_end: (KeyCode::Char('5'), Some(KeyCode::Char('6'))), + scroll_start: (KeyCode::Char('7'), Some(KeyCode::Char('8'))), + scroll_up_many: (KeyCode::Char('9'), Some(KeyCode::Char('0'))), + scroll_up_one: (KeyCode::F(1), Some(KeyCode::F(2))), + select_next_panel: (KeyCode::F(3), Some(KeyCode::F(4))), + select_previous_panel: (KeyCode::F(5), Some(KeyCode::F(6))), + sort_by_name: (KeyCode::Up, Some(KeyCode::Down)), + sort_by_state: (KeyCode::Char('['), Some(KeyCode::Char(']'))), + sort_by_status: (KeyCode::Tab, None), + sort_by_cpu: (KeyCode::F(7), Some(KeyCode::F(8))), + sort_by_memory: (KeyCode::Home, Some(KeyCode::End)), + sort_by_id: (KeyCode::F(9), Some(KeyCode::F(10))), + sort_by_image: (KeyCode::F(11), Some(KeyCode::F(12))), + sort_by_rx: (KeyCode::Left, Some(KeyCode::Right)), + sort_by_tx: (KeyCode::PageDown, Some(KeyCode::PageUp)), + sort_reset: (KeyCode::Char(','), Some(KeyCode::Char('.'))), + toggle_help: (KeyCode::Char('-'), Some(KeyCode::Char('='))), + toggle_mouse_capture: (KeyCode::Char('\\'), Some(KeyCode::Char('/'))), + scroll_many: KeyModifiers::ALT, }; - assert_eq!(expected, result); } } diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index e8c0237..52ecc24 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -1,9 +1,10 @@ use bollard::{ Docker, - container::{ - ListContainersOptions, LogsOptions, MemoryStatsStats, RemoveContainerOptions, - StartContainerOptions, Stats, StatsOptions, + query_parameters::{ + ListContainersOptions, LogsOptions, RemoveContainerOptions, RestartContainerOptions, + StartContainerOptions, StatsOptions, StopContainerOptions, }, + secret::ContainerStatsResponse, service::ContainerSummary, }; use futures_util::StreamExt; @@ -75,31 +76,44 @@ pub struct DockerData { impl DockerData { /// Use docker stats to calculate current cpu usage #[allow(clippy::cast_precision_loss)] - fn calculate_usage(stats: &Stats) -> f64 { + fn calculate_usage(stats: &ContainerStatsResponse) -> f64 { 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)) = ( - stats.cpu_stats.system_cpu_usage, - stats.precpu_stats.system_cpu_usage, + let total_usage = stats.precpu_stats.as_ref().map_or(0, |i| { + i.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 online_cpus = stats.cpu_stats.online_cpus.unwrap_or_else(|| { - u64::try_from( - stats - .cpu_stats - .cpu_usage - .percpu_usage - .as_ref() - .map_or(0, std::vec::Vec::len), - ) - .unwrap_or_default() - }) as f64; + let online_cpus = f64::from(stats.cpu_stats.as_ref().map_or(0, |i| { + i.online_cpus.unwrap_or_else(|| { + u32::try_from( + stats + .cpu_stats + .clone() + .unwrap_or_default() + .cpu_usage + .unwrap_or_default() + .percpu_usage + .as_ref() + .map_or(0, std::vec::Vec::len), + ) + .unwrap_or_default() + }) + })); if system_delta > 0.0 && cpu_delta > 0.0 { cpu_percentage = (cpu_delta / system_delta) * online_cpus * 100.0; } @@ -133,18 +147,20 @@ impl DockerData { while let Some(Ok(stats)) = stream.next().await { // 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_cache = stats.memory_stats.stats.map_or(0, |i| match i { - MemoryStatsStats::V1(x) => x.inactive_file, - MemoryStatsStats::V2(x) => x.inactive_file, + let mem_cache = stats.memory_stats.as_ref().map_or(&0, |i| { + i.stats + .as_ref() + .map_or(&0, |i| i.get("inactive_file").unwrap_or(&0)) }); ( Some( stats .memory_stats - .usage - .unwrap_or_default() - .saturating_sub(mem_cache), + .as_ref() + .map_or(0, |i| i.usage.unwrap_or_default()) + .saturating_sub(*mem_cache), ), Some(Self::calculate_usage(&stats)), ) @@ -152,26 +168,25 @@ impl DockerData { (None, None) }; - let op_key = stats - .networks - .as_ref() - .and_then(|networks| networks.keys().next().cloned()); - - 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) - }; + // TODO is hardcoded eth0 a good idea here? + let (rx, tx) = stats.networks.as_ref().map_or((0, 0), |i| { + i.get("eth0").map_or((0, 0), |x| { + ( + x.rx_bytes.unwrap_or_default(), + x.tx_bytes.unwrap_or_default(), + ) + }) + }); app_data.lock().update_stats_by_id( id, cpu_stats, mem_stat, - stats.memory_stats.limit.unwrap_or_default(), + stats + .memory_stats + .unwrap_or_default() + .limit + .unwrap_or_default(), rx, tx, ); @@ -206,7 +221,7 @@ impl DockerData { async fn update_all_containers(&self) { let containers = self .docker - .list_containers(Some(ListContainersOptions:: { + .list_containers(Some(ListContainersOptions { all: true, ..Default::default() })) @@ -244,11 +259,11 @@ impl DockerData { spawns: Arc>>>, stderr: bool, ) { - let options = Some(LogsOptions:: { + let options = Some(LogsOptions { stdout: true, stderr, timestamps: true, - since: i64::try_from(since).unwrap_or_default(), + since: i32::try_from(since).unwrap_or_default(), ..Default::default() }); @@ -365,14 +380,22 @@ impl DockerData { .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::) + .await + } DockerCommand::Resume => docker.unpause_container(id.get()).await, DockerCommand::Start => { docker - .start_container(id.get(), None::>) + .start_container(id.get(), None::) + .await + } + DockerCommand::Stop => { + docker + .stop_container(id.get(), None::) .await } - DockerCommand::Stop => docker.stop_container(id.get(), None).await, } .is_err() { @@ -448,119 +471,72 @@ impl DockerData { #[allow(clippy::float_cmp)] mod tests { - use bollard::container::{ - BlkioStats, CPUStats, CPUUsage, MemoryStats, PidsStats, Stats, StorageStats, ThrottlingData, - }; + use bollard::secret::{ContainerCpuStats, ContainerCpuUsage}; use super::*; - fn gen_stats() -> Stats { - Stats { - read: String::new(), - preread: String::new(), - num_procs: 1, - pids_stats: PidsStats { - current: None, - limit: None, - }, - network: None, + fn gen_stats() -> ContainerStatsResponse { + ContainerStatsResponse { + read: None, + preread: None, + num_procs: Some(1), + pids_stats: None, networks: None, - memory_stats: MemoryStats { - stats: None, - max_usage: None, - usage: None, - 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 { + memory_stats: None, + blkio_stats: None, + cpu_stats: Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: 10, - total_usage: 100, - usage_in_kernelmode: 20, - }, + usage_in_usermode: Some(10), + total_usage: Some(100), + usage_in_kernelmode: Some(20), + }), system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }, - precpu_stats: CPUStats { - cpu_usage: CPUUsage { + throttling_data: None, + }), + precpu_stats: Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: 10, - total_usage: 100, - usage_in_kernelmode: 20, - }, + usage_in_usermode: Some(10), + total_usage: Some(100), + usage_in_kernelmode: Some(20), + }), system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }, - 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(), + throttling_data: None, + }), + storage_stats: None, + name: None, + id: None, } } #[test] fn test_calculate_usage_50() { let mut stats = gen_stats(); - stats.precpu_stats = CPUStats { - cpu_usage: CPUUsage { + stats.precpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: 10, - total_usage: 100, - usage_in_kernelmode: 20, - }, + usage_in_usermode: Some(10), + total_usage: Some(100), + usage_in_kernelmode: Some(20), + }), system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - stats.cpu_stats = CPUStats { - cpu_usage: CPUUsage { + throttling_data: None, + }); + stats.cpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![150]), - usage_in_usermode: 20, - total_usage: 150, - usage_in_kernelmode: 30, - }, + usage_in_usermode: Some(20), + total_usage: Some(150), + usage_in_kernelmode: Some(30), + }), system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; + throttling_data: None, + }); let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(50.0, cpu_percentage); } @@ -568,37 +544,28 @@ mod tests { #[test] fn test_calculate_usage_25() { let mut stats = gen_stats(); - stats.precpu_stats = CPUStats { - cpu_usage: CPUUsage { + stats.precpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: 10, - total_usage: 100, - usage_in_kernelmode: 20, - }, + usage_in_usermode: Some(10), + total_usage: Some(100), + usage_in_kernelmode: Some(20), + }), system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - stats.cpu_stats = CPUStats { - cpu_usage: CPUUsage { + throttling_data: None, + }); + stats.cpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![75]), - usage_in_usermode: 20, - total_usage: 125, - usage_in_kernelmode: 30, - }, + usage_in_usermode: Some(20), + total_usage: Some(125), + usage_in_kernelmode: Some(30), + }), system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - + throttling_data: None, + }); let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(25.0, cpu_percentage); } @@ -606,38 +573,28 @@ mod tests { #[test] fn test_calculate_usage_75() { let mut stats = gen_stats(); - stats.precpu_stats = CPUStats { - cpu_usage: CPUUsage { + stats.precpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: 10, - total_usage: 100, - usage_in_kernelmode: 20, - }, + usage_in_usermode: Some(10), + total_usage: Some(100), + usage_in_kernelmode: Some(20), + }), system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - - stats.cpu_stats = CPUStats { - cpu_usage: CPUUsage { + throttling_data: None, + }); + stats.cpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![175]), - usage_in_usermode: 20, - total_usage: 175, - usage_in_kernelmode: 30, - }, + usage_in_usermode: Some(20), + total_usage: Some(175), + usage_in_kernelmode: Some(30), + }), system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - + throttling_data: None, + }); let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(75.0, cpu_percentage); } @@ -645,36 +602,28 @@ mod tests { #[test] fn test_calculate_usage_100() { let mut stats = gen_stats(); - stats.precpu_stats = CPUStats { - cpu_usage: CPUUsage { + stats.precpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: 10, - total_usage: 100, - usage_in_kernelmode: 20, - }, + usage_in_usermode: Some(10), + total_usage: Some(100), + usage_in_kernelmode: Some(20), + }), system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - stats.cpu_stats = CPUStats { - cpu_usage: CPUUsage { + throttling_data: None, + }); + stats.cpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![200]), - usage_in_usermode: 20, - total_usage: 200, - usage_in_kernelmode: 30, - }, + usage_in_usermode: Some(20), + total_usage: Some(200), + usage_in_kernelmode: Some(30), + }), system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; + throttling_data: None, + }); let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(100.0, cpu_percentage); } @@ -682,38 +631,28 @@ mod tests { #[test] fn test_calculate_usage_175() { let mut stats = gen_stats(); - stats.precpu_stats = CPUStats { - cpu_usage: CPUUsage { + stats.precpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: 10, - total_usage: 100, - usage_in_kernelmode: 20, - }, + usage_in_usermode: Some(10), + total_usage: Some(100), + usage_in_kernelmode: Some(20), + }), system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - - stats.cpu_stats = CPUStats { - cpu_usage: CPUUsage { + throttling_data: None, + }); + stats.cpu_stats = Some(ContainerCpuStats { + cpu_usage: Some(ContainerCpuUsage { percpu_usage: Some(vec![275]), - usage_in_usermode: 20, - total_usage: 275, - usage_in_kernelmode: 30, - }, + usage_in_usermode: Some(20), + total_usage: Some(275), + usage_in_kernelmode: Some(30), + }), system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: ThrottlingData { - periods: 0, - throttled_periods: 0, - throttled_time: 0, - }, - }; - + throttling_data: None, + }); let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(175.0, cpu_percentage); } diff --git a/src/input_handler/message.rs b/src/input_handler/message.rs index ba50101..1d5ab4a 100644 --- a/src/input_handler/message.rs +++ b/src/input_handler/message.rs @@ -3,5 +3,5 @@ use crossterm::event::{KeyCode, KeyModifiers, MouseEvent}; #[derive(Debug, Clone, Copy)] pub enum InputMessages { ButtonPress((KeyCode, KeyModifiers)), - MouseEvent(MouseEvent), + MouseEvent((MouseEvent, KeyModifiers)), } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 077d5c3..fcf098c 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -5,8 +5,7 @@ use std::{ time::SystemTime, }; -use bollard::container::LogsOptions; -// use bollard::container::LogsOptions; +use bollard::query_parameters::LogsOptions; use cansi::v3::categorise_text; use crossterm::{ event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}, @@ -68,7 +67,7 @@ impl InputHandler { while let Some(message) = self.rx.recv().await { match message { InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await, - InputMessages::MouseEvent(mouse_event) => { + InputMessages::MouseEvent((mouse_event, modifider)) => { let status = self.gui_state.lock().get_status(); let contains = |s: Status| status.contains(&s); @@ -79,7 +78,7 @@ impl InputHandler { | !contains(Status::DeleteConfirm) | !contains(Status::Filter) { - self.mouse_press(mouse_event); + self.mouse_press(mouse_event, modifider); } } } @@ -188,7 +187,7 @@ impl InputHandler { let path = log_path.join(format!("{name}_{now}.log")); - let options = Some(LogsOptions:: { + let options = Some(LogsOptions { stderr: true, stdout: true, timestamps: args.show_timestamp, @@ -286,6 +285,36 @@ impl InputHandler { } } + /// If keymap.scroll_modifier is pressed, return 10, else return 1, to speed up scrolling + fn get_modifier_total(&self, modifier: KeyModifiers) -> u8 { + if modifier == self.keymap.scroll_many { + 10 + } else { + 1 + } + } + + /// Advance the "cursor" along the logs + fn logs_forward(&self, modifier: KeyModifiers) { + let panel = self.gui_state.lock().get_selected_panel(); + if panel == SelectablePanel::Logs { + for _ in 0..self.get_modifier_total(modifier) { + let width = self.gui_state.lock().get_screen_width(); + self.app_data.lock().log_forward(width); + } + } + } + + /// Retreat the "cursor" along the logs + fn logs_back(&self, modifier: KeyModifiers) { + let panel = self.gui_state.lock().get_selected_panel(); + if panel == SelectablePanel::Logs { + for _ in 0..self.get_modifier_total(modifier) { + self.app_data.lock().log_back(); + } + } + } + /// 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 fn next_panel_key(&self) { @@ -391,6 +420,11 @@ impl InputHandler { /// Handle input that refers to the sorting of columns fn handle_sort(&self, key_code: KeyCode) { match key_code { + _ if self.keymap.force_redraw.0 == key_code + || self.keymap.force_redraw.1 == Some(key_code) => + { + self.gui_state.lock().set_clear(); + } _ if self.keymap.sort_reset.0 == key_code || self.keymap.sort_reset.1 == Some(key_code) => { @@ -467,7 +501,8 @@ impl InputHandler { } /// Handle button presses in all other scenarios - async fn handle_others(&mut self, key_code: KeyCode) { + #[allow(clippy::cognitive_complexity)] + async fn handle_others(&mut self, key_code: KeyCode, modifier: KeyModifiers) { self.handle_sort(key_code); // shift key plus arrows match key_code { @@ -537,28 +572,28 @@ impl InputHandler { _ if self.keymap.scroll_up_one.0 == key_code || self.keymap.scroll_up_one.1 == Some(key_code) => { - self.previous(); + self.scroll_up(modifier); } _ if self.keymap.scroll_up_many.0 == key_code || self.keymap.scroll_up_many.1 == Some(key_code) => { for _ in 0..=6 { - self.previous(); + self.scroll_up(modifier); } } _ if self.keymap.scroll_down_one.0 == key_code || self.keymap.scroll_down_one.1 == Some(key_code) => { - self.next(); + self.scroll_down(modifier); } _ if self.keymap.scroll_down_many.0 == key_code || self.keymap.scroll_down_many.1 == Some(key_code) => { for _ in 0..=6 { - self.next(); + self.scroll_down(modifier); } } @@ -569,6 +604,18 @@ impl InputHandler { self.docker_tx.send(DockerMessage::Update).await.ok(); } + _ if self.keymap.log_scroll_back.0 == key_code + || self.keymap.log_scroll_back.1 == Some(key_code) => + { + self.logs_back(modifier); + } + + _ if self.keymap.log_scroll_forward.0 == key_code + || self.keymap.log_scroll_forward.1 == Some(key_code) => + { + self.logs_forward(modifier); + } + KeyCode::Enter => self.enter_key().await, _ => (), } @@ -603,7 +650,7 @@ impl InputHandler { } else if contains_delete { self.handle_delete(key_code).await; } else { - self.handle_others(key_code).await; + self.handle_others(key_code, key_modifier).await; } } } @@ -628,7 +675,7 @@ impl InputHandler { } /// Handle mouse button events - fn mouse_press(&self, mouse_event: MouseEvent) { + fn mouse_press(&self, mouse_event: MouseEvent, modifier: KeyModifiers) { let status = self.gui_state.lock().get_status(); if status.contains(&Status::Help) { let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1); @@ -638,8 +685,8 @@ impl InputHandler { } } else { match mouse_event.kind { - MouseEventKind::ScrollUp => self.previous(), - MouseEventKind::ScrollDown => self.next(), + MouseEventKind::ScrollUp => self.scroll_up(modifier), + MouseEventKind::ScrollDown => self.scroll_down(modifier), MouseEventKind::Down(MouseButton::Left) => { let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1); let header = self.gui_state.lock().get_intersect_header(mouse_point); @@ -659,21 +706,37 @@ impl InputHandler { } /// Change state to next, depending which panel is currently in focus - fn next(&self) { + fn scroll_down(&self, modifier: KeyModifiers) { let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { - SelectablePanel::Containers => self.app_data.lock().containers_next(), - SelectablePanel::Logs => self.app_data.lock().log_next(), + SelectablePanel::Containers => { + for _ in 0..self.get_modifier_total(modifier) { + self.app_data.lock().containers_next(); + } + } + SelectablePanel::Logs => { + for _ in 0..self.get_modifier_total(modifier) { + self.app_data.lock().log_next(); + } + } SelectablePanel::Commands => self.app_data.lock().docker_controls_next(), } } /// Change state to previous, depending which panel is currently in focus - fn previous(&self) { + fn scroll_up(&self, modifier: KeyModifiers) { let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { - SelectablePanel::Containers => self.app_data.lock().containers_previous(), - SelectablePanel::Logs => self.app_data.lock().log_previous(), + SelectablePanel::Containers => { + for _ in 0..self.get_modifier_total(modifier) { + self.app_data.lock().containers_previous(); + } + } + SelectablePanel::Logs => { + for _ in 0..self.get_modifier_total(modifier) { + self.app_data.lock().log_previous(); + } + } SelectablePanel::Commands => self.app_data.lock().docker_controls_previous(), } } diff --git a/src/main.rs b/src/main.rs index 66c642b..4de8728 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +#![allow(clippy::collapsible_if)] +// Zigbuild is stuck on 1.87.0, which means Mac builds won't work when using collapsible ifs + use app_data::AppData; use app_error::AppError; use bollard::{API_DEFAULT_VERSION, Docker}; @@ -149,7 +152,7 @@ async fn main() { #[allow(clippy::unwrap_used)] mod tests { - use std::sync::Arc; + use std::{str::FromStr, sync::Arc}; use bollard::service::{ContainerSummary, Port}; @@ -208,7 +211,7 @@ mod tests { current_sorted_id: vec![], error: None, sorted_by: None, - redraw: Arc::new(Rerender::new()), + rerender: Arc::new(Rerender::new()), filter: Filter::new(), config: gen_config(), } @@ -228,6 +231,7 @@ mod tests { pub fn gen_container_summary(index: usize, state: &str) -> ContainerSummary { ContainerSummary { + image_manifest_descriptor: None, id: Some(format!("{index}")), names: Some(vec![format!("container_{}", index)]), image: Some(format!("image_{index}")), @@ -243,7 +247,7 @@ mod tests { size_rw: None, size_root_fs: None, labels: None, - state: Some(state.to_owned()), + state: Some(bollard::secret::ContainerSummaryStateEnum::from_str(state).unwrap()), status: Some(format!("Up {index} hour")), host_config: None, network_settings: None, diff --git a/src/ui/draw_blocks/filter.rs b/src/ui/draw_blocks/filter.rs index cf62973..987a46e 100644 --- a/src/ui/draw_blocks/filter.rs +++ b/src/ui/draw_blocks/filter.rs @@ -8,7 +8,7 @@ use ratatui::{ use crate::{app_data::FilterBy, config::AppColors, ui::FrameData}; /// Create the filter_by by spans, coloured dependant on which one is selected -fn filter_by_spans(colors: AppColors, fd: &FrameData) -> [Span; 4] { +fn filter_by_spans(colors: AppColors, fd: &'_ FrameData) -> [Span<'_>; 4] { let selected = Style::default() .bg(colors.filter.selected_filter_background) .fg(colors.filter.selected_filter_text); diff --git a/src/ui/draw_blocks/help.rs b/src/ui/draw_blocks/help.rs index a28cbbe..83a3051 100644 --- a/src/ui/draw_blocks/help.rs +++ b/src/ui/draw_blocks/help.rs @@ -109,7 +109,17 @@ impl HelpInfo { button_item("PgUp PgDown"), or(), button_item("Home End"), - button_desc("change selected line"), + button_desc("scroll vertically"), + ]), + Line::from(vec![ + space(), + button_item("← →"), + button_desc("horizontal scroll across logs"), + ]), + Line::from(vec![ + space(), + button_item("ctrl"), + button_desc("increase scroll speed, used in conjuction scroll keys"), ]), Line::from(vec![ space(), @@ -123,6 +133,11 @@ impl HelpInfo { #[cfg(target_os = "windows")] button_desc(" - not available on Windows"), ]), + Line::from(vec![ + space(), + button_item("f"), + button_desc("force clear the screen & redraw the gui"), + ]), Line::from(vec![ space(), button_item("h"), @@ -268,6 +283,13 @@ impl HelpInfo { or_secondary(km.scroll_up_many, "scroll list by up many"), or_secondary(km.scroll_end, "scroll list to end"), or_secondary(km.scroll_start, "scroll list to start"), + or_secondary(km.log_scroll_forward, "horizontal scroll logs right"), + or_secondary(km.log_scroll_back, "horizontal scroll logs left"), + Line::from(vec![ + space(), + button_item(km.scroll_many.to_string().as_str()), + button_desc("increase scroll speed, used in conjuction scroll keys"), + ]), Line::from(vec![ space(), button_item("enter"), @@ -277,6 +299,7 @@ impl HelpInfo { or_secondary(km.exec, "exec into a container"), #[cfg(target_os = "windows")] or_secondary(km.exec, "exec into a container - not available on Windows"), + or_secondary(km.force_redraw, "force clear the screen & redraw the gui"), or_secondary( km.toggle_help, "toggle this help information - or click heading", @@ -427,7 +450,7 @@ pub fn draw( #[allow(clippy::unwrap_used, clippy::too_many_lines)] mod tests { use crate::config::{AppColors, Keymap}; - use crossterm::event::KeyCode; + use crossterm::event::{KeyCode, KeyModifiers}; use insta::assert_snapshot; use jiff::tz::TimeZone; use ratatui::style::{Color, Modifier}; @@ -436,8 +459,10 @@ mod tests { #[test] /// This will cause issues once the version has more than the current 5 chars (0.5.0) + /// This test is incredibly annoying + /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); fn test_draw_blocks_help() { - let mut setup = test_setup(87, 35, true, true); + let mut setup = test_setup(87, 37, true, true); let tz = setup.app_data.lock().config.timezone.clone(); setup @@ -457,36 +482,45 @@ mod tests { for (row_index, result_row) in get_result(&setup) { for (result_cell_index, result_cell) in result_row.iter().enumerate() { + println!( + "{} {} {} {} {}", + row_index, + result_cell_index, + result_cell.symbol(), + result_cell.bg, + result_cell.fg + ); match (row_index, result_cell_index) { // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 34, _) | (0..=33, 0 | 86) => { + (0 | 36, _) | (0..=35, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } - // border is black on magenta - (1 | 32, _) | (1..=31, 1 | 85) => { + // border is red on black + (1 | 34, _) | (1..=31, 1 | 85) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - // oxker logo && description + // Buttons (2..=10, 2..=85) | (12, 19..=66) | (14, 2..=10 | 13..=27) | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) - | (16 | 23, 2..=12) - | (17..=20 | 22 | 25 | 27, 2..=8) - | (21, 2..=9 | 12..=18) - | (24 | 26, 2..=10) => { + | (16 | 27 | 29, 2..=10) + | (17, 2..=11) + | (18 | 26, 2..=12) + | (19 | 20 | 21 | 22 | 24 | 25 | 28 | 23 | 30, 2..=8) + | (24, 2..=9 | 12..=18) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); } - // The URL is white and underlined - (30, 25..=60) => { + // The URL is yellow and underlined + (33, 25..=60) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); } - // The rest is black on magenta + // The rest is red on black _ => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); @@ -498,8 +532,10 @@ mod tests { #[test] /// Test that the help panel gets drawn with custom colors + /// This test is incredibly annoying + /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); fn test_draw_blocks_help_custom_colors() { - let mut setup = test_setup(87, 35, true, true); + let mut setup = test_setup(87, 37, true, true); let mut colors = AppColors::new(); let tz = setup.app_data.lock().config.timezone.clone(); @@ -521,34 +557,34 @@ mod tests { .unwrap(); assert_snapshot!(setup.terminal.backend()); - for (row_index, result_row) in get_result(&setup) { for (result_cell_index, result_cell) in result_row.iter().enumerate() { match (row_index, result_cell_index) { // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 34, _) | (0..=33, 0 | 86) => { + (0 | 36, _) | (0..=35, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } // border is red on black - (1 | 32, _) | (1..=31, 1 | 85) => { + (1 | 34, _) | (1..=31, 1 | 85) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Red); } - // oxker logo && description + // Buttons (2..=10, 2..=85) | (12, 19..=66) | (14, 2..=10 | 13..=27) | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) - | (16 | 23, 2..=12) - | (17..=20 | 22 | 25 | 27, 2..=8) - | (21, 2..=9 | 12..=18) - | (24 | 26, 2..=10) => { + | (16 | 27 | 29, 2..=10) + | (17, 2..=11) + | (18 | 26, 2..=12) + | (19 | 20 | 21 | 22 | 24 | 25 | 28 | 23 | 30, 2..=8) + | (24, 2..=9 | 12..=18) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); } // The URL is yellow and underlined - (30, 25..=60) => { + (33, 25..=60) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); @@ -566,39 +602,43 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with one definition for each entry fn test_draw_blocks_help_custom_keymap_one_definition() { - let mut setup = test_setup(98, 47, true, true); + let mut setup = test_setup(98, 50, true, true); let input = Keymap { clear: (KeyCode::Char('a'), None), + delete_confirm: (KeyCode::Char('b'), None), delete_deny: (KeyCode::Char('c'), None), - delete_confirm: (KeyCode::Char('e'), 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), - quit: (KeyCode::Char('k'), None), + exec: (KeyCode::Char('d'), None), + filter_mode: (KeyCode::Char('e'), None), + force_redraw: (KeyCode::Char('f'), None), + log_scroll_back: (KeyCode::Char('g'), None), + log_scroll_forward: (KeyCode::Char('h'), None), + log_section_height_decrease: (KeyCode::Char('i'), None), + log_section_height_increase: (KeyCode::Char('j'), None), + log_section_toggle: (KeyCode::Char('k'), None), + quit: (KeyCode::Char('l'), None), save_logs: (KeyCode::Char('m'), None), - scroll_down_many: (KeyCode::Char('o'), None), - scroll_down_one: (KeyCode::Char('q'), None), - scroll_end: (KeyCode::Char('s'), None), - scroll_start: (KeyCode::Char('u'), None), - scroll_up_many: (KeyCode::Char('w'), None), - scroll_up_one: (KeyCode::Char('y'), None), - select_next_panel: (KeyCode::Char('0'), None), - select_previous_panel: (KeyCode::Char('2'), None), - sort_by_name: (KeyCode::Char('4'), None), - sort_by_state: (KeyCode::Char('6'), None), - sort_by_status: (KeyCode::Char('8'), None), - sort_by_cpu: (KeyCode::F(1), None), - sort_by_memory: (KeyCode::Char('#'), None), - sort_by_id: (KeyCode::Char('/'), None), - sort_by_image: (KeyCode::Char(','), None), - sort_by_rx: (KeyCode::Char('.'), None), - sort_by_tx: (KeyCode::Insert, None), - sort_reset: (KeyCode::Up, None), - toggle_help: (KeyCode::Home, None), - toggle_mouse_capture: (KeyCode::PageDown, None), + scroll_down_many: (KeyCode::Char('n'), None), + scroll_down_one: (KeyCode::Char('o'), None), + scroll_end: (KeyCode::Char('p'), None), + scroll_many: KeyModifiers::ALT, + scroll_start: (KeyCode::Char('q'), None), + scroll_up_many: (KeyCode::Char('r'), None), + scroll_up_one: (KeyCode::Char('s'), None), + select_next_panel: (KeyCode::Char('t'), None), + select_previous_panel: (KeyCode::Char('u'), None), + sort_by_cpu: (KeyCode::Char('v'), None), + sort_by_id: (KeyCode::Char('w'), None), + sort_by_image: (KeyCode::Char('x'), None), + sort_by_memory: (KeyCode::Char('y'), None), + sort_by_name: (KeyCode::Char('z'), None), + sort_by_rx: (KeyCode::Char('0'), None), + sort_by_state: (KeyCode::Char('1'), None), + sort_by_status: (KeyCode::Char('2'), None), + sort_by_tx: (KeyCode::Char('3'), None), + sort_reset: (KeyCode::Char('4'), None), + toggle_help: (KeyCode::Char('5'), None), + toggle_mouse_capture: (KeyCode::Char('6'), None), }; setup @@ -614,39 +654,43 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with two definition for each entry fn test_draw_blocks_help_custom_keymap_two_definitions() { - let mut setup = test_setup(110, 47, true, true); + let mut setup = test_setup(110, 50, true, true); let keymap = Keymap { - clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), - delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), - delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), - 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'))), - quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), - save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), - scroll_down_many: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), - scroll_down_one: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), - scroll_end: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), - scroll_start: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), - scroll_up_many: (KeyCode::Char('w'), Some(KeyCode::Char('x'))), - scroll_up_one: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), - select_next_panel: (KeyCode::Char('0'), Some(KeyCode::Char('1'))), - select_previous_panel: (KeyCode::Char('2'), Some(KeyCode::Char('3'))), - sort_by_name: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), - sort_by_state: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), - sort_by_status: (KeyCode::Char('8'), Some(KeyCode::Char('9'))), - sort_by_cpu: (KeyCode::F(1), Some(KeyCode::F(12))), - sort_by_memory: (KeyCode::Char('#'), Some(KeyCode::Char('-'))), - sort_by_id: (KeyCode::Char('/'), Some(KeyCode::Char('='))), - sort_by_image: (KeyCode::Char(','), Some(KeyCode::Char('\\'))), - sort_by_rx: (KeyCode::Char('.'), Some(KeyCode::Char(']'))), - sort_by_tx: (KeyCode::Insert, Some(KeyCode::BackTab)), - sort_reset: (KeyCode::Up, Some(KeyCode::Down)), - toggle_help: (KeyCode::Home, Some(KeyCode::End)), - toggle_mouse_capture: (KeyCode::PageDown, Some(KeyCode::PageUp)), + clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))), + delete_confirm: (KeyCode::Char('b'), Some(KeyCode::Char('B'))), + delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), + exec: (KeyCode::Char('d'), Some(KeyCode::Char('D'))), + filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), + force_redraw: (KeyCode::Char('f'), Some(KeyCode::Char('F'))), + log_scroll_back: (KeyCode::Char('f'), Some(KeyCode::Char('F'))), + log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), + log_section_height_decrease: (KeyCode::Char('h'), Some(KeyCode::Char('H'))), + log_section_height_increase: (KeyCode::Char('i'), Some(KeyCode::Char('I'))), + log_section_toggle: (KeyCode::Char('j'), Some(KeyCode::Char('J'))), + quit: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), + save_logs: (KeyCode::Char('l'), Some(KeyCode::Char('L'))), + scroll_down_many: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), + scroll_down_one: (KeyCode::Char('n'), Some(KeyCode::Char('N'))), + scroll_end: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), + scroll_many: KeyModifiers::ALT, + scroll_start: (KeyCode::Char('p'), Some(KeyCode::Char('P'))), + scroll_up_many: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))), + scroll_up_one: (KeyCode::Char('r'), Some(KeyCode::Char('R'))), + select_next_panel: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), + select_previous_panel: (KeyCode::Char('t'), Some(KeyCode::Char('T'))), + sort_by_cpu: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), + sort_by_id: (KeyCode::Char('v'), Some(KeyCode::Char('V'))), + sort_by_image: (KeyCode::Char('w'), Some(KeyCode::Char('W'))), + sort_by_memory: (KeyCode::Char('x'), Some(KeyCode::Char('X'))), + sort_by_name: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))), + sort_by_rx: (KeyCode::Char('z'), Some(KeyCode::Char('Z'))), + sort_by_state: (KeyCode::Char('0'), Some(KeyCode::Char('9'))), + sort_by_status: (KeyCode::Char('1'), Some(KeyCode::Char('8'))), + sort_by_tx: (KeyCode::Char('2'), Some(KeyCode::Char('7'))), + sort_reset: (KeyCode::Char('3'), Some(KeyCode::Char('6'))), + toggle_help: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), + toggle_mouse_capture: (KeyCode::Char('5'), Some(KeyCode::PageDown)), }; setup @@ -662,39 +706,43 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with either one or two definition for each entry fn test_draw_blocks_help_one_and_two_definitions() { - let mut setup = test_setup(110, 47, true, true); + let mut setup = test_setup(110, 50, true, true); let keymap = Keymap { - clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), - delete_deny: (KeyCode::Char('c'), None), - delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), - exec: (KeyCode::Char('g'), None), - 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), - save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), - scroll_down_many: (KeyCode::Char('o'), None), - scroll_down_one: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), - scroll_end: (KeyCode::Char('s'), None), - scroll_start: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), - scroll_up_many: (KeyCode::Char('w'), None), - scroll_up_one: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), - select_next_panel: (KeyCode::Char('0'), None), - select_previous_panel: (KeyCode::Char('2'), Some(KeyCode::Char('3'))), - sort_by_name: (KeyCode::Char('4'), None), - sort_by_state: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), - sort_by_status: (KeyCode::Char('8'), None), - sort_by_cpu: (KeyCode::F(1), Some(KeyCode::F(12))), - sort_by_memory: (KeyCode::Char('#'), None), - sort_by_id: (KeyCode::Char('/'), Some(KeyCode::Char('='))), - sort_by_image: (KeyCode::Char(','), None), - sort_by_rx: (KeyCode::Char('.'), Some(KeyCode::Char(']'))), - sort_by_tx: (KeyCode::Insert, None), - sort_reset: (KeyCode::Up, Some(KeyCode::Down)), - toggle_help: (KeyCode::Home, None), - toggle_mouse_capture: (KeyCode::PageDown, Some(KeyCode::PageUp)), + clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))), + delete_confirm: (KeyCode::Char('b'), None), + delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), + exec: (KeyCode::Char('d'), None), + filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), + force_redraw: (KeyCode::Char('f'), None), + log_scroll_back: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), + log_scroll_forward: (KeyCode::Char('h'), None), + log_section_height_decrease: (KeyCode::Char('i'), Some(KeyCode::Char('I'))), + log_section_height_increase: (KeyCode::Char('j'), None), + log_section_toggle: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), + quit: (KeyCode::Char('l'), None), + save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), + scroll_down_many: (KeyCode::Char('n'), None), + scroll_down_one: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), + scroll_end: (KeyCode::Char('p'), None), + scroll_many: KeyModifiers::ALT, + scroll_start: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))), + scroll_up_many: (KeyCode::Char('r'), None), + scroll_up_one: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), + select_next_panel: (KeyCode::Char('t'), None), + select_previous_panel: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), + sort_by_cpu: (KeyCode::Char('v'), None), + sort_by_id: (KeyCode::Char('w'), Some(KeyCode::Char('W'))), + sort_by_image: (KeyCode::Char('x'), None), + sort_by_memory: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))), + sort_by_name: (KeyCode::Char('z'), None), + sort_by_rx: (KeyCode::Char('0'), Some(KeyCode::Char('9'))), + sort_by_state: (KeyCode::Char('1'), None), + sort_by_status: (KeyCode::Char('2'), Some(KeyCode::Char('7'))), + sort_by_tx: (KeyCode::Char('3'), None), + sort_reset: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), + toggle_help: (KeyCode::Char('5'), None), + toggle_mouse_capture: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), }; let tz = setup.app_data.lock().config.timezone.clone(); @@ -731,10 +779,10 @@ mod tests { for (row_index, result_row) in get_result(&setup) { for (result_cell_index, result_cell) in result_row.iter().enumerate() { match (row_index, result_cell_index) { - (14, 31..=45) => { + (13, 31..=45) => { assert_eq!(result_cell.fg, AppColors::new().popup_help.text); } - (14, 46..=55) => { + (13, 46..=55) => { assert_eq!(result_cell.fg, AppColors::new().popup_help.text_highlight); } _ => (), diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs index 6381e5b..61d501f 100644 --- a/src/ui/draw_blocks/logs.rs +++ b/src/ui/draw_blocks/logs.rs @@ -40,7 +40,7 @@ pub fn draw( f.render_widget(paragraph, area); } else { let padding = usize::from(area.height / 5); - let logs = app_data.lock().get_logs(area.height, padding); + let logs = app_data.lock().get_logs(area.as_size(), padding); if logs.is_empty() { let mut paragraph = Paragraph::new("no logs found") .block(block) @@ -356,7 +356,6 @@ mod tests { insert_logs(&setup); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); - setup .terminal .draw(|f| { diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index ec06c61..d65a37c 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -72,7 +72,6 @@ pub fn max_line_width(text: &str) -> usize { .max() .unwrap_or_default() } - /// Generate block, add a border if is the selected panel, /// add custom title based on state of each panel fn generate_block<'a>( @@ -101,7 +100,15 @@ fn generate_block<'a>( let mut block = Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) - .title(title); + .title(ratatui::text::Line::from(title).left_aligned()); + + if panel == SelectablePanel::Logs { + if let Some(x) = fd.scroll_title.as_ref() { + block = block + .title_bottom(x.to_owned()) + .title_alignment(ratatui::layout::Alignment::Right); + } + } if !fd.status.contains(&Status::Filter) { if fd.selected_panel == panel { block = block.border_style(Style::default().fg(colors.borders.selected)); @@ -151,7 +158,7 @@ pub mod tests { /// Create a FrameData struct from two Arc's, instead of from UI impl From<(&Arc>, &Arc>)> for FrameData { fn from(data: (&Arc>, &Arc>)) -> Self { - let (app_data, gui_data) = (data.0.lock(), data.1.lock()); + let (mut app_data, gui_data) = (data.0.lock(), data.1.lock()); // let container_section_height = app_data.get_container_len(); // let container_section_height = if container_section_height < 12 { @@ -178,6 +185,7 @@ pub mod tests { loading_icon: gui_data.get_loading().to_string(), log_height: gui_data.get_log_height(), log_title: app_data.get_log_title(), + scroll_title: app_data.get_scroll_title(gui_data.get_screen_width()), port_max_lens: app_data.get_longest_port(), ports: app_data.get_selected_ports(), selected_panel: gui_data.get_selected_panel(), @@ -208,6 +216,7 @@ pub mod tests { let gui_state = Arc::new(Mutex::new(gui_state)); let fd = FrameData::from((&app_data, &gui_state)); let area = Rect::new(0, 0, w, h); + gui_state.lock().set_screen_width(w); TuiTestSetup { app_data, gui_state, @@ -220,9 +229,9 @@ pub mod tests { /// Just a shorthand for when enumerating over result cells pub fn get_result( - setup: &TuiTestSetup, + setup: &'_ TuiTestSetup, // w: u16, - ) -> std::iter::Enumerate> { + ) -> std::iter::Enumerate> { setup .terminal .backend() @@ -276,7 +285,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -305,7 +314,7 @@ pub mod tests { setup.app_data.lock().containers.items[1] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -340,7 +349,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -373,7 +382,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -402,7 +411,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -434,7 +443,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -464,7 +473,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -498,7 +507,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); @@ -530,7 +539,7 @@ pub mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); diff --git a/src/ui/draw_blocks/ports.rs b/src/ui/draw_blocks/ports.rs index fee6dff..44fd16d 100644 --- a/src/ui/draw_blocks/ports.rs +++ b/src/ui/draw_blocks/ports.rs @@ -182,7 +182,7 @@ mod tests { setup.app_data.lock().containers.items[0] .ports .push(ContainerPorts { - ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), private: 8003, public: Some(8003), }); diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap index 747eec0..ad841ca 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap @@ -1,6 +1,5 @@ --- source: src/ui/draw_blocks/help.rs -assertion_line: 456 expression: setup.terminal.backend() --- " " @@ -18,9 +17,12 @@ expression: setup.terminal.backend() " │ 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 │ " +" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ " +" │ ( ← → ) horizontal scroll across logs │ " +" │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " +" │ ( f ) force clear the screen & redraw the gui │ " " │ ( h ) toggle this help information - or click heading │ " " │ ( s ) save logs to file │ " " │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " @@ -35,6 +37,5 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap index a0f9ea1..ad841ca 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap @@ -17,9 +17,12 @@ expression: setup.terminal.backend() " │ 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 │ " +" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ " +" │ ( ← → ) horizontal scroll across logs │ " +" │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " +" │ ( f ) force clear the screen & redraw the gui │ " " │ ( h ) toggle this help information - or click heading │ " " │ ( s ) save logs to file │ " " │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " @@ -34,6 +37,5 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap index 8cebda0..f8933a4 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap @@ -2,50 +2,53 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────╮ " -" │ │ " -" │ 88 │ " -" │ 88 │ " -" │ 88 │ " -" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " -" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " -" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " -" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " -" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " -" │ │ " -" │ A simple tui to view & control docker containers │ " -" │ │ " -" │ ( 0 ) select next panel │ " -" │ ( 2 ) select previous panel │ " -" │ ( q ) scroll list down by one │ " -" │ ( y ) scroll list up by one │ " -" │ ( o ) scroll list down by many │ " -" │ ( w ) scroll list by up many │ " -" │ ( s ) scroll list to end │ " -" │ ( u ) scroll list to start │ " -" │ ( enter ) send docker container command │ " -" │ ( g ) exec into a container │ " -" │ ( Home ) toggle this help information - or click heading │ " -" │ ( m ) save logs to file │ " -" │ ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " -" │ ( i ) enter filter mode │ " -" │ ( Up ) reset container sorting │ " -" │ ( 4 ) sort containers by name │ " -" │ ( 6 ) sort containers by state │ " -" │ ( 8 ) sort containers by status │ " -" │ ( F1 ) sort containers by cpu │ " -" │ ( # ) sort containers by memory │ " -" │ ( / ) sort containers by id │ " -" │ ( , ) sort containers by image │ " -" │ ( . ) sort containers by rx │ " -" │ ( Insert ) sort containers by tx │ " -" │ ( z ) decrease log section height │ " -" │ ( x ) increase log section height │ " -" │ ( W ) toggle log section visibility │ " -" │ ( a ) close dialog │ " -" │ ( k ) quit at any time │ " -" │ │ " -" │ currently an early work in progress, all and any input appreciated │ " -" │ https://github.com/mrjackwills/oxker │ " -" │ │ " -" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ " +" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ " +" │ │ " +" │ 88 │ " +" │ 88 │ " +" │ 88 │ " +" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " +" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " +" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " +" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " +" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " +" │ │ " +" │ A simple tui to view & control docker containers │ " +" │ │ " +" │ ( t ) select next panel │ " +" │ ( u ) select previous panel │ " +" │ ( o ) scroll list down by one │ " +" │ ( s ) scroll list up by one │ " +" │ ( n ) scroll list down by many │ " +" │ ( r ) scroll list by up many │ " +" │ ( p ) scroll list to end │ " +" │ ( q ) scroll list to start │ " +" │ ( h ) horizontal scroll logs right │ " +" │ ( g ) horizontal scroll logs left │ " +" │ ( Alt ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( enter ) send docker container command │ " +" │ ( d ) exec into a container │ " +" │ ( f ) force clear the screen & redraw the gui │ " +" │ ( 5 ) toggle this help information - or click heading │ " +" │ ( m ) save logs to file │ " +" │ ( 6 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " +" │ ( e ) enter filter mode │ " +" │ ( 4 ) reset container sorting │ " +" │ ( z ) sort containers by name │ " +" │ ( 1 ) sort containers by state │ " +" │ ( 2 ) sort containers by status │ " +" │ ( v ) sort containers by cpu │ " +" │ ( y ) sort containers by memory │ " +" │ ( w ) sort containers by id │ " +" │ ( x ) sort containers by image │ " +" │ ( 0 ) sort containers by rx │ " +" │ ( 3 ) sort containers by tx │ " +" │ ( i ) decrease log section height │ " +" │ ( j ) increase log section height │ " +" │ ( k ) toggle log section visibility │ " +" │ ( a ) close dialog │ " +" │ ( l ) quit at any time │ " +" │ │ " +" │ currently an early work in progress, all and any input appreciated │ " +" │ https://github.com/mrjackwills/oxker │ " +" ╰────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap index 818fc99..40866fb 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap @@ -2,50 +2,53 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " -" │ │ " -" │ 88 │ " -" │ 88 │ " -" │ 88 │ " -" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " -" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " -" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " -" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " -" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " -" │ │ " -" │ A simple tui to view & control docker containers │ " -" │ │ " -" │ ( 0 ) or ( 1 ) select next panel │ " -" │ ( 2 ) or ( 3 ) select previous panel │ " -" │ ( q ) or ( r ) scroll list down by one │ " -" │ ( y ) or ( z ) scroll list up by one │ " -" │ ( o ) or ( p ) scroll list down by many │ " -" │ ( w ) or ( x ) scroll list by up many │ " -" │ ( s ) or ( t ) scroll list to end │ " -" │ ( u ) or ( v ) scroll list to start │ " -" │ ( enter ) send docker container command │ " -" │ ( g ) or ( h ) exec into a container │ " -" │ ( Home ) or ( End ) toggle this help information - or click heading │ " -" │ ( m ) or ( n ) save logs to file │ " -" │ ( Page Down ) or ( Page Up ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " -" │ ( i ) or ( j ) enter filter mode │ " -" │ ( Up ) or ( Down ) reset container sorting │ " -" │ ( 4 ) or ( 5 ) sort containers by name │ " -" │ ( 6 ) or ( 7 ) sort containers by state │ " -" │ ( 8 ) or ( 9 ) sort containers by status │ " -" │ ( F1 ) or ( F12 ) sort containers by cpu │ " -" │ ( # ) or ( - ) sort containers by memory │ " -" │ ( / ) or ( = ) sort containers by id │ " -" │ ( , ) or ( \ ) sort containers by image │ " -" │ ( . ) or ( ] ) sort containers by rx │ " -" │ ( 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 │ " -" │ ( k ) or ( l ) quit at any time │ " -" │ │ " -" │ currently an early work in progress, all and any input appreciated │ " -" │ https://github.com/mrjackwills/oxker │ " -" │ │ " -" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " +" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────────────╮ " +" │ │ " +" │ 88 │ " +" │ 88 │ " +" │ 88 │ " +" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " +" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " +" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " +" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " +" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " +" │ │ " +" │ A simple tui to view & control docker containers │ " +" │ │ " +" │ ( s ) or ( S ) select next panel │ " +" │ ( t ) or ( T ) select previous panel │ " +" │ ( n ) or ( N ) scroll list down by one │ " +" │ ( r ) or ( R ) scroll list up by one │ " +" │ ( m ) or ( M ) scroll list down by many │ " +" │ ( q ) or ( Q ) scroll list by up many │ " +" │ ( o ) or ( O ) scroll list to end │ " +" │ ( p ) or ( P ) scroll list to start │ " +" │ ( g ) or ( G ) horizontal scroll logs right │ " +" │ ( f ) or ( F ) horizontal scroll logs left │ " +" │ ( Alt ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( enter ) send docker container command │ " +" │ ( d ) or ( D ) exec into a container │ " +" │ ( f ) or ( F ) force clear the screen & redraw the gui │ " +" │ ( 4 ) or ( 5 ) toggle this help information - or click heading │ " +" │ ( l ) or ( L ) save logs to file │ " +" │ ( 5 ) or ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " +" │ ( e ) or ( E ) enter filter mode │ " +" │ ( 3 ) or ( 6 ) reset container sorting │ " +" │ ( y ) or ( Y ) sort containers by name │ " +" │ ( 0 ) or ( 9 ) sort containers by state │ " +" │ ( 1 ) or ( 8 ) sort containers by status │ " +" │ ( u ) or ( U ) sort containers by cpu │ " +" │ ( x ) or ( X ) sort containers by memory │ " +" │ ( v ) or ( V ) sort containers by id │ " +" │ ( w ) or ( W ) sort containers by image │ " +" │ ( z ) or ( Z ) sort containers by rx │ " +" │ ( 2 ) or ( 7 ) sort containers by tx │ " +" │ ( h ) or ( H ) decrease log section height │ " +" │ ( i ) or ( I ) increase log section height │ " +" │ ( j ) or ( J ) toggle log section visibility │ " +" │ ( a ) or ( A ) close dialog │ " +" │ ( k ) or ( K ) quit at any time │ " +" │ │ " +" │ currently an early work in progress, all and any input appreciated │ " +" │ https://github.com/mrjackwills/oxker │ " +" ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap index 1778328..3694cac 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap @@ -2,50 +2,53 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " -" │ │ " -" │ 88 │ " -" │ 88 │ " -" │ 88 │ " -" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " -" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " -" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " -" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " -" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " -" │ │ " -" │ A simple tui to view & control docker containers │ " -" │ │ " -" │ ( 0 ) select next panel │ " -" │ ( 2 ) or ( 3 ) select previous panel │ " -" │ ( q ) or ( r ) scroll list down by one │ " -" │ ( y ) or ( z ) scroll list up by one │ " -" │ ( o ) scroll list down by many │ " -" │ ( w ) scroll list by up many │ " -" │ ( s ) scroll list to end │ " -" │ ( u ) or ( v ) scroll list to start │ " -" │ ( enter ) send docker container command │ " -" │ ( g ) exec into a container │ " -" │ ( Home ) toggle this help information - or click heading │ " -" │ ( m ) or ( n ) save logs to file │ " -" │ ( Page Down ) or ( Page Up ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " -" │ ( i ) or ( j ) enter filter mode │ " -" │ ( Up ) or ( Down ) reset container sorting │ " -" │ ( 4 ) sort containers by name │ " -" │ ( 6 ) or ( 7 ) sort containers by state │ " -" │ ( 8 ) sort containers by status │ " -" │ ( F1 ) or ( F12 ) sort containers by cpu │ " -" │ ( # ) sort containers by memory │ " -" │ ( / ) or ( = ) sort containers by id │ " -" │ ( , ) sort containers by image │ " -" │ ( . ) or ( ] ) sort containers by rx │ " -" │ ( 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 │ " -" │ ( k ) quit at any time │ " -" │ │ " -" │ currently an early work in progress, all and any input appreciated │ " -" │ https://github.com/mrjackwills/oxker │ " -" │ │ " -" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " +" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────╮ " +" │ │ " +" │ 88 │ " +" │ 88 │ " +" │ 88 │ " +" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " +" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " +" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " +" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " +" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " +" │ │ " +" │ A simple tui to view & control docker containers │ " +" │ │ " +" │ ( t ) select next panel │ " +" │ ( u ) or ( U ) select previous panel │ " +" │ ( o ) or ( O ) scroll list down by one │ " +" │ ( s ) or ( S ) scroll list up by one │ " +" │ ( n ) scroll list down by many │ " +" │ ( r ) scroll list by up many │ " +" │ ( p ) scroll list to end │ " +" │ ( q ) or ( Q ) scroll list to start │ " +" │ ( h ) horizontal scroll logs right │ " +" │ ( g ) or ( G ) horizontal scroll logs left │ " +" │ ( Alt ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( enter ) send docker container command │ " +" │ ( d ) exec into a container │ " +" │ ( f ) force clear the screen & redraw the gui │ " +" │ ( 5 ) toggle this help information - or click heading │ " +" │ ( m ) or ( M ) save logs to file │ " +" │ ( 6 ) or ( 7 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " +" │ ( e ) or ( E ) enter filter mode │ " +" │ ( 4 ) or ( 5 ) reset container sorting │ " +" │ ( z ) sort containers by name │ " +" │ ( 1 ) sort containers by state │ " +" │ ( 2 ) or ( 7 ) sort containers by status │ " +" │ ( v ) sort containers by cpu │ " +" │ ( y ) or ( Y ) sort containers by memory │ " +" │ ( w ) or ( W ) sort containers by id │ " +" │ ( x ) sort containers by image │ " +" │ ( 0 ) or ( 9 ) sort containers by rx │ " +" │ ( 3 ) sort containers by tx │ " +" │ ( i ) or ( I ) decrease log section height │ " +" │ ( j ) increase log section height │ " +" │ ( k ) or ( K ) toggle log section visibility │ " +" │ ( a ) or ( A ) close dialog │ " +" │ ( l ) quit at any time │ " +" │ │ " +" │ currently an early work in progress, all and any input appreciated │ " +" │ https://github.com/mrjackwills/oxker │ " +" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap index 291cc9f..67b6242 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap @@ -2,7 +2,6 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " " ╭ 0.00.000 ─────────────────────────────────────────────────────────────────────────╮ " " │ │ " " │ 88 │ " @@ -19,9 +18,12 @@ expression: setup.terminal.backend() " │ logs timezone: Asia/Tokyo │ " " │ │ " " │ ( tab ) or ( shift+tab ) change panels │ " -" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ " +" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ " +" │ ( ← → ) horizontal scroll across logs │ " +" │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " +" │ ( f ) force clear the screen & redraw the gui │ " " │ ( h ) toggle this help information - or click heading │ " " │ ( s ) save logs to file │ " " │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " @@ -36,6 +38,4 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " -" " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap index ed89388..f6603f1 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap @@ -4,25 +4,26 @@ 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 │" +"│⚪ container_1 ✓ running Up 1 ho╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 ho│ │ ││ restart │" +"│ container_3 ✓ running Up 3 ho│ 88 │ ││ stop │" +"│ │ 88 │ ││ delete │" "│ │ 88 │ ││ │" -"╰────────────────────────────────────│ 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 │ │" +"╰────────────────────────────────────│ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │────────────────────╯╰──────────────╯" +"╭ Logs 3/3 - container_1 - image_1 ──│ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │────────────────────────────────────╮" +"│ line 1 │ 8b d8 )888( 8888[ 8PP""""""" 88 │ │" +"│ line 2 │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ │" +"│▶ line 3 │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ │" "│ │ │ │" "│ │ A simple tui to view & control docker containers │ │" "│ │ │ │" "│ │ ( tab ) or ( shift+tab ) change panels │ │" -"│ │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ │" +"│ │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ │" +"│ │ ( ← → ) horizontal scroll across logs │ │" +"│ │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ │" "│ │ ( enter ) send docker container command │ │" "│ │ ( e ) exec into a container │ │" +"│ │ ( f ) force clear the screen & redraw the gui │ │" "│ │ ( h ) toggle this help information - or click heading │ │" "│ │ ( s ) save logs to file │ │" "│ │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ │" @@ -30,15 +31,14 @@ expression: setup.terminal.backend() "│ │ ( 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│" -"│ │ • • │ │ ││ │" -"│ │ •• • • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │" -"│ │• •• ││ │• •• ││ │" -"│ │• • ││ │• • ││ │" +"╰────────────────────────────────────│ ( \ ) toggle log section visibility │────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00%│ ( esc ) close dialog │──────╮╭────────── ports ───────────╮" +"│10.00%│ •• │ ( q ) quit at any time │ ││ ip private public│" +"│ │ • • │ │ ││ 8001 │" +"│ │ •• • │ currently an early work in progress, all and any input appreciated │ ││127.0.0.1 8003 8003│" +"│ │ • • │ https://github.com/mrjackwills/oxker │ ││ │" +"│ │ •• • • │ │ ││ │" +"│ │• •• │ │ ││ │" +"│ │• • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │" "│ │ ││ │ ││ │" "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index e16e720..dd0aac9 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -187,6 +187,7 @@ pub struct GuiState { log_height: u16, rerender: Arc, selected_panel: SelectablePanel, + screen_width: u16, show_logs: bool, status: HashSet, pub info_box_text: Option<(String, Instant)>, @@ -205,6 +206,7 @@ impl GuiState { loading_index: 0, loading_set: HashSet::new(), log_height: 75, + screen_width: 0, rerender: Arc::clone(redraw), selected_panel: SelectablePanel::default(), show_logs, @@ -215,7 +217,7 @@ impl GuiState { 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(); + self.rerender.update_draw(); } } @@ -228,10 +230,20 @@ impl GuiState { self.show_logs = false; self.selected_panel = SelectablePanel::Containers; } - self.rerender.update(); + self.rerender.update_draw(); } } + /// Set the screen width, used for offset char calculations + pub const fn set_screen_width(&mut self, width: u16) { + self.screen_width = width; + } + + /// Get the screen width, used for offset char calculations + pub const fn get_screen_width(&self) -> u16 { + self.screen_width + } + pub const fn get_show_logs(&self) -> bool { self.show_logs } @@ -241,7 +253,7 @@ impl GuiState { if !self.show_logs && self.selected_panel == SelectablePanel::Logs { self.selected_panel = SelectablePanel::Containers; } - self.rerender.update(); + self.rerender.update_draw(); } /// Set the log_height to zero, for now only used by tests @@ -260,6 +272,11 @@ impl GuiState { self.intersect_panel.clear(); } + /// Set the rerender clear to true, to flush the screen and redraw + pub fn set_clear(&self) { + self.rerender.set_clear(); + } + /// Get the currently selected panel pub const fn get_selected_panel(&self) -> SelectablePanel { self.selected_panel @@ -275,7 +292,7 @@ impl GuiState { .first() { self.selected_panel = *data.0; - self.rerender.update(); + self.rerender.update_draw(); } } @@ -348,7 +365,7 @@ impl GuiState { self.status_del(Status::DeleteConfirm); } self.delete_container_id = id; - self.rerender.update(); + self.rerender.update_draw(); } /// Return a copy of the Status HashSet @@ -369,7 +386,7 @@ impl GuiState { } _ => (), } - self.rerender.update(); + self.rerender.update_draw(); } /// Inset the ExecMode into self, and set the Status as exec @@ -378,7 +395,7 @@ impl GuiState { pub fn set_exec_mode(&mut self, mode: ExecMode) { self.exec_mode = Some(mode); self.status.insert(Status::Exec); - self.rerender.update(); + self.rerender.update_draw(); } pub fn get_exec_mode(&self) -> Option { @@ -390,7 +407,7 @@ impl GuiState { pub fn status_push(&mut self, status: Status) { if status != Status::Exec { self.status.insert(status); - self.rerender.update(); + self.rerender.update_draw(); } } @@ -403,7 +420,7 @@ impl GuiState { { self.selected_panel = self.selected_panel.next(); } - self.rerender.update(); + self.rerender.update_draw(); } /// Change to previous selectable panel @@ -415,7 +432,7 @@ impl GuiState { { self.selected_panel = self.selected_panel.prev(); } - self.rerender.update(); + self.rerender.update_draw(); } /// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array @@ -426,7 +443,7 @@ impl GuiState { self.loading_index += 1; } self.loading_set.insert(uuid); - self.rerender.update(); + self.rerender.update_draw(); } pub fn is_loading(&self) -> bool { @@ -459,7 +476,7 @@ impl GuiState { /// Stop the loading_spin function, and reset gui loading status pub fn stop_loading_animation(&mut self, loading_uuid: Uuid) { self.loading_set.remove(&loading_uuid); - self.rerender.update(); + self.rerender.update_draw(); if self.loading_set.is_empty() { self.loading_index = 0; if let Some(h) = &self.loading_handle { @@ -472,12 +489,12 @@ impl GuiState { /// Set info box content pub fn set_info_box(&mut self, text: &str) { self.info_box_text = Some((text.to_owned(), std::time::Instant::now())); - self.rerender.update(); + self.rerender.update_draw(); } /// Remove info box content pub fn reset_info_box(&mut self) { self.info_box_text = None; - self.rerender.update(); + self.rerender.update_draw(); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index de55e09..c46dacf 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -50,7 +50,7 @@ pub struct Ui { input_tx: Sender, is_running: Arc, now: Instant, - redraw: Arc, + rerender: Arc, terminal: Terminal>, } @@ -73,7 +73,7 @@ impl Ui { gui_state: Arc>, input_tx: Sender, is_running: Arc, - redraw: Arc, + rerender: Arc, ) { match Self::setup_terminal() { Ok(mut terminal) => { @@ -85,7 +85,7 @@ impl Ui { input_tx, is_running, now: Instant::now(), - redraw, + rerender, terminal, }; if let Err(e) = ui.draw_ui().await { @@ -169,6 +169,13 @@ impl Ui { Ok(()) } + /// Check if the user has attempt to clear the screen, and if so clear and redraw + fn check_clear(&mut self) { + if self.rerender.get_clear() { + self.terminal.clear().ok(); + self.rerender.update_draw(); + } + } /// Use external docker cli to exec into a container async fn exec(&mut self) { let exec_mode = self.gui_state.lock().get_exec_mode(); @@ -191,7 +198,8 @@ impl Ui { /// Use the previously redrawn time, the current time, the docker_interval, and the redraw struct, to calculate /// if the screen should be redrawn or not fn should_redraw(&self, previous: &mut Instant, docker_interval_ms: u128) -> bool { - let result = self.redraw.swap() || previous.elapsed().as_millis() >= docker_interval_ms; + let result = + self.rerender.swap_draw() || previous.elapsed().as_millis() >= docker_interval_ms; if result { *previous = std::time::Instant::now(); } @@ -205,7 +213,15 @@ impl Ui { let docker_interval_ms = u128::from(self.app_data.lock().config.docker_interval_ms); let mut drawn_at = std::time::Instant::now(); + if let Ok(size) = self.terminal.size() { + self.gui_state.lock().set_screen_width(size.width); + } + while self.is_running.load(Ordering::SeqCst) { + // if self.redraw.get_clear() { + // self.terminal.clear().ok(); + // continue; + // } if self.should_redraw(&mut drawn_at, docker_interval_ms) { let fd = FrameData::from(&*self); @@ -239,18 +255,21 @@ impl Ui { event::MouseEventKind::Down(_) | event::MouseEventKind::ScrollDown | event::MouseEventKind::ScrollUp => { - self.input_tx.send(InputMessages::MouseEvent(m)).await.ok(); + self.input_tx + .send(InputMessages::MouseEvent((m, m.modifiers))) + .await + .ok(); } _ => (), } - } else if let Event::Resize(_, _) = event { + } else if let Event::Resize(width, _) = event { self.gui_state.lock().clear_area_map(); - // self.gui_state.lock().set_window_height(row); - self.terminal.autoresize().ok(); + self.gui_state.lock().set_screen_width(width); } } } + self.check_clear(); } Ok(()) } @@ -279,7 +298,6 @@ pub struct FrameData { filter_by: FilterBy, filter_term: Option, has_containers: bool, - // container_section_height: u16, log_height: u16, show_logs: bool, has_error: Option, @@ -290,13 +308,14 @@ pub struct FrameData { port_max_lens: (usize, usize, usize), ports: Option<(Vec, State)>, selected_panel: SelectablePanel, + scroll_title: Option, sorted_by: Option<(Header, SortedOrder)>, status: HashSet, } impl From<&Ui> for FrameData { fn from(ui: &Ui) -> Self { - let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock()); + let (mut app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock()); let (filter_by, filter_term) = app_data.get_filter(); Self { @@ -317,6 +336,7 @@ impl From<&Ui> for FrameData { log_title: app_data.get_log_title(), port_max_lens: app_data.get_longest_port(), ports: app_data.get_selected_ports(), + scroll_title: app_data.get_scroll_title(gui_data.get_screen_width()), selected_panel: gui_data.get_selected_panel(), sorted_by: app_data.get_sorted(), status: gui_data.get_status(), diff --git a/src/ui/redraw.rs b/src/ui/redraw.rs index 4fd40d2..7593643 100644 --- a/src/ui/redraw.rs +++ b/src/ui/redraw.rs @@ -1,21 +1,35 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[derive(Debug)] -pub struct Rerender(AtomicBool); +pub struct Rerender { + draw: AtomicBool, + clear: AtomicBool, +} impl Rerender { pub const fn new() -> Self { - Self(AtomicBool::new(true)) + Self { + draw: AtomicBool::new(true), + clear: AtomicBool::new(false), + } } - pub fn update(&self) { - self.0.store(true, Ordering::SeqCst); + pub fn update_draw(&self) { + self.draw.store(true, Ordering::SeqCst); } - /// Return the value of the self, and set to false - pub fn swap(&self) -> bool { + pub fn get_clear(&self) -> bool { + self.clear.swap(false, Ordering::SeqCst) + } + + pub fn set_clear(&self) { + self.clear.store(true, Ordering::SeqCst); + } + + /// Return the value of the draw, and set to false + pub fn swap_draw(&self) -> bool { match self - .0 + .draw .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) { Ok(previous_value) => previous_value,