diff --git a/services/web-ui/public/screens-admin.jsx b/services/web-ui/public/screens-admin.jsx index e6d66e7..0d93cbb 100644 --- a/services/web-ui/public/screens-admin.jsx +++ b/services/web-ui/public/screens-admin.jsx @@ -1,18 +1,46 @@ // screens-admin.jsx — Users, Tokens, Containers, Cluster (graph), Settings function _normalizeNode(n, x, y) { + const cap = n.capabilities || {}; + + // GPUs: capabilities.gpus entries with name+memory_mb = driver-bound (nvidia-smi confirmed). + // Entries with only type+device = detected by /dev file but driver status unknown. + const gpus = (cap.gpus || []).map(g => ({ + name: g.name || (g.type ? g.type.toUpperCase() : 'GPU'), + memMb: g.memory_mb || null, + index: g.index ?? 0, + device: g.device || null, + bound: !!(g.name && g.memory_mb), // name+memory = nvidia-smi confirmed driver bound + })); + + // Blackmagic DeckLink: capabilities.blackmagic + capabilities.blackmagic_model + const bmdPorts = (cap.blackmagic || []).map(b => ({ + index: b.index ?? 0, + device: b.device || null, + model: cap.blackmagic_model || null, + online: b.online !== false, + })); + + const memUsedMb = n.mem_used_mb || n.memory_used_mb || (n.mem && n.mem < 1000 ? n.mem * 1024 : n.mem || 0); + const memTotalMb = n.mem_total_mb || n.memory_total_mb || (n.memTotal && n.memTotal < 1000 ? n.memTotal * 1024 : n.memTotal || 0); + return { - id: n.id || n.hostname || n.name || 'node', - role: n.role || 'worker', - status: n.status || (n.online ? 'online' : 'offline'), - ip: n.ip || n.ip_address || '—', - version: n.version || '—', - uptime: n.uptime || '—', - cpu: n.cpu || n.cpu_percent || 0, - mem: n.mem || n.memory_used || n.memory_used_gb || 0, - memTotal: n.memTotal || n.mem_total || n.memory_total || n.memory_total_gb || 0, - gpus: n.gpus || (n.gpu_count ? Array(n.gpu_count).fill('GPU') : []), - devices: n.devices || n.capture_devices || [], + id: n.hostname || n.id || n.name || 'node', + dbId: n.id, + role: n.role || 'worker', + status: n.status || (n.online ? 'online' : 'offline'), + ip: n.ip_address || n.ip || '—', + version: n.version || '—', + uptime: n.uptime || '—', + cpu: parseFloat(n.cpu_usage || n.cpu || n.cpu_percent || 0), + mem: Math.round(memUsedMb / 1024 * 10) / 10, + memTotal: Math.round(memTotalMb / 1024 * 10) / 10, + // Raw capabilities for the hardware panel + gpus, + bmdPorts, + // Legacy flat arrays kept for the stat-row summary cards + gpuCount: gpus.length, + bmdCount: bmdPorts.length, x, y, }; } @@ -978,7 +1006,7 @@ function Cluster() { const removeNode = (node) => { if (!window.confirm('Remove node ' + node.id + ' from the cluster?\nThis does not stop the machine — it only removes it from cluster membership.')) return; - window.ZAMPP_API.fetch('/cluster/nodes/' + encodeURIComponent(node.id), { method: 'DELETE' }) + window.ZAMPP_API.fetch('/cluster/nodes/' + encodeURIComponent(node.dbId || node.id), { method: 'DELETE' }) .then(() => refresh()) .catch(e => setAdviceModal({ title: 'Remove failed', lines: [e.message] })); }; @@ -1011,7 +1039,11 @@ function Cluster() {