import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { Server, Activity, HardDrive, Clock, Plus } from 'lucide-react'; import { api, Machine, Job, SyncPair } from '../api/client'; import { Badge } from '@/components/ui/Badge'; import { Button } from '@/components/ui/Button'; import { Card, CardBody } from '@/components/ui/Card'; import { PageHeader } from '@/components/ui/PageHeader'; import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/Table'; import { EmptyState } from '@/components/ui/EmptyState'; import { Skeleton } from '@/components/ui/Skeleton'; import { statusVariant, statusLabel } from '@/lib/status'; import { formatRelativeTime } from '@/lib/utils'; import { subscribeMachineStatus } from '@/lib/sse'; export default function Dashboard() { const [machines, setMachines] = useState([]); const [jobs, setJobs] = useState([]); const [pairs, setPairs] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { Promise.all([ api('/api/machines'), api('/api/jobs?limit=5'), api('/api/sync-pairs'), ]) .then(([m, j, p]) => { setMachines(m); setJobs(j); setPairs(p); }) .catch(() => {}) .finally(() => setLoading(false)); api('/api/machines/refresh', { method: 'POST' }).catch(() => {}); }, []); useEffect(() => { const unsub = subscribeMachineStatus((evt) => { setMachines((prev) => prev.map((m) => m.id === evt.machine_id ? { ...m, status: evt.status } : m ) ); }); return unsub; }, []); const pairName = (id: number) => pairs.find(p => p.id === id)?.name ?? `Pair ${id}`; const online = machines.filter(m => m.status.startsWith('online')).length; const todayJobs = jobs.filter(j => { if (!j.started_at) return false; return j.started_at.startsWith(new Date().toISOString().split('T')[0]); }).length; const runningJobs = jobs.filter(j => ['running', 'queued', 'waking_up'].includes(j.status) ).length; const kpis = [ { label: 'Total Machines', value: machines.length, icon: Server, className: 'text-sky-400', bgClass: 'bg-sky-500/10', }, { label: 'Online', value: online, icon: Activity, className: 'text-emerald-400', bgClass: 'bg-emerald-500/10', accent: online > 0, }, { label: 'Jobs Today', value: todayJobs, icon: Clock, className: 'text-amber-400', bgClass: 'bg-amber-500/10', }, { label: 'Running', value: runningJobs, icon: HardDrive, className: 'text-accent', bgClass: 'bg-accent/10', }, ]; return (
{loading ? Array.from({ length: 4 }).map((_, i) => ( )) : kpis.map(kpi => { const Icon = kpi.icon; return (
{kpi.label}

{kpi.value}

); })}

Recent Jobs

{loading ? (
{Array.from({ length: 3 }).map((_, i) => ( ))}
) : jobs.length === 0 ? ( } title="No jobs yet" description="Sync pairs will appear here once jobs are executed" /> ) : ( ID Sync Pair Status Started {jobs.map(j => ( #{j.id} {pairName(j.sync_pair_id)} {j.started_at ? formatRelativeTime(j.started_at) : '-'} ))}
)}
); }