Build and push server image / build-and-push (push) Successful in 39s
Moved out of the Stats tab's side column into a new horizontal bar shared by every frame page (Photos/Configuration/Stats), sitting between the page title and the tabs so it's on screen regardless of which tab is active. _device_status_bar.html is a new partial included via a device_status block in app_base.html; device_status_bar.js is the fetch/render/poll logic extracted from frame_stats.js and adapted to a wrapping row of label/value pairs instead of a stacked list. frame_stats.html's Device card and now-single-card .side-col are gone -- Battery history and Lifetime stats just stack directly.
78 lines
2.6 KiB
JavaScript
78 lines
2.6 KiB
JavaScript
// Device status bar: always-visible strip (below the page title, above
|
|
// the tabs -- see _device_status_bar.html) showing last-seen/firmware/
|
|
// battery, so it's not tucked away on just the Stats tab. Shared by
|
|
// every frame page; each sets window.FRAME_API before this loads.
|
|
|
|
let lastDeviceStatus = null;
|
|
|
|
function renderDeviceStatusBar(device) {
|
|
const el = document.getElementById('device-status');
|
|
if (!el) return;
|
|
el.innerHTML = '';
|
|
if (!device || !device.last_seen) {
|
|
el.innerHTML = '<p class="sub">The frame hasn\'t checked in yet.</p>';
|
|
return;
|
|
}
|
|
const now = Date.now() / 1000;
|
|
const rows = [];
|
|
const ago = formatDuration(Math.max(0, now - device.last_seen));
|
|
rows.push(['Last seen', `${ago} ago`, device.overdue]);
|
|
if (device.firmware_version) {
|
|
let fw = `v${device.firmware_version}`;
|
|
if (device.firmware_available && device.firmware_available !== device.firmware_version) {
|
|
fw += ` (v${device.firmware_available} waiting)`;
|
|
}
|
|
rows.push(['Firmware', fw, false]);
|
|
}
|
|
if (device.battery) {
|
|
rows.push(['Battery', `${device.battery.percent}%`, false]);
|
|
}
|
|
if (device.on_battery_since) {
|
|
rows.push(['On battery for', formatDuration(now - device.on_battery_since), false]);
|
|
}
|
|
if (device.battery_estimate_s !== null && device.battery_estimate_s !== undefined) {
|
|
rows.push(['Est. remaining', `~${formatDuration(device.battery_estimate_s)}`, false]);
|
|
}
|
|
for (const [label, value, alert] of rows) {
|
|
const stat = document.createElement('span');
|
|
stat.className = 'device-stat' + (alert ? ' alert' : '');
|
|
const labelPart = document.createTextNode(label + ': ');
|
|
const valuePart = document.createElement('strong');
|
|
valuePart.textContent = value;
|
|
stat.appendChild(labelPart);
|
|
stat.appendChild(valuePart);
|
|
el.appendChild(stat);
|
|
}
|
|
}
|
|
|
|
async function loadDeviceStatusBar() {
|
|
try {
|
|
const resp = await fetch(`${window.FRAME_API}/queue`);
|
|
if (!resp.ok) {
|
|
return;
|
|
}
|
|
const data = await resp.json();
|
|
lastDeviceStatus = data.device;
|
|
renderDeviceStatusBar(data.device);
|
|
} catch (e) { /* retried on the next poll */ }
|
|
}
|
|
|
|
loadDeviceStatusBar();
|
|
|
|
document.addEventListener('themechange', () => {
|
|
if (lastDeviceStatus) {
|
|
renderDeviceStatusBar(lastDeviceStatus);
|
|
}
|
|
});
|
|
|
|
// Fast tick: re-renders "Last seen"/"On battery for" from already-
|
|
// fetched data every second so they count up smoothly without hitting
|
|
// the server that often.
|
|
setInterval(() => {
|
|
if (lastDeviceStatus) {
|
|
renderDeviceStatusBar(lastDeviceStatus);
|
|
}
|
|
}, 1000);
|
|
|
|
setInterval(loadDeviceStatusBar, 10000);
|