import { useCallback, useEffect, useMemo, useState } from 'react'; import { api } from '../api'; import { toast } from './ui/sonner'; import { Button } from './ui/button'; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet'; import type { Contact, PaneState, RepeaterAclResponse, RepeaterLppTelemetryResponse, RepeaterStatusResponse, } from '../types'; import { TelemetryPane } from './repeater/RepeaterTelemetryPane'; import { AclPane } from './repeater/RepeaterAclPane'; import { LppTelemetryPane } from './repeater/RepeaterLppTelemetryPane'; import { ConsolePane } from './repeater/RepeaterConsolePane'; import { RepeaterLogin } from './RepeaterLogin'; import { useRememberedServerPassword } from '../hooks/useRememberedServerPassword'; interface RoomServerPanelProps { contact: Contact; onAuthenticatedChange?: (authenticated: boolean) => void; } type RoomPaneKey = 'status' | 'acl' | 'lppTelemetry'; type RoomPaneData = { status: RepeaterStatusResponse | null; acl: RepeaterAclResponse | null; lppTelemetry: RepeaterLppTelemetryResponse | null; }; type RoomPaneStates = Record; type ConsoleEntry = { command: string; response: string; timestamp: number; outgoing: boolean; }; const INITIAL_PANE_STATE: PaneState = { loading: false, attempt: 0, error: null, fetched_at: null, }; function createInitialPaneStates(): RoomPaneStates { return { status: { ...INITIAL_PANE_STATE }, acl: { ...INITIAL_PANE_STATE }, lppTelemetry: { ...INITIAL_PANE_STATE }, }; } export function RoomServerPanel({ contact, onAuthenticatedChange }: RoomServerPanelProps) { const { password, setPassword, rememberPassword, setRememberPassword, persistAfterLogin } = useRememberedServerPassword('room', contact.public_key); const [loginLoading, setLoginLoading] = useState(false); const [loginError, setLoginError] = useState(null); const [authenticated, setAuthenticated] = useState(false); const [advancedOpen, setAdvancedOpen] = useState(false); const [paneData, setPaneData] = useState({ status: null, acl: null, lppTelemetry: null, }); const [paneStates, setPaneStates] = useState(createInitialPaneStates); const [consoleHistory, setConsoleHistory] = useState([]); const [consoleLoading, setConsoleLoading] = useState(false); useEffect(() => { setLoginLoading(false); setLoginError(null); setAuthenticated(false); setAdvancedOpen(false); setPaneData({ status: null, acl: null, lppTelemetry: null, }); setPaneStates(createInitialPaneStates()); setConsoleHistory([]); setConsoleLoading(false); }, [contact.public_key]); useEffect(() => { onAuthenticatedChange?.(authenticated); }, [authenticated, onAuthenticatedChange]); const refreshPane = useCallback( async (pane: K, loader: () => Promise) => { setPaneStates((prev) => ({ ...prev, [pane]: { ...prev[pane], loading: true, attempt: prev[pane].attempt + 1, error: null, }, })); try { const data = await loader(); setPaneData((prev) => ({ ...prev, [pane]: data })); setPaneStates((prev) => ({ ...prev, [pane]: { loading: false, attempt: prev[pane].attempt, error: null, fetched_at: Date.now(), }, })); } catch (err) { setPaneStates((prev) => ({ ...prev, [pane]: { ...prev[pane], loading: false, error: err instanceof Error ? err.message : 'Unknown error', }, })); } }, [] ); const performLogin = useCallback( async (password: string) => { if (loginLoading) return; setLoginLoading(true); setLoginError(null); try { const result = await api.roomLogin(contact.public_key, password); setAuthenticated(true); if (result.authenticated) { toast.success('Room login confirmed'); } else { toast.warning('Room login not confirmed', { description: result.message ?? 'Room login was not confirmed', }); } } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error'; setAuthenticated(true); setLoginError(message); toast.error('Room login failed', { description: message }); } finally { setLoginLoading(false); } }, [contact.public_key, loginLoading] ); const handleLogin = useCallback( async (password: string) => { await performLogin(password); persistAfterLogin(password); }, [performLogin, persistAfterLogin] ); const handleLoginAsGuest = useCallback(async () => { await performLogin(''); persistAfterLogin(''); }, [performLogin, persistAfterLogin]); const handleConsoleCommand = useCallback( async (command: string) => { setConsoleLoading(true); const timestamp = Date.now(); setConsoleHistory((prev) => [ ...prev, { command, response: command, timestamp, outgoing: true }, ]); try { const response = await api.sendRepeaterCommand(contact.public_key, command); setConsoleHistory((prev) => [ ...prev, { command, response: response.response, timestamp: Date.now(), outgoing: false, }, ]); } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error'; setConsoleHistory((prev) => [ ...prev, { command, response: `(error) ${message}`, timestamp: Date.now(), outgoing: false, }, ]); } finally { setConsoleLoading(false); } }, [contact.public_key] ); const panelTitle = useMemo(() => contact.name || contact.public_key.slice(0, 12), [contact]); if (!authenticated) { return (
Room server access is experimental and in public alpha. Please report any issues on{' '} GitHub .
); } return (
Room Server Tools Room server telemetry, ACL tools, sensor data, and CLI console

Room Server Tools

{panelTitle}

refreshPane('status', () => api.roomStatus(contact.public_key))} /> refreshPane('acl', () => api.roomAcl(contact.public_key))} /> refreshPane('lppTelemetry', () => api.roomLppTelemetry(contact.public_key)) } />
); }