fix(diarization-ui): jobs page server-side fallback list + robust live refresh/status

This commit is contained in:
2026-03-21 15:10:39 +01:00
parent 7397dcaf4c
commit 6990be7903

50
app.py
View File

@@ -769,12 +769,19 @@ def jobs_delete(job_id: int):
@app.get("/jobs", response_class=HTMLResponse)
def jobs_page(queued: Optional[int] = None):
items = _jobs_payload(200)
pre = "".join([
f"<div class='card'><b>Job #{it['id']}</b> [{it['kind']}] · <b>{it['status']}</b><br><small>{it['created_at']}</small></div>"
for it in items
]) or "<p>Keine Jobs.</p>"
notice = f"<p><b>Job #{queued} wurde eingereiht.</b></p>" if queued else ""
body = f"""
<h2>Hintergrundverarbeitung</h2>
<p class='hint'>Maximal 2 Jobs gleichzeitig. Seite aktualisiert automatisch.</p>
{notice}
<div id='jobs-root'></div>
<div id='jobs-status' class='hint'>Live-Update aktiv …</div>
<div id='jobs-root'>{pre}</div>
<script>
function since(ts) {{
if(!ts) return '-';
@@ -788,24 +795,29 @@ async function post(url) {{
if(!r.ok) alert('Fehler '+r.status);
}}
async function renderJobs() {{
const r = await fetch('/jobs/data');
const j = await r.json();
const root = document.getElementById('jobs-root');
root.innerHTML = '';
if(!j.items.length) {{ root.innerHTML = '<p>Keine Jobs.</p>'; return; }}
for(const it of j.items) {{
const d = document.createElement('div'); d.className='card';
const actions = [];
if(!['done','error','cancelled'].includes(it.status)) actions.push("<a href='#' class='iconbtn' onclick=\"cancelJob("+it.id+");return false;\">⛔</a>");
actions.push("<a href='#' class='iconbtn' onclick=\"deleteJob("+it.id+");return false;\">🗑️</a>");
const result = it.result_document_id ? ("<a href='/document/"+it.result_document_id+"'>Ergebnis öffnen</a>") : '';
const err = it.error ? ("<pre>"+String(it.error).replaceAll('<','&lt;')+"</pre>") : '';
d.innerHTML = "<b>Job #"+it.id+"</b> ["+it.kind+"] · <b>"+it.status+"</b> · läuft: "+since(it.started_at || it.created_at)+"<br><small>"+it.created_at+"</small><br>"
+(it.project_name?('Projekt: '+it.project_name+'<br>'):'')
+(it.document_title?('Dokument: '+it.document_title+'<br>'):'')
+(it.prompt_name?('Prompt: '+it.prompt_name+'<br>'):'')
+"<div class='row' style='margin-top:8px'>"+actions.join(' ')+" "+result+"</div>"+err;
root.appendChild(d);
try {{
const r = await fetch('/jobs/data');
const j = await r.json();
const root = document.getElementById('jobs-root');
root.innerHTML = '';
if(!j.items.length) {{ root.innerHTML = '<p>Keine Jobs.</p>'; return; }}
for(const it of j.items) {{
const d = document.createElement('div'); d.className='card';
const actions = [];
if(!['done','error','cancelled'].includes(it.status)) actions.push("<a href='#' class='iconbtn' onclick=\"cancelJob("+it.id+");return false;\"></a>");
actions.push("<a href='#' class='iconbtn' onclick=\"deleteJob("+it.id+");return false;\">🗑️</a>");
const result = it.result_document_id ? ("<a href='/document/"+it.result_document_id+"'>Ergebnis öffnen</a>") : '';
const err = it.error ? ("<pre>"+String(it.error).replaceAll('<','&lt;')+"</pre>") : '';
d.innerHTML = "<b>Job #"+it.id+"</b> ["+it.kind+"] · <b>"+it.status+"</b> · läuft: "+since(it.started_at || it.created_at)+"<br><small>"+it.created_at+"</small><br>"
+(it.project_name?('Projekt: '+it.project_name+'<br>'):'')
+(it.document_title?('Dokument: '+it.document_title+'<br>'):'')
+(it.prompt_name?('Prompt: '+it.prompt_name+'<br>'):'')
+"<div class='row' style='margin-top:8px'>"+actions.join(' ')+" "+result+"</div>"+err;
root.appendChild(d);
}}
document.getElementById('jobs-status').textContent = 'Live-Update aktiv';
}} catch(e) {{
document.getElementById('jobs-status').textContent = 'Live-Update Fehler: '+e;
}}
}}
async function cancelJob(id) {{ if(!confirm('Job abbrechen?')) return; await post('/jobs/'+id+'/cancel'); renderJobs(); }}