147 lines
6.5 KiB
HTML
147 lines
6.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 chart(samples) {
|
||
if (!samples || samples.length < 2) {
|
||
return '<p class="tiny">Kein Temperaturverlauf (nur für neue Drucke).</p>';
|
||
}
|
||
const w = 640, h = 180, pad = 28;
|
||
const temps = samples.flatMap((s) => [s.nozzle, s.bed, s.chamber, s.nozzle_target, s.bed_target].filter((v) => v != null));
|
||
const minT = Math.min(...temps, 0);
|
||
const maxT = Math.max(...temps, 100);
|
||
const x = (i) => pad + (i / (samples.length - 1)) * (w - pad * 2);
|
||
const y = (v) => h - pad - ((Number(v) - minT) / Math.max(1, maxT - minT)) * (h - pad * 2);
|
||
function path(key, color) {
|
||
const pts = samples.map((s, i) => s[key] == null ? null : `${x(i)},${y(s[key])}`).filter(Boolean);
|
||
if (pts.length < 2) return "";
|
||
return `<polyline fill="none" stroke="${color}" stroke-width="1.6" points="${pts.join(" ")}" />`;
|
||
}
|
||
return `<svg class="temp-chart" viewBox="0 0 ${w} ${h}" role="img" aria-label="Temperaturverlauf">
|
||
${path("nozzle_target", "#fdba74")}
|
||
${path("bed_target", "#93c5fd")}
|
||
${path("nozzle", "#ea580c")}
|
||
${path("bed", "#2563eb")}
|
||
${path("chamber", "#16a34a")}
|
||
<text x="${pad}" y="14" class="chart-label">${Math.round(maxT)} °C</text>
|
||
<text x="${pad}" y="${h - 8}" class="chart-label">${Math.round(minT)} °C</text>
|
||
</svg>
|
||
<p class="tiny chart-legend"><span class="leg nozzle">Düse</span> <span class="leg bed">Bett</span> <span class="leg chamber">Kammer</span> · gestrichelt ≈ Soll</p>`;
|
||
}
|
||
|
||
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 cost = Number(run.filament_cost || 0) + Number(run.energy_cost || 0);
|
||
|
||
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((e.created_at || "").replace("T", " ").slice(0, 19))}</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(run.started_at || "—")} → ${escapeHtml(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(run.energy_cost)})</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>
|
||
${chart(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 %}
|