import { useMemo, useState } from "react"; import { ApiError } from "../../api/client"; import type { Machine } from "../../api/types"; import { useAuth } from "../../auth/AuthContext"; import { PageHeader } from "../../components/layout/PageHeader"; import { InfoIcon, KeyIcon, RefreshIcon, SearchIcon, TrashIcon, } from "../../components/layout/icons"; import { Badge } from "../../components/ui/Badge"; import { Button } from "../../components/ui/Button"; import { Input } from "../../components/ui/Input"; import { Panel } from "../../components/ui/Panel"; import { EmptyState, ErrorState } from "../../components/ui/States"; import { StatusDot } from "../../components/ui/StatusDot"; import { machineTone } from "../../components/ui/status"; import { Table, type Column } from "../../components/ui/Table"; import { TableSkeleton } from "../../components/ui/TableSkeleton"; import { absoluteTime, relativeTime } from "../../lib/time"; import "./machines.css"; import { DeleteMachineDialog } from "./DeleteMachineDialog"; import { MachineDetailDrawer } from "./MachineDetailDrawer"; import { MachineKeysModal } from "./MachineKeysModal"; import { useMachines } from "./hooks"; export function MachinesPage() { const { isAdmin } = useAuth(); const machinesQuery = useMachines(); const [filter, setFilter] = useState(""); const [detailFor, setDetailFor] = useState(null); const [keysFor, setKeysFor] = useState(null); const [deleteFor, setDeleteFor] = useState(null); const { data } = machinesQuery; const machines = useMemo(() => data ?? [], [data]); const filtered = useMemo(() => { const q = filter.trim().toLowerCase(); if (!q) return machines; return machines.filter( (m) => m.hostname.toLowerCase().includes(q) || m.agent_id.toLowerCase().includes(q), ); }, [machines, filter]); const onlineCount = useMemo( () => machines.filter((m) => m.status === "online").length, [machines], ); const columns: Column[] = [ { key: "status", header: "", cellClass: "dt__status", render: (m) => ( ), }, { key: "hostname", header: "Hostname", render: (m) => {m.hostname}, }, { key: "os", header: "OS", render: (m) => ( {m.os_version ?? "Unknown"} ), }, { key: "mode", header: "Mode", render: (m) => m.is_persistent ? ( Persistent ) : ( Attended ), }, { key: "last_seen", header: "Last seen", render: (m) => ( {relativeTime(m.last_seen)} ), }, { key: "agent_id", header: "Agent ID", render: (m) => ( {m.agent_id} ), }, { key: "actions", header: "", cellClass: "dt__actions", render: (m) => ( e.stopPropagation()} > {isAdmin && ( )} ), }, ]; return (
void machinesQuery.refetch()} loading={machinesQuery.isFetching} > Refresh } />
setFilter(e.target.value)} aria-label="Filter machines" />
{onlineCount} online ยท{" "} {machines.length} total
{machinesQuery.isLoading ? ( <> Loading machines ) : machinesQuery.isError ? ( void machinesQuery.refetch()}> Retry } /> ) : filtered.length === 0 ? ( filter ? ( setFilter("")}> Clear filter } /> ) : ( ) ) : ( m.id} onRowClick={setDetailFor} rowLabel={(m) => m.hostname} /> )} setDetailFor(null)} /> {isAdmin && ( setKeysFor(null)} /> )} setDeleteFor(null)} /> ); }