chore: merge release-v0.2.0 into main

This commit is contained in:
Jack Wills
2023-01-21 22:20:50 +00:00
16 changed files with 688 additions and 369 deletions
+2
View File
@@ -16,6 +16,8 @@
"seccomp=unconfined" "seccomp=unconfined"
], ],
"postCreateCommand": "cargo install cross",
"mounts": [ "mounts": [
"source=/etc/timezone,target=/etc/timezone,type=bind,readonly" "source=/etc/timezone,target=/etc/timezone,type=bind,readonly"
], ],
+11 -7
View File
@@ -1,16 +1,20 @@
### 2023-01-03 ### 2023-01-21
### Chores ### Chores
+ dependencies updated, [9b09146aadae5727a5fee4de5fe0c1d70c581c22] + dependencies updated, [8cd199db49186fad6ce432bb277e3a10f0a08d34], [d880b829c123dbe57deccadef97810e45c083737], [66d57c99558ca14d9593d6dbfd5b0e8e5d59055d], [33f9374908942f4a3b90be227fad94ca353cf351], [007d5d83d7f1b93e1e78777a4417b2740db706bd]
+ create_release.sh typos, [9a27d46a044452080144ee1367dc95886b10abf8]
+ dev container post create install cross, [2d253f034182741d434e4bac12317f24221d0d4a]
### Features ### Features
+ `install.sh` script added, for automated platform selection, download, and installation, [7a42eba6b0968314af40ff87bcc42d288f6860bc], [e0703b76a1a28cfe266f130a7f7dec92f1b5ad58] **all potentially considered breaking changes**
+ store Logs in own struct, use a hashset to track timestamps, hopefully closes #11, [657ea2d751a71f05b17547b47c492d5676817336]
### Fixes + Spawn docker commands into own thread, can now execute multiple docker commands at the same time, [9ec43e124a62a80f4e78acba85fc3af5980ce260]
+ If a sort order is set, sort containers on every `update_stats()` execution, [cfdea77594e48c8c20a4d6e6c7ea31c9181361a1] + align memory columns correctly, minimum byte display value now `0.00 kB`, rather than `0 B`, closes #20, [bd7dfcd2c512a527d66a1388f90006988a487186], [51c580010a24de2427373795803936d498dc8cee]
### Refactors ### Refactors
+ input sort executed in app_data struct `sort_by_header()`, [3cdc5fae02097628799209f371ae9292e513e76c] + main.rs tidy up, [97b89349dc2de275ca514a1e6420255a63d775e8]
+ derive Default for GuiState, [9dcd0509efeb464f58fb53d813bd78de2447949d]
+ param reduction, AtomicBool to Relaxed, [0350293de3c00c6e5e5d787b7596bb3413d1cda1]
see <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details see <a href='https://github.com/mrjackwills/oxker/blob/main/CHANGELOG.md'>CHANGELOG.md</a> for more details
Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 106 KiB

+19
View File
@@ -1,3 +1,22 @@
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.2.0'>v0.2.0</a>
### 2023-01-21
### Chores
+ dependencies updated, [8cd199db](https://github.com/mrjackwills/oxker/commit/8cd199db49186fad6ce432bb277e3a10f0a08d34), [d880b829](https://github.com/mrjackwills/oxker/commit/d880b829c123dbe57deccadef97810e45c083737), [66d57c99](https://github.com/mrjackwills/oxker/commit/66d57c99558ca14d9593d6dbfd5b0e8e5d59055d), [33f93749](https://github.com/mrjackwills/oxker/commit/33f9374908942f4a3b90be227fad94ca353cf351), [007d5d83](https://github.com/mrjackwills/oxker/commit/007d5d83d7f1b93e1e78777a4417b2740db706bd)
+ create_release.sh typos, [9a27d46a](https://github.com/mrjackwills/oxker/commit/9a27d46a044452080144ee1367dc95886b10abf8)
+ dev container post create install cross, [2d253f03](https://github.com/mrjackwills/oxker/commit/2d253f034182741d434e4bac12317f24221d0d4a)
### Features
**all potentially considered breaking changes**
+ store Logs in own struct, use a hashset to track timestamps, hopefully closes [#11](https://github.com/mrjackwills/oxker/issues/11), [657ea2d7](https://github.com/mrjackwills/oxker/commit/657ea2d751a71f05b17547b47c492d5676817336)
+ Spawn docker commands into own thread, can now execute multiple docker commands at the same time, [9ec43e12](https://github.com/mrjackwills/oxker/commit/9ec43e124a62a80f4e78acba85fc3af5980ce260)
+ align memory columns correctly, minimum byte display value now `0.00 kB`, rather than `0 B`, closes [#20](https://github.com/mrjackwills/oxker/issues/20), [bd7dfcd2](https://github.com/mrjackwills/oxker/commit/bd7dfcd2c512a527d66a1388f90006988a487186), [51c58001](https://github.com/mrjackwills/oxker/commit/51c580010a24de2427373795803936d498dc8cee)
### Refactors
+ main.rs tidy up, [97b89349](https://github.com/mrjackwills/oxker/commit/97b89349dc2de275ca514a1e6420255a63d775e8)
+ derive Default for GuiState, [9dcd0509](https://github.com/mrjackwills/oxker/commit/9dcd0509efeb464f58fb53d813bd78de2447949d)
+ param reduction, AtomicBool to Relaxed, [0350293d](https://github.com/mrjackwills/oxker/commit/0350293de3c00c6e5e5d787b7596bb3413d1cda1)
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.11'>v0.1.11</a> # <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.1.11'>v0.1.11</a>
### 2023-01-03 ### 2023-01-03
Generated
+283 -76
View File
@@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.68" version = "1.0.68"
@@ -20,6 +29,12 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -28,11 +43,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bollard" name = "bollard"
version = "0.13.0" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82e7850583ead5f8bbef247e2a3c37a19bd576e8420cd262a6711921827e1e5" checksum = "af254ed2da4936ef73309e9597180558821cb16ae9bba4cb24ce6b612d8d80ed"
dependencies = [ dependencies = [
"base64", "base64 0.21.0",
"bollard-stubs", "bollard-stubs",
"bytes", "bytes",
"futures-core", "futures-core",
@@ -46,6 +61,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"serde_repr",
"serde_urlencoded", "serde_urlencoded",
"thiserror", "thiserror",
"tokio", "tokio",
@@ -56,14 +72,20 @@ dependencies = [
[[package]] [[package]]
name = "bollard-stubs" name = "bollard-stubs"
version = "1.42.0-rc.3" version = "1.42.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" checksum = "602bda35f33aeb571cef387dcd4042c643a8bf689d8aaac2cc47ea24cb7bc7e0"
dependencies = [ dependencies = [
"serde", "serde",
"serde_with", "serde_with",
] ]
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.3.0" version = "1.3.0"
@@ -95,10 +117,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "chrono"
version = "4.0.32" version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"num-integer",
"num-traits",
"serde",
"winapi",
]
[[package]]
name = "clap"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"clap_derive", "clap_derive",
@@ -113,9 +148,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.0.21" version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error", "proc-macro-error",
@@ -126,13 +161,29 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.25.0" version = "0.25.0"
@@ -159,36 +210,45 @@ dependencies = [
] ]
[[package]] [[package]]
name = "darling" name = "cxx"
version = "0.13.4" version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e"
dependencies = [ dependencies = [
"darling_core", "cc",
"darling_macro", "cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
] ]
[[package]] [[package]]
name = "darling_core" name = "cxx-build"
version = "0.13.4" version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200"
dependencies = [ dependencies = [
"fnv", "cc",
"ident_case", "codespan-reporting",
"once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "scratch",
"syn", "syn",
] ]
[[package]] [[package]]
name = "darling_macro" name = "cxxbridge-flags"
version = "0.13.4" version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea"
[[package]]
name = "cxxbridge-macro"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e"
dependencies = [ dependencies = [
"darling_core", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
@@ -410,10 +470,28 @@ dependencies = [
] ]
[[package]] [[package]]
name = "ident_case" name = "iana-time-zone"
version = "1.0.1" version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]] [[package]]
name = "idna" name = "idna"
@@ -433,13 +511,14 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown", "hashbrown",
"serde",
] ]
[[package]] [[package]]
name = "io-lifetimes" name = "io-lifetimes"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys",
@@ -463,6 +542,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@@ -475,6 +563,15 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.1.4" version = "0.1.4"
@@ -528,6 +625,25 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.15.0" version = "1.15.0"
@@ -558,7 +674,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "oxker" name = "oxker"
version = "0.1.10" version = "0.1.11"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bollard", "bollard",
@@ -586,9 +702,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.5" version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@@ -667,9 +783,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.49" version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -724,9 +840,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.6" version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@@ -748,6 +864,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.152" version = "1.0.152"
@@ -779,6 +901,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_repr"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -793,24 +926,17 @@ dependencies = [
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "1.14.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" checksum = "30d904179146de381af4c93d3af6ca4984b3152db687dacb9c3c35e86f39809c"
dependencies = [ dependencies = [
"base64 0.13.1",
"chrono",
"hex",
"indexmap",
"serde", "serde",
"serde_with_macros", "serde_json",
] "time",
[[package]]
name = "serde_with_macros"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@@ -896,9 +1022,9 @@ dependencies = [
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.3" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [ dependencies = [
"winapi-util", "winapi-util",
] ]
@@ -932,6 +1058,33 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
dependencies = [
"itoa",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "time-macros"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
dependencies = [
"time-core",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@@ -949,9 +1102,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.23.0" version = "1.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@@ -1058,9 +1211,9 @@ dependencies = [
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]] [[package]]
name = "tui" name = "tui"
@@ -1086,9 +1239,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.8" version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
@@ -1166,6 +1319,60 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@@ -1214,42 +1421,42 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.0" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.42.0" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.42.0" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.42.0" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.42.0" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.0" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.42.0" version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "oxker" name = "oxker"
version = "0.1.11" version = "0.2.0"
edition = "2021" edition = "2021"
authors = ["Jack Wills <email@mrjackwills.com>"] authors = ["Jack Wills <email@mrjackwills.com>"]
description = "A simple tui to view & control docker containers" description = "A simple tui to view & control docker containers"
@@ -13,13 +13,13 @@ categories = ["command-line-utilities"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bollard = "0.13" bollard = "0.14"
cansi = "2.2" cansi = "2.2"
clap={version="4.0", features = ["derive", "unicode", "color"] } clap={version="4.1", features = ["derive", "unicode", "color"] }
crossterm = "0.25" crossterm = "0.25"
futures-util = "0.3" futures-util = "0.3"
parking_lot = {version= "0.12"} parking_lot = {version= "0.12"}
tokio = {version = "1.23", features=["full"]} tokio = {version = "1.24", features=["full"]}
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
tui = "0.19" tui = "0.19"
+1 -1
View File
@@ -51,7 +51,7 @@ rm oxker_linux_x86_64.tar.gz oxker
or, for automatic platform selection, download, and installation (to `$HOME/.local/bin`) or, for automatic platform selection, download, and installation (to `$HOME/.local/bin`)
*One should verify all scripts before running in your shell* *One should always verify script content before running in a shell*
```bash ```bash
curl https://raw.githubusercontent.com/mrjackwills/oxker/main/install.sh | bash curl https://raw.githubusercontent.com/mrjackwills/oxker/main/install.sh | bash
+4 -4
View File
@@ -105,11 +105,11 @@ update_release_body_and_changelog () {
# Update changelog to add links to commits [hex:8](url_with_full_commit) # Update changelog to add links to commits [hex:8](url_with_full_commit)
# "[aaaaaaaaaabbbbbbbbbbccccccccccddddddddd]" -> "[aaaaaaaa](https:/www.../commit/aaaaaaaaaabbbbbbbbbbccccccccccddddddddd)" # "[aaaaaaaaaabbbbbbbbbbccccccccccddddddddd]" -> "[aaaaaaaa](https:/www.../commit/aaaaaaaaaabbbbbbbbbbccccccccccddddddddd)"
sed -i -E "s=(\s)\[([0-9a-f]{8})([0-9a-f]{32})\]= [\2](${GIT_REPO_URL}/commit/\2\3)=g" ./CHANGELOG.md sed -i -E "s=(\s)\[([0-9a-f]{8})([0-9a-f]{32})\]= [\2](${GIT_REPO_URL}/commit/\2\3)=g" CHANGELOG.md
# Update changelog to add links to closed issues - comma included! # Update changelog to add links to closed issues
# "closes #1" -> "closes [#1](https:/www.../issues/1),"" # "closes #1" -> "closes [#1](https:/www.../issues/1)""
sed -i -r -E "s=closes \#([0-9]+)=closes [#\1](${GIT_REPO_URL}/issues/\1)=g" ./CHANGELOG.md sed -i -r -E "s=closes \#([0-9]+)=closes [#\1](${GIT_REPO_URL}/issues/\1)=g" CHANGELOG.md
} }
# update version in cargo.toml, to match selected current version # update version in cargo.toml, to match selected current version
+101 -20
View File
@@ -1,4 +1,8 @@
use std::{cmp::Ordering, collections::VecDeque, fmt}; use std::{
cmp::Ordering,
collections::{HashSet, VecDeque},
fmt,
};
use tui::{ use tui::{
style::Color, style::Color,
@@ -343,8 +347,7 @@ impl fmt::Display for ByteStats {
let p = match as_f64 { let p = match as_f64 {
x if x >= ONE_GB => format!("{y:.2} GB", y = as_f64 / ONE_GB), x if x >= ONE_GB => format!("{y:.2} GB", y = as_f64 / ONE_GB),
x if x >= ONE_MB => format!("{y:.2} MB", y = as_f64 / ONE_MB), x if x >= ONE_MB => format!("{y:.2} MB", y = as_f64 / ONE_MB),
x if x >= ONE_KB => format!("{y:.2} kB", y = as_f64 / ONE_KB), _ => format!("{y:.2} kB", y = as_f64 / ONE_KB),
_ => format!("{} B", self.0),
}; };
write!(f, "{p:>x$}", x = f.width().unwrap_or(1)) write!(f, "{p:>x$}", x = f.width().unwrap_or(1))
} }
@@ -353,6 +356,87 @@ impl fmt::Display for ByteStats {
pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State); pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State);
pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State); pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State);
/// Used to make sure that each log entry, for each container, is unique,
/// will only push a log entry into the logs vec if timetstamp of said log entry isn't in the hashset
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct LogsTz(String);
/// The docker log, which should always contain a timestamp, is in the format `2023-01-14T19:13:30.783138328Z Lorem ipsum dolor sit amet`
/// So just split at the inclusive index of the first space, needs to be inclusive, hence the use of format to at the space, so that we can remove the whole thing when the `-t` flag is set
/// Need to make sure that this isn't an empty string?!
impl From<&String> for LogsTz {
fn from(value: &String) -> Self {
Self(value.split_inclusive(' ').take(1).collect::<String>())
}
}
impl fmt::Display for LogsTz {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Store the logs alongside a HashSet, each log *should* generate a unique timestamp,
/// so if we store the timestamp seperately in a HashSet, we can then check if we should insert a log line into the
/// stateful list dependant on whethere the timestamp is in the HashSet or not
#[derive(Debug, Clone)]
pub struct Logs {
logs: StatefulList<ListItem<'static>>,
tz: HashSet<LogsTz>,
}
impl Default for Logs {
fn default() -> Self {
let mut logs = StatefulList::new(vec![]);
logs.end();
Self {
logs,
tz: HashSet::new(),
}
}
}
impl Logs {
/// Only allow a new log line to be inserted if the log timestamp isn't in the tz HashSet
pub fn insert(&mut self, line: ListItem<'static>, tz: LogsTz) {
if self.tz.insert(tz) {
self.logs.items.push(line);
};
}
pub fn to_vec(&self) -> Vec<ListItem<'static>> {
self.logs.items.clone()
}
/// The rest of the methods are basically forwarding from the underlying StatefulList
pub fn get_state_title(&self) -> String {
self.logs.get_state_title()
}
pub fn next(&mut self) {
self.logs.next();
}
pub fn previous(&mut self) {
self.logs.previous();
}
pub fn end(&mut self) {
self.logs.end();
}
pub fn start(&mut self) {
self.logs.start();
}
pub fn len(&self) -> usize {
self.logs.items.len()
}
pub fn state(&mut self) -> &mut ListState {
&mut self.logs.state
}
}
/// Info for each container /// Info for each container
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ContainerItem { pub struct ContainerItem {
@@ -362,7 +446,7 @@ pub struct ContainerItem {
pub id: ContainerId, pub id: ContainerId,
pub image: String, pub image: String,
pub last_updated: u64, pub last_updated: u64,
pub logs: StatefulList<ListItem<'static>>, pub logs: Logs,
pub mem_limit: ByteStats, pub mem_limit: ByteStats,
pub mem_stats: VecDeque<ByteStats>, pub mem_stats: VecDeque<ByteStats>,
pub name: String, pub name: String,
@@ -386,8 +470,6 @@ impl ContainerItem {
) -> Self { ) -> Self {
let mut docker_controls = StatefulList::new(DockerControls::gen_vec(state)); let mut docker_controls = StatefulList::new(DockerControls::gen_vec(state));
docker_controls.start(); docker_controls.start();
let mut logs = StatefulList::new(vec![]);
logs.end();
Self { Self {
created, created,
cpu_stats: VecDeque::with_capacity(60), cpu_stats: VecDeque::with_capacity(60),
@@ -396,7 +478,7 @@ impl ContainerItem {
image, image,
is_oxker, is_oxker,
last_updated: 0, last_updated: 0,
logs, logs: Logs::default(),
mem_limit: ByteStats::default(), mem_limit: ByteStats::default(),
mem_stats: VecDeque::with_capacity(60), mem_stats: VecDeque::with_capacity(60),
name, name,
@@ -463,15 +545,15 @@ impl ContainerItem {
/// Container information panel headings + widths, for nice pretty formatting /// Container information panel headings + widths, for nice pretty formatting
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Columns { pub struct Columns {
pub state: (Header, usize), pub state: (Header, u8),
pub status: (Header, usize), pub status: (Header, u8),
pub cpu: (Header, usize), pub cpu: (Header, u8),
pub mem: (Header, usize), pub mem: (Header, u8, u8),
pub id: (Header, usize), pub id: (Header, u8),
pub name: (Header, usize), pub name: (Header, u8),
pub image: (Header, usize), pub image: (Header, u8),
pub net_rx: (Header, usize), pub net_rx: (Header, u8),
pub net_tx: (Header, usize), pub net_tx: (Header, u8),
} }
impl Columns { impl Columns {
@@ -480,14 +562,13 @@ impl Columns {
Self { Self {
state: (Header::State, 11), state: (Header::State, 11),
status: (Header::Status, 16), status: (Header::Status, 16),
// 7 to allow for "100.00%"
cpu: (Header::Cpu, 7), cpu: (Header::Cpu, 7),
mem: (Header::Memory, 12), mem: (Header::Memory, 7, 7),
id: (Header::Id, 8), id: (Header::Id, 8),
name: (Header::Name, 4), name: (Header::Name, 4),
image: (Header::Image, 5), image: (Header::Image, 5),
net_rx: (Header::Rx, 5), net_rx: (Header::Rx, 7),
net_tx: (Header::Tx, 5), net_tx: (Header::Tx, 7),
} }
} }
} }
+35 -26
View File
@@ -12,7 +12,6 @@ pub use container_state::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AppData { pub struct AppData {
error: Option<AppError>, error: Option<AppError>,
logs_parsed: bool,
sorted_by: Option<(Header, SortedOrder)>, sorted_by: Option<(Header, SortedOrder)>,
pub args: CliArgs, pub args: CliArgs,
pub containers: StatefulList<ContainerItem>, pub containers: StatefulList<ContainerItem>,
@@ -62,7 +61,6 @@ impl AppData {
args, args,
containers: StatefulList::new(vec![]), containers: StatefulList::new(vec![]),
error: None, error: None,
logs_parsed: false,
sorted_by: None, sorted_by: None,
} }
} }
@@ -193,7 +191,7 @@ impl AppData {
/// Check if the selected container is a dockerised version of oxker /// Check if the selected container is a dockerised version of oxker
/// So that can disallow commands to be send /// So that can disallow commands to be send
/// Is a poor way of implementing this /// Is a shabby way of implementing this
pub fn selected_container_is_oxker(&self) -> bool { pub fn selected_container_is_oxker(&self) -> bool {
if let Some(index) = self.containers.state.selected() { if let Some(index) = self.containers.state.selected() {
if let Some(x) = self.containers.items.get(index) { if let Some(x) = self.containers.items.get(index) {
@@ -352,7 +350,7 @@ impl AppData {
.iter() .iter()
.filter(|i| !i.cpu_stats.is_empty()) .filter(|i| !i.cpu_stats.is_empty())
.count(); .count();
self.logs_parsed && count_is_running == number_with_cpu_status count_is_running == number_with_cpu_status
} }
/// Just get the total number of containers /// Just get the total number of containers
@@ -364,7 +362,7 @@ impl AppData {
/// So can display nicely and evenly /// So can display nicely and evenly
pub fn get_width(&self) -> Columns { pub fn get_width(&self) -> Columns {
let mut output = Columns::new(); let mut output = Columns::new();
let count = |x: &String| x.chars().count(); let count = |x: &String| u8::try_from(x.chars().count()).unwrap_or(12);
// Should probably find a refactor here somewhere // Should probably find a refactor here somewhere
for container in &self.containers.items { for container in &self.containers.items {
@@ -375,11 +373,6 @@ impl AppData {
.unwrap_or(&CpuStats::default()) .unwrap_or(&CpuStats::default())
.to_string(), .to_string(),
); );
let mem_count = count(&format!(
"{} / {}",
container.mem_stats.back().unwrap_or(&ByteStats::default()),
container.mem_limit
));
let rx_count = count(&container.rx.to_string()); let rx_count = count(&container.rx.to_string());
let tx_count = count(&container.tx.to_string()); let tx_count = count(&container.tx.to_string());
@@ -387,6 +380,14 @@ impl AppData {
let name_count = count(&container.name); let name_count = count(&container.name);
let state_count = count(&container.state.to_string()); let state_count = count(&container.state.to_string());
let status_count = count(&container.status); let status_count = count(&container.status);
let mem_current_count = count(
&container
.mem_stats
.back()
.unwrap_or(&ByteStats::default())
.to_string(),
);
let mem_limit_count = count(&container.mem_limit.to_string());
if cpu_count > output.cpu.1 { if cpu_count > output.cpu.1 {
output.cpu.1 = cpu_count; output.cpu.1 = cpu_count;
@@ -394,8 +395,11 @@ impl AppData {
if image_count > output.image.1 { if image_count > output.image.1 {
output.image.1 = image_count; output.image.1 = image_count;
}; };
if mem_count > output.mem.1 { if mem_current_count > output.mem.1 {
output.mem.1 = mem_count; output.mem.1 = mem_current_count;
};
if mem_limit_count > output.mem.2 {
output.mem.2 = mem_limit_count;
}; };
if name_count > output.name.1 { if name_count > output.name.1 {
output.name.1 = name_count; output.name.1 = name_count;
@@ -548,8 +552,8 @@ impl AppData {
if item.image != image { if item.image != image {
item.image = image; item.image = image;
}; };
// else container not known, so make new ContainerItem and push into containers Vec
} else { } else {
// container not known, so make new ContainerItem and push into containers Vec
let container = let container =
ContainerItem::new(created, id, image, is_oxker, name, state, status); ContainerItem::new(created, id, image, is_oxker, name, state, status);
self.containers.items.push(container); self.containers.items.push(container);
@@ -559,34 +563,39 @@ impl AppData {
} }
/// update logs of a given container, based on id /// update logs of a given container, based on id
pub fn update_log_by_id(&mut self, output: &[String], id: &ContainerId) { pub fn update_log_by_id(&mut self, output: Vec<String>, id: &ContainerId) {
let tz = Self::get_systemtime();
let color = self.args.color; let color = self.args.color;
let raw = self.args.raw; let raw = self.args.raw;
if let Some(container) = self.get_container_by_id(id) { let timestamp = self.args.timestamp;
container.last_updated = tz;
let current_len = container.logs.items.len();
for i in output { if let Some(container) = self.get_container_by_id(id) {
container.last_updated = Self::get_systemtime();
let current_len = container.logs.len();
for mut i in output {
let tz = LogsTz::from(&i);
// Strip the timestamp if `-t` flag set
if !timestamp {
i = i.replace(&tz.to_string(), "");
}
let lines = if color { let lines = if color {
log_sanitizer::colorize_logs(i) log_sanitizer::colorize_logs(&i)
} else if raw { } else if raw {
log_sanitizer::raw(i) log_sanitizer::raw(&i)
} else { } else {
log_sanitizer::remove_ansi(i) log_sanitizer::remove_ansi(&i)
}; };
container.logs.items.push(ListItem::new(lines)); container.logs.insert(ListItem::new(lines), tz);
} }
// Set the logs selected row for each container // Set the logs selected row for each container
// Either when no long currently selected, or currently selected (before updated) is already at end // Either when no long currently selected, or currently selected (before updated) is already at end
if container.logs.state.selected().is_none() if container.logs.state().selected().is_none()
|| container.logs.state.selected().map_or(1, |f| f + 1) == current_len || container.logs.state().selected().map_or(1, |f| f + 1) == current_len
{ {
container.logs.end(); container.logs.end();
} }
} }
self.logs_parsed = true;
} }
} }
+90 -87
View File
@@ -3,7 +3,7 @@ use bollard::{
service::ContainerSummary, service::ContainerSummary,
Docker, Docker,
}; };
use futures_util::{Future, StreamExt}; use futures_util::StreamExt;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@@ -33,7 +33,7 @@ enum SpawnId {
/// Cpu & Mem stats take twice as long as the update interval to get a value, so will have two being executed at the same time /// Cpu & Mem stats take twice as long as the update interval to get a value, so will have two being executed at the same time
/// SpawnId::Stats takes container_id and binate value to enable both cycles of the same container_id to be inserted into the hashmap /// SpawnId::Stats takes container_id and binate value to enable both cycles of the same container_id to be inserted into the hashmap
/// Binate value is toggled when all join handles have been spawned off /// Binate value is toggled when all handles have been spawned off
/// Also effectively means that if the docker_update interval minimum will be 1000ms /// Also effectively means that if the docker_update interval minimum will be 1000ms
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
enum Binate { enum Binate {
@@ -56,7 +56,6 @@ pub struct DockerData {
binate: Binate, binate: Binate,
docker: Arc<Docker>, docker: Arc<Docker>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
initialised: bool,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
receiver: Receiver<DockerMessage>, receiver: Receiver<DockerMessage>,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>, spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
@@ -94,12 +93,12 @@ impl DockerData {
/// don't take &self, so that can tokio::spawn into it's own thread /// don't take &self, so that can tokio::spawn into it's own thread
/// remove if from spawns hashmap when complete /// remove if from spawns hashmap when complete
async fn update_container_stat( async fn update_container_stat(
app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker: Arc<Docker>,
id: ContainerId, id: ContainerId,
app_data: Arc<Mutex<AppData>>,
is_running: bool, is_running: bool,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
spawn_id: SpawnId, spawn_id: SpawnId,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
) { ) {
let mut stream = docker let mut stream = docker
.stats( .stats(
@@ -156,18 +155,18 @@ impl DockerData {
let docker = Arc::clone(&self.docker); let docker = Arc::clone(&self.docker);
let app_data = Arc::clone(&self.app_data); let app_data = Arc::clone(&self.app_data);
let spawns = Arc::clone(&self.spawns); let spawns = Arc::clone(&self.spawns);
let spawn_key = SpawnId::Stats((id.clone(), self.binate)); let spawn_id = SpawnId::Stats((id.clone(), self.binate));
self.spawns self.spawns
.lock() .lock()
.entry(spawn_key.clone()) .entry(spawn_id.clone())
.or_insert_with(|| { .or_insert_with(|| {
tokio::spawn(Self::update_container_stat( tokio::spawn(Self::update_container_stat(
app_data,
docker, docker,
id.clone(), id.clone(),
app_data,
*is_running, *is_running,
spawn_id,
spawns, spawns,
spawn_key,
)) ))
}); });
} }
@@ -223,19 +222,17 @@ impl DockerData {
} }
/// Update single container logs /// Update single container logs
/// don't take &self, so that can tokio::spawn into it's own thread /// remove it from spawns hashmap when complete
/// remove if from spawns hashmap when complete
async fn update_log( async fn update_log(
app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker: Arc<Docker>,
id: ContainerId, id: ContainerId,
timestamps: bool,
since: u64, since: u64,
app_data: Arc<Mutex<AppData>>,
spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>, spawns: Arc<Mutex<HashMap<SpawnId, JoinHandle<()>>>>,
) { ) {
let options = Some(LogsOptions::<String> { let options = Some(LogsOptions::<String> {
stdout: true, stdout: true,
timestamps, timestamps: true,
since: i64::try_from(since).unwrap_or_default(), since: i64::try_from(since).unwrap_or_default(),
..Default::default() ..Default::default()
}); });
@@ -243,16 +240,14 @@ impl DockerData {
let mut logs = docker.logs(id.get(), options); let mut logs = docker.logs(id.get(), options);
let mut output = vec![]; let mut output = vec![];
while let Some(value) = logs.next().await { while let Some(Ok(value)) = logs.next().await {
if let Ok(data) = value { let data = value.to_string();
let log_string = data.to_string(); if !data.trim().is_empty() {
if !log_string.trim().is_empty() { output.push(data);
output.push(log_string);
}
} }
} }
spawns.lock().remove(&SpawnId::Log(id.clone())); spawns.lock().remove(&SpawnId::Log(id.clone()));
app_data.lock().update_log_by_id(&output, &id); app_data.lock().update_log_by_id(output, &id);
} }
/// Update all logs, spawn each container into own tokio::spawn thread /// Update all logs, spawn each container into own tokio::spawn thread
@@ -264,14 +259,7 @@ impl DockerData {
let key = SpawnId::Log(id.clone()); let key = SpawnId::Log(id.clone());
self.spawns.lock().insert( self.spawns.lock().insert(
key, key,
tokio::spawn(Self::update_log( tokio::spawn(Self::update_log(app_data, docker, id.clone(), 0, spawns)),
docker,
id.clone(),
self.args.timestamp,
0,
app_data,
spawns,
)),
); );
} }
} }
@@ -290,11 +278,10 @@ impl DockerData {
let app_data = Arc::clone(&self.app_data); let app_data = Arc::clone(&self.app_data);
let spawns = Arc::clone(&self.spawns); let spawns = Arc::clone(&self.spawns);
tokio::spawn(Self::update_log( tokio::spawn(Self::update_log(
app_data,
docker, docker,
container.id.clone(), container.id.clone(),
self.args.timestamp,
container.last_updated, container.last_updated,
app_data,
spawns, spawns,
)) ))
}); });
@@ -305,8 +292,8 @@ impl DockerData {
} }
/// Animate the loading icon /// Animate the loading icon
async fn loading_spin(&mut self, loading_uuid: Uuid) -> JoinHandle<()> { async fn loading_spin(loading_uuid: Uuid, gui_state: &Arc<Mutex<GuiState>>) -> JoinHandle<()> {
let gui_state = Arc::clone(&self.gui_state); let gui_state = Arc::clone(&gui_state);
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
@@ -316,89 +303,106 @@ impl DockerData {
} }
/// Stop the loading_spin function, and reset gui loading status /// Stop the loading_spin function, and reset gui loading status
fn stop_loading_spin(&mut self, handle: &JoinHandle<()>, loading_uuid: Uuid) { fn stop_loading_spin(
gui_state: &Arc<Mutex<GuiState>>,
handle: &JoinHandle<()>,
loading_uuid: Uuid,
) {
handle.abort(); handle.abort();
self.gui_state.lock().remove_loading(loading_uuid); gui_state.lock().remove_loading(loading_uuid);
} }
/// Initialize docker container data, before any messages are received /// Initialize docker container data, before any messages are received
async fn initialise_container_data(&mut self) { async fn initialise_container_data(&mut self) {
self.gui_state.lock().status_push(Status::Init); self.gui_state.lock().status_push(Status::Init);
let loading_uuid = Uuid::new_v4(); let loading_uuid = Uuid::new_v4();
let loading_spin = self.loading_spin(loading_uuid).await; let loading_spin = Self::loading_spin(loading_uuid, &Arc::clone(&self.gui_state)).await;
let all_ids = self.update_all_containers().await; let all_ids = self.update_all_containers().await;
self.update_all_container_stats(&all_ids); self.update_all_container_stats(&all_ids);
// Maybe only do a single one at first?
self.init_all_logs(&all_ids); self.init_all_logs(&all_ids);
if all_ids.is_empty() {
self.initialised = true;
}
// wait until all logs have initialised // wait until all logs have initialised
while !self.initialised { while !self.app_data.lock().initialised(&all_ids) {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
self.initialised = self.app_data.lock().initialised(&all_ids);
} }
self.gui_state.lock().status_del(Status::Init); self.gui_state.lock().status_del(Status::Init);
self.stop_loading_spin(&loading_spin, loading_uuid); Self::stop_loading_spin(&self.gui_state, &loading_spin, loading_uuid);
} }
/// Set the global error as the docker error, and set gui_state to error /// Set the global error as the docker error, and set gui_state to error
fn set_error(&mut self, error: DockerControls) { fn set_error(
self.app_data app_data: &Arc<Mutex<AppData>>,
.lock() error: DockerControls,
.set_error(AppError::DockerCommand(error)); gui_state: &Arc<Mutex<GuiState>>,
self.gui_state.lock().status_push(Status::Error);
}
/// Execute a docker command, will start and stop the loading spinner, and set correct error
async fn exec_docker(
&mut self,
docker_fn: impl Future<Output = Result<(), bollard::errors::Error>> + Send,
control: DockerControls,
) { ) {
let uuid = Uuid::new_v4(); app_data.lock().set_error(AppError::DockerCommand(error));
let loading_spin = self.loading_spin(uuid).await; gui_state.lock().status_push(Status::Error);
if docker_fn.await.is_err() {
self.set_error(control);
};
self.stop_loading_spin(&loading_spin, uuid);
} }
/// Handle incoming messages, container controls & all container information update /// Handle incoming messages, container controls & all container information update
/// Spawn dowcker commands off into own thread
async fn message_handler(&mut self) { async fn message_handler(&mut self) {
while let Some(message) = self.receiver.recv().await { while let Some(message) = self.receiver.recv().await {
let docker = Arc::clone(&self.docker); let docker = Arc::clone(&self.docker);
let gui_state = Arc::clone(&self.gui_state);
let app_data = Arc::clone(&self.app_data);
let uuid = Uuid::new_v4();
match message { match message {
DockerMessage::Pause(id) => { DockerMessage::Pause(id) => {
self.exec_docker(docker.pause_container(id.get()), DockerControls::Pause) tokio::spawn(async move {
.await; let loading_spin = Self::loading_spin(uuid, &gui_state).await;
if docker.pause_container(id.get()).await.is_err() {
Self::set_error(&app_data, DockerControls::Pause, &gui_state);
}
Self::stop_loading_spin(&gui_state, &loading_spin, uuid);
});
self.update_everything().await;
} }
DockerMessage::Restart(id) => { DockerMessage::Restart(id) => {
self.exec_docker( tokio::spawn(async move {
docker.restart_container(id.get(), None), let loading_spin = Self::loading_spin(uuid, &gui_state).await;
DockerControls::Restart, if docker.restart_container(id.get(), None).await.is_err() {
) Self::set_error(&app_data, DockerControls::Restart, &gui_state);
.await; }
Self::stop_loading_spin(&gui_state, &loading_spin, uuid);
});
self.update_everything().await;
} }
DockerMessage::Start(id) => { DockerMessage::Start(id) => {
self.exec_docker( tokio::spawn(async move {
docker.start_container(id.get(), None::<StartContainerOptions<String>>), let loading_spin = Self::loading_spin(uuid, &gui_state).await;
DockerControls::Start, if docker
) .start_container(id.get(), None::<StartContainerOptions<String>>)
.await; .await
.is_err()
{
Self::set_error(&app_data, DockerControls::Start, &gui_state);
}
Self::stop_loading_spin(&gui_state, &loading_spin, uuid);
});
self.update_everything().await;
} }
DockerMessage::Stop(id) => { DockerMessage::Stop(id) => {
self.exec_docker(docker.stop_container(id.get(), None), DockerControls::Stop) tokio::spawn(async move {
.await; let loading_spin = Self::loading_spin(uuid, &gui_state).await;
if docker.stop_container(id.get(), None).await.is_err() {
Self::set_error(&app_data, DockerControls::Stop, &gui_state);
}
Self::stop_loading_spin(&gui_state, &loading_spin, uuid);
});
self.update_everything().await;
} }
DockerMessage::Unpause(id) => { DockerMessage::Unpause(id) => {
self.exec_docker(docker.unpause_container(id.get()), DockerControls::Unpause) tokio::spawn(async move {
.await; let loading_spin = Self::loading_spin(uuid, &gui_state).await;
if docker.unpause_container(id.get()).await.is_err() {
Self::set_error(&app_data, DockerControls::Unpause, &gui_state);
}
Self::stop_loading_spin(&gui_state, &loading_spin, uuid);
});
self.update_everything().await; self.update_everything().await;
} }
DockerMessage::Update => self.update_everything().await, DockerMessage::Update => self.update_everything().await,
@@ -408,7 +412,7 @@ impl DockerData {
.values() .values()
.into_iter() .into_iter()
.for_each(tokio::task::JoinHandle::abort); .for_each(tokio::task::JoinHandle::abort);
self.is_running.store(false, Ordering::SeqCst); self.is_running.store(false, Ordering::Relaxed);
} }
} }
} }
@@ -417,9 +421,9 @@ impl DockerData {
/// Initialise self, and start the message receiving loop /// Initialise self, and start the message receiving loop
pub async fn init( pub async fn init(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Arc<Docker>, docker: Docker,
docker_rx: Receiver<DockerMessage>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
receiver: Receiver<DockerMessage>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
) { ) {
let args = app_data.lock().args; let args = app_data.lock().args;
@@ -427,13 +431,12 @@ impl DockerData {
let mut inner = Self { let mut inner = Self {
app_data, app_data,
args, args,
docker,
gui_state,
initialised: false,
receiver,
spawns: Arc::new(Mutex::new(HashMap::new())),
is_running,
binate: Binate::One, binate: Binate::One,
docker: Arc::new(docker),
gui_state,
is_running,
receiver: docker_rx,
spawns: Arc::new(Mutex::new(HashMap::new())),
}; };
inner.initialise_container_data().await; inner.initialise_container_data().await;
+2 -2
View File
@@ -73,7 +73,7 @@ impl InputHandler {
} }
} }
} }
if !self.is_running.load(Ordering::SeqCst) { if !self.is_running.load(Ordering::Relaxed) {
break; break;
} }
} }
@@ -134,7 +134,7 @@ impl InputHandler {
.lock() .lock()
.status_contains(&[Status::Error, Status::Init]); .status_contains(&[Status::Error, Status::Init]);
if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() { if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() {
self.is_running.store(false, Ordering::SeqCst); self.is_running.store(false, Ordering::Relaxed);
} }
} }
+69 -58
View File
@@ -1,10 +1,13 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![warn(clippy::unused_async, clippy::unwrap_used, clippy::expect_used)] #![warn(clippy::unused_async, clippy::unwrap_used, clippy::expect_used)]
// Wanring - These are indeed pedantic // Wanring - These are indeed pedantic
// #![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
// #![warn(clippy::nursery)] #![warn(clippy::nursery)]
// #![allow(clippy::module_name_repetitions, clippy::doc_markdown, clippy::similar_names)] #![allow(
clippy::module_name_repetitions,
clippy::doc_markdown,
clippy::similar_names
)]
// Only allow when debugging // Only allow when debugging
// #![allow(unused)] // #![allow(unused)]
@@ -12,9 +15,11 @@ use app_data::AppData;
use app_error::AppError; use app_error::AppError;
use bollard::Docker; use bollard::Docker;
use docker_data::DockerData; use docker_data::DockerData;
use input_handler::InputMessages;
use parking_lot::Mutex; use parking_lot::Mutex;
use parse_args::CliArgs; use parse_args::CliArgs;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::{info, Level}; use tracing::{info, Level};
mod app_data; mod app_data;
@@ -26,11 +31,58 @@ mod ui;
use ui::{create_ui, GuiState, Status}; use ui::{create_ui, GuiState, Status};
use crate::docker_data::DockerMessage;
const ENTRY_POINT: &str = "./start_oxker.sh"; const ENTRY_POINT: &str = "./start_oxker.sh";
/// write to file if `-g` is set?
fn setup_tracing() { fn setup_tracing() {
tracing_subscriber::fmt().with_max_level(Level::INFO).init(); tracing_subscriber::fmt().with_max_level(Level::INFO).init();
// TODO write to file? }
/// Create docker daemon handler, and only spawn up the docker data handler if a ping returns non-error
async fn docker_init(
app_data: &Arc<Mutex<AppData>>,
docker_rx: Receiver<DockerMessage>,
gui_state: &Arc<Mutex<GuiState>>,
is_running: &Arc<AtomicBool>,
) {
if let Ok(docker) = Docker::connect_with_socket_defaults() {
if docker.ping().await.is_ok() {
let app_data = Arc::clone(&app_data);
let gui_state = Arc::clone(&gui_state);
let is_running = Arc::clone(&is_running);
tokio::spawn(DockerData::init(
app_data, docker, docker_rx, gui_state, is_running,
));
} else {
app_data.lock().set_error(AppError::DockerConnect);
gui_state.lock().status_push(Status::DockerConnect);
}
} else {
app_data.lock().set_error(AppError::DockerConnect);
gui_state.lock().status_push(Status::DockerConnect);
}
}
/// Create data for, and then spawn a tokio thread, for the input handler
async fn handler_init(
app_data: &Arc<Mutex<AppData>>,
docker_sx: &Sender<DockerMessage>,
gui_state: &Arc<Mutex<GuiState>>,
input_rx: Receiver<InputMessages>,
is_running: &Arc<AtomicBool>,
) {
let input_app_data = Arc::clone(&app_data);
let input_gui_state = Arc::clone(&gui_state);
let input_is_running = Arc::clone(&is_running);
tokio::spawn(input_handler::InputHandler::init(
input_app_data,
input_rx,
docker_sx.clone(),
input_gui_state,
input_is_running,
));
} }
#[tokio::main] #[tokio::main]
@@ -40,67 +92,26 @@ async fn main() {
let app_data = Arc::new(Mutex::new(AppData::default(args))); let app_data = Arc::new(Mutex::new(AppData::default(args)));
let gui_state = Arc::new(Mutex::new(GuiState::default())); let gui_state = Arc::new(Mutex::new(GuiState::default()));
let is_running = Arc::new(AtomicBool::new(true)); let is_running = Arc::new(AtomicBool::new(true));
let docker_app_data = Arc::clone(&app_data);
let docker_gui_state = Arc::clone(&gui_state);
let (docker_sx, docker_rx) = tokio::sync::mpsc::channel(16); let (docker_sx, docker_rx) = tokio::sync::mpsc::channel(16);
// Create docker daemon handler, and only spawn up the docker data handler if ping returns non-error
if let Ok(docker) = Docker::connect_with_socket_defaults() {
if docker.ping().await.is_ok() {
let docker = Arc::new(docker);
let is_running = Arc::clone(&is_running);
tokio::spawn(DockerData::init(
docker_app_data,
docker,
docker_gui_state,
docker_rx,
is_running,
));
} else {
app_data.lock().set_error(AppError::DockerConnect);
docker_gui_state.lock().status_push(Status::DockerConnect);
}
} else {
app_data.lock().set_error(AppError::DockerConnect);
docker_gui_state.lock().status_push(Status::DockerConnect);
}
let input_app_data = Arc::clone(&app_data);
let (input_sx, input_rx) = tokio::sync::mpsc::channel(16); let (input_sx, input_rx) = tokio::sync::mpsc::channel(16);
let input_is_running = Arc::clone(&is_running); docker_init(&app_data, docker_rx, &gui_state, &is_running).await;
let input_gui_state = Arc::clone(&gui_state);
let input_docker_sender = docker_sx.clone();
// Spawn input handling into own tokio thread handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running).await;
tokio::spawn(input_handler::InputHandler::init(
input_app_data,
input_rx,
input_docker_sender,
input_gui_state,
input_is_running,
));
if args.gui { if args.gui {
let update_duration = std::time::Duration::from_millis(u64::from(args.docker_interval)); create_ui(app_data, docker_sx, gui_state, is_running, input_sx)
create_ui( .await
app_data, .unwrap_or(());
input_sx,
is_running,
gui_state,
docker_sx,
update_duration,
)
.await
.unwrap_or(());
} else { } else {
// Debug mode for testing, mostly pointless, doesn't take terminal nor draw gui // Debug mode for testing, mostly pointless, doesn't take terminal
// TODO this needs to be improved to display something actually useful info!("in debug mode");
loop { loop {
info!("in debug mode"); docker_sx.send(DockerMessage::Update).await.unwrap_or(());
tokio::time::sleep(std::time::Duration::from_millis(5000)).await; tokio::time::sleep(std::time::Duration::from_millis(u64::from(
args.docker_interval,
)))
.await;
} }
} }
} }
+33 -30
View File
@@ -55,19 +55,22 @@ fn generate_block<'a>(
.lock() .lock()
.update_heading_map(Region::Panel(panel), area); .update_heading_map(Region::Panel(panel), area);
let current_selected_panel = gui_state.lock().selected_panel; let current_selected_panel = gui_state.lock().selected_panel;
let title = match panel { let mut title = match panel {
SelectablePanel::Containers => { SelectablePanel::Containers => {
format!( format!(
" {} {} ", "{} {}",
panel.title(), panel.title(),
app_data.lock().containers.get_state_title() app_data.lock().containers.get_state_title()
) )
} }
SelectablePanel::Logs => { SelectablePanel::Logs => {
format!(" {} {} ", panel.title(), app_data.lock().get_log_title()) format!("{} {}", panel.title(), app_data.lock().get_log_title())
} }
SelectablePanel::Commands => String::new(), SelectablePanel::Commands => String::new(),
}; };
if !title.is_empty() {
title = format!(" {title} ");
}
let mut block = Block::default() let mut block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
@@ -136,19 +139,21 @@ pub fn containers<B: Backend>(
let state_style = Style::default().fg(i.state.get_color()); let state_style = Style::default().fg(i.state.get_color());
let blue = Style::default().fg(Color::Blue); let blue = Style::default().fg(Color::Blue);
let mems = format!(
"{:>1} / {:>1}",
i.mem_stats.back().unwrap_or(&ByteStats::default()),
i.mem_limit
);
let lines = Spans::from(vec![ let lines = Spans::from(vec![
Span::styled( Span::styled(
format!("{:<width$}", i.state.to_string(), width = widths.state.1), format!(
"{:<width$}",
i.state.to_string(),
width = widths.state.1.into()
),
state_style, state_style,
), ),
Span::styled( Span::styled(
format!("{MARGIN}{:>width$}", i.status, width = &widths.status.1), format!(
"{MARGIN}{:>width$}",
i.status,
width = &widths.status.1.into()
),
state_style, state_style,
), ),
Span::styled( Span::styled(
@@ -156,12 +161,18 @@ pub fn containers<B: Backend>(
"{}{:>width$}", "{}{:>width$}",
MARGIN, MARGIN,
i.cpu_stats.back().unwrap_or(&CpuStats::default()), i.cpu_stats.back().unwrap_or(&CpuStats::default()),
width = &widths.cpu.1 width = &widths.cpu.1.into()
), ),
state_style, state_style,
), ),
Span::styled( Span::styled(
format!("{MARGIN}{mems:>width$}", width = &widths.mem.1), format!(
"{MARGIN}{:>width_current$} / {:>width_limit$}",
i.mem_stats.back().unwrap_or(&ByteStats::default()),
i.mem_limit,
width_current = &widths.mem.1.into(),
width_limit = &widths.mem.2.into()
),
state_style, state_style,
), ),
Span::styled( Span::styled(
@@ -169,24 +180,24 @@ pub fn containers<B: Backend>(
"{}{:>width$}", "{}{:>width$}",
MARGIN, MARGIN,
i.id.get().chars().take(8).collect::<String>(), i.id.get().chars().take(8).collect::<String>(),
width = &widths.id.1 width = &widths.id.1.into()
), ),
blue, blue,
), ),
Span::styled( Span::styled(
format!("{MARGIN}{:>width$}", i.name, width = widths.name.1), format!("{MARGIN}{:>width$}", i.name, width = widths.name.1.into()),
blue, blue,
), ),
Span::styled( Span::styled(
format!("{MARGIN}{:>width$}", i.image, width = widths.image.1), format!("{MARGIN}{:>width$}", i.image, width = widths.image.1.into()),
blue, blue,
), ),
Span::styled( Span::styled(
format!("{MARGIN}{:>width$}", i.rx, width = widths.net_rx.1), format!("{MARGIN}{:>width$}", i.rx, width = widths.net_rx.1.into()),
Style::default().fg(Color::Rgb(255, 233, 193)), Style::default().fg(Color::Rgb(255, 233, 193)),
), ),
Span::styled( Span::styled(
format!("{MARGIN}{:>width$}", i.tx, width = widths.net_tx.1), format!("{MARGIN}{:>width$}", i.tx, width = widths.net_tx.1.into()),
Style::default().fg(Color::Rgb(205, 140, 140)), Style::default().fg(Color::Rgb(205, 140, 140)),
), ),
]); ]);
@@ -226,22 +237,14 @@ pub fn logs<B: Backend>(
.alignment(Alignment::Center); .alignment(Alignment::Center);
f.render_widget(paragraph, area); f.render_widget(paragraph, area);
} else if let Some(index) = index { } else if let Some(index) = index {
let items = app_data.lock().containers.items[index] let items = List::new(app_data.lock().containers.items[index].logs.to_vec())
.logs
.items
.iter()
.enumerate()
.map(|i| i.1.clone())
.collect::<Vec<_>>();
let items = List::new(items)
.block(block) .block(block)
.highlight_symbol(ARROW) .highlight_symbol(ARROW)
.highlight_style(Style::default().add_modifier(Modifier::BOLD)); .highlight_style(Style::default().add_modifier(Modifier::BOLD));
f.render_stateful_widget( f.render_stateful_widget(
items, items,
area, area,
&mut app_data.lock().containers.items[index].logs.state, app_data.lock().containers.items[index].logs.state(),
); );
} else { } else {
let paragraph = Paragraph::new("no logs found") let paragraph = Paragraph::new("no logs found")
@@ -415,7 +418,7 @@ pub fn heading_bar<B: Backend>(
(Header::State, columns.state.1), (Header::State, columns.state.1),
(Header::Status, columns.status.1), (Header::Status, columns.status.1),
(Header::Cpu, columns.cpu.1), (Header::Cpu, columns.cpu.1),
(Header::Memory, columns.mem.1), (Header::Memory, columns.mem.1 + columns.mem.2 + 3),
(Header::Id, columns.id.1), (Header::Id, columns.id.1),
(Header::Name, columns.name.1), (Header::Name, columns.name.1),
(Header::Image, columns.image.1), (Header::Image, columns.image.1),
@@ -426,7 +429,7 @@ pub fn heading_bar<B: Backend>(
let header_data = header_meta let header_data = header_meta
.iter() .iter()
.map(|i| { .map(|i| {
let header_block = gen_header(&i.0, i.1); let header_block = gen_header(&i.0, i.1.into());
(header_block.0, i.0, Constraint::Max(header_block.1)) (header_block.0, i.0, Constraint::Max(header_block.1))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
+9 -20
View File
@@ -7,8 +7,9 @@ use uuid::Uuid;
use crate::app_data::Header; use crate::app_data::Header;
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] #[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
pub enum SelectablePanel { pub enum SelectablePanel {
#[default]
Containers, Containers,
Commands, Commands,
Logs, Logs,
@@ -124,8 +125,9 @@ impl BoxLocation {
} }
/// State for the loading animation /// State for the loading animation
#[derive(Debug, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub enum Loading { pub enum Loading {
#[default]
One, One,
Two, Two,
Three, Three,
@@ -184,7 +186,7 @@ pub enum Status {
} }
/// Global gui_state, stored in an Arc<Mutex> /// Global gui_state, stored in an Arc<Mutex>
#[derive(Debug, Clone)] #[derive(Debug, Default, Clone)]
pub struct GuiState { pub struct GuiState {
panel_map: HashMap<SelectablePanel, Rect>, panel_map: HashMap<SelectablePanel, Rect>,
heading_map: HashMap<Header, Rect>, heading_map: HashMap<Header, Rect>,
@@ -195,19 +197,6 @@ pub struct GuiState {
pub info_box_text: Option<String>, pub info_box_text: Option<String>,
} }
impl GuiState { impl GuiState {
/// Generate a default gui_state
pub fn default() -> Self {
Self {
panel_map: HashMap::new(),
heading_map: HashMap::new(),
loading_icon: Loading::One,
selected_panel: SelectablePanel::Containers,
is_loading: HashSet::new(),
info_box_text: None,
status: HashSet::new(),
}
}
/// Clear panels hash map, so on resize can fix the sizes for mouse clicks /// Clear panels hash map, so on resize can fix the sizes for mouse clicks
pub fn clear_area_map(&mut self) { pub fn clear_area_map(&mut self) {
self.panel_map.clear(); self.panel_map.clear();
@@ -257,12 +246,12 @@ impl GuiState {
status.iter().any(|i| self.status.contains(i)) status.iter().any(|i| self.status.contains(i))
} }
/// Remove a gui_status into the current gui_status hashset /// Remove a gui_status into the current gui_status HashSet
pub fn status_del(&mut self, status: Status) { pub fn status_del(&mut self, status: Status) {
self.status.remove(&status); self.status.remove(&status);
} }
/// Insert a gui_status into the current gui_status hashset /// Insert a gui_status into the current gui_status HashSet
pub fn status_push(&mut self, status: Status) { pub fn status_push(&mut self, status: Status) {
self.status.insert(status); self.status.insert(status);
} }
@@ -277,7 +266,7 @@ impl GuiState {
self.selected_panel = self.selected_panel.prev(); self.selected_panel = self.selected_panel.prev();
} }
/// Insert a new loading_uuid into hashset, and advance the animation by one frame /// Insert a new loading_uuid into HashSet, and advance the animation by one frame
pub fn next_loading(&mut self, uuid: Uuid) { pub fn next_loading(&mut self, uuid: Uuid) {
self.loading_icon = self.loading_icon.next(); self.loading_icon = self.loading_icon.next();
self.is_loading.insert(uuid); self.is_loading.insert(uuid);
@@ -292,7 +281,7 @@ impl GuiState {
} }
} }
/// Remove a loading_uuid from the is_loading hashset /// Remove a loading_uuid from the is_loading HashSet
pub fn remove_loading(&mut self, uuid: Uuid) { pub fn remove_loading(&mut self, uuid: Uuid) {
self.is_loading.remove(&uuid); self.is_loading.remove(&uuid);
} }
+25 -34
View File
@@ -6,13 +6,10 @@ use crossterm::{
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
io, io::{self, Write},
sync::{atomic::Ordering, Arc}, sync::{atomic::Ordering, Arc},
}; };
use std::{ use std::{sync::atomic::AtomicBool, time::Instant};
sync::atomic::AtomicBool,
time::{Duration, Instant},
};
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
use tui::{ use tui::{
backend::{Backend, CrosstermBackend}, backend::{Backend, CrosstermBackend},
@@ -34,11 +31,10 @@ use crate::{
/// Take control of the terminal in order to draw gui /// Take control of the terminal in order to draw gui
pub async fn create_ui( pub async fn create_ui(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
sender: Sender<InputMessages>,
is_running: Arc<AtomicBool>,
gui_state: Arc<Mutex<GuiState>>,
docker_sx: Sender<DockerMessage>, docker_sx: Sender<DockerMessage>,
update_duration: Duration, gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>,
sender: Sender<InputMessages>,
) -> Result<()> { ) -> Result<()> {
enable_raw_mode()?; enable_raw_mode()?;
let mut stdout = io::stdout(); let mut stdout = io::stdout();
@@ -47,17 +43,14 @@ pub async fn create_ui(
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
let res = run_app( let res = run_app(
&mut terminal,
app_data, app_data,
sender,
is_running,
gui_state,
docker_sx, docker_sx,
update_duration, gui_state,
is_running,
sender,
&mut terminal,
) )
.await; .await;
terminal.clear()?;
disable_raw_mode()?; disable_raw_mode()?;
execute!( execute!(
terminal.backend_mut(), terminal.backend_mut(),
@@ -69,45 +62,43 @@ pub async fn create_ui(
if let Err(err) = res { if let Err(err) = res {
println!("{err}"); println!("{err}");
} }
std::io::stdout().flush().unwrap_or(());
Ok(()) Ok(())
} }
/// Run a loop to draw the gui /// Run a loop to draw the gui
async fn run_app<B: Backend + Send>( async fn run_app<B: Backend + Send>(
terminal: &mut Terminal<B>,
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
sender: Sender<InputMessages>,
is_running: Arc<AtomicBool>,
gui_state: Arc<Mutex<GuiState>>,
docker_sx: Sender<DockerMessage>, docker_sx: Sender<DockerMessage>,
update_duration: Duration, gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>,
sender: Sender<InputMessages>,
terminal: &mut Terminal<B>,
) -> Result<(), AppError> { ) -> Result<(), AppError> {
let update_duration =
std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval));
let input_poll_rate = std::time::Duration::from_millis(75); let input_poll_rate = std::time::Duration::from_millis(75);
let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]); let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]);
let mut now = Instant::now();
if status_dockerconnect { if status_dockerconnect {
let mut seconds = 5; let mut seconds = 5;
loop { loop {
if seconds < 1 { if seconds < 1 {
is_running.store(false, Ordering::SeqCst);
break; break;
} }
if now.elapsed() >= std::time::Duration::from_secs(1) {
seconds -= 1;
now = Instant::now();
}
if terminal if terminal
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) .draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds)))
.is_err() .is_err()
{ {
return Err(AppError::Terminal); return Err(AppError::Terminal);
} }
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
seconds -= 1;
} }
} else { } else {
let mut now = Instant::now(); while is_running.load(Ordering::Relaxed) {
loop {
if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() {
return Err(AppError::Terminal);
}
// TODO could only draw if in gui mode, that way all inputs & docker commands will run, and can just trace!("{event"}) all over the place
// refactor this into own function, so can be called without drawing to the terminal
if crossterm::event::poll(input_poll_rate).unwrap_or(false) { if crossterm::event::poll(input_poll_rate).unwrap_or(false) {
if let Ok(event) = event::read() { if let Ok(event) = event::read() {
if let Event::Key(key) = event { if let Event::Key(key) = event {
@@ -131,12 +122,12 @@ async fn run_app<B: Backend + Send>(
docker_sx.send(DockerMessage::Update).await.unwrap_or(()); docker_sx.send(DockerMessage::Update).await.unwrap_or(());
now = Instant::now(); now = Instant::now();
} }
if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() {
if !is_running.load(Ordering::SeqCst) { return Err(AppError::Terminal);
break;
} }
} }
} }
terminal.clear().unwrap_or(());
Ok(()) Ok(())
} }