Render host escalation cards with Ratatui
This commit is contained in:
parent
f468592d95
commit
dfe1a387a6
5 changed files with 1105 additions and 79 deletions
509
Cargo.lock
generated
509
Cargo.lock
generated
|
|
@ -17,6 +17,12 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|
@ -82,6 +88,15 @@ version = "1.0.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
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]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
|
|
@ -112,7 +127,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -124,7 +139,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -231,6 +246,12 @@ version = "3.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "by_address"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -243,6 +264,15 @@ version = "1.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "castaway"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.67"
|
version = "1.2.67"
|
||||||
|
|
@ -323,7 +353,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -366,6 +396,20 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.16.4"
|
version = "0.16.4"
|
||||||
|
|
@ -411,6 +455,15 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
@ -460,6 +513,33 @@ 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 = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
@ -503,7 +583,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -512,8 +592,18 @@ version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.20.11",
|
||||||
"darling_macro",
|
"darling_macro 0.20.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.24.0",
|
||||||
|
"darling_macro 0.24.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -527,7 +617,20 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4"
|
||||||
|
dependencies = [
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -536,9 +639,20 @@ version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.20.11",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.24.0",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -566,7 +680,7 @@ dependencies = [
|
||||||
"defmt-parser",
|
"defmt-parser",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -623,10 +737,10 @@ version = "0.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.20.11",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -636,7 +750,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_builder_core",
|
"derive_builder_core",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -668,7 +804,16 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "document-features"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
|
||||||
|
dependencies = [
|
||||||
|
"litrs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -701,6 +846,12 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-io"
|
name = "embedded-io"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
@ -789,6 +940,12 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
|
|
@ -853,7 +1010,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -929,7 +1086,7 @@ checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1055,7 +1212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e261d54091f0d1c729bc83f54548c071bdec60a697de1e58e88bdfd7a99d24e"
|
checksum = "7e261d54091f0d1c729bc83f54548c071bdec60a697de1e58e88bdfd7a99d24e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gix-hash",
|
"gix-hash",
|
||||||
"hashbrown",
|
"hashbrown 0.17.1",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1193,11 +1350,27 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heapless"
|
name = "heapless"
|
||||||
|
|
@ -1495,7 +1668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.17.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1511,6 +1684,28 @@ dependencies = [
|
||||||
"web-time",
|
"web-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8"
|
||||||
|
dependencies = [
|
||||||
|
"darling 0.24.0",
|
||||||
|
"indoc",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
|
|
@ -1523,6 +1718,15 @@ version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -1553,7 +1757,7 @@ checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1598,7 +1802,7 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"simd_cesu8",
|
"simd_cesu8",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1617,7 +1821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1657,6 +1861,17 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "konst"
|
name = "konst"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
|
|
@ -1685,6 +1900,7 @@ dependencies = [
|
||||||
"lazaret-linux",
|
"lazaret-linux",
|
||||||
"lazaret-sandbox-contract",
|
"lazaret-sandbox-contract",
|
||||||
"nix 0.31.3",
|
"nix 0.31.3",
|
||||||
|
"ratatui",
|
||||||
"rustix",
|
"rustix",
|
||||||
"rustls",
|
"rustls",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -1896,6 +2112,21 @@ version = "0.2.186"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
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.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -1908,6 +2139,12 @@ version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litrs"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
|
@ -1923,6 +2160,15 @@ version = "0.4.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.18.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-slab"
|
name = "lru-slab"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
|
@ -1976,6 +2222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
@ -2068,7 +2315,16 @@ dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2110,8 +2366,8 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum 0.27.2",
|
||||||
"strum_macros",
|
"strum_macros 0.27.2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2127,8 +2383,8 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum 0.27.2",
|
||||||
"strum_macros",
|
"strum_macros 0.27.2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2184,6 +2440,39 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"libm",
|
||||||
|
"palette_derive",
|
||||||
|
"palette_math",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette_derive"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be"
|
||||||
|
dependencies = [
|
||||||
|
"by_address",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette_math"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12"
|
||||||
|
dependencies = [
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -2411,6 +2700,72 @@ dependencies = [
|
||||||
"rand_core 0.10.1",
|
"rand_core 0.10.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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-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",
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
"itertools",
|
||||||
|
"kasuari",
|
||||||
|
"lru",
|
||||||
|
"palette",
|
||||||
|
"serde",
|
||||||
|
"strum 0.28.0",
|
||||||
|
"thiserror",
|
||||||
|
"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-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 0.28.0",
|
||||||
|
"time",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rcgen"
|
name = "rcgen"
|
||||||
version = "0.14.8"
|
version = "0.14.8"
|
||||||
|
|
@ -2459,7 +2814,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2750,7 +3105,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2836,6 +3191,27 @@ version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
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]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.8"
|
version = "1.4.8"
|
||||||
|
|
@ -2933,6 +3309,15 @@ version = "0.27.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros 0.28.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum_macros"
|
name = "strum_macros"
|
||||||
version = "0.27.2"
|
version = "0.27.2"
|
||||||
|
|
@ -2942,7 +3327,19 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2962,6 +3359,17 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -2979,7 +3387,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3023,7 +3431,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3033,7 +3441,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
|
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
|
"libc",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
|
"num_threads",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
|
@ -3105,7 +3515,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3247,7 +3657,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3298,6 +3708,23 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -3446,7 +3873,7 @@ dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3553,7 +3980,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3564,7 +3991,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3745,7 +4172,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3766,7 +4193,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3786,7 +4213,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3807,7 +4234,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3840,7 +4267,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ oci-client = { version = "=0.17.0", default-features = false, features = ["rustl
|
||||||
oci-spec = { version = "=0.10.0", default-features = false, features = ["runtime"] }
|
oci-spec = { version = "=0.10.0", default-features = false, features = ["runtime"] }
|
||||||
postcard = { version = "=1.1.3", default-features = false, features = ["use-std"] }
|
postcard = { version = "=1.1.3", default-features = false, features = ["use-std"] }
|
||||||
rcgen = { version = "=0.14.8", default-features = false, features = ["aws_lc_rs", "x509-parser"] }
|
rcgen = { version = "=0.14.8", default-features = false, features = ["aws_lc_rs", "x509-parser"] }
|
||||||
|
ratatui = { version = "=0.30.2", default-features = false, features = ["crossterm"] }
|
||||||
redb = "=4.1.0"
|
redb = "=4.1.0"
|
||||||
libc = "=0.2.186"
|
libc = "=0.2.186"
|
||||||
nix = { version = "=0.31.3", features = ["fs", "signal"] }
|
nix = { version = "=0.31.3", features = ["fs", "signal"] }
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ rustix.workspace = true
|
||||||
rustls.workspace = true
|
rustls.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
nix.workspace = true
|
nix.workspace = true
|
||||||
|
ratatui.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,13 @@ use lazaret_core::records::{
|
||||||
};
|
};
|
||||||
use nix::sys::signal::{SigSet, SigmaskHow, Signal};
|
use nix::sys::signal::{SigSet, SigmaskHow, Signal};
|
||||||
use nix::sys::signalfd::{SfdFlags, SignalFd};
|
use nix::sys::signalfd::{SfdFlags, SignalFd};
|
||||||
|
use ratatui::backend::{Backend, CrosstermBackend};
|
||||||
|
use ratatui::buffer::Buffer;
|
||||||
|
use ratatui::crossterm::style::Colored;
|
||||||
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
use ratatui::text::{Line, Span};
|
||||||
|
use ratatui::widgets::{Block, Borders, Paragraph, Widget};
|
||||||
use rustix::fs::{OFlags, fcntl_getfl, fcntl_setfl};
|
use rustix::fs::{OFlags, fcntl_getfl, fcntl_setfl};
|
||||||
use rustix::termios::{
|
use rustix::termios::{
|
||||||
OptionalActions, QueueSelector, SpecialCodeIndex, Termios, tcflush, tcgetattr, tcgetwinsize,
|
OptionalActions, QueueSelector, SpecialCodeIndex, Termios, tcflush, tcgetattr, tcgetwinsize,
|
||||||
|
|
@ -23,7 +30,8 @@ use crate::{ClientError, ClientSessionIdentity};
|
||||||
mod prompt;
|
mod prompt;
|
||||||
|
|
||||||
use prompt::{
|
use prompt::{
|
||||||
PromptChoice, PromptInput, PromptInputDecoder, display_argv, display_bytes, prompt_outcome,
|
PromptChoice, PromptInput, PromptInputDecoder, PromptLayout, PromptLineKind, PromptView,
|
||||||
|
prompt_outcome,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait InteractiveSessionTransport {
|
pub trait InteractiveSessionTransport {
|
||||||
|
|
@ -287,6 +295,8 @@ impl HostTerminal for SystemTerminal {
|
||||||
if !self.raw {
|
if !self.raw {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
self.terminal.write_all(b"\x1b[?7h\x1b[?25h")?;
|
||||||
|
self.terminal.flush()?;
|
||||||
fcntl_setfl(&self.terminal, self.original_flags)?;
|
fcntl_setfl(&self.terminal, self.original_flags)?;
|
||||||
tcsetattr(&self.terminal, OptionalActions::Flush, &self.original)?;
|
tcsetattr(&self.terminal, OptionalActions::Flush, &self.original)?;
|
||||||
self.raw = false;
|
self.raw = false;
|
||||||
|
|
@ -347,32 +357,42 @@ impl HostTerminal for SystemTerminal {
|
||||||
self.key_translator.clear();
|
self.key_translator.clear();
|
||||||
tcflush(&self.terminal, QueueSelector::IFlush)?;
|
tcflush(&self.terminal, QueueSelector::IFlush)?;
|
||||||
let mut decoder = PromptInputDecoder::new();
|
let mut decoder = PromptInputDecoder::new();
|
||||||
self.render_prompt(prompt, decoder.choice)?;
|
let mut view = PromptView::default();
|
||||||
|
self.render_prompt(prompt, decoder.choice, &mut view, false)?;
|
||||||
let mut resumed_size = None;
|
let mut resumed_size = None;
|
||||||
loop {
|
loop {
|
||||||
match self.next_event()? {
|
match self.next_event()? {
|
||||||
HostTerminalEvent::Input(bytes) => {
|
HostTerminalEvent::Input(bytes) => {
|
||||||
let previous = decoder.choice;
|
let previous = decoder.choice;
|
||||||
if let PromptInput::Decide(choice) = decoder.feed(&bytes, Instant::now()) {
|
let input = decoder.feed(&bytes, Instant::now());
|
||||||
return Ok(prompt_outcome(choice, resumed_size));
|
let mut reviewed = false;
|
||||||
|
while let Some(review) = decoder.take_review() {
|
||||||
|
view.apply_review(review);
|
||||||
|
reviewed = true;
|
||||||
}
|
}
|
||||||
if decoder.choice != previous {
|
if let PromptInput::Decide(choice) = input {
|
||||||
self.render_prompt(prompt, decoder.choice)?;
|
return self.complete_prompt(prompt_outcome(choice, resumed_size));
|
||||||
|
}
|
||||||
|
if decoder.choice != previous || reviewed {
|
||||||
|
self.render_prompt(prompt, decoder.choice, &mut view, false)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HostTerminalEvent::LocalSuspend => {
|
HostTerminalEvent::LocalSuspend => {
|
||||||
self.suspend_and_resume()?;
|
self.suspend_and_resume()?;
|
||||||
resumed_size = Some(self.size(prompt.session_id)?);
|
resumed_size = Some(self.size(prompt.session_id)?);
|
||||||
self.render_prompt(prompt, decoder.choice)?;
|
self.render_prompt(prompt, decoder.choice, &mut view, true)?;
|
||||||
|
}
|
||||||
|
HostTerminalEvent::Terminate => {
|
||||||
|
self.write_output(b"\x1b[?7h\x1b[?25h")?;
|
||||||
|
return Err(io::ErrorKind::Interrupted.into());
|
||||||
}
|
}
|
||||||
HostTerminalEvent::Terminate => return Err(io::ErrorKind::Interrupted.into()),
|
|
||||||
HostTerminalEvent::Resize => {
|
HostTerminalEvent::Resize => {
|
||||||
resumed_size = Some(self.size(prompt.session_id)?);
|
resumed_size = Some(self.size(prompt.session_id)?);
|
||||||
self.render_prompt(prompt, decoder.choice)?;
|
self.render_prompt(prompt, decoder.choice, &mut view, true)?;
|
||||||
}
|
}
|
||||||
HostTerminalEvent::Idle => {
|
HostTerminalEvent::Idle => {
|
||||||
if let PromptInput::Decide(choice) = decoder.expire(Instant::now()) {
|
if let PromptInput::Decide(choice) = decoder.expire(Instant::now()) {
|
||||||
return Ok(prompt_outcome(choice, resumed_size));
|
return self.complete_prompt(prompt_outcome(choice, resumed_size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HostTerminalEvent::Break => {}
|
HostTerminalEvent::Break => {}
|
||||||
|
|
@ -388,30 +408,159 @@ impl HostTerminal for SystemTerminal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemTerminal {
|
impl SystemTerminal {
|
||||||
fn render_prompt(&mut self, prompt: &TerminalPrompt, choice: PromptChoice) -> io::Result<()> {
|
fn complete_prompt(&mut self, outcome: PromptOutcome) -> io::Result<PromptOutcome> {
|
||||||
let approve = if choice == PromptChoice::Approve {
|
self.write_output(b"\x1b[?7h\x1b[?25h")?;
|
||||||
"[ Approve ]"
|
Ok(outcome)
|
||||||
} else {
|
|
||||||
" Approve "
|
|
||||||
};
|
|
||||||
let deny = if choice == PromptChoice::Deny {
|
|
||||||
"[ Deny ]"
|
|
||||||
} else {
|
|
||||||
" Deny "
|
|
||||||
};
|
|
||||||
self.write_output(
|
|
||||||
format!(
|
|
||||||
"\x1b[?2026h\x1b[2J\x1b[HLazaret host request\r\nrequester: {} / {}\r\ncommand: {}\r\njustification: {}\r\n\r\n{} {}\r\n\x1b[?2026l",
|
|
||||||
display_bytes(prompt.agent_profile.as_bytes()),
|
|
||||||
hex(prompt.session_id.as_bytes()),
|
|
||||||
display_argv(&prompt.command_argv),
|
|
||||||
display_bytes(prompt.justification.as_bytes()),
|
|
||||||
approve,
|
|
||||||
deny,
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_prompt(
|
||||||
|
&mut self,
|
||||||
|
prompt: &TerminalPrompt,
|
||||||
|
choice: PromptChoice,
|
||||||
|
view: &mut PromptView,
|
||||||
|
clear_before: bool,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let size = self.size(prompt.session_id)?;
|
||||||
|
let layout = PromptLayout::new(prompt, size.columns.get(), size.rows.get());
|
||||||
|
self.terminal.write_all(b"\x1b[?2026h\x1b[?25l\x1b[?7l")?;
|
||||||
|
if clear_before {
|
||||||
|
self.terminal.write_all(b"\x1b[2J\x1b[H")?;
|
||||||
|
}
|
||||||
|
render_prompt_card(&mut self.terminal, &layout, choice, view)?;
|
||||||
|
self.terminal.write_all(b"\x1b[?7h\x1b[?2026l")?;
|
||||||
|
self.terminal.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_card(
|
||||||
|
terminal: &mut File,
|
||||||
|
layout: &PromptLayout,
|
||||||
|
choice: PromptChoice,
|
||||||
|
view: &mut PromptView,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let buffer = prompt_buffer(layout, choice, view);
|
||||||
|
let area = *buffer.area();
|
||||||
|
if area.width == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Colored::set_ansi_color_disabled(false);
|
||||||
|
let mut backend = CrosstermBackend::new(terminal);
|
||||||
|
backend.draw(buffer.content().iter().enumerate().map(|(index, cell)| {
|
||||||
|
let index = u16::try_from(index).expect("prompt buffer dimensions fit u16");
|
||||||
|
(
|
||||||
|
area.x + index % area.width,
|
||||||
|
area.y + index / area.width,
|
||||||
|
cell,
|
||||||
|
)
|
||||||
|
}))?;
|
||||||
|
Backend::flush(&mut backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_buffer(layout: &PromptLayout, choice: PromptChoice, view: &mut PromptView) -> Buffer {
|
||||||
|
let geometry = layout.geometry;
|
||||||
|
let area = Rect::new(
|
||||||
|
geometry.column - 1,
|
||||||
|
geometry.row - 1,
|
||||||
|
geometry.width,
|
||||||
|
geometry.height,
|
||||||
|
);
|
||||||
|
let surface = Style::default().bg(Color::Rgb(35, 37, 41)).fg(Color::White);
|
||||||
|
let border = Style::default().fg(Color::Cyan).bg(Color::Rgb(35, 37, 41));
|
||||||
|
let block = Block::bordered()
|
||||||
|
.title(Line::from(Span::styled(
|
||||||
|
layout.heading.as_str(),
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightCyan)
|
||||||
|
.bg(Color::Rgb(35, 37, 41))
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)))
|
||||||
|
.style(surface)
|
||||||
|
.border_style(border);
|
||||||
|
let inner = block.inner(area);
|
||||||
|
let [
|
||||||
|
status_area,
|
||||||
|
detail_area,
|
||||||
|
rule_area,
|
||||||
|
selection_area,
|
||||||
|
action_area,
|
||||||
|
legend_area,
|
||||||
|
] = Layout::vertical([
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Min(0),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
])
|
||||||
|
.areas(inner);
|
||||||
|
let (status, details) = layout.detail_window(view, detail_area.height);
|
||||||
|
let mut buffer = Buffer::empty(area);
|
||||||
|
block.render(area, &mut buffer);
|
||||||
|
Paragraph::new(prompt_line(status))
|
||||||
|
.style(surface)
|
||||||
|
.render(status_area, &mut buffer);
|
||||||
|
Paragraph::new(details.into_iter().map(prompt_line).collect::<Vec<_>>())
|
||||||
|
.style(surface)
|
||||||
|
.render(detail_area, &mut buffer);
|
||||||
|
Block::new()
|
||||||
|
.borders(Borders::TOP)
|
||||||
|
.style(surface)
|
||||||
|
.border_style(border)
|
||||||
|
.render(rule_area, &mut buffer);
|
||||||
|
Paragraph::new(selection_text(choice))
|
||||||
|
.style(surface.fg(Color::Yellow).add_modifier(Modifier::BOLD))
|
||||||
|
.render(selection_area, &mut buffer);
|
||||||
|
Paragraph::new(action_text(choice))
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.style(action_style(choice))
|
||||||
|
.render(action_area, &mut buffer);
|
||||||
|
Paragraph::new("Up/Down: review | Left/Right: select | Enter: decide")
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.style(surface.fg(Color::Gray))
|
||||||
|
.render(legend_area, &mut buffer);
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_line(line: prompt::PromptLine) -> Line<'static> {
|
||||||
|
Line::styled(line.text, prompt_line_style(line.kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_line_style(kind: PromptLineKind) -> Style {
|
||||||
|
match kind {
|
||||||
|
PromptLineKind::Section => Style::default()
|
||||||
|
.fg(Color::Yellow)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
PromptLineKind::TrustedValue | PromptLineKind::Continuation => {
|
||||||
|
Style::default().fg(Color::LightCyan)
|
||||||
|
}
|
||||||
|
PromptLineKind::RequesterValue => Style::default().fg(Color::White),
|
||||||
|
PromptLineKind::Argument => Style::default().fg(Color::Gray),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection_text(choice: PromptChoice) -> &'static str {
|
||||||
|
match choice {
|
||||||
|
PromptChoice::Approve => "Selection: APPROVE | Esc: deny",
|
||||||
|
PromptChoice::Deny => "Selection: DENY (default) | Esc: deny",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action_text(choice: PromptChoice) -> &'static str {
|
||||||
|
match choice {
|
||||||
|
PromptChoice::Approve => "< APPROVE > [ Deny ]",
|
||||||
|
PromptChoice::Deny => "[ Approve ] > DENY (default) <",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action_style(choice: PromptChoice) -> Style {
|
||||||
|
let color = match choice {
|
||||||
|
PromptChoice::Approve => Color::LightGreen,
|
||||||
|
PromptChoice::Deny => Color::LightRed,
|
||||||
|
};
|
||||||
|
Style::default()
|
||||||
|
.bg(Color::Rgb(35, 37, 41))
|
||||||
|
.fg(color)
|
||||||
|
.add_modifier(Modifier::BOLD)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for SystemTerminal {
|
impl Drop for SystemTerminal {
|
||||||
|
|
@ -732,15 +881,19 @@ mod tests {
|
||||||
use std::io::Write as _;
|
use std::io::Write as _;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use lazaret_core::records::TerminalPrompt;
|
||||||
use nix::sys::signal::{SigSet, SigmaskHow, Signal, kill};
|
use nix::sys::signal::{SigSet, SigmaskHow, Signal, kill};
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
|
use ratatui::layout::Rect;
|
||||||
|
use ratatui::style::Color;
|
||||||
use rustix::fs::fcntl_getfl;
|
use rustix::fs::fcntl_getfl;
|
||||||
use rustix::pty::{OpenptFlags, grantpt, ioctl_tiocgptpeer, openpt, unlockpt};
|
use rustix::pty::{OpenptFlags, grantpt, ioctl_tiocgptpeer, openpt, unlockpt};
|
||||||
use rustix::termios::{LocalModes, tcgetattr};
|
use rustix::termios::{LocalModes, tcgetattr};
|
||||||
|
|
||||||
|
use super::prompt::{display_argv, display_bytes};
|
||||||
use super::{
|
use super::{
|
||||||
HostTerminal, HostTerminalEvent, PromptChoice, PromptInput, PromptInputDecoder,
|
HostTerminal, HostTerminalEvent, PromptChoice, PromptInput, PromptInputDecoder,
|
||||||
SignalQueue, SystemTerminal, display_argv, display_bytes,
|
SignalQueue, SystemTerminal,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn terminal() -> (SystemTerminal, File) {
|
fn terminal() -> (SystemTerminal, File) {
|
||||||
|
|
@ -861,6 +1014,22 @@ mod tests {
|
||||||
PromptInputDecoder::new().feed(b"y", start),
|
PromptInputDecoder::new().feed(b"y", start),
|
||||||
PromptInput::Decide(PromptChoice::Approve)
|
PromptInput::Decide(PromptChoice::Approve)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let mut decoder = PromptInputDecoder::new();
|
||||||
|
assert!(matches!(
|
||||||
|
decoder.feed(b"\x1b[A\x1b[B", start),
|
||||||
|
PromptInput::Continue
|
||||||
|
));
|
||||||
|
assert_eq!(decoder.take_review(), Some(super::prompt::PromptReview::Up));
|
||||||
|
assert_eq!(
|
||||||
|
decoder.take_review(),
|
||||||
|
Some(super::prompt::PromptReview::Down)
|
||||||
|
);
|
||||||
|
assert_eq!(decoder.choice, PromptChoice::Deny);
|
||||||
|
assert!(matches!(
|
||||||
|
decoder.feed(b"\r", start),
|
||||||
|
PromptInput::Decide(PromptChoice::Deny)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -872,6 +1041,40 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ratatui_prompt_buffer_is_scoped_to_the_centered_opaque_card() {
|
||||||
|
use lazaret_core::identity::{RequestDigest, RequestId, SessionId};
|
||||||
|
|
||||||
|
let prompt = TerminalPrompt {
|
||||||
|
session_id: SessionId::new([1; 32]),
|
||||||
|
request_id: RequestId::new([2; 32]),
|
||||||
|
request_sha256: RequestDigest::new([3; 32]),
|
||||||
|
agent_profile: "trusted-agent".into(),
|
||||||
|
command_argv: vec![b"/usr/bin/host-tool".to_vec()],
|
||||||
|
justification: "request host operation".into(),
|
||||||
|
};
|
||||||
|
let layout = super::PromptLayout::new(&prompt, 140, 40);
|
||||||
|
let buffer = super::prompt_buffer(
|
||||||
|
&layout,
|
||||||
|
PromptChoice::Deny,
|
||||||
|
&mut super::PromptView::default(),
|
||||||
|
);
|
||||||
|
assert_eq!(*buffer.area(), Rect::new(35, 10, 70, 20));
|
||||||
|
assert!(
|
||||||
|
buffer
|
||||||
|
.content()
|
||||||
|
.iter()
|
||||||
|
.all(|cell| cell.bg == Color::Rgb(35, 37, 41))
|
||||||
|
);
|
||||||
|
let rendered = buffer
|
||||||
|
.content()
|
||||||
|
.iter()
|
||||||
|
.map(ratatui::buffer::Cell::symbol)
|
||||||
|
.collect::<String>();
|
||||||
|
assert!(rendered.contains("DENY (default)"));
|
||||||
|
assert!(rendered.contains("Up/Down: review"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn qualified_host_keys_translate_to_the_controlled_guest_profile() {
|
fn qualified_host_keys_translate_to_the_controlled_guest_profile() {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use lazaret_core::records::{DeveloperDecisionKind, TerminalResize};
|
use lazaret_core::records::{DeveloperDecisionKind, TerminalPrompt, TerminalResize};
|
||||||
|
|
||||||
use super::{PROMPT_ESCAPE_INTERVAL, PromptOutcome};
|
use super::{PROMPT_ESCAPE_INTERVAL, PromptOutcome, hex};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub(super) enum PromptChoice {
|
pub(super) enum PromptChoice {
|
||||||
|
|
@ -15,10 +16,275 @@ pub(super) enum PromptInput {
|
||||||
Decide(PromptChoice),
|
Decide(PromptChoice),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) enum PromptReview {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct PromptView {
|
||||||
|
detail_offset: usize,
|
||||||
|
review_delta: i8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromptView {
|
||||||
|
pub(super) fn apply_review(&mut self, review: PromptReview) {
|
||||||
|
self.review_delta = match review {
|
||||||
|
PromptReview::Up => self.review_delta.saturating_sub(1),
|
||||||
|
PromptReview::Down => self.review_delta.saturating_add(1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_offset(&mut self, maximum: usize) {
|
||||||
|
let delta = usize::from(self.review_delta.unsigned_abs());
|
||||||
|
self.detail_offset = if self.review_delta.is_negative() {
|
||||||
|
self.detail_offset.saturating_sub(delta)
|
||||||
|
} else {
|
||||||
|
self.detail_offset.saturating_add(delta).min(maximum)
|
||||||
|
};
|
||||||
|
self.review_delta = 0;
|
||||||
|
self.detail_offset = self.detail_offset.min(maximum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) struct PromptGeometry {
|
||||||
|
pub(super) column: u16,
|
||||||
|
pub(super) row: u16,
|
||||||
|
pub(super) width: u16,
|
||||||
|
pub(super) height: u16,
|
||||||
|
pub(super) fullscreen: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(super) enum PromptLineKind {
|
||||||
|
Section,
|
||||||
|
TrustedValue,
|
||||||
|
RequesterValue,
|
||||||
|
Argument,
|
||||||
|
Continuation,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct PromptLine {
|
||||||
|
pub(super) kind: PromptLineKind,
|
||||||
|
pub(super) text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct PromptLayout {
|
||||||
|
pub(super) geometry: PromptGeometry,
|
||||||
|
pub(super) heading: String,
|
||||||
|
details: Vec<PromptLine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromptLayout {
|
||||||
|
pub(super) fn new(prompt: &TerminalPrompt, columns: u16, rows: u16) -> Self {
|
||||||
|
let geometry = prompt_geometry(columns, rows);
|
||||||
|
let inner_width = usize::from(geometry.width.saturating_sub(2)).max(1);
|
||||||
|
let details = detail_lines(prompt, inner_width);
|
||||||
|
Self {
|
||||||
|
geometry,
|
||||||
|
heading: if geometry.fullscreen {
|
||||||
|
"LAZARET HOST ESCALATION | FULL-SCREEN TRUSTED DECISION".into()
|
||||||
|
} else {
|
||||||
|
"LAZARET HOST ESCALATION | TRUSTED DECISION SURFACE".into()
|
||||||
|
},
|
||||||
|
details,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn detail_window(
|
||||||
|
&self,
|
||||||
|
view: &mut PromptView,
|
||||||
|
capacity: u16,
|
||||||
|
) -> (PromptLine, Vec<PromptLine>) {
|
||||||
|
let visible_details = usize::from(capacity);
|
||||||
|
let maximum = self.details.len().saturating_sub(visible_details);
|
||||||
|
view.apply_offset(maximum);
|
||||||
|
let end = view
|
||||||
|
.detail_offset
|
||||||
|
.saturating_add(visible_details)
|
||||||
|
.min(self.details.len());
|
||||||
|
(
|
||||||
|
PromptLine {
|
||||||
|
kind: PromptLineKind::Section,
|
||||||
|
text: format!(
|
||||||
|
"REQUEST DETAILS {}-{} OF {} | UP/DOWN TO REVIEW",
|
||||||
|
view.detail_offset.saturating_add(1),
|
||||||
|
end,
|
||||||
|
self.details.len()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
self.details[view.detail_offset..end]
|
||||||
|
.iter()
|
||||||
|
.map(|line| PromptLine {
|
||||||
|
kind: line.kind,
|
||||||
|
text: line.text.clone(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIN_MODAL_COLUMNS: u16 = 60;
|
||||||
|
const MIN_MODAL_ROWS: u16 = 18;
|
||||||
|
|
||||||
|
fn prompt_geometry(columns: u16, rows: u16) -> PromptGeometry {
|
||||||
|
let width = columns / 2;
|
||||||
|
let height = rows / 2;
|
||||||
|
let fullscreen = width < MIN_MODAL_COLUMNS || height < MIN_MODAL_ROWS;
|
||||||
|
if fullscreen {
|
||||||
|
return PromptGeometry {
|
||||||
|
column: 1,
|
||||||
|
row: 1,
|
||||||
|
width: columns.max(1),
|
||||||
|
height: rows.max(1),
|
||||||
|
fullscreen: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PromptGeometry {
|
||||||
|
column: (columns - width) / 2 + 1,
|
||||||
|
row: (rows - height) / 2 + 1,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fullscreen: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detail_lines(prompt: &TerminalPrompt, width: usize) -> Vec<PromptLine> {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind: PromptLineKind::TrustedValue,
|
||||||
|
text: format!(
|
||||||
|
"Profile (Lazaret): {}",
|
||||||
|
display_bytes(prompt.agent_profile.as_bytes())
|
||||||
|
),
|
||||||
|
});
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind: PromptLineKind::TrustedValue,
|
||||||
|
text: "Session (Lazaret):".into(),
|
||||||
|
});
|
||||||
|
lines.extend(wrap_with_indent(
|
||||||
|
&hex(prompt.session_id.as_bytes()),
|
||||||
|
width,
|
||||||
|
2,
|
||||||
|
PromptLineKind::Continuation,
|
||||||
|
));
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind: PromptLineKind::Section,
|
||||||
|
text: "REQUESTER-PROVIDED JUSTIFICATION".into(),
|
||||||
|
});
|
||||||
|
lines.extend(wrap_words(
|
||||||
|
&display_bytes(prompt.justification.as_bytes()),
|
||||||
|
width,
|
||||||
|
PromptLineKind::RequesterValue,
|
||||||
|
));
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind: PromptLineKind::Section,
|
||||||
|
text: "REQUESTER-PROVIDED ARGUMENTS".into(),
|
||||||
|
});
|
||||||
|
for (index, argument) in prompt.command_argv.iter().enumerate() {
|
||||||
|
let prefix = format!("[{index}] ");
|
||||||
|
lines.extend(wrap_with_prefix(
|
||||||
|
&display_bytes(argument),
|
||||||
|
width,
|
||||||
|
&prefix,
|
||||||
|
PromptLineKind::Argument,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_words(text: &str, width: usize, kind: PromptLineKind) -> Vec<PromptLine> {
|
||||||
|
let width = width.max(1);
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
let mut line = String::new();
|
||||||
|
for word in text.split_whitespace() {
|
||||||
|
if line.is_empty() {
|
||||||
|
line.push_str(word);
|
||||||
|
} else if line.len() + 1 + word.len() <= width {
|
||||||
|
line.push(' ');
|
||||||
|
line.push_str(word);
|
||||||
|
} else {
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind,
|
||||||
|
text: std::mem::take(&mut line),
|
||||||
|
});
|
||||||
|
line.push_str(word);
|
||||||
|
}
|
||||||
|
while line.len() > width {
|
||||||
|
let remainder = line.split_off(width);
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind,
|
||||||
|
text: std::mem::replace(&mut line, remainder),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if line.is_empty() {
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind,
|
||||||
|
text: "(empty)".into(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
lines.push(PromptLine { kind, text: line });
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_with_indent(
|
||||||
|
text: &str,
|
||||||
|
width: usize,
|
||||||
|
indent: usize,
|
||||||
|
kind: PromptLineKind,
|
||||||
|
) -> Vec<PromptLine> {
|
||||||
|
wrap_with_prefix(text, width, &" ".repeat(indent), kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_with_prefix(
|
||||||
|
text: &str,
|
||||||
|
width: usize,
|
||||||
|
prefix: &str,
|
||||||
|
kind: PromptLineKind,
|
||||||
|
) -> Vec<PromptLine> {
|
||||||
|
let width = width.max(1);
|
||||||
|
let prefix = if prefix.len() >= width {
|
||||||
|
&prefix[..width]
|
||||||
|
} else {
|
||||||
|
prefix
|
||||||
|
};
|
||||||
|
let continuation = " ".repeat(prefix.len());
|
||||||
|
let available = width.saturating_sub(prefix.len()).max(1);
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
let mut remaining = text;
|
||||||
|
let mut current_prefix = prefix;
|
||||||
|
loop {
|
||||||
|
let (segment, rest) = if remaining.len() <= available {
|
||||||
|
(remaining, None)
|
||||||
|
} else {
|
||||||
|
let mut split = available;
|
||||||
|
while !remaining.is_char_boundary(split) {
|
||||||
|
split -= 1;
|
||||||
|
}
|
||||||
|
(&remaining[..split], Some(&remaining[split..]))
|
||||||
|
};
|
||||||
|
lines.push(PromptLine {
|
||||||
|
kind,
|
||||||
|
text: format!("{current_prefix}{segment}"),
|
||||||
|
});
|
||||||
|
let Some(rest) = rest else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
remaining = rest;
|
||||||
|
current_prefix = &continuation;
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) struct PromptInputDecoder {
|
pub(super) struct PromptInputDecoder {
|
||||||
pub(super) choice: PromptChoice,
|
pub(super) choice: PromptChoice,
|
||||||
escape: Vec<u8>,
|
escape: Vec<u8>,
|
||||||
escape_deadline: Option<Instant>,
|
escape_deadline: Option<Instant>,
|
||||||
|
reviews: VecDeque<PromptReview>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PromptInputDecoder {
|
impl PromptInputDecoder {
|
||||||
|
|
@ -27,6 +293,7 @@ impl PromptInputDecoder {
|
||||||
choice: PromptChoice::Deny,
|
choice: PromptChoice::Deny,
|
||||||
escape: Vec::new(),
|
escape: Vec::new(),
|
||||||
escape_deadline: None,
|
escape_deadline: None,
|
||||||
|
reviews: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,6 +315,14 @@ impl PromptInputDecoder {
|
||||||
self.choice = PromptChoice::Deny;
|
self.choice = PromptChoice::Deny;
|
||||||
self.clear_escape();
|
self.clear_escape();
|
||||||
}
|
}
|
||||||
|
b"\x1b[A" | b"\x1bOA" => {
|
||||||
|
self.reviews.push_back(PromptReview::Up);
|
||||||
|
self.clear_escape();
|
||||||
|
}
|
||||||
|
b"\x1b[B" | b"\x1bOB" => {
|
||||||
|
self.reviews.push_back(PromptReview::Down);
|
||||||
|
self.clear_escape();
|
||||||
|
}
|
||||||
sequence if supported_prompt_key_prefix(sequence) => {}
|
sequence if supported_prompt_key_prefix(sequence) => {}
|
||||||
_ => return PromptInput::Decide(PromptChoice::Deny),
|
_ => return PromptInput::Decide(PromptChoice::Deny),
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +349,10 @@ impl PromptInputDecoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn take_review(&mut self) -> Option<PromptReview> {
|
||||||
|
self.reviews.pop_front()
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_escape(&mut self) {
|
fn clear_escape(&mut self) {
|
||||||
self.escape.clear();
|
self.escape.clear();
|
||||||
self.escape_deadline = None;
|
self.escape_deadline = None;
|
||||||
|
|
@ -81,9 +360,18 @@ impl PromptInputDecoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supported_prompt_key_prefix(bytes: &[u8]) -> bool {
|
fn supported_prompt_key_prefix(bytes: &[u8]) -> bool {
|
||||||
[b"\x1b[D".as_slice(), b"\x1b[C", b"\x1bOD", b"\x1bOC"]
|
[
|
||||||
.iter()
|
b"\x1b[A".as_slice(),
|
||||||
.any(|sequence| sequence.starts_with(bytes))
|
b"\x1b[B",
|
||||||
|
b"\x1b[D",
|
||||||
|
b"\x1b[C",
|
||||||
|
b"\x1bOA",
|
||||||
|
b"\x1bOB",
|
||||||
|
b"\x1bOD",
|
||||||
|
b"\x1bOC",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|sequence| sequence.starts_with(bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn prompt_outcome(
|
pub(super) fn prompt_outcome(
|
||||||
|
|
@ -99,6 +387,7 @@ pub(super) fn prompt_outcome(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
pub(super) fn display_argv(argv: &[Vec<u8>]) -> String {
|
pub(super) fn display_argv(argv: &[Vec<u8>]) -> String {
|
||||||
let arguments = argv
|
let arguments = argv
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -121,3 +410,108 @@ pub(super) fn display_bytes(bytes: &[u8]) -> String {
|
||||||
output
|
output
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use lazaret_core::identity::{RequestDigest, RequestId, SessionId};
|
||||||
|
|
||||||
|
use super::{PromptLayout, PromptView};
|
||||||
|
|
||||||
|
fn prompt(justification: &str) -> lazaret_core::records::TerminalPrompt {
|
||||||
|
lazaret_core::records::TerminalPrompt {
|
||||||
|
session_id: SessionId::new([1; 32]),
|
||||||
|
request_id: RequestId::new([2; 32]),
|
||||||
|
request_sha256: RequestDigest::new([3; 32]),
|
||||||
|
agent_profile: "trusted-agent".into(),
|
||||||
|
command_argv: vec![
|
||||||
|
b"/usr/bin/privileged-tool".to_vec(),
|
||||||
|
b"--replace-host-state".to_vec(),
|
||||||
|
],
|
||||||
|
justification: justification.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modal_geometry_uses_the_qualified_centered_thresholds() {
|
||||||
|
let prompt = prompt("needed");
|
||||||
|
for (columns, rows, expected) in [
|
||||||
|
(
|
||||||
|
140,
|
||||||
|
40,
|
||||||
|
super::PromptGeometry {
|
||||||
|
column: 36,
|
||||||
|
row: 11,
|
||||||
|
width: 70,
|
||||||
|
height: 20,
|
||||||
|
fullscreen: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
120,
|
||||||
|
38,
|
||||||
|
super::PromptGeometry {
|
||||||
|
column: 31,
|
||||||
|
row: 10,
|
||||||
|
width: 60,
|
||||||
|
height: 19,
|
||||||
|
fullscreen: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
100,
|
||||||
|
24,
|
||||||
|
super::PromptGeometry {
|
||||||
|
column: 1,
|
||||||
|
row: 1,
|
||||||
|
width: 100,
|
||||||
|
height: 24,
|
||||||
|
fullscreen: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let layout = PromptLayout::new(&prompt, columns, rows);
|
||||||
|
assert_eq!(layout.geometry, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn requester_details_scroll_without_displacing_the_decision_controls() {
|
||||||
|
let prompt = prompt(
|
||||||
|
"one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty",
|
||||||
|
);
|
||||||
|
let mut view = PromptView::default();
|
||||||
|
let initial = PromptLayout::new(&prompt, 120, 38);
|
||||||
|
let (status, _) = initial.detail_window(&mut view, 10);
|
||||||
|
assert_eq!(
|
||||||
|
status.text,
|
||||||
|
"REQUEST DETAILS 1-10 OF 11 | UP/DOWN TO REVIEW"
|
||||||
|
);
|
||||||
|
view.apply_review(super::PromptReview::Down);
|
||||||
|
let (status, details) = initial.detail_window(&mut view, 10);
|
||||||
|
assert_eq!(
|
||||||
|
status.text,
|
||||||
|
"REQUEST DETAILS 2-11 OF 11 | UP/DOWN TO REVIEW"
|
||||||
|
);
|
||||||
|
assert_eq!(details.len(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn requester_text_is_escaped_and_arguments_are_separate_rows() {
|
||||||
|
let mut prompt = prompt("review \u{1b}[2J this");
|
||||||
|
prompt.command_argv = vec![b"one two".to_vec(), b"three\x1b[H".to_vec()];
|
||||||
|
let layout = PromptLayout::new(&prompt, 100, 24);
|
||||||
|
assert!(
|
||||||
|
layout
|
||||||
|
.details
|
||||||
|
.iter()
|
||||||
|
.any(|line| line.text == "review \\x1b[2J this")
|
||||||
|
);
|
||||||
|
assert!(layout.details.iter().any(|line| line.text == "[0] one two"));
|
||||||
|
assert!(
|
||||||
|
layout
|
||||||
|
.details
|
||||||
|
.iter()
|
||||||
|
.any(|line| line.text == "[1] three\\x1b[H")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue