Grotti 3.5.2: Zu wenig Rest blockiert den Start nicht mehr, und ein anlaufender Druck wird nicht als Neustart neu eingereiht.

This commit is contained in:
Dominic Gundel committed 2026-10-21 21:04:28 +02:00
1 parent 5166ed6530
commit d1eb87ba25
13 files changed
+98 -77

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ const CSRF = document.querySelector('meta[name="csrf"]')?.content || "";
const STATE_LABELS = {
printing: "druckt", paused: "pausiert", complete: "fertig", standby: "bereit",
heating: "heizt", error: "Fehler", cancelled: "abgebrochen", offline: "offline",
waiting: "wartet", running: "läuft",
waiting: "wartet", running: "läuft", starting: "startet",
};
function escapeHtml(value) {
+1 -1
View File
@@ -49,7 +49,7 @@
<dt>Auftrag</dt><dd>Ein Eintrag in der Warteschlange: eine Datei für einen Drucker oder für den nächsten freien.</dd>
<dt>Ablauf</dt><dd>Auftrag mit mehreren Schritten und Wiederholungen. Nur Kobra 3.</dd>
<dt>Dieser Drucker</dt><dd>Frei und Platte klar → sofort; sonst Warteschlange für genau diesen Drucker.</dd>
<dt>Nächster passender</dt><dd>Grotti claimt den nächsten freien Drucker mit passender Katalog-Sorte und Restfilament.</dd>
<dt>Nächster passender</dt><dd>Grotti claimt den nächsten freien Drucker mit passender Katalog-Sorte. Zu wenig Rest warnt nur und hält den Start nicht auf.</dd>
<dt>Platte belegt</dt><dd>Nach jedem Druck gesetzt. Blockiert den nächsten Auftrag auf diesem Drucker.</dd>
<dt>Alert</dt><dd>Wichtiges Ereignis (Fertig, Fehler, Abbruch, offline). Steht in der Seitenleiste und geht auf Wunsch an Telegram.</dd>
<dt>Ereignis</dt><dd>Jeder Eintrag unter Aktivität. Ablauf-Schritte und Starts sind Ereignisse, aber keine Alerts.</dd>
+6 -4
View File
@@ -160,7 +160,7 @@
{% endif %}
<form method="dialog"><button class="ghost" value="cancel">Schließen</button></form>
</dialog>
<script src="{{ url_for('static', filename='api.js') }}?v=32"></script>
<script src="{{ url_for('static', filename='api.js') }}?v=33"></script>
<script src="{{ url_for('static', filename='ui.js') }}?v=6"></script>
<script>
async function doLogout() {
@@ -315,9 +315,9 @@ function runLineHtml({ name, progress, remain, filename, tipExtra, cancelId, hos
const pct = Math.max(0, Math.min(100, Math.round((Number(progress) || 0) * 100)));
const finish = remain > 0 ? finishClock(remain) : "";
const bits = [name];
if (pct > 0 || remain > 0) bits.push(pct + " %");
if (finish) bits.push(finish);
if (tipExtra) bits.push(tipExtra);
else if (pct > 0 || remain > 0) bits.push(pct + " %");
if (finish) bits.push(finish);
const tip = [
filename ? jobLabel(filename) : "",
remain > 0 ? "noch " + hours(remain) : "",
@@ -375,9 +375,10 @@ function renderLiveChips({ actionItems, running, activeSends, alertCount, etaTex
const pct = Math.max(0, Math.min(100, Math.round((Number(p.progress) || 0) * 100)));
const finish = p.remain > 0 ? finishClock(p.remain) : "";
const pausedFil = p.state === "paused" && (p.filament_empty || p.pause_reason);
const starting = p.starting || p.state === "starting";
const label = [
shortPrinter(p.name || p.host),
pausedFil ? "Material!" : (pct ? pct + "%" : ""),
pausedFil ? "Material!" : (starting ? "startet" : (pct ? pct + "%" : "")),
finish,
].filter(Boolean).join(" · ");
const tip = [p.name || "", p.pause_reason || ""].filter(Boolean).join(" — ");
@@ -479,6 +480,7 @@ async function refreshJetzt() {
progress: p.progress,
remain: p.remain,
filename: p.filename,
tipExtra: (p.starting || p.state === "starting") ? "wird gestartet" : "",
cancelId: p.queue_id,
})).join(""),
);
+36 -9
View File
@@ -322,6 +322,14 @@ function actionsHtml(state, printer) {
function flowHtml(printer) {
const job = printer.queue_running;
const bits = [];
const pending = startingJob(printer);
if (pending) {
const name = jobName(pending.filename || "") || "Datei";
bits.push(`<div class="strip flow">
<div class="flow-head">${icon("play")}<span class="grow">Startet ${escapeHtml(name)}</span></div>
<div class="flow-msg">Die Datei ist auf dem Drucker. Bis der Druck läuft, kann es etwa eine Minute dauern — der Auftrag bleibt hier.</div>
</div>`);
}
// Nur Ablauf braucht den Strip (Schritte). Einfacher Queue-Druck: Karte zeigt schon DRUCKT.
if (job && job.is_ablauf) {
const rep = repeatText(job);
@@ -511,6 +519,16 @@ function steerPanelHtml(printer) {
</div>`;
}
function startingJob(printer) {
const job = printer && printer.queue_running;
if (!job || job.is_ablauf) return null;
const phase = String(job.phase || "").toLowerCase();
if (phase !== "starting" && phase !== "await_print") return null;
const state = shownState(printer);
if (state === "printing" || state === "paused" || state === "heating") return null;
return job;
}
function heroHtml(printer, state) {
const remain = hours(printer.remain);
const eta = finishClock(printer.remain);
@@ -532,6 +550,11 @@ function heroHtml(printer, state) {
if (state === "offline") {
return `<div class="hero idle"><span class="hero-main">nicht erreichbar</span></div>`;
}
const pending = startingJob(printer);
if (pending) {
const name = jobName(pending.filename || "") || "Datei";
return `<div class="hero"><span class="hero-main"><small>startet</small>${escapeHtml(name)}</span><span class="hero-side">kann eine Minute dauern</span></div>`;
}
if (state === "complete") {
const took = hours(printer.elapsed);
const last = jobLabel(printer.filename || "");
@@ -541,7 +564,7 @@ function heroHtml(printer, state) {
if (state === "cancelled") {
return `<div class="hero idle"><span class="hero-main">abgebrochen</span></div>`;
}
const busyQueue = printer.queue_running ? "Ablauf läuft" : "";
const busyQueue = printer.queue_running && printer.queue_running.is_ablauf ? "Ablauf läuft" : "";
return `<div class="hero idle"><span class="hero-main">${needsClear(printer) ? "Platte belegt" : "bereit"}</span>${busyQueue ? `<span class="hero-side">${busyQueue}</span>` : ""}</div>`;
}
@@ -809,13 +832,15 @@ function patchCard(article, printer) {
else runningJobs.delete(printer.host);
const role = (name) => article.querySelector(`[data-role=${name}]`);
setHtml(role("chip"), chipHtml(state, labels[state]));
const pendingStart = startingJob(printer);
setHtml(role("chip"), chipHtml(pendingStart ? "heating" : state, pendingStart ? "startet" : labels[state]));
setHtml(role("hero"), heroHtml(printer, state));
patchBar(role("bar"), printer, state);
setHtml(role("stats"), statsHtml(printer, state));
setHtml(role("temps"), tempbarsHtml(printer, state));
const jobline = role("jobline");
const jobVisible = jobLineVisible(printer, state, shownFile);
const startName = pendingStart ? (pendingStart.filename || "") : "";
const jobVisible = jobLineVisible(printer, state, shownFile || startName) || !!pendingStart;
const hasThumb = !!(jobVisible && printer.preview);
if (jobline) {
jobline.hidden = !jobVisible;
@@ -847,8 +872,8 @@ function patchCard(article, printer) {
}
const job = role("job");
if (job) {
setText(job, jobName(shownFile) || "");
job.title = shownFile || "";
setText(job, jobName(shownFile || startName) || "");
job.title = shownFile || startName || "";
}
setHtml(role("swatches"), swatchesHtml(printer));
setHtml(role("flow"), flowHtml(printer));
@@ -976,7 +1001,8 @@ function patchDesk(printer) {
const nameEl = role("name");
if (nameEl) nameEl.textContent = printer.name || printer.host;
setHtml(role("badge"), printer.family_label ? `<span class="badge">${escapeHtml(printer.family_label)}</span>` : "");
setHtml(role("chip"), chipHtml(state, labels[state]));
const pendingStart = startingJob(printer);
setHtml(role("chip"), chipHtml(pendingStart ? "heating" : state, pendingStart ? "startet" : labels[state]));
setHtml(role("pills"), pillsHtml(printer.host));
const mainsail = role("mainsail");
if (mainsail) {
@@ -989,12 +1015,13 @@ function patchDesk(printer) {
setHtml(role("temps"), tempbarsHtml(printer, state));
const jobline = role("jobline");
const jobVisible = jobLineVisible(printer, state, shownFile);
const startName = pendingStart ? (pendingStart.filename || "") : "";
const jobVisible = jobLineVisible(printer, state, shownFile || startName) || !!pendingStart;
if (jobline) jobline.hidden = !jobVisible && !printer.preview;
const job = role("job");
if (job) {
setText(job, jobName(shownFile) || (jobVisible ? "" : "Kein Auftrag"));
job.title = shownFile || "";
setText(job, jobName(shownFile || startName) || (jobVisible ? "" : "Kein Auftrag"));
job.title = shownFile || startName || "";
}
const preview = role("preview");
if (preview) {
+5 -2
View File
@@ -136,9 +136,12 @@ function render() {
} else if (st === "waiting" || st === "running") {
tools.push(`<button type="button" class="icon-btn danger" data-cancel="${job.id}" title="Abbrechen">${icon("x")}</button>`);
}
const chipState = st === "running" ? "printing" : st === "waiting" ? "waiting" : st === "error" ? "error" : st === "done" ? "done" : st === "cancelled" || st === "dismissed" ? "dismissed" : "complete";
const phase = String(job.phase || "").toLowerCase();
const starting = st === "running" && (phase === "starting" || phase === "await_print");
const chipState = starting ? "heating" : st === "running" ? "printing" : st === "waiting" ? "waiting" : st === "error" ? "error" : st === "done" ? "done" : st === "cancelled" || st === "dismissed" ? "dismissed" : "complete";
const chipLabel = starting ? "startet" : (STATUS_LABEL[st] || st);
return `<tr data-id="${job.id}">
<td>${chipHtml(chipState, STATUS_LABEL[st] || st)}</td>
<td>${chipHtml(chipState, chipLabel)}</td>
<td><span class="cell-file" title="${escapeHtml(job.filename || "")}">${escapeHtml(jobLabel(job.filename))}</span>${rep ? " · " + escapeHtml(rep) : ""}</td>
<td>${job.host ? `<a href="/#${encodeURIComponent(job.host)}">${escapeHtml(targetOf(job))}</a>` : escapeHtml(targetOf(job))}</td>
<td>${steps || "—"}</td>