From 0fcea990063ff2bfed6631d678117e7fa922fbfe Mon Sep 17 00:00:00 2001 From: nessi Date: Wed, 18 Mar 2026 10:04:55 +0100 Subject: [PATCH] feat: add periodic tray menu refresh and normalize tunnel metrics field names Add background task to refresh tray menu every 5 seconds to keep status display current. Add RawTunnelMetrics type and normalizeTunnelMetrics helper to handle both snake_case and camelCase field names from backend responses. Update refreshTunnelMetrics to normalize metrics before setting state and explicitly cast active status to boolean. --- desktop-client/src-tauri/src/lib.rs | 8 ++++++++ desktop-client/src/App.tsx | 20 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/desktop-client/src-tauri/src/lib.rs b/desktop-client/src-tauri/src/lib.rs index 08e26ad..026cda6 100644 --- a/desktop-client/src-tauri/src/lib.rs +++ b/desktop-client/src-tauri/src/lib.rs @@ -665,6 +665,14 @@ pub fn run() { }); refresh_tray_menu(app.handle()); + let app_handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + loop { + refresh_tray_menu(&app_handle); + tauri::async_runtime::sleep(std::time::Duration::from_secs(5)).await; + } + }); + Ok(()) }) .on_window_event(|window, event| match event { diff --git a/desktop-client/src/App.tsx b/desktop-client/src/App.tsx index bb649cc..22becc2 100644 --- a/desktop-client/src/App.tsx +++ b/desktop-client/src/App.tsx @@ -17,6 +17,14 @@ type TunnelMetrics = { txBytes: number; }; +type RawTunnelMetrics = { + active?: boolean; + rxBytes?: number; + txBytes?: number; + rx_bytes?: number; + tx_bytes?: number; +}; + function formatInvokeError(err: unknown, fallback: string) { if (typeof err === "string" && err.trim().length > 0) { return err; @@ -67,6 +75,14 @@ function formatDataSize(bytes: number) { return `${value >= 100 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unitIndex]}`; } +function normalizeTunnelMetrics(value: RawTunnelMetrics | null | undefined): TunnelMetrics { + return { + active: Boolean(value?.active), + rxBytes: Number(value?.rxBytes ?? value?.rx_bytes ?? 0), + txBytes: Number(value?.txBytes ?? value?.tx_bytes ?? 0) + }; +} + export function App() { const [serverUrl, setServerUrl] = useState("http://localhost"); const [username, setUsername] = useState(""); @@ -84,14 +100,14 @@ export function App() { async function refreshTunnelMetrics() { try { - const value = await invoke("tunnel_metrics"); + const value = normalizeTunnelMetrics(await invoke("tunnel_metrics")); setMetrics(value); setConnected(value.active); } catch { try { const active = await invoke("tunnel_status"); setConnected(active); - setMetrics((current) => ({ ...current, active })); + setMetrics((current) => ({ ...current, active: Boolean(active) })); } catch { setMetrics({ active: false, rxBytes: 0, txBytes: 0 }); setConnected(false);