build-logs: SSE live streaming + raw download (#726 phase 4)

Backend (hive-c0re):
- build_logs.rs: add tokio::sync::broadcast::Sender<i64> to BuildLogs;
  append() and finish() notify subscribers after each sqlite write.
  Add BuildLogProgress struct and get_progress(id, stdout_cursor,
  stderr_cursor) for incremental delta reads.
- dashboard.rs: two new endpoints —
    GET /api/build-logs/id/{id}/stream  SSE; streams BuildLogFrame
      {stdout_append, stderr_append, status?, done} deltas until the
      build finishes or the browser disconnects. Backed by an mpsc
      channel task that watches the per-build broadcast notifications.
    GET /api/build-logs/id/{id}/raw    text/plain download with
      Content-Disposition: attachment; filename build-log-{agent}-{id}.txt

Frontend (dashboard):
- tabs.js: running builds (status === null) connect an EventSource to
  /stream and append lines live; "live" badge pulses amber while active,
  flips to ok/fail on done. Finished builds still use the JSON fetch path.
  Collapsing a running panel closes the EventSource; re-expanding
  reconnects. Adds a "⬇ raw" download link to every expanded row.
- dashboard.css: .build-logs-dl inline download link; .build-logs-live
  live pulse @keyframes animation.

Docs: web-ui.md updated for all three new endpoints + behaviour.
This commit is contained in:
iris 2026-05-31 21:23:01 +02:00 committed by mara
commit 050e130eba
5 changed files with 365 additions and 14 deletions

View file

@ -587,6 +587,28 @@ a:hover {
.badge-ok { background: rgba(166,227,161,0.12); color: var(--green); border-color: var(--green); }
.badge-fail { background: rgba(243,139,168,0.12); color: var(--red); border-color: var(--red); }
.badge-running { background: rgba(250,179,135,0.12); color: var(--amber); border-color: var(--amber); }
/* Download link sits inline after the row button. Shown only while
the detail is expanded (toggled by JS). */
.build-logs-dl {
display: none;
font-size: 0.72em;
padding: 0.15em 0.45em;
margin-left: 0.3em;
color: var(--muted);
text-decoration: none;
border: 1px solid var(--border);
border-radius: 3px;
}
.build-logs-dl:not([hidden]) { display: inline-block; }
.build-logs-dl:hover { color: var(--fg); border-color: var(--purple-dim); }
/* Live-streaming indicator badge inside the detail pane header. */
.build-logs-live-badge { margin-bottom: 0.4em; }
/* Pulse animation on the "live" badge text while streaming. */
.build-logs-live-badge.badge-running { animation: live-pulse 1.4s ease-in-out infinite; }
@keyframes live-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.45; }
}
/* Notification controls sit between the banner and the
containers section. Hidden by JS when notifications are