Bladeren bron

feat(spoolbuddy-settings): show CPU load on the device card

  The SpoolBuddy daemon already reports load_avg (1/5/15 min) and
  cpu_count in its heartbeat, but the Settings -> SpoolBuddy card only
  showed CPU temp / memory / disk / system uptime. Adds a fifth tile
  rendering the 1-min load with core count and percent-of-cores --
  e.g. "1.20 / 4 (30%)" -- next to the existing CPU temp tile. Falls
  back to a bare load number when cpu_count is missing and hides the
  tile when the daemon doesn't emit load_avg.

  settings.spoolbuddy.cpuLoad translated across all 9 locales.
maziggy 5 dagen geleden
bovenliggende
commit
ebba1385d3

+ 1 - 0
CHANGELOG.md

@@ -13,6 +13,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Connection Diagnostic — self-service triage for "printer won't connect / won't print"** — A triage review of recently-closed issues found roughly a third were user-side setup errors (printer not in LAN developer mode, blocked ports, Docker bridge networking, wrong access code, printer on a different subnet), each costing a multi-round-trip "enable debug logging → build a support bundle → upload it" exchange. A new diagnostic (`backend/app/services/printer_diagnostic.py`) runs those checks automatically: TCP reachability of MQTT 8883 / FTPS 990 / RTSPS 322, LAN developer mode, Docker network mode, printer/host subnet match, and MQTT credential class — each returning a pass / fail / warn / skip status with a localized plain-language fix. Exposed via `GET /printers/{id}/diagnostic` (saved printer) and `POST /printers/diagnostic` (pre-save Add-Printer flow), and surfaced as a one-click "Run diagnostic" from the printer card actions menu (plus a quick button on the card when a printer is offline), the Add-Printer dialog, and a new Connection Diagnostic section on the System page. The in-app bug reporter scans configured printers when the report form opens and always shows the result — a healthy confirmation when nothing's wrong, or the detected problem and its fix inline — so setup mistakes get self-resolved instead of becoming GitHub issues. The GitHub `config.yml` troubleshooting link was repointed from the wiki source repo to the rendered troubleshooting page. Backend service unit tests (15) and frontend modal tests (3) added; all diagnostic strings translated across the 8 locales. Backend ruff clean, frontend build clean, i18n parity green.
 
 ### Changed
+- **Settings → SpoolBuddy: CPU load tile added to the device card** — The SpoolBuddy daemon's heartbeat already reports `load_avg` (1/5/15 min) and `cpu_count` via `system_stats` (see `spoolbuddy/daemon/system_stats.py`), but the device card on the Bambuddy SpoolBuddy settings only rendered CPU temp / memory / disk / system uptime. Adds a fifth tile next to CPU temp showing the 1-minute load average alongside core count and a percent-of-cores readout — for a 4-core Pi: `1.20 / 4 (30%)`. Falls back to a bare load number when `cpu_count` isn't reported, and the tile is hidden entirely when the daemon doesn't emit `load_avg` (older builds). Useful for spotting the "I2C/SPI stuck after idle overnight" pattern early — sustained high load before the bus dies points at runaway daemon work rather than a kernel hang. Translated across all 9 locales (de/es/fr/it/ja/pt-BR/zh-CN/zh-TW). Frontend build clean, i18n parity green.
 - **Virtual printer: setup diagnostic + one-click slicer-certificate export** — Two recurring virtual-printer support pains, addressed on the Virtual Printers settings page. **(1) Setup check** — a new stethoscope action on each VP card runs `GET /virtual-printers/{id}/diagnostic` and shows a pass/fail/warn/skip checklist: VP enabled, services running, bind interface still exists, access code set, target printer (proxy mode), and — decisively — a live TCP probe of the FTP/MQTT/discovery ports on the bind IP. The manager swallows per-service start errors (`run_with_logging`), so a service object can exist while nothing is actually listening; probing the bind IP from outside is the only reliable signal, and it catches the common "VP doesn't show up in the slicer" bind-IP-conflict and stale-interface cases. New `backend/app/services/virtual_printer/diagnostic.py` + `VPDiagnosticResult` schema + `VirtualPrinterDiagnosticModal.tsx`. **(2) Slicer certificate** — virtual printers present a TLS cert signed by a shared CA the slicer must trust; until now users had to `docker exec` in and `cat bbl_ca.crt` to get it. A new "Slicer certificate" row on the Virtual Printers settings card (alongside the Archive name source toggle) offers Copy and Download (`bambuddy-virtual-printer-ca.crt`) plus the CA's SHA-256 fingerprint, served by `GET /virtual-printers/ca-certificate` — only the public certificate, never the CA private key. The CA is generated on demand so the button works before the first VP is enabled. Copy uses a non-secure-context fallback (Bambuddy is usually on plain-HTTP LAN), extracted into a shared `utils/clipboard.ts`. 9 backend diagnostic/CA unit tests + 4 route integration tests + 6 frontend tests (diagnostic modal, clipboard helpers); all `vpDiagnostic.*` / `virtualPrinter.caCert.*` strings translated across the 9 locales. Backend ruff clean, frontend build clean, i18n parity green.
 - **Bug-report panel: connection diagnostic no longer overflows on multi-printer setups** — The "Report a Bug" panel scans every configured printer on open and surfaces connection problems inline so users can self-fix before filing. The first cut rendered a full ~6-row checklist for *each* problem printer stacked vertically; a user with many printers all reporting issues pushed the description box, screenshot uploader and Submit button far below the fold in the `max-w-md` / `max-h-[80vh]` panel. The diagnostic section is now a compact summary — one line ("N of M printers have connection issues") followed by the affected printers as collapsed rows (healthy printers count toward M but render no detail). Each row expands on demand to that printer's full checklist via the shared `Collapsible` widget; when exactly one printer has problems the row is auto-expanded since that's the case where inline detail is wanted with no extra click. The panel now stays a fixed ~3 lines plus one row per affected printer regardless of fleet size, keeping the report form reachable. Healthy-fleet confirmation line is unchanged. New `bugReport.diagnosticSummary` key (with `{{problems}}`/`{{total}}`) replaces the static `diagnosticHeading`; `diagnosticIntro` reworded to be printer-count-neutral and point at the expand affordance — both translated across all 9 locales. 2 new tests in `BugReportBubble.test.tsx` (multiple problems stay collapsed and expand on click; a single problem auto-expands); 11 tests green; frontend build clean; i18n parity holds at 4903 keys × 9 locales.
 - **Color Catalog sync now identifies itself as Bambuddy to filamentcolors.xyz** — The FilamentColors.xyz sync client in `inventory.py` created its `httpx.AsyncClient` with no `User-Agent`, so it leaked httpx's default `python-httpx/x.y` string — the only outbound client that did (`bambu_cloud`, `makerworld`, `firmware_check` all send the honest `Bambuddy/1.0 (+https://github.com/maziggy/bambuddy)`). It now sends the same honest UA, consistent with the rest of the codebase. Surfaced while investigating #1456 (a Cloudflare `403` on the sync that turned out to be the reporter's network/IP reputation, not Bambuddy — the UA leak was a separate inconsistency found in passing, and this change does not by itself resolve a Cloudflare IP block).

+ 15 - 0
frontend/src/components/SpoolBuddySettings.tsx

@@ -4,6 +4,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
 import {
   Loader2,
   Trash2,
+  Activity,
   Cpu,
   HardDrive,
   Thermometer,
@@ -218,6 +219,20 @@ function DeviceCard({ device, onUnregister, isDeleting }: DeviceCardProps) {
                   </div>
                 </div>
               )}
+              {stats.load_avg && stats.load_avg.length > 0 && (
+                <div className="flex items-center gap-2">
+                  <Activity className="w-3.5 h-3.5 text-bambu-gray" />
+                  <div>
+                    <div className="text-bambu-gray">{t('settings.spoolbuddy.cpuLoad')}</div>
+                    <div className="text-white">
+                      {stats.load_avg[0].toFixed(2)}
+                      {stats.cpu_count
+                        ? ` / ${stats.cpu_count} (${Math.round((stats.load_avg[0] / stats.cpu_count) * 100)}%)`
+                        : ''}
+                    </div>
+                  </div>
+                </div>
+              )}
               {mem && mem.percent !== undefined && (
                 <div className="flex items-center gap-2">
                   <Cpu className="w-3.5 h-3.5 text-bambu-gray" />

+ 1 - 0
frontend/src/i18n/locales/de.ts

@@ -1452,6 +1452,7 @@ export default {
       nfc: 'NFC',
       scale: 'Waage',
       cpuTemp: 'CPU-Temp.',
+      cpuLoad: 'CPU-Last',
       memory: 'Speicher',
       disk: 'Festplatte',
       update: 'Aktualisieren',

+ 1 - 0
frontend/src/i18n/locales/en.ts

@@ -1453,6 +1453,7 @@ export default {
       nfc: 'NFC',
       scale: 'Scale',
       cpuTemp: 'CPU temp',
+      cpuLoad: 'CPU load',
       memory: 'Memory',
       disk: 'Disk',
       // Device actions

+ 1 - 0
frontend/src/i18n/locales/es.ts

@@ -1453,6 +1453,7 @@ export default {
       nfc: 'NFC',
       scale: 'Báscula',
       cpuTemp: 'Temp. de CPU',
+      cpuLoad: 'Carga de CPU',
       memory: 'Memoria',
       disk: 'Disco',
       // Device actions

+ 1 - 0
frontend/src/i18n/locales/fr.ts

@@ -2336,6 +2336,7 @@ export default {
       nfc: 'NFC',
       scale: 'Balance',
       cpuTemp: 'Temp. CPU',
+      cpuLoad: 'Charge CPU',
       memory: 'Mémoire',
       disk: 'Disque',
       update: 'Mettre à jour',

+ 1 - 0
frontend/src/i18n/locales/it.ts

@@ -2335,6 +2335,7 @@ export default {
       nfc: 'NFC',
       scale: 'Bilancia',
       cpuTemp: 'Temp. CPU',
+      cpuLoad: 'Carico CPU',
       memory: 'Memoria',
       disk: 'Disco',
       update: 'Aggiorna',

+ 1 - 0
frontend/src/i18n/locales/ja.ts

@@ -1451,6 +1451,7 @@ export default {
       nfc: 'NFC',
       scale: '計量器',
       cpuTemp: 'CPU 温度',
+      cpuLoad: 'CPU 負荷',
       memory: 'メモリ',
       disk: 'ディスク',
       update: 'アップデート',

+ 1 - 0
frontend/src/i18n/locales/pt-BR.ts

@@ -2335,6 +2335,7 @@ export default {
       nfc: 'NFC',
       scale: 'Balança',
       cpuTemp: 'Temp. CPU',
+      cpuLoad: 'Carga da CPU',
       memory: 'Memória',
       disk: 'Disco',
       update: 'Atualizar',

+ 1 - 0
frontend/src/i18n/locales/zh-CN.ts

@@ -1452,6 +1452,7 @@ export default {
       nfc: 'NFC',
       scale: '秤',
       cpuTemp: 'CPU 温度',
+      cpuLoad: 'CPU 负载',
       memory: '内存',
       disk: '磁盘',
       // Device actions

+ 1 - 0
frontend/src/i18n/locales/zh-TW.ts

@@ -1452,6 +1452,7 @@ export default {
       nfc: 'NFC',
       scale: '磅秤',
       cpuTemp: 'CPU 溫度',
+      cpuLoad: 'CPU 負載',
       memory: '記憶體',
       disk: '磁碟',
       // Device actions

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-Bn4REDBt.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-vzamAN0i.js"></script>
+    <script type="module" crossorigin src="/assets/index-Bn4REDBt.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BzucE4G0.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff