- A roll put back into the slot it left, with the tag data unchanged, is mapped again and CC2 Control's question answered; if the touchscreen then replaces the id, it was another roll and goes to storage. - A touchscreen edit of a scanned roll keeps the mapping and re-answers CC2 Control's question so its use is counted. - Bookings name the print and its result from CC2 Control's log. - A weigh-in in CC2 Control sets Spoolman's remaining weight instead of being booked as use. - After "Take out" with the roll still in the tray, the tray's logged use is booked to that roll until it leaves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GcEk5tkfFSYD6NTF66NkC
116 lines
7.1 KiB
HTML
116 lines
7.1 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}CC2 Bridge – Status{% endblock %}
|
||
{% block content %}
|
||
<div id="banners"></div>
|
||
<div id="printers"><div class="panel muted">Lade …</div></div>
|
||
<p class="small muted" id="lastpoll"></p>
|
||
<h2>Protokoll</h2>
|
||
<div class="panel table-wrap">
|
||
<table><thead><tr><th style="width:170px">Zeit</th><th style="width:80px"></th><th>Aktion</th></tr></thead>
|
||
<tbody id="log"></tbody></table>
|
||
</div>
|
||
{% endblock %}
|
||
{% block script %}
|
||
<script>
|
||
const QUESTION = { inserted: 'eingelegt', changed: 'geändert' };
|
||
const TRAY = { 0: ['leer', 'warn'], 1: ['eingelegt', 'ok'], 2: ['fördert', 'ok'] };
|
||
const LEVEL = { info: ['Info', ''], warn: ['Warnung', 'warn'], error: ['Fehler', 'err'], dry: ['Dry-Run', 'dry'] };
|
||
|
||
function colorOf(ch) {
|
||
const c = (ch.color || '').replace(/^0x/i, '');
|
||
return /^[0-9a-f]{6}$/i.test(c) ? c : null;
|
||
}
|
||
|
||
function slotCard(p, s) {
|
||
const ch = s.record.channel || {};
|
||
const tray = TRAY[s.tray_status];
|
||
const col = colorOf(ch);
|
||
const m = s.mapping, sm = s.spoolman, cc = s.cc2_spool;
|
||
let tag = '<span class="muted">kein Tag</span>';
|
||
if (s.record.occupied) {
|
||
tag = `${col ? `<span class="swatch" style="background:#${col}"></span> ` : ''}${esc(ch.detailed_type || ch.type)}` +
|
||
` <span class="muted small">${esc(ch.manufacturer)}, weight ${esc(ch.weight)}</span>`;
|
||
}
|
||
let assign = '<span class="muted">–</span>';
|
||
if (m) {
|
||
assign = s.cc2_matches ? '<span class="tag ok">zugewiesen</span>'
|
||
: (m.cc2 ? '<span class="tag warn">nicht zugewiesen</span>' : '<span class="tag warn">noch nicht angelegt</span>');
|
||
}
|
||
if (s.cc2_question) assign += ` <span class="tag warn">CC2 fragt: Spule ${esc(QUESTION[s.cc2_question] || s.cc2_question)}</span>`;
|
||
const orphan = !m && !s.takeout && s.record.spool_id != null && (s.tray_status === 1 || s.tray_status === 2);
|
||
const lb = s.last_booking;
|
||
return `<div class="panel">
|
||
<h3>Slot ${s.slot + 1} ${tray ? `<span class="tag ${tray[1]}">${tray[0]}</span>` : ''}</h3>
|
||
<p class="small muted" style="margin:-4px 0 8px">Ort in Spoolman: ${esc(s.location)}</p>
|
||
<dl class="kv">
|
||
<dt>Tag</dt><dd>${tag}</dd>
|
||
<dt>Tag-ID</dt><dd>${s.record.spool_id ?? '<span class="muted">–</span>'}</dd>
|
||
<dt>Spoolman</dt><dd>${m ? `${sm && sm.color ? `<span class="swatch" style="background:#${sm.color}"></span> ` : ''}<b>${esc(m.name || m.spool)}</b> <span class="muted">#${m.spool}</span>` +
|
||
(sm ? `<br><span class="small muted">${esc(sm.vendor)} ${esc(sm.material)}, Rest ${sm.remaining != null ? Math.round(sm.remaining) + ' g' : '?'}</span>` : '') : '<span class="muted">nicht zugeordnet</span>'}</dd>
|
||
<dt>CC2 Control</dt><dd>${cc ? esc(cc.name) + ` <span class="small muted">Rest ${Number(cc.remaining).toFixed(1)} g</span><br>` : ''}${assign}</dd>
|
||
<dt>Buchung</dt><dd>${lb ? `${lb.grams.toFixed(2)} g <span class="small muted">${fmtTime(lb.time)}</span>` +
|
||
(lb.details ? `<br><span class="small muted">${esc(lb.details)}</span>` : '') : '<span class="muted">–</span>'}</dd>
|
||
</dl>
|
||
${s.takeout ? `<p class="small" style="margin:10px 0 0;color:var(--warn)">${esc(s.takeout.name)} wurde in CC2 Control herausgenommen
|
||
und steht in Spoolman im Lager, steckt aber noch. Was dieser Slot druckt, bucht die Bridge trotzdem auf ${esc(s.takeout.name)},
|
||
bis die Rolle entnommen wird. Wieder zuordnen: Rolle herausnehmen und neu einlegen.</p>` : ''}
|
||
${orphan ? `<p class="small" style="margin:10px 0 0;color:var(--warn)">Tag ${s.record.spool_id} steht noch im Slot, ist aber keiner Rolle
|
||
zugeordnet. Liegt diese Rolle hier, sie herausnehmen und neu einlegen (oder in CC2 Control auswählen).</p>` : ''}
|
||
${m ? `<div class="row" style="margin-top:10px">
|
||
<button onclick="slotAction('${p.id}', ${s.slot}, 'reassign')">Zuordnung erneut senden</button>
|
||
<button class="danger" onclick="slotAction('${p.id}', ${s.slot}, 'release')">Aufheben</button></div>` : ''}
|
||
</div>`;
|
||
}
|
||
|
||
let NAMES = {};
|
||
async function slotAction(pid, slot, action) {
|
||
const name = NAMES[pid] || pid;
|
||
const text = action === 'release'
|
||
? `${name}: Zuordnung von Slot ${slot + 1} aufheben? Die Spule wird in Spoolman ins Lager gebucht.`
|
||
: `${name}: Zuordnung von Slot ${slot + 1} erneut an CC2 Control senden?`;
|
||
if (!confirm(text)) return;
|
||
try { await api('POST', `/api/printers/${pid}/slots/${slot}/${action}`); toast('Eingereiht'); setTimeout(load, 800); }
|
||
catch (e) { toast(e.message); }
|
||
}
|
||
|
||
async function enableCC2(pid) {
|
||
const name = NAMES[pid] || pid;
|
||
if (!confirm(`Spulenverwaltung in CC2 Control von ${name} einschalten?\n\nAb dann zählt CC2 Control den Verbrauch je Slot.`)) return;
|
||
try {
|
||
const r = await api('POST', `/api/printers/${pid}/cc2/enable`, { confirm: true });
|
||
toast(r.dry_run ? 'Dry-Run: nur protokolliert' : 'Wird eingeschaltet');
|
||
setTimeout(load, 1500);
|
||
} catch (e) { toast(e.message); }
|
||
}
|
||
|
||
async function load() {
|
||
let st;
|
||
try { st = await api('GET', '/api/status'); } catch (e) { $('banners').innerHTML = `<div class="banner err">${esc(e.message)}</div>`; return; }
|
||
setMode(st.dry_run);
|
||
const b = [];
|
||
if (st.dry_run) b.push('<div class="banner dry">Dry-Run aktiv: Schreibaktionen werden nur protokolliert („würde …“). Umschalten unter Einstellungen.</div>');
|
||
if (!st.ssh_key) b.push('<div class="banner warn">Kein SSH-Schlüssel hinterlegt. <a href="/settings">Einstellungen</a></div>');
|
||
if (st.spoolman_error) b.push(`<div class="banner err">${esc(st.spoolman_error)}</div>`);
|
||
$('banners').innerHTML = b.join('');
|
||
NAMES = Object.fromEntries((st.printers || []).map((p) => [p.id, p.name]));
|
||
if (st.printers) $('printers').innerHTML = st.printers.map((p) => {
|
||
const pb = [];
|
||
if (!p.enabled) pb.push('<div class="banner warn">Abfrage pausiert (in den Einstellungen deaktiviert).</div>');
|
||
else if (st.ssh_key && !p.host_key) pb.push('<div class="banner warn">Host-Key noch nicht bestätigt. <a href="/settings">Verbindung testen</a></div>');
|
||
if (p.cc2_enabled === false) pb.push(`<div class="banner err"><b>Spulenverwaltung in CC2 Control ist ausgeschaltet.</b> Verbrauch wird nicht gezählt. <button class="primary" onclick="enableCC2('${p.id}')">Einschalten …</button></div>`);
|
||
if (p.job_active) pb.push('<div class="banner ok">Druck läuft – Verbrauch wird nach dem Druck gebucht.</div>');
|
||
for (const v of Object.values(p.errors || {})) if (v) pb.push(`<div class="banner err">${esc(v)}</div>`);
|
||
return `<h2>${esc(p.name)} <span class="small muted" style="font-weight:400">${esc(p.host)}</span></h2>${pb.join('')}
|
||
<div class="grid">${p.slots.map((s) => slotCard(p, s)).join('')}</div>`;
|
||
}).join('') || '<div class="panel muted">Kein Drucker eingerichtet. <a href="/settings">Einstellungen</a></div>';
|
||
$('lastpoll').textContent = st.last_poll ? 'Letzte Abfrage: ' + fmtTime(st.last_poll) : 'Noch keine Abfrage';
|
||
$('log').innerHTML = (st.log || []).map((e) => {
|
||
const l = LEVEL[e.level] || [e.level, ''];
|
||
return `<tr><td class="small muted">${fmtTime(e.time)}</td><td><span class="tag ${l[1]}">${l[0]}</span></td><td>${esc(e.msg)}</td></tr>`;
|
||
}).join('') || '<tr><td colspan="3" class="muted">Noch keine Einträge</td></tr>';
|
||
}
|
||
load();
|
||
setInterval(load, 3000);
|
||
</script>
|
||
{% endblock %}
|