chat_ui_components/components/
stats_panel.rs

1use 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                // Circuit / WebRTC address display
92                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                // Dial peer section inside stats panel
104                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}