import { useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { Server, Database, HardDrive, Cpu, MemoryStick, Printer, Archive, Clock, CheckCircle2, XCircle, Loader2, RefreshCw, Plug, FolderKanban, Palette, Bug, Download, Headphones, FolderOpen, Stethoscope, HeartPulse, } from 'lucide-react'; import { api, supportApi, type Printer as PrinterModel } from '../api/client'; import { Card } from '../components/Card'; import { LogViewer } from '../components/LogViewer'; import { ConnectionDiagnosticModal } from '../components/ConnectionDiagnostic'; import { SystemHealthPanel } from '../components/SystemHealthPanel'; import { formatDateTime, type TimeFormat } from '../utils/date'; function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; } function StatCard({ icon: Icon, label, value, subValue, color = 'text-bambu-green', }: { icon: React.ElementType; label: string; value: string | number; subValue?: string; color?: string; }) { return (

{label}

{value}

{subValue &&

{subValue}

}
); } function ProgressBar({ percent, color = 'bg-bambu-green' }: { percent: number; color?: string }) { return (
); } function Section({ title, icon: Icon, children, }: { title: string; icon: React.ElementType; children: React.ReactNode; }) { return (

{title}

{children}
); } export function SystemInfoPage() { const { t } = useTranslation(); const queryClient = useQueryClient(); const [bundleError, setBundleError] = useState(null); const [bundleDownloading, setBundleDownloading] = useState(false); const [debugToggling, setDebugToggling] = useState(false); const [diagnosticPrinter, setDiagnosticPrinter] = useState(null); const { data: systemInfo, isLoading, refetch, isFetching } = useQuery({ queryKey: ['systemInfo'], queryFn: api.getSystemInfo, refetchInterval: 30000, // Auto-refresh every 30 seconds }); const { data: debugLoggingState } = useQuery({ queryKey: ['debugLogging'], queryFn: supportApi.getDebugLoggingState, staleTime: 10 * 1000, // 10 seconds refetchInterval: 10 * 1000, }); const { data: settings } = useQuery({ queryKey: ['settings'], queryFn: api.getSettings, }); const { data: libraryStats } = useQuery({ queryKey: ['library-stats'], queryFn: api.getLibraryStats, }); const { data: allPrinters } = useQuery({ queryKey: ['printers'], queryFn: api.getPrinters, }); const { data: systemHealth, refetch: refetchHealth, isFetching: healthFetching, } = useQuery({ queryKey: ['systemHealth'], queryFn: api.getSystemHealth, staleTime: 60 * 1000, }); const timeFormat: TimeFormat = settings?.time_format || 'system'; const handleToggleDebugLogging = async () => { setDebugToggling(true); try { const newState = await supportApi.setDebugLogging(!debugLoggingState?.enabled); // Immediately update the cache with the new state (includes fresh enabled_at timestamp) queryClient.setQueryData(['debugLogging'], newState); } catch (err) { console.error('Failed to toggle debug logging:', err); } finally { setDebugToggling(false); } }; const handleDownloadBundle = async () => { setBundleError(null); setBundleDownloading(true); try { await supportApi.downloadSupportBundle(); } catch (err) { setBundleError(err instanceof Error ? err.message : 'Failed to download support bundle'); } finally { setBundleDownloading(false); } }; if (isLoading) { return (
); } if (!systemInfo) { return (
{t('system.failedToLoad', 'Failed to load system information')}
); } const diskColor = systemInfo.storage.disk_percent_used > 90 ? 'bg-red-500' : systemInfo.storage.disk_percent_used > 75 ? 'bg-yellow-500' : 'bg-bambu-green'; const memoryColor = systemInfo.memory.percent_used > 90 ? 'bg-red-500' : systemInfo.memory.percent_used > 75 ? 'bg-yellow-500' : 'bg-bambu-green'; return (
{/* Header */}

{t('system.title', 'System Information')}

{t('system.subtitle', 'Monitor system resources and database statistics')}

{/* Application Info */}
{/* Support & Troubleshooting */}

{t('support.description', 'Enable debug logging to capture detailed information, then download a support bundle to share when reporting issues.')}

{/* Debug Logging Toggle */}

{t('support.debugLogging', 'Debug Logging')}

{debugLoggingState?.enabled ? t('support.debugLoggingEnabled', 'Capturing detailed logs') : t('support.debugLoggingDisabled', 'Normal logging level')} {debugLoggingState?.enabled && debugLoggingState.duration_seconds !== null && ( ({Math.floor(debugLoggingState.duration_seconds / 60)}m {debugLoggingState.duration_seconds % 60}s) )}

{/* Support Bundle Download */}

{t('support.supportBundle', 'Support Bundle')}

{t('support.supportBundleDescription', 'Download system info and logs as a ZIP file')}

{/* Progress indicator — bundle generation now runs connection + virtual-printer diagnostics and the log-health scan before writing the ZIP (#1506 follow-up), so the wait is longer than a pure file-export. List what's running so it's not opaque. */} {bundleDownloading && (

{t('support.bundleGenerating', 'Generating...')}

  • {t('support.bundleStepConnection', 'Running printer connectivity checks')}
  • {t('support.bundleStepVirtualPrinters', 'Running virtual-printer setup checks')}
  • {t('support.bundleStepLogScan', 'Scanning recent logs for known issues')}
  • {t('support.bundleStepBuild', 'Building the support bundle ZIP')}
)} {/* Error message */} {bundleError && (
{bundleError}
)} {/* Instructions */} {!debugLoggingState?.enabled && (

{t('support.instructions', 'To report an issue:')}
1. {t('support.step1', 'Enable debug logging')}
2. {t('support.step2', 'Reproduce the issue')}
3. {t('support.step3', 'Download the support bundle')}
4. {t('support.step4', 'Attach the ZIP file to your issue report')}

)} {/* Privacy Info */}

{t('support.privacyTitle', 'What\'s in the support bundle?')}

{t('support.collected', 'Collected:')}

  • • {t('support.collectItem1', 'App version and debug mode')}
  • • {t('support.collectItem2', 'OS, architecture, Python version')}
  • • {t('support.collectItem3', 'Database statistics (counts only)')}
  • • {t('support.collectItem4', 'Printer models and nozzle counts')}
  • • {t('support.collectItem5', 'Non-sensitive settings (themes, formats)')}
  • • {t('support.collectItem6', 'Debug logs (sanitized)')}
  • • {t('support.collectItem7', 'Printer connectivity and firmware versions')}
  • • {t('support.collectItem8', 'Integration status (Spoolman, MQTT, HA)')}
  • • {t('support.collectItem9', 'Network interfaces (subnets only)')}
  • • {t('support.collectItem10', 'Python package versions')}
  • • {t('support.collectItem11', 'Database health checks')}
  • • {t('support.collectItem12', 'Docker environment details')}

{t('support.notCollected', 'NOT collected:')}

  • • {t('support.notItem1', 'Printer names and serial numbers')}
  • • {t('support.notItem2', 'Access codes and passwords')}
  • • {t('support.notItem3', 'Email addresses')}
  • • {t('support.notItem4', 'API keys and tokens')}
  • • {t('support.notItem5', 'Webhook URLs')}
  • • {t('support.notItem6', 'Your hostname or username')}
  • • {t('support.notItem7', 'IP addresses')}

{t('support.privacyNote', 'Email addresses in logs are replaced with [EMAIL], printer names with [PRINTER], serial numbers with [SERIAL], and IP addresses with [IP].')}

{/* Log Viewer */}
{/* Connection Diagnostic */}

{t( 'diagnostic.sectionDescription', "Check why a printer won't connect or won't print — port reachability, LAN developer mode, Docker network mode, and credentials.", )}

{allPrinters && allPrinters.length > 0 ? (
{allPrinters.map((printer) => (
{printer.name} {printer.ip_address}
))}
) : (

{t('diagnostic.noPrinters', 'No printers configured.')}

)}
{/* System Health */}

{t('systemHealth.sectionDescription')}

{systemHealth ? ( ) : (

{t('common.loading')}

)}
{/* Database Stats */}
{/* Connected Printers */}
{systemInfo.printers.connected}
{t('system.ofTotal', 'of {{total}} printers connected', { total: systemInfo.printers.total, })}
{systemInfo.printers.connected_list.length > 0 ? (
{systemInfo.printers.connected_list.map((printer) => (
{printer.name}
{printer.model} {printer.state}
))}
) : (

{t('system.noPrintersConnected', 'No printers connected')}

)}
{/* Storage */}
{t('system.diskUsage', 'Disk Usage')} {systemInfo.storage.disk_used_formatted} / {systemInfo.storage.disk_total_formatted}

{systemInfo.storage.disk_free_formatted} {t('system.free', 'free')} ( {(100 - systemInfo.storage.disk_percent_used).toFixed(1)}%)

{libraryStats && ( )}
{/* System Resources */}
{/* Memory */}
{t('system.memoryUsage', 'Memory Usage')} {systemInfo.memory.used_formatted} / {systemInfo.memory.total_formatted}

{systemInfo.memory.available_formatted} {t('system.available', 'available')}

{/* CPU */}
{/* System Details */}
{diagnosticPrinter && ( setDiagnosticPrinter(null)} /> )}
); }