Files
cc2-spoolman-bridge/app/templates/log.html
T
Jonas MenzelandClaude Opus 5.5 ae44441cfb
CI / test (push) Successful in 3m44s
CI / image (push) Successful in 44s
New web UI with iPhone web app
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
2026-10-25 19:55:07 +01:00

79 lines
3.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}