import { useState, useEffect, useRef } from "react"; import { Link, useNavigate } from "react-router-dom"; import type { User, Project, DroneRegistration, ChatSession, SubProcessStat } from "../lib/api"; import { projectApi, droneApi, chatSessionApi } from "../lib/api"; import { socketClient } from "../lib/socket"; import Clock from "../components/Clock"; import GadgetGrid from "../components/GadgetGrid"; import LogRenderer from "../components/LogRenderer"; import type { LogRendererEntry } from "../components/LogRenderer"; function SystemReady() { return (
// SYSTEM READY
Please sign in to continue.
Accounts are administered. Contact your administrator for access.
Sign In
); } interface DashboardSidebarProps { onNavigate: (view: string, data?: unknown) => void; selectedDrone: DroneRegistration | null; onSelectDrone: (drone: DroneRegistration | null) => void; } function DroneInspector({ drone, onClose, }: { drone: DroneRegistration; onClose: () => void; }) { const navigate = useNavigate(); const [processStats, setProcessStats] = useState([]); const [logEntries, setLogEntries] = useState([]); const mountedRef = useRef(true); const pollRef = useRef | null>(null); useEffect(() => { mountedRef.current = true; const regId = drone._id; if (drone.status === 'offline') return; socketClient.subscribeDrone(regId); const handleLog = (data: { timestamp: string; level: string; component: string; message: string }) => { if (!mountedRef.current) return; setLogEntries((prev) => { const next = [...prev, { id: `insp-log-${prev.length}`, timestamp: new Date(data.timestamp), level: data.level, component: data.component, message: data.message, }]; return next.length > 100 ? next.slice(next.length - 100) : next; }); }; socketClient.on('drone:log', handleLog); const poll = async () => { if (!mountedRef.current) return; const result = await socketClient.requestProcessStats(regId); if (result.success && result.processes && mountedRef.current) { setProcessStats(result.processes as SubProcessStat[]); } }; poll(); pollRef.current = setInterval(poll, 1000); return () => { mountedRef.current = false; if (pollRef.current) clearInterval(pollRef.current); socketClient.off('drone:log', handleLog); socketClient.unsubscribeDrone(regId); }; }, [drone._id, drone.status]); const runningCount = processStats.filter((p) => p.status === 'running').length; return (

Drone Inspector

Hostname
{drone.hostname}
Workspace
{drone.workspaceDir}
Status
{drone.status}
SubProcesses
{runningCount > 0 ? `${runningCount} running` : processStats.length > 0 ? 'All stopped' : 'No managed processes'}
Registered
{new Date(drone.createdAt).toLocaleString()}
{drone.status !== 'offline' && (
)} {drone.status !== 'offline' && logEntries.length > 0 && (

Live Log

)}
); } function DashboardSidebar({ onNavigate: _onNavigate, selectedDrone, onSelectDrone, }: DashboardSidebarProps) { const navigate = useNavigate(); const [projects, setProjects] = useState([]); const [drones, setDrones] = useState([]); const [recentChats, setRecentChats] = useState([]); const [loading, setLoading] = useState(true); const [noDroneMessage, setNoDroneMessage] = useState(null); useEffect(() => { loadData(); }, []); const loadData = async () => { try { const [projectsData, dronesData, recentChatsData] = await Promise.all([ projectApi.getAll(), droneApi.getAll(), chatSessionApi.getAll(undefined, 10), ]); setProjects(projectsData); setDrones(dronesData); setRecentChats(recentChatsData); } catch (err) { console.error("Failed to load dashboard data", err); } finally { setLoading(false); } }; const getProjectName = (session: ChatSession): string => { const projectRef = session.project; if (typeof projectRef === 'object' && projectRef !== null && 'name' in projectRef) { return projectRef.name; } return 'Unknown Project'; }; const getProjectId = (session: ChatSession): string => { const projectRef = session.project; if (typeof projectRef === 'object' && projectRef !== null && '_id' in projectRef) { return projectRef._id; } return typeof projectRef === 'string' ? projectRef : ''; }; const handleOpenChat = (session: ChatSession) => { if (!selectedDrone) { setNoDroneMessage('Select a drone first to resume a chat'); setTimeout(() => setNoDroneMessage(null), 3000); return; } const projectId = getProjectId(session); navigate(`/projects/${projectId}/chat-session/${session._id}`, { state: { drone: selectedDrone, project: typeof session.project === 'object' ? session.project : undefined, }, }); }; const modeBadgeColors: Record = { plan: 'bg-blue-500/20 text-blue-400', build: 'bg-brand/20 text-brand', test: 'bg-green-500/20 text-green-400', ship: 'bg-purple-500/20 text-purple-400', dev: 'bg-orange-500/20 text-orange-400', }; const formatRelativeTime = (dateStr: string): string => { const now = Date.now(); const then = new Date(dateStr).getTime(); const diffMs = now - then; const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHr = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHr / 24); if (diffSec < 60) return 'just now'; if (diffMin < 60) return `${diffMin}m ago`; if (diffHr < 24) return `${diffHr}h ago`; if (diffDay < 7) return `${diffDay}d ago`; return new Date(dateStr).toLocaleDateString(); }; return ( ); } interface HomeProps { user: User | null; } export default function Home({ user }: HomeProps) { const navigate = useNavigate(); const [selectedDrone, setSelectedDrone] = useState( null, ); if (!user) { return (
); } const mainContent = selectedDrone ? (
setSelectedDrone(null)} /> { if (view === "project" && typeof navigate === "function") { navigate("/projects"); } }} selectedDrone={selectedDrone} onSelectDrone={setSelectedDrone} />
) : (

Welcome, {user.displayName}!

Your dashboard is under construction.

Select a project or chat session from the sidebar to get started.

Open Project Manager
{ if (view === "project" && typeof navigate === "function") { navigate("/projects"); } }} selectedDrone={selectedDrone} onSelectDrone={setSelectedDrone} />
); return (
{mainContent}
); }