cockpit: battery/power card; van-battery publishes thresholds in state

New "Battery / Power" card shows mains vs battery, charge % with a severity-
colored bar (only bites while on battery), the last alert level, and the
configured warn/shutdown thresholds. van-battery now includes warn_levels +
shutdown_level in /run/van-battery/state.json so the card stays self-describing
instead of hardcoding the numbers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Andreas Wrede
2026-06-28 21:56:20 -04:00
co-authored by Claude Opus 4.8
parent 52e948654e
commit 473a75c729
4 changed files with 55 additions and 3 deletions
+41 -1
View File
@@ -97,6 +97,45 @@ function renderThermal(th) {
el.innerHTML = html;
}
/* ---------- Battery / power source (van-battery daemon state) ---------- */
async function readBattery() {
try {
return JSON.parse(await run(["cat", "/run/van-battery/state.json"]));
} catch (e) {
return null;
}
}
function renderBattery(b) {
const el = document.getElementById("battery");
if (!b || b.capacity == null) {
el.innerHTML = `<p class="muted">van-battery daemon not running (no state file).</p>`;
return;
}
const cap = b.capacity;
const shut = b.shutdown_level != null ? b.shutdown_level : 10;
const warns = b.warn_levels || [];
const lowest = warns.length ? Math.min.apply(null, warns) : 25;
// Charge severity only bites while on battery; on mains it's just charging/full.
let lvl = "ok";
if (b.on_battery) lvl = cap <= shut ? "crit" : cap <= lowest ? "warn" : "ok";
const pillClass = { ok: "ok", warn: "warn", crit: "bad" }[lvl];
const src = b.on_battery
? `<span class="pill warn">on battery</span>`
: `<span class="pill ok">on mains</span>`;
let html = `<p>Power: ${src} &nbsp; Charge: <span class="pill ${pillClass}">${esc(cap)}%</span></p>`;
html += `<div class="bar"><div class="bar-fill ${lvl}" style="width:${Math.max(0, Math.min(100, cap))}%"></div></div>`;
if (b.on_battery && b.alerted_below != null)
html += `<p class="muted">Low-battery alert sent at ${esc(b.alerted_below)}%.</p>`;
html += `<p class="muted">On battery: alerts at ${esc(warns.join("/"))}%, auto-shutdown at ${esc(shut)}%.` +
` Alerts: <code>journalctl -u van-battery</code></p>`;
el.innerHTML = html;
}
/* ---------- WAN failover (van-failover daemon state) ---------- */
async function readFailover() {
@@ -225,9 +264,10 @@ async function restartAP() {
async function refresh() {
try {
const [ap, th, fo, wan] = await Promise.all([readAP(), readThermal(), readFailover(), readWAN()]);
const [ap, th, bat, fo, wan] = await Promise.all([readAP(), readThermal(), readBattery(), readFailover(), readWAN()]);
renderAP(ap);
renderThermal(th);
renderBattery(bat);
renderFailover(fo);
renderWAN(wan);
document.getElementById("updated").textContent = "updated " + new Date().toLocaleTimeString();