diff --git a/Cargo.lock b/Cargo.lock index 5722ea321..018c3bff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,19 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi-to-tui" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42366bb9d958f042bf58f0a85e1b2d091997c1257ca49bddd7e4827aadc65fd" +dependencies = [ + "nom 8.0.0", + "ratatui-core", + "simdutf8", + "smallvec", + "thiserror 2.0.18", +] + [[package]] name = "anstream" version = "1.0.0" @@ -76,11 +89,26 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -95,6 +123,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -175,12 +212,42 @@ dependencies = [ "smallvec", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bs58" version = "0.5.1" @@ -196,6 +263,18 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "bytes" version = "1.12.0" @@ -207,9 +286,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" dependencies = [ "serde_core", ] @@ -218,12 +297,14 @@ dependencies = [ name = "cascade" version = "0.1.0-beta4-dev" dependencies = [ + "ansi-to-tui", "anstream", "camino", "cascade-api", "clap", "futures-util", "jiff", + "ratatui", "reqwest", "tokio", "tracing", @@ -297,11 +378,20 @@ dependencies = [ "url", ] +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" -version = "1.2.64" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "shlex", @@ -321,13 +411,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", - "cpufeatures", - "rand_core", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] [[package]] @@ -374,7 +464,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -389,18 +479,50 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "constant_time_eq" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -410,6 +532,12 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -435,6 +563,53 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.13.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf", +] + [[package]] name = "daemonbase" version = "0.1.5" @@ -444,7 +619,7 @@ dependencies = [ "clap", "jiff", "log", - "nix", + "nix 0.31.3", "serde", "syslog", "time", @@ -472,7 +647,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.118", ] [[package]] @@ -483,9 +658,47 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", ] +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + [[package]] name = "deranged" version = "0.5.8" @@ -495,6 +708,38 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.118", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.6" @@ -503,7 +748,16 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", ] [[package]] @@ -525,7 +779,7 @@ dependencies = [ "octseq", "openssl", "parking_lot", - "rand", + "rand 0.10.1", "ring", "rustversion", "secrecy", @@ -558,7 +812,7 @@ checksum = "6fef7ef74e413e36d5364db163ca577ccb56f2f74377705d5f920ee3e1544127" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -596,7 +850,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -615,18 +869,66 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -719,7 +1021,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -751,6 +1053,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -762,6 +1074,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -770,8 +1094,8 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi", - "rand_core", + "r-efi 6.0.0", + "rand_core 0.10.1", ] [[package]] @@ -799,6 +1123,17 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -806,6 +1141,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", + "equivalent", + "foldhash", ] [[package]] @@ -1092,6 +1429,28 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1110,6 +1469,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1118,10 +1486,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "jiff-tzdb-platform", "log", @@ -1133,13 +1502,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1159,21 +1528,32 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", "wasm-bindgen", ] +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + [[package]] name = "kmip-protocol" version = "0.5.0" -source = "git+https://github.com/NLnetLabs/kmip-protocol?branch=next#2b0043846a7d2458f83a6a12e5e4fc656273e1fa" +source = "git+https://github.com/NLnetLabs/kmip-protocol.git?branch=next#2b0043846a7d2458f83a6a12e5e4fc656273e1fa" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "enum-ordinalize", "hex", @@ -1204,6 +1584,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1216,6 +1602,21 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.13.0", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1228,6 +1629,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -1239,17 +1646,36 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] -name = "matchers" -version = "0.2.0" +name = "lru" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ - "regex-automata", + "hashbrown 0.17.1", +] + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix 0.29.0", + "winapi", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", ] [[package]] @@ -1266,7 +1692,7 @@ checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1275,6 +1701,12 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + [[package]] name = "memoffset" version = "0.9.1" @@ -1290,6 +1722,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "1.2.1" @@ -1297,23 +1735,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "nix" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1329,6 +1800,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1376,7 +1858,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "foreign-types", "libc", @@ -1392,7 +1874,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1407,6 +1889,39 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1436,6 +1951,101 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.6", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1478,6 +2088,28 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1507,18 +2139,24 @@ checksum = "9adf1691c04c0a5ff46ff8f262b58beb07b0dbb61f96f9f54f6cbd82106ed87f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1536,6 +2174,15 @@ dependencies = [ "scheduled-thread-pool", ] +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.10.1" @@ -1544,15 +2191,122 @@ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.3", - "rand_core", + "rand_core 0.10.1", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rand_core" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "ratatui" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termina", + "ratatui-termwiz", + "ratatui-widgets", + "serde", +] + +[[package]] +name = "ratatui-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" +dependencies = [ + "bitflags 2.13.0", + "compact_str", + "critical-section", + "hashbrown 0.17.1", + "itertools", + "kasuari", + "lru", + "palette", + "serde", + "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-termina" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2" +dependencies = [ + "instability", + "ratatui-core", + "termina", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" +dependencies = [ + "ratatui-core", + "termwiz", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.17.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "serde", + "strum", + "time", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "rayon" version = "1.12.0" @@ -1579,7 +2333,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -1599,7 +2353,19 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1681,7 +2447,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -1690,9 +2456,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "log", "once_cell", @@ -1714,9 +2480,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "zeroize", ] @@ -1835,7 +2601,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1912,7 +2678,18 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", ] [[package]] @@ -1930,6 +2707,27 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -1940,6 +2738,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.3" @@ -1974,12 +2778,39 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1995,6 +2826,17 @@ dependencies = [ "is_ci", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.118" @@ -2023,7 +2865,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2051,6 +2893,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "termina" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" +dependencies = [ + "bitflags 2.13.0", + "parking_lot", + "rustix", + "signal-hook", + "windows-sys 0.61.2", +] + [[package]] name = "terminal_size" version = "0.4.4" @@ -2061,6 +2916,109 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom 7.1.3", + "phf", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64", + "bitflags 2.13.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix 0.29.0", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -2072,9 +3030,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.49" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", "libc", @@ -2094,9 +3052,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -2152,7 +3110,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2262,7 +3220,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.0", "bytes", "futures-util", "http", @@ -2306,7 +3264,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2354,12 +3312,47 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "untrusted" version = "0.9.0" @@ -2393,10 +3386,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.3" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ + "atomic", "getrandom 0.4.3", "js-sys", "wasm-bindgen", @@ -2414,6 +3408,21 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + [[package]] name = "want" version = "0.3.1" @@ -2429,11 +3438,20 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -2444,9 +3462,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.75" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -2454,9 +3472,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2464,31 +3482,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -2503,6 +3521,100 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -2524,7 +3636,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2535,7 +3647,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2653,6 +3765,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.3" @@ -2678,7 +3796,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -2699,7 +3817,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -2739,7 +3857,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index e88e21bf3..64ea1054c 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -18,6 +18,8 @@ readme = "README.md" # --- Dependencies ------------------------------------------------------------- [dependencies] +ansi-to-tui = "8.0.1" +ratatui = "0.30.2" # The CLI uses 'anstream' to conditionally disable ANSI colorization in its # output (e.g. in response to 'TERM=dumb'). @@ -69,7 +71,7 @@ version = "2.4" # most idiomatic option. 'tokio' is the perfect 'async' runtime for this. [dependencies.tokio] workspace = true -features = ["macros", "net", "rt"] +features = ["macros", "net", "rt", "rt-multi-thread"] # The CLI uses 'tracing' to log output. While 'log' would suffice, 'tracing' # is already used elsewhere in this codebase. diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs index a8cf462a3..4a6ce5d1c 100644 --- a/crates/cli/src/commands/mod.rs +++ b/crates/cli/src/commands/mod.rs @@ -63,6 +63,9 @@ pub enum Command { /// Print example config or policy files #[command(name = "template")] Template(self::template::Template), + + #[command(name = "tui")] + Tui, } impl Command { @@ -91,6 +94,7 @@ impl Command { Self::Hsm(hsm) => hsm.execute(client).await, Self::Tsig(tsig) => tsig.execute(client).await, Self::Template(template) => template.execute(client).await, + Self::Tui => crate::tui::launch(client), } } } diff --git a/crates/cli/src/commands/policy.rs b/crates/cli/src/commands/policy.rs index aaa4613bd..65633ca2d 100644 --- a/crates/cli/src/commands/policy.rs +++ b/crates/cli/src/commands/policy.rs @@ -1,4 +1,5 @@ use std::fmt::Display; +use std::fmt::Write; use cascade_api::{ AutoConfigPolicyInfo, KeyManagerPolicyInfo, LoaderPolicyInfo, ReviewPolicyMode, @@ -62,7 +63,7 @@ impl Policy { } }; - print_policy(&p); + print_policy(&p).unwrap(); } PolicyCommand::Reload => { let res: Result = @@ -108,7 +109,9 @@ impl Policy { } } -fn print_policy(p: &PolicyInfo) { +pub fn print_policy(p: &PolicyInfo) -> Result { + let mut res = String::new(); + let out = &mut res; let PolicyInfo { name, zones, @@ -128,12 +131,14 @@ fn print_policy(p: &PolicyInfo) { none.clone() }; - println!("{name}:"); - println!(" zones: {zones}"); - print_loader_policy(loader); - print_key_manager_policy(key_manager); - print_signer_policy(signer); - print_server_policy(server); + writeln!(out, "{name}:")?; + writeln!(out, " zones: {zones}")?; + print_loader_policy(out, loader)?; + print_key_manager_policy(out, key_manager)?; + print_signer_policy(out, signer)?; + print_server_policy(out, server)?; + + Ok(res) } fn or_none(x: &Option) -> String { @@ -142,12 +147,17 @@ fn or_none(x: &Option) -> String { .unwrap_or("".into()) } -fn print_loader_policy(LoaderPolicyInfo { review }: &LoaderPolicyInfo) { - println!(" loader:"); - print_review(review); +fn print_loader_policy( + out: &mut String, + LoaderPolicyInfo { review }: &LoaderPolicyInfo, +) -> Result<(), std::fmt::Error> { + writeln!(out, " loader:")?; + print_review(out, review)?; + Ok(()) } fn print_key_manager_policy( + out: &mut String, KeyManagerPolicyInfo { hsm_server_id, use_csk, @@ -171,78 +181,92 @@ fn print_key_manager_policy( auto_remove_delay, publication_nameservers, }: &KeyManagerPolicyInfo, -) { +) -> Result<(), std::fmt::Error> { // TODO: we should probably condense this information a lot or hide unnecessary details. For example, only // show CSK info when `use_csk` is `true`. - println!(" key manager:"); - println!(" HSM server: {}", or_none(hsm_server_id)); - println!(" DS algorithm: {ds_algorithm}"); + writeln!(out, " key manager:")?; + writeln!(out, " HSM server: {}", or_none(hsm_server_id))?; + writeln!(out, " DS algorithm: {ds_algorithm}")?; if *auto_remove { - println!( - " auto-remove: true (delay {}s)", + writeln!( + out, + " auto-remove: true (delay {}s)?", auto_remove_delay.as_secs() - ); + )?; } else { - println!(" auto-remove: false",); + writeln!(out, " auto-remove: false",)?; } - println!(" algorithm: {algorithm}"); - print_auto_flags(auto_algorithm); + writeln!(out, " algorithm: {algorithm}")?; + print_auto_flags(out, auto_algorithm)?; if *use_csk { - println!(" CSK:"); - println!(" validity: {}s", or_none(csk_validity)); - print_auto_flags(auto_csk); + writeln!(out, " CSK:")?; + writeln!(out, " validity: {}s", or_none(csk_validity))?; + print_auto_flags(out, auto_csk)?; } else { - println!(" KSK:"); - println!(" validity: {}s", or_none(ksk_validity)); - print_auto_flags(auto_ksk); - println!(" ZSK:"); - println!(" validity: {}s", or_none(zsk_validity)); - print_auto_flags(auto_zsk); + writeln!(out, " KSK:")?; + writeln!(out, " validity: {}s", or_none(ksk_validity))?; + print_auto_flags(out, auto_ksk)?; + writeln!(out, " ZSK:")?; + writeln!(out, " validity: {}s", or_none(zsk_validity))?; + print_auto_flags(out, auto_zsk)?; } - println!(" records:"); - println!(" TTL: {default_ttl}s"); - println!(" DNSKEY:"); - println!(" signature inception offset: {dnskey_inception_offset}s"); - println!(" signature lifetime: {dnskey_signature_lifetime}s"); - println!(" signature remain time: {dnskey_remain_time}s"); - println!(" CDS:"); - println!(" signature inception offset: {cds_inception_offset}s"); - println!(" signature lifetime: {cds_signature_lifetime}s"); - println!(" signature remain time: {cds_remain_time}s"); + writeln!(out, " records:")?; + writeln!(out, " TTL: {default_ttl}s")?; + writeln!(out, " DNSKEY:")?; + writeln!( + out, + " signature inception offset: {dnskey_inception_offset}s" + )?; + writeln!( + out, + " signature lifetime: {dnskey_signature_lifetime}s" + )?; + writeln!(out, " signature remain time: {dnskey_remain_time}s")?; + writeln!(out, " CDS:")?; + writeln!( + out, + " signature inception offset: {cds_inception_offset}s" + )?; + writeln!(out, " signature lifetime: {cds_signature_lifetime}s")?; + writeln!(out, " signature remain time: {cds_remain_time}s")?; if publication_nameservers.is_empty() { - println!(" publication nameservers: "); + writeln!(out, " publication nameservers: ")?; } else { - println!(" publication nameservers:"); + writeln!(out, " publication nameservers:")?; for ns in publication_nameservers { - println!(" - {ns}") + writeln!(out, " - {ns}")? } } + + Ok(()) } -fn print_auto_flags(auto: &AutoConfigPolicyInfo) { - print!(" auto flags:"); +fn print_auto_flags(out: &mut String, auto: &AutoConfigPolicyInfo) -> Result<(), std::fmt::Error> { + write!(out, " auto flags:")?; if !auto.start && !auto.report && !auto.expire && !auto.done { - println!(" "); - return; + writeln!(out, " ")?; + return Ok(()); } if auto.start { - print!(" start"); + write!(out, " start")?; } if auto.report { - print!(" report"); + write!(out, " report")?; } if auto.expire { - print!(" expire"); + write!(out, " expire")?; } if auto.done { - print!(" done"); + write!(out, " done")?; } - println!(); + writeln!(out)?; + Ok(()) } fn print_signer_policy( + out: &mut String, SignerPolicyInfo { review, serial_policy, @@ -253,7 +277,7 @@ fn print_signer_policy( key_roll_time, denial, }: &SignerPolicyInfo, -) { +) -> Result<(), std::fmt::Error> { let serial_policy = match serial_policy { SignerSerialPolicyInfo::Keep => "keep", SignerSerialPolicyInfo::Counter => "counter", @@ -269,18 +293,26 @@ fn print_signer_policy( }, }; - println!(" signer:"); - println!(" serial policy: {serial_policy}"); - println!(" signature inception offset: {sig_inception_offset}s"); - println!(" signature validity offset: {sig_validity_offset}s"); - println!(" signature remain time: {sig_remain_time}s"); - println!(" signature refresh interval: {signature_refresh_interval}s"); - println!(" key roll time: {key_roll_time}s"); - println!(" denial: {denial}"); - print_review(review); + writeln!(out, " signer:")?; + writeln!(out, " serial policy: {serial_policy}")?; + writeln!( + out, + " signature inception offset: {sig_inception_offset}s" + )?; + writeln!(out, " signature validity offset: {sig_validity_offset}s")?; + writeln!(out, " signature remain time: {sig_remain_time}s")?; + writeln!( + out, + " signature refresh interval: {signature_refresh_interval}s" + )?; + writeln!(out, " key roll time: {key_roll_time}s")?; + writeln!(out, " denial: {denial}")?; + print_review(out, review)?; + Ok(()) } fn print_server_policy( + out: &mut String, ServerPolicyInfo { outbound: cascade_api::OutboundPolicyInfo { @@ -288,44 +320,54 @@ fn print_server_policy( send_notify_to, }, }: &ServerPolicyInfo, -) { - println!(" server:"); - println!(" outbound:"); - print_nameserver_comms_policy("provide XFR to", provide_xfr_to); - print_nameserver_comms_policy("send NOTIFY to", send_notify_to); +) -> Result<(), std::fmt::Error> { + writeln!(out, " server:")?; + writeln!(out, " outbound:")?; + print_nameserver_comms_policy(out, "provide XFR to", provide_xfr_to)?; + print_nameserver_comms_policy(out, "send NOTIFY to", send_notify_to)?; + Ok(()) } -fn print_review(ReviewPolicyInfo { mode, on_reject }: &ReviewPolicyInfo) { - print!(" review:"); +fn print_review( + out: &mut String, + ReviewPolicyInfo { mode, on_reject }: &ReviewPolicyInfo, +) -> Result<(), std::fmt::Error> { + write!(out, " review:")?; match mode { ReviewPolicyMode::Off => { - println!(" off"); - return; + writeln!(out, " off")?; + return Ok(()); } ReviewPolicyMode::Manual => { - println!(""); - println!(" mode: manual"); + writeln!(out)?; + writeln!(out, " mode: manual")?; } ReviewPolicyMode::Script { hook } => { - println!(""); - println!(" mode: script"); - println!(" hook: {hook}") + writeln!(out)?; + writeln!(out, " mode: script")?; + writeln!(out, " hook: {hook}")? } } let on_reject = match on_reject { cascade_api::ReviewPolicyOnReject::Discard => "discard", cascade_api::ReviewPolicyOnReject::Halt => "halt", }; - println!(" on reject: {on_reject}") + writeln!(out, " on reject: {on_reject}")?; + Ok(()) } -fn print_nameserver_comms_policy(name: &str, n: &[NameserverCommsPolicyInfo]) { +fn print_nameserver_comms_policy( + out: &mut String, + name: &str, + n: &[NameserverCommsPolicyInfo], +) -> Result<(), std::fmt::Error> { if n.is_empty() { - println!(" {name}: "); - return; + writeln!(out, " {name}: ")?; + return Ok(()); } - println!(" {name}:"); + writeln!(out, " {name}:")?; for item in n { - println!(" {item}"); + writeln!(out, " {item}")?; } + Ok(()) } diff --git a/crates/cli/src/commands/zone.rs b/crates/cli/src/commands/zone.rs index bbfb0d4f2..f59f31b7c 100644 --- a/crates/cli/src/commands/zone.rs +++ b/crates/cli/src/commands/zone.rs @@ -1,3 +1,4 @@ +use std::fmt::Write; use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::time::{Duration, SystemTime}; @@ -388,12 +389,27 @@ impl Zone { let url = format!("zone/{}/status", zone); let response: Result = client.get_json(&url).await?; - match response { - Ok(status) => Self::print_zone_status(client, status, detailed).await, + let status = match response { + Ok(status) => status, Err(ZoneStatusError::ZoneDoesNotExist) => { - Err(format!("zone `{zone}` does not exist")) + return Err(format!("zone `{zone}` does not exist")); } - } + }; + + // Fetch the policy for the zone. + let url = format!("policy/{}", status.policy); + let response: Result = client.get_json(&url).await?; + + let policy = response.map_err(|_| { + format!( + "policy `{}` used by zone `{}` does not exist", + status.policy, status.name + ) + })?; + + let s = Self::print_zone_status(&status, &policy, detailed).unwrap(); + println!("{s}"); + Ok(()) } ZoneCommand::History { zone } => { let url = format!("zone/{}/history", zone); @@ -579,25 +595,16 @@ impl Zone { } } - async fn print_zone_status( - client: CascadeApiClient, - zone: ZoneStatus, + pub fn print_zone_status( + zone: &ZoneStatus, + policy: &PolicyInfo, detailed: bool, - ) -> Result<(), String> { - // Fetch the policy for the zone. - let url = format!("policy/{}", zone.policy); - let response: Result = client.get_json(&url).await?; - - let policy = response.map_err(|_| { - format!( - "policy `{}` used by zone `{}` does not exist", - zone.policy, zone.name - ) - })?; + ) -> Result { + let mut out = String::new(); - println!("zone: {}", zone.name); - println!("policy: {}", zone.policy); - println!("source: {}", zone.source); + writeln!(out, "zone: {}", zone.name)?; + writeln!(out, "policy: {}", zone.policy)?; + writeln!(out, "source: {}", zone.source)?; let loader_review = match &policy.loader.review.mode { ReviewPolicyMode::Off => "off", @@ -611,92 +618,99 @@ impl Zone { ReviewPolicyMode::Manual => "manual", }; - println!(""); - println!("review"); - println!(" loaded: {loader_review}"); - println!(" signed: {signer_review}"); - println!(""); + writeln!(out)?; + writeln!(out, "review")?; + writeln!(out, " loaded: {loader_review}")?; + writeln!(out, " signed: {signer_review}")?; + writeln!(out)?; - println!("last published"); + writeln!(out, "last published")?; if let Some(last) = &zone.last_published { - println!(" loaded serial: {}", last.loaded_serial); - println!(" signed serial: {}", last.signed_serial); - println!( + writeln!(out, " loaded serial: {}", last.loaded_serial)?; + writeln!(out, " signed serial: {}", last.signed_serial)?; + writeln!( + out, " timestamp: {}", jiff::Timestamp::try_from(last.timestamp).unwrap() - ); - println!(" size: {} records", last.num_records); + )?; + writeln!(out, " size: {} records", last.num_records)?; } else { - println!(" "); + writeln!(out, " ")?; } // Output information per step progressed until the first still // in-progress/aborted step or show all steps if all have completed. - println!(""); - print_status(&zone, &policy); + writeln!(out)?; + print_status(&mut out, zone, policy)?; if zone.last_published.is_some() { - println!(""); - println!("Published zone available at:"); - for addr in zone.publish_addr { - println!(" - {addr}") + writeln!(out)?; + writeln!(out, "Published zone available at:")?; + for addr in &zone.publish_addr { + writeln!(out, " - {addr}")?; } } - if let Some(error) = zone.error { - println!(""); - println!("An error occurred during the last operation:"); - println!(" {}ERROR: {error}{}", ansi::RED, ansi::RESET); - println!( + if let Some(error) = &zone.error { + writeln!(out)?; + writeln!(out, "An error occurred during the last operation:")?; + writeln!(out, " {}ERROR: {error}{}", ansi::RED, ansi::RESET)?; + writeln!( + out, " Run {}`cascade zone history {}`{} for more information.", ansi::BLUE, zone.name, ansi::RESET - ); + )?; } if zone.maintenance_mode { - println!(""); - println!( + writeln!(out)?; + writeln!( + out, "{}WARNING: This zone is in maintenance mode{}", ansi::YELLOW, ansi::RESET - ); - println!(" Cascade will not automatically start new loading and signing operations"); - println!( + )?; + writeln!( + out, + " Cascade will not automatically start new loading and signing operations" + )?; + writeln!( + out, " Run {}`cascade zone maintenance disable {}`{} to resume normal operation", ansi::BLUE, zone.name, ansi::RESET - ); + )?; } if detailed { - println!(""); - println!("DNSSEC keys:"); - for key in zone.keys { + writeln!(out)?; + writeln!(out, "DNSSEC keys:")?; + for key in &zone.keys { match key.key_type { KeyType::Ksk => print!(" KSK"), KeyType::Zsk => print!(" ZSK"), KeyType::Csk => print!(" CSK"), } - println!(" tagged {}:", key.key_tag); - println!(" Reference: {}", key.pubref); + writeln!(out, " tagged {}:", key.key_tag)?; + writeln!(out, " Reference: {}", key.pubref)?; if key.signer { - println!(" Actively used for signing"); + writeln!(out, " Actively used for signing")?; } } - println!(" Details:"); + writeln!(out, " Details:")?; for line in zone.key_status.lines() { - println!(" {line}"); + writeln!(out, " {line}")?; } } - Ok(()) + Ok(out) } } -pub fn print_status(zone: &ZoneStatus, policy: &PolicyInfo) { +pub fn print_status(out: &mut String, zone: &ZoneStatus, policy: &PolicyInfo) -> std::fmt::Result { let current = zone.progress; let progress = match current { @@ -711,46 +725,52 @@ pub fn print_status(zone: &ZoneStatus, policy: &PolicyInfo) { Progress::HaltSigned => "halted after signed review", }; - println!("status: {}{progress}{}", ansi::BLUE, ansi::RESET); + writeln!(out, "status: {}{progress}{}", ansi::BLUE, ansi::RESET)?; if matches!(current, Progress::Waiting | Progress::Restoring) { - return; + return Ok(()); } - print_load_phase(current, zone.unsigned_serial, &zone.receipt_report); + print_load_phase(out, current, zone.unsigned_serial, &zone.receipt_report)?; print_loaded_review_phase( + out, &zone.name, zone.unsigned_serial, policy, current, &zone.unsigned_review_addr, - ); + )?; print_sign_phase( + out, current, zone.unsigned_serial, zone.signed_serial, &zone.signing_report, - ); + )?; print_signed_review_phase( + out, &zone.name, zone.signed_serial, policy, current, &zone.signed_review_addr, - ); - print_publish_phase(); + )?; + print_publish_phase(out)?; + + Ok(()) } fn print_load_phase( + out: &mut String, current: Progress, unsigned_serial: Option, receipt_report: &Option, -) { +) -> std::fmt::Result { if current < Progress::Loading { - println!(" {Pending} load"); + writeln!(out, " {Pending} load")?; } else if current > Progress::Loading { let unsigned_serial = serial_to_string(unsigned_serial); - println!(" {Done} load (serial: {unsigned_serial})"); + writeln!(out, " {Done} load (serial: {unsigned_serial})")?; } else { let short_serial = if let Some(unsigned_serial) = unsigned_serial { format!(" (serial: {unsigned_serial})") @@ -787,76 +807,91 @@ fn print_load_phase( "".into() }; - println!(" {Ongoing} load{short_serial}"); - println!(" | serial: {unsigned_serial}"); - println!(" | start time: {start_time}"); - println!(" | progress: {bytes}{total_size}{percentage}"); - println!(" |"); + writeln!(out, " {Ongoing} load{short_serial}")?; + writeln!(out, " | serial: {unsigned_serial}")?; + writeln!(out, " | start time: {start_time}")?; + writeln!(out, " | progress: {bytes}{total_size}{percentage}")?; + writeln!(out, " |")?; } + + Ok(()) } fn print_loaded_review_phase( + out: &mut String, zone: &ZoneName, serial: Option, policy: &PolicyInfo, current: Progress, addrs: &[SocketAddr], -) { +) -> std::fmt::Result { use ansi::{BLUE, DIM, RED, RESET, YELLOW}; if let ReviewPolicyMode::Off = policy.loader.review.mode { - println!(" {Pending} {DIM}review loaded zone (disabled){RESET}"); - return; + writeln!(out, " {Pending} {DIM}review loaded zone (disabled){RESET}")?; + return Ok(()); } if current < Progress::LoadedReview { - println!(" {Pending} review loaded zone"); + writeln!(out, " {Pending} review loaded zone")?; } else if current == Progress::LoadedReview { if let ReviewPolicyMode::Script { hook } = &policy.loader.review.mode { - println!(" {Ongoing} review loaded zone"); - println!(" | {BLUE}automatic zone review in progress{RESET}"); - println!(" | review hook: \"{hook}\"",); - println!(" |"); + writeln!(out, " {Ongoing} review loaded zone")?; + writeln!(out, " | {BLUE}automatic zone review in progress{RESET}")?; + writeln!(out, " | review hook: \"{hook}\"",)?; + writeln!(out, " |")?; } else { let serial = serial.map_or_else(|| "".into(), |s| s.to_string()); - println!(" {Stopped} review loaded zone"); - println!(" | {YELLOW}zone must be reviewed manually{RESET}"); + writeln!(out, " {Stopped} review loaded zone")?; + writeln!(out, " | {YELLOW}zone must be reviewed manually{RESET}")?; if addrs.is_empty() { - println!(" | {RED}no loaded review addresses were specified{RESET}"); + writeln!( + out, + " | {RED}no loaded review addresses were specified{RESET}" + )?; } else { - println!(" | loaded zone is available at:"); + writeln!(out, " | loaded zone is available at:")?; for addr in addrs { - println!(" | - {addr}"); + writeln!(out, " | - {addr}")?; } } - println!(" | possible actions:"); - println!(" | {BLUE}cascade zone approve --unsigned {zone} {serial}{RESET}"); - println!(" | {BLUE}cascade zone reject --unsigned {zone} {serial}{RESET}"); - println!(" |"); + writeln!(out, " | possible actions:")?; + writeln!( + out, + " | {BLUE}cascade zone approve --unsigned {zone} {serial}{RESET}" + )?; + writeln!( + out, + " | {BLUE}cascade zone reject --unsigned {zone} {serial}{RESET}" + )?; + writeln!(out, " |")?; } } else if current == Progress::HaltLoaded { - println!(" {Error} review loaded zone"); - println!(" | {RED}ERROR: zone was rejected{RESET}"); - println!(" | possible actions:"); - println!(" | {BLUE}cascade zone override {zone}{RESET}",); - println!(" | {BLUE}cascade zone reset {zone}{RESET}",); - println!(" |"); + writeln!(out, " {Error} review loaded zone")?; + writeln!(out, " | {RED}ERROR: zone was rejected{RESET}")?; + writeln!(out, " | possible actions:")?; + writeln!(out, " | {BLUE}cascade zone override {zone}{RESET}",)?; + writeln!(out, " | {BLUE}cascade zone reset {zone}{RESET}",)?; + writeln!(out, " |")?; } else { - println!(" {Done} review loaded zone"); + writeln!(out, " {Done} review loaded zone")?; } + + Ok(()) } fn print_sign_phase( + out: &mut String, current: Progress, unsigned_serial: Option, signed_serial: Option, signing_report: &Option, -) { +) -> std::fmt::Result { if current < Progress::Signing { - println!(" {Pending} sign"); + writeln!(out, " {Pending} sign")?; } else if current > Progress::Signing { let signed_serial = serial_to_string(signed_serial); - println!(" {Done} sign (serial: {signed_serial})"); + writeln!(out, " {Done} sign (serial: {signed_serial})")?; } else { let start_time = match &signing_report.as_ref().map(|r| &r.stage_report) { None => None, @@ -872,69 +907,87 @@ fn print_sign_phase( "".into() }; let signed_serial = serial_to_string(signed_serial); - println!(" {Ongoing} sign{short_signed_serial}"); - println!(" | loaded serial: {unsigned_serial}"); - println!(" | signed serial: {signed_serial}"); - println!( + writeln!(out, " {Ongoing} sign{short_signed_serial}")?; + writeln!(out, " | loaded serial: {unsigned_serial}")?; + writeln!(out, " | signed serial: {signed_serial}")?; + writeln!( + out, " | start time: {}", to_rfc3339_ago(start_time, "") - ); - println!(" |"); + )?; + writeln!(out, " |")?; } + + Ok(()) } fn print_signed_review_phase( + out: &mut String, zone: &ZoneName, signed_serial: Option, policy: &PolicyInfo, current: Progress, addrs: &[SocketAddr], -) { +) -> std::fmt::Result { use ansi::{BLUE, DIM, RED, RESET, YELLOW}; if let ReviewPolicyMode::Off = policy.signer.review.mode { - println!(" {Pending} {DIM}review signed zone (disabled){RESET}"); - return; + writeln!(out, " {Pending} {DIM}review signed zone (disabled){RESET}")?; + return Ok(()); } if current < Progress::SignedReview { - println!(" {Pending} review signed zone"); + writeln!(out, " {Pending} review signed zone")?; } else if current == Progress::SignedReview { if let ReviewPolicyMode::Script { hook } = &policy.signer.review.mode { - println!(" {Ongoing} review signed zone"); - println!(" | {YELLOW}automatic zone review in progress{RESET}"); - println!(" | review hook: \"{hook}\"",); + writeln!(out, " {Ongoing} review signed zone")?; + writeln!( + out, + " | {YELLOW}automatic zone review in progress{RESET}" + )?; + writeln!(out, " | review hook: \"{hook}\"",)?; } else { let serial = signed_serial.map_or_else(|| "".into(), |s| s.to_string()); - println!(" {Stopped} review signed zone"); - println!(" | {YELLOW}zone must be reviewed manually{RESET}"); + writeln!(out, " {Stopped} review signed zone")?; + writeln!(out, " | {YELLOW}zone must be reviewed manually{RESET}")?; if addrs.is_empty() { - println!(" | {RED}no signed review addresses were specified{RESET}"); + writeln!( + out, + " | {RED}no signed review addresses were specified{RESET}" + )?; } else { - println!(" | signed zone is available at:"); + writeln!(out, " | signed zone is available at:")?; for addr in addrs { - println!(" | - {addr}"); + writeln!(out, " | - {addr}")?; } } - println!(" | possible actions:"); - println!(" | {BLUE}cascade zone approve --signed {zone} {serial}{RESET}"); - println!(" | {BLUE}cascade zone reject --signed {zone} {serial}{RESET}"); - println!(" |"); + writeln!(out, " | possible actions:")?; + writeln!( + out, + " | {BLUE}cascade zone approve --signed {zone} {serial}{RESET}" + )?; + writeln!( + out, + " | {BLUE}cascade zone reject --signed {zone} {serial}{RESET}" + )?; + writeln!(out, " |")?; } } else if current == Progress::HaltSigned { - println!(" {Error} review signed zone"); - println!(" | {RED}ERROR: zone was rejected{RESET}"); - println!(" | possible actions:"); - println!(" | {BLUE}cascade zone override {zone}{RESET}"); - println!(" | {BLUE}cascade zone reset {zone}{RESET}"); - println!(" |"); + writeln!(out, " {Error} review signed zone")?; + writeln!(out, " | {RED}ERROR: zone was rejected{RESET}")?; + writeln!(out, " | possible actions:")?; + writeln!(out, " | {BLUE}cascade zone override {zone}{RESET}")?; + writeln!(out, " | {BLUE}cascade zone reset {zone}{RESET}")?; + writeln!(out, " |")?; } else { - println!(" {Done} review signed zone"); + writeln!(out, " {Done} review signed zone")?; } + + Ok(()) } -fn print_publish_phase() { - println!(" {Pending} publish"); +fn print_publish_phase(out: &mut String) -> std::fmt::Result { + writeln!(out, " {Pending} publish") } enum Icon { diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 449adc014..e36be9c86 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -9,6 +9,7 @@ mod ansi; mod args; mod client; mod commands; +mod tui; #[tokio::main(flavor = "current_thread")] async fn main() -> ExitCode { diff --git a/crates/cli/src/tui.rs b/crates/cli/src/tui.rs new file mode 100644 index 000000000..0600629e6 --- /dev/null +++ b/crates/cli/src/tui.rs @@ -0,0 +1,435 @@ +use std::{collections::HashMap, ops::ControlFlow, time::Duration}; + +use ansi_to_tui::IntoText as _; +use cascade_api::{ + PolicyInfo, PolicyInfoError, PolicyListResult, ZoneName, ZoneStatus, ZoneStatusError, + ZonesListResult, +}; +use ratatui::{ + DefaultTerminal, Frame, + crossterm::{ + self, + event::{KeyCode, KeyModifiers}, + }, + layout::{Constraint, Layout, Rect}, + style::Style, + text::{Line, Span, Text}, + widgets::{Block, Clear, List, ListDirection, ListState, Padding, Paragraph, Tabs}, +}; +use tokio::sync::oneshot; + +use crate::client::CascadeApiClient; + +/// The whole state of the TUI +#[derive(Default)] +struct State { + tab: Tab, + zones: Zones, + policies: Policies, +} + +#[derive(Default)] +struct Zones { + list: Fetch>, + status: HashMap>, + state: ListState, +} + +#[derive(Default)] +struct Policies { + list: Fetch>, + info: HashMap>, + state: ListState, +} + +/// The different tabs that we have in the TUI +#[repr(u32)] +#[derive(Clone, Copy, Default, PartialEq, Eq)] +enum Tab { + #[default] + Zones = 0, + Policies = 1, + _Config = 2, +} + +/// A bit of information that needs to be fetched via the HTTP API +#[derive(Default)] +enum Fetch { + /// This information has not been requested yet + #[default] + Unrequested, + Pending(oneshot::Receiver>), + Done(Result), +} + +impl Fetch { + fn get_or_fetch( + &mut self, + f: impl (FnOnce() -> Result) + Send + 'static, + ) -> Option<&mut Result> { + match self { + Self::Unrequested => { + let (tx, rx) = oneshot::channel(); + std::thread::spawn(|| { + let res = f(); + tx.send(res).map_err(|_| ()).unwrap(); + }); + *self = Self::Pending(rx); + None + } + Self::Pending(r) => { + if let Ok(v) = r.try_recv() { + *self = Self::Done(v); + } + let Self::Done(v) = self else { + return None; + }; + Some(v) + } + Self::Done(v) => Some(v), + } + } + + fn reset(&mut self) { + *self = Self::Unrequested; + } +} + +pub fn launch(client: CascadeApiClient) -> Result<(), String> { + ratatui::run(|terminal| app(client, terminal)).map_err(|e| e.to_string()) +} + +fn app(client: CascadeApiClient, terminal: &mut DefaultTerminal) -> std::io::Result<()> { + let mut state = State::default(); + loop { + terminal.draw(|frame| render(&mut state, &client, frame))?; + match handle_events(&mut state) { + ControlFlow::Continue(()) => {} + ControlFlow::Break(r) => return r, + } + } +} + +fn handle_events(state: &mut State) -> ControlFlow> { + let has_event = match crossterm::event::poll(Duration::from_millis(100)) { + Ok(b) => b, + Err(e) => return ControlFlow::Break(Err(e)), + }; + if has_event { + let event = match crossterm::event::read() { + Ok(event) => event, + Err(err) => return ControlFlow::Break(Err(err)), + }; + if let Some(key_event) = event.as_key_press_event() { + match key_event.code { + KeyCode::Char('1') => state.tab = Tab::Zones, + KeyCode::Char('2') => state.tab = Tab::Policies, + KeyCode::Char('r') if key_event.modifiers == KeyModifiers::CONTROL => { + state.zones.list.reset(); + state.zones.status.clear(); + state.policies.list.reset(); + state.policies.info.clear(); + } + KeyCode::Down if state.tab == Tab::Zones => { + state.zones.state.select_next(); + } + KeyCode::Up if state.tab == Tab::Zones => { + state.zones.state.select_previous(); + } + KeyCode::Down if state.tab == Tab::Policies => { + state.policies.state.select_next(); + } + KeyCode::Up if state.tab == Tab::Policies => { + state.policies.state.select_previous(); + } + KeyCode::Char('q') | KeyCode::Esc => return ControlFlow::Break(Ok(())), + _ => {} + } + } + } + + ControlFlow::Continue(()) +} + +fn render(state: &mut State, client: &CascadeApiClient, frame: &mut Frame) { + let [title, tabs, main, keys_area] = Layout::vertical([ + Constraint::Length(1), + Constraint::Length(1), + Constraint::Percentage(100), + Constraint::Length(1), + ]) + .areas(frame.area()); + + frame.render_widget( + Span::styled( + "Cascade TUI 2000: Limited Edition", + Style::new().bold().underlined(), + ), + title, + ); + + let tabs_widget = Tabs::new(vec!["(1) Zones", "(2) Policies", "(3) Config"]) + .style(Style::default().white()) + .highlight_style(Style::default().bold().blue()) + .padding("", "") + .divider(" | ") + .select(Some(state.tab as u32 as usize)); + + frame.render_widget(tabs_widget, tabs); + + match state.tab { + Tab::Zones => zones_tab(frame, main, state, client), + Tab::Policies => policies_tab(frame, main, state, client), + Tab::_Config => todo!(), + } + + let keys = [ + ("1-3", "switch tabs"), + ("ctrl+r", "reload"), + ("up/down", "select"), + ("q/ESC", "quit"), + ]; + + let mut line = Line::default(); + + let mut first = true; + for (key, action) in keys { + if !first { + line.push_span(Span::raw(", ")); + } + line.push_span(Span::styled(key, Style::new().blue())); + line.push_span(Span::raw(" - ")); + line.push_span(Span::raw(action)); + first = false; + } + + frame.render_widget(line, keys_area); +} + +fn zones_tab(frame: &mut Frame, area: Rect, state: &mut State, client: &CascadeApiClient) { + let [left, right] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]).areas(area); + + let block = Block::bordered(); + frame.render_widget(&block, left); + frame.render_widget(&block, right); + + let left = block.inner(left); + let right = block.inner(right); + + let zones = { + let client = client.clone(); + state.zones.list.get_or_fetch(move || { + let url = "zone/"; + let runtime = tokio::runtime::Runtime::new().unwrap(); + let response: Result = runtime.block_on(client.get_json(url)); + match response { + Ok(s) => Ok(s.zones), + Err(e) => Err(e), + } + }) + }; + + let Some(zones) = zones else { + let text = Text::from("Loading..."); + draw_popup(frame, area, text); + return; + }; + + let zones = match zones { + Ok(zones) => zones, + Err(e) => { + let text = Text::from(format!("ERROR: Could not fetch zones\n{e}")); + draw_popup(frame, area, text); + return; + } + }; + + let zone_names = zones.iter().map(|z| z.to_string()).collect::>(); + let zone_list = List::new(zone_names) + .style(Style::new().white()) + .highlight_style(Style::new().black().on_white()) + .highlight_symbol("> ") + .repeat_highlight_symbol(true) + .direction(ListDirection::TopToBottom); + + if state.zones.state.selected().is_none() && !zone_list.is_empty() { + state.zones.state.select_first(); + } + + frame.render_stateful_widget(zone_list, left, &mut state.zones.state); + + if let Some(idx) = state.zones.state.selected() + && let Some(name) = zones.get(idx) + { + let fetch = state.zones.status.entry(name.clone()).or_default(); + let name = name.clone(); + let client = client.clone(); + let res = fetch.get_or_fetch(move || { + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let url = format!("zone/{name}/status"); + let response: Result, String> = + runtime.block_on(client.get_json(&url)); + + let status = match response { + Ok(Ok(s)) => s, + Ok(Err(ZoneStatusError::ZoneDoesNotExist)) => { + return Err("zone does not exist".into()); + } + Err(e) => return Err(e), + }; + + let policy_name = &status.policy; + + let url = format!("policy/{policy_name}"); + let response: Result, String> = + runtime.block_on(client.get_json(&url)); + + let policy = match response { + Ok(Ok(p)) => p, + Ok(Err(PolicyInfoError::PolicyDoesNotExist)) => { + return Err("policy does not exist".into()); + } + Err(e) => return Err(e), + }; + + Ok((status, policy)) + }); + match res { + Some(Ok((status, policy))) => { + let string = + crate::commands::zone::Zone::print_zone_status(status, policy, false).unwrap(); + let text = string.into_text().unwrap(); + let text = Paragraph::new(text); + + frame.render_widget(text, right); + } + Some(Err(e)) => { + let text = Paragraph::new(e as &str); + + frame.render_widget(text, right); + } + None => {} + } + } +} + +fn policies_tab(frame: &mut Frame, area: Rect, state: &mut State, client: &CascadeApiClient) { + let [left, right] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]).areas(area); + + let block = Block::bordered(); + frame.render_widget(&block, left); + frame.render_widget(&block, right); + + let left = block.inner(left); + let right = block.inner(right); + + let policies = { + let client = client.clone(); + state.policies.list.get_or_fetch(move || { + let url = "policy/"; + let runtime = tokio::runtime::Runtime::new().unwrap(); + let response: Result = runtime.block_on(client.get_json(url)); + match response { + Ok(s) => Ok(s.policies), + Err(e) => Err(e), + } + }) + }; + + let Some(policies) = policies else { + let text = Text::from("Loading..."); + draw_popup(frame, area, text); + return; + }; + + let policies = match policies { + Ok(policies) => policies, + Err(e) => { + let text = Text::from(format!("ERROR: Could not fetch policies\n{e}")); + draw_popup(frame, area, text); + return; + } + }; + + let policy_names = policies.iter().map(|z| z.to_string()).collect::>(); + let policy_list = List::new(policy_names) + .style(Style::new().white()) + .highlight_style(Style::new().black().on_white()) + .highlight_symbol("> ") + .repeat_highlight_symbol(true) + .direction(ListDirection::TopToBottom); + + if state.policies.state.selected().is_none() && !policies.is_empty() { + state.policies.state.select_first(); + } + + frame.render_stateful_widget(policy_list, left, &mut state.policies.state); + + if let Some(idx) = state.policies.state.selected() + && let Some(name) = policies.get(idx) + { + let fetch = state.policies.info.entry(name.clone()).or_default(); + let name = name.clone(); + let client = client.clone(); + let res = fetch.get_or_fetch(move || { + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let url = format!("policy/{name}"); + let response: Result, String> = + runtime.block_on(client.get_json(&url)); + + let policy = match response { + Ok(Ok(p)) => p, + Ok(Err(PolicyInfoError::PolicyDoesNotExist)) => { + return Err("policy does not exist".into()); + } + Err(e) => return Err(e), + }; + + Ok(policy) + }); + match res { + Some(Ok(policy)) => { + let string = crate::commands::policy::print_policy(policy).unwrap(); + let text = string.into_text().unwrap(); + let text = Paragraph::new(text); + + frame.render_widget(text, right); + } + Some(Err(e)) => { + let text = Paragraph::new(e as &str); + + frame.render_widget(text, right); + } + None => {} + } + } +} + +fn draw_popup(frame: &mut Frame, area: Rect, text: Text) { + let height = text.height(); + let width = text.width(); + + let [_, area, _] = Layout::vertical([ + Constraint::Fill(1), + Constraint::Length(height as u16 + 2), + Constraint::Fill(1), + ]) + .areas(area); + + let [_, area, _] = Layout::horizontal([ + Constraint::Fill(1), + Constraint::Length(width as u16 + 6), + Constraint::Fill(1), + ]) + .areas(area); + + frame.render_widget(Clear, area); + let block = Block::bordered().padding(Padding::horizontal(2)); + frame.render_widget(&block, area); + + frame.render_widget(text, block.inner(area)); +}