import { useCallback, useEffect, useRef, useState } from 'react'; import { api } from '../api'; import { fmtId } from '../util'; import { DesktopApiError, desktopCapability } from '../desktop-api'; import { isDesktopUuid } from '../../../bridge/src/desktop-contract.mjs'; import { unsupportedReason } from '../useDesktopSession'; import type { BootstrapTrustPosture, Instance, SandboxRuntimeCapabilityId } from '../types'; /** Open Desktop readiness per instance (#2547): decided by the backend capability only. */ interface DesktopReadiness { enabled: boolean; reason?: string; incarnation?: string; checkedAt: number } const DESKTOP_CAPABILITY_TTL_MS = 30_000; async function desktopReadiness(instanceId: string): Promise { const checkedAt = Date.now(); if (!isDesktopUuid(instanceId)) return { enabled: false, reason: 'Desktop requires a gateway instance id', checkedAt }; try { const capability = await desktopCapability(instanceId); if ('state' in capability && capability.state === 'unsupported') return { enabled: false, reason: unsupportedReason(capability.reason), checkedAt }; if (!('supported' in capability)) return { enabled: false, reason: 'Desktop readiness unknown', checkedAt }; if (!capability.supported) return { enabled: false, reason: capability.reason_codes.join(', ') || 'This instance does not support desktop assistance', incarnation: capability.incarnation, checkedAt }; if (capability.readiness === 'ready') return { enabled: true, incarnation: capability.incarnation, checkedAt }; if (capability.readiness === 'not_ready') return { enabled: false, reason: ['Desktop is still getting ready', ...capability.reason_codes].join(': '), incarnation: capability.incarnation, checkedAt }; return { enabled: false, reason: 'Desktop readiness unknown', incarnation: capability.incarnation, checkedAt }; } catch (error) { return { enabled: false, reason: error instanceof DesktopApiError ? error.message : 'Desktop assistance is temporarily unavailable.', checkedAt }; } } interface Inv { count: number; fetched_at: string; instances: Instance[]; bootstrap_trust?: BootstrapTrustPosture } interface OperationStatus { id?: string; state?: string; result?: Record; error?: unknown; failure?: unknown; } type FastStartActionId = 'snapshot' | 'restore' | 'fork' | 'warm-pool'; interface FastStartAction { id: FastStartActionId; label: string; disabled: boolean; reason?: string; } export function Inventory({ onStartSession, onLaunchInstance, onOpenDesktop, refreshTick = 0, refreshMs = 5_000 }: { onStartSession?: (instanceId?: string) => void; onLaunchInstance?: () => void; onOpenDesktop?: (instanceId: string) => void; refreshTick?: number; refreshMs?: number }) { const [data, setData] = useState(null); const [err, setErr] = useState(''); const [actionErr, setActionErr] = useState(''); const [actionMsg, setActionMsg] = useState(''); const [desktopReady, setDesktopReady] = useState>({}); const desktopReadyRef = useRef>({}); const desktopEnabled = typeof onOpenDesktop === 'function'; // Refresh Open Desktop readiness for running instances on the inventory poll, // reusing a capability answer for 30 s per instance. A failed lookup only // disables the button with its reason; it never breaks the inventory poll. const refreshDesktopReadiness = useCallback((instances: Instance[]) => { if (!desktopEnabled) return; const now = Date.now(); const running = instances.filter((i) => i.state === 'running'); const stale = running.filter((i) => { const cached = desktopReadyRef.current[i.id]; return !cached || now - cached.checkedAt >= DESKTOP_CAPABILITY_TTL_MS; }); if (!stale.length) return; void Promise.all(stale.map(async (i) => [i.id, await desktopReadiness(i.id)] as const)).then((entries) => { const next = { ...desktopReadyRef.current }; for (const [id, readiness] of entries) next[id] = readiness; desktopReadyRef.current = next; setDesktopReady(next); }); }, [desktopEnabled]); const load = useCallback(() => { api('/api/inventory').then((d) => { setData(d); setErr(''); refreshDesktopReadiness(d.instances); }).catch((e) => setErr((e as Error).message)); }, [refreshDesktopReadiness]); // Poll (and react to the app-wide refreshTick) so instances launched after this // tab first mounted appear without a manual reload — matches the other data tabs. useEffect(() => { load(); const timer = window.setInterval(load, refreshMs); return () => window.clearInterval(timer); }, [load, refreshMs, refreshTick]); const control = (path: string, method: string, fallbackMessage = '') => api<{ already_gone?: boolean; message?: string }>(path, { method }) .then((result) => { setActionErr(''); setActionMsg(result.message ?? (result.already_gone ? 'Instance already removed; inventory refreshed.' : fallbackMessage)); load(); }) .catch((e) => { setActionMsg(''); setActionErr((e as Error).message); }); const fastStartControl = async (instance: Instance, action: FastStartAction) => { if (action.disabled) return; const baseName = normalizedName(instance.launch_context?.name ?? fmtId(instance.id)); const defaultAsset = `${baseName}-${action.id === 'snapshot' ? 'snapshot' : action.id}`; const asset = window.prompt(`${action.label} asset id`, defaultAsset); if (asset === null) return; const body: Record = { asset_ref: asset.trim() }; if (action.id !== 'snapshot') { const nextName = window.prompt('New instance name', `${baseName}-${action.id === 'warm-pool' ? 'warm' : action.id}`); if (nextName === null) return; body.name = normalizedName(nextName); } setActionErr(''); setActionMsg(`${action.label} requested; waiting for operation...`); try { const accepted = await api<{ id?: string; operation?: { id?: string } }>( `/api/instances/${encodeURIComponent(instance.id)}/${action.id}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), }, ); const operationId = accepted.id ?? accepted.operation?.id; if (!operationId) { setActionMsg(`${action.label} accepted.`); load(); return; } const terminal = await waitForOperation(operationId); await api('/api/audit/intent', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ event: 'instance.fast_start.terminal', detail: { instance_id: instance.id, provider: instance.provider, action: action.id, operation_id: operationId, state: terminal.state, result: terminal.result, error: terminal.error ?? terminal.failure, }, }), }).catch(() => undefined); setActionMsg(`${action.label} ${terminal.state ?? 'completed'}: ${operationId}`); load(); } catch (e) { setActionMsg(''); setActionErr((e as Error).message); } }; if (err) return

Could not load inventory: {err}

; if (!data) return

Loading…

; if (!data.instances.length) { return (

No instances

Bridge is connected, but no host, Docker, or VM targets are registered.

{onLaunchInstance && }
); } return ( <>

Agent instances

{data.count} {data.count === 1 ? 'target' : 'targets'} · {new Date(data.fetched_at).toLocaleTimeString()}

{onLaunchInstance && }
{actionErr &&

Action failed: {actionErr}

} {actionMsg &&

{actionMsg}

} {data.bootstrap_trust && } {data.instances.map((i) => ( {/* Runtime state and session readiness are separate: Docker can be running while the embedded agent is still failing registration. */} {(() => { const sessionReady = i.session_backends?.some((b) => b.available); const unavailableReason = i.session_backends?.find((b) => !b.available)?.reason; const reconnectable = isReconnectable(i); const health = instanceHealth(i); return ( <> ); })()} ))}
Available instance deployments
InstanceRuntimeLoadout TransportHost daemonStateTenantManage
{i.launch_context?.name ?? fmtId(i.id)} {i.launch_context?.name &&
{fmtId(i.id)}
}
{i.runtime_posture.label} {i.provider &&
{i.provider}
} {hasVfio(i) && (
VFIO{assignedGpuDevices(i).length ? ` · ${assignedGpuDevices(i).join(', ')}` : ''}
)} {i.gpu?.reason &&
{i.gpu.reason}
} {i.runtime_posture.warning &&
{i.runtime_posture.warning}
}
{i.loadout} {i.launch_context?.image_ref &&
{i.launch_context.image_ref}
} {i.launch_context?.source &&
{i.launch_context.source}
} {i.storage && (
Storage: {i.storage.persistent ? 'persistent' : 'ephemeral'}{i.storage.delete_on_destroy ? ' · delete on destroy' : ''}
)} {i.storage?.reason &&
{i.storage.reason}
}
{i.transport.label}
{i.transport.mode}{i.transport.stale ? ' · stale' : ''}
{i.managed_docker_posture && }
{i.host_daemon.status.replace('_', ' ')} {i.host_daemon.detail &&
{i.host_daemon.detail}
} {i.host_daemon.operator_command && {i.host_daemon.operator_command}}
{health.detail && health.kind !== 'healthy' &&
{health.detail}
}
{i.tenant} {i.state === 'running' && onStartSession && ( )}{' '} {i.state === 'running' && onOpenDesktop && (() => { const readiness = desktopReady[i.id]; const enabled = readiness?.enabled === true; const title = readiness ? readiness.reason : 'Checking desktop capability…'; return ( ); })()}{' '} {reconnectable && ( )}{' '} {fastStartActions(i).map((action) => ( ))}{' '} {i.state === 'running' ? : }{' '}
); } function ManagedDockerBadge({ posture }: { posture: NonNullable }) { if (posture.secure_default) return
Managed UDS · split identity
control identity valid · workload UID {posture.workload_uid}
; if (posture.requires_recreation) return
Recreate required
Existing container lacks managed identity evidence; recreate it to adopt the secure default.
; return
Compatibility transport
{posture.fallback_reason ?? `${posture.transport_mode} is not equivalent to native UDS peer identity`}
; } function BootstrapTrustBanner({ posture }: { posture: BootstrapTrustPosture }) { const detail = [ posture.ca_provider_ref, posture.trust_bundle_ref, posture.rotation_state, posture.expires_at ? `expires ${new Date(posture.expires_at).toLocaleDateString()}` : undefined, ].filter(Boolean).join(' · '); return (

{posture.label} {detail && {detail}} {posture.status !== 'secure' && {posture.recovery}}

); } // VM runtimes included per #1778 — the bridge signals the in-guest agent via // qemu-guest-agent, the container/docker path via docker exec. const RECONNECTABLE_RUNTIMES = ['docker', 'container', 'vm', 'qemu', 'kvm']; const FAST_START_ACTIONS: Array<{ id: FastStartActionId; label: string; capabilities: SandboxRuntimeCapabilityId[]; }> = [ { id: 'snapshot', label: 'Snapshot', capabilities: ['instance.snapshot', 'instance.checkpoint'] }, { id: 'restore', label: 'Restore', capabilities: ['instance.restore'] }, { id: 'fork', label: 'Fork', capabilities: ['instance.fork'] }, { id: 'warm-pool', label: 'Warm pool', capabilities: ['warm_pool.manage'] }, ]; function isReconnectable(i: Instance): boolean { const runtime = String(i.runtime_posture?.kind ?? i.runtime).toLowerCase(); const running = String(i.state).toLowerCase() === 'running'; const agentMissing = i.agent_ready === false || i.session_backends?.some((b) => b.available === false); return running && RECONNECTABLE_RUNTIMES.includes(runtime) && Boolean(agentMissing); } function instanceHealth(i: Instance): { kind: 'healthy' | 'stale-agent'; label: string; detail?: string } { const running = String(i.state).toLowerCase() === 'running'; const unavailableReason = i.session_backends?.find((b) => b.available === false)?.reason; const agentMissing = i.agent_ready === false || Boolean(unavailableReason); if (running && agentMissing) { return { kind: 'stale-agent', label: 'agent unreachable', detail: unavailableReason ?? 'Runtime is still running, but the agent is not registered.', }; } return { kind: 'healthy', label: i.state }; } function hasVfio(instance: Instance) { return instance.capabilities?.some((capability) => capability.id === 'device.vfio') || instance.capability_constraints?.some((constraint) => constraint.capability === 'device.vfio') || Boolean(instance.gpu?.assigned || instance.gpu?.available || instance.gpu?.devices?.length); } function assignedGpuDevices(instance: Instance) { return instance.gpu?.devices?.filter(Boolean) ?? []; } function fastStartActions(instance: Instance): FastStartAction[] { const runtime = String(instance.runtime_posture?.kind ?? instance.runtime).toLowerCase(); if (!['vm', 'qemu', 'kvm'].includes(runtime)) return []; const capabilities = new Set((instance.capabilities ?? []).map((capability) => capability.id)); const constraints = instance.capability_constraints ?? []; const provider = String(instance.provider ?? '').toLowerCase(); return FAST_START_ACTIONS.flatMap((action) => { const advertised = action.capabilities.some((capability) => capabilities.has(capability)); const exclusion = constraints.find((constraint) => constraint.excludes?.some((excluded) => action.capabilities.includes(excluded)) || action.capabilities.includes(constraint.capability)); if (!advertised && !exclusion) return []; const label = action.id === 'snapshot' && provider === 'libvirt' ? 'Checkpoint' : action.label; return [{ id: action.id, label, disabled: Boolean(exclusion), reason: exclusion?.reason, }]; }); } function normalizedName(value: string) { const cleaned = String(value || 'cockpit-vm') .toLowerCase() .replace(/[^a-z0-9-]/g, '-') .replace(/-+$/g, '') .slice(0, 63); const prefixed = /^[a-z]/.test(cleaned) ? cleaned : `a-${cleaned.replace(/^-+/, '')}`; return prefixed.length >= 2 ? prefixed : 'cockpit-vm'; } async function waitForOperation(operationId: string): Promise { let last: OperationStatus = { id: operationId, state: 'unknown' }; for (let i = 0; i < 60; i += 1) { last = await api(`/api/operations/${encodeURIComponent(operationId)}`); const state = String(last.state ?? '').toLowerCase(); if (['succeeded', 'failed', 'canceled', 'cancelled'].includes(state)) return last; await new Promise((resolve) => window.setTimeout(resolve, 1_000)); } return last; }