import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' type Container = { id: string; name: string; status: string } function isContainerResponse(value: unknown): value is { containers: Container[] } { if (!value || typeof value !== 'object') return false const containers = (value as Record).containers return Array.isArray(containers) && containers.every((container: unknown) => { if (!container || typeof container !== 'object') return false const item = container as Record return typeof item.id === 'string' && typeof item.name === 'string' && typeof item.status === 'string' }) } export default function NodeDockerContainers({ nodeId, clusterId, detailed = false }: { nodeId: string; clusterId: string; detailed?: boolean }) { const [containers, setContainers] = useState(null) const [status, setStatus] = useState('Connecting…') const [attempt, setAttempt] = useState(0) useEffect(() => { let active = true const controller = new AbortController() let socket: WebSocket | undefined const disconnected = () => { if (!active) return setContainers(null) setStatus('Docker feed disconnected. Reconnect to try again.') } const connect = async () => { setContainers(null) setStatus('Loading node details…') try { const apiBaseUrl = import.meta.env.VITE_API_BASE_URL if (!apiBaseUrl) throw new Error('API URL is not configured.') const token = localStorage.getItem('access_token') const response = await fetch( `${apiBaseUrl.replace(/\/$/, '')}/spock/node-interfaces/${encodeURIComponent(nodeId)}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, signal: controller.signal, }, ) if (!response.ok) throw new Error('Failed to load node details.') const result = await response.json() const node = result?.node if (result?.status !== 'success' || node?.uuid !== nodeId || typeof node.host !== 'string' || !node.host.trim()) { throw new Error('Invalid node host received.') } if (!active) return const host = node.host.trim() const hostname = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host setStatus('Connecting…') socket = new WebSocket(`ws://${hostname}:9100/ws/docker/containers`) socket.onopen = () => { if (active) setStatus('Waiting for containers…') } socket.onmessage = (event) => { if (!active) return try { const message: unknown = JSON.parse(event.data) if (!isContainerResponse(message)) throw new Error('Invalid container data') setContainers(message.containers) setStatus('Live') } catch { setContainers(null) setStatus('Invalid container data received.') } } socket.onerror = disconnected socket.onclose = disconnected } catch (error) { if (!active) return setStatus(error instanceof Error ? error.message : 'Unable to connect to the Docker feed.') } } void connect() return () => { active = false controller.abort() socket?.close() } }, [attempt, nodeId]) const nodePath = `/clusters/${encodeURIComponent(clusterId)}/nodes/${encodeURIComponent(nodeId)}` const visibleContainers = detailed ? containers : containers?.slice(0, 3) return (

Docker containers

{detailed && Back to node}

{status}

{containers !== null && (containers.length === 0 ?

No containers found.

: (
{visibleContainers?.map((container) => ( ))}
NameContainer IDStatus
{container.name} {container.id} {container.status}
))} {!detailed && containers && containers.length > 3 && (

View all {containers.length} containers

)}
) }