From d6f5c2c42f18b34be6a5b1cfdd9c79bce054e13a Mon Sep 17 00:00:00 2001 From: = Date: Sun, 11 Jan 2026 23:39:38 -0500 Subject: [PATCH 1/4] - still needs testing on rover (tested only on my local machine) but seems to work! - added ping library [https://www.npmjs.com/package/ping] to package,json - toast notifications to be done at a later date --- package-lock.json | 10 ++ package.json | 1 + src/app/dashboard/api/route.ts | 68 +++++++- .../panels/NetworkHealthTelemetryPanel.tsx | 159 +++++++++--------- 4 files changed, 151 insertions(+), 87 deletions(-) diff --git a/package-lock.json b/package-lock.json index da93818..142b1f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "leaflet": "^1.9.4", "next": "15.1.6", "next-runtime-env": "^3.3.0", + "ping": "^1.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hot-toast": "^2.6.0", @@ -5358,6 +5359,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/ping": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ping/-/ping-1.0.0.tgz", + "integrity": "sha512-3dxdgGtV+7P/EVo42JhkGSomeO/0GGicSz3mI/yK+AI+VGNAOfakw5XfcbGI4IjyBY+ZZwvuRXdhnNF2uliKew==", + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/pngparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pngparse/-/pngparse-2.0.1.tgz", diff --git a/package.json b/package.json index ec55702..3795dde 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "leaflet": "^1.9.4", "next": "15.1.6", "next-runtime-env": "^3.3.0", + "ping": "^1.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hot-toast": "^2.6.0", diff --git a/src/app/dashboard/api/route.ts b/src/app/dashboard/api/route.ts index 22188b2..2fa378f 100644 --- a/src/app/dashboard/api/route.ts +++ b/src/app/dashboard/api/route.ts @@ -2,6 +2,36 @@ const USERNAME = 'ubnt'; const PASSWORD = 'samitherover'; const baseStationIP = '192.168.0.2'; +const hosts = ['192.168.0.2', '172.19.228.1']; // Add more hosts here as needed (must match hosts in NetworkHealthTelemetryPanel.tsx) + +const ping = require('ping'); + +// Ping configuration +const pingConfig = { + timeout: 5, +}; + +// Function to ping multiple hosts and return RTT times in dictionary format +async function pingHosts(hosts: string[]): Promise<{ [key: string]: number }> { + const results: { [key: string]: number } = {}; + + const pingPromises = hosts.map(async (host) => { + try { + const res = await ping.promise.probe(host, pingConfig); + if (res.alive) { + results[host] = parseFloat(res.time as string); + } else { + results[host] = -1; + } + } catch (error) { + results[host] = -1; + } + }); + + await Promise.all(pingPromises); + return results; +} + // Authenticates with the base station async function authenticate() { const response = await fetch(`http://${baseStationIP}/api/auth`, { @@ -39,25 +69,45 @@ async function fetchStatus(cookie: string) { } export async function GET(request: Request) { + // Initialize response data with defaults + let uplinkCapacity = 0; + let downlinkCapacity = 0; + let uplinkThroughput = 0; + let downlinkThroughput = 0; + let baseStationError: string | null = null; + + // Try to fetch base station data, but don't fail if it's unavailable try { const authStatus = await authenticate(); - - const status = await fetchStatus(authStatus.cookie); - // Extract only the needed fields for the frontend - const uplinkCapacity = status.wireless?.polling?.ucap ?? 0; - const downlinkCapacity = status.wireless?.polling?.dcap ?? 0; - const uplinkThroughput = status.wireless?.throughput?.tx ?? 0; - const downlinkThroughput = status.wireless?.throughput?.rx ?? 0; + uplinkCapacity = status.wireless?.polling?.ucap ?? 0; + downlinkCapacity = status.wireless?.polling?.dcap ?? 0; + uplinkThroughput = status.wireless?.throughput?.tx ?? 0; + downlinkThroughput = status.wireless?.throughput?.rx ?? 0; + } catch (error: any) { + baseStationError = error.message; + console.warn('Failed to fetch base station data:', error.message); + } + + // Always ping hosts, regardless of base station status + try { + const pings = await pingHosts(hosts); return Response.json({ uplinkCapacity, downlinkCapacity, uplinkThroughput, downlinkThroughput, + pings, + baseStationError, // Include error info in response }); } catch (error: any) { - return new Response(JSON.stringify({ error: error.message }), { status: 500 }); + console.error('Failed to ping hosts:', error); // put toast notifications here LATER + return Response.json( + { error: 'Failed to ping hosts: ' + error.message }, + { status: 500 } + ); } -} \ No newline at end of file +} + diff --git a/src/components/panels/NetworkHealthTelemetryPanel.tsx b/src/components/panels/NetworkHealthTelemetryPanel.tsx index 7496694..05d8775 100644 --- a/src/components/panels/NetworkHealthTelemetryPanel.tsx +++ b/src/components/panels/NetworkHealthTelemetryPanel.tsx @@ -1,74 +1,83 @@ import React, { useEffect, useState } from "react"; -import { - BarChart, - Bar, - XAxis, - YAxis, - Tooltip, - ResponsiveContainer, - Cell, - LabelList, -} from "recharts"; + +const dotStyle = (up: boolean): React.CSSProperties => ({ + width: 12, + height: 12, + borderRadius: "50%", + display: "inline-block", + backgroundColor: up ? "#22c55e" : "#ef4444", +}); + +const hosts = ["192.168.0.2", "172.19.228.1"]; // Add more hosts here as needed (must match hosts in route.ts) +const hostnames: { [key: string]: string } = {"192.168.0.2": "Base"}; // set nicknames here + const NetworkHealthTelemetryPanel: React.FC = () => { const [stats, setStats] = useState({ - uplinkThroughput: 0, - downlinkThroughput: 0, - uplinkCapacity: 100, - downlinkCapacity: 100, + uplinkRttMs: 0, + downlinkRttMs: 0, + uplinkUp: false, + downlinkUp: false, }); + const [pings, setPings] = useState<{ [key: string]: number }>({}); + const [baseStationError, setBaseStationError] = useState(null); + + console.log('pings state:', pings); + useEffect(() => { let interval: NodeJS.Timeout; - // Polling function to fetch data from the API route + const poll = async () => { try { - const response = await fetch("/dashboard/api", { + const response = await fetch("/dashboard/api", { method: "GET", + headers: { + "Content-Type": "application/json", + } }); if (!response.ok) { - console.error("Failed to fetch status from API route"); + console.error(`API returned ${response.status}: ${response.statusText}`); return; } - const data = await response.json(); - const uplinkCapacity = data.uplinkCapacity ?? 0; - const downlinkCapacity = data.downlinkCapacity ?? 0; - const uplinkThroughput = data.uplinkThroughput?? 0; - const downlinkThroughput = data.downlinkThroughput ?? 0; + const data = await response.json(); setStats({ - uplinkThroughput, - downlinkThroughput, - uplinkCapacity, - downlinkCapacity, + uplinkRttMs: data.uplinkRttMs ?? 0, + downlinkRttMs: data.downlinkRttMs ?? 0, + uplinkUp: Boolean(data.uplinkUp), + downlinkUp: Boolean(data.downlinkUp), }); + // Set ping results for each host + if (data.pings) { + setPings(data.pings); + } + // Track base station errors + if (data.baseStationError) { + setBaseStationError(data.baseStationError); + } else { + setBaseStationError(null); + } } catch (error) { console.error("Polling error:", error); } }; - // Initial poll + start interval poll(); interval = setInterval(poll, 1000); - // Cleanup on unmount - return () => { - if (interval) clearInterval(interval); - }; + return () => clearInterval(interval); }, []); - // rounded throughput kbps / 10 to get 2 decimal places, then divided by 100 to finish conversion to Mbps - const data = [ - { - name: "Uplink", - Throughput: Math.round(stats.uplinkThroughput / 10) / 100, - Capacity: stats.uplinkCapacity / 1000, - }, - { - name: "Downlink", - Throughput: Math.round(stats.downlinkThroughput / 10) / 100, - Capacity: stats.downlinkCapacity / 1000, - }, + const rows = [ + // Add ping results for each host + ...hosts.map((host) => ({ + + name: hostnames[host] ?? host, + rttMs: pings[host] ?? 0, + up: pings[host] !== -1 && pings[host] !== undefined, + + })), ]; return ( @@ -83,42 +92,36 @@ const NetworkHealthTelemetryPanel: React.FC = () => { flexDirection: "column", }} > +
+ + + + + + + + -
- - - Math.floor(dataMax * 1.2)]} - stroke="#ccc" - tick={{ fill: "#ccc" }} - /> - - - - - - {data.map((entry, index) => ( - 0.85 - ? "#ef4444" - : "#3b82f6" - } - /> - ))} - - - +
+ {rows.map((r) => ( + + + + + + ))} + +
+ Name + + RTT (ms) + + Status +
{r.name} + {r.rttMs === -1 ? "Offline" : r.rttMs} + + +
); From 1a5ffa4a7365f1973d800ff579fb0de890faf893 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 14 Jan 2026 22:40:16 -0500 Subject: [PATCH 2/4] - Timeout duration changed to 1s - hosts are now solely taken from route.js --- src/app/dashboard/api/route.ts | 4 ++-- src/components/panels/NetworkHealthTelemetryPanel.tsx | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/dashboard/api/route.ts b/src/app/dashboard/api/route.ts index 2fa378f..c5dd128 100644 --- a/src/app/dashboard/api/route.ts +++ b/src/app/dashboard/api/route.ts @@ -2,13 +2,13 @@ const USERNAME = 'ubnt'; const PASSWORD = 'samitherover'; const baseStationIP = '192.168.0.2'; -const hosts = ['192.168.0.2', '172.19.228.1']; // Add more hosts here as needed (must match hosts in NetworkHealthTelemetryPanel.tsx) +const hosts = ['192.168.0.2', '172.19.228.1']; // Add more hosts here as needed const ping = require('ping'); // Ping configuration const pingConfig = { - timeout: 5, + timeout: 1, // timeout set to 1 second }; // Function to ping multiple hosts and return RTT times in dictionary format diff --git a/src/components/panels/NetworkHealthTelemetryPanel.tsx b/src/components/panels/NetworkHealthTelemetryPanel.tsx index 05d8775..debf9a4 100644 --- a/src/components/panels/NetworkHealthTelemetryPanel.tsx +++ b/src/components/panels/NetworkHealthTelemetryPanel.tsx @@ -8,9 +8,6 @@ const dotStyle = (up: boolean): React.CSSProperties => ({ backgroundColor: up ? "#22c55e" : "#ef4444", }); -const hosts = ["192.168.0.2", "172.19.228.1"]; // Add more hosts here as needed (must match hosts in route.ts) -const hostnames: { [key: string]: string } = {"192.168.0.2": "Base"}; // set nicknames here - const NetworkHealthTelemetryPanel: React.FC = () => { const [stats, setStats] = useState({ uplinkRttMs: 0, @@ -71,9 +68,9 @@ const NetworkHealthTelemetryPanel: React.FC = () => { const rows = [ // Add ping results for each host - ...hosts.map((host) => ({ + ...Object.keys(pings).map((host) => ({ - name: hostnames[host] ?? host, + name: host, rttMs: pings[host] ?? 0, up: pings[host] !== -1 && pings[host] !== undefined, From 934b1d2baf4ccb144ebdb43f38bee0893efd4262 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 15 Jan 2026 01:43:27 -0500 Subject: [PATCH 3/4] - original graph added back into the panel - basically merged the two versions together - removed a LOT of unnecessary padding and made graph shorter - should be able to see ~ 5 devices status w/ graph now - as always needs testing with rover before merging --- .../panels/NetworkHealthTelemetryPanel.tsx | 96 +++++++++++++++++-- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/src/components/panels/NetworkHealthTelemetryPanel.tsx b/src/components/panels/NetworkHealthTelemetryPanel.tsx index debf9a4..eb5f52e 100644 --- a/src/components/panels/NetworkHealthTelemetryPanel.tsx +++ b/src/components/panels/NetworkHealthTelemetryPanel.tsx @@ -1,4 +1,14 @@ import React, { useEffect, useState } from "react"; +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + Cell, + LabelList, +} from "recharts"; const dotStyle = (up: boolean): React.CSSProperties => ({ width: 12, @@ -10,10 +20,10 @@ const dotStyle = (up: boolean): React.CSSProperties => ({ const NetworkHealthTelemetryPanel: React.FC = () => { const [stats, setStats] = useState({ - uplinkRttMs: 0, - downlinkRttMs: 0, - uplinkUp: false, - downlinkUp: false, + uplinkThroughput: 0, + downlinkThroughput: 0, + uplinkCapacity: 100, + downlinkCapacity: 100, }); const [pings, setPings] = useState<{ [key: string]: number }>({}); @@ -39,11 +49,15 @@ const NetworkHealthTelemetryPanel: React.FC = () => { const data = await response.json(); + const uplinkCapacity = data.uplinkCapacity ?? 0; + const downlinkCapacity = data.downlinkCapacity ?? 0; + const uplinkThroughput = data.uplinkThroughput?? 0; + const downlinkThroughput = data.downlinkThroughput ?? 0; setStats({ - uplinkRttMs: data.uplinkRttMs ?? 0, - downlinkRttMs: data.downlinkRttMs ?? 0, - uplinkUp: Boolean(data.uplinkUp), - downlinkUp: Boolean(data.downlinkUp), + uplinkThroughput, + downlinkThroughput, + uplinkCapacity, + downlinkCapacity, }); // Set ping results for each host if (data.pings) { @@ -77,19 +91,32 @@ const NetworkHealthTelemetryPanel: React.FC = () => { })), ]; + const data = [ + { + name: "Uplink", + Throughput: Math.round(stats.uplinkThroughput / 10) / 100, + Capacity: stats.uplinkCapacity / 1000, + }, + { + name: "Downlink", + Throughput: Math.round(stats.downlinkThroughput / 10) / 100, + Capacity: stats.downlinkCapacity / 1000, + }, + ]; + return (
-
+
@@ -120,7 +147,56 @@ const NetworkHealthTelemetryPanel: React.FC = () => {
+
+ +
+ + + Math.floor(dataMax * 1.2)]} + stroke="#ccc" + tick={{ fill: "#ccc" }} + /> + + + + + + {data.map((entry, index) => ( + 0.85 + ? "#ef4444" + : "#3b82f6" + } + /> + ))} + + + +
+
); }; From 8488e4f3aa52fa08f418add6c4764919824e8a35 Mon Sep 17 00:00:00 2001 From: Jack Wong <120768324+jackcwong2007@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:18:24 -0500 Subject: [PATCH 4/4] removed comment --- src/app/dashboard/api/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/dashboard/api/route.ts b/src/app/dashboard/api/route.ts index c5dd128..b4c9131 100644 --- a/src/app/dashboard/api/route.ts +++ b/src/app/dashboard/api/route.ts @@ -103,7 +103,7 @@ export async function GET(request: Request) { baseStationError, // Include error info in response }); } catch (error: any) { - console.error('Failed to ping hosts:', error); // put toast notifications here LATER + console.error('Failed to ping hosts:', error); return Response.json( { error: 'Failed to ping hosts: ' + error.message }, { status: 500 }