185 lines
6.7 KiB
JavaScript
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 };
|