import { useState } from "react"; import { ApiError } from "../../api/client"; import type { KeyMetadata, Machine } from "../../api/types"; import { Button } from "../../components/ui/Button"; import { ConfirmDialog } from "../../components/ui/ConfirmDialog"; import { Modal } from "../../components/ui/Modal"; import { Badge } from "../../components/ui/Badge"; import { Spinner } from "../../components/ui/Spinner"; import { EmptyState, ErrorState } from "../../components/ui/States"; import { useToast } from "../../components/ui/toast-context"; import { absoluteTime, relativeTime } from "../../lib/time"; import { useCreateMachineKey, useMachineKeys, useRevokeMachineKey, } from "./hooks"; import { KeyRevealModal } from "./KeyRevealModal"; interface MachineKeysModalProps { machine: Machine | null; onClose: () => void; } function keyState(k: KeyMetadata): { tone: "ok" | "neutral"; label: string } { return k.revoked_at ? { tone: "neutral", label: "Revoked" } : { tone: "ok", label: "Active" }; } /** * Admin-only per-agent key management. Lists key metadata (never the secret), * mints new keys (revealed once via KeyRevealModal), and revokes existing keys. */ export function MachineKeysModal({ machine, onClose }: MachineKeysModalProps) { const toast = useToast(); const agentId = machine?.agent_id ?? ""; const keysQuery = useMachineKeys(machine?.agent_id ?? null, machine != null); const createKey = useCreateMachineKey(agentId); const revokeKey = useRevokeMachineKey(agentId); const [revealKey, setRevealKey] = useState(null); const [pendingRevoke, setPendingRevoke] = useState(null); function handleCreate() { createKey.mutate(undefined, { onSuccess: (created) => { setRevealKey(created.key); toast.success("Key created", "Copy it now — it is shown only once."); }, onError: (err) => { toast.error( "Could not create key", err instanceof ApiError ? err.message : "Unexpected error.", ); }, }); } function handleRevoke() { if (!pendingRevoke) return; const id = pendingRevoke.id; revokeKey.mutate(id, { onSuccess: () => { toast.success("Key revoked"); setPendingRevoke(null); }, onError: (err) => { toast.error( "Could not revoke key", err instanceof ApiError ? err.message : "Unexpected error.", ); setPendingRevoke(null); }, }); } const keys = keysQuery.data ?? []; return ( <> Agent keys ·{" "} {machine?.hostname} } ariaLabel={`Agent keys for ${machine?.hostname ?? "machine"}`} onClose={onClose} wide footer={ <> } > {keysQuery.isLoading ? (
) : keysQuery.isError ? ( ) : keys.length === 0 ? ( ) : ( {keys.map((k) => { const s = keyState(k); return ( ); })}
State Key ID Created Last used
{s.label} {k.id} {relativeTime(k.created_at)} {k.last_used_at ? relativeTime(k.last_used_at) : "never"} {!k.revoked_at && ( )}
)}
setRevealKey(null)} /> Revoking this key immediately blocks any agent authenticating with it. This cannot be undone. Key{" "} {pendingRevoke?.id}. } onConfirm={handleRevoke} onCancel={() => setPendingRevoke(null)} /> ); }