Grotti 2.2.0: Auth, API-Tokens, MCP und Doku-Trennung.
This commit is contained in:
1 parent
b20f8a7779
commit
5a3716b26a
65 files changed
+2212
-198
No files matched your search
+33
-20
@@ -49,7 +49,7 @@
|
||||
<div class="desk-facts" data-role="facts"></div>
|
||||
<div data-role="flow"></div>
|
||||
<div data-role="strips"></div>
|
||||
<details class="desk-fold desk-steer-fold" data-fold="steer" open>
|
||||
<details class="desk-fold desk-steer-fold" data-fold="steer">
|
||||
<summary class="desk-fold-sum"><svg class="ico caret"><use href="#i-down"/></svg><span class="grow">Steuerung</span></summary>
|
||||
<div data-role="steer"></div>
|
||||
</details>
|
||||
@@ -61,7 +61,7 @@
|
||||
</aside>
|
||||
</div>
|
||||
<div class="desk-tools">
|
||||
<details class="desk-fold desk-tool" data-fold="path" open>
|
||||
<details class="desk-fold desk-tool" data-fold="path">
|
||||
<summary class="desk-fold-sum"><svg class="ico caret"><use href="#i-down"/></svg><span class="grow">3D-Pfad</span><span class="tiny" data-role="path-caption"></span></summary>
|
||||
<div class="desk-fold-body">
|
||||
<p class="tiny">Ziehen dreht · Rad zoomt · Rechtsklick/Shift schwenkt · Doppelklick setzt zurück</p>
|
||||
@@ -75,7 +75,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details class="desk-fold desk-tool desk-tool-mesh" data-fold="mesh" open>
|
||||
<details class="desk-fold desk-tool desk-tool-mesh" data-fold="mesh">
|
||||
<summary class="desk-fold-sum"><svg class="ico caret"><use href="#i-down"/></svg><span class="grow">Bettnetz</span></summary>
|
||||
<div class="desk-fold-body" data-role="mesh">
|
||||
<p class="desk-empty"><svg class="ico"><use href="#i-grid"/></svg><span>Noch kein Bettnetz</span></p>
|
||||
@@ -88,16 +88,15 @@
|
||||
<script src="{{ url_for('static', filename='three.min.js') }}?v=1"></script>
|
||||
<script src="{{ url_for('static', filename='gcode-viewer.js') }}?v=8"></script>
|
||||
<script>
|
||||
const labels = {
|
||||
printing: "druckt", paused: "pausiert", complete: "fertig", standby: "bereit",
|
||||
heating: "heizt", error: "Fehler", cancelled: "abgebrochen", offline: "offline"
|
||||
};
|
||||
const labels = STATE_LABELS;
|
||||
const rank = { printing: 0, paused: 1, error: 2, heating: 3, complete: 4, cancelled: 5, standby: 6, offline: 7 };
|
||||
const DESK_STATES = ["printing", "paused", "complete", "standby", "heating", "error", "cancelled", "offline"];
|
||||
let deskHost = "";
|
||||
let hideOffline = false;
|
||||
let libraryFiles = [];
|
||||
let wallSpools = [];
|
||||
let deskViewer = null;
|
||||
let deskCamFails = 0;
|
||||
let deskViewerFileId = null;
|
||||
let deskExcludeSelected = "";
|
||||
const wallPullTried = new Set();
|
||||
@@ -123,9 +122,9 @@ function syncDeskCamCollapsed() {
|
||||
}
|
||||
|
||||
function restoreDeskFolds() {
|
||||
let closed = [];
|
||||
try { closed = JSON.parse(localStorage.getItem(DESK_FOLD_KEY) || "[]"); } catch (error) { closed = []; }
|
||||
if (!Array.isArray(closed)) closed = [];
|
||||
let closed;
|
||||
try { closed = JSON.parse(localStorage.getItem(DESK_FOLD_KEY) || "null"); } catch (error) { closed = null; }
|
||||
if (!Array.isArray(closed)) closed = ["steer", "path", "mesh"];
|
||||
deskEl.querySelectorAll("details[data-fold]").forEach((box) => {
|
||||
box.open = !closed.includes(box.dataset.fold);
|
||||
box.addEventListener("toggle", () => {
|
||||
@@ -193,13 +192,6 @@ function heatMapHtml(printer) {
|
||||
return `<div class="mesh desk-mesh"><p>Aktuelles Netz, oben ist hinten. ${low.toFixed(2)} bis ${high.toFixed(2)} mm</p><div class="gridmesh" style="grid-template-columns:repeat(${rows[0].length},1fr)">${cells}</div><p class="tiny">${bits.join(" · ")}</p></div>`;
|
||||
}
|
||||
|
||||
function shownState(printer) {
|
||||
const state = printer.state || "offline";
|
||||
const hot = (printer.nozzle_target || 0) > 40 || (printer.bed_target || 0) > 30;
|
||||
if ((state === "standby" || state === "complete") && hot) return "heating";
|
||||
return state;
|
||||
}
|
||||
|
||||
function jobName(filename) {
|
||||
return (filename || "").replace(/\.gcode$/i, "").replace(/^(\d+-)+/, "");
|
||||
}
|
||||
@@ -575,13 +567,14 @@ function refreshDeskCamera(printer) {
|
||||
const img = deskEl.querySelector('[data-role="camera"]');
|
||||
const empty = deskEl.querySelector('[data-role="cam-empty"]');
|
||||
if (!img || !empty) return;
|
||||
if (!printer || !printer.camera) {
|
||||
if (!printer || !printer.camera || printer.has_camera === false) {
|
||||
img.removeAttribute("src");
|
||||
img.hidden = true;
|
||||
empty.hidden = false;
|
||||
empty.innerHTML = icon("camera") + "<span>Keine Kamera</span>";
|
||||
setDeskCameraMode(false, "keine");
|
||||
img.dataset.srcKey = "";
|
||||
deskCamFails = 0;
|
||||
return;
|
||||
}
|
||||
// Layout (no-cam) nicht bei jedem Frame-Fehler umwerfen — sonst springt die
|
||||
@@ -593,6 +586,7 @@ function refreshDeskCamera(printer) {
|
||||
const probe = new Image();
|
||||
probe.onload = () => {
|
||||
if (img.dataset.srcKey !== next) return;
|
||||
deskCamFails = 0;
|
||||
img.src = next;
|
||||
img.hidden = false;
|
||||
empty.hidden = true;
|
||||
@@ -600,7 +594,16 @@ function refreshDeskCamera(printer) {
|
||||
};
|
||||
probe.onerror = () => {
|
||||
if (img.dataset.srcKey !== next) return;
|
||||
// Altes Bild behalten; nur wenn noch nie was kam, Platzhalter zeigen
|
||||
deskCamFails += 1;
|
||||
if (deskCamFails >= 2) {
|
||||
img.removeAttribute("src");
|
||||
img.hidden = true;
|
||||
empty.hidden = false;
|
||||
empty.innerHTML = icon("camera") + "<span>Keine Kamera</span>";
|
||||
setDeskCameraMode(false, "offline");
|
||||
img.dataset.srcKey = "";
|
||||
return;
|
||||
}
|
||||
if (!img.src || img.hidden) {
|
||||
empty.hidden = false;
|
||||
empty.innerHTML = icon("camera") + "<span>Kamera nicht erreichbar</span>";
|
||||
@@ -652,7 +655,9 @@ function patchDesk(printer) {
|
||||
deskEl.dataset.host = printer.host;
|
||||
deskEl.dataset.filename = printer.filename || "";
|
||||
deskEl.dataset.progress = String(printer.progress || 0);
|
||||
deskEl.className = "desk " + state;
|
||||
deskEl.classList.remove(...DESK_STATES);
|
||||
deskEl.classList.add(state);
|
||||
syncDeskCamCollapsed();
|
||||
|
||||
const role = (name) => deskEl.querySelector(`[data-role=${name}]`);
|
||||
const nameEl = role("name");
|
||||
@@ -1006,6 +1011,14 @@ async function refresh() {
|
||||
document.getElementById("summary").innerHTML = summaryText(all) || "keine Drucker";
|
||||
document.getElementById("when").textContent = new Date().toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
|
||||
|
||||
if (!all.length) {
|
||||
lastFleetKey = "";
|
||||
cardNodes.clear();
|
||||
cardsEl.innerHTML = `<div class="empty-state">${icon("printer", "lg")}<p>Noch keine Drucker.</p><p class="tiny">Unter Einstellungen anlegen.</p><a href="/einstellungen">Zu den Einstellungen</a></div>`;
|
||||
if (deskHost) closeDesk();
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = ensureFleetShell(printers);
|
||||
const seen = new Set();
|
||||
for (const printer of printers) {
|
||||
|
||||
Reference in new issue
Block a user