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..b4c9131 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 + +const ping = require('ping'); + +// Ping configuration +const pingConfig = { + timeout: 1, // timeout set to 1 second +}; + +// 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); + 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..eb5f52e 100644 --- a/src/components/panels/NetworkHealthTelemetryPanel.tsx +++ b/src/components/panels/NetworkHealthTelemetryPanel.tsx @@ -9,6 +9,15 @@ import { Cell, LabelList, } from "recharts"; + +const dotStyle = (up: boolean): React.CSSProperties => ({ + width: 12, + height: 12, + borderRadius: "50%", + display: "inline-block", + backgroundColor: up ? "#22c55e" : "#ef4444", +}); + const NetworkHealthTelemetryPanel: React.FC = () => { const [stats, setStats] = useState({ uplinkThroughput: 0, @@ -17,47 +26,71 @@ const NetworkHealthTelemetryPanel: React.FC = () => { downlinkCapacity: 100, }); + 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; - setStats({ uplinkThroughput, downlinkThroughput, uplinkCapacity, downlinkCapacity, }); + // 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 rows = [ + // Add ping results for each host + ...Object.keys(pings).map((host) => ({ + + name: host, + rttMs: pings[host] ?? 0, + up: pings[host] !== -1 && pings[host] !== undefined, + + })), + ]; + const data = [ { name: "Uplink", @@ -72,6 +105,48 @@ const NetworkHealthTelemetryPanel: React.FC = () => { ]; return ( +
+
+ + + + + + + + + + + {rows.map((r) => ( + + + + + + ))} + +
+ Name + + RTT (ms) + + Status +
{r.name} + {r.rttMs === -1 ? "Offline" : r.rttMs} + + +
+
{ }} > -
+
{
+
); };