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

136 lines
5.8 KiB
JavaScript

// overview.js — Dashboard: Quick Actions, Modelle & Recent Jobs
import { $, esc, icon } 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>`;
}
function renderQuickActions() {
// 3 Kacheln (Cookbook, Server-Status, Aktivität/Guides)
$("#ov-quick").innerHTML = `
<div class="card" style="cursor:pointer; display:flex; flex-direction:column; gap:12px; transition:border-color 0.2s" onclick="document.querySelector('.nav-item[data-view=\\'cookbook\\']').click()" onmouseover="this.style.borderColor='var(--act)'" onmouseout="this.style.borderColor='var(--line)'">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0; font-size:16px;">Modell finden</h3>
<span style="color:var(--act)">${icon("book")}</span>
</div>
<p style="margin:0; font-size:13px; color:var(--mut);">Durchsuche HuggingFace nach neuen Modellen im Cookbook.</p>
</div>
<div class="card" style="cursor:pointer; display:flex; flex-direction:column; gap:12px; transition:border-color 0.2s" onclick="document.querySelector('.nav-item[data-view=\\'activity\\']').click()" onmouseover="this.style.borderColor='var(--act)'" onmouseout="this.style.borderColor='var(--line)'">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0; font-size:16px;">Live Metriken</h3>
<span style="color:var(--act)">${icon("pulse")}</span>
</div>
<p style="margin:0; font-size:13px; color:var(--mut);">${SYS ? `System läuft (RAM: ${SYS.ram.percent.toFixed(0)}%, CPU: ${SYS.cpu.percent.toFixed(0)}%)` : 'Lade Metriken...'}</p>
</div>
<div class="card" style="cursor:pointer; display:flex; flex-direction:column; gap:12px; transition:border-color 0.2s" onclick="document.querySelector('.nav-item[data-view=\\'server\\']').click()" onmouseover="this.style.borderColor='var(--act)'" onmouseout="this.style.borderColor='var(--line)'">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0; font-size:16px;">Wartung</h3>
<span style="color:var(--act)">${icon("server")}</span>
</div>
<p style="margin:0; font-size:13px; color:var(--mut);">Server neustarten, VRAM leeren oder Engine aktualisieren.</p>
</div>
`;
}
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" style="padding:10px 4px">
<div class="li-main">
<div class="li-id" style="font-size:12.5px">${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 };