/* ------------------------------------------------------------ 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) { 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 = {}; // { name: { types: [], metrics: [], props: [], values: [] } } data.forEach(row => { const name = row.name; if (!name) return; // ignore rows without a name if (!groups[name]) { groups[name] = { types: [], metrics: [], props: [], values: [] }; } // Metric rows - contain type + metric if ('type' in row && 'metric' in row) { groups[name].types.push(row.type); groups[name].metrics.push(row.metric); } // Property rows - contain property + value else if ('property' in row && 'value' in row) { groups[name].props.push(row.property); groups[name].values.push(row.value); } }); // Convert each group into a single row object const merged = []; Object.entries(groups).forEach(([name, grp]) => { merged.push({ name, type: grp.types, // array of types metric: grp.metrics, // array of metrics property: grp.props, // array of property names value: grp.values, // array of property values }); }); return merged; } /* ------------------------------------------------------------------ 3b. Order rows - put “System”, “CPU”, “RAM” first ------------------------------------------------------------------ */ function orderRows(rows) { // this should be updatable if i want const priority = ['System', 'CPU', 'RAM']; const priorityMap = {}; priority.forEach((name, idx) => (priorityMap[name] = idx)); return [...rows].sort((a, b) => { const aIdx = priorityMap.hasOwnProperty(a.name) ? priorityMap[a.name] : Infinity; // anything not in priority goes to the end const bIdx = priorityMap.hasOwnProperty(b.name) ? priorityMap[b.name] : Infinity; return aIdx - bIdx; }); } /* ------------------------------------------------------------ 4. Build an HTML table from an array of objects ------------------------------------------------------------ */ function buildTable(data) { const cols = ['name', 'type', 'metric', 'property', 'value']; // 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; }