import { useState, useEffect, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import api from "../api/client"; import useMqttWebSocket from "./useMqttWebSocket"; export default function MqttDashboard() { const [devices, setDevices] = useState([]); const [brokerConnected, setBrokerConnected] = useState(false); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [hoveredRow, setHoveredRow] = useState(null); const [liveEvents, setLiveEvents] = useState([]); const navigate = useNavigate(); const fetchStatus = async () => { try { const data = await api.get("/mqtt/status"); setDevices(data.devices || []); setBrokerConnected(data.broker_connected); setError(""); } catch (err) { setError(err.message); } finally { setLoading(false); } }; useEffect(() => { fetchStatus(); const interval = setInterval(fetchStatus, 30000); return () => clearInterval(interval); }, []); const handleWsMessage = useCallback((data) => { setLiveEvents((prev) => [data, ...prev].slice(0, 50)); // Update device status on heartbeat if (data.type === "status/heartbeat") { setDevices((prev) => { const existing = prev.find((d) => d.device_serial === data.device_serial); if (existing) { return prev.map((d) => d.device_serial === data.device_serial ? { ...d, online: true, seconds_since_heartbeat: 0 } : d ); } return prev; }); } }, []); const { connected: wsConnected } = useMqttWebSocket({ enabled: true, onMessage: handleWsMessage, }); const sendPing = async (serial, e) => { e.stopPropagation(); try { await api.post(`/mqtt/command/${serial}`, { cmd: "ping", contents: {} }); } catch (err) { setError(err.message); } }; const formatTime = (seconds) => { if (!seconds && seconds !== 0) return "Never"; if (seconds < 60) return `${seconds}s ago`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; return `${Math.floor(seconds / 86400)}d ago`; }; return (

MQTT Dashboard

Broker {brokerConnected ? "Connected" : "Disconnected"} Live
{error && (
{error}
)} {loading ? (
Loading...
) : devices.length === 0 ? (
No devices have sent heartbeats yet. Devices will appear here once they connect to the MQTT broker.
) : (
{devices.map((device, index) => ( setHoveredRow(device.device_serial)} onMouseLeave={() => setHoveredRow(null)} onClick={() => navigate(`/mqtt/commands?device=${device.device_serial}`)} > ))}
Status Serial Device ID Firmware IP Address Uptime Last Seen Actions
{device.device_serial} {device.last_heartbeat?.device_id || "-"} {device.last_heartbeat?.firmware_version ? `v${device.last_heartbeat.firmware_version}` : "-"} {device.last_heartbeat?.ip_address || "-"} {device.last_heartbeat?.uptime_display || "-"} {formatTime(device.seconds_since_heartbeat)} e.stopPropagation()}>
)} {/* Live Event Feed */}

Live Feed {wsConnected && ( (streaming) )}

{liveEvents.length === 0 ? (
{wsConnected ? "Waiting for MQTT messages..." : "WebSocket not connected — live events will appear once connected."}
) : (
{liveEvents.map((event, i) => ( ))}
Type Device Data
{event.type} {event.device_serial} {event.type === "logs" ? event.payload?.message || JSON.stringify(event.payload) : event.type === "status/heartbeat" ? `Uptime: ${event.payload?.payload?.timestamp || "?"} | IP: ${event.payload?.payload?.ip_address || "?"}` : JSON.stringify(event.payload).substring(0, 120)}
)}
); }