Server: permanent battery history log + graph in the web UI
Build and push server image / build-and-push (push) Successful in 35s
Build and push server image / build-and-push (push) Successful in 35s
battery_history stays cycle-scoped (reset on recharge, feeds the "on battery for"/estimate numbers), but nothing kept a permanent record -- added battery_log, appended on every report and never reset, capped at ~2 years of hourly reports as a sanity bound rather than a real limit. New GET /api/battery-log serves it; the web UI draws it as a plain canvas line chart (no chart library) under a new "Battery history" section, loaded once on page load. Also caught up server/README.md, which never documented the OTA firmware endpoints or the /api/queue response's current "device" shape from the earlier status-panel work.
This commit is contained in:
@@ -117,6 +117,9 @@
|
||||
<h2 class="section">Device</h2>
|
||||
<div id="device-status"><p class="sub">Loading...</p></div>
|
||||
|
||||
<h2 class="section">Battery history</h2>
|
||||
<div id="battery-chart-wrap"><p class="sub">Loading...</p></div>
|
||||
|
||||
<h2 class="section">Firmware update</h2>
|
||||
<p class="sub" id="firmware-available">
|
||||
{% if cfg.firmware_available_version %}Uploaded: v{{ cfg.firmware_available_version }} -- the frame
|
||||
@@ -527,7 +530,85 @@
|
||||
}
|
||||
}
|
||||
|
||||
function drawBatteryChart(log) {
|
||||
const wrap = document.getElementById('battery-chart-wrap');
|
||||
if (!log || log.length < 2) {
|
||||
wrap.innerHTML = '<p class="sub">Not enough data yet.</p>';
|
||||
return;
|
||||
}
|
||||
wrap.innerHTML = '';
|
||||
const width = wrap.clientWidth || 440;
|
||||
const height = 180;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.style.border = '1px solid #e5e7eb';
|
||||
canvas.style.borderRadius = '4px';
|
||||
wrap.appendChild(canvas);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const pad = { left: 28, right: 8, top: 10, bottom: 20 };
|
||||
const plotW = width - pad.left - pad.right;
|
||||
const plotH = height - pad.top - pad.bottom;
|
||||
|
||||
const times = log.map((p) => p[0]);
|
||||
const minT = Math.min(...times);
|
||||
const maxT = Math.max(...times);
|
||||
const spanT = Math.max(1, maxT - minT);
|
||||
|
||||
const x = (t) => pad.left + ((t - minT) / spanT) * plotW;
|
||||
const y = (pct) => pad.top + (1 - pct / 100) * plotH;
|
||||
|
||||
ctx.strokeStyle = '#e5e7eb';
|
||||
ctx.fillStyle = '#999';
|
||||
ctx.font = '10px system-ui, sans-serif';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.textAlign = 'left';
|
||||
[0, 25, 50, 75, 100].forEach((pct) => {
|
||||
const yy = y(pct);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pad.left, yy);
|
||||
ctx.lineTo(width - pad.right, yy);
|
||||
ctx.stroke();
|
||||
ctx.fillText(String(pct), 2, yy + 3);
|
||||
});
|
||||
|
||||
ctx.strokeStyle = '#2563eb';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
log.forEach((p, i) => {
|
||||
const px = x(p[0]);
|
||||
const py = y(p[1]);
|
||||
if (i === 0) ctx.moveTo(px, py);
|
||||
else ctx.lineTo(px, py);
|
||||
});
|
||||
ctx.stroke();
|
||||
|
||||
const fmt = (t) => new Date(t * 1000).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(fmt(minT), pad.left, height - 4);
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(fmt(maxT), width - pad.right, height - 4);
|
||||
}
|
||||
|
||||
async function loadBatteryLog() {
|
||||
const wrap = document.getElementById('battery-chart-wrap');
|
||||
try {
|
||||
const resp = await fetch('/api/battery-log');
|
||||
if (!resp.ok) {
|
||||
wrap.innerHTML = '<p class="sub">Could not load.</p>';
|
||||
return;
|
||||
}
|
||||
const data = await resp.json();
|
||||
drawBatteryChart(data.log);
|
||||
} catch (e) {
|
||||
wrap.innerHTML = '<p class="sub">Could not load.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
loadQueue();
|
||||
loadBatteryLog();
|
||||
|
||||
// Fast tick: re-renders "Last seen"/"On battery for" etc. from the
|
||||
// already-fetched device data every second, so they count up smoothly
|
||||
|
||||
Reference in New Issue
Block a user