feat: Live-Download-Fortschritt + Jobs abbrechen
Job-Engine:
- Ausgabe wird binaer + byteweise gelesen und CRLF-bewusst verarbeitet:
einzelnes \r (tqdm/hf-Fortschritt) ueberschreibt die letzte Logzeile statt
sie zu fluten; \r\n bzw. \n = echte neue Zeile. So kommt der Download-
Fortschritt live an. (Text-Modus wuerde \r zu \n uebersetzen -> daher binaer.)
- cancel_job(): laufenden Prozess terminieren, Status "canceled", _PROCS-Registry
- POST /api/jobs/{id}/cancel
Frontend:
- Aktivitaet: Fortschrittsbalken + %-Anzeige aus der letzten Logzeile,
"Abbrechen"-Button je laufendem Job, Status "abgebrochen"
- Server/Aktueller Vorgang: Fortschrittsbalken + Abbrechen (mit Rueckfrage),
"abgebrochen"-Behandlung
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
// jobs.js — Aktivität (v3): Live-System-Metriken + Hintergrund-Jobs mit Log.
|
||||
// Exportiert track(id), damit andere Panels einen Job auto-aufklappen.
|
||||
|
||||
import { $, esc, fmtBytes } from "../core/ui.js";
|
||||
import { api } from "../core/api.js";
|
||||
import { $, esc, fmtBytes, toast } from "../core/ui.js";
|
||||
|
||||
const tracked = new Set();
|
||||
let JOBS = [];
|
||||
@@ -12,8 +13,21 @@ export function track(id) { tracked.add(id); renderJobs(); }
|
||||
const hist = { cpu: [], ram: [], gpu: [] };
|
||||
const MAX_HIST = 60;
|
||||
|
||||
function statusBadge(s) { return s === "done" ? '<span class="badge b-run">fertig</span>' : s === "failed" ? '<span class="badge b-err">fehler</span>' : '<span class="badge b-load">läuft…</span>'; }
|
||||
function dotClass(s) { return s === "done" ? "on" : s === "failed" ? "" : "load"; }
|
||||
function statusBadge(s) { return s === "done" ? '<span class="badge b-run">fertig</span>' : s === "failed" ? '<span class="badge b-err">fehler</span>' : s === "canceled" ? '<span class="badge">abgebrochen</span>' : '<span class="badge b-load">läuft…</span>'; }
|
||||
function dotClass(s) { return s === "done" ? "on" : (s === "failed" || s === "canceled") ? "" : "load"; }
|
||||
|
||||
// Fortschritt in % aus der letzten Log-Zeile ziehen (tqdm schreibt z.B. " 45%|…").
|
||||
function jobPct(j) {
|
||||
if (j.state !== "running") return null;
|
||||
const last = (j.log || [])[j.log.length - 1] || "";
|
||||
const m = last.match(/(\d+(?:\.\d+)?)\s*%/);
|
||||
return m ? Math.min(100, parseFloat(m[1])) : null;
|
||||
}
|
||||
|
||||
async function cancelJob(id) {
|
||||
try { await api(`/api/jobs/${id}/cancel`, { method: "POST" }); toast("Abbruch angefordert…"); renderJobs(); }
|
||||
catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
function tile(label, value, sub) {
|
||||
return `<div class="tile"><div class="t-l">${label}</div><div class="t-v">${value}</div><div class="t-s">${sub}</div></div>`;
|
||||
@@ -76,6 +90,8 @@ function mount() {
|
||||
<div id="jobs-empty" class="empty-c"><div class="e-t">Gerade nichts los.</div><div class="e-s">Alles ruhig — keine laufenden Aufgaben.</div></div>`;
|
||||
|
||||
$("#v-activity").addEventListener("click", e => {
|
||||
const cancel = e.target.closest("[data-cancel]");
|
||||
if (cancel) { e.stopPropagation(); cancelJob(cancel.getAttribute("data-cancel")); return; }
|
||||
const h = e.target.closest(".job-h"); if (!h) return;
|
||||
const id = h.getAttribute("data-id");
|
||||
tracked.has(id) ? tracked.delete(id) : tracked.add(id);
|
||||
@@ -91,8 +107,14 @@ function renderJobs() {
|
||||
$("#job-count").textContent = JOBS.length ? (failed ? failed + " Fehler" : JOBS.length + " gesamt") : "";
|
||||
c.innerHTML = JOBS.map(j => {
|
||||
const log = tracked.has(j.id) ? `<div class="log">${esc((j.log || []).join("\n"))}</div>` : "";
|
||||
const p = jobPct(j);
|
||||
const prog = p != null ? `<div class="bar" style="margin-top:8px"><i style="width:${p}%"></i></div>` : "";
|
||||
const pctTxt = p != null ? `<span class="mono-sm" style="margin-left:auto">${Math.round(p)} %</span>` : "";
|
||||
const cancelBtn = j.state === "running"
|
||||
? `<button class="ghost" data-cancel="${esc(j.id)}" style="${p != null ? "" : "margin-left:auto;"}margin-right:2px;padding:2px 9px;font-size:12px">Abbrechen</button>`
|
||||
: "";
|
||||
return `<div class="job"><div class="job-h" data-id="${esc(j.id)}">
|
||||
<span class="li-dot ${dotClass(j.state)}"></span><span class="mid">${esc(j.label)}</span>${statusBadge(j.state)}</div>${log}</div>`;
|
||||
<span class="li-dot ${dotClass(j.state)}"></span><span class="mid">${esc(j.label)}</span>${pctTxt}${cancelBtn}${statusBadge(j.state)}</div>${prog}${log}</div>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user