364939466f
Architektur auf Separation of Concerns umgestellt – ohne Build-Schritt,
ohne neues Framework, ohne DB (KISS bleibt). Endpoint-URLs unveraendert,
daher 1:1-kompatibel zum bisherigen Stand.
Backend (Top-Level-Helfer + ein Router je Bereich):
- app.py auf duennen Einstieg reduziert (FastAPI + include_router + static)
- config/auth/jobengine/llamaswap als getrennte Helfer-Module
- Endpoints in routers/{models,jobs,maintenance}.py
Frontend (native ES-Module statt Single-File):
- index.html = Huelle: Sidebar-Nav, Topbar, Alert-Banner, Hash-Routing
- css/{base,components}.css – Tokens + Komponenten
- js/core/{api,ui,nav}.js + js/panels/{overview,models,maintenance,jobs}.js + main.js
- Panel-Vertrag: { id, mount?(), onStatus?(s), onJobs?(jobs) }
- Optik an docs/mission-control-overview.png angelehnt (Hero, KPI-Kacheln,
Listen, Aktivitaets-Stream, getoente Karten)
Doku: CLAUDE.md + README auf die neue Struktur aktualisiert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.1 KiB
JavaScript
68 lines
2.1 KiB
JavaScript
// jobs.js — Aktivitaets-Stream ("Incident Stream"): Hintergrund-Jobs mit Live-Log.
|
|
// Exportiert track(id), damit andere Panels einen frisch gestarteten Job auto-aufklappen.
|
|
|
|
import { $, esc } from "../core/ui.js";
|
|
|
|
const tracked = new Set();
|
|
let JOBS = [];
|
|
|
|
export function track(id) {
|
|
tracked.add(id);
|
|
render();
|
|
}
|
|
|
|
function statusBadge(state) {
|
|
if (state === "done") return '<span class="badge b-run">fertig</span>';
|
|
if (state === "failed") return '<span class="badge b-err">fehler</span>';
|
|
return '<span class="badge b-load">läuft…</span>';
|
|
}
|
|
function dotClass(state) {
|
|
if (state === "done") return "on";
|
|
if (state === "failed") return "";
|
|
return "load";
|
|
}
|
|
|
|
function mount() {
|
|
$("#ov-activity").innerHTML = `
|
|
<div class="card-h"><h3>Aktivität</h3><span class="meta" id="job-count"></span></div>
|
|
<div id="jobs"></div>
|
|
<div id="jobs-empty" class="empty-c">
|
|
<div class="e-t">Noch nichts losgemacht.</div>
|
|
<div class="e-s">Downloads, Updates & Co. erscheinen hier mit Live-Log.</div>
|
|
</div>`;
|
|
|
|
// Klicks auf Job-Kopf -> auf/zuklappen (Event-Delegation)
|
|
$("#jobs").addEventListener("click", e => {
|
|
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);
|
|
render();
|
|
});
|
|
}
|
|
|
|
function render() {
|
|
const c = $("#jobs");
|
|
if (!c) return;
|
|
$("#jobs-empty").style.display = JOBS.length ? "none" : "flex";
|
|
const failed = JOBS.filter(j => j.state === "failed").length;
|
|
$("#job-count").textContent = JOBS.length ? (failed ? failed + " Fehler" : JOBS.length + " gesamt") : "";
|
|
|
|
c.innerHTML = JOBS.map(j => {
|
|
const open = tracked.has(j.id);
|
|
const log = open ? `<div class="log">${esc((j.log || []).join("\n"))}</div>` : "";
|
|
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>`;
|
|
}).join("");
|
|
}
|
|
|
|
function onJobs(jobs) {
|
|
JOBS = jobs || [];
|
|
render();
|
|
}
|
|
|
|
export default { id: "jobs", mount, onJobs };
|