Kaynağa Gözat

fix(settings): remove the "Save thumbnails" and "Notification Language" controls, which nothing read (the API keeps both fields); a SQLite-to-PostgreSQL restore fills empty required timestamps in UTC instead of the host's local time

maziggy 2 gün önce
ebeveyn
işleme
a10d5430de

+ 2 - 0
CHANGELOG.md

@@ -164,6 +164,8 @@ All notable changes to Bambuddy will be documented in this file.
 - **The frontend build no longer warns about `path` and `crypto` being externalized for the STEP previewer (#2976)** — `occt-import-js`, the Emscripten build behind STEP previews, requires both modules, but only inside its `ENVIRONMENT_IS_NODE` branches; in the browser it loads its `.wasm` from the URL the preview worker passes and draws randomness from `crypto.getRandomValues`. Vite still externalized both and printed two warnings on every build. `vite.config.ts` now drops exactly those two warnings for that one package through `build.rolldownOptions.onLog`, so an externalization anywhere else, or of any other module, still shows.
 
 ### Fixed
+- **Settings: removed "Save thumbnails" and "Notification Language", which did nothing** — Nothing read either: thumbnails were always stored, and notifications always used their template text as written. The API still accepts both fields.
+- **Restoring an old SQLite backup onto PostgreSQL could put some timestamps hours off** — Required timestamps the backup left empty were filled with the host's local time, while every other timestamp is stored in UTC, so on a host ahead of UTC those rows appeared hours in the future. They are now filled in UTC.
 - **Maintenance: a printer's time offline was counted as print time** — A printer that dropped off mid-print and came back printing hours later had the whole gap added to its runtime, so hours-based maintenance came due early. Time while Bambuddy itself was restarting is still counted, as before.
 - **Maintenance: duplicate maintenance items** — A printer's items are created when its maintenance is first shown, and two pages (or a page and the background check) could each create the same one; marking it performed could then update the copy not shown, and assigning that type again failed. Existing duplicates are merged on upgrade (the most recently performed one is kept, or the one the page showed when none was, with the history of all), and they can no longer be created.
 - **Maintenance API: "performed" answered in hours for an item set to days** — Its response used the maintenance type's interval type instead of the item's own setting, which the list already used.

+ 4 - 2
backend/app/api/routes/settings.py

@@ -1333,7 +1333,7 @@ async def _import_sqlite_to_postgres(sqlite_path: Path):
 
                 # Identify columns that need type conversion (SQLite stores booleans
                 # as int and datetimes as str — asyncpg requires native Python types)
-                from datetime import datetime as dt
+                from datetime import datetime as dt, timezone
 
                 bool_columns = set()
                 datetime_columns = set()
@@ -1365,7 +1365,9 @@ async def _import_sqlite_to_postgres(sqlite_path: Path):
                                     if sd is not None:
                                         not_null_defaults[col.name] = sd
 
-                now = dt.now()
+                # Naive UTC, as every stored timestamp is: the host's local
+                # time put backfilled rows hours off (DB-C4).
+                now = dt.now(timezone.utc).replace(tzinfo=None)
                 value_fixes = _restored_value_fixes(table_name)
 
                 def _convert_row(

+ 37 - 0
backend/tests/unit/test_restore_schema_compat.py

@@ -304,3 +304,40 @@ def test_the_manifest_is_read(tmp_path):
     (tmp_path / "manifest.json").write_text('{"format": 1, "app_version": "1.2.7"}')
 
     assert _read_backup_manifest(tmp_path)["app_version"] == "1.2.7"
+
+
+@pytest.mark.asyncio
+async def test_an_empty_required_timestamp_is_filled_with_utc_now(tmp_path):
+    """DB-C4: every stored timestamp is naive UTC, but an empty required one
+    in an old backup was filled with the host's local time -- on a Berlin host
+    1-2 h in the future. The host here is 14 h ahead of UTC."""
+    import os
+    import time
+    from datetime import datetime, timezone
+
+    path = _source(
+        tmp_path,
+        "CREATE TABLE cost_centers (id INTEGER PRIMARY KEY, code TEXT, name TEXT, created_at TEXT, updated_at TEXT)",
+        ["INSERT INTO cost_centers (id, code, name, created_at, updated_at) VALUES (1, 'abc', 'Lab', NULL, NULL)"],
+    )
+    from backend.app.api.routes import settings as settings_module
+
+    before = os.environ.get("TZ")
+    os.environ["TZ"] = "Pacific/Kiritimati"
+    time.tzset()
+    try:
+        engine, executed = _mock_engine()
+        with patch("backend.app.core.database._create_engine", new=MagicMock(return_value=engine)):
+            await settings_module._import_sqlite_to_postgres(path)
+    finally:
+        # Back to the zone the process had, whatever it was.
+        if before is None:
+            os.environ.pop("TZ", None)
+        else:
+            os.environ["TZ"] = before
+        time.tzset()
+
+    params = next(p for sql, p in executed if sql.startswith("INSERT INTO cost_centers"))[0]
+    utc_now = datetime.now(timezone.utc).replace(tzinfo=None)
+    assert params["created_at"].tzinfo is None
+    assert abs((params["created_at"] - utc_now).total_seconds()) < 120

+ 34 - 0
frontend/src/__tests__/pages/SettingsPageUnsavedFields.test.tsx

@@ -123,3 +123,37 @@ describe('SettingsPage printer temperature history retention', () => {
     });
   });
 });
+
+/**
+ * settings-2 / settings-3: "Save thumbnails" and "Notification Language" were
+ * saved but nothing read them -- thumbnails were always stored, notifications
+ * always used their template text. The controls are gone; the API keeps the
+ * fields for scripts that send them.
+ */
+describe('SettingsPage controls that did nothing', () => {
+  beforeEach(() => {
+    window.history.replaceState({}, '', '/');
+    vi.mocked(localStorage.getItem).mockReset();
+    localStorage.clear();
+    setAuthToken(null);
+    server.use(
+      http.get('/api/v1/settings/', () => HttpResponse.json(settings)),
+      http.get('/api/v1/auth/status', () => HttpResponse.json({ auth_enabled: false, requires_setup: false })),
+    );
+  });
+
+  it('has no "Save thumbnails" toggle', async () => {
+    render(<SettingsPage />);
+    expect(await screen.findByText('Capture finish photo')).toBeInTheDocument();
+    expect(screen.queryByText('Save thumbnails')).not.toBeInTheDocument();
+  });
+
+  it('has no notification language picker', async () => {
+    const user = userEvent.setup();
+    render(<SettingsPage />);
+    await screen.findByText('Date Format');
+    await user.click(screen.getAllByText('Notifications')[0]);
+    expect(await screen.findByRole('button', { name: /^Add$/ }, { timeout: 3000 })).toBeInTheDocument();
+    expect(screen.queryByText('Notification Language')).not.toBeInTheDocument();
+  });
+});

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

@@ -2185,8 +2185,6 @@ export default {
     upToDate: 'Sie sind auf dem neuesten Stand',
     updateAvailable: 'Update verfügbar',
     // Notifications
-    notificationLanguage: 'Benachrichtigungssprache',
-    notificationLanguageDescription: 'Sprache für Push-Benachrichtigungen',
     bedCooledThreshold: 'Bett-Abkühlung Schwellenwert',
     bedCooledThresholdDescription: 'Temperatur, unter der das Bett nach einem Druck als abgekühlt gilt',
     userNotificationsEnabled: 'Benutzerbenachrichtigungen',
@@ -2313,7 +2311,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Seitenleisten-Reihenfolge',
     // Camera
-    saveThumbnails: 'Vorschaubilder speichern',
     captureFinishPhoto: 'Abschlussfoto aufnehmen',
     noPrintersConfigured: 'Keine Drucker konfiguriert',
     // Updates
@@ -2857,7 +2854,6 @@ export default {
     // Archive
     autoArchivePrints: 'Drucke automatisch archivieren',
     autoArchiveDescription: '3MF-Dateien automatisch speichern, wenn Drucke abgeschlossen sind. Aus: Kein Drucker archiviert Drucke, die außerhalb von Bambuddy gestartet wurden.',
-    saveThumbnailsDescription: 'Vorschaubilder aus 3MF-Dateien extrahieren und speichern',
     captureFinishPhotoDescription: 'Foto von der Druckerkamera aufnehmen, wenn der Druck abgeschlossen ist. Bambuddy zeichnet während des Drucks einen kurzen Zeitraffer auf, damit das Foto aus dem Moment vor dem Absenken der Druckplatte stammen kann. Die Zeitraffer-Datei bleibt erhalten, wenn du den Zeitraffer für diesen Druck aktiviert hast, andernfalls wird sie nach Aufnahme des Fotos automatisch gelöscht.',
     finishPhotoRestorePlate: 'Druckplatte für Abschlussfoto anheben',
     finishPhotoRestorePlateDescription: 'Der Drucker senkt die Druckplatte am Druckende um etwa 100 mm ab, wodurch der fertige Druck unterhalb des Kamerabildausschnitts liegt. Bambuddy hebt sie wieder bis knapp über die zuletzt gedruckte Schicht an, nimmt das Foto auf und senkt sie danach wieder ab. Wird übersprungen, wenn die Druckhöhe unbekannt ist oder ein weiterer Auftrag in der Warteschlange steht.',

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

@@ -2207,8 +2207,6 @@ export default {
     upToDate: 'You are up to date',
     updateAvailable: 'Update available',
     // Notifications
-    notificationLanguage: 'Notification Language',
-    notificationLanguageDescription: 'Language for push notifications',
     bedCooledThreshold: 'Bed Cooled Threshold',
     bedCooledThresholdDescription: 'Temperature below which the bed is considered cooled after a print',
     userNotificationsEnabled: 'User Notifications',
@@ -2335,7 +2333,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Sidebar order',
     // Camera
-    saveThumbnails: 'Save thumbnails',
     captureFinishPhoto: 'Capture finish photo',
     noPrintersConfigured: 'No printers configured',
     // Updates
@@ -2879,7 +2876,6 @@ export default {
     // Archive
     autoArchivePrints: 'Auto-archive prints',
     autoArchiveDescription: 'Automatically save 3MF files when prints complete. When off, no printer archives prints started outside Bambuddy.',
-    saveThumbnailsDescription: 'Extract and save preview images from 3MF files',
     captureFinishPhotoDescription: 'Take a photo from printer camera when print completes. Bambuddy records a brief timelapse during the print so the photo can be sourced from the moment before the bed drops; the timelapse file is kept if you enabled timelapse for this print, otherwise it is deleted automatically after the photo is captured.',
     finishPhotoRestorePlate: 'Restore plate for finish photo',
     finishPhotoRestorePlateDescription: 'The printer drops the build plate about 100 mm when a print ends, leaving the finished print below the camera\'s framing. Bambuddy raises it back to just above the last printed layer, takes the photo, then lowers it again. Skipped when the print height is unknown or another job is queued.',

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

@@ -2188,8 +2188,6 @@ export default {
     upToDate: 'Está actualizado',
     updateAvailable: 'Actualización disponible',
     // Notifications
-    notificationLanguage: 'Idioma de las notificaciones',
-    notificationLanguageDescription: 'Idioma de las notificaciones push',
     bedCooledThreshold: 'Umbral de cama enfriada',
     bedCooledThresholdDescription: 'Temperatura por debajo de la cual se considera que la cama se ha enfriado tras una impresión',
     userNotificationsEnabled: 'Notificaciones de usuario',
@@ -2316,7 +2314,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Orden de la barra lateral',
     // Camera
-    saveThumbnails: 'Guardar miniaturas',
     captureFinishPhoto: 'Capturar foto de finalización',
     noPrintersConfigured: 'No hay impresoras configuradas',
     // Updates
@@ -2859,7 +2856,6 @@ export default {
     // Archive
     autoArchivePrints: 'Archivar impresiones automáticamente',
     autoArchiveDescription: 'Guardar automáticamente los archivos 3MF cuando se completan las impresiones. Desactivado: ninguna impresora archiva las impresiones iniciadas fuera de Bambuddy.',
-    saveThumbnailsDescription: 'Extraer y guardar imágenes de vista previa de los archivos 3MF',
     captureFinishPhotoDescription: 'Tomar una foto desde la cámara de la impresora cuando se completa la impresión. Bambuddy graba un breve timelapse durante la impresión para que la foto pueda obtenerse del momento previo al descenso de la cama; el archivo del timelapse se conserva si activaste el timelapse para esta impresión, de lo contrario se elimina automáticamente tras capturar la foto.',
     finishPhotoRestorePlate: 'Elevar la cama para la foto final',
     finishPhotoRestorePlateDescription: 'La impresora baja la cama unos 100 mm al terminar una impresión, dejando la pieza terminada por debajo del encuadre de la cámara. Bambuddy la vuelve a subir hasta justo encima de la última capa impresa, toma la foto y luego la baja de nuevo. Se omite si se desconoce la altura de la impresión o si hay otro trabajo en cola.',

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

@@ -2140,8 +2140,6 @@ export default {
     upToDate: 'Bambuddy est à jour',
     updateAvailable: 'Mise à jour disponible',
     // Notifications
-    notificationLanguage: 'Langue des notifications',
-    notificationLanguageDescription: 'Langue pour les notifications push',
     bedCooledThreshold: 'Seuil de refroidissement du plateau',
     bedCooledThresholdDescription: 'Température en dessous de laquelle le plateau est considéré comme refroidi',
     userNotificationsEnabled: 'Notifications utilisateur',
@@ -2268,7 +2266,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Ordre de la barre latérale',
     // Camera
-    saveThumbnails: 'Enregistrer les vignettes',
     captureFinishPhoto: 'Prendre une photo à la fin',
     noPrintersConfigured: 'Aucune imprimante configurée',
     // Updates
@@ -2810,7 +2807,6 @@ export default {
     progressInTitleDescription: 'Affiche le pourcentage de l\'impression en cours et un anneau de progression dans l\'onglet du navigateur.',
     autoArchivePrints: 'Archiver automatiquement les impressions',
     autoArchiveDescription: 'Sauvegarder automatiquement les fichiers 3MF à la fin des impressions. Désactivé : aucune imprimante n\'archive les impressions lancées en dehors de Bambuddy.',
-    saveThumbnailsDescription: 'Extraire et sauvegarder les images d\'aperçu des fichiers 3MF',
     captureFinishPhotoDescription: 'Prendre une photo avec la caméra de l\'imprimante à la fin de l\'impression. Bambuddy enregistre un court timelapse pendant l\'impression afin que la photo puisse provenir du moment précédant l\'abaissement du plateau ; le fichier du timelapse est conservé si vous avez activé le timelapse pour cette impression, sinon il est supprimé automatiquement après la capture de la photo.',
     finishPhotoRestorePlate: 'Remonter le plateau pour la photo finale',
     finishPhotoRestorePlateDescription: 'L\'imprimante abaisse le plateau d\'environ 100 mm à la fin d\'une impression, plaçant l\'objet terminé sous le cadrage de la caméra. Bambuddy le remonte juste au-dessus de la dernière couche imprimée, prend la photo, puis le rabaisse. Ignoré si la hauteur d\'impression est inconnue ou si un autre travail est en file d\'attente.',

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

@@ -2140,8 +2140,6 @@ export default {
     upToDate: 'Sei aggiornato',
     updateAvailable: 'Aggiornamento disponibile',
     // Notifications
-    notificationLanguage: 'Lingua notifiche',
-    notificationLanguageDescription: 'Lingua per notifiche push',
     bedCooledThreshold: 'Soglia raffreddamento piatto',
     bedCooledThresholdDescription: 'Temperatura sotto la quale il piatto è considerato raffreddato dopo una stampa',
     userNotificationsEnabled: 'Notifiche utente',
@@ -2268,7 +2266,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Ordine barra laterale',
     // Camera
-    saveThumbnails: 'Salva miniature',
     captureFinishPhoto: 'Acquisisci foto finale',
     noPrintersConfigured: 'Nessuna stampante configurata',
     // Updates
@@ -2809,7 +2806,6 @@ export default {
     progressInTitleDescription: 'Mostra la percentuale della stampa attiva e un anello di avanzamento nella scheda del browser.',
     autoArchivePrints: 'Archiviazione automatica stampe',
     autoArchiveDescription: 'Salva automaticamente i file 3MF al completamento delle stampe. Disattivato: nessuna stampante archivia le stampe avviate al di fuori di Bambuddy.',
-    saveThumbnailsDescription: 'Estrai e salva le immagini di anteprima dai file 3MF',
     captureFinishPhotoDescription: 'Scatta una foto dalla fotocamera della stampante al completamento della stampa. Bambuddy registra un breve timelapse durante la stampa in modo che la foto possa essere ricavata dal momento precedente all\'abbassamento del piatto; il file del timelapse viene mantenuto se hai abilitato il timelapse per questa stampa, altrimenti viene eliminato automaticamente dopo l\'acquisizione della foto.',
     finishPhotoRestorePlate: 'Solleva il piatto per la foto finale',
     finishPhotoRestorePlateDescription: 'La stampante abbassa il piatto di circa 100 mm al termine di una stampa, lasciando l\'oggetto finito sotto l\'inquadratura della fotocamera. Bambuddy lo risolleva fino a poco sopra l\'ultimo strato stampato, scatta la foto e poi lo riabbassa. Ignorato se l\'altezza di stampa è sconosciuta o se un altro lavoro è in coda.',

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

@@ -2184,8 +2184,6 @@ export default {
     upToDate: '最新です',
     updateAvailable: 'アップデートあり',
     // Notifications
-    notificationLanguage: '通知の言語',
-    notificationLanguageDescription: 'プッシュ通知の言語',
     bedCooledThreshold: 'ベッド冷却しきい値',
     bedCooledThresholdDescription: '印刷後にベッドが冷却されたと見なす温度',
     userNotificationsEnabled: 'ユーザー通知',
@@ -2312,7 +2310,6 @@ export default {
     // Sidebar
     sidebarOrder: 'サイドバーの順序',
     // Camera
-    saveThumbnails: 'サムネイルを保存',
     captureFinishPhoto: '完了写真を撮影',
     noPrintersConfigured: 'プリンターが設定されていません',
     // Updates
@@ -2856,7 +2853,6 @@ export default {
     // Archive
     autoArchivePrints: '印刷を自動アーカイブ',
     autoArchiveDescription: '印刷完了時に3MFファイルを自動保存。オフの場合、Bambuddy 以外から開始された印刷はどのプリンターでもアーカイブされません。',
-    saveThumbnailsDescription: '3MFファイルからプレビュー画像を抽出して保存',
     captureFinishPhotoDescription: '印刷完了時にプリンターカメラから写真を撮影します。Bambuddy は印刷中に短いタイムラプスを記録し、ベッドが下がる前の瞬間から写真を取得できるようにします。この印刷でタイムラプスを有効にしていた場合はタイムラプスファイルが保存され、それ以外の場合は写真の取得後に自動的に削除されます。',
     finishPhotoRestorePlate: '完了写真のためにプレートを戻す',
     finishPhotoRestorePlateDescription: 'プリンターは印刷終了時にビルドプレートを約 100 mm 下降させるため、完成した造形物がカメラの画角より下に来ます。Bambuddy はプレートを最終印刷レイヤーのすぐ上まで戻して写真を撮影し、その後再び下降させます。造形高さが不明な場合や次のジョブがキューにある場合はスキップされます。',

+ 0 - 4
frontend/src/i18n/locales/ko.ts

@@ -2091,8 +2091,6 @@ export default {
     latestVersion: '최신 버전',
     upToDate: '최신 상태입니다',
     updateAvailable: '업데이트 가능',
-    notificationLanguage: '알림 언어',
-    notificationLanguageDescription: '푸시 알림 언어',
     bedCooledThreshold: '베드 냉각 임계값',
     bedCooledThresholdDescription: '인쇄 후 베드가 냉각된 것으로 간주되는 온도',
     userNotificationsEnabled: '사용자 알림',
@@ -2212,7 +2210,6 @@ export default {
     allPrinters: '모든 프린터',
     noDefaultPrinter: '기본값 없음 (매번 묻기)',
     sidebarOrder: '사이드바 순서',
-    saveThumbnails: '썸네일 저장',
     captureFinishPhoto: '완료 사진 캡처',
     noPrintersConfigured: '설정된 프린터 없음',
     checkForUpdatesLabel: '업데이트 확인',
@@ -2721,7 +2718,6 @@ export default {
     progressInTitleDescription: '브라우저 탭에 진행 중인 인쇄의 백분율과 진행 링을 표시합니다.',
     autoArchivePrints: '인쇄 자동 아카이브',
     autoArchiveDescription: '인쇄 완료 시 3MF 파일 자동 저장. 끄면 Bambuddy 외부에서 시작된 인쇄는 어떤 프린터에서도 보관되지 않습니다.',
-    saveThumbnailsDescription: '3MF 파일에서 미리보기 이미지 추출 및 저장',
     captureFinishPhotoDescription: '인쇄 완료 시 프린터 카메라로 사진 촬영. Bambuddy는 인쇄 중 짧은 타임랩스를 기록하여 베드가 내려가기 전 순간에서 사진을 가져올 수 있도록 합니다. 이 인쇄에 대해 타임랩스를 활성화한 경우 타임랩스 파일이 보관되며, 그렇지 않으면 사진 촬영 후 자동으로 삭제됩니다.',
     finishPhotoRestorePlate: '완료 사진을 위해 베드 올리기',
     finishPhotoRestorePlateDescription: '프린터는 인쇄가 끝나면 베드를 약 100 mm 내리므로 완성된 출력물이 카메라 화각 아래에 놓입니다. Bambuddy는 베드를 마지막 인쇄 레이어 바로 위까지 다시 올려 사진을 찍은 뒤 다시 내립니다. 출력 높이를 알 수 없거나 다른 작업이 대기 중이면 건너뜁니다.',

+ 0 - 4
frontend/src/i18n/locales/nl.ts

@@ -2205,8 +2205,6 @@ export default {
     upToDate: 'Je bent up-to-date',
     updateAvailable: 'Update beschikbaar',
     // Notifications
-    notificationLanguage: 'Taal voor meldingen',
-    notificationLanguageDescription: 'Taal voor pushmeldingen',
     bedCooledThreshold: 'Drempel voor afgekoeld bed',
     bedCooledThresholdDescription: 'Temperatuur waaronder het bed na een afdruk als afgekoeld wordt beschouwd',
     userNotificationsEnabled: 'Gebruikersmeldingen',
@@ -2333,7 +2331,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Volgorde zijbalk',
     // Camera
-    saveThumbnails: 'Miniaturen opslaan',
     captureFinishPhoto: 'Eindfoto vastleggen',
     noPrintersConfigured: 'Geen printers geconfigureerd',
     // Updates
@@ -2877,7 +2874,6 @@ export default {
     // Archive
     autoArchivePrints: 'Afdrukken automatisch archiveren',
     autoArchiveDescription: '3MF-bestanden automatisch opslaan wanneer afdrukken zijn voltooid. Uit: geen enkele printer archiveert afdrukken die buiten Bambuddy zijn gestart.',
-    saveThumbnailsDescription: 'Voorbeeldafbeeldingen uit 3MF-bestanden extraheren en opslaan',
     captureFinishPhotoDescription: 'Maak een foto met de printercamera wanneer de afdruk is voltooid. Bambuddy neemt tijdens de afdruk een korte timelapse op zodat de foto kan worden genomen van het moment voordat het bed zakt; het timelapsebestand blijft behouden als je timelapse voor deze afdruk hebt ingeschakeld, anders wordt het automatisch verwijderd nadat de foto is vastgelegd.',
     finishPhotoRestorePlate: 'Plaat herstellen voor eindfoto',
     finishPhotoRestorePlateDescription: 'De printer laat de bouwplaat ongeveer 100 mm zakken wanneer een afdruk eindigt, waardoor de voltooide afdruk onder het camerabeeld komt. Bambuddy brengt de plaat terug tot net boven de laatst geprinte laag, maakt de foto en laat hem daarna weer zakken. Wordt overgeslagen wanneer de afdrukhoogte onbekend is of er een andere taak in de wachtrij staat.',

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

@@ -2140,8 +2140,6 @@ export default {
     upToDate: 'Você está atualizado',
     updateAvailable: 'Atualização disponível',
     // Notifications
-    notificationLanguage: 'Idioma das Notificações',
-    notificationLanguageDescription: 'Idioma para notificações push',
     bedCooledThreshold: 'Limite de Resfriamento da Cama',
     bedCooledThresholdDescription: 'Temperatura abaixo da qual a cama é considerada resfriada após uma impressão',
     userNotificationsEnabled: 'Notificações do Usuário',
@@ -2268,7 +2266,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Ordem da barra lateral',
     // Camera
-    saveThumbnails: 'Salvar miniaturas',
     captureFinishPhoto: 'Capturar foto de conclusão',
     noPrintersConfigured: 'Nenhuma impressora configurada',
     // Updates
@@ -2809,7 +2806,6 @@ export default {
     progressInTitleDescription: 'Mostra a porcentagem da impressão ativa e um anel de progresso na aba do navegador.',
     autoArchivePrints: 'Arquivar impressões automaticamente',
     autoArchiveDescription: 'Salvar automaticamente arquivos 3MF quando impressões forem concluídas. Desativado: nenhuma impressora arquiva impressões iniciadas fora do Bambuddy.',
-    saveThumbnailsDescription: 'Extrair e salvar imagens de pré-visualização dos arquivos 3MF',
     captureFinishPhotoDescription: 'Tirar foto da câmera da impressora quando a impressão for concluída. Bambuddy grava um timelapse curto durante a impressão para que a foto possa ser obtida do momento antes da mesa descer; o arquivo do timelapse é mantido se você habilitou o timelapse para esta impressão, caso contrário ele é excluído automaticamente após a captura da foto.',
     finishPhotoRestorePlate: 'Elevar a mesa para a foto final',
     finishPhotoRestorePlateDescription: 'A impressora baixa a mesa cerca de 100 mm ao fim de uma impressão, deixando a peça pronta abaixo do enquadramento da câmera. O Bambuddy a eleva novamente até logo acima da última camada impressa, tira a foto e depois a baixa de novo. Ignorado quando a altura da impressão é desconhecida ou há outro trabalho na fila.',

+ 0 - 4
frontend/src/i18n/locales/ru.ts

@@ -2088,8 +2088,6 @@ export default {
     latestVersion: "Последняя версия",
     upToDate: "Установлена последняя версия",
     updateAvailable: "Доступно обновление",
-    notificationLanguage: "Язык уведомлений",
-    notificationLanguageDescription: "Язык push-уведомлений",
     bedCooledThreshold: "Порог остывания стола",
     bedCooledThresholdDescription: "Температура, ниже которой стол после печати считается остывшим",
     userNotificationsEnabled: "Пользовательские уведомления",
@@ -2209,7 +2207,6 @@ export default {
     allPrinters: "Все принтеры",
     noDefaultPrinter: "Без принтера по умолчанию (спрашивать каждый раз)",
     sidebarOrder: "Порядок пунктов боковой панели",
-    saveThumbnails: "Сохранять миниатюры",
     captureFinishPhoto: "Снимок после завершения",
     noPrintersConfigured: "Принтеры не настроены",
     checkForUpdatesLabel: "Проверять обновления",
@@ -2725,7 +2722,6 @@ export default {
     progressInTitleDescription: "Показывает процент текущей печати и кольцо прогресса во вкладке браузера.",
     autoArchivePrints: "Автоматически архивировать печать",
     autoArchiveDescription: "Автоматически сохранять 3MF после завершения печати. Если выключено, ни один принтер не архивирует печать, запущенную не из Bambuddy.",
-    saveThumbnailsDescription: "Извлекать и сохранять изображения предпросмотра из 3MF",
     captureFinishPhotoDescription: "Сделать снимок камерой принтера после завершения печати. Во время печати Bambuddy записывает короткий таймлапс, чтобы получить кадр до опускания стола. Если таймлапс был включён для задания, файл сохранится; иначе после получения снимка он будет автоматически удалён.",
     finishPhotoRestorePlate: "Поднимать стол для финального снимка",
     finishPhotoRestorePlateDescription: "По окончании печати принтер опускает стол примерно на 100 мм, и готовая модель оказывается ниже кадра камеры. Bambuddy поднимает стол обратно чуть выше последнего напечатанного слоя, делает снимок и снова опускает его. Пропускается, если высота печати неизвестна или в очереди есть другое задание.",

+ 0 - 4
frontend/src/i18n/locales/sv.ts

@@ -2204,8 +2204,6 @@ export default {
     upToDate: 'Du har den senaste versionen',
     updateAvailable: 'Uppdatering tillgänglig',
     // Notifications
-    notificationLanguage: 'Notisspråk',
-    notificationLanguageDescription: 'Språk för push-notiser',
     bedCooledThreshold: 'Tröskelvärde för sval byggplatta',
     bedCooledThresholdDescription: 'Temperatur under vilken byggplattan anses sval efter en utskrift',
     userNotificationsEnabled: 'Användarnotiser',
@@ -2332,7 +2330,6 @@ export default {
     // Sidebar
     sidebarOrder: 'Sidofältsordning',
     // Camera
-    saveThumbnails: 'Spara minibilder',
     captureFinishPhoto: 'Ta färdig-foto',
     noPrintersConfigured: 'Inga skrivare konfigurerade',
     // Updates
@@ -2876,7 +2873,6 @@ export default {
     // Archive
     autoArchivePrints: 'Auto-arkivera utskrifter',
     autoArchiveDescription: 'Spara automatiskt 3MF-filer när utskrifter slutförs. Av: ingen skrivare arkiverar utskrifter som startats utanför Bambuddy.',
-    saveThumbnailsDescription: 'Extrahera och spara förhandsvisningsbilder från 3MF-filer',
     captureFinishPhotoDescription: 'Ta ett foto från skrivarkameran när utskriften slutförs. Bambuddy spelar in en kort timelapse under utskriften så att fotot kan hämtas från ögonblicket innan byggplattan sänks; timelapse-filen behålls om du aktiverat timelapse för denna utskrift, annars tas den automatiskt bort efter att fotot tagits.',
     finishPhotoRestorePlate: 'Återställ platta för foto',
     finishPhotoRestorePlateDescription: 'Skrivaren sänker byggplattan cirka 100 mm när en utskrift slutförs, vilket lämnar den färdiga utskriften under kamerans bildruta. Bambuddy höjer tillbaka den precis ovanför det sista utskrivna skiktet, tar fotot och sänker den igen. Hoppas över när utskriftshöjden är okänd eller om ett annat jobb är köat.',

+ 0 - 4
frontend/src/i18n/locales/tr.ts

@@ -2188,8 +2188,6 @@ export default {
     upToDate: 'Güncelsiniz',
     updateAvailable: 'Güncelleme mevcut',
     // Bildirimler
-    notificationLanguage: 'Bildirim Dili',
-    notificationLanguageDescription: 'Push bildirimleri için dil',
     bedCooledThreshold: 'Tabla Soğuma Eşiği',
     bedCooledThresholdDescription: 'Bir baskıdan sonra tablanın soğumuş kabul edildiği sıcaklık',
     userNotificationsEnabled: 'Kullanıcı Bildirimleri',
@@ -2316,7 +2314,6 @@ export default {
     // Kenar çubuğu
     sidebarOrder: 'Kenar çubuğu sırası',
     // Kamera
-    saveThumbnails: 'Küçük resimleri kaydet',
     captureFinishPhoto: 'Bitiş fotoğrafı çek',
     noPrintersConfigured: 'Yapılandırılmış yazıcı yok',
     // Arşiv ayarları
@@ -2860,7 +2857,6 @@ export default {
     // Arşiv
     autoArchivePrints: 'Baskıları otomatik arşivle',
     autoArchiveDescription: 'Baskılar tamamlandığında 3MF dosyalarını otomatik olarak kaydet. Kapalıyken hiçbir yazıcı Bambuddy dışında başlatılan baskıları arşivlemez.',
-    saveThumbnailsDescription: '3MF dosyalarından önizleme görüntülerini çıkar ve kaydet',
     captureFinishPhotoDescription: 'Baskı tamamlandığında yazıcı kamerasından bir fotoğraf çek. Bambuddy, baskı sırasında kısa bir zaman atlamalı kayıt yapar, böylece fotoğraf tabla inmeden önceki andan alınabilir. Bu baskı için zaman atlamalı kaydı etkinleştirdiyseniz dosya saklanır, aksi takdirde fotoğraf çekildikten sonra otomatik olarak silinir.',
     finishPhotoRestorePlate: 'Bitiş fotoğrafı için tablayı yükselt',
     finishPhotoRestorePlateDescription: 'Yazıcı, baskı bittiğinde tablayı yaklaşık 100 mm aşağı indirir ve tamamlanmış baskı kameranın çerçevesinin altında kalır. Bambuddy tablayı son basılan katmanın hemen üzerine geri kaldırır, fotoğrafı çeker ve ardından tekrar indirir. Baskı yüksekliği bilinmiyorsa veya kuyrukta başka bir iş varsa atlanır.',

+ 0 - 4
frontend/src/i18n/locales/uk.ts

@@ -2203,8 +2203,6 @@ export default {
     upToDate: "У вас установлено останню версію",
     updateAvailable: "Доступне оновлення",
     // Notifications
-    notificationLanguage: "Мова сповіщень",
-    notificationLanguageDescription: "Мова для push-повідомлень",
     bedCooledThreshold: "Поріг охолодження столу",
     bedCooledThresholdDescription: "Температура, нижче якої стіл вважається охолодженим після друку",
     userNotificationsEnabled: "Сповіщення користувача",
@@ -2331,7 +2329,6 @@ export default {
     // Sidebar
     sidebarOrder: "Порядок бічної панелі",
     // Camera
-    saveThumbnails: "Зберегти мініатюри",
     captureFinishPhoto: "Зробити фото після завершення",
     noPrintersConfigured: "Принтери не налаштовані",
     // Updates
@@ -2874,7 +2871,6 @@ export default {
     // Archive
     autoArchivePrints: "Автоматично архівувати друки",
     autoArchiveDescription: "Автоматично зберігати файли 3MF після завершення друку. Якщо вимкнено, жоден принтер не архівує друк, запущений не з Bambuddy.",
-    saveThumbnailsDescription: "Витягніть і збережіть зображення попереднього перегляду з файлів 3MF.",
     captureFinishPhotoDescription: "Зробіть фотографію з камери принтера після завершення друку. Bambuddy записує короткий проміжок часу під час друку, щоб фотографію можна було отримати з моменту, коли стіл опускається; файл уповільненої зйомки зберігається, якщо ви ввімкнули уповільнену зйомку для цього друку, інакше він автоматично видаляється після зйомки фотографії.",
     finishPhotoRestorePlate: "Піднімати стіл для фінального знімка",
     finishPhotoRestorePlateDescription: "Після завершення друку принтер опускає стіл приблизно на 100 мм, і готова модель опиняється нижче кадру камери. Bambuddy піднімає стіл назад трохи вище останнього надрукованого шару, робить знімок і знову опускає його. Пропускається, якщо висота друку невідома або в черзі є інше завдання.",

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

@@ -2185,8 +2185,6 @@ export default {
     upToDate: '已是最新版本',
     updateAvailable: '有可用更新',
     // Notifications
-    notificationLanguage: '通知语言',
-    notificationLanguageDescription: '推送通知的语言',
     bedCooledThreshold: '热床冷却阈值',
     bedCooledThresholdDescription: '打印后热床被视为已冷却的温度',
     userNotificationsEnabled: '用户通知',
@@ -2313,7 +2311,6 @@ export default {
     // Sidebar
     sidebarOrder: '侧边栏顺序',
     // Camera
-    saveThumbnails: '保存缩略图',
     captureFinishPhoto: '拍摄完成照片',
     noPrintersConfigured: '未配置打印机',
     // Updates
@@ -2854,7 +2851,6 @@ export default {
     progressInTitleDescription: '在浏览器标签页中显示当前打印的百分比和进度环。',
     autoArchivePrints: '自动归档打印',
     autoArchiveDescription: '打印完成时自动保存3MF文件。关闭时,所有打印机都不会归档在 Bambuddy 之外启动的打印。',
-    saveThumbnailsDescription: '从3MF文件中提取并保存预览图像',
     captureFinishPhotoDescription: '打印完成时从打印机摄像头拍照。Bambuddy 会在打印期间录制一段短延时摄影,以便从热床下降前的瞬间获取照片;如果您为本次打印启用了延时摄影,文件将保留,否则会在拍照完成后自动删除。',
     finishPhotoRestorePlate: '为完成照片抬升热床',
     finishPhotoRestorePlateDescription: '打印结束时打印机会将热床下降约 100 mm,使完成的模型落在相机取景范围之下。Bambuddy 会将热床抬回到最后一层打印高度略上方,拍摄照片后再次下降。若打印高度未知或队列中还有其他任务,则跳过此步骤。',

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

@@ -2185,8 +2185,6 @@ export default {
     upToDate: '已是最新版本',
     updateAvailable: '有可用更新',
     // Notifications
-    notificationLanguage: '通知語言',
-    notificationLanguageDescription: '推送通知的語言',
     bedCooledThreshold: '熱床冷卻閾值',
     bedCooledThresholdDescription: '列印後熱床被視為已冷卻的溫度',
     userNotificationsEnabled: '使用者通知',
@@ -2313,7 +2311,6 @@ export default {
     // Sidebar
     sidebarOrder: '側邊欄順序',
     // Camera
-    saveThumbnails: '儲存縮圖',
     captureFinishPhoto: '拍攝完成照片',
     noPrintersConfigured: '未設定印表機',
     // Updates
@@ -2854,7 +2851,6 @@ export default {
     progressInTitleDescription: '在瀏覽器分頁中顯示目前列印的百分比和進度環。',
     autoArchivePrints: '自動歸檔列印',
     autoArchiveDescription: '列印完成時自動儲存3MF檔案。關閉時,所有印表機都不會歸檔在 Bambuddy 之外啟動的列印。',
-    saveThumbnailsDescription: '從3MF檔案中提取並儲存預覽影像',
     captureFinishPhotoDescription: '列印完成時從印表機攝影機拍照。Bambuddy 會在列印期間錄製一段短縮時攝影,以便從熱床下降前的瞬間取得照片;如果您為本次列印啟用了縮時攝影,檔案將保留,否則會在拍照完成後自動刪除。',
     finishPhotoRestorePlate: '為完成照片抬升熱床',
     finishPhotoRestorePlateDescription: '列印結束時印表機會將熱床下降約 100 mm,使完成的模型落在相機取景範圍之下。Bambuddy 會將熱床抬回到最後一層列印高度略上方,拍攝照片後再次下降。若列印高度未知或佇列中還有其他任務,則跳過此步驟。',

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

@@ -1254,7 +1254,6 @@ export function SettingsPage() {
     // Check if there are actual changes
     const hasChanges =
       baseline.auto_archive !== localSettings.auto_archive ||
-      baseline.save_thumbnails !== localSettings.save_thumbnails ||
       baseline.capture_finish_photo !== localSettings.capture_finish_photo ||
       (baseline.finish_photo_restore_plate ?? true) !== (localSettings.finish_photo_restore_plate ?? true) ||
       baseline.default_filament_cost !== localSettings.default_filament_cost ||
@@ -1270,7 +1269,6 @@ export function SettingsPage() {
       (baseline.announcements_enabled ?? true) !== (localSettings.announcements_enabled ?? true) ||
       (baseline.announcements_all_users ?? false) !== (localSettings.announcements_all_users ?? false) ||
       (baseline.local_login_enabled ?? true) !== (localSettings.local_login_enabled ?? true) ||
-      baseline.notification_language !== localSettings.notification_language ||
       (baseline.bed_cooled_threshold ?? 35) !== (localSettings.bed_cooled_threshold ?? 35) ||
       baseline.ams_humidity_good !== localSettings.ams_humidity_good ||
       baseline.ams_humidity_fair !== localSettings.ams_humidity_fair ||
@@ -1376,7 +1374,6 @@ export function SettingsPage() {
       // Only send the fields we manage on this page (exclude virtual_printer_* which are managed separately)
       const settingsToSave: AppSettingsUpdate = {
         auto_archive: localSettings.auto_archive,
-        save_thumbnails: localSettings.save_thumbnails,
         capture_finish_photo: localSettings.capture_finish_photo,
         // #2547: `?? true` mirrors the toggle's own default, so an install
         // whose settings payload predates this field saves what the user is
@@ -1400,7 +1397,6 @@ export function SettingsPage() {
         announcements_enabled: localSettings.announcements_enabled ?? true,
         announcements_all_users: localSettings.announcements_all_users ?? false,
         local_login_enabled: localSettings.local_login_enabled,
-        notification_language: localSettings.notification_language,
         bed_cooled_threshold: localSettings.bed_cooled_threshold,
         ams_humidity_good: localSettings.ams_humidity_good,
         ams_humidity_fair: localSettings.ams_humidity_fair,
@@ -2115,23 +2111,6 @@ 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">{t('settings.saveThumbnails')}</p>
-                  <p className="text-sm text-bambu-gray">
-                    {t('settings.saveThumbnailsDescription')}
-                  </p>
-                </div>
-                <label className="relative inline-flex items-center cursor-pointer">
-                  <input
-                    type="checkbox"
-                    checked={localSettings.save_thumbnails}
-                    onChange={(e) => updateSetting('save_thumbnails', 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="flex items-center justify-between">
                 <div>
                   <p className="text-white">{t('settings.captureFinishPhoto')}</p>
@@ -4092,29 +4071,6 @@ export function SettingsPage() {
               </div>
             </div>
 
-            {/* Notification Language Setting */}
-            <Card className="mb-4">
-              <CardContent className="py-3">
-                <div className="flex items-center justify-between">
-                  <div>
-                    <p className="text-white text-sm font-medium">{t('settings.notificationLanguage')}</p>
-                    <p className="text-xs text-bambu-gray">{t('settings.notificationLanguageDescription')}</p>
-                  </div>
-                  <select
-                    value={localSettings.notification_language || 'en'}
-                    onChange={(e) => updateSetting('notification_language', e.target.value)}
-                    className="px-2 py-1.5 bg-bambu-dark border border-bambu-dark-tertiary rounded text-white text-sm focus:outline-none focus:ring-1 focus:ring-bambu-green"
-                  >
-                    {availableLanguages.map((lang) => (
-                      <option key={lang.code} value={lang.code}>
-                        {lang.nativeName}
-                      </option>
-                    ))}
-                  </select>
-                </div>
-              </CardContent>
-            </Card>
-
             {/* Bed Cooled Threshold Setting */}
             <Card className="mb-4">
               <CardContent className="py-3">