diff --git a/meshchatx/src/backend/rnstatus_handler.py b/meshchatx/src/backend/rnstatus_handler.py index 82c8d6b..424408b 100644 --- a/meshchatx/src/backend/rnstatus_handler.py +++ b/meshchatx/src/backend/rnstatus_handler.py @@ -3,6 +3,7 @@ import time from typing import Any import RNS +from RNS.Discovery import InterfaceDiscovery def size_str(num, suffix="B"): @@ -24,6 +25,63 @@ def size_str(num, suffix="B"): return f"{num:.2f}{last_unit}{suffix}" +def fmt_per_second(value: Any) -> str | None: + if value is None: + return None + try: + x = float(value) + except (TypeError, ValueError): + return str(value) + ax = abs(x) + if ax == 0: + return "0" + if ax >= 100: + return f"{x:.1f}" + if ax >= 1: + return f"{x:.2f}" + return f"{x:.3g}" + + +def fmt_packet_count(value: Any) -> str | None: + if value is None: + return None + try: + x = float(value) + except (TypeError, ValueError): + return str(value) + return f"{int(round(x)):,}" + + +def fmt_percentage(value: Any) -> str | None: + if value is None: + return None + try: + x = float(value) + except (TypeError, ValueError): + return str(value) + ax = abs(x) + if ax >= 100: + return f"{x:.1f}" + if ax >= 10: + return f"{x:.2f}" + return f"{x:.3g}" + + +def stat_name_matches_discovered(stat_name: str, discovered_list: list) -> bool: + if not stat_name or not discovered_list: + return False + for d in discovered_list: + if not isinstance(d, dict): + continue + ro = d.get("reachable_on") + if ro and str(ro) in stat_name: + return True + dn = d.get("name") + if dn and str(dn) and str(dn) in stat_name: + return True + return False + + class RNStatusHandler: def __init__(self, reticulum_instance): self.reticulum = reticulum_instance @@ -56,6 +114,12 @@ class RNStatusHandler: "link_count": link_count, } + discovered_list: list = [] + with contextlib.suppress(Exception): + discovered_list = InterfaceDiscovery( + discover_interfaces=False, + ).list_discovered_interfaces() + blackhole_enabled = False blackhole_sources = [] blackhole_count = 0 @@ -139,6 +203,7 @@ class RNStatusHandler: formatted_if: dict[str, Any] = { "name": name, "status": "Up" if ifstat.get("status") else "Down", + "discovered": stat_name_matches_discovered(name, discovered_list), } mode = ifstat.get("mode") @@ -165,9 +230,9 @@ class RNStatusHandler: formatted_if["tx_bytes"] = ifstat["txb"] formatted_if["tx_bytes_str"] = size_str(ifstat["txb"]) if "rxs" in ifstat: - formatted_if["rx_packets"] = ifstat["rxs"] + formatted_if["rx_packets"] = fmt_packet_count(ifstat["rxs"]) if "txs" in ifstat: - formatted_if["tx_packets"] = ifstat["txs"] + formatted_if["tx_packets"] = fmt_packet_count(ifstat["txs"]) if "clients" in ifstat and ifstat["clients"] is not None: formatted_if["clients"] = ifstat["clients"] @@ -194,29 +259,29 @@ class RNStatusHandler: if "airtime_short" in ifstat and "airtime_long" in ifstat: formatted_if["airtime"] = { - "short": ifstat["airtime_short"], - "long": ifstat["airtime_long"], + "short": fmt_percentage(ifstat["airtime_short"]), + "long": fmt_percentage(ifstat["airtime_long"]), } if "channel_load_short" in ifstat and "channel_load_long" in ifstat: formatted_if["channel_load"] = { - "short": ifstat["channel_load_short"], - "long": ifstat["channel_load_long"], + "short": fmt_percentage(ifstat["channel_load_short"]), + "long": fmt_percentage(ifstat["channel_load_long"]), } if "peers" in ifstat and ifstat["peers"] is not None: formatted_if["peers"] = ifstat["peers"] if "incoming_announce_frequency" in ifstat: - formatted_if["incoming_announce_frequency"] = ifstat[ - "incoming_announce_frequency" - ] + formatted_if["incoming_announce_frequency"] = fmt_per_second( + ifstat["incoming_announce_frequency"] + ) if "outgoing_announce_frequency" in ifstat: - formatted_if["outgoing_announce_frequency"] = ifstat[ - "outgoing_announce_frequency" - ] + formatted_if["outgoing_announce_frequency"] = fmt_per_second( + ifstat["outgoing_announce_frequency"] + ) if "held_announces" in ifstat: - formatted_if["held_announces"] = ifstat["held_announces"] + formatted_if["held_announces"] = fmt_packet_count(ifstat["held_announces"]) if "ifac_netname" in ifstat and ifstat["ifac_netname"] is not None: formatted_if["network_name"] = ifstat["ifac_netname"] diff --git a/meshchatx/src/frontend/components/rnstatus/RNStatusPage.vue b/meshchatx/src/frontend/components/rnstatus/RNStatusPage.vue index e10e05e..6372018 100644 --- a/meshchatx/src/frontend/components/rnstatus/RNStatusPage.vue +++ b/meshchatx/src/frontend/components/rnstatus/RNStatusPage.vue @@ -4,7 +4,7 @@ >
-
+
Network Diagnostics @@ -17,27 +17,29 @@
-
+
-
-
+
-
Active Links: {{ linkCount }}
+
+ Active Links: {{ formatInt(linkCount) }} +
-
+
Blackhole: {{ blackholeEnabled ? "Publishing" : "Active" }} - {{ blackholeCount }} Identities + + {{ formatInt(blackholeCount) }} Identities +
-
+
Blackhole Sources
-
-
-
{{ iface.name }}
+
+
+
+
+

+ {{ iface.name }} +

+ + Discovered + +
+
{{ iface.status }}
-
+
Mode
{{ iface.mode }}
@@ -122,19 +151,25 @@
RX Packets
-
{{ iface.rx_packets }}
+
+ {{ iface.rx_packets }} +
TX Packets
-
{{ iface.tx_packets }}
+
+ {{ iface.tx_packets }} +
Clients
-
{{ iface.clients }}
+
{{ formatInt(iface.clients) }}
Peers
-
{{ iface.peers }} reachable
+
+ {{ formatInt(iface.peers) }} reachable +
Noise Floor
@@ -159,7 +194,7 @@
Battery
- {{ iface.battery_percent }}% + {{ formatInt(iface.battery_percent) }}% ({{ iface.battery_state }})
@@ -170,13 +205,13 @@
Incoming Announces
-
+
{{ iface.incoming_announce_frequency }}/s
Outgoing Announces
-
+
{{ iface.outgoing_announce_frequency }}/s
@@ -231,6 +266,16 @@ export default { this.refreshStatus(); }, methods: { + formatInt(value) { + if (value === null || value === undefined) { + return ""; + } + const n = Number(value); + if (Number.isNaN(n)) { + return String(value); + } + return n.toLocaleString(); + }, async refreshStatus() { this.isLoading = true; try { diff --git a/tests/frontend/RNStatusPage.test.js b/tests/frontend/RNStatusPage.test.js index ac528e5..d0ce96c 100644 --- a/tests/frontend/RNStatusPage.test.js +++ b/tests/frontend/RNStatusPage.test.js @@ -19,6 +19,7 @@ describe("RNStatusPage.vue", () => { { name: "Interface 1", status: "Up", + discovered: true, bitrate: "100 bps", rx_bytes_str: "10 B", tx_bytes_str: "5 B", @@ -62,6 +63,7 @@ describe("RNStatusPage.vue", () => { expect(wrapper.text()).toContain("RNStatus - Network Status"); expect(wrapper.text()).toContain("Interface 1"); + expect(wrapper.text()).toContain("Discovered"); expect(wrapper.text()).toContain("Active Links: 5"); expect(wrapper.text()).toContain("Blackhole: Publishing"); expect(wrapper.text()).toContain("src1");