/* ------------------------------------------------------------ 1. Socket-IO connection & helper functions (unchanged) ------------------------------------------------------------ */ const socket = io(); socket.on('host_metrics', renderStatsTable); socket.on('connect_error', err => { safeSetText('host_metrics', `Could not connect to server - ${err.message}`); }); socket.on('reconnect', attempt => { safeSetText('host_metrics', `Re-connected (attempt ${attempt})`); }); function safeSetText(id, txt) { const el = document.getElementById(id); if (el) el.textContent = txt; } /* ------------------------------------------------------------------ 2. Table rendering - now orders rows before building the table ------------------------------------------------------------------ */ // helper function for table row ordering function renderStatsTable(data) { socket.emit('tableRendered'); renderGenericTable('host_metrics', data, 'No Stats available'); } function renderGenericTable(containerId, data, emptyMsg) { const container = document.getElementById(containerId); if (!Array.isArray(data) || !data.length) { container.textContent = emptyMsg; return; } /* Merge rows by name (new logic) */ const mergedData = mergeRowsByName(data); /* Order the merged rows – priority first */ const orderedData = orderRows(mergedData); /* Build the table from the ordered data */ const table = buildTable(orderedData); table.id = 'host_metrics_table'; container.innerHTML = ''; container.appendChild(table); } /* ------------------------------------------------------------ 3. Merge rows by name ------------------------------------------------------------ */ function mergeRowsByName(data) { const groups = {}; // { source: { ... } } data.forEach(row => { const source = row.Source; // <-- changed if (!source) return; if (!groups[source]) { groups[source] = { Metric: [], Data: [], Property: [], Value: [] }; } if ('Metric' in row && 'Data' in row) { groups[source].Metric.push(row.Metric); groups[source].Data.push(row.Data); } else if ('Property' in row && 'Value' in row) { groups[source].Property.push(row.Property); groups[source].Value.push(row.Value); } }); const merged = []; Object.entries(groups).forEach(([source, grp]) => { merged.push({ Source: source, // <-- keep the original key Metric: grp.Metric, Data: grp.Data, Property: grp.Property, Value: grp.Value }); }); return merged; } // 3b. Order rows – put “System”, “CPU”, “RAM” first function orderRows(rows) { // Priority list – can be updated later const priority = ['System', 'CPU', 'RAM']; // Map source → priority index const priorityMap = {}; priority.forEach((src, idx) => { priorityMap[src] = idx; }); // Stable sort: keep original position if priorities are equal return [...rows].sort((a, b) => { const aIdx = priorityMap.hasOwnProperty(a.Source) ? priorityMap[a.Source] : Infinity; // anything not in priority goes to the end const bIdx = priorityMap.hasOwnProperty(b.Source) ? priorityMap[b.Source] : Infinity; // If both have the same priority (or both Infinity), keep original order return aIdx - bIdx; }); } /* ------------------------------------------------------------ 4. Build an HTML table from an array of objects ------------------------------------------------------------ */ function buildTable(data) { const cols = ['Source', 'Property', 'Value', 'Metric', 'Data']; // explicit order const table = document.createElement('table'); // Header const thead = table.createTHead(); const headerRow = thead.insertRow(); cols.forEach(col => { const th = document.createElement('th'); th.textContent = col; headerRow.appendChild(th); }); // Body 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)) { // Create a for each item val.forEach((v, idx) => { td.id = 'host_metrics_column'; const span = document.createElement('span'); span.textContent = v; td.appendChild(span); // Insert a line break after every item except the last if (idx < val.length - 1) td.appendChild(document.createElement('br')); }); } else { td.textContent = val !== undefined ? val : ''; } }); }); return table; }