diff --git a/sdl-mgr/html/js/dash.js b/sdl-mgr/html/js/dash.js index 286b49e..caa76a2 100644 --- a/sdl-mgr/html/js/dash.js +++ b/sdl-mgr/html/js/dash.js @@ -13,7 +13,7 @@ let lastSdlStatus = null; let lastSdlTimestamp = null; let statusCheckTimer = null; const STALE_GRACE_MS = 2000; - +let lastWorkers = null; // ------------------------------- // GPU Summary (for dashboard) @@ -201,22 +201,32 @@ async function initMqtt() { }); // 5) Register handlers - mqttClient.on('connect', () => { + mqttClient.on('connect', () => { console.log('MQTT connected (dashboard)'); - const topic = mqttConfig.topics['sdl-web'].sub['sdl_cluster-status']; - console.log('Subscribing to:', topic); + const statusTopic = mqttConfig.topics['sdl-web'].sub['sdl_cluster-status']; + const workersTopic = mqttConfig.topics['sdl-web'].sub['sdl_cluster-workers']; + + console.log('Subscribing to:', statusTopic); + console.log('Subscribing to:', workersTopic); - mqttClient.subscribe(topic, { qos: 1 }, err => { + mqttClient.subscribe(statusTopic, { qos: 1 }, err => { if (err) { - console.error('MQTT subscribe error:', err); + console.error('MQTT subscribe error (status):', err); } else { - console.log('Subscribed to', topic); + console.log('Subscribed to', statusTopic); + } + }); + + mqttClient.subscribe(workersTopic, { qos: 1 }, err => { + if (err) { + console.error('MQTT subscribe error (workers):', err); + } else { + console.log('Subscribed to', workersTopic); } }); startStatusAgeMonitor(); - }); mqttClient.on('message', (topic, payload, packet) => { @@ -235,6 +245,9 @@ async function initMqtt() { lastSdlStatus = msg; lastSdlTimestamp = Date.parse(msg.ts); renderModulesTable(msg); + } else if (msg.type === 'cluster-workers') { + lastWorkers = msg; + renderWorkersTable(msg); } } catch (e) { @@ -472,3 +485,73 @@ function markAllModulesDown() { } +function renderWorkersTable(msg) { + const workers = msg.msg?.workers || {}; + const workerCount = Object.keys(workers).length; + + let html = `
Workers (${workerCount})
`; + + if (workerCount === 0) { + html += '

No workers connected

'; + document.getElementById('dash-sdl-wkrs').innerHTML = html; + return; + } + + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + for (const [sdl_id, worker] of Object.entries(workers)) { + const status = worker.status === 'active' + ? 'Active' + : 'Inactive'; + + const cpuAvail = worker.resources?.cpus?.available || 0; + const cpuTotal = worker.resources?.cpus?.allocated || 0; + const cpuUsed = worker.resources?.cpus?.used || 0; + + const memAvail = worker.resources?.memory?.available || 0; + const memTotal = worker.resources?.memory?.allocated || 0; + const memUsed = worker.resources?.memory?.used || 0; + + // Convert bytes to GB + const memAvailGB = Math.round(memAvail / (1024 * 1024 * 1024)); + const memTotalGB = Math.round(memTotal / (1024 * 1024 * 1024)); + const memUsedGB = memUsed > 0 ? Math.round(memUsed / (1024 * 1024 * 1024)) : 0; + + const gpuAvail = worker.resources?.gpus?.available || 0; + const gpuTotal = worker.resources?.gpus?.allocated || 0; + const gpuUsed = worker.resources?.gpus?.used || 0; + + const platform = `${worker.platform || 'unknown'} / ${worker.arch || 'unknown'}`; + + const lastSeen = worker.last_seen + ? new Date(worker.last_seen).toLocaleString() + : 'N/A'; + + html += ''; + html += ``; + html += ``; + html += ``; + html += ``; + html += ``; + html += ``; + html += ``; + html += ''; + } + + html += ''; + html += '
HostnameStatus CPU RAM GPUPlatformLast Seen
${worker.hostname}
${worker.sdl_id}
${status}${cpuAvail} / ${cpuTotal}${cpuUsed > 0 ? `
(${cpuUsed} used)` : ''}
${memAvailGB} / ${memTotalGB} GB${memUsedGB > 0 ? `
(${memUsedGB} GB used)` : ''}
${gpuAvail} / ${gpuTotal}${gpuUsed > 0 ? `
(${gpuUsed} used)` : ''}
${platform}${lastSeen}
'; + + document.getElementById('dash-sdl-wkrs').innerHTML = html; +}