This repository has been archived on 2026-07-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mission-control/static/js/main.js
T
Hitonabi 26dac1a10b v4 Schritt 4: News-Board
- routers/news.py: aggregiert kuratierte RSS/Atom-Feeds (HuggingFace, llama.cpp, Ollama,
  r/LocalLLaMA, Simon Willison) via httpx + stdlib xml.etree, In-memory-Cache 30 min,
  Hardware-relevante Beitraege (rocm/gfx1151/...) markiert. Degradiert pro Feed sauber.
- news.js: Beitragsliste (Quelle, Titel-Link, relatives Datum) + Aktualisieren.
- Nav 'News' + View + main.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:44:46 +02:00

141 lines
4.2 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 connect from "./panels/connect.js";
import news from "./panels/news.js";
import guides from "./panels/guides.js";
const panels = [overview, models, server, jobs, cookbook, connect, news, 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);
}
setSecChip(s);
for (const p of panels) p.onStatus?.(s);
}
// Security-Chip: ehrlich anzeigen, ob ein Zugangs-Token aktiv ist.
function setSecChip(s) {
const chip = $("#sec-chip"), tx = $("#sec-chip-tx");
if (!chip || !tx) return;
const secured = !!s?.secured;
chip.classList.toggle("ok", secured);
tx.textContent = secured ? "Geschützt · Heimnetz" : "Nur im Heimnetz (offen)";
}
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);