chat_ui_components/components/
stats_panel.rs1use dioxus::prelude::*;
2use chat_core::history::types::{ConnectionStats, ConnectionStatus};
3use crate::transport_utils::transport_name_to_info;
4
5#[component]
6pub fn StatsPanel(
7 stats: Option<Box<ConnectionStats>>,
8 local_peer_id: String,
9 on_close: EventHandler<()>,
10 on_dial_peer: EventHandler<String>,
11) -> Element {
12 let mut peer_addr_input = use_signal(|| "".to_string());
13
14 let (status, bootstrap, direct, relay, transports, circuit_address) = match stats {
15 Some(s) => (
16 match s.status {
17 ConnectionStatus::Online => "online",
18 ConnectionStatus::Offline => "offline",
19 ConnectionStatus::Connecting => "connecting",
20 },
21 s.bootstrap_connections,
22 s.direct_peers,
23 s.relay_connections,
24 s.active_transports.clone(),
25 s.circuit_address.clone(),
26 ),
27 None => ("offline", 0, 0, 0, vec![], None),
28 };
29 let peer_id_short = local_peer_id.chars().take(20).collect::<String>();
30 let addr_display = circuit_address.unwrap_or_else(|| "No relay reservation yet".to_string());
31
32 rsx! {
33 div {
34 class: "stats-panel",
35 div {
36 class: "stats-header",
37 h3 { "Connection Stats" }
38 button {
39 class: "close-btn",
40 onclick: move |_| on_close.call(()),
41 "×"
42 }
43 }
44 div {
45 class: "stats-content",
46 div {
47 class: "stat-row",
48 span { class: "stat-label", "Status" }
49 span { class: "stat-value", "{status}" }
50 }
51 div {
52 class: "stat-row",
53 span { class: "stat-label", "Peer ID" }
54 span {
55 class: "stat-value mono",
56 "{peer_id_short}..."
57 }
58 }
59 div {
60 class: "stat-row",
61 span { class: "stat-label", "Bootstrap" }
62 span { class: "stat-value", "{bootstrap}" }
63 }
64 div {
65 class: "stat-row",
66 span { class: "stat-label", "Direct Peers" }
67 span { class: "stat-value", "{direct}" }
68 }
69 div {
70 class: "stat-row",
71 span { class: "stat-label", "Relay Conns" }
72 span { class: "stat-value", "{relay}" }
73 }
74 div {
75 class: "stat-section",
76 div { class: "stat-label", "Active Transports" }
77 ul {
78 class: "stat-list",
79 if transports.is_empty() {
80 li { "None yet" }
81 } else {
82 for t in transports {
83 {
84 let info = transport_name_to_info(&t);
85 rsx! { li { "{info.0} {info.1}" } }
86 }
87 }
88 }
89 }
90 }
91 div {
93 class: "stat-section",
94 div { class: "stat-label", "Your Address" }
95 div {
96 class: "peer-address-row",
97 span {
98 class: "peer-address",
99 "{addr_display}"
100 }
101 }
102 }
103 div {
105 class: "stat-section",
106 div { class: "stat-label", "Dial Peer" }
107 div {
108 class: "peer-dial-row",
109 input {
110 id: "peer-address-input",
111 value: "{peer_addr_input}",
112 placeholder: "/ip4/.../tcp/.../p2p/...",
113 oninput: move |evt| peer_addr_input.set(evt.value().clone()),
114 onkeypress: move |evt| {
115 if evt.key().to_string() == "Enter" {
116 let addr = peer_addr_input.read().trim().to_string();
117 if !addr.is_empty() {
118 on_dial_peer.call(addr);
119 peer_addr_input.set("".to_string());
120 }
121 }
122 }
123 }
124 button {
125 id: "peer-dial-btn",
126 class: "btn-primary",
127 onclick: move |_| {
128 let addr = peer_addr_input.read().trim().to_string();
129 if !addr.is_empty() {
130 on_dial_peer.call(addr);
131 peer_addr_input.set("".to_string());
132 }
133 },
134 "Dial"
135 }
136 }
137 }
138 }
139 }
140 }
141}