"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; type MachineRow = { id: string; name: string; code?: string | null; location?: string | null; latestHeartbeat: null | { ts: string; status: string; message?: string | null; ip?: string | null; fwVersion?: string | null; }; }; function secondsAgo(ts?: string) { if (!ts) return "never"; const diff = Math.floor((Date.now() - new Date(ts).getTime()) / 1000); if (diff < 60) return `${diff}s ago`; return `${Math.floor(diff / 60)}m ago`; } function isOffline(ts?: string) { if (!ts) return true; return Date.now() - new Date(ts).getTime() > 15000; // 15s threshold } function badgeClass(status?: string, offline?: boolean) { if (offline) return "bg-white/10 text-zinc-300"; const s = (status ?? "").toUpperCase(); if (s === "RUN") return "bg-emerald-500/15 text-emerald-300"; if (s === "IDLE") return "bg-yellow-500/15 text-yellow-300"; if (s === "STOP" || s === "DOWN") return "bg-red-500/15 text-red-300"; return "bg-white/10 text-white"; } export default function MachinesPage() { const [machines, setMachines] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { let alive = true; async function load() { try { const res = await fetch("/api/machines", { cache: "no-store" }); const json = await res.json(); if (alive) { setMachines(json.machines ?? []); setLoading(false); } } catch { if (alive) setLoading(false); } } load(); const t = setInterval(load, 5000); return () => { alive = false; clearInterval(t); }; }, []); return (

Machines

Select a machine to view live KPIs.

Back to Overview
{loading &&
Loading machines…
} {!loading && machines.length === 0 && (
No machines found for this org.
)}
{(!loading ? machines : []).map((m) => { const hb = m.latestHeartbeat; const offline = isOffline(hb?.ts); const statusLabel = offline ? "OFFLINE" : hb?.status ?? "UNKNOWN"; const lastSeen = secondsAgo(hb?.ts); return (
{m.name}
{m.code ? m.code : "—"} • Last seen {lastSeen}
{statusLabel}
Status
{offline ? "No heartbeat" : hb?.message ?? "OK"}
); })}
); }