Auto-commit: v2.13 — Add Live Status diagnostic panel to run_details UI
- Backend (web/routes/run.py): Extended /run/<id>/status JSON response with
server_timestamp, db_log_count, and latest_log_timestamp so clients can
detect whether the DB is being written to independently of the log text.
- Frontend (templates/run_details.html):
• Added Live Status Panel above the System Log card, showing:
- Polling state badge (Initializing / Requesting / Waiting Ns / Error / Idle)
- Last Successful Update timestamp (HH:MM:SS, updated every successful poll)
- DB diagnostics (log count + latest log timestamp from server response)
- Last Error message displayed inline when a poll fails
- Force Refresh button to immediately trigger a new poll
• Refactored JS polling loop: countdown timer with clearCountdown/
startWaitCountdown helpers, forceRefresh() clears pending timers before
re-polling, explicit pollTimer/countdownInterval tracking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -286,6 +286,25 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Live Status Panel -->
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-body py-2 px-3">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div class="d-flex align-items-center gap-3 flex-wrap">
|
||||
<span class="small text-muted fw-semibold">Poll:</span>
|
||||
<span id="poll-state" class="badge bg-secondary">Initializing...</span>
|
||||
<span class="small text-muted">Last update:</span>
|
||||
<span id="last-update-time" class="small fw-bold text-info">—</span>
|
||||
<span id="db-diagnostics" class="small text-muted"></span>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-info py-0 px-2" onclick="forceRefresh()" title="Immediately trigger a new poll request">
|
||||
<i class="fas fa-bolt me-1"></i>Force Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div id="poll-error-msg" class="small text-danger mt-1" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Collapsible Log -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#logCollapse">
|
||||
@@ -344,6 +363,8 @@
|
||||
const costEl = document.getElementById('run-cost');
|
||||
|
||||
let lastLog = '';
|
||||
let pollTimer = null;
|
||||
let countdownInterval = null;
|
||||
|
||||
// Phase → colour mapping (matches utils.log phase labels)
|
||||
const PHASE_COLORS = {
|
||||
@@ -386,10 +407,67 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
// --- Live Status Panel helpers ---
|
||||
|
||||
function clearCountdown() {
|
||||
if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; }
|
||||
}
|
||||
|
||||
function setPollState(text, badgeClass) {
|
||||
const el = document.getElementById('poll-state');
|
||||
if (el) { el.className = 'badge ' + badgeClass; el.innerText = text; }
|
||||
}
|
||||
|
||||
function setPollError(msg) {
|
||||
const el = document.getElementById('poll-error-msg');
|
||||
if (!el) return;
|
||||
if (msg) { el.innerText = 'Last error: ' + msg; el.style.display = ''; }
|
||||
else { el.innerText = ''; el.style.display = 'none'; }
|
||||
}
|
||||
|
||||
function startWaitCountdown(seconds, isError) {
|
||||
clearCountdown();
|
||||
let rem = seconds;
|
||||
const cls = isError ? 'bg-danger' : 'bg-secondary';
|
||||
const prefix = isError ? 'Error — retry in' : 'Waiting';
|
||||
setPollState(prefix + ' (' + rem + 's)', cls);
|
||||
countdownInterval = setInterval(() => {
|
||||
rem--;
|
||||
if (rem <= 0) { clearCountdown(); }
|
||||
else { setPollState(prefix + ' (' + rem + 's)', cls); }
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function forceRefresh() {
|
||||
clearCountdown();
|
||||
if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
|
||||
updateLog();
|
||||
}
|
||||
|
||||
// --- Main polling function ---
|
||||
|
||||
function updateLog() {
|
||||
setPollState('Requesting...', 'bg-primary');
|
||||
fetch(`/run/${runId}/status`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Update "Last Successful Update" timestamp
|
||||
const now = new Date();
|
||||
const lastUpdateEl = document.getElementById('last-update-time');
|
||||
if (lastUpdateEl) lastUpdateEl.innerText = now.toLocaleTimeString();
|
||||
|
||||
// Update DB diagnostics
|
||||
const diagEl = document.getElementById('db-diagnostics');
|
||||
if (diagEl) {
|
||||
const parts = [];
|
||||
if (data.db_log_count !== undefined) parts.push('DB logs: ' + data.db_log_count);
|
||||
if (data.latest_log_timestamp) parts.push('Latest: ' + String(data.latest_log_timestamp).substring(11, 19));
|
||||
diagEl.innerText = parts.join(' | ');
|
||||
}
|
||||
|
||||
// Clear any previous poll error
|
||||
setPollError(null);
|
||||
|
||||
// Update Status Text + current phase
|
||||
const statusLabel = data.status.charAt(0).toUpperCase() + data.status.slice(1);
|
||||
if (data.status === 'running') {
|
||||
@@ -435,11 +513,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Poll if running
|
||||
// Schedule next poll or stop
|
||||
if (data.status === 'running' || data.status === 'queued') {
|
||||
setTimeout(updateLog, 2000);
|
||||
startWaitCountdown(2, false);
|
||||
pollTimer = setTimeout(updateLog, 2000);
|
||||
} else {
|
||||
// If the run was active when we loaded the page, reload now that it's finished to show artifacts
|
||||
setPollState('Idle', 'bg-success');
|
||||
// If the run was active when we loaded the page, reload to show artifacts
|
||||
if (initialStatus === 'running' || initialStatus === 'queued') {
|
||||
window.location.reload();
|
||||
}
|
||||
@@ -447,7 +527,10 @@
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Polling failed:", err);
|
||||
setTimeout(updateLog, 5000);
|
||||
const errMsg = err.message || String(err);
|
||||
setPollError(errMsg);
|
||||
startWaitCountdown(5, true);
|
||||
pollTimer = setTimeout(updateLog, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user