Console: the last OTA state per machine and app run is remembered (console_ota_memo) and shown when the upload's 256 KiB tail no longer holds it

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-04 21:27:59 +00:00
parent 15e3fccebd
commit df61a116ad

View file

@ -55,6 +55,31 @@ function parseNodeTail(tail) {
n.last_warning = errs.length ? errs[errs.length - 1].replace(/^\S+ \S+ /, '').slice(0, 200) : null;
return n;
}
// The app uploads at most the last 256 KiB of its log, so an update line scrolls out of the tail in about half an hour
// on a PC that logs every block (4 October 2026: PC 1's "installing 0.3.4" left its card while the install was still
// stuck). The last OTA state seen per machine and app run is kept in console_ota_memo and read back, for the same run,
// when the tail has none; a new run (the app relaunched) starts clean.
async function otaMemo(sql, groups) {
try {
await sql(`CREATE TABLE IF NOT EXISTS console_ota_memo (id8 text PRIMARY KEY, run_id text NOT NULL, ota jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now())`);
const live = [...groups.values()].filter(g => g.app && !g.id.startsWith('legacy:'));
if (!live.length) return;
const rows = await sql(`SELECT id8, run_id, ota FROM console_ota_memo WHERE id8 = ANY($1)`, [live.map(g => g.id)]);
const memo = new Map(rows.map(r => [r.id8, r]));
for (const g of live) {
const m = memo.get(g.id);
if (g.app.ota) {
if (!m || m.run_id !== g.app.run_id || JSON.stringify(m.ota) !== JSON.stringify(g.app.ota)) {
await sql(`INSERT INTO console_ota_memo (id8, run_id, ota, updated_at) VALUES ($1, $2, $3::jsonb, now())
ON CONFLICT (id8) DO UPDATE SET run_id = EXCLUDED.run_id, ota = EXCLUDED.ota, updated_at = now()`, [g.id, g.app.run_id, JSON.stringify(g.app.ota)]);
}
} else if (m && m.run_id === g.app.run_id && m.ota) {
g.app.ota = { ...m.ota, remembered: true };
}
}
} catch (e) { /* a convenience: the cards render without it */ }
}
async function machines(sql) {
// node logs get a longer tail: the connection-manager (peers) line is rare on a busy node
// the app log's tail is wide (400 KB, about three hours on a PC that logs every block) so a stuck update's lines do
@ -75,7 +100,7 @@ async function machines(sql) {
g.labels.push({ label: r.label, at, run_id: r.run_id });
if (p.stream === 'node') { g.node = { ...parseNodeTail(r.tail), at, run_id: r.run_id }; }
else if (p.stream === 'miner') g.cards.push({ label: r.label.replace(/^miner-/, ''), vendor: p.vendor, card: p.card, at, ...parseMinerTail(r.tail) });
else if (p.stream === 'app') g.app = { at, ...parseAppTail(r.tail) };
else if (p.stream === 'app') g.app = { at, run_id: r.run_id, ...parseAppTail(r.tail) };
groups.set(p.id, g);
}
// node version lives in the first upload of a run (the tail may have lost the banner): one cheap query for the current runs
@ -88,6 +113,7 @@ async function machines(sql) {
const hd = parseHeader(h.head); if (hd && !g.header) g.header = hd;
}
}
await otaMemo(sql, groups);
const now = Date.now();
const out = [...groups.values()].map(g => {
const id8 = g.id.startsWith('legacy:') ? null : g.id;