feat(printers): per-printer Maintenance Mode toggle (#1476)

Operator-flipped out-of-service state per printer for three scenarios:
  parallel Bambuddy installs (dev + prod where the printer rejects all
  but one MQTT client), printers under repair, and temporary suspension.

  The backend Printer.is_active gate has shipped since day one and is
  already honoured by every consumer — MQTT (printer_manager), queue
  dispatch (print_scheduler, print_queue), metrics, scheduler, picker,
  backup, maintenance dashboard. The missing piece was UI exposure.

  Three entry points to flip is_active:
  - Three-dot overflow menu (Enter / Exit maintenance mode, wrench icon)
  - Exit button inside the in-card amber panel
  - Checkbox in EditPrinterModal

  Card UI: expanded mode shows an amber panel (Wrench + "In Maintenance"
  + subtitle + Exit) where the cover/progress container would normally
  render — same height, no layout shift. Compact mode shows an amber
  pill in place of the progress bar. Header pill swaps Connected/Offline
  for "Maintenance" and the diagnostic CTA is suppressed (deliberate
  state, not involuntary offline). HMS / Queue / Firmware pills fall
  away naturally via the existing status?.connected gates.

  Mid-print entry (RUNNING / PAUSE) triggers a confirmation dialog —
  disconnecting MQTT mid-print stops progress tracking and completion
  notifications for the in-flight job. Idle / FINISH / FAILED skip the
  dialog and toggle directly.

  Scope: no backend change, no new permission, no behaviour change for
  any other consumer. PrinterCreate.is_active?: boolean added to the
  TypeScript surface so the field flows through api.updatePrinter.
This commit is contained in:
maziggy
2026-06-21 11:08:06 +02:00
parent 6b517ddc63
commit 36a16b8ae4
19 changed files with 468 additions and 62 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -26,8 +26,8 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-BsijWO1C.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-7mua31Zk.css">
<script type="module" crossorigin src="/assets/index-CShQfZib.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-aPHrdXng.css">
</head>
<body>
<div id="root"></div>