New web UI with iPhone web app
CI / test (push) Successful in 3m44s
CI / image (push) Successful in 44s

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
This commit is contained in:
Jonas MenzelandClaude Opus 5.5 committed 2026-10-25 19:55:07 +01:00
1 parent 67c3b54903
commit ae44441cfb
16 files changed
+982 -285

No files matched your search

+29
View File
@@ -198,6 +198,7 @@ class PrinterWorker:
self.tray_status: dict[int, int] = {}
self.record: dict | None = None
self.cc2_data: dict | None = None
self.print_info: dict | None = None # CC2 Control /api/printer, only read while a job runs
@property
def id(self) -> str:
@@ -250,6 +251,12 @@ class PrinterWorker:
self._error("cc2", None)
except CC2Error as e:
self._error("cc2", str(e))
self.print_info = None
if self.cc2_data is not None and (self.cc2_data.get("job") or {}).get("active"):
try:
self.print_info = self.cc2.printer()
except CC2Error:
pass # progress is only shown in the UI; the spool list above is what counts
if self.record is not None:
self._process_record(self.record)
self._process_scans()
@@ -693,9 +700,31 @@ class PrinterWorker:
"cc2_enabled": None if cc2 is None else bool(cc2.get("enabled")),
"job_active": None if cc2 is None else bool((cc2.get("job") or {}).get("active")),
"host_key": self.bridge.keys.host_key(self.id) is not None,
"job": self._job_summary(),
"slots": slots,
}
def _job_summary(self) -> dict | None:
job = (self.cc2_data or {}).get("job") or {}
if not job.get("active"):
return None
used = [s for s in job.get("slots") or [] if isinstance(s, dict)]
info = self.print_info or {}
pr, machine = info.get("print") or {}, info.get("machine") or {}
progress = machine.get("progress")
return {
"file": str(job.get("file") or pr.get("filename") or "").rsplit("/", 1)[-1],
"started": job.get("started"),
"grams": round(sum(_num(s.get("grams")) for s in used), 2),
"slots": [{"slot": i, "grams": round(_num(s.get("grams")), 2)} for i, s in enumerate(used)
if _num(s.get("grams")) > 0],
"progress": progress if isinstance(progress, (int, float)) else None,
"layer": pr.get("current_layer"),
"layers": pr.get("total_layers"),
"remaining": pr.get("remaining"),
"state": pr.get("state"),
}
class Bridge:
def __init__(self, data_dir: Path, settings: SettingsStore, keys: KeyStore, *,