diff --git a/Cargo.lock b/Cargo.lock index 58418f38..9ce0f43e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.1" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" dependencies = [ "actix-codec", "actix-rt", @@ -61,7 +61,7 @@ dependencies = [ "derive_more", "encoding_rs", "flate2", - "foldhash 0.1.5", + "foldhash 0.2.0", "futures-core", "h2 0.3.27", "http 0.2.12", @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -154,7 +154,7 @@ dependencies = [ "actix-service", "actix-utils", "futures-core", - "impl-more", + "impl-more 0.1.9", "pin-project-lite", "rustls-pki-types", "tokio", @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.13.0" +version = "4.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" dependencies = [ "actix-codec", "actix-http", @@ -195,10 +195,10 @@ dependencies = [ "cookie", "derive_more", "encoding_rs", - "foldhash 0.1.5", + "foldhash 0.2.0", "futures-core", "futures-util", - "impl-more", + "impl-more 0.3.1", "itoa", "language-tags", "log", @@ -226,7 +226,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -312,9 +312,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -431,9 +431,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "ascii" @@ -481,7 +481,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -493,7 +493,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -505,7 +505,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -601,7 +601,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -641,7 +641,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -658,7 +658,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -687,7 +687,7 @@ dependencies = [ "manyhow", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -703,7 +703,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -819,7 +819,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex 1.3.0", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -866,9 +866,9 @@ dependencies = [ [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium 0.7.0", @@ -950,9 +950,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" dependencies = [ "borsh-derive", "bytes", @@ -961,22 +961,22 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "brotli" -version = "8.0.3" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -985,9 +985,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.1" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -995,13 +995,13 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "a4e308e67087593070530a666f7fe8815cbd2e61d8f5b7313b71b7d721d1dfde" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -1021,9 +1021,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "byte-unit" -version = "5.2.0" +version = "5.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6d47a4e2961fb8721bcfc54feae6455f2f64e7054f9bc67e875f0e77f4c58d" +checksum = "37bcaa4a0975bed4a760af3efe4368825098ce5f9d37a30c5a021d635dc63d8f" dependencies = [ "rust_decimal", "schemars", @@ -1067,9 +1067,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "bytestring" @@ -1196,9 +1196,9 @@ dependencies = [ [[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", "jobserver", @@ -1229,9 +1229,9 @@ 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 0.3.0", @@ -1337,7 +1337,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1551,36 +1551,36 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2be1bdbf929c2a2242cbbe15d6583c56f1cc723c6c8452d0179362de28c9d5" +checksum = "3cd990d8a6304475bbad64534a0d418f5572f44d5f011437e6b9f1ee7d5c2570" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a0336914de11298290783a95a9a7154b894da601659eb5f8f8bc62d1bea98f8" +checksum = "ccabe4636007296721080e02d7dab46d4319638ec4e3f6f7402fcb46dc5122c6" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb972cba51a52c1b2a329fec993b911e4d1f9cfab3795811a319b6746c28e014" +checksum = "da7ed173c870c0aea202a9830880156905a028a88df076e35ce383a8acbf90a7" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642c920666bfed9aebca39d8c6e7cb76f09314cc7a4074b1db5edcccdde771b9" +checksum = "800cc586df98b12c502e76707c96565e40629a5322eaa15aaa34ba05f5721e31" dependencies = [ "serde", "serde_derive", @@ -1588,9 +1588,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1231caaeee3d2363d9b2dba9d6c1f7ff835b8ede6612fba98120af73df44bd" +checksum = "ae93f863f9094ae34d2567f9edb0ae2c41d35228b286598354dd78b198868ebd" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -1615,9 +1615,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb83e89be8b413e4f7a4215a02d5c5f3e6f04b1060f5db293dd1007b2871dcf5" +checksum = "38c505162bcf77dcb859905b3eac56a1917fc3cf326424fb06e7732031e3a8ae" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -1628,24 +1628,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d14f8068a98f0a85ffa63dc5fe73cb486a955adbe7311465d13cde54c656d5f" +checksum = "e3b786958bcb79bdb5fbae095af58f0c2da7d7895c475c991f6a6bb5a9c7e6d9" [[package]] name = "cranelift-control" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c070aee9312b9736028e99b58d45e1099683386082af38529d5e2ce8c76648f3" +checksum = "2faf9a5009bce7f725ce2af7a08c4883ebac6af933e7e0aa7d84f976f4e6deb5" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d619bb3d14251e96dc9b6a846d6955d78048a168cc3876eb2b789b855c1c22" +checksum = "017271194ba5e101d626560d0d6767efd341468d1ba0f4d015f19fe64020b65b" dependencies = [ "cranelift-bitset", "serde", @@ -1654,9 +1654,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2350fcff24d78be5e4201e1eeb4b306e474b9f21e452722b21ffc4f773e8d49a" +checksum = "f80847f0929967f0cec82f9e0543b3901e0f0063690405891f22107b5a130fd8" dependencies = [ "cranelift-codegen", "log", @@ -1666,15 +1666,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdc2b14d7491c53c2989b967b4c07511374733abbc01a895fb01ea31e97bfc8" +checksum = "75904abbc0e7b46d20f7a49c8042c8a4481c0db4253b99889c723c566295d506" [[package]] name = "cranelift-native" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98dbe1326d0001a17b3b0675e3adafcfbd0e7f25f1f845a2f1bb9ce3029f359" +checksum = "6e0135923540574362e16f01bf40000664263840991039ff3041ba717de6cf3a" dependencies = [ "cranelift-codegen", "libc", @@ -1683,9 +1683,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.123.10" +version = "0.123.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36d7af563cd300c8a1e4e64387929b40e32867112143f0a0e1ce90f977ce4a41" +checksum = "93fb12f76c482e034f6ebefa843c914e74112f088215d8d36d33a649f9fab99b" [[package]] name = "crc32fast" @@ -1880,7 +1880,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1912,7 +1912,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1923,7 +1923,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1955,6 +1955,38 @@ dependencies = [ "uuid", ] +[[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" @@ -2021,7 +2053,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2029,9 +2061,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive-where" @@ -2041,7 +2070,7 @@ checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2052,7 +2081,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2074,7 +2103,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", "unicode-xid", ] @@ -2176,7 +2205,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2324,7 +2353,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2515,7 +2544,24 @@ dependencies = [ "globset", "hashbrown 0.16.1", "ignore", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "filescream" +version = "0.1.0" +source = "git+https://github.com/tinythings/omnitrace.git#c41ec28599d3cb2c528ad6392506cc7f61599f22" +dependencies = [ + "async-trait", + "bitflags 2.13.0", + "blake3", + "globset", + "hashbrown 0.16.1", + "ignore", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git)", "serde", "serde_json", "tokio", @@ -2718,7 +2764,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2806,7 +2852,7 @@ checksum = "f2b6d1e2f75c16bfbcd0f95d84f99858a6e2f885c2287d1f5c3a96e8444a34b4" dependencies = [ "attribute-derive", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2870,17 +2916,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", "wasm-bindgen", ] @@ -2957,9 +3001,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", @@ -3225,7 +3269,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.14", + "h2 0.4.15", "http 1.4.2", "http-body", "httparse", @@ -3522,7 +3566,7 @@ dependencies = [ "bitflags 2.13.0", "libc", "log", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "serde", "tokio", ] @@ -3549,6 +3593,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +[[package]] +name = "impl-more" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" + [[package]] name = "indexmap" version = "1.9.3" @@ -3610,7 +3660,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3712,7 +3762,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3778,10 +3828,11 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "34f877a98676d2fb664698d74cc6a51ce6c484ce8c770f05d0108ec9090aeb46" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -3791,13 +3842,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "0666b5ab5ecaca213fc2a85b8c0083d9004e84ee2d5f9a7e0017aaf50986f25f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3827,7 +3878,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3846,7 +3897,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3861,9 +3912,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.100" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -3974,9 +4025,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "leb128" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" [[package]] name = "leb128fmt" @@ -4112,9 +4163,9 @@ dependencies = [ [[package]] name = "libffi" -version = "5.1.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" +checksum = "ed185dbb87539a100c1b36c219e16e71572c6d4d4fed3ded898140f755adeaaf" dependencies = [ "libc", "libffi-sys", @@ -4122,9 +4173,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" +checksum = "25831b230b6a90bdea9f28339c1d00d59773a1c492e8ca09b1ad80e56394c261" dependencies = [ "cc", ] @@ -4244,7 +4295,7 @@ dependencies = [ "dashmap", "env_logger", "fastrand", - "filescream", + "filescream 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "humantime", "iface", "indexmap 2.14.0", @@ -4256,7 +4307,7 @@ dependencies = [ "log", "netpacket", "nettools", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "procdog", "serde", "serde_json", @@ -4445,9 +4496,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1116a951fd9d5110720bb2ae66f72ce5d7b10bd8aa8744924677157089ce13f" +checksum = "3deaad727e11899800b9a177af14682bfa5d5d96fa525a6c9bd13e650c019f40" dependencies = [ "zlib-rs", ] @@ -4525,9 +4576,9 @@ 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 = "lru" @@ -4612,9 +4663,9 @@ dependencies = [ [[package]] name = "malachite-base" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b6f86fdbb1eb9955946be91775239dfcb0acdb1a51bb07d5fc9b8c854f5ccd" +checksum = "a4f44099731f17094b07825c88ccb5fbd1bfa1f82fafff7daa33e8b8652db16e" dependencies = [ "hashbrown 0.16.1", "itertools 0.14.0", @@ -4624,9 +4675,9 @@ dependencies = [ [[package]] name = "malachite-bigint" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fcd6e504ffc67db2b3c6d5e90e08054646e2b04f42115a5460bf1c1e37d3bc" +checksum = "cc58206ba15e9c406e20c95c5f86efa07b12f94080945908e910b3a0faa23fef" dependencies = [ "malachite-base", "malachite-nz", @@ -4637,9 +4688,9 @@ dependencies = [ [[package]] name = "malachite-nz" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0197a2f5cfee19d59178e282985c6ca79a9233e26a2adcf40acb693896aa09f6" +checksum = "a137660cdba20f136c8a223125f08088adb4e0b72fbb8466f08c43e31cc0427d" dependencies = [ "itertools 0.14.0", "libm", @@ -4649,11 +4700,12 @@ dependencies = [ [[package]] name = "malachite-q" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2add95162aede090c48f0ee51bea7d328847ce3180aa44588111f846cc116b" +checksum = "5ffcbeed95e34c0fcc3864ccd146e129cbbf7de1513d3afbcfb47c7674c82d94" dependencies = [ "itertools 0.14.0", + "libm", "malachite-base", "malachite-nz", ] @@ -4667,7 +4719,7 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4726,9 +4778,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -4904,7 +4956,7 @@ dependencies = [ "glob", "libc", "log", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "pnet", "serde", "serde_json", @@ -4920,7 +4972,7 @@ dependencies = [ "bitflags 2.13.0", "libc", "log", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "serde", "serde_json", "tokio", @@ -5044,7 +5096,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5105,7 +5157,7 @@ checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5177,6 +5229,21 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "omnitrace-core" +version = "0.1.0" +source = "git+https://github.com/tinythings/omnitrace.git#c41ec28599d3cb2c528ad6392506cc7f61599f22" +dependencies = [ + "async-trait", + "bitflags 2.13.0", + "libc", + "log", + "serde", + "serde_json", + "tokio", + "tokio-util", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -5191,9 +5258,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.80" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ "bitflags 2.13.0", "cfg-if", @@ -5211,7 +5278,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5231,9 +5298,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -5402,7 +5469,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5598,7 +5665,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5682,7 +5749,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5695,7 +5762,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5733,7 +5800,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5857,7 +5924,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5905,7 +5972,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6031,7 +6098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6084,6 +6151,28 @@ dependencies = [ "toml_edit 0.25.12+spec-1.1.0", ] +[[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-macro-utils" version = "0.10.0" @@ -6112,7 +6201,7 @@ dependencies = [ "async-trait", "bitflags 2.13.0", "libc", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "serde", "serde_json", "tokio", @@ -6194,7 +6283,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.117", + "syn 2.0.118", "tempfile", ] @@ -6208,7 +6297,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6252,9 +6341,9 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329f575a931601f71fbcb3b31d32d16273da5ba7f532fc10be2e432e710b02de" +checksum = "558181096e0df4984f45cfc3a7087052df4a61c36089b135a08ceca9cbd352fb" dependencies = [ "cranelift-bitset", "log", @@ -6264,13 +6353,13 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bccae89ed67a40989e780105fab43e6c71a077b9fc8ae4c805ff5f73d2a79c8" +checksum = "b5d52e2f14e168d75cdabe9bd5fb1ff18a1b119dc6699684aee895dbc3524da9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6316,9 +6405,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -6336,9 +6425,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "aws-lc-rs", "bytes", @@ -6372,9 +6461,9 @@ dependencies = [ [[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", ] @@ -6398,7 +6487,7 @@ dependencies = [ "proc-macro-utils", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6489,7 +6578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -6563,14 +6652,15 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.30.1" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1695748e3a735b34968c887ceea5a380b43545903868ae8f5b666593100f6b68" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", "ratatui-macros", + "ratatui-termina", "ratatui-termion", "ratatui-termwiz", "ratatui-widgets", @@ -6589,15 +6679,14 @@ dependencies = [ [[package]] name = "ratatui-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3603f354bba8c595fa47860e60142d7372b7210c27044c6a7d0e1a4336b44" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ "bitflags 2.13.0", "compact_str", "critical-section", "hashbrown 0.17.1", - "indoc", "itertools 0.14.0", "kasuari", "lru", @@ -6612,9 +6701,9 @@ dependencies = [ [[package]] name = "ratatui-crossterm" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2867bedcbd6a690ca4f8672a687b730ec07660c79844517b084311b529980c" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ "cfg-if", "crossterm 0.28.1", @@ -6625,8 +6714,8 @@ dependencies = [ [[package]] name = "ratatui-glamour" -version = "0.1.1" -source = "git+https://github.com/tinythings/ratatui-glamour.git#7990b0c46d0a9b57965f77bfbace56140ec09f0b" +version = "0.2.0" +source = "git+https://github.com/tinythings/ratatui-glamour.git#1545394cc3481bd5595f5a1f1e1eec3287ccb2a6" dependencies = [ "crossterm 0.28.1", "ratatui", @@ -6635,19 +6724,30 @@ dependencies = [ [[package]] name = "ratatui-macros" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fac59720679490d89d200df411faa249be728681adcabed3d047ae72c48f1d" +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-termion" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c16cc35a9d9114e0b2bb4b22018b96ae7f5fe60e2595dc73e622b4e78624835" +checksum = "87c732202fa5a71a9da0991013f0853e53f87048f45198e3a1ca3ee722accc2f" dependencies = [ "instability", "ratatui-core", @@ -6656,9 +6756,9 @@ dependencies = [ [[package]] name = "ratatui-termwiz" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "386b8ff8f74ed749509391c56d549761a2fcdb408e1f42e467286bcb7dac8967" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" dependencies = [ "ratatui-core", "termwiz", @@ -6666,9 +6766,9 @@ dependencies = [ [[package]] name = "ratatui-widgets" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef4f17dd7ac3abf5adc2b920a03c61eee4bfe6a88fa5191936895525371d79c" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ "bitflags 2.13.0", "hashbrown 0.17.1", @@ -6776,7 +6876,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6889,7 +6989,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.14", + "h2 0.4.15", "http 1.4.2", "http-body", "http-body-util", @@ -6955,7 +7055,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7100,7 +7200,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.117", + "syn 2.0.118", "walkdir", ] @@ -7205,9 +7305,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 = [ "aws-lc-rs", "log", @@ -7292,7 +7392,7 @@ dependencies = [ [[package]] name = "rustpython" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "dirs", "env_logger", @@ -7311,7 +7411,7 @@ dependencies = [ [[package]] name = "rustpython-codegen" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "bitflags 2.13.0", "indexmap 2.14.0", @@ -7334,11 +7434,11 @@ dependencies = [ [[package]] name = "rustpython-common" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "ascii", "bitflags 2.13.0", - "getrandom 0.4.2", + "getrandom 0.4.3", "itertools 0.14.0", "libc", "lock_api", @@ -7358,7 +7458,7 @@ dependencies = [ [[package]] name = "rustpython-compiler" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "rustpython-codegen", "rustpython-compiler-core", @@ -7372,7 +7472,7 @@ dependencies = [ [[package]] name = "rustpython-compiler-core" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "bitflags 2.13.0", "bitflagset", @@ -7380,6 +7480,7 @@ dependencies = [ "lz4_flex", "malachite-bigint", "num-complex", + "num-traits", "rustpython-ruff_source_file", "rustpython-wtf8", ] @@ -7387,24 +7488,24 @@ dependencies = [ [[package]] name = "rustpython-derive" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "rustpython-compiler", "rustpython-derive-impl", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "rustpython-derive-impl" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", "rustpython-compiler-core", "rustpython-doc", - "syn 2.0.117", + "syn 2.0.118", "syn-ext", "textwrap", ] @@ -7412,7 +7513,7 @@ dependencies = [ [[package]] name = "rustpython-doc" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "phf 0.13.1", ] @@ -7420,10 +7521,10 @@ dependencies = [ [[package]] name = "rustpython-host_env" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "bitflags 2.13.0", - "getrandom 0.4.2", + "getrandom 0.4.3", "junction", "libc", "libffi", @@ -7445,7 +7546,7 @@ dependencies = [ [[package]] name = "rustpython-literal" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "hexf-parse", "icu_properties", @@ -7458,7 +7559,7 @@ dependencies = [ [[package]] name = "rustpython-pylib" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "glob", "rustpython-compiler-core", @@ -7539,7 +7640,7 @@ dependencies = [ [[package]] name = "rustpython-sre_engine" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "bitflags 2.13.0", "icu_properties", @@ -7551,7 +7652,7 @@ dependencies = [ [[package]] name = "rustpython-stdlib" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "adler32", "ascii", @@ -7613,8 +7714,6 @@ dependencies = [ "shake", "socket2 0.6.4", "system-configuration", - "ucd", - "unic-ucd-age", "unicode_names2 2.0.0", "uuid", "webpki-roots", @@ -7629,7 +7728,7 @@ dependencies = [ [[package]] name = "rustpython-vm" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "ascii", "bitflags 2.13.0", @@ -7688,7 +7787,7 @@ dependencies = [ [[package]] name = "rustpython-wtf8" version = "0.5.0" -source = "git+https://github.com/RustPython/RustPython.git#be6017cbc8d1fa2b11fda5d6014d706ceac7b1a9" +source = "git+https://github.com/RustPython/RustPython.git#39a2fda23f2577f70bccac6f1bbb1723aff3c625" dependencies = [ "ascii", "bstr", @@ -7704,9 +7803,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustyline" -version = "18.0.0" +version = "18.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a990b25f351b25139ddc7f21ee3f6f56f86d6846b74ac8fad3a719a287cd4a0" +checksum = "53f6a737db68eb1a8ccff86b584b2fc13eca6a7bb6f78ebc7c529547e3ab9684" dependencies = [ "bitflags 2.13.0", "cfg-if", @@ -7810,7 +7909,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7912,7 +8011,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -7937,7 +8036,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8209,9 +8308,9 @@ dependencies = [ [[package]] name = "smawk" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" [[package]] name = "socket2" @@ -8244,7 +8343,7 @@ dependencies = [ "glob", "libc", "log", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "serde", "tokio", ] @@ -8368,7 +8467,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8380,7 +8479,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8402,9 +8501,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -8419,7 +8518,7 @@ checksum = "b126de4ef6c2a628a68609dd00733766c3b015894698a438ebdf374933fc31d1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8439,7 +8538,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8545,6 +8644,7 @@ dependencies = [ "colored", "daemonize", "ed25519-dalek", + "filescream 0.1.0 (git+https://github.com/tinythings/omnitrace.git)", "futures", "globset", "indexmap 2.14.0", @@ -8559,6 +8659,7 @@ dependencies = [ "libtelemetry", "libwebapi", "log", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git)", "once_cell", "rand 0.9.4", "rsa", @@ -8571,6 +8672,7 @@ dependencies = [ "tempfile", "tokio", "tokio-rustls", + "tokio-util", "uuid", ] @@ -8671,7 +8773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -8719,6 +8821,19 @@ dependencies = [ "winapi-util", ] +[[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 0.12.5", + "rustix 1.1.4", + "signal-hook", + "windows-sys 0.61.2", +] + [[package]] name = "terminal_size" version = "0.4.4" @@ -8843,7 +8958,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8854,7 +8969,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8868,12 +8983,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -8885,15 +8999,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" dependencies = [ "num-conv", "time-core", @@ -8949,7 +9063,7 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -8977,7 +9091,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9139,7 +9253,7 @@ dependencies = [ "base64", "bytes", "flate2", - "h2 0.4.14", + "h2 0.4.15", "http 1.4.2", "http-body", "http-body-util", @@ -9170,7 +9284,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9258,7 +9372,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9320,12 +9434,6 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" -[[package]] -name = "ucd" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4fa6e588762366f1eb4991ce59ad1b93651d0b769dfb4e4d1c5c4b943d1159" - [[package]] name = "ucd-trie" version = "0.1.7" @@ -9343,47 +9451,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-age" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8cfdfe71af46b871dc6af2c24fcd360e2f3392ee4c5111877f2947f311671c" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.9.0" @@ -9585,7 +9652,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -9615,12 +9682,12 @@ checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d" [[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.2", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -9696,9 +9763,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi-common" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12f0cd37e6a4513802e3918a9d891ce846d69a7594225b558eafc588c16e7fb" +checksum = "8a5eb67546a23b30761dc1e30325984e166e8f6961ae97e3e82d5a7570255838" dependencies = [ "anyhow", "bitflags 2.13.0", @@ -9721,27 +9788,18 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.123" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -9752,9 +9810,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.73" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -9762,9 +9820,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.123" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9772,22 +9830,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.123" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.123" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] @@ -9804,34 +9862,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser 0.244.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.251.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a879a421bd17c528b74721b2abf4c62e8f1d1889c2ba8c3c50d02deaf2ce395" +checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" dependencies = [ "leb128fmt", - "wasmparser 0.251.0", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", + "wasmparser 0.252.0", ] [[package]] @@ -9886,21 +9922,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.244.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.13.0", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - -[[package]] -name = "wasmparser" -version = "0.251.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437970b35b1a85cfde9c74b2398352d8d653f3bd8e3a3db0c063ea8f5b4b36ff" +checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c" dependencies = [ "bitflags 2.13.0", "indexmap 2.14.0", @@ -9935,9 +9959,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507d213104e83a7519d91af444a8b19c04281f2eef162d448ee7a894ac1c827d" +checksum = "4b4442dc12aa2473def8334f0e0f2b489be52c52507c938bbdc8be69ded4ded6" dependencies = [ "addr2line", "anyhow", @@ -9990,9 +10014,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9784b325c3b85562ac6d7f81c8348c42af1f137d98dd4fc6631860e4e68bb655" +checksum = "5d881c3d6205898a226cc487b117f23b9ed1c7da39952d65bd5eeb6745b3789c" dependencies = [ "anyhow", "cpp_demangle", @@ -10017,18 +10041,18 @@ dependencies = [ [[package]] name = "wasmtime-internal-asm-macros" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcaa9336cd5ba934ba734dfdfe35f5245c3c74b4e34f9af9e114fad892d81b3d" +checksum = "5ab1876bcfa51d6a05dea1c13933f53cbc1e316c783fddebc859f56a736eae07" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-internal-cache" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e297746bc001fd919f8f9bb3d28ed1a01fb1ff10a5ccd9720e649b5807bf2b68" +checksum = "2195a1521a5214a71b67ca2ba2d9317ae15917288ea9d8e5836142b921c9d155" dependencies = [ "anyhow", "base64", @@ -10046,30 +10070,30 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91aba228ec4f646cb9514be55538c842822cb96f2c306f75d664eea6b6f2e9eb" +checksum = "8ae1407944a0b13a8a77930b5b951aa7134beccecad7efac1ef9f03adb7d1a0f" dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", - "wit-parser 0.236.1", + "wit-parser", ] [[package]] name = "wasmtime-internal-component-util" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f4a2387b0aea544aa2317e295583be54fed852e0d1a31c0070984bfd6a507" +checksum = "646a53678ce6aaf6f097e18ca51f650f2841aea6d2bcd7b61931397b8b8f30db" [[package]] name = "wasmtime-internal-cranelift" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d938ae501275f44e7e5532ae4bb720542b429357014d33842e128c46fb9b54" +checksum = "ab3495aa8300e4ca6b53f81a53ce5eff6621fd5ff8378ef9ae552d1479d57371" dependencies = [ "anyhow", "cfg-if", @@ -10094,9 +10118,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1443b0914ff848ee7920e0f232368168e2819b739c54f3c352f0559b6164343" +checksum = "29b5e4023a6b167da157338f5f0f505945eb45e78f1cac2d4dcce0922457d7d4" dependencies = [ "anyhow", "cc", @@ -10110,9 +10134,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "861d6f2a1652e95ca10b02552934b3bd460d7416b285fe10d7ca8c0a2b90dc3e" +checksum = "9da71e2d573e3cc6f753a3b7bff98f425ca060c0e8071cc55c3d867a9edf3ecc" dependencies = [ "cc", "object", @@ -10122,9 +10146,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1caeb3140c46319fecf09d93dc38a373eb535fd478e401a9fb2ac2da30fe5f6" +checksum = "627d8f57909a4f9bb1dbe57a96229a54b89d5995353d0b321f3cb9a1a118977a" dependencies = [ "anyhow", "cfg-if", @@ -10134,24 +10158,24 @@ dependencies = [ [[package]] name = "wasmtime-internal-math" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c631615929951a4076aae64da7d6cad88668d292f19672606392c24ae9c5a00" +checksum = "45b99315585a8a27125dd9b0150edb115d6f6ff0baae453c21d30822aab77f00" dependencies = [ "libm", ] [[package]] name = "wasmtime-internal-slab" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b28104d57b5bdb5d8facb3a8418463ec6c2cb40bb4adf9833b727ebf6a254eb" +checksum = "8eaee97281dd3fe47ec3d46c16fb9fe2dd32f37d0523c2d5c484f11b348734e4" [[package]] name = "wasmtime-internal-unwinder" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd89f2db7377869aeaf66b71f56def8df54b9482e4f4e5533ccec2505f5c691" +checksum = "d0c005f82c48492b6b44fa19ee5205bd933c4f8baca41e314eca8331dd3c4fd9" dependencies = [ "anyhow", "cfg-if", @@ -10162,20 +10186,20 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9cdb9c2e3965ee15629d067203cb800e9822664d04335dadc6fe1788d4fc335" +checksum = "7b73639a9c0c0e33a2ef942ca99b6772b48393be92bebbd0767c607e5b0a68e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "wasmtime-internal-winch" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f693c8db710f20b927bcee025acd345acf599d055b63f122613d52f5553a5f" +checksum = "392ca021d084c7426616ef77e1284315555f11bcbb34f416d74b0732db622811" dependencies = [ "anyhow", "cranelift-codegen", @@ -10190,22 +10214,22 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c97d4e849494d290e05573298bd372e12be86b2074502dc5e02f4ef7628002" +checksum = "7fd4703351476262d715b72431e80d10289908e3494050071d6521267f522d97" dependencies = [ "anyhow", "bitflags 2.13.0", "heck", "indexmap 2.14.0", - "wit-parser 0.236.1", + "wit-parser", ] [[package]] name = "wasmtime-wasi" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eabc75a6afeac11870ee5402268ec0f61fc394728d6dcbe5091a57dc6eb5a57" +checksum = "21921b6e8e8ed876a288cb3b0b3aee68809fed8182ce26c9977ffc4af4cb77f6" dependencies = [ "anyhow", "async-trait", @@ -10234,9 +10258,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi-io" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367b77d382241c7f9b3cde3c8cfc1c0d800f04d665622779a724b71d0a2a2028" +checksum = "2cceb2d110d8de61e7b0e8c501b838d3c6403a14cdca8cb612ff1228db537c6d" dependencies = [ "anyhow", "async-trait", @@ -10256,31 +10280,31 @@ dependencies = [ [[package]] name = "wast" -version = "251.0.0" +version = "252.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc7467dda0a96142eb2c980329dfb62480b1e1d3622fdeb1a44e2bca6ceed74" +checksum = "942a3449d6a593fccc111a6241c8df52bda168af30e40bf9580d4394d7374c65" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.2", - "wasm-encoder 0.251.0", + "wasm-encoder 0.252.0", ] [[package]] name = "wat" -version = "1.251.0" +version = "1.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b1086c9e85b95bd6a229a928bc6c6d0662e42af0250c88d067b418831ea4d4" +checksum = "c72a4ba7088f7bac94cf516e49882bdf97068904a563768cf249efc839ec42cb" dependencies = [ - "wast 251.0.0", + "wast 252.0.0", ] [[package]] name = "web-sys" -version = "0.3.100" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -10298,18 +10322,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" dependencies = [ "rustls-pki-types", ] @@ -10390,9 +10414,9 @@ dependencies = [ [[package]] name = "which" -version = "8.0.3" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c789537cf2f7f55be8e6192f92e464174ee55f91af622777f7f1ceb0dbccd03e" +checksum = "48d7cd18d4acb58fb3cdfe9ea54e6cd96a4e7d4cc45c56338b236e82dad47248" dependencies = [ "libc", ] @@ -10415,9 +10439,9 @@ checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "wiggle" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aed7ef247a05956b0a25e7905fdb709ae89e506547af42897e40301b0658d07" +checksum = "55a0751406b641ff50ef42d4a1ca843a03040c488c0c27f92093633447464013" dependencies = [ "anyhow", "async-trait", @@ -10430,27 +10454,27 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d5550c5f49730d0a8babd089771ff7412e598cf8f7bbe3b647b8e2147a11b" +checksum = "ab62083fdcecdd0cac61b8c46e7de4f2629ebe8699fd9ce790d922cc89d50f5f" dependencies = [ "anyhow", "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "witx", ] [[package]] name = "wiggle-macro" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602175405f0c04fd47439ad6afc5e151c4864883259254d3676562bfb00a7ce8" +checksum = "756b7a4a7f57ee2f53e9ef3501ed0faacda4b8dcb169a921cddc8bc09ebd199e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wiggle-generate", ] @@ -10487,9 +10511,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "36.0.10" +version = "36.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4332c8656af179fb8fc3ae5114c738c29399ee97b638d431725201c17f99294e" +checksum = "61ec880b20caaa72245944b54cfb22aca111f8c805e12a7542b40d66921e5323" dependencies = [ "anyhow", "cranelift-assembler-x64", @@ -10571,7 +10595,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10582,7 +10606,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10593,7 +10617,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10604,7 +10628,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10615,7 +10639,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10626,7 +10650,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -10895,82 +10919,12 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser 0.244.0", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.13.0", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.244.0", - "wasm-metadata", - "wasmparser 0.244.0", - "wit-parser 0.244.0", -] - [[package]] name = "wit-parser" version = "0.236.1" @@ -10989,24 +10943,6 @@ dependencies = [ "wasmparser 0.236.1", ] -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.244.0", -] - [[package]] name = "witx" version = "0.9.1" @@ -11102,7 +11038,7 @@ dependencies = [ "async-trait", "bitflags 2.13.0", "log", - "omnitrace-core", + "omnitrace-core 0.1.0 (git+https://github.com/tinythings/omnitrace.git?branch=master)", "serde", "serde_json", "tokio", @@ -11157,7 +11093,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] @@ -11205,7 +11141,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "zbus_names", "zvariant", "zvariant_utils", @@ -11239,7 +11175,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -11259,28 +11195,28 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -11315,7 +11251,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -11334,9 +11270,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" [[package]] name = "zmij" @@ -11407,7 +11343,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "zvariant_utils", ] @@ -11420,6 +11356,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.117", + "syn 2.0.118", "winnow 1.0.3", ] diff --git a/Makefile b/Makefile index 3913d30c..c59bd553 100644 --- a/Makefile +++ b/Makefile @@ -193,7 +193,7 @@ musl-aarch64-modules-dist: $(call stage_modules_dist_from,release,aarch64-unknown-linux-musl,$(MUSL_MODULE_PACKAGE_SPECS),$(call musl_modules_dist_dir,aarch64,release)) all-dev: - @scripts/maybe-mxrun.sh all-dev || $(MAKE) _all_dev + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _all_dev; else scripts/maybe-mxrun.sh all-dev || $(MAKE) _all_dev; fi _all_dev: cargo build -v --workspace $(PLATFORM_WORKSPACE_EXCLUDES) @@ -202,7 +202,7 @@ _all_dev: $(call write_mxrun_manifest,all-dev,) all: - @scripts/maybe-mxrun.sh all || $(MAKE) _all + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _all; else scripts/maybe-mxrun.sh all || $(MAKE) _all; fi _all: cargo build --release --workspace $(PLATFORM_WORKSPACE_EXCLUDES) @@ -211,7 +211,7 @@ _all: $(call write_mxrun_manifest,all,) dev: - @scripts/maybe-mxrun.sh dev || $(MAKE) _dev + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _dev; else scripts/maybe-mxrun.sh dev || $(MAKE) _dev; fi _dev: cargo build -v --workspace $(CORE_EXCLUDES) @@ -220,7 +220,7 @@ _dev: $(call write_mxrun_manifest,dev,) build: - @scripts/maybe-mxrun.sh release || $(MAKE) _build + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _build; else scripts/maybe-mxrun.sh release || $(MAKE) _build; fi _build: cargo build --release --workspace $(CORE_EXCLUDES) @@ -229,7 +229,7 @@ _build: $(call write_mxrun_manifest,release,) modules-dev: - @scripts/maybe-mxrun.sh modules-dev || $(MAKE) _modules_dev + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _modules_dev; else scripts/maybe-mxrun.sh modules-dev || $(MAKE) _modules_dev; fi _modules_dev: @CARGO_BUILD_JOBS=$(MODULE_BUILD_JOBS) cargo build -v $(foreach pkg,$(MODULE_PACKAGE_SPECS),-p $(pkg)) @@ -237,7 +237,7 @@ _modules_dev: $(call write_mxrun_manifest,modules-dev,) modules: - @scripts/maybe-mxrun.sh modules || $(MAKE) _modules + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _modules; else scripts/maybe-mxrun.sh modules || $(MAKE) _modules; fi _modules: @CARGO_BUILD_JOBS=$(MODULE_BUILD_JOBS) cargo build --release $(foreach pkg,$(MODULE_PACKAGE_SPECS),-p $(pkg)) @@ -245,7 +245,7 @@ _modules: $(call write_mxrun_manifest,modules,) modules-dist-dev: - @scripts/maybe-mxrun.sh modules-dist-dev || $(MAKE) _modules_dist_dev + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _modules_dist_dev; else scripts/maybe-mxrun.sh modules-dist-dev || $(MAKE) _modules_dist_dev; fi _modules_dist_dev: @CARGO_BUILD_JOBS=$(MODULE_BUILD_JOBS) cargo build --release $(foreach pkg,$(MODULE_PACKAGE_SPECS),-p $(pkg)) @@ -254,31 +254,31 @@ _modules_dist_dev: $(call write_mxrun_manifest,modules-dist-dev,with-dist) test: setup - @scripts/maybe-mxrun.sh test || $(MAKE) _test + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _test; else scripts/maybe-mxrun.sh test || $(MAKE) _test; fi _test: @CARGO_BUILD_JOBS=$(TEST_BUILD_JOBS) cargo nextest run --no-fail-fast --workspace $(PLATFORM_WORKSPACE_EXCLUDES) --test-threads $(TEST_RUN_THREADS) test-core: setup - @scripts/maybe-mxrun.sh test-core || $(MAKE) _test_core + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _test_core; else scripts/maybe-mxrun.sh test-core || $(MAKE) _test_core; fi _test_core: @CARGO_BUILD_JOBS=$(TEST_BUILD_JOBS) cargo nextest run --no-fail-fast $(foreach pkg,$(CORE_PACKAGE_SPECS),-p $(pkg)) --lib --bins --test-threads $(TEST_RUN_THREADS) test-modules: setup - @scripts/maybe-mxrun.sh test-modules || $(MAKE) _test_modules + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _test_modules; else scripts/maybe-mxrun.sh test-modules || $(MAKE) _test_modules; fi _test_modules: @CARGO_BUILD_JOBS=$(TEST_BUILD_JOBS) cargo nextest run --no-fail-fast $(foreach pkg,$(MODULE_PACKAGE_SPECS),-p $(pkg)) --bins --test-threads $(TEST_RUN_THREADS) test-sensors: setup - @scripts/maybe-mxrun.sh test-sensors || $(MAKE) _test_sensors + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _test_sensors; else scripts/maybe-mxrun.sh test-sensors || $(MAKE) _test_sensors; fi _test_sensors: @CARGO_BUILD_JOBS=$(TEST_BUILD_JOBS) cargo nextest run --no-fail-fast $(foreach pkg,$(SENSOR_PACKAGE_SPECS),-p $(pkg)) --lib --bins --test-threads $(TEST_RUN_THREADS) test-integration: setup - @scripts/maybe-mxrun.sh test-integration || $(MAKE) _test_integration + @if [ -n "$$SSH_CONNECTION" ]; then $(MAKE) _test_integration; else scripts/maybe-mxrun.sh test-integration || $(MAKE) _test_integration; fi _test_integration: @CARGO_BUILD_JOBS=$(TEST_BUILD_JOBS) cargo nextest run --no-fail-fast $(INTEGRATION_TEST_TARGETS) --test-threads $(TEST_RUN_THREADS) diff --git a/libmodpak/src/lib.rs b/libmodpak/src/lib.rs index 11219738..7144e25f 100644 --- a/libmodpak/src/lib.rs +++ b/libmodpak/src/lib.rs @@ -1129,10 +1129,12 @@ impl SysInspectModPak { self.remove_profile_entry(name) } - /// Add module or library selectors to the named profile. - pub fn add_profile_matches(&self, name: &str, matches: Vec, library: bool) -> Result<(), SysinspectError> { + /// Add module, model, or library selectors to the named profile. + pub fn add_profile_matches(&self, name: &str, matches: Vec, library: bool, model: bool) -> Result<(), SysinspectError> { let mut profile = self.get_profile(name)?; - if library { + if model { + profile.add_models(matches); + } else if library { profile.add_libraries(matches); } else { profile.add_modules(matches); @@ -1140,10 +1142,12 @@ impl SysInspectModPak { self.set_profile(name, &profile) } - /// Remove module or library selectors from the named profile. - pub fn remove_profile_matches(&self, name: &str, matches: Vec, library: bool) -> Result<(), SysinspectError> { + /// Remove module, model, or library selectors from the named profile. + pub fn remove_profile_matches(&self, name: &str, matches: Vec, library: bool, model: bool) -> Result<(), SysinspectError> { let mut profile = self.get_profile(name)?; - if library { + if model { + profile.remove_models(matches); + } else if library { profile.remove_libraries(matches); } else { profile.remove_modules(matches); @@ -1151,12 +1155,19 @@ impl SysInspectModPak { self.set_profile(name, &profile) } - /// List module or library selectors for profiles matching the optional glob expression. - pub fn list_profile_matches(&self, expr: Option<&str>, library: bool) -> Result, SysinspectError> { + /// List module, model, or library selectors for profiles matching the optional glob expression. + pub fn list_profile_matches(&self, expr: Option<&str>, library: bool, model: bool) -> Result, SysinspectError> { let mut out = Vec::new(); for profile in self.list_profiles(expr)? { let data = self.get_profile(&profile)?; - for entry in if library { data.libraries() } else { data.modules() } { + let entries: &[String] = if model { + data.models() + } else if library { + data.libraries() + } else { + data.modules() + }; + for entry in entries { out.push(format!("{profile}: {entry}")); } } diff --git a/libmodpak/src/lib_ut.rs b/libmodpak/src/lib_ut.rs index cc623e8e..eaf9b087 100644 --- a/libmodpak/src/lib_ut.rs +++ b/libmodpak/src/lib_ut.rs @@ -356,18 +356,23 @@ mod tests { let repo = SysInspectModPak::new(root.path().join("repo")).expect("repo should be created"); repo.new_profile("toto").expect("profile should be created"); - repo.add_profile_matches("toto", vec!["runtime.lua".to_string(), "net.*".to_string()], false).expect("module selectors should be added"); - repo.add_profile_matches("toto", vec!["runtime/lua/*.lua".to_string()], true).expect("library selectors should be added"); + repo.add_profile_matches("toto", vec!["runtime.lua".to_string(), "net.*".to_string()], false, false) + .expect("module selectors should be added"); + repo.add_profile_matches("toto", vec!["runtime/lua/*.lua".to_string()], true, false).expect("library selectors should be added"); assert_eq!(repo.list_profiles(None).expect("profiles should list"), vec!["toto".to_string()]); - assert!(repo.list_profile_matches(Some("toto"), false).expect("profile modules should list").contains(&"toto: runtime.lua".to_string())); - assert!(repo.list_profile_matches(Some("toto"), false).expect("profile modules should list").contains(&"toto: net.*".to_string())); assert!( - repo.list_profile_matches(Some("toto"), true).expect("profile libraries should list").contains(&"toto: runtime/lua/*.lua".to_string()) + repo.list_profile_matches(Some("toto"), false, false).expect("profile modules should list").contains(&"toto: runtime.lua".to_string()) + ); + assert!(repo.list_profile_matches(Some("toto"), false, false).expect("profile modules should list").contains(&"toto: net.*".to_string())); + assert!( + repo.list_profile_matches(Some("toto"), true, false) + .expect("profile libraries should list") + .contains(&"toto: runtime/lua/*.lua".to_string()) ); - repo.remove_profile_matches("toto", vec!["net.*".to_string()], false).expect("module selector should be removed"); - assert!(!repo.list_profile_matches(Some("toto"), false).expect("profile modules should list").contains(&"toto: net.*".to_string())); + repo.remove_profile_matches("toto", vec!["net.*".to_string()], false, false).expect("module selector should be removed"); + assert!(!repo.list_profile_matches(Some("toto"), false, false).expect("profile modules should list").contains(&"toto: net.*".to_string())); repo.delete_profile("toto").expect("profile should be deleted"); assert!(repo.list_profiles(None).expect("profiles should list").is_empty()); @@ -405,7 +410,7 @@ mod tests { fs::write(root.path().join("profiles.index"), "profiles:\n Toto:\n file: totobullshit.profile\n checksum: deadbeef\n") .expect("profiles index should be written"); - repo.add_profile_matches("Toto", vec!["net.*".to_string()], false).expect("profile should be updated"); + repo.add_profile_matches("Toto", vec!["net.*".to_string()], false, false).expect("profile should be updated"); let idx = repo.get_profiles_index().expect("profiles index should load"); let profile = repo.get_profile("Toto").expect("profile should load"); @@ -446,8 +451,8 @@ mod tests { write_module(&mut repo, "linux", "x86_64", "runtime.lua", "runtime/lua"); write_module(&mut repo, "netbsd", "noarch", "runtime.lua", "runtime/lua"); repo.new_profile("toto").expect("profile should be created"); - repo.add_profile_matches("toto", vec!["runtime.lua".to_string()], false).expect("module selector should be added"); - repo.add_profile_matches("toto", vec!["runtime/lua/*.lua".to_string()], true).expect("library selector should be added"); + repo.add_profile_matches("toto", vec!["runtime.lua".to_string()], false, false).expect("module selector should be added"); + repo.add_profile_matches("toto", vec!["runtime/lua/*.lua".to_string()], true, false).expect("library selector should be added"); let rendered = repo.show_profile("toto").expect("profile should render"); let module_pos = rendered.find("runtime.lua").expect("module row should exist"); diff --git a/libmodpak/src/mpk.rs b/libmodpak/src/mpk.rs index e21d541c..0b7f75c7 100644 --- a/libmodpak/src/mpk.rs +++ b/libmodpak/src/mpk.rs @@ -176,6 +176,8 @@ impl ModPakProfileRef { pub struct ModPakProfile { name: String, #[serde(default)] + models: Vec, + #[serde(default)] modules: Vec, #[serde(default)] libraries: Vec, @@ -201,6 +203,11 @@ impl ModPakProfile { pub fn libraries(&self) -> &[String] { &self.libraries } + + /// Return the model identifier list. + pub fn models(&self) -> &[String] { + &self.models + } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] @@ -340,6 +347,20 @@ impl ModPakProfile { self.libraries.retain(|library| !libraries.contains(library)); } + /// Add model identifiers, keeping insertion order and skipping duplicates. + pub fn add_models(&mut self, models: Vec) { + for model in models { + if !self.models.contains(&model) { + self.models.push(model); + } + } + } + + /// Remove matching model identifiers. + pub fn remove_models(&mut self, models: Vec) { + self.models.retain(|model| !models.contains(model)); + } + /// Serialize one profile file to YAML. pub fn to_yaml(&self) -> Result { Ok(serde_yaml::to_string(self)?) diff --git a/libmodpak/tests/profile_sync.rs b/libmodpak/tests/profile_sync.rs index 9db97a27..9948d2a8 100644 --- a/libmodpak/tests/profile_sync.rs +++ b/libmodpak/tests/profile_sync.rs @@ -91,10 +91,10 @@ async fn narrow_profile_syncs_only_allowed_artifacts_and_removes_old_ones() { set_script_modules(&master.path().join("data/repo"), &["alpha.demo", "beta.demo"]); repo.new_profile("Alpha").expect("Alpha should be created"); repo.new_profile("Beta").expect("Beta should be created"); - repo.add_profile_matches("Alpha", vec!["alpha.demo".to_string()], false).expect("Alpha module selector should be added"); - repo.add_profile_matches("Alpha", vec!["runtime/lua/alpha.lua".to_string()], true).expect("Alpha library selector should be added"); - repo.add_profile_matches("Beta", vec!["beta.demo".to_string()], false).expect("Beta module selector should be added"); - repo.add_profile_matches("Beta", vec!["runtime/lua/beta.lua".to_string()], true).expect("Beta library selector should be added"); + repo.add_profile_matches("Alpha", vec!["alpha.demo".to_string()], false, false).expect("Alpha module selector should be added"); + repo.add_profile_matches("Alpha", vec!["runtime/lua/alpha.lua".to_string()], true, false).expect("Alpha library selector should be added"); + repo.add_profile_matches("Beta", vec!["beta.demo".to_string()], false, false).expect("Beta module selector should be added"); + repo.add_profile_matches("Beta", vec!["runtime/lua/beta.lua".to_string()], true, false).expect("Beta library selector should be added"); let (port, server) = start_fileserver(master.path().join("data")).await; let minion = tempfile::tempdir().expect("minion tempdir should be created"); @@ -136,8 +136,8 @@ async fn overlapping_multi_profile_sync_merges_by_union_and_dedup() { set_script_modules(&master.path().join("data/repo"), &["alpha.demo", "beta.demo", "gamma.demo"]); repo.new_profile("One").expect("One should be created"); repo.new_profile("Two").expect("Two should be created"); - repo.add_profile_matches("One", vec!["alpha.demo".to_string(), "beta.demo".to_string()], false).expect("One selectors should be added"); - repo.add_profile_matches("Two", vec!["beta.demo".to_string(), "gamma.demo".to_string()], false).expect("Two selectors should be added"); + repo.add_profile_matches("One", vec!["alpha.demo".to_string(), "beta.demo".to_string()], false, false).expect("One selectors should be added"); + repo.add_profile_matches("Two", vec!["beta.demo".to_string(), "gamma.demo".to_string()], false, false).expect("Two selectors should be added"); let (port, server) = start_fileserver(master.path().join("data")).await; let minion = tempfile::tempdir().expect("minion tempdir should be created"); @@ -165,7 +165,7 @@ async fn sync_fails_if_effective_profiles_are_missing_from_profiles_index() { add_script_module(&master.path().join("data/repo"), "alpha.demo", "# alpha"); set_script_modules(&master.path().join("data/repo"), &["alpha.demo"]); repo.new_profile("Existing").expect("Existing should be created"); - repo.add_profile_matches("Existing", vec!["alpha.demo".to_string()], false).expect("Existing selector should be added"); + repo.add_profile_matches("Existing", vec!["alpha.demo".to_string()], false, false).expect("Existing selector should be added"); let (port, server) = start_fileserver(master.path().join("data")).await; let minion = tempfile::tempdir().expect("minion tempdir should be created"); @@ -212,7 +212,7 @@ async fn sync_allows_implicit_default_when_profiles_index_has_named_profiles() { add_script_module(&master.path().join("data/repo"), "alpha.demo", "# alpha"); set_script_modules(&master.path().join("data/repo"), &["alpha.demo"]); repo.new_profile("Existing").expect("Existing should be created"); - repo.add_profile_matches("Existing", vec!["alpha.demo".to_string()], false).expect("Existing selector should be added"); + repo.add_profile_matches("Existing", vec!["alpha.demo".to_string()], false, false).expect("Existing selector should be added"); let (port, server) = start_fileserver(master.path().join("data")).await; let minion = tempfile::tempdir().expect("minion tempdir should be created"); @@ -258,7 +258,7 @@ async fn sync_fails_if_downloaded_profile_checksum_does_not_match_index() { add_script_module(&master.path().join("data/repo"), "alpha.demo", "# alpha"); set_script_modules(&master.path().join("data/repo"), &["alpha.demo"]); repo.new_profile("Broken").expect("Broken should be created"); - repo.add_profile_matches("Broken", vec!["alpha.demo".to_string()], false).expect("Broken selector should be added"); + repo.add_profile_matches("Broken", vec!["alpha.demo".to_string()], false, false).expect("Broken selector should be added"); fs::write(master.path().join("data/profiles.index"), "profiles:\n Broken:\n file: broken.profile\n checksum: deadbeef\n") .expect("profiles index should be overwritten"); diff --git a/libsysinspect/src/console/mod.rs b/libsysinspect/src/console/mod.rs index 4d8cf7d7..f59d38f4 100644 --- a/libsysinspect/src/console/mod.rs +++ b/libsysinspect/src/console/mod.rs @@ -485,6 +485,9 @@ pub struct ConsoleModelRow { /// Public direct action entrypoints exported through `interface.actions`. #[serde(default)] pub public_actions: Vec, + /// Deduplicated module ids referenced by the model's actions. + #[serde(default)] + pub modules: Vec, /// Declared state names across all actions. #[serde(default)] pub states: Vec, diff --git a/libsysinspect/src/context/mod.rs b/libsysinspect/src/context/mod.rs index ddade778..e2d4517a 100644 --- a/libsysinspect/src/context/mod.rs +++ b/libsysinspect/src/context/mod.rs @@ -91,6 +91,8 @@ pub struct ProfileConsoleRequest { #[serde(default)] library: bool, #[serde(default)] + model: bool, + #[serde(default)] profiles: Vec, } @@ -120,6 +122,11 @@ impl ProfileConsoleRequest { self.library } + /// Return whether the request targets model identifiers instead of module selectors. + pub fn model(&self) -> bool { + self.model + } + /// Return the profile names carried by tag or untag requests. pub fn profiles(&self) -> &[String] { &self.profiles diff --git a/libsysinspect/src/mdescr/browse_types.rs b/libsysinspect/src/mdescr/browse_types.rs index 8bad1d1d..da218123 100644 --- a/libsysinspect/src/mdescr/browse_types.rs +++ b/libsysinspect/src/mdescr/browse_types.rs @@ -73,6 +73,7 @@ pub struct BrowsedModel { pub public_entrypoints: Vec, pub public_actions: Vec, pub actions: Vec, + pub modules: Vec, /// Deduplicated list of all declared action state keys. pub states: Vec, pub diagnostics: Vec, diff --git a/libsysinspect/src/mdescr/browser.rs b/libsysinspect/src/mdescr/browser.rs index 6b5ac8f8..8c11cd89 100644 --- a/libsysinspect/src/mdescr/browser.rs +++ b/libsysinspect/src/mdescr/browser.rs @@ -462,6 +462,7 @@ impl ModelBrowser { let (actions, a_diags) = self.actions(); diagnostics.extend(a_diags); + let modules = action_modules(&actions); let (interface_checkbooks, interface_entities, interface_actions, interface_diags) = self.interface_lists(); diagnostics.extend(interface_diags); @@ -538,6 +539,7 @@ impl ModelBrowser { public_entrypoints, public_actions, actions, + modules, states, diagnostics, }) @@ -604,6 +606,13 @@ impl ModelBrowser { } } +fn action_modules(actions: &[BrowsedAction]) -> Vec { + let mut modules: Vec = actions.iter().map(|action| action.module.clone()).filter(|name| !name.trim().is_empty()).collect(); + modules.sort(); + modules.dedup(); + modules +} + /// Convert a serde_yaml Value to a human-readable display string. fn value_to_display(v: &serde_yaml::Value) -> String { match v { diff --git a/libsysinspect/src/mdescr/browser_ut.rs b/libsysinspect/src/mdescr/browser_ut.rs index 44e0c49f..17cf9176 100644 --- a/libsysinspect/src/mdescr/browser_ut.rs +++ b/libsysinspect/src/mdescr/browser_ut.rs @@ -1549,3 +1549,50 @@ actions: assert_eq!(first.1, "Optional host override"); assert!(!first.2, "explicit [false, ...] should be optional"); } + +#[test] +fn summarize_collects_deduped_modules() { + let td = tempfile::TempDir::new().unwrap(); + write_model( + &td, + r#" +name: Module Summary +version: "0.1" +description: Summary contains modules. +maintainer: tester +entities: + e1: + descr: Test +actions: + a1: + description: First action. + module: module.one + bind: [e1] + state: + $: + args: + cmd: echo + a2: + description: Second action. + module: module.one + bind: [e1] + state: + $: + args: + cmd: echo + a3: + description: Third action. + module: foo.bar + bind: [e1] + state: + $: + args: + cmd: echo +"#, + ); + + let browser = ModelBrowser::load(Arc::new(MinionConfig::default()), td.path()).expect("load"); + let summary = browser.summarize().expect("summarize"); + + assert_eq!(summary.modules, vec!["foo.bar".to_string(), "module.one".to_string()]); +} diff --git a/libsysproto/src/rqtypes.rs b/libsysproto/src/rqtypes.rs index c57f4ef3..d622e2ae 100644 --- a/libsysproto/src/rqtypes.rs +++ b/libsysproto/src/rqtypes.rs @@ -134,6 +134,14 @@ pub enum RequestType { /// Master→Minion: model cycle acknowledged. #[serde(rename = "cack")] CycleAck, + + /// Master→Minion: model configuration changed on the master. + #[serde(rename = "mupd")] + ModelUpdated, + + /// Master→Minion: model configuration removed from the master. + #[serde(rename = "mrm")] + ModelRemoved, } /// Classifies an outbound minion message so the transport layer can decide diff --git a/modules/runtime/py3-runtime/src/prt.rs b/modules/runtime/py3-runtime/src/prt.rs index a2b05943..7b46d868 100644 --- a/modules/runtime/py3-runtime/src/prt.rs +++ b/modules/runtime/py3-runtime/src/prt.rs @@ -446,7 +446,7 @@ class _SysinspectHost: host = _SysinspectHost() "#; match vm.run_code_obj( - match vm.compile(prelude, Exec, "".to_string()) { + match vm.compile(prelude, Exec, "") { Ok(code) => code, Err(err) => return Err(Py3RuntimeError::Vm(format!("Unable to compile Python prelude: {err}"))), }, @@ -471,7 +471,7 @@ host = _SysinspectHost() /// * `Result<()>` - Result of the operation fn exec_scope_code(&self, vm: &VirtualMachine, scope: rustpython_vm::scope::Scope, code: &str, filename: &str) -> Result<()> { match vm.run_code_obj( - match vm.compile(code, Exec, filename.to_string()) { + match vm.compile(code, Exec, filename) { Ok(code) => code, Err(err) => return Err(Py3RuntimeError::Vm(format!("Unable to compile Python source {filename}: {err}"))), }, @@ -519,7 +519,7 @@ host = _SysinspectHost() } if let Err(err) = vm.run_code_obj( - match vm.compile(code, Exec, filename.to_string()) { + match vm.compile(code, Exec, filename) { Ok(obj) => obj, Err(err) => return Err(Py3RuntimeError::Vm(format!("Unable to compile source code for {modname}: {err}"))), }, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 0b951b0a..77bcff88 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -57,6 +57,8 @@ mod online; mod palette; mod platforms; mod profiles; +#[cfg(test)] +mod profiles_ut; mod rawlogs; mod repomanager; mod setup; @@ -151,6 +153,8 @@ type ClusterUpgradeTaskResult = Result<(usize, usize, usize, usize, usize, Vec), String>; +type SetupTaskResult = Result; + #[derive(Debug)] pub struct ClusterStartProgressState { pub visible: bool, @@ -297,6 +301,8 @@ pub struct SysInspectUX { pub registration_form: minreg::RegistrationForm, pub registration_progress: Arc>, pub registration_task: Option>, + pub setup_progress: Arc>, + pub setup_task: Option>, pub delete_progress: DeleteProgressState, pub delete_task: Option>>, pub delete_success_message: String, @@ -424,6 +430,8 @@ impl Default for SysInspectUX { registration_form: minreg::RegistrationForm::default(), registration_progress: Arc::new(std::sync::Mutex::new(minreg::RegistrationProgress::placeholder())), registration_task: None, + setup_progress: Arc::new(std::sync::Mutex::new(setup::SetupProgress::hidden())), + setup_task: None, delete_progress: DeleteProgressState::default(), delete_task: None, delete_success_message: String::new(), @@ -510,61 +518,52 @@ impl SysInspectUX { self.setup_wizard.ok_pressed = false; self.setup_wizard.focus = setup::SetupFocus::SysMasterPath; } else { - match self.setup_wizard.write_config() { - Ok(config_path) => { - self.setup_wizard.ok_pressed = false; - self.setup_wizard.visible = false; - - // Spawn master in daemon mode (no TUI output pollution) - let master_bin = if self.setup_wizard.installation_mode == setup::InstallationMode::Custom { - std::path::PathBuf::from(self.setup_wizard.custom_destination.value()).join("bin/sysmaster") - } else { - std::path::PathBuf::from(self.setup_wizard.sysmaster_path.value()) - }; - std::process::Command::new(&master_bin) - .arg("--daemon") - .arg("-c") - .arg(config_path.to_string_lossy().as_ref()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .spawn() - .map(|c| { - std::thread::spawn(move || { - c.wait_with_output().ok(); - }); - }) - .ok(); - - // Wait for master to come up - for _ in 0..10 { - std::thread::sleep(std::time::Duration::from_millis(500)); - if self.try_reconnect_silent().is_ok() { - return self.run_normal_loop(term); - } - } - - // Not yet up — stay in setup loop, will auto-reconnect - self.info_alert_visible = true; - self.info_alert_title = "Setup Complete".to_string(); - self.info_alert_styled = None; - self.info_alert_message = format!( - "Config written to:\n{}\n\nMaster is starting in the background.\nThe UI will reconnect automatically.", - config_path.display(), - ); - } - Err(e) => { - self.error_alert_visible = true; - self.error_alert_message = e; - self.setup_wizard.ok_pressed = false; - } + self.setup_wizard.ok_pressed = false; + self.setup_wizard.visible = false; + let request = self.setup_wizard.to_request(); + let progress = self.setup_progress.clone(); + if let Ok(mut state) = progress.lock() { + *state = setup::SetupProgress::new(); + } + self.setup_task = Some(tokio::task::spawn_blocking(move || request.run_with_progress(progress))); + } + } + if self.setup_task.as_ref().is_some_and(|task| task.is_finished()) + && let Some(task) = self.setup_task.take() + { + let result = tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(task)); + if let Ok(mut progress) = self.setup_progress.lock() { + *progress = setup::SetupProgress::hidden(); + } + match result { + Ok(Ok(done)) => { + self.pending_exit = true; + self.info_alert_visible = true; + self.info_alert_title = "Master Installation Complete".to_string(); + self.info_alert_styled = Some(done.info_styled); + self.info_alert_message = done.info_message; + } + Ok(Err(err)) => { + self.error_alert_visible = true; + self.error_alert_message = err; + self.setup_wizard.visible = true; + } + Err(err) => { + self.error_alert_visible = true; + self.error_alert_message = format!("Setup task failed: {err}"); + self.setup_wizard.visible = true; } } } - if !self.error_alert_visible && !self.setup_wizard.visible && self.try_reconnect_silent().is_ok() { + if self.setup_task.is_none() && !self.error_alert_visible && !self.setup_wizard.visible && self.try_reconnect_silent().is_ok() { return self.run_normal_loop(term); } // Periodic silent reconnect in setup mode - if !self.setup_wizard.ok_pressed && self.last_reconnect_attempt.elapsed() >= Duration::from_secs(5) && self.evtipc.is_some() { + if self.setup_task.is_none() + && !self.setup_wizard.ok_pressed + && self.last_reconnect_attempt.elapsed() >= Duration::from_secs(5) + && self.evtipc.is_some() + { self.last_reconnect_attempt = Instant::now(); if self.try_reconnect_silent().is_ok() { return self.run_normal_loop(term); @@ -702,7 +701,8 @@ impl SysInspectUX { } fn on_events_setup(&mut self) -> io::Result<()> { - if event::poll(Duration::from_secs(1))? + let poll_dur = if self.setup_progress.lock().unwrap().visible { Duration::from_millis(50) } else { Duration::from_secs(1) }; + if event::poll(poll_dur)? && let Event::Key(e) = event::read()? && e.kind == KeyEventKind::Press { @@ -1005,9 +1005,19 @@ impl SysInspectUX { if self.repo_manager.needs_reload { self.repo_manager.needs_reload = false; let _ = self.load_module_index(); + let _ = self.load_model_list(); + let _ = self.load_library_index(); if self.repo_manager.active_tab == 4 { let _ = self.load_platforms(); } + self.repo_manager.profiles.has_global_modules.set(self.repo_manager.module_groups.values().any(|v| !v.is_empty())); + self.repo_manager.profiles.has_global_models.set(!self.repo_manager.model_rows.is_empty()); + if self.minions_rows.is_empty() + && let Ok(rows) = self.fetch_minions() + { + self.minions_rows = rows; + } + self.repo_manager.profiles.has_connected_minions.set(!self.minions_rows.is_empty()); self.mark_repo_sync_pending(); } } else { @@ -2632,10 +2642,32 @@ impl SysInspectUX { self.repo_manager.exit_staging(); match self.repo_manager.staging_mode { repomanager::StagingMode::ProfileModuleAdd => { - self.bulk_add_profile_matches(checked, false); + self.bulk_add_profile_matches(checked, false, false); + } + repomanager::StagingMode::ProfileModelAdd => { + self.bulk_add_profile_matches(checked.clone(), false, false); + let model_ids: Vec = checked.iter().map(|m| m.name.clone()).collect(); + let profile_name = self.repo_manager.profiles.detail_name.clone(); + let _ = self.do_profile_add_matches(&profile_name, model_ids, false, true); + match self.load_profile_detail(&profile_name) { + Ok((models, modules, model_groups, ungrouped_modules, libraries)) => { + self.repo_manager.profiles.enter_detail( + profile_name, + models, + modules, + model_groups, + ungrouped_modules, + libraries, + ); + } + Err(e) => { + self.error_alert_visible = true; + self.error_alert_message = e; + } + } } repomanager::StagingMode::ProfileLibraryAdd => { - self.bulk_add_profile_matches(checked, true); + self.bulk_add_profile_matches(checked, true, false); } _ => { self.bulk_add_modules(checked); @@ -2673,12 +2705,6 @@ impl SysInspectUX { } } } - if !self.repo_manager.staging - && matches!(self.repo_manager.staging_mode, repomanager::StagingMode::ProfileModuleAdd | repomanager::StagingMode::ProfileLibraryAdd) - { - self.repo_manager.profiles.detail_visible = true; - self.status_at_profiles(); - } return handled; } if self.repo_manager.info_visible { @@ -2759,6 +2785,52 @@ impl SysInspectUX { } return true; } + if self.repo_manager.profiles.assign.visible { + let handled = self.repo_manager.profiles.handle_assign_key(e.code); + if !handled && e.code == KeyCode::Enter { + let profile_name = self.repo_manager.profiles.assign.profile_name.clone(); + let selected: Vec = self + .repo_manager + .profiles + .assign + .minions + .iter() + .filter(|(_, checked)| *checked) + .map(|(row, _)| row.minion_id.clone()) + .collect(); + match self.repo_manager.profiles.assign.focus { + profiles::ProfAssignFocus::SelectAll => { + self.repo_manager.profiles.assign_select_all_visible(); + } + profiles::ProfAssignFocus::TagBtn if !selected.is_empty() => match self.do_profile_tag(&profile_name, &selected) { + Ok(()) => { + self.repo_manager.profiles.assign.visible = false; + self.status_at_profiles(); + } + Err(e) => { + self.error_alert_visible = true; + self.error_alert_message = format!("Tag failed: {e}"); + } + }, + profiles::ProfAssignFocus::UntagBtn if !selected.is_empty() => match self.do_profile_untag(&profile_name, &selected) { + Ok(()) => { + self.repo_manager.profiles.assign.visible = false; + self.status_at_profiles(); + } + Err(e) => { + self.error_alert_visible = true; + self.error_alert_message = format!("Untag failed: {e}"); + } + }, + profiles::ProfAssignFocus::CloseBtn => { + self.repo_manager.profiles.assign.visible = false; + self.status_at_profiles(); + } + _ => {} + } + } + return true; + } if self.repo_manager.profiles.create_visible { let handled = self.repo_manager.profiles.handle_create_key(e.code); if !handled && e.code == KeyCode::Enter { @@ -2787,6 +2859,14 @@ impl SysInspectUX { profiles::ProfDetailFocus::AddModuleBtn => { self.repo_manager.enter_profile_module_staging(); } + profiles::ProfDetailFocus::AddFromModelBtn => { + if let Err(err) = self.load_model_list() { + self.error_alert_visible = true; + self.error_alert_message = err; + return true; + } + self.repo_manager.enter_profile_model_staging(); + } profiles::ProfDetailFocus::AddLibraryBtn => { self.repo_manager.enter_profile_library_staging(); } @@ -2794,6 +2874,19 @@ impl SysInspectUX { self.repo_manager.profiles.detail_visible = false; self.status_at_profiles(); } + profiles::ProfDetailFocus::AssignBtn => { + self.repo_manager.profiles.has_connected_minions.set(!self.minions_rows.is_empty()); + if let Some(name) = self.repo_manager.profiles.selected_profile_name().map(|s| s.to_string()) { + self.repo_manager.profiles.assign.minions = self.minions_rows.iter().map(|m| (m.clone(), false)).collect(); + self.repo_manager.profiles.assign.profile_name = name; + self.repo_manager.profiles.assign.focus = profiles::ProfAssignFocus::Query; + self.repo_manager.profiles.assign.filter = ratatui_cheese::input::InputState::new(); + self.repo_manager.profiles.assign.mcursor = 0; + self.repo_manager.profiles.assign.mscroll.set(0); + self.repo_manager.profiles.assign.visible = true; + self.status_at_profiles(); + } + } _ => {} } } @@ -2981,8 +3074,8 @@ impl SysInspectUX { None => return true, }; match self.load_profile_detail(&name) { - Ok((modules, libraries)) => { - self.repo_manager.profiles.enter_detail(name, modules, libraries); + Ok((profile_models, modules, model_groups, ungrouped_modules, libraries)) => { + self.repo_manager.profiles.enter_detail(name, profile_models, modules, model_groups, ungrouped_modules, libraries); self.status_at_profiles(); } Err(e) => { @@ -3047,6 +3140,7 @@ impl SysInspectUX { version: Some(r.kind.clone()), descr: r.checksum.clone(), path: std::path::PathBuf::new(), + profile_modules: Vec::new(), checked: false, platform: None, arch: None, @@ -3066,6 +3160,7 @@ impl SysInspectUX { version: r.version.clone(), descr: r.descr.clone(), path: std::path::PathBuf::new(), + profile_modules: Vec::new(), checked: false, platform: Some(r.platform.clone()), arch: Some(r.arch.clone()), @@ -3201,7 +3296,16 @@ impl SysInspectUX { } } - fn load_profile_detail(&mut self, name: &str) -> Result<(Vec, Vec), String> { + fn load_profile_detail(&mut self, name: &str) -> Result { + self.load_model_list()?; + self.repo_manager.profiles.has_global_modules.set(self.repo_manager.module_groups.values().any(|v| !v.is_empty())); + self.repo_manager.profiles.has_global_models.set(!self.repo_manager.model_rows.is_empty()); + if self.minions_rows.is_empty() + && let Ok(rows) = self.fetch_minions() + { + self.minions_rows = rows; + } + self.repo_manager.profiles.has_connected_minions.set(!self.minions_rows.is_empty()); let ctx_mods = serde_json::json!({"op": "list", "name": name, "library": false}).to_string(); let payload_mods = self.call_profile_rpc(&ctx_mods)?; let module_selectors: Vec = match payload_mods { @@ -3216,6 +3320,13 @@ impl SysInspectUX { _ => return Err("Unexpected payload for profile library selectors".to_string()), }; + let ctx_models = serde_json::json!({"op": "list", "name": name, "model": true}).to_string(); + let payload_models = self.call_profile_rpc(&ctx_models)?; + let profile_models: Vec = match payload_models { + ConsolePayload::StringList { items } => items.iter().filter_map(|s| s.split_once(": ").map(|x| x.1.to_string())).collect(), + _ => vec![], + }; + let resolved_modules: Vec = module_selectors .iter() .filter_map(|s| s.split_once(": ").map(|x| x.1)) @@ -3230,6 +3341,7 @@ impl SysInspectUX { version: r.version.clone().unwrap_or_default(), descr: r.descr.clone(), selector: sel.to_string(), + covered: true, }) .collect::>() }) @@ -3253,7 +3365,9 @@ impl SysInspectUX { }) .collect(); - Ok((resolved_modules, resolved_libraries)) + let (model_groups, ungrouped_modules) = profiles::group_modules_by_models(&profile_models, &self.repo_manager.model_rows, &resolved_modules); + + Ok((profile_models, resolved_modules, model_groups, ungrouped_modules, resolved_libraries)) } fn do_profile_create(&mut self, name: &str) -> Result<(), String> { @@ -3272,8 +3386,8 @@ impl SysInspectUX { Ok(()) } - fn do_profile_add_matches(&mut self, name: &str, matches: Vec, library: bool) -> Result<(), String> { - let ctx = serde_json::json!({"op": "add", "name": name, "matches": matches, "library": library}).to_string(); + fn do_profile_add_matches(&mut self, name: &str, matches: Vec, library: bool, model: bool) -> Result<(), String> { + let ctx = serde_json::json!({"op": "add", "name": name, "matches": matches, "library": library, "model": model}).to_string(); self.call_profile_rpc(&ctx)?; self.mark_repo_sync_pending(); Ok(()) @@ -3286,17 +3400,57 @@ impl SysInspectUX { Ok(()) } - fn bulk_add_profile_matches(&mut self, checked: Vec, library: bool) { - let names: Vec = checked.iter().map(|m| m.name.clone()).collect(); + fn do_profile_tag(&mut self, profile_name: &str, minion_ids: &[String]) -> Result<(), String> { + let ctx = serde_json::json!({"op": "tag", "profiles": [profile_name]}).to_string(); + for mid in minion_ids { + let ctx = ctx.clone(); + let mid = mid.clone(); + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + call_master_console(&self.cfg, &format!("{SCHEME_COMMAND}{CLUSTER_PROFILE}"), "*", None, Some(&mid), Some(&ctx)).await + }) + }) + .map_err(|e| e.to_string())?; + } + Ok(()) + } + + fn do_profile_untag(&mut self, profile_name: &str, minion_ids: &[String]) -> Result<(), String> { + let ctx = serde_json::json!({"op": "untag", "profiles": [profile_name]}).to_string(); + for mid in minion_ids { + let ctx = ctx.clone(); + let mid = mid.clone(); + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + call_master_console(&self.cfg, &format!("{SCHEME_COMMAND}{CLUSTER_PROFILE}"), "*", None, Some(&mid), Some(&ctx)).await + }) + }) + .map_err(|e| e.to_string())?; + } + Ok(()) + } + + fn bulk_add_profile_matches(&mut self, checked: Vec, library: bool, model: bool) { + let names: Vec = if model { + checked.iter().map(|m| m.name.clone()).collect() + } else if library { + checked.iter().map(|m| m.name.clone()).collect() + } else { + let mut modules: Vec = + checked.iter().flat_map(|m| if m.profile_modules.is_empty() { vec![m.name.clone()] } else { m.profile_modules.clone() }).collect(); + modules.sort(); + modules.dedup(); + modules + }; let name = self.repo_manager.profiles.detail_name.clone(); - if let Err(e) = self.do_profile_add_matches(&name, names, library) { + if let Err(e) = self.do_profile_add_matches(&name, names, library, model) { self.error_alert_visible = true; self.error_alert_message = e; return; } match self.load_profile_detail(&name) { - Ok((modules, libraries)) => { - self.repo_manager.profiles.enter_detail(name, modules, libraries); + Ok((profile_models, modules, model_groups, ungrouped_modules, libraries)) => { + self.repo_manager.profiles.enter_detail(name, profile_models, modules, model_groups, ungrouped_modules, libraries); } Err(e) => { self.error_alert_visible = true; @@ -3506,6 +3660,7 @@ impl SysInspectUX { public_entrypoints, public_entrypoint_kinds, public_actions: m.public_actions.clone(), + modules: m.modules.clone(), states: m.states.clone(), target_actions, } @@ -3536,6 +3691,7 @@ impl SysInspectUX { ids.retain(|id| id != model_id); } self.write_enabled_models_dropin(ids)?; + self.reload_master_config()?; self.refresh_local_model_rows(&self.enabled_model_ids_with(model_id, enabled))?; self.repo_manager.models_dirty = true; Ok(()) @@ -3582,6 +3738,7 @@ impl SysInspectUX { let enabled_ids = self.enabled_model_ids_with(&model_id, true); match Self::copy_dir_recursive(path, &dst) .and_then(|_| self.write_enabled_models_dropin(enabled_ids.clone())) + .and_then(|_| self.reload_master_config()) .and_then(|_| self.refresh_local_model_rows(&enabled_ids)) { Ok(()) => { @@ -3630,6 +3787,24 @@ impl SysInspectUX { fn process_module_add(&mut self, path: &std::path::Path) { if path.is_dir() { + let dir_name = path.file_name().unwrap_or_default().to_string_lossy().to_string(); + let module_spec = path.join(format!("{dir_name}.spec")); + if module_spec.exists() { + let module_name = Self::read_spec_name(&module_spec).unwrap_or_else(|| dir_name.clone()); + let (version, descr) = Self::read_spec_version_descr(&module_spec); + let bin = path.join(&dir_name); + self.repo_manager.enter_staging(vec![repomanager::StagedModule { + name: module_name, + version, + descr, + path: if bin.exists() { bin } else { module_spec }, + profile_modules: Vec::new(), + checked: true, + platform: None, + arch: None, + }]); + return; + } let mut staged = Self::scan_dir_for_modules(path); if staged.is_empty() { self.error_alert_visible = true; @@ -3649,13 +3824,14 @@ impl SysInspectUX { } else { let spec = path.with_extension("spec"); if spec.exists() { - let module_name = path.file_stem().unwrap_or_default().to_string_lossy().to_string(); + let module_name = Self::read_spec_name(&spec).unwrap_or_else(|| path.file_stem().unwrap_or_default().to_string_lossy().to_string()); let (version, descr) = Self::read_spec_version_descr(&spec); self.repo_manager.enter_staging(vec![repomanager::StagedModule { name: module_name, version, descr, path: path.to_path_buf(), + profile_modules: Vec::new(), checked: true, platform: None, arch: None, @@ -3688,6 +3864,7 @@ impl SysInspectUX { version, descr, path: if bin.exists() { bin } else { spec }, + profile_modules: Vec::new(), checked: true, platform: None, arch: None, diff --git a/src/ui/profiles.rs b/src/ui/profiles.rs index f3e33e7d..7919975a 100644 --- a/src/ui/profiles.rs +++ b/src/ui/profiles.rs @@ -1,45 +1,62 @@ use super::palette; use super::title::{self, TitleSegment, TitleStyle}; use crossterm::event::KeyCode; +use libsysinspect::console::{ConsoleModelRow, ConsoleOnlineMinionRow}; use ratatui::layout::{Position, Rect}; use ratatui::prelude::Buffer; use ratatui::style::{Color, Modifier, Style}; -use ratatui::widgets::{Block, BorderType, Borders, Clear, Widget}; +use ratatui::widgets::{Block, BorderType, Borders, Clear, Scrollbar, ScrollbarState, StatefulWidget, Widget}; use ratatui_cheese::input::InputState; +use ratatui_cheese::tree::{Tree, TreeGroup, TreeItem, TreeState, TreeStyles}; use ratatui_glamour::color::blend_2d; use ratatui_glamour::rule::dashed_title; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; +use unicode_width::UnicodeWidthStr; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ProfileModuleView { + PerModel, + All, +} #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ProfDetailFocus { Modules, Libraries, AddModuleBtn, + AddFromModelBtn, AddLibraryBtn, CloseBtn, + AssignBtn, } impl ProfDetailFocus { - pub fn next(self, has_modules: bool, has_libraries: bool) -> Self { + pub fn next(self, has_modules: bool, has_libraries: bool, has_global_mods: bool, has_global_mdls: bool, has_connected_m: bool) -> Self { use ProfDetailFocus::*; let mut cur = self; loop { cur = match cur { Modules => Libraries, Libraries => AddModuleBtn, - AddModuleBtn => AddLibraryBtn, - AddLibraryBtn => CloseBtn, + AddModuleBtn => AddFromModelBtn, + AddFromModelBtn => AddLibraryBtn, + AddLibraryBtn => AssignBtn, + AssignBtn => CloseBtn, CloseBtn => Modules, }; match cur { Modules if !has_modules => continue, Libraries if !has_libraries => continue, + AddModuleBtn if !has_global_mods => continue, + AddFromModelBtn if !has_global_mdls => continue, + AddLibraryBtn if !has_libraries => continue, + AssignBtn if !has_connected_m => continue, _ => return cur, } } } - pub fn prev(self, has_modules: bool, has_libraries: bool) -> Self { + pub fn prev(self, has_modules: bool, has_libraries: bool, has_global_mods: bool, has_global_mdls: bool, has_connected_m: bool) -> Self { use ProfDetailFocus::*; let mut cur = self; loop { @@ -47,12 +64,18 @@ impl ProfDetailFocus { Modules => CloseBtn, Libraries => Modules, AddModuleBtn => Libraries, - AddLibraryBtn => AddModuleBtn, - CloseBtn => AddLibraryBtn, + AddFromModelBtn => AddModuleBtn, + AddLibraryBtn => AddFromModelBtn, + AssignBtn => AddLibraryBtn, + CloseBtn => AssignBtn, }; match cur { Modules if !has_modules => continue, Libraries if !has_libraries => continue, + AddModuleBtn if !has_global_mods => continue, + AddFromModelBtn if !has_global_mdls => continue, + AddLibraryBtn if !has_libraries => continue, + AssignBtn if !has_connected_m => continue, _ => return cur, } } @@ -72,15 +95,50 @@ pub enum ProfDeleteFocus { NoBtn, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ProfAssignFocus { + Query, + SelectAll, + TagBtn, + UntagBtn, + CloseBtn, +} + #[derive(Debug)] +pub struct ProfileAssignState { + pub visible: bool, + pub profile_name: String, + pub focus: ProfAssignFocus, + pub minions: Vec<(ConsoleOnlineMinionRow, bool)>, + pub mcursor: usize, + pub mscroll: Cell, + pub filter: InputState, +} + +impl Default for ProfileAssignState { + fn default() -> Self { + Self { + visible: false, + profile_name: String::new(), + focus: ProfAssignFocus::Query, + minions: Vec::new(), + mcursor: 0, + mscroll: Cell::new(0), + filter: InputState::new(), + } + } +} + +#[derive(Debug, Clone)] pub struct ResolvedModule { pub name: String, pub version: String, pub descr: String, pub selector: String, + pub covered: bool, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ResolvedLibrary { pub name: String, pub kind: String, @@ -88,6 +146,16 @@ pub struct ResolvedLibrary { pub selector: String, } +#[derive(Debug, Clone)] +pub struct ResolvedModelGroup { + pub id: String, + pub name: String, + pub modules: Vec, + pub broken: bool, +} + +pub type LoadedProfileDetail = (Vec, Vec, Vec, Vec, Vec); + #[derive(Debug)] pub struct ProfilesManager { // List @@ -98,12 +166,21 @@ pub struct ProfilesManager { // Detail overlay pub detail_visible: bool, pub detail_name: String, + pub detail_models: Vec, pub detail_modules: Vec, + pub detail_model_groups: Vec, + pub detail_ungrouped_modules: Vec, pub detail_libraries: Vec, pub detail_focus: ProfDetailFocus, - pub detail_moffset: Cell, + pub detail_tree_state: RefCell>, + pub detail_module_view: Cell, + pub detail_all_scroll: Cell, pub detail_loffset: Cell, + pub has_global_modules: Cell, + pub has_global_models: Cell, + pub has_connected_minions: Cell, + // Create overlay pub create_visible: bool, pub create_input: InputState, @@ -113,6 +190,9 @@ pub struct ProfilesManager { pub delete_visible: bool, pub delete_name: String, pub delete_focus: ProfDeleteFocus, + + // Assign overlay + pub assign: ProfileAssignState, } impl Default for ProfilesManager { @@ -123,17 +203,26 @@ impl Default for ProfilesManager { scroll: Cell::new(0), detail_visible: false, detail_name: String::new(), + detail_models: Vec::new(), detail_modules: Vec::new(), + detail_model_groups: Vec::new(), + detail_ungrouped_modules: Vec::new(), detail_libraries: Vec::new(), detail_focus: ProfDetailFocus::Modules, - detail_moffset: Cell::new(0), + detail_tree_state: RefCell::new(None), + detail_module_view: Cell::new(ProfileModuleView::PerModel), + detail_all_scroll: Cell::new(0), detail_loffset: Cell::new(0), + has_global_modules: Cell::new(false), + has_global_models: Cell::new(false), + has_connected_minions: Cell::new(false), create_visible: false, create_input: InputState::new(), create_focus: ProfCreateFocus::Input, delete_visible: false, delete_name: String::new(), delete_focus: ProfDeleteFocus::YesBtn, + assign: ProfileAssignState::default(), } } } @@ -177,6 +266,10 @@ impl ProfilesManager { self.profiles.iter().filter(|n| n.to_lowercase().contains(&f)).count() } + fn has_modules(&self) -> bool { + !self.detail_model_groups.is_empty() || !self.detail_ungrouped_modules.is_empty() + } + // ── Detail key handling ── pub fn handle_detail_key(&mut self, key: KeyCode) -> bool { @@ -186,19 +279,34 @@ impl ProfilesManager { self.detail_visible = false; } KeyCode::Tab => { - let hm = !self.detail_modules.is_empty(); + let hm = self.has_modules(); let hl = !self.detail_libraries.is_empty(); - self.detail_focus = self.detail_focus.next(hm, hl); + let gm = self.has_global_modules.get(); + let gmd = self.has_global_models.get(); + let cm = self.has_connected_minions.get(); + self.detail_focus = self.detail_focus.next(hm, hl, gm, gmd, cm); } KeyCode::BackTab => { - let hm = !self.detail_modules.is_empty(); + let hm = self.has_modules(); let hl = !self.detail_libraries.is_empty(); - self.detail_focus = self.detail_focus.prev(hm, hl); + let gm = self.has_global_modules.get(); + let gmd = self.has_global_models.get(); + let cm = self.has_connected_minions.get(); + self.detail_focus = self.detail_focus.prev(hm, hl, gm, gmd, cm); } KeyCode::Up => match self.detail_focus { Modules => { - let o = self.detail_moffset.get(); - self.detail_moffset.set(o.saturating_sub(1)); + if self.detail_module_view.get() == ProfileModuleView::All { + self.detail_all_scroll.set(self.detail_all_scroll.get().saturating_sub(1)); + } else { + let groups = self.build_profile_tree(); + if groups.is_empty() { + return true; + } + let mut ts = self.detail_tree_state.borrow_mut().take().unwrap_or_else(|| TreeState::new(groups.len())); + ts.select_prev(&groups); + *self.detail_tree_state.borrow_mut() = Some(ts); + } } Libraries => { let o = self.detail_loffset.get(); @@ -208,10 +316,20 @@ impl ProfilesManager { }, KeyCode::Down => match self.detail_focus { Modules => { - let o = self.detail_moffset.get(); - let view_h = 10usize; // approximate, clamped in render - let max = self.detail_modules.len().saturating_sub(view_h); - self.detail_moffset.set((o + 1).min(max)); + if self.detail_module_view.get() == ProfileModuleView::All { + let items = self.build_all_modules_list(); + let view_h = 10usize; + let max = items.len().saturating_sub(view_h); + self.detail_all_scroll.set((self.detail_all_scroll.get() + 1).min(max)); + } else { + let groups = self.build_profile_tree(); + if groups.is_empty() { + return true; + } + let mut ts = self.detail_tree_state.borrow_mut().take().unwrap_or_else(|| TreeState::new(groups.len())); + ts.select_next(&groups); + *self.detail_tree_state.borrow_mut() = Some(ts); + } } Libraries => { let o = self.detail_loffset.get(); @@ -223,8 +341,19 @@ impl ProfilesManager { }, KeyCode::PageUp => match self.detail_focus { Modules => { - let o = self.detail_moffset.get(); - self.detail_moffset.set(o.saturating_sub(10)); + if self.detail_module_view.get() == ProfileModuleView::All { + self.detail_all_scroll.set(self.detail_all_scroll.get().saturating_sub(10)); + } else { + let groups = self.build_profile_tree(); + if groups.is_empty() { + return true; + } + let mut ts = self.detail_tree_state.borrow_mut().take().unwrap_or_else(|| TreeState::new(groups.len())); + for _ in 0..10 { + ts.select_prev(&groups); + } + *self.detail_tree_state.borrow_mut() = Some(ts); + } } Libraries => { let o = self.detail_loffset.get(); @@ -234,9 +363,22 @@ impl ProfilesManager { }, KeyCode::PageDown => match self.detail_focus { Modules => { - let o = self.detail_moffset.get(); - let max = self.detail_modules.len().saturating_sub(10); - self.detail_moffset.set((o + 10).min(max)); + if self.detail_module_view.get() == ProfileModuleView::All { + let items = self.build_all_modules_list(); + let view_h = 10usize; + let max = items.len().saturating_sub(view_h); + self.detail_all_scroll.set((self.detail_all_scroll.get() + 10).min(max)); + } else { + let groups = self.build_profile_tree(); + if groups.is_empty() { + return true; + } + let mut ts = self.detail_tree_state.borrow_mut().take().unwrap_or_else(|| TreeState::new(groups.len())); + for _ in 0..10 { + ts.select_next(&groups); + } + *self.detail_tree_state.borrow_mut() = Some(ts); + } } Libraries => { let o = self.detail_loffset.get(); @@ -245,7 +387,35 @@ impl ProfilesManager { } _ => {} }, - KeyCode::Enter => return false, + KeyCode::Char('+') if self.detail_focus == ProfDetailFocus::Modules => { + if let Some(ref mut ts) = *self.detail_tree_state.borrow_mut() { + ts.expand_all(); + } + } + KeyCode::Char('-') if self.detail_focus == ProfDetailFocus::Modules => { + if let Some(ref mut ts) = *self.detail_tree_state.borrow_mut() { + ts.collapse_all(); + } + } + KeyCode::Left | KeyCode::Right if self.detail_focus == ProfDetailFocus::Modules => { + self.detail_module_view.set(match self.detail_module_view.get() { + ProfileModuleView::PerModel => ProfileModuleView::All, + ProfileModuleView::All => ProfileModuleView::PerModel, + }); + } + KeyCode::Enter => { + if self.detail_focus == ProfDetailFocus::Modules { + let groups = self.build_profile_tree(); + if groups.is_empty() { + return true; + } + let mut ts = self.detail_tree_state.borrow_mut().take().unwrap_or_else(|| TreeState::new(groups.len())); + ts.toggle_selected(); + *self.detail_tree_state.borrow_mut() = Some(ts); + return true; + } + return false; + } _ => {} } true @@ -326,20 +496,178 @@ impl ProfilesManager { true } + // ── Assign key handling ── + + pub fn assign_select_all_visible(&mut self) { + let indices = self.assign_filtered_indices(); + for idx in indices { + if let Some((_, checked)) = self.assign.minions.get_mut(idx) { + *checked = true; + } + } + } + + fn matches_assign_query(host: &str, query: &str) -> bool { + if query.is_empty() { + return true; + } + let q = query.to_lowercase(); + let host = host.to_lowercase(); + match (q.starts_with('*'), q.ends_with('*')) { + (true, true) => { + let inner = &q[1..q.len() - 1]; + host.contains(inner) + } + (true, false) => { + let suffix = &q[1..]; + host.ends_with(suffix) + } + (false, true) => { + let prefix = &q[..q.len() - 1]; + host.starts_with(prefix) + } + (false, false) => host == q, + } + } + + fn assign_filtered_indices(&self) -> Vec { + let filter_val = self.assign.filter.value(); + if filter_val.is_empty() { + (0..self.assign.minions.len()).collect() + } else { + self.assign + .minions + .iter() + .enumerate() + .filter(|(_, (row, _))| Self::matches_assign_query(&row.fqdn, filter_val) || Self::matches_assign_query(&row.hostname, filter_val)) + .map(|(i, _)| i) + .collect() + } + } + + fn online_host(row: &ConsoleOnlineMinionRow) -> String { + if !row.fqdn.trim().is_empty() { + row.fqdn.clone() + } else if !row.hostname.trim().is_empty() { + row.hostname.clone() + } else { + "unknown".to_string() + } + } + + fn fmt_octal_ip(ip: &str) -> String { + if ip.is_empty() { + return "unknown".to_string(); + } + let parts: Vec<&str> = ip.split('.').collect(); + if parts.len() == 4 && parts.iter().all(|p| p.parse::().is_ok()) { + format!("{:>3}.{:>3}.{:>3}.{:>3}", parts[0], parts[1], parts[2], parts[3]) + } else { + ip.to_string() + } + } + + fn trunc_kernel(s: &str, max: usize) -> String { + if s.chars().count() <= max { s.to_string() } else { format!("{}…", s.chars().take(max.saturating_sub(1)).collect::()) } + } + + pub fn handle_assign_key(&mut self, key: KeyCode) -> bool { + use ProfAssignFocus::*; + match key { + KeyCode::Esc => { + self.assign.visible = false; + } + KeyCode::Tab => { + self.assign.focus = match self.assign.focus { + Query => SelectAll, + SelectAll => TagBtn, + TagBtn => UntagBtn, + UntagBtn => CloseBtn, + CloseBtn => Query, + }; + } + KeyCode::BackTab => { + self.assign.focus = match self.assign.focus { + Query => CloseBtn, + SelectAll => Query, + TagBtn => SelectAll, + UntagBtn => TagBtn, + CloseBtn => UntagBtn, + }; + } + KeyCode::Up => { + self.assign.mcursor = self.assign.mcursor.saturating_sub(1); + } + KeyCode::Down => { + let max = self.assign_filtered_indices().len().saturating_sub(1); + self.assign.mcursor = (self.assign.mcursor + 1).min(max); + } + KeyCode::Char(' ') => { + let indices = self.assign_filtered_indices(); + if let Some(&real_idx) = indices.get(self.assign.mcursor) + && let Some((_, checked)) = self.assign.minions.get_mut(real_idx) + { + *checked = !*checked; + } + } + KeyCode::Char(c) if self.assign.focus == Query => { + self.assign.filter.insert_char(c); + let max = self.assign_filtered_indices().len().saturating_sub(1); + self.assign.mcursor = self.assign.mcursor.min(max); + } + KeyCode::Backspace if self.assign.focus == Query => { + self.assign.filter.delete_before(); + let max = self.assign_filtered_indices().len().saturating_sub(1); + self.assign.mcursor = self.assign.mcursor.min(max); + } + KeyCode::Delete if self.assign.focus == Query => { + self.assign.filter.delete_at(); + let max = self.assign_filtered_indices().len().saturating_sub(1); + self.assign.mcursor = self.assign.mcursor.min(max); + } + KeyCode::Left if self.assign.focus == Query => { + self.assign.filter.move_left(); + } + KeyCode::Right if self.assign.focus == Query => { + self.assign.filter.move_right(); + } + KeyCode::Home if self.assign.focus == Query => { + self.assign.filter.home(); + } + KeyCode::End if self.assign.focus == Query => { + self.assign.filter.end(); + } + KeyCode::Enter => return false, + _ => {} + } + true + } + // ── State management ── - pub fn enter_detail(&mut self, name: String, modules: Vec, libraries: Vec) { + pub fn enter_detail( + &mut self, name: String, models: Vec, modules: Vec, model_groups: Vec, + ungrouped_modules: Vec, libraries: Vec, + ) { self.detail_name = name; + self.detail_models = models; self.detail_modules = modules; + self.detail_model_groups = model_groups; + self.detail_ungrouped_modules = ungrouped_modules; self.detail_libraries = libraries; - self.detail_focus = if !self.detail_modules.is_empty() { - ProfDetailFocus::Modules - } else if !self.detail_libraries.is_empty() { - ProfDetailFocus::Libraries - } else { - ProfDetailFocus::AddModuleBtn - }; - self.detail_moffset.set(0); + self.detail_focus = ProfDetailFocus::Modules; + if !self.has_modules() { + self.detail_focus = self.detail_focus.next( + self.has_modules(), + !self.detail_libraries.is_empty(), + self.has_global_modules.get(), + self.has_global_models.get(), + self.has_connected_minions.get(), + ); + } + *self.detail_tree_state.borrow_mut() = None; + self.detail_module_view.set(ProfileModuleView::All); + self.detail_all_scroll.set(0); self.detail_loffset.set(0); self.detail_visible = true; } @@ -502,10 +830,11 @@ impl ProfilesManager { let mut row_y = inner.y; // ── Modules section ── + let view_label = if self.detail_module_view.get() == ProfileModuleView::PerModel { " Modules (per model) " } else { " Modules (all) " }; dashed_title( Rect { x: inner.x, y: row_y, width: inner.width, height: 1 }, buf, - " Modules ", + view_label, palette::PROCESSING, palette::PRIMARY, palette::PROCESSING_DIMMED, @@ -514,8 +843,80 @@ impl ProfilesManager { if mod_h > 0 { let mod_area = Rect { x: inner.x, y: row_y, width: inner.width.saturating_sub(1), height: mod_h.saturating_sub(1) }; - self.render_resolved_modules(mod_area, buf, self.detail_focus == ProfDetailFocus::Modules); - row_y = mod_area.bottom() + 1; + let focused = self.detail_focus == ProfDetailFocus::Modules; + if self.detail_module_view.get() == ProfileModuleView::PerModel { + let groups = self.build_profile_tree(); + let n_groups = groups.len(); + if n_groups == 0 { + let msg = "(no models in this profile)"; + let x = mod_area.x + (mod_area.width.saturating_sub(msg.len() as u16)) / 2; + let y = mod_area.y + mod_area.height / 2; + buf.set_string(x, y, msg, Style::default().fg(palette::MUTED)); + row_y = mod_area.bottom() + 1; + } else { + let total_items: usize = n_groups + groups.iter().map(|g| g.children_slice().len()).sum::(); + let treesel = if focused { Style::default().fg(palette::BLACK).bg(palette::HIGHLIGHT) } else { Style::default().fg(palette::FG) }; + let tree_styles = TreeStyles { + parent: Style::default().fg(palette::PROCESSING).add_modifier(Modifier::BOLD), + child: Style::default().fg(palette::FG), + selected: treesel, + chevron: Style::default().fg(palette::MUTED), + chevron_active: Style::default().fg(palette::MUTED), + chevron_dim: Style::default().fg(palette::MUTED), + count: Style::default().fg(palette::GRAY_1), + icon: Style::default().fg(palette::ERROR), + }; + let tree = Tree::default().groups(groups).styles(tree_styles).chevron_collapsed("▸ ").chevron_expanded("▾ "); + let tree_inner = Rect::new(mod_area.x, mod_area.y, mod_area.width.saturating_sub(1), mod_area.height); + let mut state = self.detail_tree_state.borrow_mut().take().unwrap_or_else(|| TreeState::new(n_groups)); + StatefulWidget::render(&tree, tree_inner, buf, &mut state); + *self.detail_tree_state.borrow_mut() = Some(state.clone()); + + let scroller_area = Rect::new(tree_inner.right().saturating_sub(1), tree_inner.y, 1, tree_inner.height); + let mut scroller = ScrollbarState::default().content_length(total_items).position(state.selected().0); + Scrollbar::default() + .begin_symbol(None) + .end_symbol(None) + .track_symbol(Some("\u{28FF}")) + .thumb_symbol("█") + .track_style(Style::default().bg(palette::BG_3)) + .thumb_style(Style::default().fg(palette::GRAY_1)) + .render(scroller_area, buf, &mut scroller); + + row_y = mod_area.bottom() + 1; + } + } else { + let items = self.build_all_modules_list(); + let total = items.len(); + let view_h = mod_area.height as usize; + let max_scroll = total.saturating_sub(view_h); + let mut s = self.detail_all_scroll.get(); + if s > max_scroll { + s = max_scroll; + } + self.detail_all_scroll.set(s); + + if total == 0 { + let msg = "(no modules in this profile)"; + let x = mod_area.x + (mod_area.width.saturating_sub(msg.len() as u16)) / 2; + let y = mod_area.y + mod_area.height / 2; + buf.set_string(x, y, msg, Style::default().fg(palette::MUTED)); + } else { + for i in 0..view_h.min(total.saturating_sub(s)) { + let (covered, name, ver, descr) = &items[s + i]; + let ry = mod_area.y + i as u16; + let row_style = if focused { Style::default().fg(palette::FG) } else { Style::default().fg(palette::MUTED) }; + let icon = if *covered { " " } else { "✖ " }; + let icon_style = if *covered { row_style } else { Style::default().fg(palette::ERROR) }; + let row = format!("{name} {ver}{descr}"); + let max_w = (mod_area.width as usize).saturating_sub(2); + buf.set_string(mod_area.x, ry, icon, icon_style); + buf.set_string(mod_area.x + 2, ry, truncate_str(&row, max_w), row_style); + } + } + Self::draw_scrollbar(buf, mod_area, s, total, view_h, focused); + row_y = mod_area.bottom() + 1; + } } // ── Libraries section ── @@ -541,22 +942,34 @@ impl ProfilesManager { // ── Buttons ── let btn_y = inner.bottom().saturating_sub(2); - let btn_labels = ["[ Add Module ]", "[ Add Library ]", "[ Close ]"]; + let btn_labels = ["[ Add Module ]", "[ Add From Model ]", "[ Add Library ]", "[ Assign ]", "[ Close ]"]; let btn_widths: Vec = btn_labels.iter().map(|l| l.len() as u16).collect(); - let total_btn_w: u16 = btn_widths.iter().sum::() + 4; // 2 gaps + let total_btn_w: u16 = btn_widths.iter().sum::() + (btn_widths.len() as u16 - 1) * 2; let mut btn_x = inner.x + (inner.width.saturating_sub(total_btn_w)) / 2; let focus_idx = match self.detail_focus { ProfDetailFocus::AddModuleBtn => 0, - ProfDetailFocus::AddLibraryBtn => 1, - ProfDetailFocus::CloseBtn => 2, + ProfDetailFocus::AddFromModelBtn => 1, + ProfDetailFocus::AddLibraryBtn => 2, + ProfDetailFocus::AssignBtn => 3, + ProfDetailFocus::CloseBtn => 4, _ => usize::MAX, }; let sel_btn = Style::default().fg(palette::WHITE).bg(palette::PROCESSING_HEAT); let unsel_btn = Style::default().fg(palette::FG).bg(palette::BG_2); for (i, label) in btn_labels.iter().enumerate() { - let style = if i == focus_idx { sel_btn } else { unsel_btn }; + let style = if i == focus_idx { + sel_btn + } else if *label == "[ Add Module ]" && !self.has_global_modules.get() + || *label == "[ Add From Model ]" && !self.has_global_models.get() + || *label == "[ Add Library ]" && self.detail_libraries.is_empty() + || *label == "[ Assign ]" && !self.has_connected_minions.get() + { + Style::default().fg(palette::MUTED).bg(palette::BG_2) + } else { + unsel_btn + }; buf.set_string(btn_x, btn_y, *label, style); btn_x += btn_widths[i] + 2; } @@ -564,40 +977,96 @@ impl ProfilesManager { Self::draw_shadow(buf, canvas, w, h); } - fn render_resolved_modules(&self, area: Rect, buf: &mut Buffer, focused: bool) { - if self.detail_modules.is_empty() { - let msg = "(no modules in this profile)"; - let x = area.x + (area.width.saturating_sub(msg.len() as u16)) / 2; - let y = area.y + area.height / 2; - buf.set_string(x, y, msg, Style::default().fg(palette::MUTED)); - Self::draw_scrollbar(buf, area, 0, 1, area.height as usize, focused); - return; + fn build_profile_tree(&self) -> Vec { + let mut groups = Vec::new(); + let mut name_w = 0usize; + let mut ver_w = 0usize; + for g in &self.detail_model_groups { + for m in &g.modules { + name_w = name_w.max(UnicodeWidthStr::width(&m.name[..])); + ver_w = ver_w.max(if m.covered { UnicodeWidthStr::width(&m.version[..]) } else { 0 }); + } } + ver_w = ver_w.max(4); + for group in &self.detail_model_groups { + let mut broken = false; + let children: Vec = group + .modules + .iter() + .map(|m| { + if !m.covered { + broken = true; + } + let (icon, ver, descr) = if m.covered { + (" ", format!("{: String { + let w = UnicodeWidthStr::width(s); + if w >= target { s.to_string() } else { format!("{}{}", s, " ".repeat(target - w)) } + } - for i in 0..view_h.min(total.saturating_sub(s)) { - let idx = s + i; - let ry = area.y + i as u16; - let m = &self.detail_modules[idx]; - let fg = if focused { palette::FG } else { palette::MUTED }; - let ver_fg = if focused { palette::HIGHLIGHT } else { palette::MUTED }; - let desc_fg = if focused { palette::GRAY_1 } else { palette::MUTED }; - let row_style = Style::default().fg(fg); - buf.set_string(area.x + 2, ry, truncate_str(&m.name, name_w as usize), row_style); - buf.set_string(area.x + 2 + name_w + 1, ry, truncate_str(&m.version, ver_w as usize), Style::default().fg(ver_fg)); - let desc_x = area.x + 2 + name_w + 1 + ver_w + 1; - let max_desc = (area.width.saturating_sub(2 + name_w + ver_w + 3)) as usize; - buf.set_string(desc_x, ry, truncate_str(&m.descr, max_desc), Style::default().fg(desc_fg)); + fn build_all_modules_list(&self) -> Vec<(bool, String, String, String)> { + let mut seen = std::collections::BTreeSet::new(); + let mut items = Vec::new(); + let mut name_w = 0usize; + let mut ver_w = 0usize; + for g in &self.detail_model_groups { + for m in &g.modules { + if !seen.insert(m.name.clone()) { + continue; + } + name_w = name_w.max(UnicodeWidthStr::width(&m.name[..])); + ver_w = ver_w.max(if m.covered { UnicodeWidthStr::width(&m.version[..]) } else { 0 }); + } } - - Self::draw_scrollbar(buf, area, s, total, view_h, focused); + for m in &self.detail_ungrouped_modules { + if !seen.insert(m.name.clone()) { + continue; + } + name_w = name_w.max(UnicodeWidthStr::width(&m.name[..])); + ver_w = ver_w.max(if m.covered { UnicodeWidthStr::width(&m.version[..]) } else { 0 }); + } + ver_w = ver_w.max(4); + let mut seen = std::collections::BTreeSet::new(); + for g in &self.detail_model_groups { + for m in &g.modules { + if !seen.insert(m.name.clone()) { + continue; + } + let (ver, descr) = if m.covered { + (format!("{: = filtered_indices.iter().map(|&idx| &self.assign.minions[idx].0).collect(); + let checked_by_ri: Vec = filtered_indices.iter().map(|&idx| self.assign.minions[idx].1).collect(); + + let ips: Vec = visible_rows.iter().map(|r| Self::fmt_octal_ip(&r.ip)).collect(); + let hosts: Vec = visible_rows.iter().map(|r| Self::online_host(r)).collect(); + let vers: Vec = visible_rows.iter().map(|r| if r.version.is_empty() { "-".to_string() } else { r.version.clone() }).collect(); + let distros: Vec = visible_rows + .iter() + .map(|r| { + if r.os_distribution.is_empty() && r.os_version.is_empty() { + "-".to_string() + } else if r.os_version.is_empty() { + r.os_distribution.clone() + } else if r.os_distribution.is_empty() { + r.os_version.clone() + } else { + format!("{}/{}", r.os_distribution, r.os_version) + } + }) + .collect(); + + let ip_w = ips.iter().map(|s| UnicodeWidthStr::width(s.as_str()) as u16).max().unwrap_or(2).max(2); + let host_w = hosts.iter().map(|s| UnicodeWidthStr::width(s.as_str()) as u16).max().unwrap_or(4).max(4); + let ver_w = vers.iter().map(|s| UnicodeWidthStr::width(s.as_str()) as u16).max().unwrap_or(2).max(2); + let dist_w = distros.iter().map(|s| UnicodeWidthStr::width(s.as_str()) as u16).max().unwrap_or(3).max(3); + + let cb_w: u16 = 3; + let fixed_w = cb_w + ip_w + host_w + ver_w + dist_w + 10; + let ker_avail = list_area.width.saturating_sub(fixed_w) as usize; + let kers: Vec = visible_rows.iter().map(|r| Self::trunc_kernel(&r.kernel, ker_avail)).collect(); + + for i in 0..view_h { + let f_idx = scroll + i; + let ry = list_area.y + i as u16; + let sel = f_idx == self.assign.mcursor; + + if sel { + for cx in 0..inner.width { + if let Some(cell) = buf.cell_mut(Position::new(inner.x + cx, ry)) { + cell.set_bg(palette::PROCESSING_HEAT); + } + } + } + + let row_fg = if sel { palette::WHITE } else { palette::FG }; + let ip_fg = if sel { palette::BLACK } else { palette::GRAY_1 }; + let host_fg = if sel { palette::WHITE } else { palette::PROCESSING_PEAK }; + let ver_fg = if sel { palette::WHITE } else { palette::PROCESSING_GLOW }; + let dist_fg = if sel { palette::WHITE } else { palette::PRIMARY }; + let ker_fg = if sel { palette::BLACK } else { palette::GRAY_1 }; + + let check = if checked_by_ri[f_idx] { "▣" } else { "□" }; + let mut col_x = list_area.x; + + buf.set_string(col_x, ry, format!("{check} "), Style::default().fg(row_fg)); + col_x += cb_w + 1; + + buf.set_string(col_x, ry, Self::pad_to_width(&ips[f_idx], ip_w as usize), Style::default().fg(ip_fg)); + col_x += ip_w + 2; + + buf.set_string(col_x, ry, Self::pad_to_width(&hosts[f_idx], host_w as usize), Style::default().fg(host_fg)); + col_x += host_w + 2; + + buf.set_string(col_x, ry, Self::pad_to_width(&vers[f_idx], ver_w as usize), Style::default().fg(ver_fg)); + col_x += ver_w + 2; + + buf.set_string(col_x, ry, Self::pad_to_width(&distros[f_idx], dist_w as usize), Style::default().fg(dist_fg)); + col_x += dist_w + 2; + + buf.set_string(col_x, ry, &kers[f_idx], Style::default().fg(ker_fg)); + } + } + + Self::draw_scrollbar(buf, list_area, scroll, total_filtered, view_h, true); + + let btn_y = list_y + list_h as u16; + let btn_labels = ["[Select All]", "[ Tag ]", "[ Untag ]", "[ Close ]"]; + let btn_widths: Vec = btn_labels.iter().map(|l| l.len() as u16).collect(); + let total_btn_w: u16 = btn_widths.iter().sum::() + (btn_widths.len() as u16 - 1) * 2; + let mut btn_x = inner.x + (inner.width.saturating_sub(total_btn_w)) / 2; + + let focus_idx = match self.assign.focus { + ProfAssignFocus::SelectAll => 0, + ProfAssignFocus::TagBtn => 1, + ProfAssignFocus::UntagBtn => 2, + ProfAssignFocus::CloseBtn => 3, + _ => usize::MAX, + }; + let sel_btn = Style::default().fg(palette::WHITE).bg(palette::PROCESSING_HEAT); + let unsel_btn = Style::default().fg(palette::FG).bg(palette::BG_2); + + for (i, label) in btn_labels.iter().enumerate() { + let style = if i == focus_idx { sel_btn } else { unsel_btn }; + buf.set_string(btn_x, btn_y, *label, style); + btn_x += btn_widths[i] + 2; + } + + Self::draw_shadow(buf, canvas, w, h); + } + + fn render_assign_filter(area: Rect, buf: &mut Buffer, focused: bool, filter_state: &InputState) { + let label_style = + if focused { Style::default().fg(palette::FORM_LABEL).add_modifier(Modifier::BOLD) } else { Style::default().fg(palette::FORM_LABEL) }; + buf.set_string(area.x, area.y, "Query: ", label_style); + + let input_x = area.x + 8u16; + let input_w = area.width.saturating_sub(8); + if input_w == 0 { + return; + } + + let field_bg = if focused { palette::HIGHLIGHT } else { palette::GRAY_1 }; + for cx in input_x..input_x + input_w { + if let Some(cell) = buf.cell_mut(Position::new(cx, area.y)) { + cell.set_bg(field_bg); + } + } + + let mut is = InputState::new(); + is.set_value(filter_state.value().to_string()); + is.set_focused(focused); + let fc = filter_state.cursor_pos(); + while is.cursor_pos() < fc { + is.move_right(); + } + let styles = ratatui_cheese::input::InputStyles { text: Style::default().fg(palette::BG_1), ..Default::default() }; + let inp = ratatui_cheese::input::Input::new("").prompt("").placeholder("hostname filter...").styles(styles); + ratatui::widgets::StatefulWidget::render(&inp, Rect::new(input_x, area.y, input_w, 1), buf, &mut is); + } + fn draw_shadow(buf: &mut Buffer, canvas: Rect, dlg_w: u16, dlg_h: u16) { let buf_area = buf.area(); let x = canvas.x; @@ -875,3 +1527,54 @@ impl ProfilesManager { fn truncate_str(s: &str, max_w: usize) -> String { if s.len() <= max_w { s.to_string() } else { format!("{}…", &s[..max_w.saturating_sub(1)]) } } + +pub fn group_modules_by_models( + profile_model_ids: &[String], model_rows: &[ConsoleModelRow], modules: &[ResolvedModule], +) -> (Vec, Vec) { + let mut groups = Vec::new(); + let mut covered_set = std::collections::BTreeSet::new(); + + for model_id in profile_model_ids { + let Some(model) = model_rows.iter().find(|r| &r.id == model_id) else { + continue; + }; + let mut entries = Vec::new(); + let mut seen = std::collections::BTreeSet::new(); + for module_name in &model.modules { + if !seen.insert(module_name.clone()) { + continue; + } + let matched = modules.iter().find(|m| &m.name == module_name); + if let Some(m) = matched { + covered_set.insert(m.name.clone()); + entries.push(ResolvedModule { + name: m.name.clone(), + version: m.version.clone(), + descr: m.descr.clone(), + selector: m.selector.clone(), + covered: true, + }); + } else { + entries.push(ResolvedModule { + name: module_name.clone(), + version: String::new(), + descr: String::new(), + selector: String::new(), + covered: false, + }); + } + } + if !entries.is_empty() { + let broken = entries.iter().any(|m| !m.covered); + groups.push(ResolvedModelGroup { + id: model.id.clone(), + name: if model.name.trim().is_empty() { model.id.clone() } else { model.name.clone() }, + modules: entries, + broken, + }); + } + } + + let extras = modules.iter().filter(|m| !covered_set.contains(&m.name)).cloned().collect(); + (groups, extras) +} diff --git a/src/ui/profiles_ut.rs b/src/ui/profiles_ut.rs new file mode 100644 index 00000000..0d12bb73 --- /dev/null +++ b/src/ui/profiles_ut.rs @@ -0,0 +1,79 @@ +use super::profiles::{ResolvedModule, group_modules_by_models}; +use libsysinspect::console::ConsoleModelRow; + +#[test] +fn groups_modules_under_profile_models_with_coverage_flags() { + let model_rows = vec![ + ConsoleModelRow { + id: "model-a".to_string(), + enabled: true, + name: "Model A".to_string(), + version: "1.0".to_string(), + description: String::new(), + entrypoints: vec![], + entrypoint_kinds: vec![], + public_entrypoints: vec![], + public_entrypoint_kinds: vec![], + public_actions: vec![], + modules: vec!["module.one".to_string(), "module.two".to_string()], + states: vec![], + target_actions: vec![], + }, + ConsoleModelRow { + id: "model-b".to_string(), + enabled: true, + name: "Model B".to_string(), + version: "1.0".to_string(), + description: String::new(), + entrypoints: vec![], + entrypoint_kinds: vec![], + public_entrypoints: vec![], + public_entrypoint_kinds: vec![], + public_actions: vec![], + modules: vec!["module.one".to_string(), "foo.bar".to_string()], + states: vec![], + target_actions: vec![], + }, + ]; + let resolved_modules = vec![ + ResolvedModule { + name: "module.one".to_string(), + version: "1".to_string(), + descr: String::new(), + selector: "module.one".to_string(), + covered: true, + }, + ResolvedModule { + name: "foo.bar".to_string(), + version: "1".to_string(), + descr: String::new(), + selector: "foo.bar".to_string(), + covered: true, + }, + ResolvedModule { + name: "orphan.mod".to_string(), + version: "1".to_string(), + descr: String::new(), + selector: "orphan.mod".to_string(), + covered: true, + }, + ]; + let profile_model_ids = vec!["model-a".to_string(), "model-b".to_string()]; + + let (groups, extras) = group_modules_by_models(&profile_model_ids, &model_rows, &resolved_modules); + + assert_eq!(groups.len(), 2); + assert_eq!(groups[0].name, "Model A"); + let g0_names: Vec<&str> = groups[0].modules.iter().map(|m| m.name.as_str()).collect(); + assert_eq!(g0_names, vec!["module.one", "module.two"]); + assert!(groups[0].modules[0].covered); + assert!(!groups[0].modules[1].covered); + + assert_eq!(groups[1].name, "Model B"); + let g1_names: Vec<&str> = groups[1].modules.iter().map(|m| m.name.as_str()).collect(); + assert_eq!(g1_names, vec!["module.one", "foo.bar"]); + assert!(groups[1].modules[0].covered); + assert!(groups[1].modules[1].covered); + + assert_eq!(extras.iter().map(|m| m.name.as_str()).collect::>(), vec!["orphan.mod"]); +} diff --git a/src/ui/repomanager.rs b/src/ui/repomanager.rs index ea3ab5ca..a7058530 100644 --- a/src/ui/repomanager.rs +++ b/src/ui/repomanager.rs @@ -26,6 +26,7 @@ pub struct StagedModule { pub version: Option, pub descr: String, pub path: std::path::PathBuf, + pub profile_modules: Vec, pub checked: bool, pub platform: Option, pub arch: Option, @@ -45,6 +46,7 @@ pub enum StagingMode { ModuleDelete, LibraryDelete, ProfileModuleAdd, + ProfileModelAdd, ProfileLibraryAdd, } @@ -224,6 +226,7 @@ impl RepoManager { version: r.version.clone(), descr: r.descr.clone(), path: std::path::PathBuf::new(), + profile_modules: Vec::new(), checked: false, platform: Some(r.platform.clone()), arch: Some(r.arch.clone()), @@ -238,6 +241,31 @@ impl RepoManager { self.delete_mode = false; } + pub fn enter_profile_model_staging(&mut self) { + self.staged = self + .model_rows + .iter() + .filter(|row| row.enabled && !row.modules.is_empty()) + .map(|row| StagedModule { + name: row.id.clone(), + version: Some(row.version.clone()), + descr: format!("{} module{}", row.modules.len(), if row.modules.len() == 1 { "" } else { "s" }), + path: std::path::PathBuf::new(), + profile_modules: row.modules.clone(), + checked: false, + platform: None, + arch: None, + }) + .collect(); + self.staging_cursor = 0; + self.staging_scroll = Cell::new(0); + self.staging_focus = StagingFocus::List; + self.staging_mode = StagingMode::ProfileModelAdd; + self.profiles.detail_visible = false; + self.staging = true; + self.delete_mode = false; + } + pub fn enter_profile_library_staging(&mut self) { self.staged = self .lib_rows @@ -247,6 +275,7 @@ impl RepoManager { version: Some(r.kind.clone()), descr: r.checksum.clone(), path: std::path::PathBuf::new(), + profile_modules: Vec::new(), checked: false, platform: None, arch: None, @@ -357,6 +386,9 @@ impl RepoManager { if self.profiles.delete_visible { self.profiles.render_delete(parent, buf); } + if self.profiles.assign.visible { + self.profiles.render_assign(parent, buf); + } if self.platforms.delete_visible { self.platforms.render_delete(parent, buf); } @@ -441,7 +473,11 @@ impl RepoManager { } fn render_staging(&self, parent: Rect, buf: &mut Buffer) { - let dlg_w = (parent.width * 3 / 4).clamp(70, 110); + let dlg_w = if matches!(self.staging_mode, StagingMode::ProfileModelAdd) { + (parent.width as f32 * 0.7) as u16 + } else { + (parent.width * 3 / 4).clamp(70, 110) + 12 + }; let module_rows = self.staged.len().min(20) as u16; let btn_height: u16 = 2; let dlg_h = (module_rows + btn_height + 2).clamp(8, parent.height * 3 / 4); @@ -953,6 +989,7 @@ impl RepoManager { let state_w: u16 = 4; let name_w = list_area.width.saturating_sub(36); let ver_w: u16 = 14; + let known_modules: std::collections::BTreeSet<&str> = self.module_groups.values().flatten().map(|r| r.name.as_str()).collect(); for i in 0..view_h.min(total.saturating_sub(s)) { let fi = s + i; let (_oi, row) = filtered[fi]; @@ -980,14 +1017,18 @@ impl RepoManager { Style::default().fg(palette::MUTED) }; buf.set_string(list_area.x + 1, ry, if row.enabled { "▣" } else { "□" }, check_style); + let model_broken = !row.modules.is_empty() && row.modules.iter().any(|m| !known_modules.contains(m.as_str())); let name_style = if sel { row_style + } else if model_broken { + Style::default().fg(palette::ERROR) } else if row.enabled { Style::default().fg(palette::PROCESSING) } else { Style::default().fg(palette::MUTED) }; - buf.set_string(list_area.x + 1 + state_w + 1, ry, truncate_str(&row.name, name_w as usize), name_style); + let name_label = if model_broken { format!("✖ {}", row.name) } else { row.name.clone() }; + buf.set_string(list_area.x + 1 + state_w + 1, ry, truncate_str(&name_label, name_w as usize), name_style); let ver_style = if sel { row_style } else if row.enabled { @@ -1365,7 +1406,7 @@ impl RepoManager { ("Id:", model.id.clone()), ("Enabled:", if model.enabled { "yes".to_string() } else { "no".to_string() }), ("Version:", model.version.clone()), - ("Entrypts:", model.entrypoints.join(", ")), + ("Entries:", model.entrypoints.join(", ")), ("States:", model.states.join(", ")), ]; for (i, (label, value)) in lines.iter().enumerate() { diff --git a/src/ui/setup.rs b/src/ui/setup.rs index 626072d9..fc097354 100644 --- a/src/ui/setup.rs +++ b/src/ui/setup.rs @@ -8,11 +8,14 @@ use ratatui::{ layout::Position, prelude::{Buffer, Rect}, style::{Color, Modifier, Style}, + text::{Line, Span, Text}, widgets::{Block, BorderType, Borders, Clear, StatefulWidget, Widget}, }; use ratatui_cheese::input::{Input, InputState}; use ratatui_glamour::color::blend_2d; use ratatui_glamour::rule::dashed_title; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum InstallationMode { @@ -100,6 +103,50 @@ pub struct MasterSetupWizard { pub error_message: Option, } +#[derive(Debug, Clone)] +pub struct SetupProgress { + pub visible: bool, + pub current: usize, + pub total: usize, + pub message: String, + pub done: bool, + pub error: Option, +} + +impl SetupProgress { + pub fn new() -> Self { + Self { visible: true, current: 0, total: 0, message: "Scanning setup artifacts...".to_string(), done: false, error: None } + } + + pub fn hidden() -> Self { + Self { visible: false, current: 0, total: 0, message: String::new(), done: false, error: None } + } +} + +#[derive(Debug, Clone)] +pub struct SetupCompletion { + pub info_message: String, + pub info_styled: Text<'static>, +} + +#[derive(Debug, Clone)] +pub struct SetupRequest { + installation_mode: InstallationMode, + sysmaster_path: String, + custom_destination: String, + bind_addr: String, + bind_port: String, + fs_port: String, + api_enabled: bool, +} + +#[derive(Debug, Clone)] +struct SetupCopyEntry { + source: PathBuf, + destination: PathBuf, + label: String, +} + impl Default for MasterSetupWizard { fn default() -> Self { let cwd = std::env::current_dir().map(|p| p.to_string_lossy().to_string()).unwrap_or_default(); @@ -144,6 +191,18 @@ impl Default for MasterSetupWizard { } impl MasterSetupWizard { + pub fn to_request(&self) -> SetupRequest { + SetupRequest { + installation_mode: self.installation_mode, + sysmaster_path: self.sysmaster_path.value().to_string(), + custom_destination: self.custom_destination.value().to_string(), + bind_addr: self.bind_addr.value().to_string(), + bind_port: self.bind_port.value().to_string(), + fs_port: self.fs_port.value().to_string(), + api_enabled: self.api_enabled, + } + } + pub fn from_config(cfg: &libsysinspect::cfg::mmconf::MasterConfig) -> Self { let root = cfg.root_dir(); let is_system = root == *"/etc/sysinspect"; @@ -522,12 +581,162 @@ impl MasterSetupWizard { /// Write config file, create directories, return the config path on success. pub fn write_config(&self) -> Result { - let root = match self.installation_mode { - InstallationMode::SystemWide => std::path::PathBuf::from("/etc/sysinspect"), - InstallationMode::Custom => std::path::PathBuf::from(self.custom_destination.value()), - }; + self.to_request().run(None).map(|done| { + let _ = done; + self.root_dir_for_write_config().join(if matches!(self.installation_mode, InstallationMode::SystemWide) { + "sysinspect.conf" + } else { + "etc/sysinspect.conf" + }) + }) + } +} + +pub fn render_progress(progress: &SetupProgress, parent: Rect, buf: &mut Buffer) { + if !progress.visible { + return; + } + + let dlg_w = (parent.width / 2).clamp(50, 80); + let dlg_h = 7u16; + let x = parent.x + (parent.width.saturating_sub(dlg_w)) / 2; + let y = parent.y + (parent.height.saturating_sub(dlg_h)) / 2; + let canvas = Rect { x, y, width: dlg_w, height: dlg_h }; + + Clear.render(canvas, buf); + + let grad = blend_2d(canvas.width as usize, canvas.height as usize, 13.0, &[palette::GRAY_0, palette::PROCESSING_GLOW] as &[Color]); + for row in 0..canvas.height { + for col in 0..canvas.width { + let idx = row as usize * canvas.width as usize + col as usize; + if let Some(cell) = buf.cell_mut(Position::new(canvas.x + col, canvas.y + row)) { + cell.set_bg(grad[idx]); + } + } + } + + let block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(Style::default().fg(palette::PROCESSING_GLOW)) + .style(Style::default()); + let inner = block.inner(canvas); + block.render(canvas, buf); + + let title_style = TitleStyle::cyberpunk(palette::PROCESSING_GLOW); + title::overlay_gradient_title( + buf, + canvas, + &title_style, + &[ + TitleSegment { text: " Master ".into(), bg: palette::PROCESSING_GLOW, fg: palette::WHITE, modifier: Modifier::empty() }, + TitleSegment { text: " Setup ".into(), bg: palette::PROCESSING_HEAT, fg: palette::WHITE, modifier: Modifier::empty() }, + ], + ); + + let msg = if progress.message.is_empty() { "Preparing setup..." } else { &progress.message }; + buf.set_string(inner.x + 1, inner.y + 1, truncate_str(msg, inner.width.saturating_sub(2) as usize), Style::default().fg(palette::FG)); + + let bar_y = inner.y + 3; + let bar_w = inner.width.saturating_sub(2); + let filled = (bar_w as usize * progress.current).checked_div(progress.total.max(1)).map(|v| v as u16).unwrap_or(0); + if filled > 0 { + buf.set_string(inner.x + 1, bar_y, "█".repeat(filled as usize), Style::default().fg(palette::PROCESSING_PEAK)); + } + if filled < bar_w { + buf.set_string(inner.x + 1 + filled, bar_y, "─".repeat((bar_w - filled) as usize), Style::default().fg(palette::MUTED)); + } + + let pct = (progress.current * 100).checked_div(progress.total.max(1)).map(|p| format!("{p}%")).unwrap_or_else(|| "0%".into()); + let pct_x = inner.x + (inner.width.saturating_sub(pct.len() as u16)) / 2; + buf.set_string(pct_x, inner.y + 4, &pct, Style::default().fg(palette::FG).add_modifier(Modifier::BOLD)); +} + +impl SetupRequest { + fn total_progress_steps(entry_count: usize) -> usize { + entry_count.saturating_add(1) + } + + fn dist_root_from_current_exe() -> Result { + let exe = std::env::current_exe().map_err(|e| format!("Cannot locate sysinspect binary: {e}"))?; + exe.parent().and_then(Path::parent).map(Path::to_path_buf).ok_or_else(|| format!("Cannot resolve dist root from {}", exe.display())) + } + + fn dist_platform_arch_from_current_exe() -> Result<(String, String), String> { + let dist_root = Self::dist_root_from_current_exe()?; + let platform = dist_root + .parent() + .and_then(Path::file_name) + .and_then(|name| name.to_str()) + .map(str::to_string) + .ok_or_else(|| format!("Cannot resolve platform from {}", dist_root.display()))?; + let label = + dist_root.file_name().and_then(|name| name.to_str()).ok_or_else(|| format!("Cannot resolve dist label from {}", dist_root.display()))?; + let parts: Vec<&str> = label.split('-').collect(); + if parts.len() < 3 { + return Err(format!("Cannot resolve architecture from dist label {label}")); + } + let arch = parts[1..parts.len() - 1].join("-"); + if arch.is_empty() { + return Err(format!("Cannot resolve architecture from dist label {label}")); + } + Ok((platform, arch)) + } + + fn collect_dir_entries(source_root: &Path, dest_root: &Path, label_prefix: &str, entries: &mut Vec) -> Result<(), String> { + let read_dir = std::fs::read_dir(source_root).map_err(|e| format!("Cannot read {}: {e}", source_root.display()))?; + for entry in read_dir { + let entry = entry.map_err(|e| format!("Cannot read directory entry in {}: {e}", source_root.display()))?; + let path = entry.path(); + let name = entry.file_name(); + let dest = dest_root.join(&name); + let label = + if label_prefix.is_empty() { name.to_string_lossy().to_string() } else { format!("{label_prefix}/{}", name.to_string_lossy()) }; + if path.is_dir() { + Self::collect_dir_entries(&path, &dest, &label, entries)?; + } else if path.is_file() { + entries.push(SetupCopyEntry { source: path, destination: dest, label }); + } + } + Ok(()) + } + + fn collect_dist_entries(&self, root: &Path) -> Result, String> { + let mut entries = Vec::new(); + let bin_dir = root.join("bin"); + let sysmaster_src = PathBuf::from(&self.sysmaster_path); + let sysinspect_src = std::env::current_exe().map_err(|e| format!("Cannot locate sysinspect binary: {e}"))?; + entries.push(SetupCopyEntry { source: sysmaster_src, destination: bin_dir.join("sysmaster"), label: "bin/sysmaster".to_string() }); + entries.push(SetupCopyEntry { source: sysinspect_src, destination: bin_dir.join("sysinspect"), label: "bin/sysinspect".to_string() }); + + let dist_root = Self::dist_root_from_current_exe()?; + let (platform, arch) = Self::dist_platform_arch_from_current_exe()?; + let payload_root = root.join("dist").join(&platform).join(&arch); + let minion_src = dist_root.join("bin/sysminion"); + if minion_src.exists() { + entries.push(SetupCopyEntry { + source: minion_src, + destination: payload_root.join("platform").join("sysminion"), + label: format!("dist/{platform}/{arch}/platform/sysminion"), + }); + } + + let models_src = dist_root.join("models"); + if models_src.exists() { + Self::collect_dir_entries(&models_src, &payload_root.join("models"), &format!("dist/{platform}/{arch}/models"), &mut entries)?; + } + + let modules_src = dist_root.join("modules"); + if modules_src.exists() { + Self::collect_dir_entries(&modules_src, &payload_root.join("modules"), &format!("dist/{platform}/{arch}/modules"), &mut entries)?; + } + + Ok(entries) + } + + fn prepare_copy_entries(&self) -> Result, String> { + let root = self.root_dir(); - // Create root and subdirs std::fs::create_dir_all(&root).map_err(|e| format!("Cannot create root dir: {e}"))?; let telemetry_dir = root.join("telemetry"); let datastore_dir = root.join("datastore"); @@ -536,25 +745,141 @@ impl MasterSetupWizard { std::fs::create_dir_all(&datastore_dir).map_err(|e| format!("Cannot create datastore dir: {e}"))?; std::fs::create_dir_all(&log_dir).map_err(|e| format!("Cannot create log dir: {e}"))?; - // Determine config path and pre-create bin/ for self-contained layouts let is_system = matches!(self.installation_mode, InstallationMode::SystemWide); let config_dir = if is_system { root.clone() } else { root.join("etc") }; std::fs::create_dir_all(&config_dir).map_err(|e| format!("Cannot create config dir: {e}"))?; + + if is_system { Ok(Vec::new()) } else { self.collect_dist_entries(&root) } + } + + fn apply_copy_entries(entries: &[SetupCopyEntry], progress: Option<&Arc>>) -> Result<(), String> { + if let Some(progress) = progress + && let Ok(mut state) = progress.lock() + { + state.current = 0; + state.total = Self::total_progress_steps(entries.len()); + state.message = + if let Some(first) = entries.first() { format!("Copying {}...", first.label) } else { "Writing sysinspect.conf...".to_string() }; + } + + for (idx, entry) in entries.iter().enumerate() { + if let Some(parent) = entry.destination.parent() { + std::fs::create_dir_all(parent).map_err(|e| format!("Cannot create {}: {e}", parent.display()))?; + } + std::fs::copy(&entry.source, &entry.destination) + .map_err(|e| format!("Cannot copy {} to {}: {e}", entry.source.display(), entry.destination.display()))?; + if let Some(progress) = progress + && let Ok(mut state) = progress.lock() + { + state.current = idx + 1; + state.total = Self::total_progress_steps(entries.len()); + state.message = + if idx + 1 < entries.len() { format!("Copying {}...", entries[idx + 1].label) } else { "Writing sysinspect.conf...".to_string() }; + } + } + + Ok(()) + } + + pub fn run_with_progress(self, progress: Arc>) -> Result { + self.run(Some(progress)) + } + + fn root_dir(&self) -> PathBuf { + match self.installation_mode { + InstallationMode::SystemWide => std::path::PathBuf::from("/etc/sysinspect"), + InstallationMode::Custom => std::path::PathBuf::from(&self.custom_destination), + } + } + + fn installed_sysinspect_path(&self) -> String { + if self.installation_mode == InstallationMode::Custom { + self.root_dir().join("bin/sysinspect").display().to_string() + } else { + "sysinspect".to_string() + } + } + + fn completion_message(&self) -> (String, Text<'static>) { + let root = self.root_dir().display().to_string(); + let command = format!("{} --ui", self.installed_sysinspect_path()); + + let plain = if self.installation_mode == InstallationMode::Custom { + format!( + "Master installation files were written successfully. Now please quit\nthis UI and go to the installation target at:\n\n {root}\n\nThen run the following command:\n\n {command}\n\nThat new instance will then ask whether to start master in daemon mode." + ) + } else { + format!( + "Master installation files were written successfully. Now please quit\nthis UI.\n\nThen run the following command:\n\n {command}\n\nThat new instance will then ask whether to start master in daemon mode." + ) + }; + + let styled = if self.installation_mode == InstallationMode::Custom { + Text::from(vec![ + Line::from(""), + Line::from(vec![Span::styled( + "Master installation files were written successfully. Now please quit", + Style::default().fg(palette::FG), + )]), + Line::from(vec![Span::styled("this UI and go to the installation target at:", Style::default().fg(palette::FG))]), + Line::from(""), + Line::from(vec![Span::styled(format!(" {root}"), Style::default().fg(palette::PRIMARY))]), + Line::from(""), + Line::from(vec![Span::styled("Then run the following command:", Style::default().fg(palette::FG))]), + Line::from(""), + Line::from(vec![Span::styled(format!(" {command}"), Style::default().fg(palette::PRIMARY))]), + Line::from(""), + Line::from(vec![Span::styled( + "That new instance will then ask whether to start master in daemon mode.", + Style::default().fg(palette::FG), + )]), + ]) + } else { + Text::from(vec![ + Line::from(""), + Line::from(vec![Span::styled( + "Master installation files were written successfully. Now please quit", + Style::default().fg(palette::FG), + )]), + Line::from(vec![Span::styled("this UI.", Style::default().fg(palette::FG))]), + Line::from(""), + Line::from(vec![Span::styled("Then run the following command:", Style::default().fg(palette::FG))]), + Line::from(""), + Line::from(vec![Span::styled(format!(" {command}"), Style::default().fg(palette::PRIMARY))]), + Line::from(""), + Line::from(vec![Span::styled( + "That new instance will then ask whether to start master in daemon mode.", + Style::default().fg(palette::FG), + )]), + ]) + }; + + (plain, styled) + } + + fn run(self, progress: Option>>) -> Result { + let root = self.root_dir(); + let entries = self.prepare_copy_entries()?; + + if let Some(progress) = progress.as_ref() + && let Ok(mut state) = progress.lock() + { + state.current = 0; + state.total = Self::total_progress_steps(entries.len()); + state.message = + if let Some(first) = entries.first() { format!("Copying {}...", first.label) } else { "Writing sysinspect.conf...".to_string() }; + } + + let is_system = matches!(self.installation_mode, InstallationMode::SystemWide); + let config_dir = if is_system { root.clone() } else { root.join("etc") }; if !is_system { - let bin_dir = root.join("bin"); - std::fs::create_dir_all(&bin_dir).map_err(|e| format!("Cannot create bin dir: {e}"))?; - let src = std::path::PathBuf::from(self.sysmaster_path.value()); - let dest = bin_dir.join("sysmaster"); - std::fs::copy(&src, &dest).map_err(|e| format!("Cannot copy sysmaster to {}: {e}", dest.display()))?; - let self_src = std::env::current_exe().map_err(|e| format!("Cannot locate sysinspect binary: {e}"))?; - let self_dest = bin_dir.join("sysinspect"); - std::fs::copy(&self_src, &self_dest).map_err(|e| format!("Cannot copy sysinspect to {}: {e}", self_dest.display()))?; + Self::apply_copy_entries(&entries, progress.as_ref())?; } let config_path = config_dir.join("sysinspect.conf"); - let bind_addr = self.bind_addr.value(); - let bind_port: u32 = self.bind_port.value().parse().map_err(|_| "Invalid bind port".to_string())?; - let fs_port: u32 = self.fs_port.value().parse().map_err(|_| "Invalid fileserver port".to_string())?; + let bind_addr = &self.bind_addr; + let bind_port: u32 = self.bind_port.parse().map_err(|_| "Invalid bind port".to_string())?; + let fs_port: u32 = self.fs_port.parse().map_err(|_| "Invalid fileserver port".to_string())?; let partial = format!( "root: \"{}\"\nbind.ip: \"{}\"\nbind.port: {}\nfileserver.bind.ip: \"{}\"\nfileserver.bind.port: {}\nfileserver.models: []\napi.enabled: {}\nlog.stream: \"{}/log/sysmaster.standard.log\"\nlog.errors: \"{}/log/sysmaster.errors.log\"\n", @@ -570,7 +895,37 @@ impl MasterSetupWizard { let master_cfg: MasterConfig = serde_yaml::from_str(&partial).map_err(|e| format!("Cannot construct config: {e}"))?; let yaml = SysInspectConfig::default().set_master_config(master_cfg).to_yaml(); std::fs::write(&config_path, yaml).map_err(|e| format!("Cannot write config: {e}"))?; + if let Some(progress) = progress.as_ref() + && let Ok(mut state) = progress.lock() + { + state.current = state.total; + state.message = "Setup complete".to_string(); + } + + let (info_message, info_styled) = self.completion_message(); + + Ok(SetupCompletion { info_message, info_styled }) + } +} - Ok(config_path) +impl MasterSetupWizard { + fn root_dir_for_write_config(&self) -> PathBuf { + match self.installation_mode { + InstallationMode::SystemWide => PathBuf::from("/etc/sysinspect"), + InstallationMode::Custom => PathBuf::from(self.custom_destination.value()), + } + } +} + +fn truncate_str(s: &str, max: usize) -> String { + let count = s.chars().count(); + if count <= max { + return s.to_string(); + } + if max <= 1 { + return "…".to_string(); } + let mut out = s.chars().take(max - 1).collect::(); + out.push('…'); + out } diff --git a/src/ui/statusbar.rs b/src/ui/statusbar.rs index b3334b66..cfd6e8a3 100644 --- a/src/ui/statusbar.rs +++ b/src/ui/statusbar.rs @@ -197,6 +197,11 @@ impl SysInspectUX { } pub(crate) fn status_at_repo_manager(&mut self) { + let p = &self.repo_manager.profiles; + if p.detail_visible || p.create_visible || p.delete_visible || p.assign.visible { + self.status_at_profiles(); + return; + } let key = |s| Span::styled(s, Style::default().fg(palette::FG)); let desc = |s| Span::styled(s, Style::default().fg(palette::FAINT)); self.status_text = Line::from(vec![ @@ -225,7 +230,18 @@ impl SysInspectUX { } else if p.create_visible { self.status_text = Line::from(vec![key("Tab "), desc("switch "), key("Enter "), desc("create "), key("Esc "), desc("cancel")]); } else if p.detail_visible { - self.status_text = Line::from(vec![key("Tab "), desc("switch section "), key("d/Del "), desc("remove "), key("Esc "), desc("close")]); + self.status_text = Line::from(vec![ + key("Tab "), + desc("switch section "), + key("\u{2190}/\u{2192} "), + desc("all modules/per model "), + key("+/- "), + desc("expand/collapse "), + key("Enter "), + desc("toggle "), + key("Esc "), + desc("close"), + ]); } else { self.status_text = Line::from(vec![ key("\u{2191}\u{2193} "), diff --git a/src/ui/wgt.rs b/src/ui/wgt.rs index 91d27db2..787e5580 100644 --- a/src/ui/wgt.rs +++ b/src/ui/wgt.rs @@ -1,7 +1,7 @@ use super::{ SysInspectUX, UISizes, elements::{ActiveBox, DbListItem, EventListItem}, - minreg, palette, title, + minreg, palette, setup, title, title::{TitleSegment, TitleStyle}, typecolors, }; @@ -436,6 +436,9 @@ impl Widget for &SysInspectUX { if !self.error_alert_visible && !self.file_picker.visible { self.setup_wizard.render(area, buf); } + if let Ok(progress) = self.setup_progress.lock() { + setup::render_progress(&progress, area, buf); + } self.registration_form.render(area, buf); if let Ok(p) = self.registration_progress.lock() { minreg::render_progress(&p, area, buf); diff --git a/sysmaster/Cargo.toml b/sysmaster/Cargo.toml index a1f00a6c..191f33b5 100644 --- a/sysmaster/Cargo.toml +++ b/sysmaster/Cargo.toml @@ -47,6 +47,9 @@ indexmap = { version = "2.14.0", features = ["serde"] } chrono = { version = "0.4.44", features = ["serde"] } async-trait = "0.1.89" globset = "0.4.18" +filescream = { git = "https://github.com/tinythings/omnitrace.git", package = "filescream" } +omnitrace-core = { git = "https://github.com/tinythings/omnitrace.git", package = "omnitrace-core" } +tokio-util = "0.7" [dev-dependencies] tempfile = "3.27.0" diff --git a/sysmaster/src/console.rs b/sysmaster/src/console.rs index 9e6507b1..dd6273a7 100644 --- a/sysmaster/src/console.rs +++ b/sysmaster/src/console.rs @@ -578,6 +578,7 @@ impl SysMaster { public_entrypoints, public_entrypoint_kinds, public_actions: m.public_actions.clone(), + modules: m.modules.clone(), states: m.states.clone(), target_actions, } @@ -1637,7 +1638,7 @@ impl SysMaster { items: if request.name().is_empty() { repo.list_profiles(None)? } else { - repo.list_profile_matches(Some(request.name()), request.library())? + repo.list_profile_matches(Some(request.name()), request.library(), request.model())? }, }), vec![], @@ -1652,7 +1653,7 @@ impl SysMaster { "add" => Ok(( { Self::require_profile_name(request)?; - repo.add_profile_matches(request.name(), request.matches().to_vec(), request.library())?; + repo.add_profile_matches(request.name(), request.matches().to_vec(), request.library(), request.model())?; ConsoleResponse::ok(ConsolePayload::Ack { action: "update_profile".to_string(), target: request.name().to_string(), @@ -1665,7 +1666,7 @@ impl SysMaster { "remove" => Ok(( { Self::require_profile_name(request)?; - repo.remove_profile_matches(request.name(), request.matches().to_vec(), request.library())?; + repo.remove_profile_matches(request.name(), request.matches().to_vec(), request.library(), request.model())?; ConsoleResponse::ok(ConsolePayload::Ack { action: "update_profile".to_string(), target: request.name().to_string(), diff --git a/sysmaster/src/master.rs b/sysmaster/src/master.rs index 1118bd24..9a8fc393 100644 --- a/sysmaster/src/master.rs +++ b/sysmaster/src/master.rs @@ -14,7 +14,10 @@ use crate::{ telemetry::{otel::OtelLogger, rds::FunctionReducer}, transport::{IncomingFrame, OutgoingFrame, PeerTransport}, }; +use async_trait::async_trait; use colored::Colorize; +use filescream::FileScream; +use filescream::events::{FileScreamEvent, FileScreamMask}; use indexmap::IndexMap; use libcommon::SysinspectError; use libdatastore::{cfg::DataStorageConfig, resources::DataStorage}; @@ -48,9 +51,12 @@ use libsysproto::{ rqtypes::{ProtoKey, ProtoValue, RequestType}, secure::SECURE_PROTOCOL_VERSION, }; +use omnitrace_core::callbacks::{Callback, CallbackHub}; +use omnitrace_core::sensor::SensorCtx; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_json::json; +use std::path::Path; use std::time::Duration as StdDuration; use std::{ collections::{HashMap, HashSet}, @@ -67,6 +73,7 @@ use tokio::sync::Mutex; use tokio::sync::{broadcast, mpsc, oneshot}; use tokio::time; use tokio::time::{Duration, sleep}; +use tokio_util::sync::CancellationToken; // Session singleton pub static SHARED_SESSION: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(SessionKeeper::new(30)))); @@ -142,9 +149,56 @@ pub struct SysMaster { vmcluster: VirtualMinionsCluster, conn_to_mid: HashMap, // Map connection addresses to minion IDs peer_direct_tx: HashMap>, + peer_cancel_tx: HashMap>, pending_console_replies: HashMap>, peer_transport: PeerTransport, datastore: Arc>, + model_watcher_token: Option, +} + +fn model_id_from_path(path: &Path) -> Option<&str> { + if path.file_name()?.to_str()? != "model.cfg" { + return None; + } + path.parent()?.file_name()?.to_str() +} + +struct ModelChangeCallback { + broadcast: tokio::sync::broadcast::Sender, +} + +#[async_trait] +impl Callback for ModelChangeCallback { + fn mask(&self) -> u64 { + FileScreamMask::CREATED.bits() | FileScreamMask::CHANGED.bits() | FileScreamMask::REMOVED.bits() + } + + async fn call(&self, event: &FileScreamEvent) -> Option { + let path = match event { + FileScreamEvent::Created { path } | FileScreamEvent::Changed { path } => path, + FileScreamEvent::Removed { path } => { + if let Some(id) = model_id_from_path(path) { + let mut msg = MasterMessage::new(RequestType::ModelRemoved, json!({"id": id})); + msg.set_target(MinionTarget::new("*", "")); + let _ = self.broadcast.send(msg); + } + return None; + } + }; + let model_id = model_id_from_path(path).map(|s| s.to_string())?; + let broadcast = self.broadcast.clone(); + let path = path.clone(); + tokio::spawn(async move { + MODEL_CACHE.lock().await.remove(&path); + if let Ok(reducer) = FunctionReducer::new(path, model_id.clone()).load_model(&MODEL_CACHE).await { + drop(reducer); + let mut msg = MasterMessage::new(RequestType::ModelUpdated, json!({"id": model_id})); + msg.set_target(MinionTarget::new("*", "")); + let _ = broadcast.send(msg); + } + }); + None + } } impl SysMaster { @@ -204,9 +258,11 @@ impl SysMaster { vmcluster, conn_to_mid: HashMap::new(), peer_direct_tx: HashMap::new(), + peer_cancel_tx: HashMap::new(), pending_console_replies: HashMap::new(), peer_transport: PeerTransport::new(), datastore: Arc::new(Mutex::new(DataStorage::new(ds_cfg, ds_path)?)), + model_watcher_token: None, }) } @@ -314,9 +370,24 @@ impl SysMaster { } self.backfill_cmdb().await?; self.vmcluster.init().await?; + self.watch_models(); Ok(()) } + fn watch_models(&mut self) { + if let Some(token) = self.model_watcher_token.take() { + token.cancel(); + } + let cancel = CancellationToken::new(); + self.model_watcher_token = Some(cancel.clone()); + let mut hub = CallbackHub::::new(); + hub.add(ModelChangeCallback { broadcast: self.broadcast.clone() }); + let ctx = SensorCtx { cancel, hub: Arc::new(hub) }; + let mut fs = FileScream::new(None); + fs.watch(self.cfg.fileserver_root().join(CFG_MODELS_ROOT)); + tokio::spawn(async move { fs.run(ctx).await }); + } + pub fn cfg(&self) -> MasterConfig { self.cfg.to_owned() } @@ -329,6 +400,7 @@ impl SysMaster { let path = self.cfg.config_path(); log::info!("Reloading master configuration from {}", path.display()); self.cfg = MasterConfig::new(path)?; + self.watch_models(); log::info!("Master configuration reloaded successfully"); Ok(()) } @@ -421,6 +493,10 @@ impl SysMaster { } self.get_session().lock().await.remove(minion_id); self.peer_transport.remove_peer(peer_addr); + self.peer_direct_tx.remove(peer_addr); + if let Some(cancel) = self.peer_cancel_tx.remove(peer_addr) { + let _ = cancel.send(true); + } } /// Drop one existing runtime session for a reconnecting minion when the supervisor reuses the same sid. @@ -429,9 +505,17 @@ impl SysMaster { log::warn!("Replacing stale runtime session {} for minion {}", addr, minion_id); self.conn_to_mid.remove(&addr); self.peer_transport.remove_peer(&addr); + self.peer_direct_tx.remove(&addr); + if let Some(cancel) = self.peer_cancel_tx.remove(&addr) { + let _ = cancel.send(true); + } } else if let Some(addr) = self.peer_transport.peer_addr(minion_id, minion_addr) { log::warn!("Replacing stale pre-EHLO peer {} for minion {}", addr, minion_id); self.peer_transport.remove_peer(&addr); + self.peer_direct_tx.remove(&addr); + if let Some(cancel) = self.peer_cancel_tx.remove(&addr) { + let _ = cancel.send(true); + } } self.get_session().lock().await.remove(minion_id); } @@ -721,6 +805,10 @@ impl SysMaster { log::debug!("Disconnect from {}, but no minion id mapped yet", minion_addr); } self.peer_transport.remove_peer(minion_addr); + self.peer_direct_tx.remove(minion_addr); + if let Some(cancel) = self.peer_cancel_tx.remove(minion_addr) { + let _ = cancel.send(true); + } } /// Process a plaintext registration request and emit the one-shot registration response. @@ -781,6 +869,7 @@ impl SysMaster { if !self.mkr().is_registered(minion_id) { log::info!("Minion at {minion_addr} ({minion_id}) is not registered"); self.to_drop.insert(minion_addr.to_owned()); + self.peer_transport.allow_plaintext(minion_addr); _ = bcast.send(self.msg_not_registered(minion_id.to_string())); return; } @@ -843,6 +932,10 @@ impl SysMaster { self.conn_to_mid.remove(minion_addr); self.get_session().lock().await.remove(minion_id); self.peer_transport.remove_peer(minion_addr); + self.peer_direct_tx.remove(minion_addr); + if let Some(cancel) = self.peer_cancel_tx.remove(minion_addr) { + let _ = cancel.send(true); + } _ = bcast.send(self.msg_bye_ack(minion_id.to_string(), payload.to_string())); } @@ -1532,24 +1625,29 @@ impl SysMaster { /// Read framed peer traffic, decode it through the peer transport object, and forward logical messages inward. async fn read_peer_frames( master: Arc>, reader: OwnedReadHalf, peer_addr: String, client_tx: mpsc::Sender<(Vec, String)>, - direct_tx: mpsc::Sender, cancel_tx: tokio::sync::watch::Sender, cancel_rx: tokio::sync::watch::Receiver, + direct_tx: mpsc::Sender, cancel_tx: tokio::sync::watch::Sender, mut cancel_rx: tokio::sync::watch::Receiver, ) { let mut reader = TokioBufReader::new(reader); loop { - if *cancel_rx.borrow() { - log::info!("Process terminated"); - return; - } - let mut len_buf = [0u8; 4]; - if reader.read_exact(&mut len_buf).await.is_err() { + if tokio::select! { + _ = cancel_rx.changed() => { return; } + result = reader.read_exact(&mut len_buf) => result, + } + .is_err() + { let _ = client_tx.send((Vec::new(), peer_addr.clone())).await; return; } let msg_len = u32::from_be_bytes(len_buf) as usize; let mut msg = vec![0u8; msg_len]; - if reader.read_exact(&mut msg).await.is_err() { + if tokio::select! { + _ = cancel_rx.changed() => { return; } + result = reader.read_exact(&mut msg) => result, + } + .is_err() + { let _ = client_tx.send((Vec::new(), peer_addr.clone())).await; return; } @@ -1594,6 +1692,9 @@ impl SysMaster { } let (cancel_tx, cancel_rx) = tokio::sync::watch::channel(false); let cancel_writer = cancel_tx.clone(); + { + master.lock().await.peer_cancel_tx.insert(peer_addr.clone(), cancel_tx.clone()); + } tokio::spawn(async move { Self::write_peer_frames(c_master_writer, writer, writer_peer_addr, bcast_sub, direct_rx, cancel_writer).await; @@ -1612,10 +1713,13 @@ impl SysMaster { loop { tokio::select! { - // Accept a new connection Ok((socket, _)) = listener.accept() => { Self::handle_peer_connection(Arc::clone(&master), tx.clone(), &bcast, socket).await; } + else => { + log::error!("Listener accept error, sleeping 1 s before retry"); + tokio::time::sleep(Duration::from_secs(1)).await; + } } } }); diff --git a/sysminion/src/minion.rs b/sysminion/src/minion.rs index bcaf1afc..2913ef1a 100644 --- a/sysminion/src/minion.rs +++ b/sysminion/src/minion.rs @@ -15,7 +15,7 @@ use colored::Colorize; use indexmap::IndexMap; use libcommon::SysinspectError; use libdpq::{DiskPersistentQueue, WorkItem}; -use libmodpak::{MODPAK_SYNC_STATE, SysInspectModPakMinion}; +use libmodpak::{MODPAK_SYNC_STATE, SysInspectModPakMinion, mpk::ModPakProfile}; use libsensors::sensors::SensorCtx; use libsensors::sensors::menotify::MeNotifySensor; use libsensors::service::SensorService; @@ -78,7 +78,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use serde_yaml::Value as YamlValue; use std::{ - collections::VecDeque, + collections::{HashMap, VecDeque}, fs, os::unix::fs::PermissionsExt, path::{Path, PathBuf}, @@ -173,6 +173,7 @@ impl BacklogSnapshot { /// Session Id of the minion pub static MINION_SID: Lazy = Lazy::new(|| Uuid::new_v4().to_string()); +static PROFILE_MODELS_CACHE: Lazy>>> = Lazy::new(|| Mutex::new(HashMap::new())); fn emit_reconnect_signal() { #[cfg(test)] @@ -1209,6 +1210,11 @@ impl SysMinion { log::debug!("Dropped internal master command for another minion"); } } else { + let model_id = msg.target().scheme().split('/').next().unwrap_or_default(); + if !model_id.is_empty() && !this.is_model_allowed_in_profiles(model_id).await { + log::debug!("Dropped command for model {}: not in any assigned profile", model_id); + continue; + } if !matches_target(&msg, this.get_minion_id(), &minion_traits(&this.cfg, true, false)) { log::debug!("Dropped master model command for another minion"); continue; @@ -1360,6 +1366,9 @@ impl SysMinion { } } + // Models are fetched fresh per-command via download_file(); no local invalidation needed. + RequestType::ModelUpdated | RequestType::ModelRemoved => {} + _ => log::error!("Unknown request type"), } } @@ -1798,6 +1807,33 @@ impl SysMinion { Err(SysinspectError::MinionGeneralError("File was not downloaded".to_string())) } + async fn is_model_allowed_in_profiles(self: &Arc, model_id: &str) -> bool { + let profiles = effective_profiles(&self.cfg); + // Unassigned minions get the synthetic "default" entry only. + if profiles.len() == 1 && profiles[0] == "default" { + return true; + } + for profile_name in profiles { + if let Some(cached) = PROFILE_MODELS_CACHE.lock().await.get(&profile_name) { + if cached.contains(&model_id.to_string()) { + return true; + } + continue; + } + if let Ok(data) = self.as_ptr().download_file(&format!("/profiles/{profile_name}.profile")).await + && let Ok(profile) = serde_yaml::from_slice::(&data) + { + let models: Vec = profile.models().to_vec(); + let allowed = models.contains(&model_id.to_string()); + PROFILE_MODELS_CACHE.lock().await.insert(profile_name, models); + if allowed { + return true; + } + } + } + false + } + /// Launch sysinspect async fn launch_sysinspect(self: Arc, cycle_id: &str, scheme: &str, msp: &ModStatePayload, context: &str) { async fn emit_execution_failure(minion: Arc, cycle_id: &str, scheme: &str, context: &str, phase: &str, error: &str) {