gwoe-antragspruefer/app/templates/v2/components/queue_widget.html

94 lines
4.5 KiB
HTML
Raw Normal View History

{#
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"
style="position:fixed;bottom:1rem;left:1rem;
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) {
var allJobs = qs.jobs || [];
var jobs = allJobs.filter(function (j) { return j.status !== 'stale'; });
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;
var workers = qs.workers_running != null ? qs.workers_running : '?';
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');
if (parts.length === 0) {
parts.push('Queue leer · ' + workers + ' Worker bereit');
}
text.textContent = parts.join(' · ');
var tip = document.getElementById('v2-queue-tooltip');
if (!tip) return;
// 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) {
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>