Files
mission-control/static/js/panels/jobs.js
T

143 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// jobs.js (Aktivität) — System-Metriken & Hintergrund-Jobs
import { $, esc, icon } from "../core/ui.js";
const tracked = new Set();
let JOBS = [];
let SYS = null;
export function track(id) {
tracked.add(id);
renderJobs();
}
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ädt…</span>';
}
function dotClass(state) {
if (state === "done") return "on";
if (state === "failed") return "";
return "load";
}
function kpi(cls, title, ic, value, sub) {
return `<div class="kpi ${cls}">
<div class="k-h"><span class="k-t">${title}</span><span class="k-ic">${icon(ic)}</span></div>
<div class="k-v">${value}</div>
<div class="k-s">${sub}</div>
</div>`;
}
function kvRow(k, v, cls = "") {
return `<div class="kv-row"><span class="kv-k">${k}</span><span class="kv-v ${cls}">${v}</span></div>`;
}
// Chart-Daten (letzte 60 Sekunden, je 3s Polling = 20 Punkte)
const hist = { cpu: [], ram: [], gpu: [] };
const MAX_HIST = 20;
function renderSys() {
if (!SYS) return;
const sysV = `${SYS.cpu.percent.toFixed(0)}<small>% CPU</small>`;
const sysS = `${SYS.ram.percent.toFixed(0)}% RAM, ${SYS.gpu_temp ? SYS.gpu_temp.toFixed(0)+'°C' : SYS.cpu.temp ? SYS.cpu.temp.toFixed(0)+'°C' : ''}`;
// History aktualisieren
hist.cpu.push(SYS.cpu.percent);
hist.ram.push(SYS.ram.percent);
let gpuP = 0;
if (SYS.gpu && SYS.gpu.vram.total) {
gpuP = ((SYS.gpu.vram.used + SYS.gpu.gtt.used) / (SYS.gpu.vram.total + SYS.gpu.gtt.total)) * 100;
}
hist.gpu.push(gpuP);
if (hist.cpu.length > MAX_HIST) hist.cpu.shift();
if (hist.ram.length > MAX_HIST) hist.ram.shift();
if (hist.gpu.length > MAX_HIST) hist.gpu.shift();
// Mini-Sparklines generieren
const makeBars = (arr, color) => {
return '<div style="display:flex; align-items:flex-end; gap:2px; height:40px; margin-top:12px;">' +
arr.map(v => `<div style="width:4px; background:var(--${color}); opacity:0.6; height:${Math.max(2, v)}%; border-radius:2px;"></div>`).join("") +
'</div>';
};
$("#act-kpis").innerHTML =
kpi("blue", "CPU Last", "gauge", sysV, sysS) +
kpi("purple", "RAM", "monitor", `${SYS.ram.percent.toFixed(0)}<small>%</small>`, "Arbeitsspeicher") +
kpi("green", "GPU VRAM", "layers", `${gpuP.toFixed(0)}<small>%</small>`, "Grafikspeicher");
const gb = b => (b / 1024 / 1024 / 1024).toFixed(1);
const ramStr = `${gb(SYS.ram.used)} GB / ${gb(SYS.ram.total)} GB`;
const gpuStr = (SYS.gpu && SYS.gpu.vram.total) ? `${gb(SYS.gpu.vram.used + SYS.gpu.gtt.used)} GB / ${gb(SYS.gpu.vram.total + SYS.gpu.gtt.total)} GB` : "";
const diskStr = `${SYS.disk.percent.toFixed(0)}% belegt`;
$("#act-sys").innerHTML = `
<div class="card-h"><h3>System-Metriken (Bosgame)</h3></div>
<div class="kv" style="margin-bottom: 24px;">
${kvRow("Arbeitsspeicher (RAM)", ramStr)}
${kvRow("Grafikspeicher (VRAM+GTT)", gpuStr)}
${kvRow("Speicherplatz (Disk)", diskStr)}
${kvRow("Temperatur (GPU / CPU)", `${SYS.gpu_temp?.toFixed(1) || ''}°C / ${SYS.cpu.temp?.toFixed(1) || ''}°C`)}
</div>
<div class="grid grid-3">
<div><div class="meta">CPU Historie</div>${makeBars(hist.cpu, "act")}</div>
<div><div class="meta">RAM Historie</div>${makeBars(hist.ram, "purple")}</div>
<div><div class="meta">VRAM Historie</div>${makeBars(hist.gpu, "on")}</div>
</div>
`;
}
function mount() {
$("#v-activity").innerHTML = `
<div class="card-h"><h3>Hintergrund-Aktivitäten</h3><span class="meta" id="job-count"></span></div>
<div id="jobs"></div>
<div id="jobs-empty" class="empty-c">
<div class="e-t">Keine laufenden Jobs.</div>
<div class="e-s">Downloads, Updates &amp; Co. erscheinen hier mit Live-Log.</div>
</div>`;
// Klicks auf Job-Kopf -> auf/zuklappen
$("#v-activity").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);
renderJobs();
});
if (SYS) renderSys();
}
function renderJobs() {
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 || [];
renderJobs();
}
function onSystem(sys) {
SYS = sys;
const c = $("#act-sys");
if (c) renderSys();
}
export default { id: "jobs", mount, onJobs, onSystem };