ソースを参照

Add toggle to disable printer firmware update checks

Add new setting "Check printer firmware" in Settings → General → Updates
that allows users to disable automatic firmware update checks from
Bambu Lab servers.

Closes #169
maziggy 3 ヶ月 前
コミット
a367ddc66e

+ 3 - 0
CHANGELOG.md

@@ -5,6 +5,9 @@ All notable changes to Bambuddy will be documented in this file.
 ## [0.1.6-final] - Not released
 
 ### New Features
+- **Disable Printer Firmware Checks** - New toggle in Settings → General → Updates to disable printer firmware update checks:
+  - Prevents Bambuddy from checking Bambu Lab servers for firmware updates
+  - Useful for users who prefer to manage firmware manually or have network restrictions
 - **Archive Plate Browsing** - Browse plate thumbnails directly in archive cards (Issue #166):
   - Hover over archive card to reveal plate navigation for multi-plate files
   - Left/right arrows to cycle through plate thumbnails

+ 1 - 0
backend/app/api/routes/settings.py

@@ -74,6 +74,7 @@ async def get_settings(db: AsyncSession = Depends(get_db)):
                 "capture_finish_photo",
                 "spoolman_enabled",
                 "check_updates",
+                "check_printer_firmware",
                 "virtual_printer_enabled",
                 "ftp_retry_enabled",
                 "mqtt_enabled",

+ 2 - 0
backend/app/schemas/settings.py

@@ -26,6 +26,7 @@ class AppSettings(BaseModel):
 
     # Updates
     check_updates: bool = Field(default=True, description="Automatically check for updates on startup")
+    check_printer_firmware: bool = Field(default=True, description="Check for printer firmware updates from Bambu Lab")
 
     # Language
     notification_language: str = Field(default="en", description="Language for push notifications (en, de)")
@@ -134,6 +135,7 @@ class AppSettingsUpdate(BaseModel):
     spoolman_url: str | None = None
     spoolman_sync_mode: str | None = None
     check_updates: bool | None = None
+    check_printer_firmware: bool | None = None
     notification_language: str | None = None
     ams_humidity_good: int | None = None
     ams_humidity_fair: int | None = None

+ 22 - 0
backend/tests/integration/test_settings_api.py

@@ -215,6 +215,28 @@ class TestSettingsAPI:
         assert result["currency"] == "JPY"
         assert result["check_updates"] is False
 
+    @pytest.mark.asyncio
+    @pytest.mark.integration
+    async def test_update_check_printer_firmware(self, async_client: AsyncClient):
+        """Verify check_printer_firmware can be updated."""
+        # Default should be True
+        response = await async_client.get("/api/v1/settings/")
+        assert response.json()["check_printer_firmware"] is True
+
+        # Update to False
+        response = await async_client.put("/api/v1/settings/", json={"check_printer_firmware": False})
+        assert response.status_code == 200
+        assert response.json()["check_printer_firmware"] is False
+
+        # Verify persistence
+        response = await async_client.get("/api/v1/settings/")
+        assert response.json()["check_printer_firmware"] is False
+
+        # Update back to True
+        response = await async_client.put("/api/v1/settings/", json={"check_printer_firmware": True})
+        assert response.status_code == 200
+        assert response.json()["check_printer_firmware"] is True
+
     # ========================================================================
     # MQTT settings tests
     # ========================================================================

+ 1 - 0
frontend/src/__tests__/components/Layout.test.tsx

@@ -29,6 +29,7 @@ describe('Layout', () => {
       http.get('/api/v1/settings/', () => {
         return HttpResponse.json({
           check_updates: false,
+          check_printer_firmware: false,
           auto_archive: true,
         });
       }),

+ 1 - 0
frontend/src/__tests__/pages/FileManagerPage.test.tsx

@@ -105,6 +105,7 @@ describe('FileManagerPage', () => {
       http.get('/api/v1/settings/', () => {
         return HttpResponse.json({
           check_updates: false,
+          check_printer_firmware: false,
           library_disk_warning_gb: 5,
         });
       }),

+ 11 - 0
frontend/src/__tests__/pages/SettingsPage.test.tsx

@@ -31,6 +31,7 @@ const mockSettings = {
   ha_url: '',
   ha_token: '',
   check_updates: false,
+  check_printer_firmware: false,
 };
 
 describe('SettingsPage', () => {
@@ -124,6 +125,16 @@ describe('SettingsPage', () => {
         expect(screen.getByText('Appearance')).toBeInTheDocument();
       });
     });
+
+    it('shows updates section with firmware toggle', async () => {
+      render(<SettingsPage />);
+
+      await waitFor(() => {
+        expect(screen.getByText('Updates')).toBeInTheDocument();
+        expect(screen.getByText('Check for updates')).toBeInTheDocument();
+        expect(screen.getByText('Check printer firmware')).toBeInTheDocument();
+      });
+    });
   });
 
   describe('tabs navigation', () => {

+ 1 - 0
frontend/src/__tests__/pages/StatsPage.test.tsx

@@ -48,6 +48,7 @@ const mockArchives = [
 const mockSettings = {
   currency: '$',
   check_updates: false,
+  check_printer_firmware: false,
 };
 
 const mockFailureAnalysis = {

+ 1 - 0
frontend/src/api/client.ts

@@ -681,6 +681,7 @@ export interface AppSettings {
   energy_cost_per_kwh: number;
   energy_tracking_mode: 'print' | 'total';
   check_updates: boolean;
+  check_printer_firmware: boolean;
   notification_language: string;
   // AMS threshold settings
   ams_humidity_good: number;  // <= this is green

+ 6 - 1
frontend/src/pages/PrintersPage.tsx

@@ -915,6 +915,7 @@ function PrinterCard({
   timeFormat = 'system',
   cameraViewMode = 'window',
   onOpenEmbeddedCamera,
+  checkPrinterFirmware = true,
 }: {
   printer: Printer;
   hideIfDisconnected?: boolean;
@@ -932,6 +933,7 @@ function PrinterCard({
   timeFormat?: 'system' | '12h' | '24h';
   cameraViewMode?: 'window' | 'embedded';
   onOpenEmbeddedCamera?: (printerId: number, printerName: string) => void;
+  checkPrinterFirmware?: boolean;
 }) {
   const queryClient = useQueryClient();
   const navigate = useNavigate();
@@ -992,12 +994,13 @@ function PrinterCard({
     refetchInterval: 30000, // Fallback polling, WebSocket handles real-time
   });
 
-  // Check for firmware updates (cached for 5 minutes)
+  // Check for firmware updates (cached for 5 minutes, can be disabled in settings)
   const { data: firmwareInfo } = useQuery({
     queryKey: ['firmwareUpdate', printer.id],
     queryFn: () => firmwareApi.checkPrinterUpdate(printer.id),
     staleTime: 5 * 60 * 1000,
     refetchInterval: 5 * 60 * 1000,
+    enabled: checkPrinterFirmware,
   });
 
   // Collect unique tray_info_idx values for cloud filament info lookup
@@ -4648,6 +4651,7 @@ export function PrintersPage() {
                     timeFormat={settings?.time_format || 'system'}
                     cameraViewMode={settings?.camera_view_mode || 'window'}
                     onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinters(prev => new Map(prev).set(id, { id, name }))}
+                    checkPrinterFirmware={settings?.check_printer_firmware !== false}
                   />
                 ))}
               </div>
@@ -4676,6 +4680,7 @@ export function PrintersPage() {
               timeFormat={settings?.time_format || 'system'}
               cameraViewMode={settings?.camera_view_mode || 'window'}
               onOpenEmbeddedCamera={(id, name) => setEmbeddedCameraPrinters(prev => new Map(prev).set(id, { id, name }))}
+              checkPrinterFirmware={settings?.check_printer_firmware !== false}
             />
           ))}
         </div>

+ 19 - 0
frontend/src/pages/SettingsPage.tsx

@@ -407,6 +407,7 @@ export function SettingsPage() {
       settings.energy_cost_per_kwh !== localSettings.energy_cost_per_kwh ||
       settings.energy_tracking_mode !== localSettings.energy_tracking_mode ||
       settings.check_updates !== localSettings.check_updates ||
+      (settings.check_printer_firmware ?? true) !== (localSettings.check_printer_firmware ?? true) ||
       settings.notification_language !== localSettings.notification_language ||
       settings.ams_humidity_good !== localSettings.ams_humidity_good ||
       settings.ams_humidity_fair !== localSettings.ams_humidity_fair ||
@@ -469,6 +470,7 @@ export function SettingsPage() {
         energy_cost_per_kwh: localSettings.energy_cost_per_kwh,
         energy_tracking_mode: localSettings.energy_tracking_mode,
         check_updates: localSettings.check_updates,
+        check_printer_firmware: localSettings.check_printer_firmware,
         notification_language: localSettings.notification_language,
         ams_humidity_good: localSettings.ams_humidity_good,
         ams_humidity_fair: localSettings.ams_humidity_fair,
@@ -1296,6 +1298,23 @@ export function SettingsPage() {
                   <div className="w-11 h-6 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-bambu-green"></div>
                 </label>
               </div>
+              <div className="flex items-center justify-between">
+                <div>
+                  <p className="text-white">Check printer firmware</p>
+                  <p className="text-sm text-bambu-gray">
+                    Check for printer firmware updates from Bambu Lab
+                  </p>
+                </div>
+                <label className="relative inline-flex items-center cursor-pointer">
+                  <input
+                    type="checkbox"
+                    checked={localSettings.check_printer_firmware ?? true}
+                    onChange={(e) => updateSetting('check_printer_firmware', e.target.checked)}
+                    className="sr-only peer"
+                  />
+                  <div className="w-11 h-6 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-bambu-green"></div>
+                </label>
+              </div>
               <div className="border-t border-bambu-dark-tertiary pt-4">
                 <div className="flex items-center justify-between mb-2">
                   <div>

ファイルの差分が大きいため隠しています
+ 0 - 0
static/assets/index-CKmo1TxA.js


+ 1 - 1
static/index.html

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

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません