void })
{/* WS status */}
{wsStatus === 'connecting' && <>Connecting…>}
- {wsStatus === 'connected' && <>Connected>}
+ {wsStatus === 'connected' && <>Connected>}
{wsStatus === 'disconnected' && <>Reconnecting…>}
diff --git a/src/app/components/StatusPage.tsx b/src/app/components/StatusPage.tsx
index 5d09b72..38337eb 100644
--- a/src/app/components/StatusPage.tsx
+++ b/src/app/components/StatusPage.tsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
-import { HardDrive, Activity, Wifi, Signal, Clock, RefreshCw, FolderOpen, Map, Loader2 } from 'lucide-react';
+import { HardDrive, Activity, Wifi, Radio, Signal, Clock, RefreshCw, FolderOpen, Map, Loader2 } from 'lucide-react';
+import { useWs } from '../ws';
import DeviceDetailOverlay from './DeviceDetailOverlay';
import MediaSet from './MediaSet';
import { ImageWithFallback } from './figma/ImageWithFallback';
@@ -13,6 +14,8 @@ interface StatusPageProps {
}
export default function StatusPage({ config, setConfig }: StatusPageProps) {
+ const { status: wsStatus } = useWs();
+
// Mock memory stats
const memory = {
heap: { total: 4096, free: 1024 }, // in KB
@@ -444,6 +447,12 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) {
192.168.1.100
MAC Address
AA:BB:CC:DD:EE:FF
+
WebSocket
+
+ {wsStatus === 'connecting' && <>Connecting>}
+ {wsStatus === 'connected' && <>Connected>}
+ {wsStatus === 'disconnected' && <>Disconnected>}
+
Uptime
diff --git a/src/app/ws.tsx b/src/app/ws.tsx
new file mode 100644
index 0000000..e0d7cac
--- /dev/null
+++ b/src/app/ws.tsx
@@ -0,0 +1,57 @@
+import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
+
+export type WsStatus = 'connecting' | 'connected' | 'disconnected';
+
+interface WsContextValue {
+ status: WsStatus;
+ send: (msg: string) => void;
+ subscribe: (handler: (msg: string) => void) => () => void;
+}
+
+const WsContext = createContext
({
+ status: 'disconnected',
+ send: () => {},
+ subscribe: () => () => {},
+});
+
+export function WsProvider({ children }: { children: React.ReactNode }) {
+ const wsRef = useRef(null);
+ const listeners = useRef void>>(new Set());
+ const [status, setStatus] = useState('connecting');
+
+ useEffect(() => {
+ let cancelled = false;
+ let ws: WebSocket | null = null;
+
+ const connect = () => {
+ if (cancelled) return;
+ ws = new WebSocket(`ws://${window.location.hostname}/ws`);
+ wsRef.current = ws;
+ setStatus('connecting');
+ ws.onopen = () => { if (!cancelled) setStatus('connected'); };
+ ws.onmessage = (e) => { if (!cancelled) listeners.current.forEach(h => h(String(e.data))); };
+ ws.onclose = () => { if (!cancelled) { setStatus('disconnected'); setTimeout(connect, 3000); } };
+ ws.onerror = () => ws?.close();
+ };
+
+ connect();
+ return () => { cancelled = true; ws?.close(); };
+ }, []);
+
+ const send = useCallback((msg: string) => {
+ if (wsRef.current?.readyState === WebSocket.OPEN) wsRef.current.send(msg);
+ }, []);
+
+ const subscribe = useCallback((handler: (msg: string) => void) => {
+ listeners.current.add(handler);
+ return () => { listeners.current.delete(handler); };
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useWs = () => useContext(WsContext);