diff --git a/app/config.py b/app/config.py index a60ff43..d172ce2 100644 --- a/app/config.py +++ b/app/config.py @@ -86,7 +86,7 @@ class Settings(BaseSettings): ssh_key: str = "" # PEM private key content (paste full key including headers) # 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) ---- # session_secret: HMAC key for signing session cookies. Empty = generate diff --git a/app/static/app.css b/app/static/app.css index cee9114..c1f30f5 100644 --- a/app/static/app.css +++ b/app/static/app.css @@ -2939,3 +2939,37 @@ th.sort-desc::after { .stage-reason-failed .stage-reason-text { color: var(--red, #f85149); } .stage-reason-cancelled .stage-reason-text, .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; +} diff --git a/app/static/app.js b/app/static/app.js index ec4f82a..584a68b 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -1544,7 +1544,30 @@ html += ''; + html += ''; + // 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 += ''; + html += 'Est. completion'; + html += '' + jobFinishStr + ''; + html += ''; + } + } + html += ''; html += '