Grotti 2.5.1: Druckbericht, Statistik, Spulenakte und Protokoll-Fixes.

This commit is contained in:
Dominic Gundel committed 2026-10-17 15:32:51 +02:00
1 parent b277ca48aa
commit efd0801cd1
30 files changed
+3393 -178

No files matched your search

+41 -6
View File
@@ -9,7 +9,7 @@
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='brand/favicon-32.png') }}">
<link rel="icon" type="image/png" sizes="512x512" href="{{ url_for('static', filename='brand/favicon.png') }}">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='brand/apple-touch-icon.png') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='grotti.css') }}?v=41">
<link rel="stylesheet" href="{{ url_for('static', filename='grotti.css') }}?v=45">
<script>
(function () {
try {
@@ -100,7 +100,8 @@
<a href="/kameras" class="{{ 'on' if page == 'kameras' else '' }}" title="Kameras"><svg class="ico"><use href="#i-camera"/></svg><span>Kameras</span></a>
<a href="/aktivitaet" class="{{ 'on' if page == 'aktivitaet' else '' }} nav-desk" title="Aktivität"><svg class="ico"><use href="#i-activity"/></svg><span>Aktivität</span></a>
<a href="/warteschlange" class="{{ 'on' if page == 'warteschlange' else '' }}" title="Warteschlange"><svg class="ico"><use href="#i-queue"/></svg><span>Warteschlange</span></a>
<a href="/protokoll" class="{{ 'on' if page == 'protokoll' else '' }} nav-desk" title="Protokoll"><svg class="ico"><use href="#i-list"/></svg><span>Protokoll</span></a>
<a href="/protokoll" class="{{ 'on' if page == 'protokoll' or page == 'bericht' else '' }} nav-desk" title="Protokoll"><svg class="ico"><use href="#i-list"/></svg><span>Protokoll</span></a>
<a href="/statistik" class="{{ 'on' if page == 'statistik' else '' }} nav-desk" title="Statistik"><svg class="ico"><use href="#i-activity"/></svg><span>Statistik</span></a>
<a href="/material" class="{{ 'on' if page == 'material' else '' }} nav-desk" title="Material"><svg class="ico"><use href="#i-spool"/></svg><span>Material</span></a>
<a href="/projekte" class="{{ 'on' if page == 'projekte' else '' }} nav-desk" title="Projekte"><svg class="ico"><use href="#i-folder"/></svg><span>Projekte</span></a>
</nav>
@@ -289,6 +290,31 @@ function directItemHtml(p) {
}
let queueCache = [];
const lastLiveFilename = Object.create(null);
function setListHtml(el, html) {
if (!el) return;
if (el._html === html) return;
el._html = html;
el.innerHTML = html;
}
function stickyLiveFilename(host, filename, state) {
const next = filename || "";
if (next) {
lastLiveFilename[host] = next;
return next;
}
const prev = lastLiveFilename[host] || "";
if (prev && ["printing", "paused", "heating", "complete"].includes(String(state || "").toLowerCase())) {
return prev;
}
if (!["printing", "paused", "heating"].includes(String(state || "").toLowerCase())) {
delete lastLiveFilename[host];
}
return prev && ["complete"].includes(String(state || "").toLowerCase()) ? prev : "";
}
async function refreshPrintQueue() {
let jobs;
let active = [];
@@ -298,7 +324,10 @@ async function refreshPrintQueue() {
queueCache = jobs || [];
const running = queueCache.filter((job) => job.status === "running");
const queuedHosts = new Set(running.map((job) => job.host));
const direct = (active || []).filter((p) => !queuedHosts.has(p.host));
const direct = (active || []).filter((p) => !queuedHosts.has(p.host)).map((p) => ({
...p,
filename: stickyLiveFilename(p.host, p.filename, p.state),
}));
const waiting = queueCache.filter((job) => job.status === "waiting");
const errors = queueCache
.filter((job) => job.status === "error")
@@ -308,15 +337,21 @@ async function refreshPrintQueue() {
const runBox = document.getElementById("run-box");
runBox.hidden = !(running.length || direct.length);
document.getElementById("run-count").textContent = running.length + direct.length;
document.getElementById("run-list").innerHTML = running.map(runItemHtml).join("") + direct.map(directItemHtml).join("");
setListHtml(
document.getElementById("run-list"),
running.map(runItemHtml).join("") + direct.map(directItemHtml).join(""),
);
const waitList = document.getElementById("wait-list");
document.getElementById("wait-count").textContent = waiting.length;
document.getElementById("wait-count").className = "count" + (errors.length ? " bad" : waiting.length ? " hot" : "");
document.getElementById("wait-empty").hidden = !!(waiting.length || errors.length);
document.getElementById("wait-box").classList.toggle("is-empty", !(waiting.length || errors.length));
waitList.innerHTML = errors.map((job) => waitItemHtml(job, 0, 1)).join("")
+ waiting.map((job, i) => waitItemHtml(job, i, waiting.length)).join("");
setListHtml(
waitList,
errors.map((job) => waitItemHtml(job, 0, 1)).join("")
+ waiting.map((job, i) => waitItemHtml(job, i, waiting.length)).join(""),
);
const panel = document.getElementById("live-panel");
panel.querySelectorAll("[data-cancel-q]").forEach((btn) => {