92 lines
3.1 KiB
JavaScript
92 lines
3.1 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_BASE_API before this loads
|
|
// -- a stable frame-level base, unlike window.FRAME_API, which the
|
|
// Layout page's widget dialogs repoint to a widget-scoped base while
|
|
// one is open.
|
|
|
|
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 = [];
|
|
if (device.expected_next_checkin) {
|
|
const remaining = device.expected_next_checkin - now;
|
|
rows.push([
|
|
'Expected in',
|
|
remaining > 0 ? `~${formatDuration(remaining)}` : 'Any moment',
|
|
device.overdue,
|
|
]);
|
|
}
|
|
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]);
|
|
// Shown as soon as there's any battery reading at all, even before
|
|
// battery_estimate_s has enough discharge samples in battery_log to
|
|
// average (see common.py) -- so it's clear the number is coming, not
|
|
// that the feature is broken.
|
|
const hasEstimate = device.battery_estimate_s !== null && device.battery_estimate_s !== undefined;
|
|
rows.push([
|
|
'Est. battery life left',
|
|
hasEstimate ? `~${formatDuration(device.battery_estimate_s)}` : 'Not enough data yet',
|
|
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_BASE_API}/status`);
|
|
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" from already-fetched data every
|
|
// second so it counts up smoothly without hitting the server that often.
|
|
setInterval(() => {
|
|
if (lastDeviceStatus) {
|
|
renderDeviceStatusBar(lastDeviceStatus);
|
|
}
|
|
}, 1000);
|
|
|
|
setInterval(loadDeviceStatusBar, 10000);
|