Redesigned status, log, tag generator and settings pages: spool rings in filament colour, live job card (progress and time left read from CC2 Control's /api/printer while a job runs), log page with filters, iOS-style settings with a write/dry-run switch. On phones: bottom tab bar, printer switcher, slot details in a bottom sheet. Dark mode follows the system. Web app manifest and home screen icons for "Add to Home Screen". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GcEk5tkfFSYD6NTF66NkC
79 lines
3.2 KiB
HTML
79 lines
3.2 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}CC2 Bridge – Protokoll{% endblock %}
|
||
{% block heading %}Protokoll{% endblock %}
|
||
{% block head %}
|
||
<style>
|
||
.wrap { max-width: 860px; }
|
||
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
|
||
.chips button { border-radius: 99px; min-height: 36px; font-size: 13px; }
|
||
.chips button[aria-pressed=true] { background: var(--ink); border-color: var(--ink); color: var(--bg); }
|
||
.day { margin: 18px 4px 8px; }
|
||
.entries { padding: 0 14px; }
|
||
.entry { display: flex; gap: 12px; padding: 12px 0; border-top: 1px solid var(--line2); }
|
||
.entry:first-child { border-top: 0; }
|
||
.entry .msg { font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; }
|
||
.entry .meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
|
||
</style>
|
||
{% endblock %}
|
||
{% block content %}
|
||
<div class="wrap">
|
||
<div class="chips" id="chips"></div>
|
||
<div id="list"><div class="card muted">Lade …</div></div>
|
||
</div>
|
||
{% endblock %}
|
||
{% block script %}
|
||
<script>
|
||
const FILTERS = [
|
||
['all', 'Alle', () => true],
|
||
['book', 'Buchungen', (e) => /gebucht/.test(e.msg) && e.level !== 'dry'],
|
||
['warn', 'Hinweise', (e) => e.level === 'warn' || e.level === 'dry'],
|
||
['error', 'Fehler', (e) => e.level === 'error'],
|
||
];
|
||
let FILTER = 'all', DATA = null;
|
||
|
||
function renderChips() {
|
||
$('chips').innerHTML = FILTERS.map(([k, label]) =>
|
||
`<button aria-pressed="${k === FILTER}" onclick="setFilter('${k}')">${label}</button>`).join('');
|
||
}
|
||
function setFilter(k) { FILTER = k; renderChips(); render(); }
|
||
|
||
function dayLabel(t) {
|
||
const d = new Date(t * 1000), today = new Date();
|
||
const start = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
|
||
const diff = Math.round((start(today) - start(d)) / 86400000);
|
||
if (diff === 0) return 'Heute';
|
||
if (diff === 1) return 'Gestern';
|
||
return d.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' });
|
||
}
|
||
|
||
function render() {
|
||
if (!DATA) return;
|
||
const names = (DATA.printers || []).map((p) => p.name);
|
||
const fn = FILTERS.find((f) => f[0] === FILTER)[2];
|
||
const groups = [];
|
||
for (const e of (DATA.log || []).filter(fn)) {
|
||
const day = dayLabel(e.time);
|
||
if (!groups.length || groups[groups.length - 1].day !== day) groups.push({ day, items: [] });
|
||
groups[groups.length - 1].items.push(e);
|
||
}
|
||
$('list').innerHTML = groups.map((g) => `<div class="label day">${esc(g.day)}</div>
|
||
<div class="group entries">${g.items.map((e) => {
|
||
const [ic, bg, fg] = logIcon(e), [pn, msg] = splitPrinter(e.msg, names);
|
||
return `<div class="entry"><div class="icon-badge" style="background:${bg};color:${fg}">${ic}</div>
|
||
<div style="flex:1;min-width:0"><div class="msg">${esc(msg)}</div>
|
||
<div class="meta">${esc(pn)} · <span class="mono">${fmtClock(e.time, true)}</span>${e.level === 'dry' ? ' · Dry-Run' : ''}</div></div></div>`;
|
||
}).join('')}</div>`).join('') || '<div class="card muted">Keine Einträge</div>';
|
||
}
|
||
|
||
async function load() {
|
||
try { DATA = await api('GET', '/api/status'); } catch (e) { $('list').innerHTML = `<div class="banner err">${esc(e.message)}</div>`; return; }
|
||
setMode(DATA.dry_run);
|
||
setPoll(DATA.last_poll);
|
||
render();
|
||
}
|
||
renderChips();
|
||
load();
|
||
setInterval(() => { if (!document.hidden) load(); }, 5000);
|
||
</script>
|
||
{% endblock %}
|