fix(989): correct four live build-log viewer bugs in logs.js
- live detection: use !h.status (null while running) not === 'running'
- SSE: replace named addEventListener('chunk'/'done') with onmessage +
frame.done check — backend sends generic message events, no named type
- SSE payload: use frame.stdout_append/stderr_append not frame.text
- timestamps: started_at/finished_at are unix seconds, multiply by 1000
was already done implicitly — actually use unix arithmetic directly
to avoid new Date() confusion entirely
Also move the misplaced .flow-main-slim rule from the logs section to
the flow-shell section of dashboard.css.
This commit is contained in:
parent
0b15cad93f
commit
ee61eac663
2 changed files with 43 additions and 27 deletions
|
|
@ -1805,6 +1805,11 @@ body.flow-shell .tabbar .tab.active.tab-link {
|
|||
bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* flow.html now has a slim header (back link only) instead of the full
|
||||
tabbar — reduce the top padding to match the smaller header height. */
|
||||
body.flow-shell .flow-main-slim {
|
||||
padding-top: calc(var(--flow-header-h) + 0.4em);
|
||||
}
|
||||
.flow-main .terminal-wrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
|
@ -1864,9 +1869,6 @@ body.logs-shell {
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body.flow-shell .flow-main-slim {
|
||||
padding-top: calc(var(--flow-header-h) + 0.4em);
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
position: sticky;
|
||||
|
|
|
|||
|
|
@ -40,13 +40,11 @@ import {
|
|||
|
||||
// ─── helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
function fmtTs(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
try {
|
||||
const d = new Date(isoStr);
|
||||
const age = Math.floor((Date.now() - d.getTime()) / 1000);
|
||||
return fmtAgeSecs(age) + ' ago';
|
||||
} catch { return isoStr; }
|
||||
// started_at / finished_at are unix seconds (i64), not milliseconds.
|
||||
function fmtTs(unixSecs) {
|
||||
if (!unixSecs) return '';
|
||||
const age = Math.floor(Date.now() / 1000) - unixSecs;
|
||||
return fmtAgeSecs(Math.max(0, age)) + ' ago';
|
||||
}
|
||||
|
||||
function fmtDuration(secs) {
|
||||
|
|
@ -76,10 +74,11 @@ import {
|
|||
const ul = el('ul', { class: 'build-logs-list' });
|
||||
for (const h of rows) {
|
||||
const li = el('li', { class: 'build-logs-item' });
|
||||
// status is null while running, 'ok'/'fail' when finished.
|
||||
const live = !h.status;
|
||||
const ok = h.status === 'ok';
|
||||
const live = h.status === 'running';
|
||||
const statusClass = ok ? 'build-logs-ok' : live ? 'build-logs-live-badge badge badge-running' : 'build-logs-fail';
|
||||
const statusLabel = ok ? '✓' : live ? 'live' : '✗';
|
||||
const statusClass = live ? 'badge badge-running' : ok ? 'badge badge-ok' : 'badge badge-fail';
|
||||
const statusLabel = live ? 'live' : ok ? 'ok' : 'fail';
|
||||
const age = h.finished_at ? fmtTs(h.finished_at) : (live ? '' : fmtTs(h.started_at));
|
||||
const runtime = h.runtime_secs != null
|
||||
? el('span', { class: 'build-logs-runtime meta' }, fmtDuration(Math.max(0, h.runtime_secs)))
|
||||
|
|
@ -125,20 +124,35 @@ import {
|
|||
const badge = el('span', { class: 'build-logs-live-badge badge badge-running' }, 'live');
|
||||
detail.append(badge, pre);
|
||||
streamEs = new EventSource('/api/build-logs/id/' + h.id + '/stream');
|
||||
streamEs.addEventListener('chunk', (e) => {
|
||||
try { pre.textContent += JSON.parse(e.data).text; } catch { /**/ }
|
||||
});
|
||||
streamEs.addEventListener('done', () => {
|
||||
badge.textContent = 'done';
|
||||
badge.className = 'build-logs-live-badge badge';
|
||||
streamEs.close(); streamEs = null;
|
||||
detail.dataset.loaded = '1';
|
||||
});
|
||||
streamEs.addEventListener('error', () => {
|
||||
badge.textContent = 'stream error';
|
||||
badge.className = 'build-logs-live-badge badge';
|
||||
streamEs.close(); streamEs = null;
|
||||
});
|
||||
let stdoutLen = 0, stderrLen = 0;
|
||||
streamEs.onmessage = (e) => {
|
||||
let frame;
|
||||
try { frame = JSON.parse(e.data); } catch { return; }
|
||||
if (frame.stdout_append) {
|
||||
pre.textContent += frame.stdout_append;
|
||||
stdoutLen += frame.stdout_append.length;
|
||||
}
|
||||
if (frame.stderr_append) {
|
||||
if (stderrLen === 0) pre.textContent += '\n--- stderr ---\n';
|
||||
pre.textContent += frame.stderr_append;
|
||||
stderrLen += frame.stderr_append.length;
|
||||
}
|
||||
if (frame.done) {
|
||||
badge.className = frame.status === 'ok' ? 'badge badge-ok' : 'badge badge-fail';
|
||||
badge.textContent = frame.status || 'done';
|
||||
streamEs.close(); streamEs = null;
|
||||
detail.dataset.loaded = '1';
|
||||
}
|
||||
};
|
||||
streamEs.onerror = () => {
|
||||
if (streamEs && streamEs.readyState === EventSource.CONNECTING) {
|
||||
pre.textContent = ''; stdoutLen = 0; stderrLen = 0;
|
||||
} else {
|
||||
badge.textContent = 'stream error';
|
||||
badge.className = 'badge badge-fail';
|
||||
if (streamEs) { streamEs.close(); streamEs = null; }
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const pre = el('pre', { class: 'build-logs-output' }, 'fetching…');
|
||||
detail.append(pre);
|
||||
|
|
|
|||
Loading…
Reference in a new issue