fix: Admin-Queue-Ansicht — Daten wurden nicht angezeigt

Bug: Template erwartete data.running, data.queued, data.failed.
API liefert aber data.jobs (mit status-Feld pro Job). Daher waren
alle drei Tabellen IMMER leer, selbst bei laufenden Jobs.

Fix:
- jobs nach status filtern (running | queued/pending | completed | failed)
- Neue Sektion "Zuletzt abgeschlossen" — vorher gar nicht angezeigt
  (20 completed Jobs auf dev waren unsichtbar)
- 4. Stat-Kachel "Abgeschlossen (Total)" mit data.processed_total
- Konfig-Info-Zeile: workers_running, max_size, avg_job_duration_seconds,
  estimated_wait_seconds — alles vorher ungenutzt im API-Response
- Spalte "Gestartet" → "Dauer (s)" (Daten-mismatch, started_at gibt's
  im API nicht)
- Wartende Jobs: bundesland-Spalte raus (nicht im API), durch
  Job-ID-Kurzform ersetzt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dotty Dotter 2026-05-03 22:49:13 +02:00
parent e2dbb796e6
commit cbc303f765

View File

@ -26,7 +26,7 @@
<div id="content" style="display:none;"> <div id="content" style="display:none;">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:24px;"> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:24px;">
<div class="v2-kasten" style="text-align:center;"> <div class="v2-kasten" style="text-align:center;">
<div style="font-family:var(--font-mono);font-size:28px;font-weight:700;color:var(--ecg-teal);" id="stat-running"></div> <div style="font-family:var(--font-mono);font-size:28px;font-weight:700;color:var(--ecg-teal);" id="stat-running"></div>
<div style="font-family:var(--font-mono);font-size:11px;opacity:0.6;margin-top:4px;">Läuft</div> <div style="font-family:var(--font-mono);font-size:11px;opacity:0.6;margin-top:4px;">Läuft</div>
@ -35,12 +35,22 @@
<div style="font-family:var(--font-mono);font-size:28px;font-weight:700;color:var(--ecg-blue);" id="stat-queued"></div> <div style="font-family:var(--font-mono);font-size:28px;font-weight:700;color:var(--ecg-blue);" id="stat-queued"></div>
<div style="font-family:var(--font-mono);font-size:11px;opacity:0.6;margin-top:4px;">Wartend</div> <div style="font-family:var(--font-mono);font-size:11px;opacity:0.6;margin-top:4px;">Wartend</div>
</div> </div>
<div class="v2-kasten" style="text-align:center;">
<div style="font-family:var(--font-mono);font-size:28px;font-weight:700;color:var(--ecg-green);" id="stat-completed"></div>
<div style="font-family:var(--font-mono);font-size:11px;opacity:0.6;margin-top:4px;">Abgeschlossen (Total)</div>
</div>
<div class="v2-kasten" style="text-align:center;"> <div class="v2-kasten" style="text-align:center;">
<div style="font-family:var(--font-mono);font-size:28px;font-weight:700;color:var(--ecg-dark);opacity:0.5;" id="stat-failed"></div> <div style="font-family:var(--font-mono);font-size:28px;font-weight:700;color:var(--ecg-dark);opacity:0.5;" id="stat-failed"></div>
<div style="font-family:var(--font-mono);font-size:11px;opacity:0.6;margin-top:4px;">Fehlgeschlagen</div> <div style="font-family:var(--font-mono);font-size:11px;opacity:0.6;margin-top:4px;">Fehlgeschlagen</div>
</div> </div>
</div> </div>
<!-- Konfig-Info -->
<div style="font-family:var(--font-mono);font-size:11px;opacity:0.65;margin-bottom:18px;">
Worker: <strong id="cfg-workers"></strong> · Max-Queue: <strong id="cfg-maxsize"></strong> ·
Ø Job-Dauer: <strong id="cfg-avg"></strong> · Geschätzte Wartezeit: <strong id="cfg-wait"></strong>
</div>
<!-- Laufende Jobs --> <!-- Laufende Jobs -->
<div style="margin-bottom:24px;"> <div style="margin-bottom:24px;">
<div style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.08em;opacity:0.5;margin-bottom:8px;text-transform:uppercase;">Laufende Jobs</div> <div style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.08em;opacity:0.5;margin-bottom:8px;text-transform:uppercase;">Laufende Jobs</div>
@ -49,7 +59,7 @@
</div> </div>
<table id="running-table" class="v2-admin-table" style="display:none;"> <table id="running-table" class="v2-admin-table" style="display:none;">
<thead> <thead>
<tr><th>Drucksache</th><th>Status</th><th>Gestartet</th></tr> <tr><th>Drucksache</th><th>Status</th><th>Dauer (s)</th></tr>
</thead> </thead>
<tbody id="running-rows"></tbody> <tbody id="running-rows"></tbody>
</table> </table>
@ -63,12 +73,26 @@
</div> </div>
<table id="queued-table" class="v2-admin-table" style="display:none;"> <table id="queued-table" class="v2-admin-table" style="display:none;">
<thead> <thead>
<tr><th>Drucksache</th><th>Bundesland</th><th>Eingereiht</th></tr> <tr><th>Drucksache</th><th>Job-ID</th></tr>
</thead> </thead>
<tbody id="queued-rows"></tbody> <tbody id="queued-rows"></tbody>
</table> </table>
</div> </div>
<!-- Abgeschlossene Jobs (Recent) -->
<div style="margin-bottom:24px;">
<div style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.08em;opacity:0.5;margin-bottom:8px;text-transform:uppercase;">Zuletzt abgeschlossen</div>
<div id="completed-empty" class="v2-kasten outline-blue" style="display:none;">
<p>Noch keine abgeschlossenen Jobs.</p>
</div>
<table id="completed-table" class="v2-admin-table" style="display:none;">
<thead>
<tr><th>Drucksache</th><th>Status</th><th>Dauer (s)</th></tr>
</thead>
<tbody id="completed-rows"></tbody>
</table>
</div>
<!-- Fehlgeschlagene Jobs --> <!-- Fehlgeschlagene Jobs -->
<div> <div>
<div style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.08em;opacity:0.5;margin-bottom:8px;text-transform:uppercase;">Fehlgeschlagene Jobs</div> <div style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.08em;opacity:0.5;margin-bottom:8px;text-transform:uppercase;">Fehlgeschlagene Jobs</div>
@ -77,7 +101,7 @@
</div> </div>
<table id="failed-table" class="v2-admin-table" style="display:none;"> <table id="failed-table" class="v2-admin-table" style="display:none;">
<thead> <thead>
<tr><th>Drucksache</th><th>Fehler</th><th>Zeit</th></tr> <tr><th>Drucksache</th><th>Fehler</th><th>Dauer (s)</th></tr>
</thead> </thead>
<tbody id="failed-rows"></tbody> <tbody id="failed-rows"></tbody>
</table> </table>
@ -110,6 +134,11 @@ function renderTable(tbodyId, tableId, emptyId, rows, renderRow) {
} }
} }
function fmtDuration(seconds) {
if (seconds == null) return '—';
return Number(seconds).toFixed(1);
}
async function refresh() { async function refresh() {
try { try {
const resp = await fetch('/api/queue/status'); const resp = await fetch('/api/queue/status');
@ -122,38 +151,56 @@ async function refresh() {
firstLoad = false; firstLoad = false;
} }
// Statistik-Kacheln // API-Format: { jobs: [{job_id, drucksache, status, duration, error}], pending, ... }
const running = data.running || []; // Status-Werte: queued | running | completed | failed
const queued = data.queued || data.waiting || []; const jobs = data.jobs || [];
const failed = data.failed || []; const running = jobs.filter(j => j.status === 'running');
const queued = jobs.filter(j => j.status === 'queued' || j.status === 'pending');
const completed = jobs.filter(j => j.status === 'completed');
const failed = jobs.filter(j => j.status === 'failed');
// Statistik-Kacheln
document.getElementById('stat-running').textContent = running.length; document.getElementById('stat-running').textContent = running.length;
document.getElementById('stat-queued').textContent = queued.length; document.getElementById('stat-queued').textContent = queued.length || data.pending || 0;
document.getElementById('stat-failed').textContent = failed.length; document.getElementById('stat-completed').textContent = data.processed_total != null ? data.processed_total : completed.length;
document.getElementById('stat-failed').textContent = data.failed_total != null ? data.failed_total : failed.length;
// Konfig-Info
document.getElementById('cfg-workers').textContent = data.workers_running != null ? data.workers_running : (data.concurrency || '—');
document.getElementById('cfg-maxsize').textContent = data.max_size != null ? data.max_size : '—';
document.getElementById('cfg-avg').textContent = data.avg_job_duration_seconds != null ? data.avg_job_duration_seconds.toFixed(1) + ' s' : '—';
document.getElementById('cfg-wait').textContent = data.estimated_wait_seconds != null ? data.estimated_wait_seconds.toFixed(0) + ' s' : '—';
// Laufende Jobs // Laufende Jobs
renderTable('running-rows', 'running-table', 'running-empty', running, j => ` renderTable('running-rows', 'running-table', 'running-empty', running, j => `
<tr> <tr>
<td style="font-family:var(--font-mono);font-size:12px;">${j.drucksache || j.id || '—'}</td> <td style="font-family:var(--font-mono);font-size:12px;">${j.drucksache || '—'}</td>
<td><span class="v2-admin-badge running">${j.status || 'running'}</span></td> <td><span class="v2-admin-badge running">${j.status || 'running'}</span></td>
<td style="font-family:var(--font-mono);font-size:11px;">${fmtTime(j.started_at || j.created_at)}</td> <td style="font-family:var(--font-mono);font-size:11px;">${fmtDuration(j.duration)}</td>
</tr>`); </tr>`);
// Wartende Jobs // Wartende Jobs
renderTable('queued-rows', 'queued-table', 'queued-empty', queued, j => ` renderTable('queued-rows', 'queued-table', 'queued-empty', queued, j => `
<tr> <tr>
<td style="font-family:var(--font-mono);font-size:12px;">${j.drucksache || j.id || '—'}</td> <td style="font-family:var(--font-mono);font-size:12px;">${j.drucksache || '—'}</td>
<td>${j.bundesland || '—'}</td> <td style="font-family:var(--font-mono);font-size:10px;opacity:0.5;">${(j.job_id || '').slice(0, 8)}</td>
<td style="font-family:var(--font-mono);font-size:11px;">${fmtTime(j.created_at || j.enqueued_at)}</td> </tr>`);
// Abgeschlossene Jobs (recent)
renderTable('completed-rows', 'completed-table', 'completed-empty', completed, j => `
<tr>
<td style="font-family:var(--font-mono);font-size:12px;">${j.drucksache || '—'}</td>
<td><span class="v2-admin-badge done">${j.status || 'completed'}</span></td>
<td style="font-family:var(--font-mono);font-size:11px;">${fmtDuration(j.duration)}</td>
</tr>`); </tr>`);
// Fehlgeschlagene Jobs // Fehlgeschlagene Jobs
renderTable('failed-rows', 'failed-table', 'failed-empty', failed, j => ` renderTable('failed-rows', 'failed-table', 'failed-empty', failed, j => `
<tr> <tr>
<td style="font-family:var(--font-mono);font-size:12px;">${j.drucksache || j.id || '—'}</td> <td style="font-family:var(--font-mono);font-size:12px;">${j.drucksache || '—'}</td>
<td style="font-size:11px;color:var(--ecg-blue);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" <td style="font-size:11px;color:var(--ecg-blue);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"
title="${(j.error || '').replace(/"/g,'&quot;')}">${j.error || '—'}</td> title="${(j.error || '').replace(/"/g,'&quot;')}">${j.error || '—'}</td>
<td style="font-family:var(--font-mono);font-size:11px;">${fmtTime(j.failed_at || j.updated_at)}</td> <td style="font-family:var(--font-mono);font-size:11px;">${fmtDuration(j.duration)}</td>
</tr>`); </tr>`);
document.getElementById('refresh-indicator').textContent = document.getElementById('refresh-indicator').textContent =