Files
mission-control/static/js/panels/overview.js
T
2026-06-20 23:44:05 +02:00

185 lines
6.7 KiB
JavaScript

// overview.js — Dashboard: Quick Actions, Modelle & Recent Jobs
import { api } from "../core/api.js";
import { $, esc, icon, toast } from "../core/ui.js";
let S = null; // letzter Status
let J = []; // letzte Job-Liste
let SYS = null;
const RUNNING = new Set(["running", "ready", "loading", "starting"]);
function counts() {
const models = S?.models || [];
return {
total: models.length,
running: models.filter(m => RUNNING.has(m.state)).length,
};
}
function renderHero() {
$("#hero").innerHTML = `<div class="hero">
<div>
<div class="eyebrow">Dashboard</div>
<h1>Mission Control</h1>
<p>Steuerzentrale für deinen lokalen llama-swap-Stack. Hier verwaltest du Modelle, Downloads und Server-Wartung.</p>
</div>
</div>`;
}
async function triggerAction(action) {
if (action === "restart_llama") {
toast("Neustart ausgelöst...");
try {
await api("/api/service/llama-swap/restart", { method: "POST" });
toast("llama-swap wird neugestartet.");
} catch(e) {
toast("Fehler: " + e.message, true);
}
} else if (action === "update_mc") {
toast("Update gestartet! Siehe Aktivitäten.");
try {
await api("/api/update", { method: "POST" });
document.querySelector('.nav-item[data-view="activity"]').click();
} catch(e) {
toast("Fehler: " + e.message, true);
}
}
}
// Global hook für onclick
window.triggerAction = triggerAction;
function renderQuickActions() {
let actionsHtml = "";
if (SYS) {
const ram_percent = SYS.ram.percent || 0;
// Wenn RAM über 90% ist, zeige Warnung.
// Wir nehmen 90 für Produktion, aber für den Test könnte es angepasst werden.
if (ram_percent >= 90) {
actionsHtml += `
<div class="card" style="background:var(--red-dim); border:1px solid var(--red); grid-column:1/-1;">
<div class="flex justify-between items-center">
<div>
<h3 style="color:var(--red); margin:0;">⚠️ Arbeitsspeicher kritisch (${ram_percent.toFixed(0)}%)</h3>
<p style="color:var(--red); margin-top:4px;">Der RAM/VRAM ist fast voll. Dies kann zu Systeminstabilität führen.</p>
</div>
<button class="primary warn" onclick="window.triggerAction('restart_llama')">VRAM leeren (Neustart)</button>
</div>
</div>
`;
}
// Simulierter Update-Check (Idealerweise vom Backend, hier als permanenter Button wenn man manuell checken will,
// oder wir blenden ihn ein wenn ein lokales flag gesetzt ist. Wir zeigen ihn hier als Feature-Highlight)
// Da wir aktuell keinen echten Git-Check im Backend haben, zeigen wir einen "Update Prüfen" Button in den QuickActions.
}
// 3 Standard Kacheln (Cookbook, Server-Status, Aktivität/Guides)
$("#ov-quick").innerHTML = actionsHtml + `
<button class="card-btn" onclick="document.querySelector('.nav-item[data-view=\\'cookbook\\']').click()">
<div class="flex justify-between items-center">
<h3>Modell finden</h3>
<span class="text-act">${icon("book")}</span>
</div>
<p>Durchsuche HuggingFace nach neuen Modellen im Cookbook.</p>
</button>
<button class="card-btn" onclick="window.triggerAction('update_mc')">
<div class="flex justify-between items-center">
<h3>Container Updates</h3>
<span class="text-act">${icon("download")}</span>
</div>
<p>Prüfe auf Updates für Mission-Control und Llama.cpp.</p>
</button>
<button class="card-btn" onclick="document.querySelector('.nav-item[data-view=\\'server\\']').click()">
<div class="flex justify-between items-center">
<h3>Wartung</h3>
<span class="text-act">${icon("server")}</span>
</div>
<p>Server neustarten, VRAM leeren oder OS aktualisieren.</p>
</button>
`;
}
function modelRow(m) {
const on = RUNNING.has(m.state);
const dot = m.state === "loading" || m.state === "starting" ? "load" : on ? "on" : "";
const state = on ? (m.state === "loading" ? "lädt…" : "geladen") : "bereit";
let caps = "";
if (m.meta && m.meta.caps) {
caps = m.meta.caps.map(c => {
if (c === "Code") return `<span title="Code" style="color:var(--blue);font-size:0.9em;margin-left:6px">{ }</span>`;
if (c === "Bild") return `<span title="Vision" style="color:var(--purple);font-size:0.9em;margin-left:6px">👁</span>`;
return "";
}).join("");
}
const filename = m.meta?.filename ? `<div class="li-sub" style="font-family:var(--mono); color:var(--mut);">${esc(m.meta.filename)}</div>` : '';
return `<div class="li">
<span class="li-dot ${dot}"></span>
<div class="li-main">
<div class="li-id" style="font-weight:500">${esc(m.name)}${caps}</div>
${filename}
</div>
<div class="li-right">
<div class="li-time">${state}</div>
</div>
</div>`;
}
function renderModels() {
const models = S?.models || [];
$("#ov-models").innerHTML = `
<div class="card-h"><h3>Aktuelle Modelle im Stack</h3><span class="meta">${models.length || ""}</span></div>
${models.length
? `<div class="list">${models.map(modelRow).join("")}</div>`
: `<div class="empty-c"><div class="e-t">Keine Modelle konfiguriert</div>
<div class="e-s">Hol dir unter „Cookbook“ eins von HuggingFace.</div></div>`}`;
}
function renderRecentJobs() {
const latest = J.slice(0, 4);
const statusBadge = (s) => {
if (s === "done") return '<span class="badge b-run" style="font-size:10px">fertig</span>';
if (s === "failed") return '<span class="badge b-err" style="font-size:10px">fehler</span>';
return '<span class="badge b-load" style="font-size:10px">lädt…</span>';
};
$("#ov-recent-jobs").innerHTML = `
<div class="card-h"><h3>Letzte Aktivitäten</h3><span class="meta" style="cursor:pointer" onclick="document.querySelector('.nav-item[data-view=\\'activity\\']').click()">Alle ansehen →</span></div>
${latest.length
? `<div class="list">
${latest.map(j => `
<div class="li">
<div class="li-main">
<div class="li-id text-sm">${esc(j.label)}</div>
</div>
<div class="li-right">${statusBadge(j.state)}</div>
</div>
`).join("")}
</div>`
: `<div class="empty-c"><div class="e-t">Keine Aktivitäten</div><div class="e-s">Alles läuft ruhig.</div></div>`
}
`;
}
function renderAll() {
renderHero();
renderQuickActions();
renderModels();
renderRecentJobs();
}
function mount() { renderAll(); }
function onStatus(s) { S = s; renderModels(); }
function onJobs(jobs) { J = jobs || []; renderRecentJobs(); }
function onSystem(sys) { SYS = sys; renderQuickActions(); }
export default { id: "overview", mount, onStatus, onJobs, onSystem };