Grotti 2.10.0: Orca-Senden, Slice-Ordner, ED-Strom, Hygiene.

Slice-Watch mit Ordnern/Filtern, Orca-Nachbearbeitung, Cockpit-Verlauf
mit ED, Zeitzone im Browser. Tasten-Grenze und README-API angeglichen,
toter Code entfernt, Requirements gepinnt.
This commit is contained in:
Dominic Gundel committed 2026-10-18 00:41:15 +02:00
1 parent efd0801cd1
commit 9e22cebb81
48 files changed
+2259 -422

No files matched your search

+72 -1
View File
@@ -61,6 +61,12 @@
</aside>
</div>
<div class="desk-tools">
<details class="desk-fold desk-tool" data-fold="verlauf" open>
<summary class="desk-fold-sum"><svg class="ico caret"><use href="#i-down"/></svg><span class="grow">Verlauf</span><span class="tiny" data-role="verlauf-caption"></span></summary>
<div class="desk-fold-body desk-verlauf" data-role="verlauf">
<p class="tiny">Lade Verlauf …</p>
</div>
</details>
<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">
@@ -100,6 +106,9 @@ let deskCamFails = 0;
let deskViewerFileId = null;
let deskSyncGen = 0;
let deskExcludeSelected = "";
let deskVerlaufAt = 0;
let deskVerlaufHtml = "";
let deskVerlaufHost = "";
const wallPullTried = new Set();
const toolpathCache = new Map();
@@ -909,6 +918,61 @@ function patchDesk(printer) {
// Kamera nur über Timer aktualisieren — sonst doppelter Reload + Layout-Ruckler
syncDeskViewer(printer).catch(() => {});
refreshDeskVerlauf(false);
}
async function refreshDeskVerlauf(force) {
if (!deskHost || deskEl.hidden) return;
const now = Date.now();
if (!force && deskVerlaufHost === deskHost && now - deskVerlaufAt < 20000) return;
const host = deskHost;
deskVerlaufAt = now;
deskVerlaufHost = host;
const box = deskEl.querySelector('[data-role="verlauf"]');
const caption = deskEl.querySelector('[data-role="verlauf-caption"]');
if (!box) return;
try {
const data = await api("/laeufe/aktuell?host=" + encodeURIComponent(host));
if (deskHost !== host) return;
const run = data.run;
if (!run) {
const empty = '<p class="desk-empty"><svg class="ico"><use href="#i-activity"/></svg><span>Noch kein Verlauf</span></p>';
if (deskVerlaufHtml !== empty) {
deskVerlaufHtml = empty;
box.innerHTML = empty;
}
if (caption) caption.textContent = "";
return;
}
const active = ["in_progress", "printing", "paused"].includes(run.state);
const energy = data.energy || {};
const kwh = energy.kwh != null
? (Number(energy.kwh) || 0).toLocaleString("de-DE", { maximumFractionDigits: 2 }) + " kWh"
+ (energy.source === "ed" ? " ED" : "")
: "";
const bits = [
active ? "läuft" : "letzter Druck",
formatDateRange(run.started_at, run.ended_at),
kwh,
].filter(Boolean);
if (caption) caption.textContent = bits.join(" · ");
const head = `<div class="desk-verlauf-head">
<span class="tiny grow">${escapeHtml(jobLabel(run.filename) || "Druck")}${run.chargen_nr ? " · " + escapeHtml(run.chargen_nr) : ""}</span>
<a class="ghost tiny-btn" href="/protokoll/${run.id}">Bericht →</a>
</div>`;
const html = head + tempChartHtml(data.samples || []);
if (deskVerlaufHtml !== html) {
deskVerlaufHtml = html;
box.innerHTML = html;
}
} catch (error) {
if (deskHost !== host) return;
const fail = '<p class="tiny msg">' + escapeHtml(error.message || "Verlauf nicht geladen") + "</p>";
if (deskVerlaufHtml !== fail) {
deskVerlaufHtml = fail;
box.innerHTML = fail;
}
}
}
async function syncDeskViewer(printer) {
@@ -1068,10 +1132,15 @@ function openDesk(host, opts) {
const prev = deskHost;
deskHost = host;
setDeskVisible(true);
if (prev !== host) destroyDeskViewer();
if (prev !== host) {
destroyDeskViewer();
deskVerlaufHtml = "";
deskVerlaufAt = 0;
}
const steer = deskEl.querySelector("[data-role=steer]");
if (!same || !steer.querySelector(".steer")) setHtml(steer, steerPanelHtml(printer));
patchDesk(printer);
refreshDeskVerlauf(true);
startCameraTimer();
if (!same) {
const back = document.getElementById("desk-back");
@@ -1087,6 +1156,8 @@ function openDesk(host, opts) {
function closeDesk(opts) {
if (!deskHost && deskEl.hidden) return;
deskHost = "";
deskVerlaufHtml = "";
deskVerlaufHost = "";
stopCameraTimer();
destroyDeskViewer();
setDeskVisible(false);