From 04dd74d15f19bc9327ae6fb668a0274a7460cc3289e42018076f0f4914fce5f9 Mon Sep 17 00:00:00 2001 From: Tyler King Date: Sun, 5 Apr 2026 14:10:01 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20Dioxus=20dashboard=20=E2=80=94=20sessio?= =?UTF-8?q?n=20analytics=20+=20WASM=20web=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New crates: bascule-dashboard — shared Dioxus component library SessionTable: live active sessions with auth/backend/TPM status StatsCards: active count, 24h total, TPM attested %, failed auth StatusBar: connection health indicator types.rs: DashboardSession, DashboardStats, HealthResponse bascule-dashboard-web — WASM web target (Dioxus 0.6 + web features) Compiles to wasm32-unknown-unknown Dark-first CSS (light mode via prefers-color-scheme) Monospace data display, clean stat cards bascule-core/store.rs — in-memory session store SessionStore with active sessions + aggregate stats Updated via SessionHandler hooks Both dashboard library and web WASM target compile clean. Server and shell builds unaffected. Zero substrate deps. Signed-off-by: Tyler King --- Cargo.lock | 901 +++++++++++++++++- Cargo.toml | 9 +- crates/bascule-core/src/lib.rs | 1 + crates/bascule-core/src/store.rs | 84 ++ crates/bascule-dashboard-web/Cargo.toml | 12 + crates/bascule-dashboard-web/assets/style.css | 139 +++ crates/bascule-dashboard-web/src/main.rs | 63 ++ crates/bascule-dashboard/Cargo.toml | 12 + .../bascule-dashboard/src/components/mod.rs | 3 + .../src/components/session_table.rs | 48 + .../src/components/stats_cards.rs | 32 + .../src/components/status_bar.rs | 17 + crates/bascule-dashboard/src/lib.rs | 6 + crates/bascule-dashboard/src/types.rs | 35 + 14 files changed, 1359 insertions(+), 3 deletions(-) create mode 100644 crates/bascule-core/src/store.rs create mode 100644 crates/bascule-dashboard-web/Cargo.toml create mode 100644 crates/bascule-dashboard-web/assets/style.css create mode 100644 crates/bascule-dashboard-web/src/main.rs create mode 100644 crates/bascule-dashboard/Cargo.toml create mode 100644 crates/bascule-dashboard/src/components/mod.rs create mode 100644 crates/bascule-dashboard/src/components/session_table.rs create mode 100644 crates/bascule-dashboard/src/components/stats_cards.rs create mode 100644 crates/bascule-dashboard/src/components/status_bar.rs create mode 100644 crates/bascule-dashboard/src/lib.rs create mode 100644 crates/bascule-dashboard/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 02e7ecb..2b996b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,26 @@ dependencies = [ "uuid", ] +[[package]] +name = "bascule-dashboard" +version = "0.1.0" +dependencies = [ + "chrono", + "dioxus", + "serde", + "serde_json", +] + +[[package]] +name = "bascule-dashboard-web" +version = "0.1.0" +dependencies = [ + "bascule-dashboard", + "dioxus", + "serde", + "serde_json", +] + [[package]] name = "bascule-server" version = "0.1.0" @@ -366,6 +386,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -422,6 +469,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -448,6 +505,56 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "const-serialize" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08259976d62c715c4826cb4a3d64a3a9e5c5f68f964ff6087319857f569f93a6" +dependencies = [ + "const-serialize-macro", + "serde", +] + +[[package]] +name = "const-serialize-macro" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04382d0d9df7434af6b1b49ea1a026ef39df1b0738b1cc373368cf175354f6eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -537,6 +644,53 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.10.0" @@ -595,6 +749,341 @@ dependencies = [ "subtle", ] +[[package]] +name = "dioxus" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a247114500f1a78e87022defa8173de847accfada8e8809dfae23a118a580c" +dependencies = [ + "dioxus-cli-config", + "dioxus-config-macro", + "dioxus-core", + "dioxus-core-macro", + "dioxus-devtools", + "dioxus-document", + "dioxus-fullstack", + "dioxus-history", + "dioxus-hooks", + "dioxus-html", + "dioxus-logger", + "dioxus-signals", + "dioxus-web", + "manganis", + "warnings", +] + +[[package]] +name = "dioxus-cli-config" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd16948f1ffdb068dd9a64812158073a4250e2af4e98ea31fdac0312e6bce86" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "dioxus-config-macro" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cbf582fbb1c32d34a1042ea675469065574109c95154468710a4d73ee98b49" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "dioxus-core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c03f451a119e47433c16e2d8eb5b15bf7d6e6734eb1a4c47574e6711dadff8d" +dependencies = [ + "const_format", + "dioxus-core-types", + "futures-channel", + "futures-util", + "generational-box", + "longest-increasing-subsequence", + "rustc-hash 1.1.0", + "rustversion", + "serde", + "slab", + "slotmap", + "tracing", + "warnings", +] + +[[package]] +name = "dioxus-core-macro" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105c954caaaedf8cd10f3d1ba576b01e18aa8d33ad435182125eefe488cf0064" +dependencies = [ + "convert_case", + "dioxus-rsx", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dioxus-core-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a82fccfa48574eb7aa183e297769540904694844598433a9eb55896ad9f93b" +dependencies = [ + "once_cell", +] + +[[package]] +name = "dioxus-devtools" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a7300f1e8181218187b03502044157eef04e0a25b518117c5ef9ae1096880" +dependencies = [ + "dioxus-core", + "dioxus-devtools-types", + "dioxus-signals", + "serde", + "serde_json", + "tracing", + "tungstenite", + "warnings", +] + +[[package]] +name = "dioxus-devtools-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f62434973c0c9c5a3bc42e9cd5e7070401c2062a437fb5528f318c3e42ebf4ff" +dependencies = [ + "dioxus-core", + "serde", +] + +[[package]] +name = "dioxus-document" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802a2014d1662b6615eec0a275745822ee4fc66aacd9d0f2fb33d6c8da79b8f2" +dependencies = [ + "dioxus-core", + "dioxus-core-macro", + "dioxus-core-types", + "dioxus-html", + "futures-channel", + "futures-util", + "generational-box", + "lazy-js-bundle", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "dioxus-fullstack" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe99b48a1348eec385b5c4bd3e80fd863b0d3b47257d34e2ddc58754dec5d128" +dependencies = [ + "base64", + "bytes", + "ciborium", + "dioxus-devtools", + "dioxus-history", + "dioxus-lib", + "dioxus-web", + "dioxus_server_macro", + "futures-channel", + "futures-util", + "generational-box", + "once_cell", + "serde", + "server_fn", + "tracing", + "web-sys", +] + +[[package]] +name = "dioxus-history" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ae4e22616c698f35b60727313134955d885de2d32e83689258e586ebc9b7909" +dependencies = [ + "dioxus-core", + "tracing", +] + +[[package]] +name = "dioxus-hooks" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948e2b3f20d9d4b2c300aaa60281b1755f3298684448920b27106da5841896d0" +dependencies = [ + "dioxus-core", + "dioxus-signals", + "futures-channel", + "futures-util", + "generational-box", + "rustversion", + "slab", + "tracing", + "warnings", +] + +[[package]] +name = "dioxus-html" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c9a40e6fee20ce7990095492dedb6a753eebe05e67d28271a249de74dc796d" +dependencies = [ + "async-trait", + "dioxus-core", + "dioxus-core-macro", + "dioxus-core-types", + "dioxus-hooks", + "dioxus-html-internal-macro", + "enumset", + "euclid", + "futures-channel", + "generational-box", + "keyboard-types", + "lazy-js-bundle", + "rustversion", + "tracing", +] + +[[package]] +name = "dioxus-html-internal-macro" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ba87b53688a2c9f619ecdf4b3b955bc1f08bd0570a80a0d626c405f6d14a76" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dioxus-interpreter-js" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330707b10ca75cb0eb05f9e5f8d80217cd0d7e62116a8277ae363c1a09b57a22" +dependencies = [ + "js-sys", + "lazy-js-bundle", + "rustc-hash 1.1.0", + "sledgehammer_bindgen", + "sledgehammer_utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "dioxus-lib" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5405b71aa9b8b0c3e0d22728f12f34217ca5277792bd315878cc6ecab7301b72" +dependencies = [ + "dioxus-config-macro", + "dioxus-core", + "dioxus-core-macro", + "dioxus-document", + "dioxus-history", + "dioxus-hooks", + "dioxus-html", + "dioxus-rsx", + "dioxus-signals", + "warnings", +] + +[[package]] +name = "dioxus-logger" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545961e752f6c8bf59c274951b3c8b18a106db6ad2f9e2035b29e1f2a3e899b1" +dependencies = [ + "console_error_panic_hook", + "dioxus-cli-config", + "tracing", + "tracing-subscriber", + "tracing-wasm", +] + +[[package]] +name = "dioxus-rsx" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb588e05800b5a7eb90b2f40fca5bbd7626e823fb5e1ba21e011de649b45aa1" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + +[[package]] +name = "dioxus-signals" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10e032dbb3a2c0386ec8b8ee59bc20b5aeb67038147c855801237b45b13d72ac" +dependencies = [ + "dioxus-core", + "futures-channel", + "futures-util", + "generational-box", + "once_cell", + "parking_lot", + "rustc-hash 1.1.0", + "tracing", + "warnings", +] + +[[package]] +name = "dioxus-web" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7c12475c3d360058b8afe1b68eb6dfc9cbb7dcd760aed37c5f85c561c83ed1" +dependencies = [ + "async-trait", + "ciborium", + "dioxus-cli-config", + "dioxus-core", + "dioxus-core-types", + "dioxus-devtools", + "dioxus-document", + "dioxus-history", + "dioxus-html", + "dioxus-interpreter-js", + "dioxus-signals", + "futures-channel", + "futures-util", + "generational-box", + "js-sys", + "lazy-js-bundle", + "rustc-hash 1.1.0", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "dioxus_server_macro" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "371a5b21989a06b53c5092e977b3f75d0e60a65a4c15a2aa1d07014c3b2dda97" +dependencies = [ + "proc-macro2", + "quote", + "server_fn_macro", + "syn", +] + [[package]] name = "dirs" version = "5.0.1" @@ -633,6 +1122,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" @@ -693,6 +1188,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enumset" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -709,6 +1225,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + [[package]] name = "fastrand" version = "2.4.0" @@ -770,6 +1295,12 @@ dependencies = [ "seize", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -873,6 +1404,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generational-box" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a673cf4fb0ea6a91aa86c08695756dfe875277a912cdbf33db9a9f62d47ed82b" +dependencies = [ + "parking_lot", + "tracing", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -934,6 +1475,40 @@ dependencies = [ "polyval", ] +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "group" version = "0.13.0" @@ -945,6 +1520,23 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1222,6 +1814,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[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" @@ -1329,6 +1927,21 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "lazy-js-bundle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49596223b9d9d4947a14a25c142a6e7d8ab3f27eb3ade269d238bb8b5c267e2" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1392,12 +2005,54 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "longest-increasing-subsequence" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" + [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "manganis" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317af44b15e7605b85f04525449a3bb631753040156c9b318e6cba8a3ea4ef73" +dependencies = [ + "const-serialize", + "manganis-core", + "manganis-macro", +] + +[[package]] +name = "manganis-core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38bee65cc725b2bba23b5dbb290f57c8be8fadbe2043fb7e2ce73022ea06519" +dependencies = [ + "const-serialize", + "dioxus-cli-config", + "dioxus-core-types", + "serde", +] + +[[package]] +name = "manganis-macro" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f4f71310913c40174d9f0cfcbcb127dad0329ecdb3945678a120db22d3d065" +dependencies = [ + "dunce", + "manganis-core", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1692,6 +2347,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1838,6 +2513,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", +] + [[package]] name = "quinn" version = "0.11.9" @@ -1849,7 +2536,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.2", "rustls", "socket2", "thiserror 2.0.18", @@ -1869,7 +2556,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash", + "rustc-hash 2.1.2", "rustls", "rustls-pki-types", "slab", @@ -2231,6 +2918,12 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -2358,6 +3051,15 @@ version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + [[package]] name = "serde" version = "1.0.228" @@ -2368,6 +3070,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -2401,6 +3114,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror 1.0.69", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -2464,6 +3188,58 @@ dependencies = [ "serial-core", ] +[[package]] +name = "server_fn" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe" +dependencies = [ + "bytes", + "const_format", + "dashmap", + "futures", + "gloo-net", + "http", + "js-sys", + "once_cell", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror 1.0.69", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaaf648c6967aef78177c0610478abb5a3455811f401f3c62d10ae9bd3901a1" +dependencies = [ + "const_format", + "convert_case", + "proc-macro2", + "quote", + "syn", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" +dependencies = [ + "server_fn_macro", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2561,6 +3337,45 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "sledgehammer_bindgen" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e83e178d176459c92bc129cfd0958afac3ced925471b889b3a75546cfc4133" +dependencies = [ + "sledgehammer_bindgen_macro", + "wasm-bindgen", +] + +[[package]] +name = "sledgehammer_bindgen_macro" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb251b407f50028476a600541542b605bb864d35d9ee1de4f6cab45d88475e6d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "sledgehammer_utils" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdd4b83524961983cea3c55383b3910fd2f24fd13a188f5b091d2d504a61ae" +dependencies = [ + "rustc-hash 1.1.0", +] + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "serde", + "version_check", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -3052,12 +3867,41 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + [[package]] name = "typenum" version = "1.19.0" @@ -3070,6 +3914,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -3104,6 +3954,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -3148,6 +4004,28 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warnings" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f68998838dab65727c9b30465595c6f7c953313559371ca8bf31759b3680ad" +dependencies = [ + "pin-project", + "tracing", + "warnings-macro", +] + +[[package]] +name = "warnings-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3249,6 +4127,19 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" @@ -3769,6 +4660,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + [[package]] name = "yoke" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 7f09696..f78be54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,12 @@ [workspace] -members = ["crates/bascule-core", "crates/bascule-server", "crates/bascule-auth-agent-id", "crates/bascule-shell"] +members = [ + "crates/bascule-core", + "crates/bascule-server", + "crates/bascule-auth-agent-id", + "crates/bascule-shell", + "crates/bascule-dashboard", + "crates/bascule-dashboard-web", +] resolver = "2" [workspace.package] diff --git a/crates/bascule-core/src/lib.rs b/crates/bascule-core/src/lib.rs index 720b125..480f0ca 100644 --- a/crates/bascule-core/src/lib.rs +++ b/crates/bascule-core/src/lib.rs @@ -18,3 +18,4 @@ pub mod proxy; pub mod pty; pub mod server; pub mod session; +pub mod store; diff --git a/crates/bascule-core/src/store.rs b/crates/bascule-core/src/store.rs new file mode 100644 index 0000000..0b7b344 --- /dev/null +++ b/crates/bascule-core/src/store.rs @@ -0,0 +1,84 @@ +//! Session store — tracks active and historical sessions for the dashboard. + +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +/// A session as the dashboard sees it. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct DashboardSession { + pub id: String, + pub principal: String, + pub auth_method: String, + pub source_ip: String, + pub backend: String, + pub container_image: Option, + pub connected_at: String, + pub tpm_attested: bool, + pub attestation_hash: Option, + pub commands_executed: u64, +} + +/// Aggregate stats for the dashboard. +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +pub struct DashboardStats { + pub active_sessions: usize, + pub total_sessions_24h: u64, + pub auth_breakdown: HashMap, + pub backend_breakdown: HashMap, + pub attested_percentage: f64, + pub failed_auth_24h: u64, +} + +/// In-memory session store updated by SessionHandler. +#[derive(Clone)] +pub struct SessionStore { + pub active: Arc>>, + pub stats: Arc>, +} + +impl SessionStore { + pub fn new() -> Self { + Self { + active: Arc::new(RwLock::new(HashMap::new())), + stats: Arc::new(RwLock::new(DashboardStats::default())), + } + } + + pub async fn add_session(&self, session: DashboardSession) { + let method = session.auth_method.clone(); + let backend = session.backend.clone(); + let attested = session.tpm_attested; + + self.active.write().await.insert(session.id.clone(), session); + + let mut stats = self.stats.write().await; + stats.active_sessions = self.active.read().await.len(); + stats.total_sessions_24h += 1; + *stats.auth_breakdown.entry(method).or_insert(0) += 1; + *stats.backend_breakdown.entry(backend).or_insert(0) += 1; + + // Recalculate attested percentage + let total = stats.total_sessions_24h as f64; + if attested { + stats.attested_percentage = ((stats.attested_percentage * (total - 1.0) + 100.0) / total).min(100.0); + } else if total > 0.0 { + stats.attested_percentage = (stats.attested_percentage * (total - 1.0)) / total; + } + } + + pub async fn remove_session(&self, id: &str) { + self.active.write().await.remove(id); + self.stats.write().await.active_sessions = self.active.read().await.len(); + } + + pub async fn record_auth_failure(&self) { + self.stats.write().await.failed_auth_24h += 1; + } +} + +impl Default for SessionStore { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/bascule-dashboard-web/Cargo.toml b/crates/bascule-dashboard-web/Cargo.toml new file mode 100644 index 0000000..1ab648d --- /dev/null +++ b/crates/bascule-dashboard-web/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bascule-dashboard-web" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Bascule dashboard — web (WASM) target" + +[dependencies] +bascule-dashboard = { path = "../bascule-dashboard" } +dioxus = { version = "0.6", features = ["web"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/crates/bascule-dashboard-web/assets/style.css b/crates/bascule-dashboard-web/assets/style.css new file mode 100644 index 0000000..181cdc4 --- /dev/null +++ b/crates/bascule-dashboard-web/assets/style.css @@ -0,0 +1,139 @@ +:root { + --bg-primary: #0f1117; + --bg-secondary: #1a1d27; + --bg-card: #222633; + --text-primary: #e4e7ef; + --text-secondary: #8b8fa3; + --accent-primary: #6c8cff; + --accent-success: #4caf82; + --accent-warning: #e8a838; + --accent-danger: #e85454; + --border: #2a2e3d; + --radius: 8px; + --font-mono: 'JetBrains Mono', 'Fira Code', monospace; + --font-sans: 'Inter', -apple-system, sans-serif; +} + +@media (prefers-color-scheme: light) { + :root { + --bg-primary: #f8f9fc; + --bg-secondary: #ffffff; + --bg-card: #ffffff; + --text-primary: #1a1d27; + --text-secondary: #6b7280; + --border: #e5e7eb; + } +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: var(--font-sans); + background: var(--bg-primary); + color: var(--text-primary); +} + +.dashboard { min-height: 100vh; } + +.dashboard-header { + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + padding: 12px 24px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.dashboard-header h1 { font-size: 1.2em; } + +.dashboard-main { + padding: 24px; + max-width: 1200px; + margin: 0 auto; +} + +.stat-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; + margin-bottom: 24px; +} + +.stat-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px; + text-align: center; +} + +.stat-value { + font-size: 2em; + font-weight: 700; + font-family: var(--font-mono); +} + +.stat-label { + font-size: 0.8em; + color: var(--text-secondary); + margin-top: 4px; +} + +.stat-primary .stat-value { color: var(--accent-primary); } +.stat-secondary .stat-value { color: var(--text-secondary); } +.stat-success .stat-value { color: var(--accent-success); } +.stat-warning .stat-value { color: var(--accent-warning); } +.stat-danger .stat-value { color: var(--accent-danger); } + +.session-table { margin-top: 24px; } +.session-table h2 { margin-bottom: 12px; } + +table { + width: 100%; + border-collapse: collapse; + font-family: var(--font-mono); + font-size: 0.85em; +} + +th { + text-align: left; + padding: 8px 12px; + color: var(--text-secondary); + border-bottom: 1px solid var(--border); + font-weight: 500; +} + +td { + padding: 8px 12px; + border-bottom: 1px solid var(--border); +} + +.badge { + display: inline-block; + padding: 2px 8px; + border-radius: 4px; + font-size: 0.8em; + background: var(--bg-secondary); + color: var(--text-secondary); +} + +.image-tag { font-size: 0.8em; color: var(--text-secondary); } +.empty { color: var(--text-secondary); padding: 24px; text-align: center; } + +.status-bar { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85em; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.status-connected { background: var(--accent-success); } +.status-disconnected { background: var(--accent-danger); } +.status-text { color: var(--text-secondary); } +.refresh-time { color: var(--text-secondary); font-size: 0.8em; } diff --git a/crates/bascule-dashboard-web/src/main.rs b/crates/bascule-dashboard-web/src/main.rs new file mode 100644 index 0000000..6a2a70f --- /dev/null +++ b/crates/bascule-dashboard-web/src/main.rs @@ -0,0 +1,63 @@ +//! Bascule Dashboard — Web (WASM) entry point. + +use dioxus::prelude::*; +use bascule_dashboard::components::session_table::SessionTable; +use bascule_dashboard::components::stats_cards::StatsCards; +use bascule_dashboard::components::status_bar::StatusBar; +use bascule_dashboard::types::{DashboardSession, DashboardStats}; + +fn main() { + dioxus::launch(App); +} + +#[component] +fn App() -> Element { + // For now, use placeholder data. In production, fetch from /api/sessions. + let sessions = vec![ + DashboardSession { + id: "sess-001".into(), + principal: "tking@guildhouse.dev".into(), + auth_method: "ssh-key".into(), + source_ip: "192.168.1.10".into(), + backend: "container".into(), + container_image: Some("k8s-ops".into()), + connected_at: "2026-04-05T10:00:00Z".into(), + tpm_attested: true, + attestation_hash: Some("e9b95f002f54222d".into()), + commands_executed: 47, + }, + DashboardSession { + id: "sess-002".into(), + principal: "agent:claude-code".into(), + auth_method: "agent-id".into(), + source_ip: "10.0.1.50".into(), + backend: "pty".into(), + container_image: None, + connected_at: "2026-04-05T10:15:00Z".into(), + tpm_attested: false, + attestation_hash: None, + commands_executed: 12, + }, + ]; + + let stats = DashboardStats { + active_sessions: 2, + total_sessions_24h: 47, + attested_percentage: 78.0, + failed_auth_24h: 3, + ..Default::default() + }; + + rsx! { + div { class: "dashboard", + header { class: "dashboard-header", + h1 { "Bascule" } + StatusBar { connected: true, last_refresh: Some("just now".into()) } + } + main { class: "dashboard-main", + StatsCards { stats: stats } + SessionTable { sessions: sessions } + } + } + } +} diff --git a/crates/bascule-dashboard/Cargo.toml b/crates/bascule-dashboard/Cargo.toml new file mode 100644 index 0000000..bee0b39 --- /dev/null +++ b/crates/bascule-dashboard/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bascule-dashboard" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Dashboard components for Bascule SSH proxy" + +[dependencies] +dioxus = "0.6" +serde = { workspace = true } +serde_json = { workspace = true } +chrono = { workspace = true } diff --git a/crates/bascule-dashboard/src/components/mod.rs b/crates/bascule-dashboard/src/components/mod.rs new file mode 100644 index 0000000..4af913b --- /dev/null +++ b/crates/bascule-dashboard/src/components/mod.rs @@ -0,0 +1,3 @@ +pub mod session_table; +pub mod stats_cards; +pub mod status_bar; diff --git a/crates/bascule-dashboard/src/components/session_table.rs b/crates/bascule-dashboard/src/components/session_table.rs new file mode 100644 index 0000000..341e0e2 --- /dev/null +++ b/crates/bascule-dashboard/src/components/session_table.rs @@ -0,0 +1,48 @@ +use dioxus::prelude::*; +use crate::types::DashboardSession; + +#[component] +pub fn SessionTable(sessions: Vec) -> Element { + rsx! { + div { class: "session-table", + h2 { "Active Sessions ({sessions.len()})" } + if sessions.is_empty() { + p { class: "empty", "No active sessions" } + } else { + table { + thead { + tr { + th { "Principal" } + th { "Method" } + th { "Backend" } + th { "Source IP" } + th { "TPM" } + th { "Commands" } + } + } + tbody { + for session in sessions.iter() { + tr { class: "session-row", + td { "{session.principal}" } + td { + span { class: "badge", "{session.auth_method}" } + } + td { + span { class: "badge", "{session.backend}" } + if let Some(ref img) = session.container_image { + span { class: "image-tag", " ({img})" } + } + } + td { "{session.source_ip}" } + td { + if session.tpm_attested { "✓" } else { "—" } + } + td { "{session.commands_executed}" } + } + } + } + } + } + } + } +} diff --git a/crates/bascule-dashboard/src/components/stats_cards.rs b/crates/bascule-dashboard/src/components/stats_cards.rs new file mode 100644 index 0000000..401ac75 --- /dev/null +++ b/crates/bascule-dashboard/src/components/stats_cards.rs @@ -0,0 +1,32 @@ +use dioxus::prelude::*; +use crate::types::DashboardStats; + +#[component] +pub fn StatsCards(stats: DashboardStats) -> Element { + rsx! { + div { class: "stat-grid", + StatCard { label: "Active Sessions".to_string(), value: format!("{}", stats.active_sessions), accent: "primary".to_string() } + StatCard { label: "24h Total".to_string(), value: format!("{}", stats.total_sessions_24h), accent: "secondary".to_string() } + StatCard { + label: "TPM Attested".to_string(), + value: format!("{:.0}%", stats.attested_percentage), + accent: (if stats.attested_percentage > 90.0 { "success" } else { "warning" }).to_string(), + } + StatCard { + label: "Failed Auth (24h)".to_string(), + value: format!("{}", stats.failed_auth_24h), + accent: (if stats.failed_auth_24h > 10 { "danger" } else { "success" }).to_string(), + } + } + } +} + +#[component] +fn StatCard(label: String, value: String, accent: String) -> Element { + rsx! { + div { class: "stat-card stat-{accent}", + div { class: "stat-value", "{value}" } + div { class: "stat-label", "{label}" } + } + } +} diff --git a/crates/bascule-dashboard/src/components/status_bar.rs b/crates/bascule-dashboard/src/components/status_bar.rs new file mode 100644 index 0000000..4722b24 --- /dev/null +++ b/crates/bascule-dashboard/src/components/status_bar.rs @@ -0,0 +1,17 @@ +use dioxus::prelude::*; + +#[component] +pub fn StatusBar(connected: bool, last_refresh: Option) -> Element { + let status_class = if connected { "connected" } else { "disconnected" }; + let status_text = if connected { "Connected" } else { "Disconnected" }; + + rsx! { + div { class: "status-bar", + span { class: "status-dot status-{status_class}" } + span { class: "status-text", "{status_text}" } + if let Some(ref ts) = last_refresh { + span { class: "refresh-time", "Last: {ts}" } + } + } + } +} diff --git a/crates/bascule-dashboard/src/lib.rs b/crates/bascule-dashboard/src/lib.rs new file mode 100644 index 0000000..b40aeab --- /dev/null +++ b/crates/bascule-dashboard/src/lib.rs @@ -0,0 +1,6 @@ +//! Bascule Dashboard — shared component library. +//! +//! Dioxus components consumed by both the web (WASM) and TUI targets. + +pub mod components; +pub mod types; diff --git a/crates/bascule-dashboard/src/types.rs b/crates/bascule-dashboard/src/types.rs new file mode 100644 index 0000000..746d3b8 --- /dev/null +++ b/crates/bascule-dashboard/src/types.rs @@ -0,0 +1,35 @@ +//! Shared types between dashboard and server. + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct DashboardSession { + pub id: String, + pub principal: String, + pub auth_method: String, + pub source_ip: String, + pub backend: String, + pub container_image: Option, + pub connected_at: String, + pub tpm_attested: bool, + pub attestation_hash: Option, + pub commands_executed: u64, +} + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct DashboardStats { + pub active_sessions: usize, + pub total_sessions_24h: u64, + pub auth_breakdown: HashMap, + pub backend_breakdown: HashMap, + pub attested_percentage: f64, + pub failed_auth_24h: u64, +} + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct HealthResponse { + pub status: String, + pub version: String, + pub active_sessions: usize, +}