2026-04-25 21:50:36 +02:00
|
|
|
{#
|
|
|
|
|
queue_widget.html — Queue-Statusbar mit Hover-Tooltip (#149).
|
|
|
|
|
|
|
|
|
|
Wird am Ende von base.html eingebunden via {% include %}. Self-contained:
|
|
|
|
|
Eigenes <div id="v2-queue-statusbar"> + <div id="v2-queue-tooltip"> +
|
|
|
|
|
Polling-Script. Pollt alle 5 s `/api/queue/status` und blendet sich aus,
|
|
|
|
|
wenn keine Jobs aktiv/fertig/fehlgeschlagen sind.
|
|
|
|
|
|
|
|
|
|
Portiert aus classic-UI (#99). Nutzt v2-Tokens statt classic-Variablen.
|
|
|
|
|
#}
|
|
|
|
|
|
|
|
|
|
<div id="v2-queue-statusbar"
|
2026-04-25 22:13:30 +02:00
|
|
|
style="position:fixed;bottom:1rem;left:1rem;
|
2026-04-25 21:50:36 +02:00
|
|
|
background:var(--ecg-card-bg);border:1px solid var(--ecg-light);
|
|
|
|
|
border-radius:6px;padding:0.4rem 0.8rem;
|
|
|
|
|
font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);
|
|
|
|
|
box-shadow:0 2px 8px rgba(0,0,0,0.1);z-index:100;cursor:default;
|
|
|
|
|
transition:all 0.2s;"
|
|
|
|
|
onmouseenter="document.getElementById('v2-queue-tooltip').style.display='block'"
|
|
|
|
|
onmouseleave="document.getElementById('v2-queue-tooltip').style.display='none'"
|
|
|
|
|
aria-label="Analyse-Queue Status">
|
|
|
|
|
<span id="v2-queue-status-text"></span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="v2-queue-tooltip"
|
|
|
|
|
style="display:none;position:fixed;bottom:3.5rem;left:1rem;
|
|
|
|
|
background:var(--ecg-card-bg);border:1px solid var(--ecg-light);
|
|
|
|
|
border-radius:6px;padding:0.8rem 1rem;
|
|
|
|
|
font-family:var(--font-sans);font-size:12px;color:var(--ecg-dark);
|
|
|
|
|
box-shadow:0 4px 16px rgba(0,0,0,0.15);z-index:101;
|
|
|
|
|
max-width:420px;max-height:320px;overflow-y:auto;">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
(function () {
|
|
|
|
|
function poll() {
|
|
|
|
|
fetch('/api/queue/status')
|
|
|
|
|
.then(function (r) { return r.json(); })
|
|
|
|
|
.then(function (qs) {
|
2026-04-25 22:13:30 +02:00
|
|
|
var allJobs = qs.jobs || [];
|
|
|
|
|
var jobs = allJobs.filter(function (j) { return j.status !== 'stale'; });
|
2026-04-25 21:50:36 +02:00
|
|
|
var processing = jobs.filter(function (j) { return j.status === 'processing'; }).length;
|
|
|
|
|
var queued = jobs.filter(function (j) { return j.status === 'queued' || j.status === 'pending'; }).length;
|
|
|
|
|
var completed = jobs.filter(function (j) { return j.status === 'completed'; }).length;
|
|
|
|
|
var failed = jobs.filter(function (j) { return j.status === 'failed'; }).length;
|
|
|
|
|
|
|
|
|
|
var bar = document.getElementById('v2-queue-statusbar');
|
|
|
|
|
var text = document.getElementById('v2-queue-status-text');
|
|
|
|
|
if (!bar || !text) return;
|
|
|
|
|
|
2026-04-25 22:13:30 +02:00
|
|
|
var workers = qs.workers_running != null ? qs.workers_running : '?';
|
2026-04-25 21:50:36 +02:00
|
|
|
var parts = [];
|
|
|
|
|
if (processing > 0) parts.push('⏳ ' + processing + ' in Bearbeitung');
|
|
|
|
|
if (queued > 0) parts.push('⏸ ' + queued + ' wartend');
|
|
|
|
|
if (completed > 0) parts.push('✓ ' + completed + ' fertig');
|
|
|
|
|
if (failed > 0) parts.push('✗ ' + failed + ' fehlgeschlagen');
|
2026-04-25 22:13:30 +02:00
|
|
|
if (parts.length === 0) {
|
|
|
|
|
parts.push('Queue leer · ' + workers + ' Worker bereit');
|
|
|
|
|
}
|
2026-04-25 21:50:36 +02:00
|
|
|
text.textContent = parts.join(' · ');
|
|
|
|
|
|
|
|
|
|
var tip = document.getElementById('v2-queue-tooltip');
|
|
|
|
|
if (!tip) return;
|
2026-04-25 22:13:30 +02:00
|
|
|
// Tooltip zeigt bevorzugt aktive Jobs, Stale als „letzter Lauf"-Block.
|
|
|
|
|
var displayJobs = jobs.length ? jobs : allJobs;
|
|
|
|
|
var rows = displayJobs.slice(0, 20).map(function (j) {
|
2026-04-25 21:50:36 +02:00
|
|
|
var icon = j.status === 'completed' ? '✓'
|
|
|
|
|
: j.status === 'processing' ? '⏳'
|
|
|
|
|
: j.status === 'failed' ? '✗'
|
|
|
|
|
: '⏸';
|
|
|
|
|
var dur = j.duration ? (' · ' + j.duration + 's') : '';
|
|
|
|
|
var bl = j.bundesland ? (' · ' + j.bundesland) : '';
|
|
|
|
|
var ds = j.drucksache || '?';
|
|
|
|
|
var dsLink = j.status === 'completed' && j.drucksache
|
|
|
|
|
? '<a href="/antrag/' + encodeURIComponent(j.drucksache) + '" style="color:var(--ecg-blue);">' + ds + '</a>'
|
|
|
|
|
: ds;
|
|
|
|
|
return '<div style="padding:0.25rem 0;border-bottom:1px solid var(--ecg-light);">'
|
|
|
|
|
+ '<span style="font-family:var(--font-mono);">' + icon + '</span> '
|
|
|
|
|
+ dsLink
|
|
|
|
|
+ '<span style="font-family:var(--font-mono);color:var(--ecg-text-muted);font-size:0.85em;">' + bl + dur + '</span>'
|
|
|
|
|
+ '</div>';
|
|
|
|
|
}).join('');
|
|
|
|
|
tip.innerHTML = '<div style="margin-bottom:0.5rem;font-weight:900;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:var(--ecg-blue);">Queue · '
|
|
|
|
|
+ workers + ' Worker</div>'
|
|
|
|
|
+ (rows || '<div style="color:var(--ecg-text-muted);">leer</div>');
|
|
|
|
|
})
|
|
|
|
|
.catch(function () { /* still */ });
|
|
|
|
|
}
|
|
|
|
|
// erster Aufruf direkt + danach alle 5 s
|
|
|
|
|
poll();
|
|
|
|
|
setInterval(poll, 5000);
|
|
|
|
|
})();
|
|
|
|
|
</script>
|