Files
mission-control/static/js/main.js
T
2026-06-20 23:30:58 +02:00

129 lines
3.7 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.
// main.js — App-Boot: Panels mounten, Nav starten, Topbar/Alert pflegen, Polling fahren.
// Panel-Vertrag: { id, mount?(), onStatus?(s), onJobs?(jobs) }.
import { api, getToken, setToken } from "./core/api.js";
import { $ } from "./core/ui.js";
import { initNav } from "./core/nav.js";
import overview from "./panels/overview.js";
import models from "./panels/models.js";
import server from "./panels/server.js";
import jobs from "./panels/jobs.js";
import cookbook from "./panels/cookbook.js";
import guides from "./panels/guides.js";
const panels = [overview, models, server, jobs, cookbook, guides];
let lastJobs = [];
let lastSystem = null;
// ---- Topbar / Alert aus dem Status ableiten ----
function applyStatus(s) {
const dot = $("#swdot"), label = $("#swlabel"), alert = $("#alert");
if (!s) {
dot.className = "dot off";
label.textContent = "Backend nicht erreichbar";
$("#top-active-text").textContent = "Backend offline";
showAlert("Backend nicht erreichbar läuft uvicorn?", false);
} else {
const host = s.swap_url.replace(/^https?:\/\//, "");
dot.className = "dot " + (s.swap_ok ? "on" : "off");
label.textContent = (s.swap_ok ? "LLM-Engine: Online " : "LLM-Engine: Offline ") + host;
const active = (s.models || []).find(m => m.state === "running");
if (active) {
$("#top-active-text").innerHTML = `Geladen: <b style="color:var(--teal)">${active.name}</b>`;
} else {
$("#top-active-text").innerHTML = "Kein Modell im VRAM";
}
if (s.swap_ok) hideAlert();
else showAlert(`LLM-Engine nicht erreichbar unter <b>${host}</b> läuft der llama-swap Dienst?`, true);
}
for (const p of panels) p.onStatus?.(s);
}
function applyJobs(jobs) {
lastJobs = jobs || [];
for (const p of panels) p.onJobs?.(lastJobs);
}
function applySystem(sys) {
lastSystem = sys;
for (const p of panels) p.onSystem?.(sys);
}
function showAlert(html, warn) {
const a = $("#alert");
a.className = "alert" + (warn ? " warn" : "");
a.innerHTML = `<span class="a-dot"></span><span>${html}</span>`;
a.style.display = "flex";
}
function hideAlert() { $("#alert").style.display = "none"; }
// ---- Polling ----
async function pollStatus() {
try { applyStatus(await api("/api/status")); }
catch { applyStatus(null); }
}
async function pollJobs() {
try { applyJobs(await api("/api/jobs")); }
catch { /* still */ }
}
function connectSystemStream() {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/api/system/stream`;
const ws = new WebSocket(wsUrl);
ws.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
applySystem(data);
} catch (err) {}
};
ws.onclose = () => {
// Bei Verbindungsabbruch nach 3 Sekunden erneut versuchen
setTimeout(connectSystemStream, 3000);
};
ws.onerror = () => ws.close();
}
// ---- Boot ----
function bootToken() {
const i = $("#token");
if (i) {
i.value = getToken();
i.addEventListener("change", e => { setToken(e.target.value); pollStatus(); });
}
const sbtn = $("#nav-settings");
const smod = $("#settings-modal");
const scls = $("#sm-close");
if (sbtn && smod && scls) {
sbtn.addEventListener("click", () => smod.style.display = "flex");
scls.addEventListener("click", () => smod.style.display = "none");
}
}
function tickClock() {
const c = $("#clock");
if (c) c.textContent = new Date().toTimeString().slice(0, 5);
}
for (const p of panels) p.mount?.();
initNav("overview");
bootToken();
tickClock();
document.addEventListener("mc:refresh", pollStatus);
pollStatus();
pollJobs();
connectSystemStream();
setInterval(tickClock, 1000);
setInterval(pollStatus, 3000);
setInterval(pollJobs, 1500);