From ed125f613d8fdde45da3a728b7d23bf9288d23c3 Mon Sep 17 00:00:00 2001 From: mblanke Date: Tue, 17 Feb 2026 09:16:30 -0500 Subject: [PATCH] feat: GB10 GPU panels + enhanced UniFi widget with health data - Added GB10 Fabric row: GPU Utilization A/B + Network Throughput - UniFi widget now shows WAN/WLAN/LAN/Internet health subsystems - Displays ISP name, bandwidth rates, latency, client counts per subsystem - Status dots (green/yellow) for each subsystem --- src/app/page.tsx | 19 +++++++ src/components/UnifiWidget.tsx | 97 +++++++++++++++++++++++++++++----- 2 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index e775a9e..965ecef 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -204,6 +204,25 @@ export default function Home() { /> + {/* GB10 Fabric Overview */} +
+ + + +
+ {/* Semantic Search */}
diff --git a/src/components/UnifiWidget.tsx b/src/components/UnifiWidget.tsx index 864e202..c12a5aa 100644 --- a/src/components/UnifiWidget.tsx +++ b/src/components/UnifiWidget.tsx @@ -1,33 +1,53 @@ "use client"; import { useEffect, useState } from "react"; -import { Wifi, WifiOff } from "lucide-react"; +import { Wifi, WifiOff, Globe, Radio, Network, ArrowDown, ArrowUp } from "lucide-react"; + +interface Health { + wan?: { status: string; wanIp: string; isp: string; txRate: number; rxRate: number; gateways: number; clients: number }; + www?: { status: string; latency: number; uptime: number; downSpeed: number; upSpeed: number }; + wlan?: { status: string; adopted: number; clients: number; aps: number; txRate: number; rxRate: number }; + lan?: { status: string; adopted: number; clients: number; switches: number; txRate: number; rxRate: number }; + vpn?: { status: string }; +} + +function formatRate(bytesPerSec: number): string { + if (bytesPerSec >= 1_000_000) return (bytesPerSec / 1_000_000).toFixed(1) + " MB/s"; + if (bytesPerSec >= 1_000) return (bytesPerSec / 1_000).toFixed(0) + " KB/s"; + return bytesPerSec + " B/s"; +} + +function StatusDot({ status }: { status: string }) { + const color = status === "ok" ? "bg-green-400" : status === "unknown" ? "bg-gray-500" : "bg-yellow-400"; + return ; +} export default function UnifiWidget() { const [devices, setDevices] = useState([]); + const [health, setHealth] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); useEffect(() => { - fetchDevices(); - const interval = setInterval(fetchDevices, 30000); + fetchData(); + const interval = setInterval(fetchData, 30000); return () => clearInterval(interval); }, []); - const fetchDevices = async () => { + const fetchData = async () => { try { const response = await fetch("/api/unifi"); if (response.ok) { const data = await response.json(); - // API returns { devices, health, totalClients } — extract the array const devArr = Array.isArray(data) ? data : Array.isArray(data?.devices) ? data.devices : []; setDevices(devArr); + setHealth(data?.health || null); setError(false); } else { setError(true); } setLoading(false); - } catch (err) { + } catch { setError(true); setLoading(false); } @@ -43,6 +63,9 @@ export default function UnifiWidget() { UniFi Network + {health?.wan && ( + {health.wan.isp} + )}
{loading ? ( @@ -52,24 +75,70 @@ export default function UnifiWidget() { ) : error ? (
-

- Configure UniFi credentials in .env -

+

Configure UniFi credentials in .env

) : ( -
-
+
+ {/* Top stats */} +

Devices Online

-

- {onlineDevices}/{devices.length} -

+

{onlineDevices}/{devices.length}

Connected Clients

{totalClients}

+ + {/* Subsystem health */} + {health && ( +
+ {health.wan && ( +
+ + + + WAN + + + {formatRate(health.wan.rxRate)} + {formatRate(health.wan.txRate)} + +
+ )} + {health.wlan && ( +
+ + + + WLAN + + {health.wlan.clients} clients · {health.wlan.aps} APs +
+ )} + {health.lan && ( +
+ + + + LAN + + {health.lan.clients} clients · {health.lan.switches} switches +
+ )} + {health.www && ( +
+ + + + Internet + + {health.www.latency}ms latency +
+ )} +
+ )}
)}