Grotti 2.2.0: Auth, API-Tokens, MCP und Doku-Trennung.

This commit is contained in:
Dominic Gundel committed 2026-10-15 10:53:12 +02:00
1 parent b20f8a7779
commit 5a3716b26a
65 files changed
+2212 -198

No files matched your search

+24 -4
View File
@@ -1,4 +1,5 @@
const TOKEN = document.querySelector('meta[name="token"]')?.content || "";
const TOKEN = ""; // Legacy: Auth über Session-Cookie oder Bearer von außen
const CSRF = document.querySelector('meta[name="csrf"]')?.content || "";
const STATE_LABELS = {
printing: "druckt", paused: "pausiert", complete: "fertig", standby: "bereit",
@@ -17,17 +18,29 @@ function escapeHtml(value) {
async function api(path, options = {}) {
const headers = new Headers(options.headers || {});
if (TOKEN) headers.set("Authorization", "Bearer " + TOKEN);
const method = (options.method || "GET").toUpperCase();
if (CSRF && method !== "GET" && method !== "HEAD") {
headers.set("X-CSRF-Token", CSRF);
}
let body = options.body;
if (options.json !== undefined) {
headers.set("Content-Type", "application/json");
body = JSON.stringify(options.json);
}
const response = await fetch("/api/v1" + path, {
method: options.method || "GET",
method,
headers,
body,
cache: "no-store",
credentials: "same-origin",
});
if (response.status === 401) {
const here = location.pathname + location.search;
if (!here.startsWith("/login") && !here.startsWith("/setup")) {
location.href = "/login?next=" + encodeURIComponent(here);
}
throw new Error("Anmeldung nötig.");
}
if (!response.ok) {
let message = response.statusText;
try {
@@ -45,8 +58,8 @@ async function api(path, options = {}) {
}
function withToken(url) {
if (!url || !TOKEN) return url;
return url + (url.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(TOKEN);
// Session-Cookie reicht für same-origin Kameras; kein Token in der URL.
return url;
}
function euro(value) {
@@ -103,6 +116,13 @@ function chipHtml(state, label, small) {
return `<span class="chip ${escapeHtml(state || "")}${small ? " sm" : ""}">${escapeHtml(text)}</span>`;
}
function shownState(printer) {
const state = (printer && printer.state) || "offline";
const hot = ((printer && printer.nozzle_target) || 0) > 40 || ((printer && printer.bed_target) || 0) > 30;
if ((state === "standby" || state === "complete") && hot) return "heating";
return state;
}
function stepsHtml(labels, index, compact) {
if (!labels || !labels.length) return "";
const parts = labels.map((label, i) => {