build-logs: surface runtime_secs in header; show duration in dashboard row

This commit is contained in:
damocles 2026-06-01 17:26:38 +02:00 committed by mara
commit 26c9c13cf5
2 changed files with 30 additions and 4 deletions

View file

@ -1126,10 +1126,19 @@ window.marked = marked;
: 'running'; : 'running';
const age = h.started_at const age = h.started_at
? fmtAgeSecs(nowUnix - h.started_at) + ' ago' : '?'; ? fmtAgeSecs(nowUnix - h.started_at) + ' ago' : '?';
const runtimeSpan = h.runtime_secs != null
? el('span', { class: 'build-logs-runtime meta' }, fmtDuration(h.runtime_secs))
: h.started_at && !h.finished_at
? el('span', {
class: 'build-logs-runtime meta',
'data-bl-elapsed': String(h.started_at),
}, fmtElapsed(Math.max(0, nowUnix - h.started_at)))
: null;
const rowBtn = el('button', const rowBtn = el('button',
{ type: 'button', class: 'build-logs-row-btn', 'aria-expanded': 'false' }, { type: 'button', class: 'build-logs-row-btn', 'aria-expanded': 'false' },
el('span', { class: `badge ${statusCls}` }, statusLabel), el('span', { class: `badge ${statusCls}` }, statusLabel),
el('span', { class: 'build-logs-kind' }, h.kind), el('span', { class: 'build-logs-kind' }, h.kind),
runtimeSpan,
el('span', { class: 'build-logs-age meta' }, age), el('span', { class: 'build-logs-age meta' }, age),
el('span', { class: 'build-logs-cmdline meta' }, h.cmdline), el('span', { class: 'build-logs-cmdline meta' }, h.cmdline),
); );
@ -2192,6 +2201,12 @@ window.marked = marked;
const elapsed = Math.max(0, Math.floor(Date.now() / 1000 - started)); const elapsed = Math.max(0, Math.floor(Date.now() / 1000 - started));
span.textContent = '· ' + fmtElapsed(elapsed); span.textContent = '· ' + fmtElapsed(elapsed);
} }
for (const span of document.querySelectorAll('.build-logs-runtime[data-bl-elapsed]')) {
const started = parseInt(span.dataset.blElapsed, 10);
if (!started) continue;
const elapsed = Math.max(0, Math.floor(Date.now() / 1000 - started));
span.textContent = fmtElapsed(elapsed);
}
}, 1000); }, 1000);
// ─── reminders ────────────────────────────────────────────────────────── // ─── reminders ──────────────────────────────────────────────────────────

View file

@ -96,6 +96,9 @@ pub struct BuildLogHeader {
pub started_at: i64, pub started_at: i64,
pub finished_at: Option<i64>, pub finished_at: Option<i64>,
pub status: Option<String>, pub status: Option<String>,
/// Elapsed seconds from `started_at` to `finished_at`. `None` while
/// the build is still in progress.
pub runtime_secs: Option<i64>,
} }
/// Full row with stdout/stderr text inlined. Returned by `get_full`, /// Full row with stdout/stderr text inlined. Returned by `get_full`,
@ -328,15 +331,19 @@ impl BuildLogs {
)?; )?;
let row = stmt let row = stmt
.query_row(params![id], |r| { .query_row(params![id], |r| {
let started_at: i64 = r.get(4)?;
let finished_at: Option<i64> = r.get(5)?;
let runtime_secs = finished_at.map(|f| f - started_at);
Ok(BuildLogFull { Ok(BuildLogFull {
header: BuildLogHeader { header: BuildLogHeader {
id: r.get(0)?, id: r.get(0)?,
agent: r.get(1)?, agent: r.get(1)?,
kind: r.get(2)?, kind: r.get(2)?,
cmdline: r.get(3)?, cmdline: r.get(3)?,
started_at: r.get(4)?, started_at,
finished_at: r.get(5)?, finished_at,
status: r.get(6)?, status: r.get(6)?,
runtime_secs,
}, },
stdout: r.get(7)?, stdout: r.get(7)?,
stderr: r.get(8)?, stderr: r.get(8)?,
@ -404,14 +411,18 @@ pub fn spawn_vacuum(coord: &Arc<crate::coordinator::Coordinator>) {
} }
fn row_to_header(r: &rusqlite::Row) -> rusqlite::Result<BuildLogHeader> { fn row_to_header(r: &rusqlite::Row) -> rusqlite::Result<BuildLogHeader> {
let started_at: i64 = r.get(4)?;
let finished_at: Option<i64> = r.get(5)?;
let runtime_secs = finished_at.map(|f| f - started_at);
Ok(BuildLogHeader { Ok(BuildLogHeader {
id: r.get(0)?, id: r.get(0)?,
agent: r.get(1)?, agent: r.get(1)?,
kind: r.get(2)?, kind: r.get(2)?,
cmdline: r.get(3)?, cmdline: r.get(3)?,
started_at: r.get(4)?, started_at,
finished_at: r.get(5)?, finished_at,
status: r.get(6)?, status: r.get(6)?,
runtime_secs,
}) })
} }