diff --git a/src/app/dashboard/api/route.ts b/src/app/dashboard/api/route.ts index edf3109..798182e 100644 --- a/src/app/dashboard/api/route.ts +++ b/src/app/dashboard/api/route.ts @@ -1,14 +1,3 @@ -import fetch from 'node-fetch'; -import fetchCookie from 'fetch-cookie'; -import { CookieJar } from 'tough-cookie'; - -const jar = new CookieJar(); -const fetchWithCookies = fetchCookie(fetch, jar); - -const USERNAME = 'ubnt'; -const PASSWORD = 'samitherover'; -const baseStationIP = '192.168.0.2'; - const hosts = ['192.168.0.2', '192.168.0.3', '192.168.0.55']; // Add more hosts here as needed const ping = require('ping'); @@ -39,75 +28,10 @@ async function pingHosts(hosts: string[]): Promise<{ [key: string]: number }> { return results; } -// Authenticates with the base station -async function authenticate() { - const formData = new URLSearchParams(); - formData.append('uri', '/index.cgi'); - formData.append('username', USERNAME); - formData.append('password', PASSWORD); - - const response = await fetchWithCookies(`http://${baseStationIP}/login.cgi`, { - method: 'POST', - body: formData, - redirect: 'manual', - }); - - if (response.status !== 302) { - throw new Error(`Authentication failed: ${response.status}`); - } -} - -// Fetches status JSON from the base station -async function fetchStatus() { - const response = await fetchWithCookies(`http://${baseStationIP}/status.cgi`, { - method: 'GET', - redirect: 'manual', - }); - - - if (response.status === 302) { - await authenticate(); - throw new Error('Not authenticated (redirected to login)'); - } - - if (!response.ok) { - throw new Error(`Failed to fetch status: ${response.status}`); - } - return response.json(); -} - 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 status : any = await fetchStatus(); - uplinkCapacity = status.wireless?.txrate ?? 0; - downlinkCapacity = status.wireless?.rxrate ?? 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 - }); + return Response.json({pings}); } catch (error: any) { console.error('Failed to ping hosts:', error); return Response.json( diff --git a/src/components/panels/NetworkHealthTelemetryPanel.tsx b/src/components/panels/NetworkHealthTelemetryPanel.tsx index 07ebbf3..02b6efa 100644 --- a/src/components/panels/NetworkHealthTelemetryPanel.tsx +++ b/src/components/panels/NetworkHealthTelemetryPanel.tsx @@ -1,4 +1,8 @@ +'use client'; + import React, { useEffect, useState } from "react"; +import ROSLIB from "roslib"; +import { useROS } from "@/ros/ROSContext"; import { BarChart, Bar, @@ -19,29 +23,32 @@ const dotStyle = (up: boolean): React.CSSProperties => ({ }); const NetworkHealthTelemetryPanel: React.FC = () => { + const { ros } = useROS(); + const [stats, setStats] = useState({ - uplinkThroughput: 0, - downlinkThroughput: 0, - uplinkCapacity: 100, - downlinkCapacity: 100, + bandwidthTx: 0, + bandwidthRx: 0, + throughputTx: 0, + throughputRx: 0, + signalStrength: 0, + noiseFloor: 0, + ccqTx: 0, }); const [pings, setPings] = useState<{ [key: string]: number }>({}); - const [baseStationError, setBaseStationError] = useState(null); - - console.log('pings state:', pings); useEffect(() => { let interval: NodeJS.Timeout; 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(`API returned ${response.status}: ${response.statusText}`); return; @@ -49,26 +56,9 @@ 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({ - 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); } @@ -80,30 +70,132 @@ const NetworkHealthTelemetryPanel: React.FC = () => { return () => clearInterval(interval); }, []); - const rows = [ - // Add ping results for each host - ...Object.keys(pings).map((host) => ({ + useEffect(() => { + if (!ros) return; - name: host, - rttMs: pings[host] ?? 0, - up: pings[host] !== -1 && pings[host] !== undefined, + const bandwidthTxTopic = new ROSLIB.Topic({ + ros, + name: "/snmp_network_stats/bandwidth_tx", + messageType: "std_msgs/msg/Float32", + }); - })), - ]; + const bandwidthRxTopic = new ROSLIB.Topic({ + ros, + name: "/snmp_network_stats/bandwidth_rx", + messageType: "std_msgs/msg/Float32", + }); + + const throughputTxTopic = new ROSLIB.Topic({ + ros, + name: "/snmp_network_stats/throughput_tx", + messageType: "std_msgs/msg/Float32", + }); + + const throughputRxTopic = new ROSLIB.Topic({ + ros, + name: "/snmp_network_stats/throughput_rx", + messageType: "std_msgs/msg/Float32", + }); + + const signalStrengthTopic = new ROSLIB.Topic({ + ros, + name: "/snmp_network_stats/signal_strength", + messageType: "std_msgs/msg/Float32", + }); + + const noiseFloorTopic = new ROSLIB.Topic({ + ros, + name: "/snmp_network_stats/noise_floor", + messageType: "std_msgs/msg/Float32", + }); + + const ccqTxTopic = new ROSLIB.Topic({ + ros, + name: "/snmp_network_stats/ccq_tx", + messageType: "std_msgs/msg/Float32", + }); + + const handleBandwidthTx = (msg: ROSLIB.Message) => { + const data = (msg as any).data as number; + setStats((prev) => ({ ...prev, bandwidthTx: data })); + }; + + const handleBandwidthRx = (msg: ROSLIB.Message) => { + const data = (msg as any).data as number; + setStats((prev) => ({ ...prev, bandwidthRx: data })); + }; + + const handleThroughputTx = (msg: ROSLIB.Message) => { + const data = (msg as any).data as number; + setStats((prev) => ({ ...prev, throughputTx: data })); + }; + + const handleThroughputRx = (msg: ROSLIB.Message) => { + const data = (msg as any).data as number; + setStats((prev) => ({ ...prev, throughputRx: data })); + }; + + const handleSignalStrength = (msg: ROSLIB.Message) => { + const data = (msg as any).data as number; + setStats((prev) => ({ ...prev, signalStrength: data })); + }; + + const handleNoiseFloor = (msg: ROSLIB.Message) => { + const data = (msg as any).data as number; + setStats((prev) => ({ ...prev, noiseFloor: data })); + }; + + const handleCcqTx = (msg: ROSLIB.Message) => { + const data = (msg as any).data as number; + setStats((prev) => ({ ...prev, ccqTx: data })); + }; + + bandwidthTxTopic.subscribe(handleBandwidthTx); + bandwidthRxTopic.subscribe(handleBandwidthRx); + throughputTxTopic.subscribe(handleThroughputTx); + throughputRxTopic.subscribe(handleThroughputRx); + signalStrengthTopic.subscribe(handleSignalStrength); + noiseFloorTopic.subscribe(handleNoiseFloor); + ccqTxTopic.subscribe(handleCcqTx); + + return () => { + bandwidthTxTopic.unsubscribe(handleBandwidthTx); + bandwidthRxTopic.unsubscribe(handleBandwidthRx); + throughputTxTopic.unsubscribe(handleThroughputTx); + throughputRxTopic.unsubscribe(handleThroughputRx); + signalStrengthTopic.unsubscribe(handleSignalStrength); + noiseFloorTopic.unsubscribe(handleNoiseFloor); + ccqTxTopic.unsubscribe(handleCcqTx); + }; + }, [ros]); + + const pingRows = Object.keys(pings).map((host) => ({ + name: host, + rttMs: pings[host] ?? 0, + up: pings[host] !== -1 && pings[host] !== undefined, + })); + + const toMbps = (bitsPerSecond: number) => bitsPerSecond / 1_000_000; - const data = [ + const throughputData = [ { - name: "Uplink", - Throughput: Math.round(stats.uplinkThroughput / 10) / 100, - Capacity: stats.uplinkCapacity, + name: "TX", + Throughput: toMbps(stats.throughputTx), + Capacity: toMbps(stats.bandwidthTx), }, { - name: "Downlink", - Throughput: Math.round(stats.downlinkThroughput / 10) / 100, - Capacity: stats.downlinkCapacity, + name: "RX", + Throughput: toMbps(stats.throughputRx), + Capacity: toMbps(stats.bandwidthRx), }, ]; + const telemetryRows = [ + { name: "Signal Strength", value: stats.signalStrength.toFixed(2), unit: "dBm" }, + { name: "Noise Floor", value: stats.noiseFloor.toFixed(2), unit: "dBm" }, + { name: "CCQ TX", value: stats.ccqTx.toFixed(2), unit: "%" }, + ]; + return (
{ - Name + Host RTT (ms) @@ -131,9 +223,8 @@ const NetworkHealthTelemetryPanel: React.FC = () => { - - {rows.map((r) => ( + {pingRows.map((r) => ( {r.name} @@ -147,56 +238,81 @@ const NetworkHealthTelemetryPanel: React.FC = () => {
-
-
- - - Math.floor(dataMax * 1.2)]} - stroke="#ccc" - tick={{ fill: "#ccc" }} - /> - - - - - - {data.map((entry, index) => ( - 0.85 - ? "#ef4444" - : "#3b82f6" - } - /> - ))} - - - +
+ + + + + + + + + {telemetryRows.map((row) => ( + + + + + ))} + +
+ Metric + + Value +
{row.name} + {row.value} {row.unit} +
+
+ +
+
+ + + Math.max(1, Math.floor(dataMax * 1.2))]} + stroke="#ccc" + tick={{ fill: "#ccc" }} + /> + + + + + + {throughputData.map((entry, index) => ( + 0 && entry.Throughput / entry.Capacity > 0.85 + ? "#ef4444" + : "#3b82f6" + } + /> + ))} + + + +
-
); };