import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { NumberInput, Switch, Button, Stack, Paper, Text, Loader, Group, Accordion, TextInput } from '@mantine/core'; import { alert } from '@app/components/toast'; import RestartConfirmationModal from '@app/components/shared/config/RestartConfirmationModal'; import { useRestartServer } from '@app/components/shared/config/useRestartServer'; import { useAdminSettings } from '@app/hooks/useAdminSettings'; import PendingBadge from '@app/components/shared/config/PendingBadge'; import apiClient from '@app/services/apiClient'; import { useLoginRequired } from '@app/hooks/useLoginRequired'; import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; interface AdvancedSettingsData { enableAlphaFunctionality?: boolean; maxDPI?: number; enableUrlToPDF?: boolean; tessdataDir?: string; disableSanitize?: boolean; tempFileManagement?: { baseTmpDir?: string; libreofficeDir?: string; systemTempDir?: string; prefix?: string; maxAgeHours?: number; cleanupIntervalMinutes?: number; startupCleanup?: boolean; cleanupSystemTemp?: boolean; }; processExecutor?: { sessionLimit?: { libreOfficeSessionLimit?: number; pdfToHtmlSessionLimit?: number; qpdfSessionLimit?: number; tesseractSessionLimit?: number; pythonOpenCvSessionLimit?: number; weasyPrintSessionLimit?: number; installAppSessionLimit?: number; calibreSessionLimit?: number; ghostscriptSessionLimit?: number; ocrMyPdfSessionLimit?: number; }; timeoutMinutes?: { libreOfficetimeoutMinutes?: number; pdfToHtmltimeoutMinutes?: number; pythonOpenCvtimeoutMinutes?: number; weasyPrinttimeoutMinutes?: number; installApptimeoutMinutes?: number; calibretimeoutMinutes?: number; tesseractTimeoutMinutes?: number; qpdfTimeoutMinutes?: number; ghostscriptTimeoutMinutes?: number; ocrMyPdfTimeoutMinutes?: number; }; }; } export default function AdminAdvancedSection() { const { t } = useTranslation(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { settings, setSettings, loading, saving, fetchSettings, saveSettings, isFieldPending, } = useAdminSettings({ sectionName: 'advanced', fetchTransformer: async () => { const [systemResponse, processExecutorResponse] = await Promise.all([ apiClient.get('/api/v1/admin/settings/section/system'), apiClient.get('/api/v1/admin/settings/section/processExecutor') ]); const systemData = systemResponse.data || {}; const processExecutorData = processExecutorResponse.data || {}; const result: any = { enableAlphaFunctionality: systemData.enableAlphaFunctionality || false, maxDPI: systemData.maxDPI || 0, enableUrlToPDF: systemData.enableUrlToPDF || false, tessdataDir: systemData.tessdataDir || '', disableSanitize: systemData.disableSanitize || false, tempFileManagement: systemData.tempFileManagement || { baseTmpDir: '', libreofficeDir: '', systemTempDir: '', prefix: 'stirling-pdf-', maxAgeHours: 24, cleanupIntervalMinutes: 30, startupCleanup: true, cleanupSystemTemp: false }, processExecutor: processExecutorData || {} }; // Merge pending blocks from both endpoints const pendingBlock: any = {}; if (systemData._pending?.enableAlphaFunctionality !== undefined) { pendingBlock.enableAlphaFunctionality = systemData._pending.enableAlphaFunctionality; } if (systemData._pending?.maxDPI !== undefined) { pendingBlock.maxDPI = systemData._pending.maxDPI; } if (systemData._pending?.enableUrlToPDF !== undefined) { pendingBlock.enableUrlToPDF = systemData._pending.enableUrlToPDF; } if (systemData._pending?.tessdataDir !== undefined) { pendingBlock.tessdataDir = systemData._pending.tessdataDir; } if (systemData._pending?.disableSanitize !== undefined) { pendingBlock.disableSanitize = systemData._pending.disableSanitize; } if (systemData._pending?.tempFileManagement) { pendingBlock.tempFileManagement = systemData._pending.tempFileManagement; } if (processExecutorData._pending) { pendingBlock.processExecutor = processExecutorData._pending; } if (Object.keys(pendingBlock).length > 0) { result._pending = pendingBlock; } return result; }, saveTransformer: (settings) => { const deltaSettings: Record = { 'system.enableAlphaFunctionality': settings.enableAlphaFunctionality, 'system.maxDPI': settings.maxDPI, 'system.enableUrlToPDF': settings.enableUrlToPDF, 'system.tessdataDir': settings.tessdataDir, 'system.disableSanitize': settings.disableSanitize }; // Add temp file management settings if (settings.tempFileManagement) { deltaSettings['system.tempFileManagement.baseTmpDir'] = settings.tempFileManagement.baseTmpDir; deltaSettings['system.tempFileManagement.libreofficeDir'] = settings.tempFileManagement.libreofficeDir; deltaSettings['system.tempFileManagement.systemTempDir'] = settings.tempFileManagement.systemTempDir; deltaSettings['system.tempFileManagement.prefix'] = settings.tempFileManagement.prefix; deltaSettings['system.tempFileManagement.maxAgeHours'] = settings.tempFileManagement.maxAgeHours; deltaSettings['system.tempFileManagement.cleanupIntervalMinutes'] = settings.tempFileManagement.cleanupIntervalMinutes; deltaSettings['system.tempFileManagement.startupCleanup'] = settings.tempFileManagement.startupCleanup; deltaSettings['system.tempFileManagement.cleanupSystemTemp'] = settings.tempFileManagement.cleanupSystemTemp; } // Add process executor settings if (settings.processExecutor?.sessionLimit) { Object.entries(settings.processExecutor.sessionLimit).forEach(([key, value]) => { deltaSettings[`processExecutor.sessionLimit.${key}`] = value; }); } if (settings.processExecutor?.timeoutMinutes) { Object.entries(settings.processExecutor.timeoutMinutes).forEach(([key, value]) => { deltaSettings[`processExecutor.timeoutMinutes.${key}`] = value; }); } return { sectionData: {}, deltaSettings }; } }); useEffect(() => { if (loginEnabled) { fetchSettings(); } }, [loginEnabled]); const handleSave = async () => { if (!validateLoginEnabled()) { return; } try { await saveSettings(); showRestartModal(); } catch (_error) { alert({ alertType: 'error', title: t('admin.error', 'Error'), body: t('admin.settings.saveError', 'Failed to save settings'), }); } }; const actualLoading = loginEnabled ? loading : false; if (actualLoading) { return ( ); } return (
{t('admin.settings.advanced.title', 'Advanced')} {t('admin.settings.advanced.description', 'Configure advanced features and experimental functionality.')}
{/* Feature Flags */} {t('admin.settings.advanced.features', 'Feature Flags')}
{t('admin.settings.advanced.enableAlphaFunctionality.label', 'Enable Alpha Features')} {t('admin.settings.advanced.enableAlphaFunctionality.description', 'Enable experimental and alpha-stage features (may be unstable)')}
{ if (!loginEnabled) return; setSettings({ ...settings, enableAlphaFunctionality: e.target.checked }); }} disabled={!loginEnabled} styles={getDisabledStyles()} />
{t('admin.settings.advanced.enableUrlToPDF.label', 'Enable URL to PDF')} {t('admin.settings.advanced.enableUrlToPDF.description', 'Allow conversion of web pages to PDF documents (internal use only)')}
{ if (!loginEnabled) return; setSettings({ ...settings, enableUrlToPDF: e.target.checked }); }} disabled={!loginEnabled} styles={getDisabledStyles()} />
{t('admin.settings.advanced.disableSanitize.label', 'Disable HTML Sanitization')} {t('admin.settings.advanced.disableSanitize.description', 'Disable HTML sanitization (WARNING: Security risk - can lead to XSS injections)')}
{ if (!loginEnabled) return; setSettings({ ...settings, disableSanitize: e.target.checked }); }} disabled={!loginEnabled} styles={getDisabledStyles()} />
{/* Processing Settings */} {t('admin.settings.advanced.processing', 'Processing')}
{t('admin.settings.advanced.maxDPI.label', 'Maximum DPI')} } description={t('admin.settings.advanced.maxDPI.description', 'Maximum DPI for image processing (0 = unlimited)')} value={settings.maxDPI || 0} onChange={(value) => setSettings({ ...settings, maxDPI: Number(value) })} min={0} max={3000} disabled={!loginEnabled} />
{t('admin.settings.advanced.tessdataDir.label', 'Tessdata Directory')} } description={t('admin.settings.advanced.tessdataDir.description', 'Path to the directory containing Tessdata files for OCR')} value={settings.tessdataDir || ''} onChange={(e) => setSettings({ ...settings, tessdataDir: e.target.value })} placeholder="/usr/share/tessdata" disabled={!loginEnabled} />
{/* Temp File Management */}
{t('admin.settings.advanced.tempFileManagement.label', 'Temp File Management')} {t('admin.settings.advanced.tempFileManagement.description', 'Configure temporary file storage and cleanup behavior')}
setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, baseTmpDir: e.target.value } })} placeholder="Default: java.io.tmpdir/stirling-pdf" disabled={!loginEnabled} />
setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, libreofficeDir: e.target.value } })} placeholder="Default: baseTmpDir/libreoffice" disabled={!loginEnabled} />
setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, systemTempDir: e.target.value } })} placeholder="System temp directory path" disabled={!loginEnabled} />
setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, prefix: e.target.value } })} placeholder="stirling-pdf-" disabled={!loginEnabled} />
setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, maxAgeHours: Number(value) } })} min={1} max={720} disabled={!loginEnabled} />
setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, cleanupIntervalMinutes: Number(value) } })} min={1} max={1440} disabled={!loginEnabled} />
{t('admin.settings.advanced.tempFileManagement.startupCleanup.label', 'Startup Cleanup')} {t('admin.settings.advanced.tempFileManagement.startupCleanup.description', 'Clean up old temp files on application startup')}
{ if (!loginEnabled) return; setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, startupCleanup: e.target.checked } }); }} disabled={!loginEnabled} styles={getDisabledStyles()} />
{t('admin.settings.advanced.tempFileManagement.cleanupSystemTemp.label', 'Cleanup System Temp')} {t('admin.settings.advanced.tempFileManagement.cleanupSystemTemp.description', 'Whether to clean broader system temp directory (use with caution)')}
{ if (!loginEnabled) return; setSettings({ ...settings, tempFileManagement: { ...settings.tempFileManagement, cleanupSystemTemp: e.target.checked } }); }} disabled={!loginEnabled} styles={getDisabledStyles()} />
{/* Process Executor Limits */} {t('admin.settings.advanced.processExecutor.label', 'Process Executor Limits')} {t('admin.settings.advanced.processExecutor.description', 'Configure session limits and timeouts for each process executor')} {/* LibreOffice */} {t('admin.settings.advanced.processExecutor.libreOffice', 'LibreOffice')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, libreOfficeSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, libreOfficetimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* PDF to HTML */} {t('admin.settings.advanced.processExecutor.pdfToHtml', 'PDF to HTML')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, pdfToHtmlSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, pdfToHtmltimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* QPDF */} {t('admin.settings.advanced.processExecutor.qpdf', 'QPDF')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, qpdfSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, qpdfTimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* Tesseract OCR */} {t('admin.settings.advanced.processExecutor.tesseract', 'Tesseract OCR')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, tesseractSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, tesseractTimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* Python OpenCV */} {t('admin.settings.advanced.processExecutor.pythonOpenCv', 'Python OpenCV')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, pythonOpenCvSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, pythonOpenCvtimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* WeasyPrint */} {t('admin.settings.advanced.processExecutor.weasyPrint', 'WeasyPrint')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, weasyPrintSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, weasyPrinttimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* Install App */} {t('admin.settings.advanced.processExecutor.installApp', 'Install App')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, installAppSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, installApptimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* Calibre */} {t('admin.settings.advanced.processExecutor.calibre', 'Calibre')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, calibreSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, calibretimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* Ghostscript */} {t('admin.settings.advanced.processExecutor.ghostscript', 'Ghostscript')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, ghostscriptSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, ghostscriptTimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* OCRmyPDF */} {t('admin.settings.advanced.processExecutor.ocrMyPdf', 'OCRmyPDF')} setSettings({ ...settings, processExecutor: { ...settings.processExecutor, sessionLimit: { ...settings.processExecutor?.sessionLimit, ocrMyPdfSessionLimit: Number(value) } } })} min={1} max={100} disabled={!loginEnabled} /> setSettings({ ...settings, processExecutor: { ...settings.processExecutor, timeoutMinutes: { ...settings.processExecutor?.timeoutMinutes, ocrMyPdfTimeoutMinutes: Number(value) } } })} min={1} max={240} disabled={!loginEnabled} /> {/* Save Button */} {/* Restart Confirmation Modal */}
); }