136 lines
5.8 KiB
JavaScript
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 };
|