feat(diagnostic, archives): install step 4 — proactive check + reactive banner

Two complementary surfaces for the most-missed install step ("Store sent
  files on external storage"):

  1. Connection diagnostic check (printer-side variant)
     - Reads state.store_to_sdcard, parsed from MQTT home_flag bit 11.
     - Pass / fail / skip; instant, no I/O.
     - Catches the newer-firmware variant where the toggle moved onto the
       printer itself (P2S 01.02 / Studio 2.6+).

     An FTP upload-and-verify probe was tried first and rejected. /cache
     is always writable from Bambuddy regardless of the slicer setting;
     only BambuStudio's own behaviour changes when the toggle flips.
     Empirically confirmed against X1C + H2D with the slicer option
     toggled off: probe still succeeded, home_flag bit 11 stayed True.

  2. Archives-page banner (slicer-side variant)
     - The slicer-side toggle is invisible to the printer — older
       BambuStudio doesn't push the change to the printer. The diagnostic
       can't see it.
     - Symptom is deterministic: archiver creates rows with
       extra_data.no_3mf_available=True (main.py:2770) when it can't pull
       the 3MF from /cache after a slicer-initiated print.
     - New endpoint GET /archives/no-3mf-warning returns whether any
       archive in the last 30 days has the flag (excluding soft-deleted).
     - Amber dismissible banner at the top of /archives; one-shot
       localStorage dismissal (matches Layout.tsx update-banner pattern,
       but persistent across sessions).
     - React-Query disabled after dismissal so the endpoint isn't polled
       once the user has been told.
This commit is contained in:
maziggy
2026-06-09 12:20:01 +02:00
parent 60e31634b8
commit f87b5bb3b8
20 changed files with 362 additions and 123 deletions
+1
View File
@@ -3771,6 +3771,7 @@ export const api = {
return request<Archive[]>(`/archives/search?${params}`);
},
rebuildSearchIndex: () => request<{ message: string }>('/archives/search/rebuild-index', { method: 'POST' }),
getNo3MFWarning: () => request<{ has_fallback: boolean }>('/archives/no-3mf-warning'),
updateArchive: (id: number, data: {
printer_id?: number | null;
project_id?: number | null;
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: 'Druckarchiv',
no3mfBanner: {
title: 'Einige kürzliche Drucke konnten nicht mit Vorschaubild archiviert werden',
body: 'Der Slicer hat die .gcode.3mf-Datei nicht auf der SD-Karte des Druckers hinterlegt, daher konnte Bambuddy weder Vorschaubild noch Slicer-Metadaten abrufen. Üblicherweise liegt das daran, dass "Gesendete Dateien auf externem Speicher speichern" im Slicer (Geräte-Tab in Bambu Studio / OrcaSlicer) deaktiviert ist.',
docsLink: 'Installationsschritt 4 anzeigen',
dismissLabel: 'Hinweis schließen',
},
searchPlaceholder: 'Archiv durchsuchen...',
filterByPrinter: 'Nach Drucker filtern',
filterByStatus: 'Nach Status filtern',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: 'Print Archives',
no3mfBanner: {
title: 'Some recent prints couldn\'t be archived with thumbnails',
body: 'The slicer didn\'t leave the .gcode.3mf on the printer\'s SD card, so Bambuddy couldn\'t pull the thumbnail or slicer metadata. This is usually because "Store sent files on external storage" is off in the slicer (Bambu Studio / OrcaSlicer Device tab).',
docsLink: 'See install step 4',
dismissLabel: 'Dismiss this notice',
},
searchPlaceholder: 'Search archives...',
filterByPrinter: 'Filter by printer',
filterByStatus: 'Filter by status',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: 'Archivos de impresión',
no3mfBanner: {
title: 'Algunas impresiones recientes no se pudieron archivar con miniaturas',
body: 'El slicer no dejó el archivo .gcode.3mf en la tarjeta SD de la impresora, así que Bambuddy no pudo recuperar ni la miniatura ni los metadatos del slicer. Esto suele deberse a que "Almacenar archivos enviados en almacenamiento externo" está desactivado en el slicer (pestaña Dispositivo de Bambu Studio / OrcaSlicer).',
docsLink: 'Ver paso 4 de instalación',
dismissLabel: 'Descartar este aviso',
},
searchPlaceholder: 'Buscar archivos...',
filterByPrinter: 'Filtrar por impresora',
filterByStatus: 'Filtrar por estado',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: 'Archives d\'impression',
no3mfBanner: {
title: 'Certaines impressions récentes n\'ont pas pu être archivées avec leur miniature',
body: 'Le slicer n\'a pas laissé le fichier .gcode.3mf sur la carte SD de l\'imprimante, donc Bambuddy n\'a pas pu récupérer la miniature ni les métadonnées slicer. C\'est généralement parce que « Stocker les fichiers envoyés sur stockage externe » est désactivé dans le slicer (onglet Périphérique de Bambu Studio / OrcaSlicer).',
docsLink: 'Voir l\'étape 4 de l\'installation',
dismissLabel: 'Ignorer ce message',
},
searchPlaceholder: 'Chercher dans les archives...',
filterByPrinter: 'Par imprimante',
filterByStatus: 'Par statut',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: 'Archivi di stampa',
no3mfBanner: {
title: 'Alcune stampe recenti non sono state archiviate con la miniatura',
body: 'Lo slicer non ha lasciato il file .gcode.3mf sulla scheda SD della stampante, quindi Bambuddy non ha potuto recuperare né la miniatura né i metadati dello slicer. Solitamente accade perché "Memorizza file inviati su archiviazione esterna" è disattivato nello slicer (scheda Dispositivo di Bambu Studio / OrcaSlicer).',
docsLink: 'Vedi passo 4 dell\'installazione',
dismissLabel: 'Chiudi questo avviso',
},
searchPlaceholder: 'Cerca archivi...',
filterByPrinter: 'Filtra per stampante',
filterByStatus: 'Filtra per stato',
+6
View File
@@ -634,6 +634,12 @@ export default {
// Archives page
archives: {
title: '印刷アーカイブ',
no3mfBanner: {
title: '最近の一部の印刷でサムネイル付きのアーカイブができませんでした',
body: 'スライサーがプリンターのSDカードに.gcode.3mfファイルを残さなかったため、Bambuddyはサムネイルもスライサーメタデータも取得できませんでした。これは通常、スライサー (Bambu Studio / OrcaSlicer のデバイスタブ) で「送信ファイルを外部ストレージに保存」がオフになっていることが原因です。',
docsLink: 'インストール手順4を参照',
dismissLabel: 'この通知を閉じる',
},
searchPlaceholder: 'アーカイブを検索...',
filterByPrinter: 'プリンターで絞り込み',
filterByStatus: 'ステータスで絞り込み',
+6
View File
@@ -592,6 +592,12 @@ export default {
},
archives: {
title: '인쇄 아카이브',
no3mfBanner: {
title: '최근 일부 인쇄가 썸네일과 함께 아카이브되지 않았습니다',
body: '슬라이서가 프린터의 SD 카드에 .gcode.3mf 파일을 남기지 않아서 Bambuddy가 썸네일이나 슬라이서 메타데이터를 가져올 수 없었습니다. 일반적으로 슬라이서 (Bambu Studio / OrcaSlicer 장치 탭)에서 "전송된 파일을 외부 저장소에 저장"이 꺼져 있을 때 발생합니다.',
docsLink: '설치 단계 4 보기',
dismissLabel: '이 알림 닫기'
},
searchPlaceholder: '아카이브 검색...',
filterByPrinter: '프린터별 필터',
filterByStatus: '상태별 필터',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: 'Arquivos de Impressão',
no3mfBanner: {
title: 'Algumas impressões recentes não puderam ser arquivadas com miniaturas',
body: 'O fatiador não deixou o arquivo .gcode.3mf no cartão SD da impressora, então o Bambuddy não conseguiu obter nem a miniatura nem os metadados do fatiador. Normalmente isso acontece porque "Armazenar arquivos enviados no armazenamento externo" está desligado no fatiador (aba Dispositivo do Bambu Studio / OrcaSlicer).',
docsLink: 'Ver passo 4 da instalação',
dismissLabel: 'Dispensar este aviso',
},
searchPlaceholder: 'Pesquisar arquivos...',
filterByPrinter: 'Filtrar por impressora',
filterByStatus: 'Filtrar por status',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Arşivler sayfası
archives: {
title: 'Baskı Arşivleri',
no3mfBanner: {
title: 'Bazı son baskılar küçük resimlerle birlikte arşivlenemedi',
body: 'Dilimleyici yazıcının SD kartına .gcode.3mf dosyasını bırakmadı, bu nedenle Bambuddy küçük resmi veya dilimleyici meta verilerini alamadı. Bu genellikle dilimleyicide (Bambu Studio / OrcaSlicer\'in Cihaz sekmesi) "Gönderilen dosyaları harici depolamada sakla" seçeneğinin kapalı olmasından kaynaklanır.',
docsLink: 'Kurulum adımı 4\'ü görüntüle',
dismissLabel: 'Bu bildirimi kapat',
},
searchPlaceholder: 'Arşivlerde ara...',
filterByPrinter: 'Yazıcıya göre filtrele',
filterByStatus: 'Duruma göre filtrele',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: '打印归档',
no3mfBanner: {
title: '最近的一些打印未能附带缩略图归档',
body: '切片机没有在打印机的 SD 卡上留下 .gcode.3mf 文件,因此 Bambuddy 无法获取缩略图或切片机元数据。这通常是因为切片机 (Bambu Studio / OrcaSlicer 的设备选项卡) 中"将发送的文件存储在外部存储中"已关闭。',
docsLink: '查看安装步骤 4',
dismissLabel: '关闭此通知',
},
searchPlaceholder: '搜索归档...',
filterByPrinter: '按打印机筛选',
filterByStatus: '按状态筛选',
+6
View File
@@ -635,6 +635,12 @@ export default {
// Archives page
archives: {
title: '列印歸檔',
no3mfBanner: {
title: '最近的一些列印未能附帶縮圖封存',
body: '切片機沒有在印表機的 SD 卡上留下 .gcode.3mf 檔案,因此 Bambuddy 無法取得縮圖或切片機中繼資料。這通常是因為切片機 (Bambu Studio / OrcaSlicer 的裝置分頁) 中「將傳送的檔案儲存在外部儲存中」已關閉。',
docsLink: '檢視安裝步驟 4',
dismissLabel: '關閉此通知',
},
searchPlaceholder: '搜尋歸檔...',
filterByPrinter: '按印表機篩選',
filterByStatus: '按狀態篩選',
+48
View File
@@ -2563,6 +2563,23 @@ export function ArchivesPage() {
const [uploadFiles, setUploadFiles] = useState<File[]>([]);
const [isDraggingOver, setIsDraggingOver] = useState(false);
const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
// Install-step-4 nudge — covers the slicer-side variant of "Store sent files
// on external storage" that the connection diagnostic can't detect (printer
// never hears about it). Symptom: archive created via no-3MF fallback. Once
// dismissed, never shown again — fixing step 4 stops new fallbacks anyway.
const [no3MFWarningDismissed, setNo3MFWarningDismissed] = useState(
() => localStorage.getItem('archiveNo3MFWarningDismissed') === 'true',
);
const { data: no3MFWarning } = useQuery({
queryKey: ['archives', 'no-3mf-warning'],
queryFn: api.getNo3MFWarning,
staleTime: 5 * 60 * 1000,
enabled: !no3MFWarningDismissed,
});
const dismissNo3MFWarning = () => {
localStorage.setItem('archiveNo3MFWarningDismissed', 'true');
setNo3MFWarningDismissed(true);
};
const [isSelectionMode, setIsSelectionMode] = useState(false);
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
const [showBatchTag, setShowBatchTag] = useState(false);
@@ -3200,6 +3217,37 @@ export function ArchivesPage() {
</div>
)}
{no3MFWarning?.has_fallback && !no3MFWarningDismissed && (
<div className="mb-6 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3 flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-amber-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-amber-200">
{t('archives.no3mfBanner.title')}
</div>
<div className="text-xs text-amber-200/80 mt-1">
{t('archives.no3mfBanner.body')}{' '}
<a
href="https://bambuddy.cool/wiki/getting-started/#step-4-enable-store-sent-files-on-external-storage"
target="_blank"
rel="noreferrer"
className="underline hover:text-amber-100 inline-flex items-center gap-1"
>
{t('archives.no3mfBanner.docsLink')}
<ExternalLink className="w-3 h-3" />
</a>
</div>
</div>
<button
onClick={dismissNo3MFWarning}
className="text-amber-200/60 hover:text-amber-200 flex-shrink-0 p-1 -m-1"
title={t('archives.no3mfBanner.dismissLabel')}
aria-label={t('archives.no3mfBanner.dismissLabel')}
>
<X className="w-4 h-4" />
</button>
</div>
)}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
<div>
<div className="flex items-start gap-3">