import { useCallback, useEffect, useMemo, useState } from 'react'; import { Ban, Loader2, Pin, Server } from 'lucide-react'; import { toast } from '@/components/ui/toast-store'; import { FleetTabHeading } from './FleetEmptyState'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { listBlueprints, pinBlueprint, describeSelector, type BlueprintListItem, } from '@/lib/blueprintsApi'; import { listNodes, type NodeRecord } from '@/lib/nodesApi'; const UNPINNED = '__unpinned__'; function formatTimestamp(ms: number | null): string { if (!ms) return ''; const date = new Date(ms); return date.toLocaleString(); } interface FederationTabProps { /** Whether the current user may change pin placement. Pinning is admin-only on the backend * (PUT /api/blueprints/:id/pin requires admin); non-admins see the placement read-only. */ canManage: boolean; } export function FederationTab({ canManage }: FederationTabProps) { const [nodes, setNodes] = useState([]); const [blueprints, setBlueprints] = useState([]); const [loading, setLoading] = useState(true); const [savingId, setSavingId] = useState(null); const refresh = useCallback(async () => { try { const [nodesResult, blueprintsResult] = await Promise.all([ listNodes(), listBlueprints(), ]); setNodes(nodesResult); setBlueprints(blueprintsResult); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to load federation data'; toast.error(message); } finally { setLoading(false); } }, []); useEffect(() => { void refresh(); }, [refresh]); const cordonedNodes = useMemo(() => nodes.filter(n => n.cordoned), [nodes]); const nodeNameById = useMemo(() => { const map = new Map(); for (const node of nodes) map.set(node.id, node.name); return map; }, [nodes]); const handlePinChange = useCallback(async (blueprintId: number, value: string) => { const nodeId = value === UNPINNED ? null : Number.parseInt(value, 10); setSavingId(blueprintId); try { const updated = await pinBlueprint(blueprintId, nodeId); setBlueprints(prev => prev.map(b => b.id === blueprintId ? { ...b, pinned_node_id: updated.pinned_node_id } : b)); const blueprint = blueprints.find(b => b.id === blueprintId); if (nodeId === null) { toast.success(`${blueprint?.name ?? 'Blueprint'} unpinned`); } else { const targetName = nodeNameById.get(nodeId) ?? `node ${nodeId}`; toast.success(`${blueprint?.name ?? 'Blueprint'} pinned to ${targetName}`); } } catch (err) { const message = err instanceof Error ? err.message : 'Failed to update pin'; toast.error(message); } finally { setSavingId(null); } }, [blueprints, nodeNameById]); if (loading) { return (
Loading federation state…
); } return (

Cordoned nodes

{cordonedNodes.length} of {nodes.length}
Toggle on each node card
{cordonedNodes.length === 0 ? (

No nodes are cordoned. Use the kebab menu on any node card to mark it unschedulable.

) : (
    {cordonedNodes.map(node => (
  • {node.name} {node.type} {node.cordoned_at && ( since {formatTimestamp(node.cordoned_at)} )}
    {node.cordoned_reason && (

    {node.cordoned_reason}

    )}
  • ))}
)}

Pin policy

Force a blueprint onto a specific node, overriding its selector. {!canManage && ' Pin changes require an administrator.'}
{blueprints.length === 0 ? (

No blueprints yet. Create one in the Deployments tab to manage placement here.

) : (
{blueprints.map(bp => { const pinnedName = bp.pinned_node_id !== null ? nodeNameById.get(bp.pinned_node_id) ?? `node ${bp.pinned_node_id}` : null; const effective = pinnedName ? `pin: ${pinnedName}` : describeSelector(bp.selector); return ( ); })}
Blueprint Selector Pinned to Effective
{bp.name}
{bp.description && (
{bp.description}
)}
{describeSelector(bp.selector)} {canManage ? ( ) : ( {pinnedName ?? '(unpinned)'} )} {effective}
)}
); }