diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0fc5312..fb2d38a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -38,8 +38,7 @@ // VS Code don't watch files under ./target "files.watcherExclude": { "**/target/**": true - }, - "rust-analyzer.checkOnSave.command": "clippy" + } } } }, diff --git a/.github/release-body.md b/.github/release-body.md index cdef158..49666c0 100644 --- a/.github/release-body.md +++ b/.github/release-body.md @@ -1,9 +1,26 @@ -### 2025-04-22 +### 2025-06-18 ### Chores -+ dependencies updated, [bbfd2462a1f45008587b488e8c6049ee76da72f2] ++ .devcontainer updated, [324f8268278081504d5357f2ed89b78ca2c25d04] ++ dependencies updated, [0ace9dd662144a589341779a64d7fcd8de7d9978], [a636007547280b3b3db69374601dbece4bc21eef] ++ Rust 1.87.0 linting, [395b1aa7e997a528e4f21e66f5f859001c1c3ec1], [67e5888e008cfd504c10e47f678f9351c838be99] + +### Docs ++ example config files updated, [63ab7de72897de460f31181c5a42befbee2f91d3], [8fb5ac4a945b75f3fcd118c53be1202ccbc43c59] ++ README.md updated, link to directories crate, closes #65, [c2bfe3296563daf4b7f077469f3eeff6895720b0] + +### Features ++ log panel size configurable, closes #50, use the `-` or `=` keys to change the height of the logs panel, or `\` to toggle visibility. Automatically hide the logs panel using a new config item `show_logs`, see `example_config/*` files for more details, [6edf99e0846bb4134d8ee5b646065b8cda8074d7] ++ build release binaries for aarch64-apple-darwin, closes #62, personally untested on MacOS - but others suggest it works as expected, [e7114d2f5e0ed8935943be64726fc2d90464a777], [2e8500902a515a246f9d9a503b4350849d634978] + +### Fixes ++ merge args color/raw fix, [d198398795698a21d81d3fd20231c482cc346ab5] + +### Refactors ++ reduce cloning of the logs text items, can expect 40-50% reduction in CPU and memory usage in certain common situations, [ecefa302b9ef5320ad4cce0b606aca70a7b459e2] ++ dead code removed, [b40b6b197e4e5fbdab083bc918d1a5d2750597f3] ### Tests -+ fix tests for MacOS, closes #61, [cfc2decd8d237f1ac3f0bdb2b3d5581684064448] ++ add more whole layout tests, [4b81c6caaf12028d7527c3f23cd2de6d1503e223] see CHANGELOG.md for more details diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml index 67e569a..c3e89ca 100644 --- a/.github/workflows/create_release_and_build.yml +++ b/.github/workflows/create_release_and_build.yml @@ -22,6 +22,9 @@ jobs: - target: arm-unknown-linux-musleabihf output_name: linux_armv6.tar.gz + - target: aarch64-apple-darwin + output_name: apple_darwin_aarch64.tar.gz + - target: x86_64-pc-windows-gnu output_name: windows_x86_64.zip @@ -38,8 +41,15 @@ jobs: - name: install cross run: cargo install cross --git https://github.com/cross-rs/cross - # Build binary + # Build binary for arm MacOS using Docker Zigbuild - name: build + if: matrix.target == 'aarch64-apple-darwin' + run: | + docker run --rm -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin + + # Build all other targets using Cross + - name: build + if: matrix.target != 'aarch64-apple-darwin' run: cross build --target ${{ matrix.target }} --release # Compress the output @@ -126,8 +136,8 @@ jobs: docker build --platform linux/arm/v6,linux/arm64,linux/amd64 \ -t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:latest \ -t ${{ secrets.DOCKERHUB_USERNAME }}/oxker:${{env.CURRENT_SEMVER}} \ - -t ghcr.io/${{ github.repository_owner }}/oxker:latest \ - -t ghcr.io/${{ github.repository_owner }}/oxker:${{env.CURRENT_SEMVER}} \ + -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 . diff --git a/.gitignore b/.gitignore index 1a14b40..5388e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target -/releases \ No newline at end of file +/releases +# 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 05cf8d3..5992cb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# v0.10.4 +### 2025-06-18 + +### Chores ++ .devcontainer updated, [324f8268](https://github.com/mrjackwills/oxker/commit/324f8268278081504d5357f2ed89b78ca2c25d04) ++ dependencies updated, [0ace9dd6](https://github.com/mrjackwills/oxker/commit/0ace9dd662144a589341779a64d7fcd8de7d9978), [a6360075](https://github.com/mrjackwills/oxker/commit/a636007547280b3b3db69374601dbece4bc21eef) ++ Rust 1.87.0 linting, [395b1aa7](https://github.com/mrjackwills/oxker/commit/395b1aa7e997a528e4f21e66f5f859001c1c3ec1), [67e5888e](https://github.com/mrjackwills/oxker/commit/67e5888e008cfd504c10e47f678f9351c838be99) + +### Docs ++ example config files updated, [63ab7de7](https://github.com/mrjackwills/oxker/commit/63ab7de72897de460f31181c5a42befbee2f91d3), [8fb5ac4a](https://github.com/mrjackwills/oxker/commit/8fb5ac4a945b75f3fcd118c53be1202ccbc43c59) ++ README.md updated, link to directories crate, closes [#65](https://github.com/mrjackwills/oxker/issues/65), [c2bfe329](https://github.com/mrjackwills/oxker/commit/c2bfe3296563daf4b7f077469f3eeff6895720b0) + +### Features ++ log panel size configurable, closes [#50](https://github.com/mrjackwills/oxker/issues/50), use the `-` or `=` keys to change the height of the logs panel, or `\` to toggle visibility. Automatically hide the logs panel using a new config item `show_logs`, see `example_config/*` files for more details, [6edf99e0](https://github.com/mrjackwills/oxker/commit/6edf99e0846bb4134d8ee5b646065b8cda8074d7) ++ build release binaries for aarch64-apple-darwin, closes [#62](https://github.com/mrjackwills/oxker/issues/62), personally untested on MacOS - but others suggest it works as expected, [e7114d2f](https://github.com/mrjackwills/oxker/commit/e7114d2f5e0ed8935943be64726fc2d90464a777), [2e850090](https://github.com/mrjackwills/oxker/commit/2e8500902a515a246f9d9a503b4350849d634978) + +### Fixes ++ merge args color/raw fix, [d1983987](https://github.com/mrjackwills/oxker/commit/d198398795698a21d81d3fd20231c482cc346ab5) + +### Refactors ++ reduce cloning of the logs text items, can expect 40-50% reduction in CPU and memory usage in certain common situations, [ecefa302](https://github.com/mrjackwills/oxker/commit/ecefa302b9ef5320ad4cce0b606aca70a7b459e2) ++ dead code removed, [b40b6b19](https://github.com/mrjackwills/oxker/commit/b40b6b197e4e5fbdab083bc918d1a5d2750597f3) + +### Tests ++ add more whole layout tests, [4b81c6ca](https://github.com/mrjackwills/oxker/commit/4b81c6caaf12028d7527c3f23cd2de6d1503e223) + # v0.10.3 ### 2025-04-22 diff --git a/Cargo.lock b/Cargo.lock index 51f6e24..e107f9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "allocator-api2" @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -55,36 +55,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -102,9 +102,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -112,7 +112,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -123,15 +123,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bollard" -version = "0.18.1" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" +checksum = "899ca34eb6924d6ec2a77c6f7f5c7339e60fd68235eaf91edd5a15f12958bb06" dependencies = [ "base64", "bollard-stubs", @@ -162,20 +162,21 @@ dependencies = [ [[package]] name = "bollard-stubs" -version = "1.47.1-rc.27.3.1" +version = "1.48.3-rc.28.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" +checksum = "64ea257e555d16a2c01e5593f40b73865cdf12efbceda33c6d14a2d8d1490368" dependencies = [ "serde", + "serde_json", "serde_repr", "serde_with", ] [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "bytes" @@ -206,24 +207,24 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.19" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -234,9 +235,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -244,9 +245,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -258,9 +259,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", @@ -270,15 +271,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "compact_str" @@ -349,7 +350,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.0.5", + "rustix 1.0.7", "signal-hook", "signal-hook-mio", "winapi", @@ -448,7 +449,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -471,6 +472,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "either" version = "1.15.0" @@ -491,9 +498,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -574,20 +581,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -609,9 +616,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -713,12 +720,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", @@ -772,21 +780,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -795,31 +804,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -827,67 +816,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -907,9 +883,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -933,7 +909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "serde", ] @@ -945,14 +921,12 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "insta" -version = "1.42.2" +version = "1.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084" +checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" dependencies = [ "console", - "linked-hash-map", "once_cell", - "pin-project", "similar", ] @@ -992,9 +966,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "jiff-tzdb", @@ -1008,9 +982,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", @@ -1050,9 +1024,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libredox" @@ -1064,12 +1038,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -1084,9 +1052,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" @@ -1096,9 +1064,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1116,34 +1084,34 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -1186,6 +1154,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "option-ext" version = "0.2.0" @@ -1200,7 +1174,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "oxker" -version = "0.10.3" +version = "0.10.4" dependencies = [ "anyhow", "bollard", @@ -1226,9 +1200,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1236,15 +1210,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1259,26 +1233,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1293,9 +1247,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -1306,6 +1260,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1341,9 +1304,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -1371,7 +1334,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -1397,9 +1360,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] @@ -1410,16 +1373,36 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "ref-cast" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustix" @@ -1436,9 +1419,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", @@ -1449,9 +1432,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -1459,6 +1442,18 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1521,9 +1516,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -1542,15 +1537,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -1574,9 +1570,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -1610,24 +1606,21 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1675,9 +1668,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -1686,9 +1679,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -1717,12 +1710,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -1758,9 +1750,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -1768,9 +1760,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -1797,9 +1789,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -1810,9 +1802,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -1822,18 +1814,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.9.0", "serde", @@ -1861,9 +1853,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1872,9 +1864,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -1963,12 +1955,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -1983,12 +1969,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", + "js-sys", "rand", + "wasm-bindgen", ] [[package]] @@ -2008,9 +1996,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -2103,9 +2091,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", @@ -2138,24 +2126,24 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -2166,7 +2154,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2175,7 +2163,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", ] [[package]] @@ -2184,14 +2181,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -2200,42 +2213,84 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2243,10 +2298,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.7.6" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -2260,23 +2321,17 @@ dependencies = [ "bitflags", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -2286,9 +2341,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -2298,18 +2353,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", @@ -2338,10 +2393,21 @@ dependencies = [ ] [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -2350,9 +2416,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a2c42fa..07124b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxker" -version = "0.10.3" +version = "0.10.4" 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.44", features = ["full"] } +tokio = { version = "1.45", features = ["full"] } tokio-util = "0.7" toml = { version = "0.8", default-features = false, features = ["parse"] } tracing = "0.1" tracing-subscriber = "0.3" -uuid = { version = "1.16", features = ["fast-rng", "v4"] } +uuid = { version = "1.17", features = ["fast-rng", "v4"] } [profile.release] lto = true diff --git a/README.md b/README.md index 7a347ad..83837ae 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ In application controls, these, amongst many other settings, can be customized w | ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. | | ```( 0 )``` | Stop sorting.| | ```( F1 )``` or ```( / )``` | Enter filter mode. | +| ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.| +| ```( \ )``` | Toggle the visibility of the logs panel.| | ```( e )``` | Exec into the selected container - not available on Windows.| | ```( h )``` | Toggle help menu.| | ```( m )``` | Toggle mouse capture - if disabled, text on screen can be selected.| @@ -143,7 +145,10 @@ A config file enables the user to persist settings, create a custom keymap, set Examples of the config file, alsong with explanations of each value, can be found in the [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config) directory. `oxker` supports `.toml`,`.json`, and `.jsonc` file formats.

-If not config file is found, `oxker` will create a `config.toml` in the user's local config directory. Command line arguments will take priority over values from the config file. +If no config file is found, a `config.toml` file will be created in an `oxker` directory in the user's local config directory, as found by the [directories crate](https://docs.rs/directories/6.0.0/directories/struct.BaseDirs.html#method.config_local_dir). +
+
+Command line arguments will take priority over values from the config file.

If running an `oxker` container, the default config location will be `/` rather than the automatically detected platform-specific local config directory, and can be mounted as follows; diff --git a/_typos.toml b/_typos.toml index 54ddf6c..6fd8cd3 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,2 +1,5 @@ [default.extend-words] -ratatui = "ratatui" \ No newline at end of file +ratatui = "ratatui" + +[default] +extend-ignore-words-re = ["[(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{40})]"] diff --git a/create_release.sh b/create_release.sh index 7eebeb3..71ad52b 100755 --- a/create_release.sh +++ b/create_release.sh @@ -230,6 +230,18 @@ cross_build_x86_windows() { cross build --target x86_64-pc-windows-gnu --release } +# Build, using zig-build, for Apple silicon +zig_build_aarch64_apple() { + # mkdir /workspace/oxker/target + echo -e "${YELLOW}docker run --rm -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin${RESET}" + docker run --rm -v "$(pwd):/io" -w /io ghcr.io/rust-cross/cargo-zigbuild cargo zigbuild --release --target aarch64-apple-darwin + if ask_yn "sudo chown $(pwd)/target"; then + echo -e "${YELLOW}sudo chown -R vscode:vscode $(pwd)/target${RESET}" + sudo chown -R vscode:vscode "$(pwd)/target" + fi + +} + # Build all releases that GitHub workflow would # This will download GB's of docker images cross_build_all() { @@ -242,6 +254,8 @@ cross_build_all() { ask_continue cross_build_x86_windows ask_continue + zig_build_aarch64_apple + ask_continue } # $1 text to colourise @@ -371,17 +385,17 @@ release_flow() { } build_choice() { - cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16) + cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16) options=( 1 "x86 musl linux" off 2 "aarch64 musl linux" off 3 "armv6 musl linux" off - 4 "x86 windows" off - 5 "all" off + 4 "aarch64 apple" off + 5 "x86 windows" off + 6 "all" off ) choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) exitStatus=$? - clear if [ $exitStatus -ne 0 ]; then exit fi @@ -403,10 +417,14 @@ build_choice() { exit ;; 4) - cross_build_x86_windows + zig_build_aarch64_apple exit ;; 5) + cross_build_x86_windows + exit + ;; + 6) cross_build_all exit ;; @@ -415,7 +433,7 @@ build_choice() { } build_container_choice() { - cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16) + cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16) options=( 1 "x86 " off 2 "aarch64" off @@ -424,7 +442,6 @@ build_container_choice() { ) choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) exitStatus=$? - clear if [ $exitStatus -ne 0 ]; then exit fi @@ -455,7 +472,7 @@ build_container_choice() { } main() { - cmd=(dialog --backtitle "Choose option" --radiolist "choose" 14 80 16) + cmd=(dialog --backtitle "Choose option" --keep-tite --radiolist "choose" 14 80 16) options=( 1 "test" off 2 "release" off @@ -464,7 +481,6 @@ main() { ) choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) exitStatus=$? - clear if [ $exitStatus -ne 0 ]; then exit fi diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index f62f643..230e982 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -29,12 +29,15 @@ // "save_dir": "$HOME", // Force use of docker cli when execing into containers, honestly mostly pointless "use_cli": false, + // Show the logs section - this can be changed during operation with the log_section_toggle key + "show_logs": true, ////////////////// // Custom Keymap // ////////////////// // Available keys are; // 1) a-z and A-Z // 2) 0-9 + // WARNING if using the \ key, it needs to be escaped, e.g. "log_section_toggle": ["\\"] // 3) / \ , . # ' [ ] ; = - // 3) F1-F12 // 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down @@ -144,6 +147,18 @@ // Toggle mouse capture "toggle_mouse_capture": [ "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": [ + "\\" ] }, //////////////////// diff --git a/example_config/example.config.toml b/example_config/example.config.toml index b8d4534..3c71386 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -32,7 +32,7 @@ timezone = "Etc/UTC" # Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.012345678Z # *Should* accept any valid strftime string up to 32 chars, see https://strftime.org/ -timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" +timestamp_format = "%Y-%m-%dT%H:%M:%S.%8f" # Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows # save_dir = "$HOME" @@ -40,6 +40,9 @@ timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" # Force use of docker cli when execing into containers, honestly mostly pointless use_cli = false +# Show the logs section - this can be changed during operation with the log_section_toggle key +show_logs = true + ################# # Custom Keymap # ################# @@ -47,61 +50,68 @@ use_cli = false # Available keys are; # 1) a-z and A-Z # 2) 0-9 +# WARNING if using the \ key, it needs to be escaped, e.g. log_section_toggle = ["\\"] # 3) / \ , . # ' [ ] ; = - # 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 # If any key clashes are found, oxker will revert to it's default keymap [keymap] # Clear any popup boxes, filter panel, or help panel -clear=["c", "esc"] +clear = ["c", "esc"] # Cancel delete - clear also works here -delete_deny=["n"] +delete_deny = ["n"] # Confirm Delete -delete_confirm=["y"] +delete_confirm = ["y"] # Exec into the selected container -exec=["e"] +exec = ["e"] # Enter filter mode -filter_mode=["/", "F1"] +filter_mode = ["/", "F1"] # Quit at anytime quit = ["q"] # Save logs of selected container to file on disk -save_logs=["s"] +save_logs = ["s"] # scroll down a list by many -scroll_down_many=["pagedown"] +scroll_down_many = ["pagedown"] # scroll down a list by one item -scroll_down_one=["down", "j"] +scroll_down_one = ["down", "j"] # scroll down to the end of a list -scroll_end=["end"] +scroll_end = ["end"] # scroll up to the start of a list -scroll_start=["home"] +scroll_start = ["home"] # scroll up a list by many -scroll_up_many=["pageup"] +scroll_up_many = ["pageup"] # scroll up a list by one item -scroll_up_one=["up", "k"] +scroll_up_one = ["up", "k"] # Select next panel -select_next_panel=["tab"] +select_next_panel = ["tab"] # Select previous panel -select_previous_panel=["backtab"] +select_previous_panel = ["backtab"] # Sort the containers based on specific column -sort_by_name=["1"] -sort_by_state=["2"] -sort_by_status=["3"] -sort_by_cpu=["4"] -sort_by_memory=["5"] -sort_by_id=["6"] -sort_by_image=["7"] -sort_by_rx=["8"] -sort_by_tx=["9"] +sort_by_name = ["1"] +sort_by_state = ["2"] +sort_by_status = ["3"] +sort_by_cpu = ["4"] +sort_by_memory = ["5"] +sort_by_id = ["6"] +sort_by_image = ["7"] +sort_by_rx = ["8"] +sort_by_tx = ["9"] # Reset the sorted containers -sort_reset=["0"] +sort_reset = ["0"] # Toggle the help panel -toggle_help=["h"] +toggle_help = ["h"] # Toggle mouse capture -toggle_mouse_capture=["m"] - +toggle_mouse_capture = ["m"] +# Reduce the height of the logs list section +log_section_height_decrease = ["-"] +# Increase the height of the logs list section +log_section_height_increase = ["+"] +# Toggle visibility of the log section +log_section_toggle = ["\\"] ################# # Custom Colors # @@ -119,7 +129,7 @@ toggle_mouse_capture=["m"] # Background color of the entire line background = "magenta" # Animated loading icon at the start of the bar -loading_spinner="white" +loading_spinner = "white" # Text color text = "black" # Text color of a selected header @@ -128,137 +138,136 @@ text_selected = "gray" # The borders around the selectable panels - Containers, Commands, Logs [colors.borders] # Border when selected -selected="lightcyan" +selected = "lightcyan" # Border when not selected -unselected="grey" +unselected = "grey" # The containers sections, in the future more color customization options should be made available in this section [colors.containers] # The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol -icon="white" +icon = "white" # Background color of panel background = "reset" # At the moment, this will only change the color of the name, id, and image columns -text="blue" +text = "blue" # Text color of the RX column -text_rx="#FFE9C1" +text_rx = "#FFE9C1" # Text color of the TX column -text_tx="#CD8C8C" +text_tx = "#CD8C8C" # The logs panel, will only be applied if color_logs is false [colors.logs] # Background color of panel background = "reset" # text color -text="reset" +text = "reset" # Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section [colors.container_state] -dead="red" -exited="red" +dead = "red" +exited = "red" paused = "yellow" -removing ="lightred" -restarting ="lightgreen" -running_healthy ="green" -running_unhealthy="#FFB224" -unknown="red" +removing = "lightred" +restarting = "lightgreen" +running_healthy = "green" +running_unhealthy = "#FFB224" +unknown = "red" # The filter panel [colors.filter] # Background color of panel background = "reset" # color of text -text="gray" +text = "gray" # background color of the selected filter by item (Name/Image/Status/All) -selected_filter_background="gray" +selected_filter_background = "gray" # text color of the selected filter by item (Name/Image/Status/All) -selected_filter_text="black" +selected_filter_text = "black" # Highlighted text color -highlight="magenta" - +highlight = "magenta" # The color the of Docker commands available for each container [colors.commands] # Background color of panel background = "reset" -pause="yellow" -restart="magenta" +pause = "yellow" +restart = "magenta" stop = "red" -delete ="gray" -resume ="blue" -start ="green" +delete = "gray" +resume = "blue" +start = "green" # The cpu chart [colors.chart_cpu] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color? -title="green" +title = "green" # Maximum CPU percentage - again paused & stopped colors not yet customizable -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped colors not yet customizable -points="magenta" +points = "magenta" # The charts y-axis -y_axis="white" +y_axis = "white" # The memory chart [colors.chart_memory] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Maximum memory use - again paused & stopped will use colors.container_state -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped will use colors.container_state -points="cyan" +points = "cyan" # The charts y-axis -y_axis="white" +y_axis = "white" # The ports chart [colors.chart_ports] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Private/Public/IP headings -headings="yellow" +headings = "yellow" # Ports & IP listing text -text="white" +text = "white" # The help popup [colors.popup_help] # Background color -background="magenta" +background = "magenta" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="white" +text_highlight = "white" # The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings [colors.popup_info] # Background color -background="blue" +background = "blue" # Text color -text="white" +text = "white" # The delete popup - used to display a confirmation warning when about to delete a container [colors.popup_delete] # Background color -background="white" +background = "white" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="red" +text_highlight = "red" # The error popup - hopefully you'll never have to see this [colors.popup_error] # Background color -background="red" +background = "red" # Text color -text="white" \ No newline at end of file +text = "white" diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index fa9587f..15f4cd8 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -30,10 +30,18 @@ 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() + } + /// Only return first 8 chars of id, is usually more than enough for uniqueness /// need to update tests to use real ids, or atleast strings of the correct-ish length pub fn get_short(&self) -> String { @@ -76,10 +84,18 @@ 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() + } + pub fn set(&mut self, value: String) { self.0 = value; } @@ -326,6 +342,54 @@ impl From<(&str, &ContainerStatus)> for State { } } +/// Need status, to check if container is unhealthy or not +impl + From<( + &bollard::secret::ContainerSummaryStateEnum, + &ContainerStatus, + )> for State +{ + fn from( + (input, status): ( + &bollard::secret::ContainerSummaryStateEnum, + &ContainerStatus, + ), + ) -> Self { + match input { + bollard::secret::ContainerSummaryStateEnum::DEAD => Self::Dead, + bollard::secret::ContainerSummaryStateEnum::EXITED => Self::Exited, + bollard::secret::ContainerSummaryStateEnum::PAUSED => Self::Paused, + bollard::secret::ContainerSummaryStateEnum::REMOVING => Self::Removing, + bollard::secret::ContainerSummaryStateEnum::RESTARTING => Self::Restarting, + bollard::secret::ContainerSummaryStateEnum::RUNNING => { + if status.unhealthy() { + Self::Running(RunningState::Unhealthy) + } else { + Self::Running(RunningState::Healthy) + } + } + _ => Self::Unknown, + } + } +} + +/// Again, need status, to check if container is unhealthy or not +impl + From<( + Option<&bollard::secret::ContainerSummaryStateEnum>, + &ContainerStatus, + )> for State +{ + fn from( + (input, status): ( + Option<&bollard::secret::ContainerSummaryStateEnum>, + &ContainerStatus, + ), + ) -> Self { + input.map_or(Self::Unknown, |input| Self::from((input, status))) + } +} + /// Again, need status, to check if container is unhealthy or not impl From<(Option, &ContainerStatus)> for State { fn from((input, status): (Option, &ContainerStatus)) -> Self { @@ -544,7 +608,7 @@ impl LogsTz { /// Store the logs alongside a HashSet, each log *should* generate a unique timestamp, /// so if we store the timestamp separately in a HashSet, we can then check if we should insert a log line into the -/// stateful list dependent on whethere the timestamp is in the HashSet or not +/// stateful list dependent on whether the timestamp is in the HashSet or not #[derive(Debug, Clone, PartialEq, Eq)] pub struct Logs { logs: StatefulList>, @@ -570,10 +634,24 @@ impl Logs { } } - pub fn to_vec(&self) -> Vec> { - self.logs.items.clone() + /// Get the logs vec, but instead of cloning to whole vec, only clone items with x of the currently selected index + /// Where x is the abs different of the index plus the panel height & a padding + /// The rest can be just empty list items + pub fn to_vec(&self, height: usize, padding: usize) -> Vec> { + let current_index = self.logs.state.selected().unwrap_or_default(); + self.logs + .items + .iter() + .enumerate() + .map(|(index, item)| { + if current_index.abs_diff(index) <= height + padding { + item.clone() + } else { + ListItem::from("") + } + }) + .collect() } - /// The rest of the methods are basically forwarding from the underlying StatefulList pub fn get_state_title(&self) -> String { self.logs.get_state_title() @@ -594,10 +672,18 @@ impl Logs { self.logs.start(); } + // TODO remove this once zigbuild uses Rust v1.87.0 + #[cfg(target_os = "macos")] + #[allow(clippy::missing_const_for_fn)] pub fn len(&self) -> usize { self.logs.items.len() } + #[cfg(not(target_os = "macos"))] + pub const fn len(&self) -> usize { + self.logs.items.len() + } + pub const fn state(&mut self) -> &mut ListState { &mut self.logs.state } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index cbbc0e3..5fc9f7a 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -14,7 +14,7 @@ use crate::{ ENTRY_POINT, app_error::AppError, config::Config, - ui::{GuiState, Redraw, Status, log_sanitizer}, + ui::{GuiState, Rerender, Status, log_sanitizer}, }; pub use container_state::*; @@ -122,7 +122,7 @@ pub struct AppData { error: Option, filter: Filter, hidden_containers: Vec, - redraw: Arc, + redraw: Arc, sorted_by: Option<(Header, SortedOrder)>, current_sorted_id: Vec, pub config: Config, @@ -137,13 +137,13 @@ pub struct AppData { pub filter: Filter, pub hidden_containers: Vec, pub current_sorted_id: Vec, - pub redraw: Arc, + pub redraw: Arc, pub sorted_by: Option<(Header, SortedOrder)>, } impl AppData { /// Generate a default app_state - pub fn new(config: Config, redraw: &Arc) -> Self { + pub fn new(config: Config, redraw: &Arc) -> Self { Self { config, containers: StatefulList::new(vec![]), @@ -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.set_true(); + self.redraw.update(); 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.set_true(); + self.redraw.update(); } /// 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.set_true(); + self.redraw.update(); } } else if self.current_sorted_id != self.get_current_ids() { self.containers.items.sort_by(|a, b| { @@ -400,17 +400,24 @@ impl AppData { .cmp(&b.created) .then_with(|| a.name.get().cmp(b.name.get())) }); - self.redraw.set_true(); + self.redraw.update(); 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() + } + pub fn get_all_id_state(&self) -> Vec<(State, ContainerId)> { self.containers .items @@ -439,25 +446,25 @@ impl AppData { /// Select the first container pub fn containers_start(&mut self) { self.containers.start(); - self.redraw.set_true(); + self.redraw.update(); } /// select the last container pub fn containers_end(&mut self) { self.containers.end(); - self.redraw.set_true(); + self.redraw.update(); } /// Select the next container pub fn containers_next(&mut self) { self.containers.next(); - self.redraw.set_true(); + self.redraw.update(); } /// select the previous container pub fn containers_previous(&mut self) { self.containers.previous(); - self.redraw.set_true(); + self.redraw.update(); } /// Get ListState of containers @@ -579,7 +586,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.set_true(); + self.redraw.update(); } } @@ -587,7 +594,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.set_true(); + self.redraw.update(); } } @@ -595,7 +602,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.set_true(); + self.redraw.update(); } } @@ -603,7 +610,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.set_true(); + self.redraw.update(); } } @@ -641,7 +648,7 @@ impl AppData { pub fn log_next(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.next(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -649,7 +656,7 @@ impl AppData { pub fn log_previous(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.previous(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -657,7 +664,7 @@ impl AppData { pub fn log_end(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.end(); - self.redraw.set_true(); + self.redraw.update(); } } @@ -665,17 +672,17 @@ impl AppData { pub fn log_start(&mut self) { if let Some(i) = self.get_mut_selected_container() { i.logs.start(); - self.redraw.set_true(); + self.redraw.update(); } } /// Get mutable Vec of current containers logs - pub fn get_logs(&self) -> Vec> { + pub fn get_logs(&self, height: u16, padding: usize) -> Vec> { self.containers .state .selected() .and_then(|i| self.containers.items.get(i)) - .map_or(vec![], |i| i.logs.to_vec()) + .map_or(vec![], |i| i.logs.to_vec(height.into(), padding)) } /// Get mutable Option of the currently selected container Logs state @@ -706,14 +713,14 @@ impl AppData { /// Remove single app_state error pub fn remove_error(&mut self) { self.error = None; - self.redraw.set_true(); + self.redraw.update(); } /// 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.set_true(); + self.redraw.update(); } /// Check if the selected container is a dockerised version of oxker @@ -803,7 +810,7 @@ impl AppData { container.mem_limit.update(mem_limit); } if self.is_selected_container(id) { - self.redraw.set_true(); + self.redraw.update(); } self.sort_containers(); } @@ -841,7 +848,7 @@ impl AppData { if self.containers.items.get(index).is_some() { self.containers.items.remove(index); if self.is_selected_container(id) { - self.redraw.set_true(); + self.redraw.update(); } } } @@ -875,7 +882,12 @@ impl AppData { .map_or(String::new(), std::clone::Clone::clone), ); - 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() @@ -971,7 +983,7 @@ impl AppData { } } if self.is_selected_container(id) { - self.redraw.set_true(); + self.redraw.update(); } } } @@ -1946,7 +1958,7 @@ mod tests { assert_eq!(result.as_ref().unwrap().selected(), Some(2)); assert_eq!(result.unwrap().offset(), 0); - let result = app_data.get_logs(); + let result = app_data.get_logs(4, 1); assert_eq!(result.len(), 3); let result = app_data.get_log_title(); @@ -2317,4 +2329,62 @@ mod tests { let result = app_data.get_log_state(); assert!(result.is_none()); } + + // *************** // + // Get logs method // + // *************** // + + #[test] + /// get_logs() returns vec of item, but the items are empty unless they are in the *visible" zone, based on height, index, and padding + fn test_app_data_update_get_logs() { + let (ids, containers) = gen_containers(); + + let mut app_data = gen_appdata(&containers); + + app_data.containers_start(); + let logs = (0..=999).map(|i| format!("{i} {i}")).collect::>(); + + app_data.update_log_by_id(logs, &ids[0]); + + let result = app_data.get_logs(10, 10); + for (index, item) in result.iter().enumerate() { + if index < 979 { + assert_eq!(item, &ListItem::new("")); + } else { + assert_eq!(item, &ListItem::new(format!("{index}"))); + } + } + + let result = app_data.get_logs(100, 20); + for (index, item) in result.iter().enumerate() { + if index < 879 { + assert_eq!(item, &ListItem::new("")); + } else { + assert_eq!(item, &ListItem::new(format!("{index}"))); + } + } + + app_data.log_start(); + let result = app_data.get_logs(10, 10); + for (index, item) in result.iter().enumerate() { + if index > 20 { + assert_eq!(item, &ListItem::new("")); + } else { + assert_eq!(item, &ListItem::new(format!("{index}"))); + } + } + + for _ in 0..=500 { + app_data.log_next(); + } + + let result = app_data.get_logs(10, 10); + for (index, item) in result.iter().enumerate() { + if (481..=521).contains(&index) { + assert_eq!(item, &ListItem::new(format!("{index}"))); + } else { + assert_eq!(item, &ListItem::new("")); + } + } + } } diff --git a/src/config/config.toml b/src/config/config.toml index b8d4534..457064a 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -32,7 +32,7 @@ timezone = "Etc/UTC" # Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.012345678Z # *Should* accept any valid strftime string up to 32 chars, see https://strftime.org/ -timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" +timestamp_format = "%Y-%m-%dT%H:%M:%S.%8f" # Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows # save_dir = "$HOME" @@ -40,6 +40,9 @@ timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" # Force use of docker cli when execing into containers, honestly mostly pointless use_cli = false +# Show the logs section - this can be changed during operation with the log_section_toggle key +show_logs = true + ################# # Custom Keymap # ################# @@ -47,6 +50,7 @@ use_cli = false # Available keys are; # 1) a-z and A-Z # 2) 0-9 +# WARNING if using the \ key, it needs to be escaped, e.g. log_section_toggle = ["\\"] # 3) / \ , . # ' [ ] ; = - # 3) F1-F12 # 4) backspace, tab, backtab, delete, end, esc, home, insert, pagedown, pageup, left, right, up, down @@ -56,52 +60,56 @@ use_cli = false [keymap] # Clear any popup boxes, filter panel, or help panel -clear=["c", "esc"] +clear = ["c", "esc"] # Cancel delete - clear also works here -delete_deny=["n"] +delete_deny = ["n"] # Confirm Delete -delete_confirm=["y"] +delete_confirm = ["y"] # Exec into the selected container -exec=["e"] +exec = ["e"] # Enter filter mode -filter_mode=["/", "F1"] +filter_mode = ["/", "F1"] # Quit at anytime quit = ["q"] # Save logs of selected container to file on disk -save_logs=["s"] +save_logs = ["s"] # scroll down a list by many -scroll_down_many=["pagedown"] +scroll_down_many = ["pagedown"] # scroll down a list by one item -scroll_down_one=["down", "j"] +scroll_down_one = ["down", "j"] # scroll down to the end of a list -scroll_end=["end"] +scroll_end = ["end"] # scroll up to the start of a list -scroll_start=["home"] +scroll_start = ["home"] # scroll up a list by many -scroll_up_many=["pageup"] +scroll_up_many = ["pageup"] # scroll up a list by one item -scroll_up_one=["up", "k"] +scroll_up_one = ["up", "k"] # Select next panel -select_next_panel=["tab"] +select_next_panel = ["tab"] # Select previous panel -select_previous_panel=["backtab"] +select_previous_panel = ["backtab"] # Sort the containers based on specific column -sort_by_name=["1"] -sort_by_state=["2"] -sort_by_status=["3"] -sort_by_cpu=["4"] -sort_by_memory=["5"] -sort_by_id=["6"] -sort_by_image=["7"] -sort_by_rx=["8"] -sort_by_tx=["9"] +sort_by_name = ["1"] +sort_by_state = ["2"] +sort_by_status = ["3"] +sort_by_cpu = ["4"] +sort_by_memory = ["5"] +sort_by_id = ["6"] +sort_by_image = ["7"] +sort_by_rx = ["8"] +sort_by_tx = ["9"] # Reset the sorted containers -sort_reset=["0"] +sort_reset = ["0"] # Toggle the help panel -toggle_help=["h"] +toggle_help = ["h"] # Toggle mouse capture -toggle_mouse_capture=["m"] - +toggle_mouse_capture = ["m"] +# Reduce the height of the logs list section +log_section_height_decrease = ["-"] +log_section_height_increase = ["+"] +# Toggle visibility of the log section +log_section_toggle = ["\\"] ################# # Custom Colors # @@ -119,7 +127,7 @@ toggle_mouse_capture=["m"] # Background color of the entire line background = "magenta" # Animated loading icon at the start of the bar -loading_spinner="white" +loading_spinner = "white" # Text color text = "black" # Text color of a selected header @@ -128,137 +136,137 @@ text_selected = "gray" # The borders around the selectable panels - Containers, Commands, Logs [colors.borders] # Border when selected -selected="lightcyan" +selected = "lightcyan" # Border when not selected -unselected="grey" +unselected = "grey" # The containers sections, in the future more color customization options should be made available in this section [colors.containers] # The icon use to illustrate which container is currently selected - at the moment the TUI library, ratatui, doesn't seem allow changing the color of the highlight symbol -icon="white" +icon = "white" # Background color of panel background = "reset" # At the moment, this will only change the color of the name, id, and image columns -text="blue" +text = "blue" # Text color of the RX column -text_rx="#FFE9C1" +text_rx = "#FFE9C1" # Text color of the TX column -text_tx="#CD8C8C" +text_tx = "#CD8C8C" # The logs panel, will only be applied if color_logs is false [colors.logs] # Background color of panel background = "reset" # text color -text="reset" +text = "reset" # Each state of a container has a color, which is used in multiple places, i.e. chart titles, state/status/cpu/memory columns in the container section [colors.container_state] -dead="red" -exited="red" +dead = "red" +exited = "red" paused = "yellow" -removing ="lightred" -restarting ="lightgreen" -running_healthy ="green" -running_unhealthy="#FFB224" -unknown="red" +removing = "lightred" +restarting = "lightgreen" +running_healthy = "green" +running_unhealthy = "#FFB224" +unknown = "red" # The filter panel [colors.filter] # Background color of panel background = "reset" # color of text -text="gray" +text = "gray" # background color of the selected filter by item (Name/Image/Status/All) -selected_filter_background="gray" +selected_filter_background = "gray" # text color of the selected filter by item (Name/Image/Status/All) -selected_filter_text="black" +selected_filter_text = "black" # Highlighted text color -highlight="magenta" +highlight = "magenta" # The color the of Docker commands available for each container [colors.commands] # Background color of panel background = "reset" -pause="yellow" -restart="magenta" +pause = "yellow" +restart = "magenta" stop = "red" -delete ="gray" -resume ="blue" -start ="green" +delete = "gray" +resume = "blue" +start = "green" # The cpu chart [colors.chart_cpu] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped colors not yet customizable - or could just use state color? -title="green" +title = "green" # Maximum CPU percentage - again paused & stopped colors not yet customizable -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped colors not yet customizable -points="magenta" +points = "magenta" # The charts y-axis -y_axis="white" +y_axis = "white" # The memory chart [colors.chart_memory] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Maximum memory use - again paused & stopped will use colors.container_state -max="#FFB224" +max = "#FFB224" # Points on the chart - again paused & stopped will use colors.container_state -points="cyan" +points = "cyan" # The charts y-axis -y_axis="white" +y_axis = "white" # The ports chart [colors.chart_ports] # Background color of panel background = "reset" # Border color -border="white" +border = "white" # Chart title - only whilst container is running, paused & stopped will use colors.container_state -title="green" +title = "green" # Private/Public/IP headings -headings="yellow" +headings = "yellow" # Ports & IP listing text -text="white" +text = "white" # The help popup [colors.popup_help] # Background color -background="magenta" +background = "magenta" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="white" +text_highlight = "white" # The info popup - used to display small messages - such as saving logs to disk, or change of mouse capture settings [colors.popup_info] # Background color -background="blue" +background = "blue" # Text color -text="white" +text = "white" # The delete popup - used to display a confirmation warning when about to delete a container [colors.popup_delete] # Background color -background="white" +background = "white" # Text color -text="black" +text = "black" # Highlighted text color -text_highlight="red" +text_highlight = "red" # The error popup - hopefully you'll never have to see this [colors.popup_error] # Background color -background="red" +background = "red" # Text color -text="white" \ No newline at end of file +text = "white" diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs index 0de79ec..9847800 100644 --- a/src/config/keymap_parser.rs +++ b/src/config/keymap_parser.rs @@ -39,6 +39,9 @@ optional_config_struct!( delete_confirm, exec, filter_mode, + log_section_height_increase, + log_section_height_decrease, + log_section_toggle, quit, save_logs, scroll_down_many, @@ -70,6 +73,9 @@ config_struct!( delete_confirm, exec, filter_mode, + log_section_height_increase, + log_section_height_decrease, + log_section_toggle, quit, save_logs, scroll_down_many, @@ -98,10 +104,13 @@ impl Keymap { pub const fn new() -> Self { Self { clear: (KeyCode::Char('c'), Some(KeyCode::Esc)), - delete_deny: (KeyCode::Char('n'), None), delete_confirm: (KeyCode::Char('y'), None), + delete_deny: (KeyCode::Char('n'), None), exec: (KeyCode::Char('e'), None), filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))), + log_section_height_decrease: (KeyCode::Char('-'), None), + log_section_height_increase: (KeyCode::Char('='), None), + log_section_toggle: (KeyCode::Char('\\'), None), quit: (KeyCode::Char('q'), None), save_logs: (KeyCode::Char('s'), None), scroll_down_many: (KeyCode::PageDown, None), @@ -112,14 +121,14 @@ impl Keymap { scroll_up_one: (KeyCode::Up, Some(KeyCode::Char('k'))), select_next_panel: (KeyCode::Tab, None), select_previous_panel: (KeyCode::BackTab, None), - sort_by_name: (KeyCode::Char('1'), None), - sort_by_state: (KeyCode::Char('2'), None), - sort_by_status: (KeyCode::Char('3'), None), sort_by_cpu: (KeyCode::Char('4'), None), - sort_by_memory: (KeyCode::Char('5'), None), sort_by_id: (KeyCode::Char('6'), None), sort_by_image: (KeyCode::Char('7'), None), + sort_by_memory: (KeyCode::Char('5'), None), + sort_by_name: (KeyCode::Char('1'), None), sort_by_rx: (KeyCode::Char('8'), None), + sort_by_state: (KeyCode::Char('2'), None), + sort_by_status: (KeyCode::Char('3'), None), sort_by_tx: (KeyCode::Char('9'), None), sort_reset: (KeyCode::Char('0'), None), toggle_help: (KeyCode::Char('h'), None), @@ -162,6 +171,22 @@ impl From> for Keymap { update_keymap(ck.clear, &mut keymap.clear, &mut clash); update_keymap(ck.delete_deny, &mut keymap.delete_deny, &mut clash); update_keymap(ck.delete_confirm, &mut keymap.delete_confirm, &mut clash); + update_keymap( + ck.log_section_height_decrease, + &mut keymap.log_section_height_decrease, + &mut clash, + ); + update_keymap( + ck.log_section_height_increase, + &mut keymap.log_section_height_increase, + &mut clash, + ); + update_keymap( + ck.log_section_toggle, + &mut keymap.log_section_toggle, + &mut clash, + ); + update_keymap(ck.exec, &mut keymap.exec, &mut clash); update_keymap(ck.filter_mode, &mut keymap.filter_mode, &mut clash); update_keymap(ck.quit, &mut keymap.quit, &mut clash); @@ -339,6 +364,8 @@ mod tests { delete_deny: Some(vec!["s".to_owned()]), delete_confirm: None, exec: None, + log_section_height_decrease: None, + log_section_height_increase: None, filter_mode: None, quit: None, save_logs: None, @@ -349,6 +376,7 @@ mod tests { 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, @@ -376,10 +404,13 @@ mod tests { let input = ConfigKeymap { clear: gen_v(("a", "b")), - delete_deny: gen_v(("c", "d")), delete_confirm: gen_v(("e", "f")), + delete_deny: gen_v(("c", "d")), 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")), @@ -390,14 +421,14 @@ mod tests { scroll_up_one: gen_v(("y", "z")), select_next_panel: gen_v(("0", "1")), select_previous_panel: gen_v(("2", "3")), - sort_by_name: gen_v(("4", "5")), - sort_by_state: gen_v(("6", "7")), - sort_by_status: gen_v(("8", "9")), sort_by_cpu: gen_v(("F1", "F12")), - sort_by_memory: gen_v(("/", "\\")), 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")), @@ -410,6 +441,9 @@ mod tests { 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'))), 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'))), diff --git a/src/config/mod.rs b/src/config/mod.rs index 9a31823..24e6877 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -30,6 +30,7 @@ pub struct Config { pub show_timestamp: bool, pub timezone: Option, pub timestamp_format: String, + pub show_logs: bool, pub use_cli: bool, } @@ -51,6 +52,7 @@ impl From<&Args> for Config { timezone: Self::parse_timezone(args.timezone.clone()), timestamp_format: Self::parse_timestamp_format(None), use_cli: args.use_cli, + show_logs: true, } } } @@ -73,6 +75,7 @@ impl From for Config { timezone: Self::parse_timezone(config_file.timezone), timestamp_format: Self::parse_timestamp_format(config_file.timestamp_format), use_cli: config_file.use_cli.unwrap_or(false), + show_logs: config_file.show_logs.unwrap_or(true), } } } @@ -132,6 +135,12 @@ impl Config { if config_from_cli.color_logs != default_args.color { self.color_logs = config_from_cli.color_logs; + self.raw_logs = !self.color_logs; + } + + if config_from_cli.raw_logs != default_args.raw { + self.raw_logs = config_from_cli.raw_logs; + self.color_logs = !self.raw_logs; } if config_from_cli.gui != default_args.gui { diff --git a/src/config/parse_config_file.rs b/src/config/parse_config_file.rs index b198f1b..ead111a 100644 --- a/src/config/parse_config_file.rs +++ b/src/config/parse_config_file.rs @@ -75,6 +75,7 @@ pub struct ConfigFile { pub timestamp_format: Option, pub timezone: Option, pub use_cli: Option, + pub show_logs: Option, } impl ConfigFile { diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 499a647..18025f9 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -1,9 +1,7 @@ use bollard::{ Docker, - container::{ - ListContainersOptions, LogsOptions, MemoryStatsStats, RemoveContainerOptions, - StartContainerOptions, Stats, StatsOptions, - }, + query_parameters::{ListContainersOptions, LogsOptions, RemoveContainerOptions, StatsOptions}, + secret::ContainerStatsResponse, service::ContainerSummary, }; use futures_util::StreamExt; @@ -75,31 +73,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; } @@ -107,6 +118,9 @@ impl DockerData { cpu_percentage } + /// Get a single docker stat in order to update mem and cpu usage + /// don't take &self, so that can tokio::spawn into it's own thread + /// remove if from spawns hashmap when complete /// Get a single docker stat in order to update mem and cpu usage /// don't take &self, so that can tokio::spawn into it's own thread /// remove if from spawns hashmap when complete @@ -128,20 +142,23 @@ impl DockerData { ) .take(1); + // some err here 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)), ) @@ -149,26 +166,22 @@ 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) - }; + let (rx, tx) = stats.networks.as_ref().map_or((0, 0), |i| { + ( + i.rx_bytes.unwrap_or_default(), + i.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, ); @@ -203,7 +216,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() })) @@ -242,11 +255,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() }); @@ -363,14 +376,31 @@ 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() { @@ -445,119 +475,73 @@ impl DockerData { #[cfg(test)] #[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); } @@ -565,37 +549,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); } @@ -603,38 +578,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); } @@ -642,36 +607,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); } @@ -679,38 +636,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/mod.rs b/src/input_handler/mod.rs index a52c8dd..4c17a9a 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -5,7 +5,8 @@ use std::{ time::SystemTime, }; -use bollard::container::LogsOptions; +use bollard::query_parameters::LogsOptions; +// use bollard::container::LogsOptions; use cansi::v3::categorise_text; use crossterm::{ event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}, @@ -187,7 +188,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, @@ -288,23 +289,15 @@ impl InputHandler { /// 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) { - self.gui_state.lock().next_panel(); - if self.app_data.lock().get_container_len() == 0 - && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands - { - self.gui_state.lock().next_panel(); - } + self.gui_state.lock().selectable_panel_next(&self.app_data); } /// Change to previously selected panel /// Need to skip the commands planel if there no are current containers running fn previous_panel_key(&self) { - self.gui_state.lock().previous_panel(); - if self.app_data.lock().get_container_len() == 0 - && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands - { - self.gui_state.lock().previous_panel(); - } + self.gui_state + .lock() + .selectable_panel_previous(&self.app_data); } fn scroll_start_key(&self) { @@ -458,9 +451,25 @@ impl InputHandler { } } + // Increase the log panel height + fn log_panel_height_increase(&self) { + self.gui_state.lock().log_height_increase(); + } + + // Decrease the log panel height + fn log_panel_height_decrease(&self) { + self.gui_state.lock().log_height_decrease(); + } + + // Toggle visibility of the log panel + fn log_panel_toggle(&self) { + self.gui_state.lock().toggle_show_logs(); + } + /// Handle button presses in all other scenarios async fn handle_others(&mut self, key_code: KeyCode) { self.handle_sort(key_code); + // shift key plus arrows match key_code { _ if self.keymap.exec.0 == key_code || self.keymap.exec.1 == Some(key_code) => { self.exec_key().await; @@ -477,6 +486,23 @@ impl InputHandler { { self.mouse_capture_key(); } + _ if self.keymap.log_section_height_decrease.0 == key_code + || self.keymap.log_section_height_decrease.1 == Some(key_code) => + { + self.log_panel_height_decrease(); + } + + _ if self.keymap.log_section_height_increase.0 == key_code + || self.keymap.log_section_height_increase.1 == Some(key_code) => + { + self.log_panel_height_increase(); + } + + _ if self.keymap.log_section_toggle.0 == key_code + || self.keymap.log_section_toggle.1 == Some(key_code) => + { + self.log_panel_toggle(); + } _ if self.keymap.save_logs.0 == key_code || self.keymap.save_logs.1 == Some(key_code) => diff --git a/src/main.rs b/src/main.rs index 6a0142d..64ed988 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ mod exec; mod input_handler; mod ui; -use ui::{GuiState, Redraw, Status, Ui}; +use ui::{GuiState, Rerender, Status, Ui}; use crate::docker_data::DockerMessage; @@ -98,10 +98,10 @@ fn handler_init( async fn main() { setup_tracing(); let config = config::Config::new(); - let redraw = Arc::new(Redraw::new()); + let redraw = Arc::new(Rerender::new()); let app_data = Arc::new(Mutex::new(AppData::new(config.clone(), &redraw))); - let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw))); + let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw, config.show_logs))); let is_running = Arc::new(AtomicBool::new(true)); let (docker_tx, docker_rx) = tokio::sync::mpsc::channel(32); @@ -149,7 +149,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}; @@ -159,7 +159,7 @@ mod tests { RunningState, State, StatefulList, }, config::{AppColors, Config, Keymap}, - ui::Redraw, + ui::Rerender, }; /// Default test config, has timestamps turned off @@ -179,6 +179,7 @@ mod tests { timestamp_format: "HH:MM:SS.NNNNN dd-mm-yyyy".to_owned(), show_timestamp: false, use_cli: false, + show_logs: true, timezone: None, } } @@ -207,7 +208,7 @@ mod tests { current_sorted_id: vec![], error: None, sorted_by: None, - redraw: Arc::new(Redraw::new()), + redraw: Arc::new(Rerender::new()), filter: Filter::new(), config: gen_config(), } @@ -227,6 +228,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}")), @@ -242,7 +244,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/commands.rs b/src/ui/draw_blocks/commands.rs index 146313d..805ae0f 100644 --- a/src/ui/draw_blocks/commands.rs +++ b/src/ui/draw_blocks/commands.rs @@ -241,7 +241,10 @@ mod tests { } // Control panel now selected, should have a blue border - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal diff --git a/src/ui/draw_blocks/containers.rs b/src/ui/draw_blocks/containers.rs index a5c7395..a63283c 100644 --- a/src/ui/draw_blocks/containers.rs +++ b/src/ui/draw_blocks/containers.rs @@ -157,7 +157,10 @@ mod tests { let mut setup = test_setup(40, 6, true, true); setup.app_data.lock().containers = StatefulList::new(vec![]); - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); let colors = setup.app_data.lock().config.app_colors; @@ -184,7 +187,10 @@ mod tests { } } - setup.gui_state.lock().previous_panel(); + setup + .gui_state + .lock() + .selectable_panel_previous(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup @@ -255,7 +261,10 @@ mod tests { } // Change selected panel, border is now no longer blue - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal diff --git a/src/ui/draw_blocks/headers.rs b/src/ui/draw_blocks/headers.rs index 792ccf4..0e24e22 100644 --- a/src/ui/draw_blocks/headers.rs +++ b/src/ui/draw_blocks/headers.rs @@ -154,10 +154,9 @@ fn draw_columns( }) .collect::>(); - let container_splits = header_data.iter().map(|i| i.2).collect::>(); let headers_section = Layout::default() .direction(Direction::Horizontal) - .constraints(container_splits) + .constraints(header_data.iter().map(|i| i.2)) .split(split_bar[1]); for (index, (paragraph, header, _)) in header_data.into_iter().enumerate() { @@ -196,19 +195,18 @@ pub fn draw( let column_width = usize::from(area.width).saturating_sub(help_width); let column_width = if column_width > 0 { column_width } else { 1 }; - let splits = if fd.has_containers { - vec![ - Constraint::Max(4), - Constraint::Max(column_width.try_into().unwrap_or_default()), - Constraint::Max(help_width.try_into().unwrap_or_default()), - ] - } else { - CONSTRAINT_100.to_vec() - }; let split_bar = Layout::default() .direction(Direction::Horizontal) - .constraints(splits) + .constraints(if fd.has_containers { + vec![ + Constraint::Max(4), + Constraint::Max(column_width.try_into().unwrap_or_default()), + Constraint::Max(help_width.try_into().unwrap_or_default()), + ] + } else { + CONSTRAINT_100.to_vec() + }) .split(area); draw_loading_spinner(colors, f, fd, split_bar[0]); diff --git a/src/ui/draw_blocks/help.rs b/src/ui/draw_blocks/help.rs index 35ede46..a28cbbe 100644 --- a/src/ui/draw_blocks/help.rs +++ b/src/ui/draw_blocks/help.rs @@ -85,6 +85,7 @@ impl HelpInfo { } /// Generate the button information span + metadata + #[allow(clippy::too_many_lines)] fn gen_keymap_info(colors: AppColors, zone: Option<&TimeZone>, show_timestamp: bool) -> Self { let button_item = |x: &str| Self::highlighted_text_span(&format!(" ( {x} ) "), colors); let button_desc = |x: &str| Self::text_span(x, colors); @@ -152,6 +153,16 @@ impl HelpInfo { button_item("1 - 9"), button_desc("sort by header - or click header"), ]), + Line::from(vec![ + space(), + button_item("- ="), + button_desc("change log section height"), + ]), + Line::from(vec![ + space(), + button_item("\\"), + button_desc("toggle log section visibility"), + ]), Line::from(vec![ space(), button_item("esc"), @@ -212,15 +223,6 @@ impl HelpInfo { fn custom_text<'a>(colors: AppColors, _keymap: &Keymap, zone: Option<&TimeZone>) -> Line<'a> { let highlighted = |x: &str| Self::highlighted_text_span(x, colors); let text = |x: &str| Self::text_span(x, colors); - - // if keymap != &Keymap::new() { - // op.push(highlighted("customised keymap, ")); - // } - - // if colors != AppColors::new() { - // op.push(highlighted("customised app colors, ")); - // }; - let zone = zone.and_then(|i| i.iana_name()).unwrap_or("Etc/UTC"); Line::from(Vec::from([text("logs timezone: "), highlighted(zone)])).centered() } @@ -295,6 +297,15 @@ impl HelpInfo { or_secondary(km.sort_by_image, "sort containers by image"), or_secondary(km.sort_by_rx, "sort containers by rx"), or_secondary(km.sort_by_tx, "sort containers by tx"), + or_secondary( + km.log_section_height_decrease, + "decrease log section height", + ), + or_secondary( + km.log_section_height_increase, + "increase log section height", + ), + or_secondary(km.log_section_toggle, "toggle log section visibility"), or_secondary(km.clear, "close dialog"), or_secondary(km.quit, "quit at any time"), ]; @@ -426,7 +437,7 @@ mod tests { #[test] /// This will cause issues once the version has more than the current 5 chars (0.5.0) fn test_draw_blocks_help() { - let mut setup = test_setup(87, 33, true, true); + let mut setup = test_setup(87, 35, true, true); let tz = setup.app_data.lock().config.timezone.clone(); setup @@ -448,29 +459,29 @@ mod tests { 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 | 32, _) | (0..=33, 0 | 86) => { + (0 | 34, _) | (0..=33, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } // border is black on magenta - (1 | 31, _) | (1..=31, 1 | 85) => { + (1 | 32, _) | (1..=31, 1 | 85) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - // oxker logo && description - (2..=10, 2..=85) | (12, 19..=66) - // button in the brackets + // oxker logo && description + (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, 2..=8) + | (17..=20 | 22 | 25 | 27, 2..=8) | (21, 2..=9 | 12..=18) - | (24, 2..=10) => { + | (24 | 26, 2..=10) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); } // The URL is white and underlined - (28, 25..=60) => { + (30, 25..=60) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); @@ -488,7 +499,7 @@ mod tests { #[test] /// Test that the help panel gets drawn with custom colors fn test_draw_blocks_help_custom_colors() { - let mut setup = test_setup(87, 33, true, true); + let mut setup = test_setup(87, 35, true, true); let mut colors = AppColors::new(); let tz = setup.app_data.lock().config.timezone.clone(); @@ -515,29 +526,29 @@ mod tests { for (result_cell_index, result_cell) in result_row.iter().enumerate() { 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 | 32, _) | (0..=33, 0 | 86) => { + (0 | 34, _) | (0..=33, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } - // border is black on magenta - (1 | 31, _) | (1..=31, 1 | 85) => { + // border is red on black + (1 | 32, _) | (1..=31, 1 | 85) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Red); } - // oxker logo && description - (2..=10, 2..=85) | (12, 19..=66) - // button in the brackets + // oxker logo && description + (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, 2..=8) + | (17..=20 | 22 | 25 | 27, 2..=8) | (21, 2..=9 | 12..=18) - | (24, 2..=10) => { + | (24 | 26, 2..=10) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); } // The URL is yellow and underlined - (28, 25..=60) => { + (30, 25..=60) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); @@ -562,6 +573,9 @@ mod tests { 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), save_logs: (KeyCode::Char('m'), None), @@ -607,6 +621,9 @@ mod tests { 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'))), @@ -653,6 +670,9 @@ mod tests { 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), @@ -691,7 +711,7 @@ mod tests { #[test] fn test_draw_blocks_help_show_timezone() { - let mut setup = test_setup(87, 35, true, true); + let mut setup = test_setup(87, 37, true, true); setup .terminal diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs index 835b01a..6381e5b 100644 --- a/src/ui/draw_blocks/logs.rs +++ b/src/ui/draw_blocks/logs.rs @@ -39,7 +39,8 @@ pub fn draw( } f.render_widget(paragraph, area); } else { - let logs = app_data.lock().get_logs(); + let padding = usize::from(area.height / 5); + let logs = app_data.lock().get_logs(area.height, padding); if logs.is_empty() { let mut paragraph = Paragraph::new("no logs found") .block(block) @@ -52,6 +53,7 @@ pub fn draw( let items = List::new(logs) .block(block) .highlight_symbol(RIGHT_ARROW) + .scroll_padding(padding) .highlight_style(Style::default().add_modifier(Modifier::BOLD)); // This should always return Some, as logs is not empty if let Some(log_state) = app_data.lock().get_log_state() { @@ -124,8 +126,14 @@ mod tests { } } - setup.gui_state.lock().next_panel(); - setup.gui_state.lock().next_panel(); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); + setup + .gui_state + .lock() + .selectable_panel_next(&setup.app_data); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); // When selected, has a blue border diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index f729c90..ec06c61 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -127,8 +127,9 @@ pub mod tests { use crate::{ app_data::{AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts}, + app_error::AppError, tests::{gen_appdata, gen_containers}, - ui::{GuiState, Redraw, draw_frame}, + ui::{GuiState, Rerender, Status, draw_frame}, }; use super::FrameData; @@ -152,32 +153,33 @@ pub mod tests { fn from(data: (&Arc>, &Arc>)) -> Self { let (app_data, gui_data) = (data.0.lock(), data.1.lock()); - // set max height for container section, needs +5 to deal with docker commands list and borders - let height = app_data.get_container_len(); - let height = if height < 12 { - u16::try_from(height + 5).unwrap_or_default() - } else { - 12 - }; + // let container_section_height = app_data.get_container_len(); + // let container_section_height = if container_section_height < 12 { + // u16::try_from(container_section_height + 5).unwrap_or_default() + // } else { + // 12 + // }; let (filter_by, filter_term) = app_data.get_filter(); Self { chart_data: app_data.get_chart_data(), - columns: app_data.get_width(), color_logs: app_data.config.color_logs, + columns: app_data.get_width(), + // container_section_height, container_title: app_data.get_container_title(), delete_confirm: gui_data.get_delete_container(), filter_by, filter_term: filter_term.cloned(), has_containers: app_data.get_container_len() > 0, has_error: app_data.get_error(), - height, - ports: app_data.get_selected_ports(), - port_max_lens: app_data.get_longest_port(), + show_logs: gui_data.get_show_logs(), info_text: gui_data.info_box_text.clone(), is_loading: gui_data.is_loading(), loading_icon: gui_data.get_loading().to_string(), + log_height: gui_data.get_log_height(), log_title: app_data.get_log_title(), + port_max_lens: app_data.get_longest_port(), + ports: app_data.get_selected_ports(), selected_panel: gui_data.get_selected_panel(), sorted_by: app_data.get_sorted(), status: gui_data.get_status(), @@ -199,8 +201,8 @@ pub mod tests { app_data.containers_start(); } - let redraw = Arc::new(Redraw::new()); - let gui_state = GuiState::new(&redraw); + let redraw = Arc::new(Rerender::new()); + let gui_state = GuiState::new(&redraw, app_data.config.show_logs); let app_data = Arc::new(Mutex::new(app_data)); let gui_state = Arc::new(Mutex::new(gui_state)); @@ -360,4 +362,189 @@ pub mod tests { assert_snapshot!(setup.terminal.backend()); } + + #[test] + /// Check that the whole layout is drawn correctly when the logs panel is removed + fn test_draw_blocks_whole_layout_no_logs() { + let mut setup = test_setup(160, 30, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + setup.gui_state.lock().log_height_zero(); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } + + #[test] + /// Check that the whole layout is drawn correctly when the logs panel height is ~4 + fn test_draw_blocks_whole_layout_short_height_logs() { + let mut setup = test_setup(160, 30, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + setup.gui_state.lock().log_height_zero(); + + for _ in 0..=3 { + setup.gui_state.lock().log_height_increase(); + } + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } + + #[test] + /// Check that the whole layout is drawn with the help panel visible + fn test_draw_blocks_whole_layout_help_panel() { + let mut setup = test_setup(160, 40, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + + setup.gui_state.lock().status_push(Status::Help); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } + + #[test] + /// Check that the whole layout is drawn with the error box is visible + fn test_draw_blocks_whole_layout_error() { + let mut setup = test_setup(160, 40, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + + setup.app_data.lock().set_error( + AppError::DockerCommand(crate::app_data::DockerCommand::Pause), + &setup.gui_state, + Status::Error, + ); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } + + #[test] + /// Check that the whole layout is drawn with the delete box is visible + fn test_draw_blocks_whole_layout_delete() { + let mut setup = test_setup(160, 40, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + setup + .gui_state + .lock() + .set_delete_container(setup.app_data.lock().get_selected_container_id()); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } + + #[test] + /// Check that the whole layout is drawn with the info box is visible + fn test_draw_blocks_whole_layout_info_box() { + let mut setup = test_setup(160, 40, true, true); + + insert_chart_data(&setup); + insert_logs(&setup); + setup.app_data.lock().containers.items[0] + .ports + .push(ContainerPorts { + ip: Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))), + private: 8003, + public: Some(8003), + }); + let colors = setup.app_data.lock().config.app_colors; + let keymap = setup.app_data.lock().config.keymap.clone(); + setup.gui_state.lock().set_info_box("This is a test"); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + draw_frame(&setup.app_data, colors, &keymap, f, &fd, &setup.gui_state); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } } 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 01c97c2..747eec0 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,5 +1,6 @@ --- source: src/ui/draw_blocks/help.rs +assertion_line: 456 expression: setup.terminal.backend() --- " " @@ -26,6 +27,8 @@ expression: setup.terminal.backend() " │ ( F1 ) or ( / ) enter filter mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " +" │ ( - = ) change log section height │ " +" │ ( \ ) toggle log section visibility │ " " │ ( esc ) close dialog │ " " │ ( q ) quit at any time │ " " │ │ " 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 01c97c2..a0f9ea1 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 @@ -26,6 +26,8 @@ expression: setup.terminal.backend() " │ ( F1 ) or ( / ) enter filter mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " +" │ ( - = ) change log section height │ " +" │ ( \ ) toggle log section visibility │ " " │ ( esc ) close dialog │ " " │ ( q ) quit at any time │ " " │ │ " 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 384ea0a..8cebda0 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,7 +2,6 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " " ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────╮ " " │ │ " " │ 88 │ " @@ -40,12 +39,13 @@ expression: setup.terminal.backend() " │ ( , ) 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 │ " " │ │ " -" │ │ " " ╰────────────────────────────────────────────────────────────────────────────────────────────╯ " -" " 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 03504ba..818fc99 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,7 +2,6 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " " ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " " │ │ " " │ 88 │ " @@ -40,12 +39,13 @@ expression: setup.terminal.backend() " │ ( , ) 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 │ " " │ │ " -" │ │ " " ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " -" " 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 f6dfd9d..1778328 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,7 +2,6 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" " " ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " " │ │ " " │ 88 │ " @@ -40,12 +39,13 @@ expression: setup.terminal.backend() " │ ( , ) 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 │ " " │ │ " -" │ │ " " ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " -" " 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 ea369da..291cc9f 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 @@ -28,6 +28,8 @@ expression: setup.terminal.backend() " │ ( F1 ) or ( / ) enter filter mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " +" │ ( - = ) change log section height │ " +" │ ( \ ) toggle log section visibility │ " " │ ( esc ) close dialog │ " " │ ( q ) quit at any time │ " " │ │ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap index f90c683..df648f6 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout.snap @@ -8,8 +8,6 @@ expression: setup.terminal.backend() "│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" "│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" "│ ││ delete │" -"│ ││ │" -"│ ││ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" "╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮" "│ line 1 │" @@ -25,10 +23,12 @@ expression: setup.terminal.backend() "│ │" "│ │" "│ │" +"│ │" "╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" "╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •••• ││100.00 kB│ ••• ││ ip private public│" -"│ │ ••• • ││ │ ••• • ││ 8001 │" -"│ │•• ••• ││ │•• ••• ││127.0.0.1 8003 8003│" +"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│" +"│ │ •• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │• •• ││ │• •• ││ │" "│ │ ││ │ ││ │" "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap new file mode 100644 index 0000000..37af4fd --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_delete.snap @@ -0,0 +1,44 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" +"│ ││ delete │" +"│ ││ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" +"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮" +"│ line 1 │" +"│ line 2 │" +"│▶ line 3 │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ ╭──────────────────────── Confirm Delete ────────────────────────╮ │" +"│ │ │ │" +"│ │ Are you sure you want to delete container: container_1 │ │" +"│ │ │ │" +"│ │ ╭─────────────────────╮ ╭─────────────────────╮ │ │" +"│ │ │ ( n ) no │ │ ( y ) yes │ │ │" +"│ │ ╰─────────────────────╯ ╰─────────────────────╯ │ │" +"│ ╰────────────────────────────────────────────────────────────────╯ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" +"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│" +"│ │ • • ││ │ •• ││ 8001 │" +"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│" +"│ │ • • ││ │ • • ││ │" +"│ │ •• • • ││ │ •• • • ││ │" +"│ │• •• ││ │• •• ││ │" +"│ │• • ││ │• • ││ │" +"│ │ ││ │ ││ │" +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap new file mode 100644 index 0000000..49919e8 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_error.snap @@ -0,0 +1,44 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" +"│ ││ delete │" +"│ ││ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" +"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮" +"│ line 1 │" +"│ line 2 │" +"│▶ line 3 │" +"│ │" +"│ │" +"│ │" +"│ ╭──────────── Error ─────────────╮ │" +"│ │ │ │" +"│ │ Unable to pause container │ │" +"│ │ │ │" +"│ │ ( c ) clear error │ │" +"│ │ │ │" +"│ │ ( q ) quit oxker │ │" +"│ │ │ │" +"│ │ │ │" +"│ ╰────────────────────────────────╯ │" +"│ │" +"│ │" +"│ │" +"│ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" +"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│" +"│ │ • • ││ │ •• ││ 8001 │" +"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│" +"│ │ • • ││ │ • • ││ │" +"│ │ •• • • ││ │ •• • • ││ │" +"│ │• •• ││ │• •• ││ │" +"│ │• • ││ │• • ││ │" +"│ │ ││ │ ││ │" +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" 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 new file mode 100644 index 0000000..ed89388 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap @@ -0,0 +1,44 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) exit help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 ho╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ ││ stop │" +"│ │ │ ││ delete │" +"│ │ 88 │ ││ │" +"╰────────────────────────────────────│ 88 │────────────────────╯╰──────────────╯" +"╭ Logs 3/3 - container_1 - image_1 ──│ 88 │────────────────────────────────────╮" +"│ line 1 │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ │" +"│ line 2 │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ │" +"│▶ line 3 │ 8b d8 )888( 8888[ 8PP""""""" 88 │ │" +"│ │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ │" +"│ │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ │" +"│ │ │ │" +"│ │ A simple tui to view & control docker containers │ │" +"│ │ │ │" +"│ │ ( tab ) or ( shift+tab ) change panels │ │" +"│ │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ │" +"│ │ ( enter ) send docker container command │ │" +"│ │ ( e ) exec into a container │ │" +"│ │ ( h ) toggle this help information - or click heading │ │" +"│ │ ( s ) save logs to file │ │" +"│ │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ │" +"│ │ ( F1 ) or ( / ) enter filter mode │ │" +"│ │ ( 0 ) stop sort │ │" +"│ │ ( 1 - 9 ) sort by header - or click header │ │" +"│ │ ( - = ) change log section height │ │" +"│ │ ( \ ) toggle log section visibility │ │" +"╰────────────────────────────────────│ ( esc ) close dialog │────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00%│ ( q ) quit at any time │──────╮╭────────── ports ───────────╮" +"│10.00%│ •• │ │ ││ ip private public│" +"│ │ • • │ currently an early work in progress, all and any input appreciated │ ││ 8001 │" +"│ │ •• • │ https://github.com/mrjackwills/oxker │ ││127.0.0.1 8003 8003│" +"│ │ • • │ │ ││ │" +"│ │ •• • • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │" +"│ │• •• ││ │• •• ││ │" +"│ │• • ││ │• • ││ │" +"│ │ ││ │ ││ │" +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap new file mode 100644 index 0000000..a9abbf8 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_info_box.snap @@ -0,0 +1,44 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" +"│ ││ delete │" +"│ ││ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" +"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮" +"│ line 1 │" +"│ line 2 │" +"│▶ line 3 │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"│ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" +"│10.00%│ •• ││100.00 kB│ •• ││ ip private public│" +"│ │ • • ││ │ •• ││ 8001 │" +"│ │ •• • ││ │ •• • ││127.0.0.1 8003 8003│" +"│ │ • • ││ │ • • ││ │" +"│ │ •• • • ││ │ •• • • ││ │" +"│ │• •• ││ │• •• ││ " +"│ │• • ││ │• • ││ This is a test " +"│ │ ││ │ ││ " +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰─────── " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap index 27fe0a0..e0a34fb 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_long_name.snap @@ -8,8 +8,6 @@ expression: setup.terminal.backend() "│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" "│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" "│ ││ delete │" -"│ ││ │" -"│ ││ │" "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰─────────────────╯" "╭ Logs 3/3 - a_long_container_name_for_the_purposes_of_this_test - a_long_image_name_for_the_purposes_of_this_test ──────────────────────────────────────────────────────────────────────────╮" "│ line 1 │" @@ -25,10 +23,12 @@ expression: setup.terminal.backend() "│ │" "│ │" "│ │" +"│ │" "╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" "╭───────────────────────────────── cpu 03.00% ─────────────────────────────────╮╭────────────────────────────── memory 30.00 kB ───────────────────────────────╮╭────────── ports ───────────╮" -"│10.00%│ •••• ││100.00 kB│ ••••• ││ ip private public│" -"│ │ •••• • ││ │ ••• • ││ 8001 │" -"│ │••• •••• ││ │••• ••• ││127.0.0.1 8003 8003│" +"│10.00%│ ••• ││100.00 kB│ •••• ││ ip private public│" +"│ │ ••• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │•• ••• ││ │•• •• ││ │" "│ │ ││ │ ││ │" "╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap new file mode 100644 index 0000000..fd83747 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_no_logs.snap @@ -0,0 +1,34 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" +"│ ││ delete │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" +"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" +"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│" +"│ │ •• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │• •• ││ │• •• ││ │" +"│ │ ││ │ ││ │" +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap new file mode 100644 index 0000000..046a12e --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_short_height_logs.snap @@ -0,0 +1,34 @@ +--- +source: src/ui/draw_blocks/mod.rs +expression: setup.terminal.backend() +--- +" name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) show help " +"╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" +"│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] +"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" +"│ container_3 ✓ running Up 3 hour 00.00% 0.00 kB / 0.00 kB 3 image_3 0.00 kB 0.00 kB ││ stop │" +"│ ││ delete │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"│ ││ │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯╰──────────────╯" +"╭ Logs 3/3 - container_1 - image_1 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮" +"│ line 2 │" +"│▶ line 3 │" +"╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00% ──────────────────────────╮╭─────────────────────── memory 30.00 kB ───────────────────────╮╭────────── ports ───────────╮" +"│10.00%│ ••• ││100.00 kB│ •• ││ ip private public│" +"│ │ •• • ││ │ •• • ││ 8001 │" +"│ │ ••• • • ││ │ ••• • • ││127.0.0.1 8003 8003│" +"│ │• •• ││ │• •• ││ │" +"│ │ ││ │ ││ │" +"╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index 0c9d57a..e16e720 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -9,11 +9,11 @@ use tokio::task::JoinHandle; use uuid::Uuid; use crate::{ - app_data::{ContainerId, Header}, + app_data::{AppData, ContainerId, Header}, exec::ExecMode, }; -use super::Redraw; +use super::Rerender; #[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)] pub enum SelectablePanel { @@ -175,7 +175,7 @@ pub enum Status { /// Global gui_state, stored in an Arc #[derive(Debug)] pub struct GuiState { - delete_container: Option, + delete_container_id: Option, exec_mode: Option, intersect_delete: HashMap, intersect_heading: HashMap, @@ -184,15 +184,17 @@ pub struct GuiState { loading_handle: Option>, loading_index: u8, loading_set: HashSet, - redraw: Arc, + log_height: u16, + rerender: Arc, selected_panel: SelectablePanel, + show_logs: bool, status: HashSet, pub info_box_text: Option<(String, Instant)>, } impl GuiState { - pub fn new(redraw: &Arc) -> Self { + pub fn new(redraw: &Arc, show_logs: bool) -> Self { Self { - delete_container: None, + delete_container_id: None, exec_mode: None, info_box_text: None, intersect_delete: HashMap::new(), @@ -202,11 +204,57 @@ impl GuiState { loading_handle: None, loading_index: 0, loading_set: HashSet::new(), - redraw: Arc::clone(redraw), + log_height: 75, + rerender: Arc::clone(redraw), selected_panel: SelectablePanel::default(), + show_logs, status: HashSet::new(), } } + /// Increase the height of the log panel, then rerender + pub fn log_height_increase(&mut self) { + if self.show_logs && self.log_height <= 75 { + self.log_height = self.log_height.saturating_add(5); + self.rerender.update(); + } + } + + /// Reduce the height of the logs panel, then rerender + /// Unselect logs panel if currently selected + pub fn log_height_decrease(&mut self) { + if self.show_logs { + self.log_height = self.log_height.saturating_sub(5); + if self.log_height == 0 && self.selected_panel == SelectablePanel::Logs { + self.show_logs = false; + self.selected_panel = SelectablePanel::Containers; + } + self.rerender.update(); + } + } + + pub const fn get_show_logs(&self) -> bool { + self.show_logs + } + + pub fn toggle_show_logs(&mut self) { + self.show_logs = !self.show_logs; + if !self.show_logs && self.selected_panel == SelectablePanel::Logs { + self.selected_panel = SelectablePanel::Containers; + } + self.rerender.update(); + } + + /// Set the log_height to zero, for now only used by tests + #[cfg(test)] + pub const fn log_height_zero(&mut self) { + self.log_height = 0; + } + + /// Get the log height, *should* be a u8 between 0 and 80, essentially a percentage + pub const fn get_log_height(&self) -> u16 { + self.log_height + } + /// Clear panels hash map, so on resize can fix the sizes for mouse clicks pub fn clear_area_map(&mut self) { self.intersect_panel.clear(); @@ -227,7 +275,7 @@ impl GuiState { .first() { self.selected_panel = *data.0; - self.redraw.set_true(); + self.rerender.update(); } } @@ -287,7 +335,7 @@ impl GuiState { /// Check if an ContainerId is set in the delete_container field pub fn get_delete_container(&self) -> Option { - self.delete_container.clone() + self.delete_container_id.clone() } /// Set either a ContainerId, or None, to the delete_container field @@ -299,8 +347,8 @@ impl GuiState { self.intersect_delete.clear(); self.status_del(Status::DeleteConfirm); } - self.delete_container = id; - self.redraw.set_true(); + self.delete_container_id = id; + self.rerender.update(); } /// Return a copy of the Status HashSet @@ -321,7 +369,7 @@ impl GuiState { } _ => (), } - self.redraw.set_true(); + self.rerender.update(); } /// Inset the ExecMode into self, and set the Status as exec @@ -330,7 +378,7 @@ impl GuiState { pub fn set_exec_mode(&mut self, mode: ExecMode) { self.exec_mode = Some(mode); self.status.insert(Status::Exec); - self.redraw.set_true(); + self.rerender.update(); } pub fn get_exec_mode(&self) -> Option { @@ -342,20 +390,32 @@ impl GuiState { pub fn status_push(&mut self, status: Status) { if status != Status::Exec { self.status.insert(status); - self.redraw.set_true(); + self.rerender.update(); } } /// Change to next selectable panel - pub fn next_panel(&mut self) { + pub fn selectable_panel_next(&mut self, app_data: &Arc>) { self.selected_panel = self.selected_panel.next(); - self.redraw.set_true(); + if (app_data.lock().get_container_len() == 0 + && self.get_selected_panel() == SelectablePanel::Commands) + || (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs) + { + self.selected_panel = self.selected_panel.next(); + } + self.rerender.update(); } /// Change to previous selectable panel - pub fn previous_panel(&mut self) { + pub fn selectable_panel_previous(&mut self, app_data: &Arc>) { self.selected_panel = self.selected_panel.prev(); - self.redraw.set_true(); + if (app_data.lock().get_container_len() == 0 + && self.get_selected_panel() == SelectablePanel::Commands) + || (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs) + { + self.selected_panel = self.selected_panel.prev(); + } + self.rerender.update(); } /// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array @@ -366,7 +426,7 @@ impl GuiState { self.loading_index += 1; } self.loading_set.insert(uuid); - self.redraw.set_true(); + self.rerender.update(); } pub fn is_loading(&self) -> bool { @@ -399,7 +459,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.redraw.set_true(); + self.rerender.update(); if self.loading_set.is_empty() { self.loading_index = 0; if let Some(h) = &self.loading_handle { @@ -412,12 +472,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.redraw.set_true(); + self.rerender.update(); } /// Remove info box content pub fn reset_info_box(&mut self) { self.info_box_text = None; - self.redraw.set_true(); + self.rerender.update(); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index cc64199..de55e09 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -24,7 +24,7 @@ mod color_match; mod draw_blocks; mod gui_state; mod redraw; -pub use redraw::Redraw; +pub use redraw::Rerender; pub use self::color_match::*; pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status}; @@ -50,7 +50,7 @@ pub struct Ui { input_tx: Sender, is_running: Arc, now: Instant, - redraw: Arc, + redraw: Arc, terminal: Terminal>, } @@ -73,7 +73,7 @@ impl Ui { gui_state: Arc>, input_tx: Sender, is_running: Arc, - redraw: Arc, + redraw: Arc, ) { match Self::setup_terminal() { Ok(mut terminal) => { @@ -245,6 +245,8 @@ impl Ui { } } else if let Event::Resize(_, _) = event { self.gui_state.lock().clear_area_map(); + // self.gui_state.lock().set_window_height(row); + self.terminal.autoresize().ok(); } } @@ -267,6 +269,7 @@ impl Ui { /// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here #[derive(Debug, Clone)] +#[allow(clippy::struct_excessive_bools)] pub struct FrameData { chart_data: Option<(CpuTuple, MemTuple)>, color_logs: bool, @@ -276,8 +279,10 @@ 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, - height: u16, info_text: Option<(String, Instant)>, is_loading: bool, loading_icon: String, @@ -293,14 +298,6 @@ impl From<&Ui> for FrameData { fn from(ui: &Ui) -> Self { let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock()); - // set max height for container section, needs +5 to deal with docker commands list and borders - let height = app_data.get_container_len(); - let height = if height < 12 { - u16::try_from(height + 5).unwrap_or_default() - } else { - 12 - }; - let (filter_by, filter_term) = app_data.get_filter(); Self { chart_data: app_data.get_chart_data(), @@ -312,10 +309,11 @@ impl From<&Ui> for FrameData { filter_term: filter_term.cloned(), has_containers: app_data.get_container_len() > 0, has_error: app_data.get_error(), - height, info_text: gui_data.info_box_text.clone(), is_loading: gui_data.is_loading(), + show_logs: gui_data.get_show_logs(), loading_icon: gui_data.get_loading().to_string(), + log_height: gui_data.get_log_height(), log_title: app_data.get_log_title(), port_max_lens: app_data.get_longest_port(), ports: app_data.get_selected_ports(), @@ -335,57 +333,63 @@ fn draw_frame( fd: &FrameData, gui_state: &Arc>, ) { - let whole_constraints = if fd.status.contains(&Status::Filter) { - vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)] - } else { - vec![Constraint::Max(1), Constraint::Min(1)] - }; - let whole_layout = Layout::default() .direction(Direction::Vertical) - .constraints(whole_constraints) + .constraints(if fd.status.contains(&Status::Filter) { + vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)] + } else { + vec![Constraint::Max(1), Constraint::Min(1)] + }) .split(f.area()); - // Split into 3, containers+controls, logs, then graphs - let upper_main = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Max(fd.height), Constraint::Min(1)].as_ref()) - .split(whole_layout[1]); - - let top_split = if fd.has_containers { - vec![Constraint::Percentage(90), Constraint::Percentage(10)] - } else { - vec![Constraint::Percentage(100)] - }; - // Containers + docker commands - let top_panel = Layout::default() - .direction(Direction::Horizontal) - .constraints(top_split) - .split(upper_main[0]); - - let lower_split = if fd.has_containers { - vec![Constraint::Percentage(70), Constraint::Percentage(30)] - } else { - vec![Constraint::Percentage(100)] - }; - - // Split into 2, logs and charts - let lower_main = Layout::default() - .direction(Direction::Vertical) - .constraints(lower_split) - .split(upper_main[1]); - - draw_blocks::containers::draw(app_data, top_panel[0], colors, f, fd, gui_state); - - draw_blocks::logs::draw(app_data, lower_main[0], colors, f, fd, gui_state); - draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap); - // Draw filter bar + // If required, draw filter bar if let Some(rect) = whole_layout.get(2) { draw_blocks::filter::draw(*rect, colors, f, fd); } + let upper_main = Layout::default() + .direction(Direction::Vertical) + .constraints(if fd.has_containers { + vec![Constraint::Percentage(75), Constraint::Percentage(25)] + } else { + vec![Constraint::Percentage(100), Constraint::Percentage(0)] + }) + .split(whole_layout[1]); + + let containers_logs_section = Layout::default() + .direction(Direction::Vertical) + .constraints(if fd.show_logs { + vec![Constraint::Min(6), Constraint::Percentage(fd.log_height)] + } else { + vec![Constraint::Percentage(100)] + }) + .split(upper_main[0]); + + // Containers + docker commands + let containers_commands = Layout::default() + .direction(Direction::Horizontal) + .constraints(if fd.has_containers { + vec![Constraint::Percentage(90), Constraint::Percentage(10)] + } else { + vec![Constraint::Percentage(100)] + }) + .split(containers_logs_section[0]); + + draw_blocks::containers::draw(app_data, containers_commands[0], colors, f, fd, gui_state); + + if fd.show_logs { + draw_blocks::logs::draw( + app_data, + containers_logs_section[1], + colors, + f, + fd, + gui_state, + ); + } + if let Some(id) = fd.delete_confirm.as_ref() { app_data.lock().get_container_name_by_id(id).map_or_else( || { @@ -400,7 +404,7 @@ fn draw_frame( } // only draw commands + charts if there are containers - if let Some(rect) = top_panel.get(1) { + if let Some(rect) = containers_commands.get(1) { draw_blocks::commands::draw(app_data, *rect, colors, f, fd, gui_state); // Can calculate the max string length here, and then use that to keep the ports section as small as possible (+4 for some padding + border) @@ -411,7 +415,7 @@ fn draw_frame( let lower = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Min(1), Constraint::Max(ports_len)]) - .split(lower_main[1]); + .split(upper_main[1]); draw_blocks::charts::draw(lower[0], colors, f, fd); draw_blocks::ports::draw(lower[1], colors, f, fd); diff --git a/src/ui/redraw.rs b/src/ui/redraw.rs index 32e21a1..4fd40d2 100644 --- a/src/ui/redraw.rs +++ b/src/ui/redraw.rs @@ -1,14 +1,14 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[derive(Debug)] -pub struct Redraw(AtomicBool); +pub struct Rerender(AtomicBool); -impl Redraw { +impl Rerender { pub const fn new() -> Self { Self(AtomicBool::new(true)) } - pub fn set_true(&self) { + pub fn update(&self) { self.0.store(true, Ordering::SeqCst); }