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.
127 lines
5.5 KiB
HTML
127 lines
5.5 KiB
HTML
{% extends "layout.html" %}
|
||
{% block body %}
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>Druckbericht</h1>
|
||
<p class="tiny" id="bericht-sub">Lauf wird geladen …</p>
|
||
</div>
|
||
<div class="page-head-side">
|
||
<a class="ghost" href="/protokoll">← Protokoll</a>
|
||
<a class="ghost" id="etikett-druck" href="#" hidden>Etikett Druck</a>
|
||
<a class="ghost" id="etikett-objekt" href="#" hidden>Etiketten Objekte</a>
|
||
</div>
|
||
</div>
|
||
<p id="bericht-note"></p>
|
||
<div id="bericht" class="bericht"></div>
|
||
{% endblock %}
|
||
{% block script %}
|
||
<script>
|
||
const runId = Number(location.pathname.split("/").pop());
|
||
const states = {
|
||
completed: "fertig", cancelled: "abgebrochen", error: "Fehler",
|
||
in_progress: "druckt", printing: "druckt", paused: "pausiert", interrupted: "unterbrochen",
|
||
};
|
||
|
||
function sourceLabel(source, estimate) {
|
||
if (estimate) return "Schätzwert";
|
||
if (source === "measured") return "Messwert";
|
||
if (source === "estimate") return "Schätzwert";
|
||
return source || "";
|
||
}
|
||
|
||
function render(data) {
|
||
const run = data.run;
|
||
const name = (run.filename || "").split("/").pop() || "ohne Datei";
|
||
document.getElementById("bericht-sub").textContent =
|
||
(run.printer_name || run.printer_host) + " · " + name
|
||
+ (run.chargen_nr ? " · " + run.chargen_nr : "");
|
||
|
||
const planned = Number(run.weight_planned_g) || 0;
|
||
const used = Number(run.weight_used_g) || 0;
|
||
const diff = Math.round(used) - Math.round(planned);
|
||
const energy = data.energy || {};
|
||
const energyEuro = energy.cost != null ? Number(energy.cost) : Number(run.energy_cost || 0);
|
||
const cost = Number(run.filament_cost || 0) + energyEuro;
|
||
const energyHint = energy.source === "ed"
|
||
? (Number(energy.kwh) || 0).toLocaleString("de-DE", { maximumFractionDigits: 2 }) + " kWh aus ED"
|
||
: (energy.kwh != null
|
||
? (Number(energy.kwh) || 0).toLocaleString("de-DE", { maximumFractionDigits: 2 }) + " kWh pauschal"
|
||
: "");
|
||
|
||
const spoolRows = (data.spools || []).map((s) => `
|
||
<tr>
|
||
<td>${escapeHtml(s.name)}${s.ace_slot ? " · ACE " + s.ace_slot : ""}</td>
|
||
<td>${escapeHtml(s.material_type || "")}</td>
|
||
<td class="num">${grams(s.weight_planned_g)}</td>
|
||
<td class="num">${grams(s.weight_g)}${s.is_estimate ? ' <span class="tag">Schätzwert</span>' : ""}</td>
|
||
<td class="num">${euro(s.cost)}</td>
|
||
<td class="nowrap">${escapeHtml(s.chargen_nr || "")}</td>
|
||
</tr>`).join("") || '<tr><td class="empty" colspan="6">Keine Spule zugeordnet.</td></tr>';
|
||
|
||
const events = (data.events || []).map((e) =>
|
||
`<li><span class="tiny">${escapeHtml(formatDateTime(e.created_at))}</span> ${escapeHtml(e.message || e.kind)}</li>`
|
||
).join("") || "<li class=\"tiny\">Keine Ereignisse.</li>";
|
||
|
||
const labels = (data.labels || []).filter((l) => !l.excluded);
|
||
const labelBits = labels.length
|
||
? labels.map((l) => `<li id="objekt-${encodeURIComponent(l.object_name || "")}"><code>${escapeHtml(l.chargen_nr)}</code> ${escapeHtml(l.object_name || "Druck")}</li>`).join("")
|
||
: (run.chargen_nr ? `<li><code>${escapeHtml(run.chargen_nr)}</code> gesamter Druck</li>` : "<li class=\"tiny\">Noch keine Charge.</li>");
|
||
|
||
const segments = (data.segments || []).map((s) => {
|
||
const end = s.mm_end == null ? "…" : Math.round(s.mm_end);
|
||
return `<li>${escapeHtml(s.spool_name)} · ${Math.round(s.mm_start)}–${end} mm${s.ace_slot ? " · Platz " + s.ace_slot : ""}</li>`;
|
||
}).join("");
|
||
|
||
document.getElementById("bericht").innerHTML = `
|
||
<div class="card bericht-head">
|
||
<div class="bericht-meta">
|
||
<p><b>${escapeHtml(states[run.state] || run.state)}</b>${run.verdict ? " · Urteil " + escapeHtml(run.verdict) : ""}</p>
|
||
<p class="tiny">${escapeHtml(formatDateRange(run.started_at, run.ended_at))}</p>
|
||
<p>Dauer <b>${hours(run.duration_s)}</b>${run.estimated_s ? " · Soll " + hours(run.estimated_s) : ""}</p>
|
||
<p>Material <b>${grams(used)}</b> von ${grams(planned)}${planned ? " (" + (diff > 0 ? "+" : "") + diff + " g)" : ""}
|
||
${run.weight_source ? ' · <span class="tag">' + sourceLabel(run.weight_source) + "</span>" : ""}</p>
|
||
<p>Kosten <b>${euro(cost)}</b> (Filament ${euro(run.filament_cost)} · Strom ${euro(energyEuro)}${energyHint ? " · " + energyHint : ""})</p>
|
||
${run.message ? "<p class=\"msg\">" + escapeHtml(run.message) + "</p>" : ""}
|
||
${data.project ? "<p class=\"tiny\">Projekt: " + escapeHtml(data.project.name) + "</p>" : ""}
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Spulen</h2>
|
||
<div class="table-wrap"><table>
|
||
<thead><tr><th>Spule</th><th>Sorte</th><th>Soll</th><th>Ist</th><th>Kosten</th><th>Charge</th></tr></thead>
|
||
<tbody>${spoolRows}</tbody>
|
||
</table></div>
|
||
${segments ? "<h3>Abschnitte</h3><ul class=\"plain\">" + segments + "</ul>" : ""}
|
||
</div>
|
||
<div class="card">
|
||
<h2>Verlauf</h2>
|
||
${tempChartHtml(data.samples)}
|
||
</div>
|
||
<div class="card grid-2">
|
||
<div>
|
||
<h2>Chargen</h2>
|
||
<ul class="plain">${labelBits}</ul>
|
||
</div>
|
||
<div>
|
||
<h2>Ereignisse</h2>
|
||
<ul class="plain">${events}</ul>
|
||
</div>
|
||
</div>`;
|
||
|
||
const druck = document.getElementById("etikett-druck");
|
||
const objekt = document.getElementById("etikett-objekt");
|
||
if (run.chargen_nr) {
|
||
druck.hidden = false;
|
||
druck.href = "/etikett/lauf/" + run.id + "?mode=druck";
|
||
objekt.hidden = false;
|
||
objekt.href = "/etikett/lauf/" + run.id + "?mode=objekt&qty=1";
|
||
} else {
|
||
druck.hidden = true;
|
||
objekt.hidden = true;
|
||
}
|
||
}
|
||
|
||
api("/laeufe/" + runId).then(render).catch((error) => showNote("bericht-note", error));
|
||
</script>
|
||
{% endblock %}
|