From 76c7f9749a3b9e987cfd9288f94be91f0574a3c2 Mon Sep 17 00:00:00 2001 From: Raunak Kumar Date: Mon, 23 Feb 2026 18:17:42 +0000 Subject: [PATCH] feat: add live Bitcoin Core status dashboard and JSON-RPC client --- Cargo.lock | 1624 ++++++++++++++++- Cargo.toml | 3 + src/app.rs | 10 +- src/components/metrics.rs | 69 + src/components/mod.rs | 1 + src/config.rs | 74 +- src/main.rs | 161 +- src/snapshots/pdm__tests__home_screen.snap | 2 +- src/snapshots/pdm__tests__menu_toggled.snap | 2 +- src/ui.rs | 51 +- .../ui_snapshots__config_screen_render.snap | 2 +- .../ui_snapshots__home_screen_render.snap | 2 +- 12 files changed, 1893 insertions(+), 108 deletions(-) create mode 100644 src/components/metrics.rs diff --git a/Cargo.lock b/Cargo.lock index bc839fb..cae6a59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,11 +31,45 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "aws-lc-rs" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -49,6 +83,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "cassowary" version = "0.3.0" @@ -64,12 +110,55 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -154,6 +243,32 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -289,6 +404,17 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -307,6 +433,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.15.0" @@ -361,6 +493,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fnv" version = "1.0.7" @@ -373,6 +511,60 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -390,8 +582,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -401,9 +595,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -423,6 +638,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hashlink" version = "0.10.0" @@ -438,12 +659,226 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + [[package]] name = "indoc" version = "2.0.7" @@ -477,6 +912,22 @@ dependencies = [ "syn", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itertools" version = "0.13.0" @@ -492,6 +943,48 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "json5" version = "0.4.1" @@ -521,6 +1014,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "litrs" version = "1.0.0" @@ -551,12 +1050,24 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mio" version = "1.1.1" @@ -575,6 +1086,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -629,9 +1146,18 @@ dependencies = [ "crossterm 0.29.0", "insta", "ratatui", + "reqwest", + "serde_json", "tempfile", + "tokio", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pest" version = "2.8.5" @@ -675,6 +1201,36 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -684,6 +1240,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.42" @@ -699,6 +1311,35 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -729,6 +1370,60 @@ dependencies = [ "bitflags", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "ron" version = "0.12.0" @@ -753,6 +1448,12 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -788,6 +1489,81 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -800,12 +1576,53 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -819,6 +1636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -886,6 +1704,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.18" @@ -922,12 +1746,34 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "static_assertions" version = "1.1.0" @@ -962,6 +1808,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.111" @@ -973,6 +1825,47 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.23.0" @@ -987,104 +1880,333 @@ dependencies = [ ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "crunchy", + "thiserror-impl 1.0.69", ] [[package]] -name = "toml" -version = "0.9.10+spec-1.1.0" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "winnow", + "thiserror-impl 2.0.18", ] [[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "serde_core", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "toml_parser" -version = "1.0.6+spec-1.1.0" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ - "winnow", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "typeid" -version = "1.0.3" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] [[package]] -name = "typenum" -version = "1.19.0" +name = "tinystr" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] -name = "ucd-trie" -version = "0.1.7" +name = "tinyvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] [[package]] -name = "unicode-ident" -version = "1.0.22" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "unicode-segmentation" -version = "1.12.0" +name = "tokio" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] [[package]] -name = "unicode-truncate" -version = "1.1.0" +name = "tokio-macros" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width 0.1.14", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unicode-width" -version = "0.1.14" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] [[package]] -name = "unicode-width" -version = "0.2.0" +name = "tokio-util" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] [[package]] -name = "version_check" +name = "toml" +version = "0.9.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1100,6 +2222,94 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1116,6 +2326,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1128,13 +2347,69 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -1146,70 +2421,192 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.14" @@ -1225,6 +2622,12 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "yaml-rust2" version = "0.10.4" @@ -1236,6 +2639,109 @@ dependencies = [ "hashlink", ] +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 0acfed8..0b1ac10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ anyhow = "1.0.100" config = "0.15.19" crossterm = "0.29.0" ratatui = "0.29.0" +tokio = { version = "1.49.0", features = ["full"] } +reqwest = { version = "0.13.2", features = ["json"] } +serde_json = "1.0.149" [dev-dependencies] insta = "1.44.3" diff --git a/src/app.rs b/src/app.rs index 62bf6fd..ec8cfad 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,11 +3,14 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use crate::components::file_explorer::FileExplorer; +use crate::components::metrics::BitcoinMetrics; +use crate::config::BitcoinConfig; use std::path::PathBuf; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CurrentScreen { Home, + BitcoinStatus, BitcoinConfig, FileExplorer, Exiting, @@ -18,6 +21,8 @@ pub struct App { pub sidebar_index: usize, pub bitcoin_conf_path: Option, pub explorer: FileExplorer, + pub bitcoin_config: Option, + pub bitcoin_metrics: Option, } impl App { @@ -27,6 +32,8 @@ impl App { sidebar_index: 0, bitcoin_conf_path: None, explorer: FileExplorer::new(), + bitcoin_config: None, + bitcoin_metrics: None, } } @@ -35,6 +42,7 @@ impl App { match self.sidebar_index { 0 => self.current_screen = CurrentScreen::Home, 1 => self.current_screen = CurrentScreen::BitcoinConfig, + 2 => self.current_screen = CurrentScreen::BitcoinStatus, _ => {} } } diff --git a/src/components/metrics.rs b/src/components/metrics.rs new file mode 100644 index 0000000..daa4441 --- /dev/null +++ b/src/components/metrics.rs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2024 PDM Authors +// SPDX-License-Identifier: AGPL-3.0-or-later + +use reqwest::Client; +use serde_json::{Value, json}; + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct BitcoinMetrics { + pub blocks: u64, + pub headers: u64, + pub connections: u32, + pub verification_progress: f64, + pub chain: String, +} + +impl BitcoinMetrics { + /// Asynchronously fetches and parses metrics from a Bitcoin Core RPC node + pub async fn fetch(client: &Client, url: &str, user: &str, pass: &str) -> anyhow::Result { + // Fetch Blockchain Info (Blocks, Headers, Sync Progress, Chain) + let body_chain = json!({ + "jsonrpc": "1.0", + "id": "pdm", + "method": "getblockchaininfo", + "params": [] + }); + + let res_chain = client + .post(url) + .basic_auth(user, Some(pass)) + .json(&body_chain) + .send() + .await? + .json::() + .await?; + + let result = &res_chain["result"]; + let blocks = result["blocks"].as_u64().unwrap_or(0); + let headers = result["headers"].as_u64().unwrap_or(0); + let verification_progress = result["verificationprogress"].as_f64().unwrap_or(0.0); + let chain = result["chain"].as_str().unwrap_or("unknown").to_string(); + + // Fetch Network Info (Peer Connections) + let body_net = json!({ + "jsonrpc": "1.0", + "id": "pdm", + "method": "getnetworkinfo", + "params": [] + }); + + let res_net = client + .post(url) + .basic_auth(user, Some(pass)) + .json(&body_net) + .send() + .await? + .json::() + .await?; + + let connections = res_net["result"]["connections"].as_u64().unwrap_or(0) as u32; + + Ok(BitcoinMetrics { + blocks, + headers, + connections, + verification_progress, + chain, + }) + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 6d3b562..c6266a8 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -3,3 +3,4 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pub mod file_explorer; +pub mod metrics; diff --git a/src/config.rs b/src/config.rs index a9da6fe..feae689 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,7 @@ use config::{Config, File, FileFormat}; use std::{collections::HashSet, path::Path}; /// Core Config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Core { // Data directory and storage pub datadir: Option, @@ -55,7 +55,7 @@ pub struct Core { } /// Network Config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Network { // Chain selection pub chain: Option, @@ -128,7 +128,7 @@ pub struct Network { } /// RPC Config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct RPC { // Server enable pub server: Option, @@ -159,7 +159,7 @@ pub struct RPC { } /// Wallet related config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Wallet { // Enable/disable pub disablewallet: Option, @@ -200,7 +200,7 @@ pub struct Wallet { } /// Debugging related config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Debugging { // Debug categories pub debug: Option, @@ -222,7 +222,7 @@ pub struct Debugging { } /// Mining related config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Mining { // Block creation pub blockmaxweight: Option, @@ -230,7 +230,7 @@ pub struct Mining { } /// Relay related config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Relay { // Relay fees pub minrelaytxfee: Option, @@ -248,7 +248,7 @@ pub struct Relay { } /// ZMQ related config -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ZMQ { // Hash notifications pub zmqpubhashblock: Option, @@ -262,7 +262,7 @@ pub struct ZMQ { pub zmqpubsequence: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct BitcoinConfig { pub core: Core, pub network: Network, @@ -275,8 +275,9 @@ pub struct BitcoinConfig { } /// Type of a configuration option value -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum ConfigType { + #[default] Bool, Int, Float, @@ -286,8 +287,9 @@ pub enum ConfigType { } /// Category of a configuration option -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum ConfigCategory { + #[default] Core, Network, RPC, @@ -299,7 +301,7 @@ pub enum ConfigCategory { } /// Schema for a single configuration option -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ConfigSchema { pub key: String, pub default: String, @@ -327,7 +329,7 @@ impl ConfigSchema { } /// A parsed configuration entry -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ConfigEntry { pub key: String, pub value: String, @@ -1232,12 +1234,15 @@ pub fn get_default_schema() -> Vec { } /// Parse bitcoin.conf file -pub fn parse_config(path: &Path) -> Result> { +pub fn parse_config(path: &Path) -> Result<(BitcoinConfig, Vec)> { let schema_list = get_default_schema(); let mut entries = Vec::new(); let mut found_keys: HashSet = HashSet::new(); let mut builder = Config::builder(); + // Create an empty config to fill up + let mut typed_config = BitcoinConfig::default(); + if path.exists() { builder = builder.add_source(File::from(path).format(FileFormat::Ini)); } @@ -1254,7 +1259,7 @@ pub fn parse_config(path: &Path) -> Result> { enabled: false, }); } - return Ok(entries); + return Ok((typed_config, entries)); } }; @@ -1325,6 +1330,17 @@ pub fn parse_config(path: &Path) -> Result> { } } + if enabled { + match key.as_str() { + "rpcuser" => typed_config.rpc.rpcuser = Some(value.clone()), + "rpcpassword" => typed_config.rpc.rpcpassword = Some(value.clone()), + "rpcport" => typed_config.rpc.rpcport = value.parse::().ok(), + "rpcbind" => typed_config.rpc.rpcbind = Some(value.clone()), + "chain" => typed_config.network.chain = Some(value.clone()), + _ => {} + } + } + entries.push(ConfigEntry { key: key.clone(), value, @@ -1376,7 +1392,7 @@ pub fn parse_config(path: &Path) -> Result> { } } - Ok(entries) + Ok((typed_config, entries)) } #[cfg(test)] @@ -1507,7 +1523,7 @@ mod tests { #[test] fn parse_config_non_existent_file_returns_defaults() { let path = Path::new("/non/existent/path/bitcoin.conf"); - let entries = parse_config(path).unwrap(); + let (_, entries) = parse_config(path).unwrap(); assert!(!entries.is_empty()); @@ -1521,7 +1537,7 @@ mod tests { #[test] fn parse_config_empty_file_returns_defaults() { let (_dir, path) = create_temp_config(""); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); assert!(!entries.is_empty()); @@ -1533,7 +1549,7 @@ mod tests { #[test] fn parse_config_parses_bool_values() { let (_dir, path) = create_temp_config("txindex=1\nserver=0\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let txindex = entries.iter().find(|e| e.key == "txindex").unwrap(); assert_eq!(txindex.value, "1"); @@ -1547,7 +1563,7 @@ mod tests { #[test] fn parse_config_parses_int_values() { let (_dir, path) = create_temp_config("dbcache=1000\nport=8334\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let dbcache = entries.iter().find(|e| e.key == "dbcache").unwrap(); assert_eq!(dbcache.value, "1000"); @@ -1561,7 +1577,7 @@ mod tests { #[test] fn parse_config_parses_string_values() { let (_dir, path) = create_temp_config("rpcuser=myuser\nrpcpassword=mypassword\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let rpcuser = entries.iter().find(|e| e.key == "rpcuser").unwrap(); assert_eq!(rpcuser.value, "myuser"); @@ -1575,7 +1591,7 @@ mod tests { #[test] fn parse_config_parses_path_values() { let (_dir, path) = create_temp_config("datadir=/home/user/.bitcoin\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let datadir = entries.iter().find(|e| e.key == "datadir").unwrap(); assert_eq!(datadir.value, "/home/user/.bitcoin"); @@ -1585,7 +1601,7 @@ mod tests { #[test] fn parse_config_parses_address_values() { let (_dir, path) = create_temp_config("zmqpubhashblock=tcp://127.0.0.1:28332\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let zmq = entries.iter().find(|e| e.key == "zmqpubhashblock").unwrap(); assert_eq!(zmq.value, "tcp://127.0.0.1:28332"); @@ -1596,7 +1612,7 @@ mod tests { fn parse_config_handles_unknown_keys() { // Use a section to ensure the config crate parses the key properly let (_dir, path) = create_temp_config("[main]\nunknownkey=unknownvalue\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let unknown = entries.iter().find(|e| e.key == "unknownkey"); assert!( @@ -1620,7 +1636,7 @@ rpcport=8332 rpcport=18332 "#; let (_dir, path) = create_temp_config(content); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); // Should find rpcport with first matching section value let rpcport = entries.iter().find(|e| e.key == "rpcport").unwrap(); @@ -1630,7 +1646,7 @@ rpcport=18332 #[test] fn parse_config_preserves_schema_info() { let (_dir, path) = create_temp_config("txindex=1\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let txindex = entries.iter().find(|e| e.key == "txindex").unwrap(); assert!(txindex.schema.is_some()); @@ -1644,7 +1660,7 @@ rpcport=18332 #[test] fn parse_config_uses_defaults_for_unset_options() { let (_dir, path) = create_temp_config("txindex=1\n"); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); // dbcache should have default value since not set let dbcache = entries.iter().find(|e| e.key == "dbcache").unwrap(); @@ -1661,7 +1677,7 @@ txindex=1 server=1 "#; let (_dir, path) = create_temp_config(content); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); let txindex = entries.iter().find(|e| e.key == "txindex").unwrap(); assert_eq!(txindex.value, "1"); @@ -1699,7 +1715,7 @@ zmqpubhashblock=tcp://127.0.0.1:28332 zmqpubhashtx=tcp://127.0.0.1:28333 "#; let (_dir, path) = create_temp_config(content); - let entries = parse_config(&path).unwrap(); + let (_, entries) = parse_config(&path).unwrap(); // Verify various entries let testnet = entries.iter().find(|e| e.key == "testnet").unwrap(); diff --git a/src/main.rs b/src/main.rs index da04a67..e0de982 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use pdm::app::{App, CurrentScreen}; +use pdm::components::metrics::BitcoinMetrics; +use pdm::config::parse_config; use pdm::ui; use anyhow::Result; @@ -12,9 +14,13 @@ use crossterm::{ terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; use ratatui::{Terminal, backend::Backend, backend::CrosstermBackend}; +use reqwest::Client; use std::io; +use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc; -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<()> { // Setup Terminal enable_raw_mode()?; let mut stdout = io::stdout(); @@ -22,9 +28,61 @@ fn main() -> Result<()> { let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; + // Create channel for Bitcoin metrics + let (btc_tx, btc_rx) = mpsc::channel::(16); + + // Shared RPC parameters (URL, user, pass) + let btc_rpc_params = Arc::new(Mutex::new(None::<(String, String, String)>)); + let btc_rpc_params_task = btc_rpc_params.clone(); + + // TESTING: Initialize with hardcoded test URL (comment out for production) + // This allows testing the connection before loading a bitcoin.conf file + #[allow(unreachable_code)] + { + // Uncomment the line below to enable test mode + *btc_rpc_params.lock().unwrap() = Some(( + "http://127.0.0.1:38332".to_string(), + "p2pool".to_string(), + "p2pool".to_string(), + )); + } + + // Spawn Bitcoin background worker task + tokio::spawn(async move { + let client = Client::new(); + let mut interval = tokio::time::interval(std::time::Duration::from_secs(5)); + + loop { + interval.tick().await; + + // Check if we have RPC parameters + let params = btc_rpc_params_task.lock().unwrap().clone(); + + if let Some((url, user, pass)) = params { + // Try to fetch metrics + match BitcoinMetrics::fetch(&client, &url, &user, &pass).await { + Ok(metrics) => { + // Send metrics through channel (ignore if receiver is gone) + let _ = btc_tx.send(metrics).await; + } + Err(_) => { + // Connection error - will retry next interval + } + } + } + } + }); + // Run App let mut app = App::new(); - let res = run_app(&mut terminal, &mut app, |_app: &mut App| event::read()); + let res = run_app( + &mut terminal, + &mut app, + btc_rx, + btc_rpc_params, + |_app: &mut App| event::read(), + ) + .await; // Restore Terminal disable_raw_mode()?; @@ -39,9 +97,11 @@ fn main() -> Result<()> { } // Accept any Backend and an Event Provider Closure -fn run_app( +async fn run_app( terminal: &mut Terminal, app: &mut App, + mut btc_rx: mpsc::Receiver, + btc_rpc_params: Arc>>, mut event_provider: F, ) -> io::Result<()> where @@ -50,6 +110,11 @@ where loop { terminal.draw(|f| ui::ui(f, app))?; + // Check for metrics updates from the Bitcoin background worker + while let Ok(metrics) = btc_rx.try_recv() { + app.bitcoin_metrics = Some(metrics); + } + // We check the event from our provider if let Event::Key(key) = event_provider(app)? && key.kind == KeyEventKind::Press @@ -66,7 +131,21 @@ where KeyCode::Enter => { if let Some(path) = app.explorer.select() { // File Selected! - app.bitcoin_conf_path = Some(path); + app.bitcoin_conf_path = Some(path.clone()); + // Parse and save the typed struct + if let Ok((bitcoin_config, _)) = parse_config(&path) { + app.bitcoin_config = Some(bitcoin_config.clone()); + + // Extract RPC credentials and populate the shared params + let user = bitcoin_config.rpc.rpcuser.clone().unwrap_or_default(); + let pass = + bitcoin_config.rpc.rpcpassword.clone().unwrap_or_default(); + let port = bitcoin_config.rpc.rpcport.unwrap_or(8332); // mainnet default + let rpc_url = format!("http://127.0.0.1:{}", port); + + // Update the shared RPC parameters for the background task + *btc_rpc_params.lock().unwrap() = Some((rpc_url, user, pass)); + } app.toggle_menu(); // Go back to main screen } } @@ -82,7 +161,7 @@ where } } KeyCode::Down => { - if app.sidebar_index < 1 { + if app.sidebar_index < 4 { app.sidebar_index += 1; app.toggle_menu(); } @@ -100,14 +179,35 @@ where } } +// AppAction enum to track file selections +#[derive(Clone, Debug)] +pub enum AppAction { + FileSelected(std::path::PathBuf), +} + +// Handle actions with URL/param updates +fn handle_action(action: AppAction, app: &mut App) -> anyhow::Result { + match action { + AppAction::FileSelected(path) => { + app.bitcoin_conf_path = Some(path.clone()); + // Parse and save the typed struct + if let Ok((bitcoin_config, _)) = parse_config(&path) { + app.bitcoin_config = Some(bitcoin_config); + } + app.toggle_menu(); // Go back to main screen + } + } + Ok(false) +} + #[cfg(test)] mod tests { use super::*; use crossterm::event::{KeyEvent, KeyEventState, KeyModifiers}; use ratatui::backend::TestBackend; - #[test] - fn test_app_integration_smoke_test() { + #[tokio::test] + async fn test_app_integration_smoke_test() { let backend = TestBackend::new(80, 25); let mut terminal = Terminal::new(backend).unwrap(); let mut app = App::new(); @@ -133,12 +233,23 @@ mod tests { } }; + // Create channel for metrics + let (_btc_tx, btc_rx) = mpsc::channel::(16); + let btc_rpc_params = Arc::new(Mutex::new(None::<(String, String, String)>)); + // First frame terminal.draw(|f| ui::ui(f, &mut app)).unwrap(); insta::assert_debug_snapshot!("home_screen", terminal.backend()); // Run app (process events + redraws) - let res = run_app(&mut terminal, &mut app, event_provider); + let res = run_app( + &mut terminal, + &mut app, + btc_rx, + btc_rpc_params, + event_provider, + ) + .await; assert!(res.is_ok()); // Final frame after DOWN @@ -147,8 +258,8 @@ mod tests { assert_eq!(app.sidebar_index, 1); } - #[test] - fn test_file_explorer_flow() { + #[tokio::test] + async fn test_file_explorer_flow() { // Setup let backend = TestBackend::new(80, 25); let mut terminal = Terminal::new(backend).unwrap(); @@ -220,13 +331,24 @@ mod tests { } }; + // Create channel for metrics + let (_btc_tx, btc_rx) = mpsc::channel::(16); + let btc_rpc_params = Arc::new(Mutex::new(None::<(String, String, String)>)); + // Run - let res = run_app(&mut terminal, &mut app, event_provider); + let res = run_app( + &mut terminal, + &mut app, + btc_rx, + btc_rpc_params, + event_provider, + ) + .await; assert!(res.is_ok()); } - #[test] - fn test_file_explorer_wrap_and_select_sets_config() { + #[tokio::test] + async fn test_file_explorer_wrap_and_select_sets_config() { use std::env::temp_dir; use std::fs::{File, create_dir_all}; @@ -272,7 +394,18 @@ mod tests { } }; - let res = run_app(&mut terminal, &mut app, event_provider); + // Create channel for metrics + let (_btc_tx, btc_rx) = mpsc::channel::(16); + let btc_rpc_params = Arc::new(Mutex::new(None::<(String, String, String)>)); + + let res = run_app( + &mut terminal, + &mut app, + btc_rx, + btc_rpc_params, + event_provider, + ) + .await; assert!(res.is_ok()); assert_eq!(app.bitcoin_conf_path, Some(file_path)); diff --git a/src/snapshots/pdm__tests__home_screen.snap b/src/snapshots/pdm__tests__home_screen.snap index ba3a6fa..fe6cc26 100644 --- a/src/snapshots/pdm__tests__home_screen.snap +++ b/src/snapshots/pdm__tests__home_screen.snap @@ -9,7 +9,7 @@ TestBackend { "┌ PDM ──────────────────┐┌ Home ───────────────────────────────────────────────┐", "│Home ││Welcome to PDM. │", "│Bitcoin Config ││ │", - "│ ││No config loaded │", + "│Bitcoin Status ││No config loaded │", "│ ││ │", "│ ││(Navigate to 'Bitcoin Config' to load) │", "│ ││ │", diff --git a/src/snapshots/pdm__tests__menu_toggled.snap b/src/snapshots/pdm__tests__menu_toggled.snap index 36aabde..1f9c89b 100644 --- a/src/snapshots/pdm__tests__menu_toggled.snap +++ b/src/snapshots/pdm__tests__menu_toggled.snap @@ -9,7 +9,7 @@ TestBackend { "┌ PDM ──────────────────┐┌ Bitcoin Config ─────────────────────────────────────┐", "│Home ││Press [Enter] to select a bitcoin.conf file │", "│Bitcoin Config ││ │", - "│ ││ │", + "│Bitcoin Status ││ │", "│ ││ │", "│ ││ │", "│ ││ │", diff --git a/src/ui.rs b/src/ui.rs index 0ea370d..7a4e555 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -18,7 +18,11 @@ pub fn ui(f: &mut Frame, app: &mut App) { .split(f.area()); // Sidebar - let items = vec![ListItem::new("Home"), ListItem::new("Bitcoin Config")]; + let items = vec![ + ListItem::new("Home"), + ListItem::new("Bitcoin Config"), + ListItem::new("Bitcoin Status"), + ]; // Highlight the active one let mut state = ListState::default(); @@ -57,6 +61,51 @@ pub fn ui(f: &mut Frame, app: &mut App) { ); f.render_widget(p, main_area); } + CurrentScreen::BitcoinStatus => { + if let Some(metrics) = &app.bitcoin_metrics { + let sync_pct = metrics.verification_progress * 100.0; + + let text = format!( + " Bitcoin Core is ONLINE \n\n\ + Network: {}\n\ + Blocks: {}\n\ + Headers: {}\n\ + Sync Status: {:.2}%\n\ + Connections: {}", + metrics.chain.to_uppercase(), + metrics.blocks, + metrics.headers, + sync_pct, + metrics.connections + ); + + let p = Paragraph::new(text) + .block( + Block::default() + .borders(Borders::ALL) + .title(" Bitcoin Node Live Status "), + ) + .style(Style::default().fg(Color::Yellow)) + .wrap(Wrap { trim: true }); + f.render_widget(p, main_area); + } else { + // Waiting for data / config + let p = Paragraph::new( + "Waiting for RPC data...\n\n\ + • Load bitcoin.conf via 'Bitcoin Config' tab\n\n\ + For testing: Uncomment the hardcoded URL in main.rs \ + and ensure Bitcoin Core is running on 127.0.0.1:38332", + ) + .block( + Block::default() + .borders(Borders::ALL) + .title(" Bitcoin Node Live Status "), + ) + .style(Style::default().fg(Color::DarkGray)) + .wrap(Wrap { trim: true }); + f.render_widget(p, main_area); + } + } CurrentScreen::FileExplorer => { render_file_explorer(f, app, main_area); } diff --git a/tests/snapshots/ui_snapshots__config_screen_render.snap b/tests/snapshots/ui_snapshots__config_screen_render.snap index 177ff52..b2b9df0 100644 --- a/tests/snapshots/ui_snapshots__config_screen_render.snap +++ b/tests/snapshots/ui_snapshots__config_screen_render.snap @@ -9,7 +9,7 @@ TestBackend { "┌ PDM ──────────────────┐┌ Bitcoin Config ─────────────────────────────────────┐", "│Home ││Press [Enter] to select a bitcoin.conf file │", "│Bitcoin Config ││ │", - "│ ││ │", + "│Bitcoin Status ││ │", "│ ││ │", "│ ││ │", "│ ││ │", diff --git a/tests/snapshots/ui_snapshots__home_screen_render.snap b/tests/snapshots/ui_snapshots__home_screen_render.snap index d16626d..7902e8e 100644 --- a/tests/snapshots/ui_snapshots__home_screen_render.snap +++ b/tests/snapshots/ui_snapshots__home_screen_render.snap @@ -9,7 +9,7 @@ TestBackend { "┌ PDM ──────────────────┐┌ Home ───────────────────────────────────────────────┐", "│Home ││Welcome to PDM. │", "│Bitcoin Config ││ │", - "│ ││No config loaded │", + "│Bitcoin Status ││No config loaded │", "│ ││ │", "│ ││(Navigate to 'Bitcoin Config' to load) │", "│ ││ │",