Files
cosmoserver/files/web/html/test.html
2026-03-09 16:32:43 -07:00

181 lines
5.6 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Matt-Cloud Cosmostat</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h2>Matt-Cloud Cosmostat Dashboard</h2>
This dashboard shows the local Matt-Cloud system stats.<p>
</div>
<div class="container">
<h2>System Stats</h2>
<div id="host_stats" class="column">Connecting...</div>
</div>
<!--
Here will go the graphs once i have all the stats first
-->
<div class="container">
<h2>System Graphs</h2>
<div id="host_graphs" class="column">
<div id="history_graphs" class="container">
</div>
</div>
</div>
<!-- Socket.IO client library -->
<script src="socket.io/socket.io.js"></script>
<script>
const socket = io();
// listen for redis updates, render and error handle
socket.on('host_stats', renderStatsTable);
socket.on('connect_error', err => {
safeSetText('host_stats', `Could not connect to server - ${err.message}`);
});
socket.on('reconnect', attempt => {
safeSetText('host_stats', `Re-connected (attempt ${attempt})`);
});
function safeSetText(id, txt) {
const el = document.getElementById(id);
if (el) el.textContent = txt;
}
// table rendering functions
function renderStatsTable(data) { renderGenericTable('host_stats', data, 'No Stats available'); }
function renderGenericTable(containerId, data, emptyMsg) {
const container = document.getElementById(containerId);
if (!Array.isArray(data) || !data.length) {
container.textContent = emptyMsg;
return;
}
const table = renderTable(data);
container.innerHTML = '';
container.appendChild(table);
}
function renderTable(data) {
// Columns are inferred from the first object (order matters)
const cols = Object.keys(data[0]);
// Create table
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();
td.textContent = item[col];
});
});
return table;
}
</script>
<script>
// ────────────────────── Globals ──────────────────────
const chartInstances = {}; // {metricName: Chart}
const colorPalette = [
'#ff6384', '#36a2eb', '#ffcd56', '#4bc0c0',
'#9966ff', '#ff9f40', '#8e5ea2', '#3e95cd'
];
// ────────────────────── Socket.io ──────────────────────
socket.on('history_stats', renderHistoryGraphs);
// ────────────────────── Rendering ──────────────────────
function renderHistoryGraphs(components) {
// 1⃣ Sanity check components is an array of objects
if (!Array.isArray(components) || !components.length) {
console.warn('history_stats payload is empty or malformed');
return;
}
// 2⃣ Clean up any old charts & canvases
Object.values(chartInstances).forEach(ch => ch.destroy());
chartInstances = {}; // reset map
const container = document.getElementById('history_graphs');
container.innerHTML = ''; // empty the container
// 3⃣ For each component create a canvas & a Chart
components.forEach((comp, idx) => {
const metricName = comp.info?.metric_name || comp.info?.name || `component-${idx+1}`;
// 3a. Create a canvas element
const canvas = document.createElement('canvas');
canvas.id = `chart-${metricName}`;
canvas.width = 800; // optional you can use CSS instead
canvas.height = 400;
canvas.style.marginBottom = '2rem';
// 3b. Append the canvas to the container
container.appendChild(canvas);
// 3c. Build the dataset for this component
const history = comp.history?.history_data || [];
const dataPoints = history.map(d => ({
x: new Date(d.timestamp * 1000), // convert seconds → ms
y: parseFloat(d.value) // values are strings in Redis
}));
const dataset = {
label: metricName,
data: dataPoints,
borderColor: colorPalette[idx % colorPalette.length],
fill: false,
tension: 0.1
};
// 3d. Create the chart
const ctx = canvas.getContext('2d');
chartInstances[metricName] = new Chart(ctx, {
type: 'line',
data: { datasets: [dataset] },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom' },
tooltip: { mode: 'index', intersect: false }
},
scales: {
x: {
type: 'time',
time: { unit: 'minute', tooltipFormat: 'YYYYMMDD HH:mm:ss' },
title: { display: true, text: 'Time' }
},
y: {
title: { display: true, text: 'Value' },
beginAtZero: false
}
}
}
});
});
}
</script>
</body>
</html>