dashboard: stream the running rebuild's build log inline in the queue

The C0R3 rebuild queue only linked out to the logs page (logs →). Add an
inline live-log panel under the queue that streams the currently-running
rebuild's build output, so the operator watches progress without leaving the
page.

Extract the build-log SSE streaming logic (append stdout/stderr, sticky-bottom
scroll, stderr separator, reconnect-replay reset, done/error handling) into a
shared `openBuildLogStream(id, pre, {onDone, onError})` in common.js, and use it
from BOTH the L0GS page BUILD tab (logs.js, previously inline) and the new C0R3
panel (core.js) — one implementation, no duplication.

The panel is one persistent instance keyed to the running entry's build_log_id
(the queue runs one build at a time), in its own container (#rebuild-live-log)
outside rebuild-queue-section so the queue's per-row re-render — rows rebuild as
the build step advances — never tears down the open stream; it reconnects only
when the running build_log_id changes and won't reopen a stream that already
sent done. Collapsible, live/ok/fail badge, raw download. Hidden when nothing
is building; each row keeps its logs → link for full history.

Frontend-only — no backend change (endpoint + build_log_id already existed).
Closes #1860.
This commit is contained in:
iris 2026-06-22 01:55:38 +02:00
commit e797b75ca9
3 changed files with 79 additions and 78 deletions

View file

@ -305,6 +305,52 @@ export function openStream(url) {
return target;
}
// Stream a build log into a <pre>, returning the EventSource. Shared by the
// L0GS page BUILD tab and the C0R3 rebuild-queue live-log panel so the
// append / sticky-scroll / stderr-separator / reconnect-replay logic lives in
// one place. Appends `stdout_append` then `stderr_append` (one `--- stderr ---`
// separator) frames from `GET /api/build-logs/id/{id}/stream`; auto-scrolls to
// the bottom unless the operator scrolled up; on the terminal `done` frame
// closes the stream and calls `onDone(status)`; on a non-transient error
// closes and calls `onError()`. The backend replays accumulated output on each
// (re)connect, so a CONNECTING reconnect resets the <pre> to avoid doubling.
export function openBuildLogStream(id, pre, { onDone, onError } = {}) {
let atBottom = true;
pre.addEventListener('scroll', () => {
atBottom = pre.scrollHeight - pre.scrollTop - pre.clientHeight < 40;
});
let stderrSeen = false;
const es = new EventSource('/api/build-logs/id/' + id + '/stream');
es.onmessage = (e) => {
let frame;
try { frame = JSON.parse(e.data); } catch { return; }
if (frame.stdout_append) {
pre.textContent += frame.stdout_append;
if (atBottom) pre.scrollTop = pre.scrollHeight;
}
if (frame.stderr_append) {
if (!stderrSeen) { pre.textContent += '\n--- stderr ---\n'; stderrSeen = true; }
pre.textContent += frame.stderr_append;
if (atBottom) pre.scrollTop = pre.scrollHeight;
}
if (frame.done) {
es.close();
if (onDone) onDone(frame.status || 'done');
}
};
es.onerror = () => {
// CONNECTING = the browser is auto-reconnecting; the stream replays from
// the start, so clear the <pre> to avoid duplicated output and wait.
if (es.readyState === EventSource.CONNECTING) {
pre.textContent = ''; stderrSeen = false;
return;
}
es.close();
if (onError) onError();
};
return es;
}
// ─── side panel ─────────────────────────────────────────────────────────
// Singleton drawer that swipes in from the right. Long content
// (file previews, approval diffs, journald logs, applied config)