Batch+Queue ins Hamburger: Overlay-Panels mit Live-Status, Queue immer sichtbar

This commit is contained in:
Dotty Dotter 2026-04-10 23:27:27 +02:00
parent cf313bd257
commit 13714410ab

View File

@ -740,6 +740,9 @@
<a href="/quellen">📚 Quellen</a> <a href="/quellen">📚 Quellen</a>
<a href="/methodik">🔍 Methodik</a> <a href="/methodik">🔍 Methodik</a>
<hr style="margin:0.3rem 0;border:none;border-top:1px solid #eee;"> <hr style="margin:0.3rem 0;border:none;border-top:1px solid #eee;">
<button onclick="event.stopPropagation();document.getElementById('queue-panel').style.display='block';document.getElementById('hamburger-menu').classList.remove('open');loadQueuePanel();">📊 Queue</button>
<button onclick="event.stopPropagation();document.getElementById('batch-panel').style.display='block';document.getElementById('hamburger-menu').classList.remove('open');">📦 Batch-Analyse</button>
<hr style="margin:0.3rem 0;border:none;border-top:1px solid #eee;">
<button id="auth-btn" onclick="event.stopPropagation();">🔑 Anmelden</button> <button id="auth-btn" onclick="event.stopPropagation();">🔑 Anmelden</button>
</div> </div>
</div> </div>
@ -2267,5 +2270,96 @@
} }
} }
</script> </script>
<!-- Queue-Panel Overlay -->
<div id="queue-panel" style="display:none;position:fixed;top:0;right:0;width:400px;height:100vh;background:white;box-shadow:-4px 0 12px rgba(0,0,0,0.15);z-index:200;overflow-y:auto;padding:1.5rem;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
<h3 style="color:var(--color-blue);margin:0;">📊 Queue</h3>
<button onclick="document.getElementById('queue-panel').style.display='none'" style="background:none;border:none;font-size:1.2rem;cursor:pointer;"></button>
</div>
<div id="queue-panel-content"><span style="color:#aaa;">Lade...</span></div>
</div>
<!-- Batch-Panel Overlay -->
<div id="batch-panel" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:450px;background:white;box-shadow:0 8px 24px rgba(0,0,0,0.2);z-index:200;border-radius:8px;padding:1.5rem;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
<h3 style="color:var(--color-blue);margin:0;">📦 Batch-Analyse</h3>
<button onclick="document.getElementById('batch-panel').style.display='none'" style="background:none;border:none;font-size:1.2rem;cursor:pointer;"></button>
</div>
<p style="font-size:0.85rem;color:#666;margin-bottom:1rem;">Analysiert automatisch die neuesten ungeprüften Anträge.</p>
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
<select id="batch-bl-modal" style="padding:0.4rem;border:1px solid #ddd;border-radius:4px;">
{% for bl in bundeslaender if bl.code != 'ALL' and bl.active %}
<option value="{{ bl.code }}">{{ bl.name }}</option>
{% endfor %}
</select>
<select id="batch-limit-modal" style="padding:0.4rem;border:1px solid #ddd;border-radius:4px;">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
<button onclick="startBatchModal()" style="padding:0.4rem 1rem;background:var(--color-green);color:white;border:none;border-radius:4px;cursor:pointer;">🚀 Starten</button>
</div>
<div id="batch-modal-status" style="margin-top:0.75rem;font-size:0.85rem;"></div>
</div>
<script>
function loadQueuePanel() {
const el = document.getElementById('queue-panel-content');
async function refresh() {
try {
const qs = await fetch('/api/queue/status').then(r => r.json());
const jobs = qs.jobs || [];
if (jobs.length === 0 && qs.pending === 0) {
el.innerHTML = '<p style="color:#888;">Keine Aufträge in der Warteschlange.</p>';
return;
}
const completed = jobs.filter(j => j.status === 'completed').length;
const pct = jobs.length > 0 ? Math.round(completed / jobs.length * 100) : 0;
el.innerHTML = `
<div style="margin-bottom:0.5rem;font-size:0.85rem;color:#666;">
${qs.concurrency} Worker · ${qs.pending} wartend · ${qs.processed_total} verarbeitet
${qs.shutting_down ? '<br><span style="color:#dc3545;">⚠ Server wird heruntergefahren</span>' : ''}
</div>
<div style="background:#eee;border-radius:4px;height:6px;margin-bottom:0.75rem;">
<div style="background:var(--color-green);height:100%;width:${pct}%;transition:width 0.5s;border-radius:4px;"></div>
</div>
<table style="width:100%;font-size:0.8rem;border-collapse:collapse;">
${jobs.map(j => {
const icon = j.status === 'completed' ? '✅' : j.status === 'processing' ? '⏳' : j.status === 'failed' ? '❌' : '⏸';
return '<tr style="border-top:1px solid #f0f0f0;"><td>' + (j.drucksache || j.job_id.substring(0,8)) + '</td><td>' + icon + '</td><td>' + (j.duration ? j.duration + 's' : '') + '</td></tr>';
}).join('')}
</table>`;
} catch { el.innerHTML = '<span style="color:#c00;">Fehler</span>'; }
}
refresh();
// Auto-refresh solange Panel offen
const iv = setInterval(() => {
if (document.getElementById('queue-panel').style.display === 'none') { clearInterval(iv); return; }
refresh();
}, 3000);
}
async function startBatchModal() {
const bl = document.getElementById('batch-bl-modal').value;
const limit = document.getElementById('batch-limit-modal').value;
const status = document.getElementById('batch-modal-status');
status.innerHTML = '<span style="color:var(--color-blue);">⏳ Wird gestartet...</span>';
try {
const resp = await fetch('/api/batch-analyze', {
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'bundesland=' + bl + '&limit=' + limit
});
if (resp.status === 401 || resp.status === 403) {
status.innerHTML = '<span style="color:#dc3545;">🔒 Admin-Berechtigung erforderlich.</span>';
return;
}
const data = await resp.json();
status.innerHTML = '<span style="color:var(--color-green);">✓ ' + (data.enqueued || 0) + ' Anträge eingereiht. Queue-Panel öffnen um Fortschritt zu sehen.</span>';
} catch (e) {
status.innerHTML = '<span style="color:#dc3545;">❌ ' + e.message + '</span>';
}
}
</script>
</body> </body>
</html> </html>