feat: job-level Est. completion in drawer header (1.0.0-53)
Some checks are pending
Security scan / pip-audit (push) Waiting to run
Security scan / bandit (push) Waiting to run
Security scan / gitleaks (push) Waiting to run
Security scan / mypy (push) Waiting to run

The drawer's per-stage Finish chip is the stage's finish, not the
whole burn-in's. Added a right-aligned "Est. completion" pill in
the drawer-job-header that uses the server-weighted burnin.percent
to extrapolate the whole job's finish time (covers precheck + SMART
+ surface_validate + final_check).

Suppressed under 0.5% job progress to avoid the early-sample
overshoot we saw earlier ("Finish: Sep 22" on a fresh start).

Bind-mount only (templates + static); no rebuild needed. Running
container reports 1.0.0-52 until next rebuild; this commit just
catches the source version up.
This commit is contained in:
Brandon Walter 2026-05-10 22:45:04 -07:00
parent c5a41d0260
commit c906ab15f7
3 changed files with 59 additions and 2 deletions

View file

@ -86,7 +86,7 @@ class Settings(BaseSettings):
ssh_key: str = "" # PEM private key content (paste full key including headers) ssh_key: str = "" # PEM private key content (paste full key including headers)
# Application version — used by the /api/v1/updates/check endpoint # Application version — used by the /api/v1/updates/check endpoint
app_version: str = "1.0.0-52" app_version: str = "1.0.0-53"
# ---- Authentication (1.0.0-22) ---- # ---- Authentication (1.0.0-22) ----
# session_secret: HMAC key for signing session cookies. Empty = generate # session_secret: HMAC key for signing session cookies. Empty = generate

View file

@ -2939,3 +2939,37 @@ th.sort-desc::after {
.stage-reason-failed .stage-reason-text { color: var(--red, #f85149); } .stage-reason-failed .stage-reason-text { color: var(--red, #f85149); }
.stage-reason-cancelled .stage-reason-text, .stage-reason-cancelled .stage-reason-text,
.stage-reason-unknown .stage-reason-text { color: var(--yellow, #d29922); } .stage-reason-unknown .stage-reason-text { color: var(--yellow, #d29922); }
/* -----------------------------------------------------------------------
Drawer job-level estimated completion (right-aligned in the header,
so it doesn't compete with the state chip + operator info).
----------------------------------------------------------------------- */
.drawer-job-header {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.drawer-job-finish {
margin-left: auto;
display: inline-flex;
align-items: baseline;
gap: 8px;
padding: 4px 10px;
background: var(--bg-soft, #161b22);
border: 1px solid var(--border, #30363d);
border-radius: 5px;
font-family: "SF Mono", "Consolas", monospace;
}
.drawer-job-finish-label {
font-size: 9px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: .04em;
}
.drawer-job-finish-value {
font-size: 12px;
color: var(--text-strong, #f0f6fc);
font-weight: 500;
font-variant-numeric: tabular-nums;
}

View file

@ -1544,7 +1544,30 @@
html += '<span class="drawer-job-meta">'; html += '<span class="drawer-job-meta">';
if (burnin.operator) html += 'by ' + _esc(burnin.operator); if (burnin.operator) html += 'by ' + _esc(burnin.operator);
if (burnin.started_at) html += ' \u00b7 ' + _drawerFmtDt(burnin.started_at); if (burnin.started_at) html += ' \u00b7 ' + _drawerFmtDt(burnin.started_at);
html += '</span></div>'; html += '</span>';
// Job-level estimated completion. Uses the weighted overall job %
// (recalculated server-side from stage progress) so it reflects
// every stage, not just the current one. Suppressed under 0.5%
// so the early sample doesn't paint a "Finish: Sep 22" stutter.
if (burnin.state === 'running' && burnin.started_at) {
var jobPct = parseFloat(burnin.percent || 0);
if (jobPct >= 0.5) {
var jobStartMs = Date.parse(burnin.started_at);
var jobElapsedSec = Math.max(0, (Date.now() - jobStartMs) / 1000);
var jobTotalSec = jobElapsedSec * (100 / jobPct);
var jobRemainSec = Math.max(0, jobTotalSec - jobElapsedSec);
var jobFinish = new Date(Date.now() + jobRemainSec * 1000);
var jobFinishStr = jobFinish.toLocaleString(undefined, {
weekday: 'short', month: 'short', day: 'numeric',
hour: 'numeric', minute: '2-digit',
});
html += '<span class="drawer-job-finish" title="Estimated completion of the entire burn-in (all stages)">';
html += '<span class="drawer-job-finish-label">Est. completion</span>';
html += '<span class="drawer-job-finish-value">' + jobFinishStr + '</span>';
html += '</span>';
}
}
html += '</div>';
html += '<div class="drawer-stages">'; html += '<div class="drawer-stages">';
var stages = burnin.stages || []; var stages = burnin.stages || [];