/* ------------------------------------------------------------------ */ /* 1. Socket‑IO connection & helpers – unchanged */ /* ------------------------------------------------------------------ */ const socket = io(); socket.on('connect_error', err => { safeSetText('status', `Could not connect to server - ${err.message}`); }); socket.on('reconnect', attempt => { safeSetText('status', `Re-connected (attempt ${attempt})`); }); function safeSetText(id, txt) { const el = document.getElementById(id); if (el) el.textContent = txt; } /* ------------------------------------------------------------------ */ /* 2. Global state */ /* ------------------------------------------------------------------ */ let selectedHost = null; // hostname that is currently displayed const hostDataMap = {}; // hostname → client object (from CLIENT_LIST) /* ------------------------------------------------------------------ */ /* 3. Build the host list once the page is ready */ /* ------------------------------------------------------------------ */ function initHostList() { const listEl = document.getElementById('host-list'); listEl.innerHTML = ''; // clear any stray markup CLIENT_LIST.forEach(host => { hostDataMap[host.hostname] = host; // cache for quick lookup const item = document.createElement('div'); item.textContent = host.hostname; item.className = 'host-item'; item.dataset.hostname = host.hostname; item.addEventListener('click', () => selectHost(host.hostname)); listEl.appendChild(item); }); // auto‑select the first host (you could also stay on "Loading…" until the user clicks) if (CLIENT_LIST.length) selectHost(CLIENT_LIST[0].hostname); } /* ------------------------------------------------------------------ */ /* 4. Handle host click – update UI and request live metrics */ /* ------------------------------------------------------------------ */ function selectHost(hostname) { if (selectedHost === hostname) return; // already selected selectedHost = hostname; // Update active styling in the list document.querySelectorAll('.host-item').forEach(el => { el.classList.toggle('active', el.dataset.hostname === hostname); }); // Render the static part of the page for this host renderHostContent(hostDataMap[hostname]); // Now request the live metrics for this host // The server sends an array of all hosts – we’ll filter below // (If you have a dedicated endpoint you could request only the chosen host here) } /* ------------------------------------------------------------------ */ /* 5. Render the static content (system properties + components) */ /* ------------------------------------------------------------------ */ function renderHostContent(host) { const main = document.getElementById('main-content'); main.innerHTML = ''; // clear // 5a. System Properties if (host.client_properties?.[0]?.system_properties?.length) { const propSection = document.createElement('div'); propSection.innerHTML = '

System Properties

'; const ul = document.createElement('ul'); ul.className = 'system-list'; host.client_properties[0].system_properties.forEach(p => { const li = document.createElement('li'); li.textContent = p.Property; ul.appendChild(li); }); propSection.appendChild(ul); main.appendChild(propSection); } // 5b. Components if (host.client_properties?.[0]?.system_components?.length) { const compSection = document.createElement('div'); compSection.innerHTML = '

Components

'; const compGrid = document.createElement('div'); compGrid.className = 'components'; host.client_properties[0].system_components.forEach(c => { const compDiv = document.createElement('div'); compDiv.className = 'component'; compDiv.innerHTML = `

${c.component_name}

`; const ul = document.createElement('ul'); ul.className = 'info-list'; c.info_strings.forEach(str => { const li = document.createElement('li'); li.textContent = str; ul.appendChild(li); }); compDiv.appendChild(ul); compGrid.appendChild(compDiv); }); compSection.appendChild(compGrid); main.appendChild(compSection); } // 5c. Placeholder for live metrics – will be filled by Socket.IO const metricsDiv = document.createElement('div'); metricsDiv.id = 'client_summary'; metricsDiv.textContent = 'Connecting…'; main.appendChild(metricsDiv); } /* ------------------------------------------------------------------ */ /* 6. Render metrics – called when a client_summary event arrives */ /* ------------------------------------------------------------------ */ socket.on('client_summary', data => { // `data` is an array of host objects (the same structure as CLIENT_LIST) // Find the one that matches the currently selected host const host = data.find(h => h.hostname === selectedHost); if (!host) return; // no data for this host yet const metrics = host.redis_data; renderStatsTable('client_summary', metrics, 'No Stats available'); }); /* 7. Table rendering – unchanged except we now target a specific container (e.g. id = 'client_summary') */ function renderStatsTable(containerId, data, emptyMsg) { socket.emit('tableRendered'); renderGenericTable(containerId, data, emptyMsg); } function renderGenericTable(containerId, data, emptyMsg) { const container = document.getElementById(containerId); if (!Array.isArray(data) || !data.length) { container.textContent = emptyMsg; return; } const mergedData = mergeRowsByName(data); const orderedData = orderRows(mergedData); const table = buildTable(orderedData); table.id = `${containerId}_table`; container.innerHTML = ''; container.appendChild(table); } function mergeRowsByName(data) { const groups = {}; // { source: { Metric: [], Data: [] } } data.forEach(row => { const source = row.Source; if (!source) return; if (!groups[source]) groups[source] = { Metric: [], Data: [] }; if ('Metric' in row && 'Data' in row) { groups[source].Metric.push(row.Metric); groups[source].Data.push(row.Data); } }); const merged = []; Object.entries(groups).forEach(([source, grp]) => { merged.push({ Source: source, Metric: grp.Metric, Data: grp.Data }); }); return merged; } function orderRows(rows) { const priority = ['System', 'CPU', 'RAM']; const priorityMap = {}; priority.forEach((src, idx) => priorityMap[src] = idx); return [...rows].sort((a, b) => { const aIdx = priorityMap[a.Source] ?? Infinity; const bIdx = priorityMap[b.Source] ?? Infinity; return aIdx - bIdx; }); } function buildTable(data) { const cols = ['Source', 'Metric', 'Data']; const table = document.createElement('table'); const thead = table.createTHead(); const headerRow = thead.insertRow(); cols.forEach(col => { const th = document.createElement('th'); th.textContent = col; headerRow.appendChild(th); }); const tbody = table.createTBody(); data.forEach(item => { const tr = tbody.insertRow(); cols.forEach(col => { const td = tr.insertCell(); const val = item[col]; if (Array.isArray(val)) { val.forEach((v, idx) => { const span = document.createElement('span'); span.textContent = v; td.appendChild(span); if (idx < val.length - 1) td.appendChild(document.createElement('br')); }); } else { td.textContent = val ?? ''; } }); }); return table; } /* ------------------------------------------------------------------ */ /* 8. Kick things off when the DOM is ready */ /* ------------------------------------------------------------------ */ document.addEventListener('DOMContentLoaded', initHostList);