119 lines
4.1 KiB
HTML
119 lines
4.1 KiB
HTML
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>CPU Stats</title>
|
||
<style>
|
||
body { font-family: system-ui, sans-serif; background: #111; color: #eee; margin: 2em; }
|
||
h1 { font-weight: 500; }
|
||
canvas { background: #1a1a1a; border-radius: 8px; display: block; }
|
||
.legend span { margin-right: 1.5em; }
|
||
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; }
|
||
#now { font-size: 1.2em; color: #9cf; }
|
||
.ranges { margin: 1em 0; }
|
||
.ranges button {
|
||
font: inherit; color: #eee; background: #222; border: 1px solid #444;
|
||
border-radius: 6px; padding: 6px 14px; cursor: pointer; margin-right: 6px;
|
||
}
|
||
.ranges button.active { background: #356; border-color: #57f; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>CPU Stats <span id="now"></span></h1>
|
||
<div class="legend">
|
||
<span><span class="dot" style="background:#4cf"></span>Usage %</span>
|
||
<span><span class="dot" style="background:#fc4"></span>Temp °C</span>
|
||
</div>
|
||
<div class="ranges">
|
||
<button data-range="minute">1 min</button>
|
||
<button data-range="hour">1 h</button>
|
||
<button data-range="day">24 h</button>
|
||
<button data-range="week">7 days</button>
|
||
</div>
|
||
<canvas id="chart" width="800" height="320"></canvas>
|
||
|
||
<script>
|
||
// name -> span in seconds (must match app.py RANGES)
|
||
const SPANS = { minute: 60, hour: 3600, day: 86400, week: 7 * 86400 };
|
||
// How often to reload the chart for each range
|
||
const RELOAD_MS = { minute: 1000, hour: 5000, day: 30000, week: 60000 };
|
||
|
||
const state = { range: 'minute', points: [] };
|
||
const canvas = document.getElementById('chart');
|
||
const ctx = canvas.getContext('2d');
|
||
const nowEl = document.getElementById('now');
|
||
const rangeButtons = document.querySelectorAll('.ranges button');
|
||
|
||
function setRange(range) {
|
||
state.range = range;
|
||
rangeButtons.forEach(b => b.classList.toggle('active', b.dataset.range === range));
|
||
setRangeButtonsEnabled(false); // while loading first data
|
||
if (setRange._timer) clearInterval(setRange._timer);
|
||
setRange._timer = setInterval(load, RELOAD_MS[range]);
|
||
load();
|
||
}
|
||
|
||
function setRangeButtonsEnabled(enabled) {
|
||
rangeButtons.forEach(b => b.disabled = !enabled);
|
||
}
|
||
|
||
async function load() {
|
||
try {
|
||
const { points } = await (await fetch('/api/history?range=' + state.range)).json();
|
||
state.points = points;
|
||
setRangeButtonsEnabled(true);
|
||
const last = points[points.length - 1];
|
||
if (last && last.u != null) {
|
||
nowEl.textContent = `- ${last.u.toFixed(0)}% / ${
|
||
last.c == null ? '?' : last.c.toFixed(1)
|
||
}°C`;
|
||
} else {
|
||
nowEl.textContent = last ? '—' : ' (no data yet)';
|
||
}
|
||
draw();
|
||
} catch (err) {
|
||
nowEl.textContent = '(waiting for server…)';
|
||
}
|
||
}
|
||
|
||
function draw() {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
// grid
|
||
ctx.strokeStyle = '#333';
|
||
for (let i = 1; i < 4; i++) {
|
||
const y = (canvas.height / 4) * i;
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, y);
|
||
ctx.lineTo(canvas.width, y);
|
||
ctx.stroke();
|
||
}
|
||
drawLine(p => p.u, v => v / 100, '#4cf');
|
||
drawLine(p => p.c, v => (v == null ? null : v / 100), '#fc4'); // scale 0–100 °C
|
||
}
|
||
|
||
// x axis: fixed window ending "now", so older data scrolls off the left
|
||
function drawLine(get, scale, color) {
|
||
const span = SPANS[state.range];
|
||
const t1 = Date.now() / 1000, t0 = t1 - span;
|
||
ctx.strokeStyle = color;
|
||
ctx.lineWidth = 2;
|
||
ctx.beginPath();
|
||
let started = false;
|
||
state.points.forEach(p => {
|
||
const v = get(p);
|
||
const s = v == null ? null : scale(v);
|
||
if (s == null) return;
|
||
const x = (p.t - t0) / span * (canvas.width - 10) + 5;
|
||
const y = canvas.height - s * (canvas.height - 10) - 5;
|
||
if (!started) { ctx.moveTo(x, y); started = true; }
|
||
else ctx.lineTo(x, y);
|
||
});
|
||
ctx.stroke();
|
||
}
|
||
|
||
rangeButtons.forEach(b => b.addEventListener('click', () => setRange(b.dataset.range)));
|
||
setRange('minute');
|
||
</script>
|
||
</body>
|
||
</html>
|