Sfoglia il codice sorgente

[Feature] Improve HMS error visibility on Printers page (#772)

  Add visual indicators so printers with HMS errors stand out in large
  print farms:

  - Red "Problem" counter in the status summary bar
  - Status pip turns red (fatal/serious) or amber (warning) for HMS errors
  - Progress bar turns amber when a print is paused
  - Status sort prioritizes printers with HMS errors at the top
maziggy 2 mesi fa
parent
commit
0db6541664

+ 1 - 0
CHANGELOG.md

@@ -38,6 +38,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Redesigned Bug Report Debug Log Flow** — Replaced the fixed 30-second debug log collection with an interactive 3-step flow: start debug logging, reproduce the issue at your own pace, then stop & submit. An elapsed timer shows recording duration with auto-stop at 5 minutes. Users now have full control over when to capture logs instead of racing a countdown. The backend splits log collection into separate start/stop endpoints, and the frontend shows a step progress indicator with pulsing active state.
 - **Redesigned Bug Report Debug Log Flow** — Replaced the fixed 30-second debug log collection with an interactive 3-step flow: start debug logging, reproduce the issue at your own pace, then stop & submit. An elapsed timer shows recording duration with auto-stop at 5 minutes. Users now have full control over when to capture logs instead of racing a countdown. The backend splits log collection into separate start/stop endpoints, and the frontend shows a step progress indicator with pulsing active state.
 
 
 ### Improved
 ### Improved
+- **HMS Error Visibility on Printers Page** ([#772](https://github.com/maziggy/bambuddy/issues/772)) — Improved visibility of printers with HMS errors for large print farms. Added a red "Problem" counter to the status summary bar showing how many connected printers have active HMS errors. The compact-mode status pip (colored dot) now turns red for fatal/serious errors (severity ≤ 2) or amber for common warnings, instead of only showing connection status. Progress bars turn amber when a print is paused. Sorting by status now places printers with HMS errors at the top, above printing and idle printers. Requested by @jimmy-brightz.
 - **Print Command Response Verification** ([#737](https://github.com/maziggy/bambuddy/issues/737)) — After sending a print command, BambuBuddy now monitors whether the printer's state changes within 15 seconds. If the printer silently ignores the command (observed on some P1S firmware versions where the MQTT command handler becomes unresponsive), a warning is logged for diagnostics. This aids debugging when users report prints not starting despite BambuBuddy showing success.
 - **Print Command Response Verification** ([#737](https://github.com/maziggy/bambuddy/issues/737)) — After sending a print command, BambuBuddy now monitors whether the printer's state changes within 15 seconds. If the printer silently ignores the command (observed on some P1S firmware versions where the MQTT command handler becomes unresponsive), a warning is logged for diagnostics. This aids debugging when users report prints not starting despite BambuBuddy showing success.
 - **Compact Assign Spool Modal** ([#725](https://github.com/maziggy/bambuddy/issues/725)) — The "Assign Spool" modal now uses a compact 3-column grid layout instead of a vertical list, showing more spools at once without scrolling. Each card displays the spool name, color, and remaining/total weight. The modal is wider with a taller scroll area. Requested by @RosdasHH.
 - **Compact Assign Spool Modal** ([#725](https://github.com/maziggy/bambuddy/issues/725)) — The "Assign Spool" modal now uses a compact 3-column grid layout instead of a vertical list, showing more spools at once without scrolling. Each card displays the spool name, color, and remaining/total weight. The modal is wider with a taller scroll area. Requested by @RosdasHH.
 - **Reformatted AMS Drying Presets Table** ([#732](https://github.com/maziggy/bambuddy/issues/732)) — The drying presets table in Settings now groups columns by AMS type (AMS 2 Pro, AMS-HT) with inline °C and h unit labels next to each input, replacing the previous flat column layout. Requested by @cadtoolbox.
 - **Reformatted AMS Drying Presets Table** ([#732](https://github.com/maziggy/bambuddy/issues/732)) — The drying presets table in Settings now groups columns by AMS type (AMS 2 Pro, AMS-HT) with inline °C and h unit labels next to each input, replacing the previous flat column layout. Requested by @cadtoolbox.

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

@@ -133,6 +133,7 @@ export default {
       printing: 'Druckt',
       printing: 'Druckt',
       paused: 'Pausiert',
       paused: 'Pausiert',
       offline: 'Offline',
       offline: 'Offline',
+      problem: 'Problem',
       error: 'Fehler',
       error: 'Fehler',
       finished: 'Fertig',
       finished: 'Fertig',
       unknown: 'Unbekannt',
       unknown: 'Unbekannt',

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

@@ -133,6 +133,7 @@ export default {
       printing: 'Printing',
       printing: 'Printing',
       paused: 'Paused',
       paused: 'Paused',
       offline: 'Offline',
       offline: 'Offline',
+      problem: 'Problem',
       error: 'Error',
       error: 'Error',
       finished: 'Finished',
       finished: 'Finished',
       unknown: 'Unknown',
       unknown: 'Unknown',

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

@@ -133,6 +133,7 @@ export default {
       printing: 'Impression en cours',
       printing: 'Impression en cours',
       paused: 'En pause',
       paused: 'En pause',
       offline: 'Hors ligne',
       offline: 'Hors ligne',
+      problem: 'Problème',
       error: 'Erreur',
       error: 'Erreur',
       finished: 'Terminé',
       finished: 'Terminé',
       unknown: 'Inconnu',
       unknown: 'Inconnu',

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

@@ -133,6 +133,7 @@ export default {
       printing: 'In stampa',
       printing: 'In stampa',
       paused: 'In pausa',
       paused: 'In pausa',
       offline: 'Offline',
       offline: 'Offline',
+      problem: 'Problema',
       error: 'Errore',
       error: 'Errore',
       finished: 'Finita',
       finished: 'Finita',
       unknown: 'Sconosciuto',
       unknown: 'Sconosciuto',

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

@@ -132,6 +132,7 @@ export default {
       printing: '印刷中',
       printing: '印刷中',
       paused: '一時停止',
       paused: '一時停止',
       offline: 'オフライン',
       offline: 'オフライン',
+      problem: '問題',
       error: 'エラー',
       error: 'エラー',
       finished: '完了',
       finished: '完了',
       unknown: '不明',
       unknown: '不明',

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

@@ -133,6 +133,7 @@ export default {
       printing: 'Imprimindo',
       printing: 'Imprimindo',
       paused: 'Pausado',
       paused: 'Pausado',
       offline: 'Offline',
       offline: 'Offline',
+      problem: 'Problema',
       error: 'Erro',
       error: 'Erro',
       finished: 'Concluído',
       finished: 'Concluído',
       unknown: 'Desconhecido',
       unknown: 'Desconhecido',

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

@@ -133,6 +133,7 @@ export default {
       printing: '打印中',
       printing: '打印中',
       paused: '已暂停',
       paused: '已暂停',
       offline: '离线',
       offline: '离线',
+      problem: '故障',
       error: '错误',
       error: '错误',
       finished: '已完成',
       finished: '已完成',
       unknown: '未知',
       unknown: '未知',

+ 60 - 28
frontend/src/pages/PrintersPage.tsx

@@ -52,7 +52,7 @@ import {
 import { useNavigate } from 'react-router-dom';
 import { useNavigate } from 'react-router-dom';
 import { api, discoveryApi, firmwareApi } from '../api/client';
 import { api, discoveryApi, firmwareApi } from '../api/client';
 import { formatDateOnly, formatETA, formatDuration, parseUTCDate } from '../utils/date';
 import { formatDateOnly, formatETA, formatDuration, parseUTCDate } from '../utils/date';
-import type { Printer, PrinterCreate, AMSUnit, DiscoveredPrinter, FirmwareUpdateInfo, FirmwareUploadStatus, LinkedSpoolInfo, SpoolAssignment } from '../api/client';
+import type { Printer, PrinterCreate, AMSUnit, DiscoveredPrinter, FirmwareUpdateInfo, FirmwareUploadStatus, LinkedSpoolInfo, SpoolAssignment, HMSError } from '../api/client';
 import { Card, CardContent } from '../components/Card';
 import { Card, CardContent } from '../components/Card';
 import { Button } from '../components/Button';
 import { Button } from '../components/Button';
 import { ConfirmModal } from '../components/ConfirmModal';
 import { ConfirmModal } from '../components/ConfirmModal';
@@ -1160,33 +1160,40 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) {
     let idle = 0;
     let idle = 0;
     let offline = 0;
     let offline = 0;
     let loading = 0;
     let loading = 0;
+    let problem = 0;
     let nextPrinterName: string | null = null;
     let nextPrinterName: string | null = null;
     let nextRemainingMin: number | null = null;
     let nextRemainingMin: number | null = null;
     let nextProgress: number = 0;
     let nextProgress: number = 0;
 
 
     printers?.forEach((printer) => {
     printers?.forEach((printer) => {
-      const status = queryClient.getQueryData<{ connected: boolean; state: string | null; remaining_time: number | null; progress: number | null }>(['printerStatus', printer.id]);
+      const status = queryClient.getQueryData<{ connected: boolean; state: string | null; remaining_time: number | null; progress: number | null; hms_errors?: HMSError[] }>(['printerStatus', printer.id]);
       if (status === undefined) {
       if (status === undefined) {
         // Status not yet loaded - don't count as offline yet
         // Status not yet loaded - don't count as offline yet
         loading++;
         loading++;
       } else if (!status.connected) {
       } else if (!status.connected) {
         offline++;
         offline++;
-      } else if (status.state === 'RUNNING') {
-        printing++;
-        if (status.remaining_time != null && status.remaining_time > 0) {
-          if (nextRemainingMin === null || status.remaining_time < nextRemainingMin) {
-            nextRemainingMin = status.remaining_time;
-            nextPrinterName = printer.name;
-            nextProgress = status.progress || 0;
+      } else {
+        // Count printers with HMS errors
+        if (status.hms_errors && filterKnownHMSErrors(status.hms_errors).length > 0) {
+          problem++;
+        }
+        if (status.state === 'RUNNING') {
+          printing++;
+          if (status.remaining_time != null && status.remaining_time > 0) {
+            if (nextRemainingMin === null || status.remaining_time < nextRemainingMin) {
+              nextRemainingMin = status.remaining_time;
+              nextPrinterName = printer.name;
+              nextProgress = status.progress || 0;
+            }
           }
           }
+        } else {
+          idle++;
         }
         }
-      } else {
-        idle++;
       }
       }
     });
     });
 
 
     return {
     return {
-      counts: { printing, idle, offline, loading, total: (printers?.length || 0) },
+      counts: { printing, idle, offline, loading, problem, total: (printers?.length || 0) },
       nextFinish: nextPrinterName && nextRemainingMin ? { name: nextPrinterName, remainingMin: nextRemainingMin, progress: nextProgress } : null,
       nextFinish: nextPrinterName && nextRemainingMin ? { name: nextPrinterName, remainingMin: nextRemainingMin, progress: nextProgress } : null,
     };
     };
     // eslint-disable-next-line react-hooks/exhaustive-deps
     // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -1218,6 +1225,14 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) {
           </span>
           </span>
         </div>
         </div>
       )}
       )}
+      {counts.problem > 0 && (
+        <div className="flex items-center gap-1.5">
+          <div className="w-2 h-2 rounded-full bg-status-error" />
+          <span className="text-bambu-gray">
+            <span className="text-white font-medium">{counts.problem}</span> {t('printers.status.problem').toLowerCase()}
+          </span>
+        </div>
+      )}
       {nextFinish && (
       {nextFinish && (
         <>
         <>
           <div className="w-px h-4 bg-bambu-dark-tertiary" />
           <div className="w-px h-4 bg-bambu-dark-tertiary" />
@@ -2415,14 +2430,29 @@ function PrinterCard({
                 <div className="flex items-center gap-2">
                 <div className="flex items-center gap-2">
                   <h3 className={`font-semibold text-white ${getTitleSize()}`}>{printer.name}</h3>
                   <h3 className={`font-semibold text-white ${getTitleSize()}`}>{printer.name}</h3>
                   {/* Connection indicator dot for compact mode */}
                   {/* Connection indicator dot for compact mode */}
-                  {viewMode === 'compact' && (
-                    <div
-                      className={`w-2 h-2 rounded-full flex-shrink-0 ${
-                        status?.connected ? 'bg-status-ok' : 'bg-status-error'
-                      }`}
-                      title={status?.connected ? t('printers.connection.connected') : t('printers.connection.offline')}
-                    />
-                  )}
+                  {viewMode === 'compact' && (() => {
+                    const hmsErrors = status?.connected && status.hms_errors ? filterKnownHMSErrors(status.hms_errors) : [];
+                    const hasSevere = hmsErrors.some(e => e.severity <= 2);
+                    const hasWarning = hmsErrors.length > 0;
+                    const pipColor = !status?.connected
+                      ? 'bg-status-error'
+                      : hasSevere
+                        ? 'bg-status-error'
+                        : hasWarning
+                          ? 'bg-status-warning'
+                          : 'bg-status-ok';
+                    const pipTitle = !status?.connected
+                      ? t('printers.connection.offline')
+                      : hasWarning
+                        ? `${hmsErrors.length} HMS ${hmsErrors.length === 1 ? 'error' : 'errors'}`
+                        : t('printers.connection.connected');
+                    return (
+                      <div
+                        className={`w-2 h-2 rounded-full flex-shrink-0 ${pipColor}`}
+                        title={pipTitle}
+                      />
+                    );
+                  })()}
                 </div>
                 </div>
                 <p className="text-sm text-bambu-gray">
                 <p className="text-sm text-bambu-gray">
                   {printer.model || 'Unknown Model'}
                   {printer.model || 'Unknown Model'}
@@ -2720,7 +2750,7 @@ function PrinterCard({
                   <div className="flex items-center gap-2">
                   <div className="flex items-center gap-2">
                     <div className="flex-1 bg-bambu-dark-tertiary rounded-full h-1.5">
                     <div className="flex-1 bg-bambu-dark-tertiary rounded-full h-1.5">
                       <div
                       <div
-                        className="bg-bambu-green h-1.5 rounded-full transition-all"
+                        className={`${status.state === 'PAUSE' ? 'bg-status-warning' : 'bg-bambu-green'} h-1.5 rounded-full transition-all`}
                         style={{ width: `${status.progress || 0}%` }}
                         style={{ width: `${status.progress || 0}%` }}
                       />
                       />
                     </div>
                     </div>
@@ -2779,7 +2809,7 @@ function PrinterCard({
                           <div className="flex items-center justify-between text-sm">
                           <div className="flex items-center justify-between text-sm">
                             <div className="flex-1 bg-bambu-dark-tertiary rounded-full h-2 mr-3">
                             <div className="flex-1 bg-bambu-dark-tertiary rounded-full h-2 mr-3">
                               <div
                               <div
-                                className="bg-bambu-green h-2 rounded-full transition-all"
+                                className={`${status.state === 'PAUSE' ? 'bg-status-warning' : 'bg-bambu-green'} h-2 rounded-full transition-all`}
                                 style={{ width: `${status.progress || 0}%` }}
                                 style={{ width: `${status.progress || 0}%` }}
                               />
                               />
                             </div>
                             </div>
@@ -5963,15 +5993,17 @@ export function PrintersPage() {
         });
         });
         break;
         break;
       case 'status':
       case 'status':
-        // Sort by status: printing > idle > offline
+        // Sort by status: HMS errors > printing > idle > offline
         sorted.sort((a, b) => {
         sorted.sort((a, b) => {
-          const statusA = queryClient.getQueryData<{ connected: boolean; state: string | null }>(['printerStatus', a.id]);
-          const statusB = queryClient.getQueryData<{ connected: boolean; state: string | null }>(['printerStatus', b.id]);
+          const statusA = queryClient.getQueryData<{ connected: boolean; state: string | null; hms_errors?: HMSError[] }>(['printerStatus', a.id]);
+          const statusB = queryClient.getQueryData<{ connected: boolean; state: string | null; hms_errors?: HMSError[] }>(['printerStatus', b.id]);
 
 
           const getPriority = (s: typeof statusA) => {
           const getPriority = (s: typeof statusA) => {
-            if (!s?.connected) return 2; // offline
-            if (s.state === 'RUNNING') return 0; // printing
-            return 1; // idle
+            if (!s?.connected) return 3; // offline
+            const hmsErrors = s.hms_errors ? filterKnownHMSErrors(s.hms_errors) : [];
+            if (hmsErrors.length > 0) return 0; // HMS errors - top priority
+            if (s.state === 'RUNNING') return 1; // printing
+            return 2; // idle
           };
           };
 
 
           return getPriority(statusA) - getPriority(statusB);
           return getPriority(statusA) - getPriority(statusB);

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


+ 1 - 1
static/index.html

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

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