From 0da1ae06d96489ec7fafe1cd29f9865cab24ff6e Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 12 Nov 2025 15:05:27 +0000 Subject: [PATCH] init --- .../filter/UserAuthenticationFilter.java | 3 +- .../core/components/shared/AppConfigModal.tsx | 21 +-- .../components/shared/FirstLoginModal.tsx | 2 +- .../shared/config/LoginRequiredBanner.tsx | 38 +++++ .../shared/config/configNavSections.tsx | 56 ++++--- .../configSections/AdminAdvancedSection.tsx | 94 ++++++++++-- .../configSections/AdminAuditSection.tsx | 36 +++-- .../AdminConnectionsSection.tsx | 91 +++++++---- .../configSections/AdminDatabaseSection.tsx | 55 +++++-- .../configSections/AdminEndpointsSection.tsx | 35 ++++- .../configSections/AdminFeaturesSection.tsx | 47 ++++-- .../configSections/AdminGeneralSection.tsx | 76 +++++++--- .../configSections/AdminLegalSection.tsx | 24 ++- .../configSections/AdminPremiumSection.tsx | 27 +++- .../configSections/AdminPrivacySection.tsx | 50 ++++-- .../configSections/AdminSecuritySection.tsx | 142 +++++++++++------- .../configSections/AdminUsageSection.tsx | 74 ++++++++- .../config/configSections/ProviderCard.tsx | 10 +- .../audit/AuditChartsSection.tsx | 35 ++++- .../configSections/audit/AuditEventsTable.tsx | 61 +++++++- .../audit/AuditExportSection.tsx | 17 ++- .../configSections/audit/AuditFiltersForm.tsx | 8 +- frontend/src/core/hooks/useLoginRequired.ts | 89 +++++++++++ frontend/src/core/services/accountService.ts | 10 ++ .../shared/config/configNavSections.tsx | 5 +- 25 files changed, 878 insertions(+), 228 deletions(-) create mode 100644 frontend/src/core/components/shared/config/LoginRequiredBanner.tsx create mode 100644 frontend/src/core/hooks/useLoginRequired.ts diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index b36dc36cc..cdf7bdd0d 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -107,7 +107,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { } } - // If we still don't have any authentication, check if it's a public endpoint. If not, deny the request + // If we still don't have any authentication, check if it's a public endpoint. If not, deny + // the request if (authentication == null || !authentication.isAuthenticated()) { String method = request.getMethod(); String contextPath = request.getContextPath(); diff --git a/frontend/src/core/components/shared/AppConfigModal.tsx b/frontend/src/core/components/shared/AppConfigModal.tsx index a080ca3c7..34195bd93 100644 --- a/frontend/src/core/components/shared/AppConfigModal.tsx +++ b/frontend/src/core/components/shared/AppConfigModal.tsx @@ -64,19 +64,21 @@ const AppConfigModal: React.FC = ({ opened, onClose }) => { headerBorder: 'var(--modal-header-border)', }), []); - // Get isAdmin and runningEE from app config + // Get isAdmin, runningEE, and enableLogin from app config const isAdmin = config?.isAdmin ?? false; const runningEE = config?.runningEE ?? false; + const loginEnabled = config?.enableLogin ?? true; - console.log('[AppConfigModal] Config:', { isAdmin, runningEE, fullConfig: config }); + console.log('[AppConfigModal] Config:', { isAdmin, runningEE, loginEnabled, fullConfig: config }); // Left navigation structure and icons const configNavSections = useMemo(() => createConfigNavSections( isAdmin, - runningEE + runningEE, + loginEnabled ), - [isAdmin, runningEE] + [isAdmin, runningEE, loginEnabled] ); const activeLabel = useMemo(() => { @@ -143,16 +145,15 @@ const AppConfigModal: React.FC = ({ opened, onClose }) => {
{ - if (!isDisabled) { - setActive(item.key); - navigate(`/settings/${item.key}`); - } + // Allow navigation even when disabled - the content inside will be disabled + setActive(item.key); + navigate(`/settings/${item.key}`); }} className={`modal-nav-item ${isMobile ? 'mobile' : ''}`} style={{ background: isActive ? colors.navItemActiveBg : 'transparent', - opacity: isDisabled ? 0.5 : 1, - cursor: isDisabled ? 'not-allowed' : 'pointer', + opacity: isDisabled ? 0.6 : 1, + cursor: 'pointer', }} > diff --git a/frontend/src/core/components/shared/FirstLoginModal.tsx b/frontend/src/core/components/shared/FirstLoginModal.tsx index fa3e42bae..7cd034edd 100644 --- a/frontend/src/core/components/shared/FirstLoginModal.tsx +++ b/frontend/src/core/components/shared/FirstLoginModal.tsx @@ -52,7 +52,7 @@ export default function FirstLoginModal({ opened, onPasswordChanged, username }: setLoading(true); setError(''); - await accountService.changePassword(currentPassword, newPassword); + await accountService.changePasswordOnLogin(currentPassword, newPassword); alert({ alertType: 'success', diff --git a/frontend/src/core/components/shared/config/LoginRequiredBanner.tsx b/frontend/src/core/components/shared/config/LoginRequiredBanner.tsx new file mode 100644 index 000000000..de611b64b --- /dev/null +++ b/frontend/src/core/components/shared/config/LoginRequiredBanner.tsx @@ -0,0 +1,38 @@ +import { Alert, Text } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import LocalIcon from '@app/components/shared/LocalIcon'; + +interface LoginRequiredBannerProps { + show: boolean; +} + +/** + * Banner component that displays when login mode is required but not enabled + * Shows prominent warning that settings are read-only + */ +export default function LoginRequiredBanner({ show }: LoginRequiredBannerProps) { + const { t } = useTranslation(); + + if (!show) return null; + + return ( + } + title={t('admin.settings.loginDisabled.title', 'Login Mode Required')} + color="blue" + variant="light" + styles={{ + root: { + borderLeft: '4px solid var(--mantine-color-blue-6)' + } + }} + > + + {t('admin.settings.loginDisabled.message', 'Login mode must be enabled to modify admin settings. Please set SECURITY_ENABLELOGIN=true in your environment or security.enableLogin: true in settings.yml, then restart the server.')} + + + {t('admin.settings.loginDisabled.readOnly', 'The settings below show example values for reference. Enable login mode to view and edit actual configuration.')} + + + ); +} diff --git a/frontend/src/core/components/shared/config/configNavSections.tsx b/frontend/src/core/components/shared/config/configNavSections.tsx index cbef8d27e..e9f97aafb 100644 --- a/frontend/src/core/components/shared/config/configNavSections.tsx +++ b/frontend/src/core/components/shared/config/configNavSections.tsx @@ -41,7 +41,8 @@ export interface ConfigColors { export const createConfigNavSections = ( isAdmin: boolean = false, - runningEE: boolean = false + runningEE: boolean = false, + loginEnabled: boolean = true ): ConfigNavSection[] => { const sections: ConfigNavSection[] = [ { @@ -63,8 +64,9 @@ export const createConfigNavSections = ( }, ]; - // Add Admin sections if user is admin - if (isAdmin) { + // Add Admin sections if user is admin OR if login is disabled (but mark as disabled) + if (isAdmin || !loginEnabled) { + const requiresLogin = !loginEnabled; // Configuration sections.push({ title: 'Configuration', @@ -73,31 +75,41 @@ export const createConfigNavSections = ( key: 'adminGeneral', label: 'System Settings', icon: 'settings-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, { key: 'adminFeatures', label: 'Features', icon: 'extension-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, { key: 'adminEndpoints', label: 'Endpoints', icon: 'api-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, { key: 'adminDatabase', label: 'Database', icon: 'storage-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, { key: 'adminAdvanced', label: 'Advanced', icon: 'tune-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, ], }); @@ -110,13 +122,17 @@ export const createConfigNavSections = ( key: 'adminSecurity', label: 'Security', icon: 'shield-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, { key: 'adminConnections', label: 'Connections', icon: 'link-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, ], }); @@ -129,23 +145,25 @@ export const createConfigNavSections = ( key: 'adminPremium', label: 'Premium', icon: 'star-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, { key: 'adminAudit', label: 'Audit', icon: 'fact-check-rounded', component: , - disabled: !runningEE, - disabledTooltip: 'Requires Enterprise license' + disabled: !runningEE || requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : 'Requires Enterprise license' }, { key: 'adminUsage', label: 'Usage Analytics', icon: 'analytics-rounded', component: , - disabled: !runningEE, - disabledTooltip: 'Requires Enterprise license' + disabled: !runningEE || requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : 'Requires Enterprise license' }, ], }); @@ -158,13 +176,17 @@ export const createConfigNavSections = ( key: 'adminLegal', label: 'Legal', icon: 'gavel-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, { key: 'adminPrivacy', label: 'Privacy', icon: 'visibility-rounded', - component: + component: , + disabled: requiresLogin, + disabledTooltip: requiresLogin ? 'Enable login mode first' : undefined }, ], }); diff --git a/frontend/src/core/components/shared/config/configSections/AdminAdvancedSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminAdvancedSection.tsx index ffb9c8d0c..15ee65b3d 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminAdvancedSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminAdvancedSection.tsx @@ -7,6 +7,8 @@ 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; @@ -55,6 +57,7 @@ interface AdvancedSettingsData { export default function AdminAdvancedSection() { const { t } = useTranslation(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { settings, @@ -165,10 +168,15 @@ export default function AdminAdvancedSection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + }, [loginEnabled]); const handleSave = async () => { + if (!validateLoginEnabled()) { + return; + } try { await saveSettings(); showRestartModal(); @@ -181,7 +189,9 @@ export default function AdminAdvancedSection() { } }; - if (loading) { + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return ( @@ -191,6 +201,7 @@ export default function AdminAdvancedSection() { return ( +
{t('admin.settings.advanced.title', 'Advanced')} @@ -213,7 +224,12 @@ export default function AdminAdvancedSection() { setSettings({ ...settings, enableAlphaFunctionality: e.target.checked })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, enableAlphaFunctionality: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -229,7 +245,12 @@ export default function AdminAdvancedSection() { setSettings({ ...settings, enableUrlToPDF: e.target.checked })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, enableUrlToPDF: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -245,7 +266,12 @@ export default function AdminAdvancedSection() { setSettings({ ...settings, disableSanitize: e.target.checked })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, disableSanitize: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -271,6 +297,7 @@ export default function AdminAdvancedSection() { onChange={(value) => setSettings({ ...settings, maxDPI: Number(value) })} min={0} max={3000} + disabled={!loginEnabled} />
@@ -286,6 +313,7 @@ export default function AdminAdvancedSection() { value={settings.tessdataDir || ''} onChange={(e) => setSettings({ ...settings, tessdataDir: e.target.value })} placeholder="/usr/share/tessdata" + disabled={!loginEnabled} />
@@ -311,6 +339,7 @@ export default function AdminAdvancedSection() { tempFileManagement: { ...settings.tempFileManagement, baseTmpDir: e.target.value } })} placeholder="Default: java.io.tmpdir/stirling-pdf" + disabled={!loginEnabled} /> @@ -324,6 +353,7 @@ export default function AdminAdvancedSection() { tempFileManagement: { ...settings.tempFileManagement, libreofficeDir: e.target.value } })} placeholder="Default: baseTmpDir/libreoffice" + disabled={!loginEnabled} /> @@ -337,6 +367,7 @@ export default function AdminAdvancedSection() { tempFileManagement: { ...settings.tempFileManagement, systemTempDir: e.target.value } })} placeholder="System temp directory path" + disabled={!loginEnabled} /> @@ -350,6 +381,7 @@ export default function AdminAdvancedSection() { tempFileManagement: { ...settings.tempFileManagement, prefix: e.target.value } })} placeholder="stirling-pdf-" + disabled={!loginEnabled} /> @@ -364,6 +396,7 @@ export default function AdminAdvancedSection() { })} min={1} max={720} + disabled={!loginEnabled} /> @@ -378,6 +411,7 @@ export default function AdminAdvancedSection() { })} min={1} max={1440} + disabled={!loginEnabled} /> @@ -391,10 +425,15 @@ export default function AdminAdvancedSection() { setSettings({ - ...settings, - tempFileManagement: { ...settings.tempFileManagement, startupCleanup: e.target.checked } - })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ + ...settings, + tempFileManagement: { ...settings.tempFileManagement, startupCleanup: e.target.checked } + }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -410,10 +449,15 @@ export default function AdminAdvancedSection() { setSettings({ - ...settings, - tempFileManagement: { ...settings.tempFileManagement, cleanupSystemTemp: e.target.checked } - })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ + ...settings, + tempFileManagement: { ...settings.tempFileManagement, cleanupSystemTemp: e.target.checked } + }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -448,6 +492,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -485,6 +531,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -522,6 +570,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -559,6 +609,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -596,6 +648,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -633,6 +687,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -670,6 +726,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -707,6 +765,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -744,6 +804,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -781,6 +843,7 @@ export default function AdminAdvancedSection() { })} min={1} max={100} + disabled={!loginEnabled} /> @@ -805,7 +869,7 @@ export default function AdminAdvancedSection() { {/* Save Button */} - diff --git a/frontend/src/core/components/shared/config/configSections/AdminAuditSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminAuditSection.tsx index 23152bdd8..46d90d94d 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminAuditSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminAuditSection.tsx @@ -6,9 +6,12 @@ import AuditSystemStatus from '@app/components/shared/config/configSections/audi import AuditChartsSection from '@app/components/shared/config/configSections/audit/AuditChartsSection'; import AuditEventsTable from '@app/components/shared/config/configSections/audit/AuditEventsTable'; import AuditExportSection from '@app/components/shared/config/configSections/audit/AuditExportSection'; +import { useLoginRequired } from '@app/hooks/useLoginRequired'; +import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; const AdminAuditSection: React.FC = () => { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const [systemStatus, setSystemStatus] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -27,10 +30,24 @@ const AdminAuditSection: React.FC = () => { } }; - fetchSystemStatus(); - }, []); + if (loginEnabled) { + fetchSystemStatus(); + } else { + // Provide example audit system status when login is disabled + setSystemStatus({ + enabled: true, + level: 'INFO', + retentionDays: 90, + totalEvents: 1234, + }); + setLoading(false); + } + }, [loginEnabled]); - if (loading) { + // Override loading state when login is disabled + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return (
@@ -56,32 +73,33 @@ const AdminAuditSection: React.FC = () => { return ( + {systemStatus.enabled ? ( - + {t('audit.tabs.dashboard', 'Dashboard')} - + {t('audit.tabs.events', 'Audit Events')} - + {t('audit.tabs.export', 'Export')} - + - + - + ) : ( diff --git a/frontend/src/core/components/shared/config/configSections/AdminConnectionsSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminConnectionsSection.tsx index ab3dc7dc0..019771af3 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminConnectionsSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminConnectionsSection.tsx @@ -12,6 +12,8 @@ import { Provider, } from '@app/components/shared/config/configSections/providerDefinitions'; import apiClient from '@app/services/apiClient'; +import { useLoginRequired } from '@app/hooks/useLoginRequired'; +import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; interface ConnectionsSettingsData { oauth2?: { @@ -45,15 +47,10 @@ interface ConnectionsSettingsData { export default function AdminConnectionsSection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); - const { - settings, - setSettings, - loading, - fetchSettings, - isFieldPending, - } = useAdminSettings({ + const adminSettings = useAdminSettings({ sectionName: 'connections', fetchTransformer: async () => { // Fetch security settings (oauth2, saml2) @@ -106,57 +103,76 @@ export default function AdminConnectionsSection() { } }); + const { + settings, + setSettings, + loading, + fetchSettings, + isFieldPending, + } = adminSettings; + useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loginEnabled]); + + // Override loading state when login is disabled + const actualLoading = loginEnabled ? loading : false; const isProviderConfigured = (provider: Provider): boolean => { if (provider.id === 'saml2') { - return settings.saml2?.enabled === true; + return settings?.saml2?.enabled === true; } if (provider.id === 'smtp') { - return settings.mail?.enabled === true; + return settings?.mail?.enabled === true; } if (provider.id === 'oauth2-generic') { - return settings.oauth2?.enabled === true; + return settings?.oauth2?.enabled === true; } // Check if specific OAuth2 provider is configured (has clientId) - const providerSettings = settings.oauth2?.client?.[provider.id]; + const providerSettings = settings?.oauth2?.client?.[provider.id]; return !!(providerSettings?.clientId); }; const getProviderSettings = (provider: Provider): Record => { if (provider.id === 'saml2') { - return settings.saml2 || {}; + return settings?.saml2 || {}; } if (provider.id === 'smtp') { - return settings.mail || {}; + return settings?.mail || {}; } if (provider.id === 'oauth2-generic') { // Generic OAuth2 settings are at the root oauth2 level return { - enabled: settings.oauth2?.enabled, - provider: settings.oauth2?.provider, - issuer: settings.oauth2?.issuer, - clientId: settings.oauth2?.clientId, - clientSecret: settings.oauth2?.clientSecret, - scopes: settings.oauth2?.scopes, - useAsUsername: settings.oauth2?.useAsUsername, - autoCreateUser: settings.oauth2?.autoCreateUser, - blockRegistration: settings.oauth2?.blockRegistration, + enabled: settings?.oauth2?.enabled, + provider: settings?.oauth2?.provider, + issuer: settings?.oauth2?.issuer, + clientId: settings?.oauth2?.clientId, + clientSecret: settings?.oauth2?.clientSecret, + scopes: settings?.oauth2?.scopes, + useAsUsername: settings?.oauth2?.useAsUsername, + autoCreateUser: settings?.oauth2?.autoCreateUser, + blockRegistration: settings?.oauth2?.blockRegistration, }; } // Specific OAuth2 provider settings - return settings.oauth2?.client?.[provider.id] || {}; + return settings?.oauth2?.client?.[provider.id] || {}; }; const handleProviderSave = async (provider: Provider, providerSettings: Record) => { + // Block save if login is disabled + if (!validateLoginEnabled()) { + return; + } + try { if (provider.id === 'smtp') { // Mail settings use a different endpoint @@ -218,7 +234,12 @@ export default function AdminConnectionsSection() { }; const handleProviderDisconnect = async (provider: Provider) => { - try { + // Block disconnect if login is disabled + if (!validateLoginEnabled()) { + return; + } + + try{ if (provider.id === 'smtp') { // Mail settings use a different endpoint const response = await apiClient.put('/api/v1/admin/settings/section/mail', { enabled: false }); @@ -271,7 +292,7 @@ export default function AdminConnectionsSection() { } }; - if (loading) { + if (actualLoading) { return ( @@ -280,9 +301,14 @@ export default function AdminConnectionsSection() { } const handleSSOAutoLoginSave = async () => { + // Block save if login is disabled + if (!validateLoginEnabled()) { + return; + } + try { const deltaSettings = { - 'premium.proFeatures.ssoAutoLogin': settings.ssoAutoLogin + 'premium.proFeatures.ssoAutoLogin': settings?.ssoAutoLogin }; const response = await apiClient.put('/api/v1/admin/settings', { settings: deltaSettings }); @@ -311,6 +337,8 @@ export default function AdminConnectionsSection() { return ( + + {/* Header */}
@@ -341,11 +369,14 @@ export default function AdminConnectionsSection() {
{ + if (!loginEnabled) return; // Block change when login disabled setSettings({ ...settings, ssoAutoLogin: e.target.checked }); handleSSOAutoLoginSave(); }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -369,6 +400,7 @@ export default function AdminConnectionsSection() { settings={getProviderSettings(provider)} onSave={(providerSettings) => handleProviderSave(provider, providerSettings)} onDisconnect={() => handleProviderDisconnect(provider)} + disabled={!loginEnabled} /> ))}
@@ -392,6 +424,7 @@ export default function AdminConnectionsSection() { provider={provider} isConfigured={false} onSave={(providerSettings) => handleProviderSave(provider, providerSettings)} + disabled={!loginEnabled} /> ))}
diff --git a/frontend/src/core/components/shared/config/configSections/AdminDatabaseSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminDatabaseSection.tsx index a28953781..a463aa522 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminDatabaseSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminDatabaseSection.tsx @@ -6,6 +6,8 @@ import RestartConfirmationModal from '@app/components/shared/config/RestartConfi import { useRestartServer } from '@app/components/shared/config/useRestartServer'; import { useAdminSettings } from '@app/hooks/useAdminSettings'; import PendingBadge from '@app/components/shared/config/PendingBadge'; +import { useLoginRequired } from '@app/hooks/useLoginRequired'; +import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; import apiClient from '@app/services/apiClient'; interface DatabaseSettingsData { @@ -21,6 +23,7 @@ interface DatabaseSettingsData { export default function AdminDatabaseSection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -78,10 +81,17 @@ export default function AdminDatabaseSection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loginEnabled]); const handleSave = async () => { + if (!validateLoginEnabled()) { + return; + } + try { await saveSettings(); showRestartModal(); @@ -94,7 +104,10 @@ export default function AdminDatabaseSection() { } }; - if (loading) { + // Override loading state when login is disabled + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return ( @@ -104,6 +117,8 @@ export default function AdminDatabaseSection() { return ( + +
@@ -130,14 +145,19 @@ export default function AdminDatabaseSection() {
setSettings({ ...settings, enableCustomDatabase: e.target.checked })} + checked={settings?.enableCustomDatabase || false} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, enableCustomDatabase: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} />
- {settings.enableCustomDatabase && ( + {settings?.enableCustomDatabase && ( <>
} description={t('admin.settings.database.customUrl.description', 'Full JDBC connection string (e.g., jdbc:postgresql://localhost:5432/postgres). If provided, individual connection settings below are not used.')} - value={settings.customDatabaseUrl || ''} + value={settings?.customDatabaseUrl || ''} onChange={(e) => setSettings({ ...settings, customDatabaseUrl: e.target.value })} placeholder="jdbc:postgresql://localhost:5432/postgres" + disabled={!loginEnabled} />
@@ -163,7 +184,7 @@ export default function AdminDatabaseSection() { } description={t('admin.settings.database.type.description', 'Type of database (not used if custom URL is provided)')} - value={settings.type || 'postgresql'} + value={settings?.type || 'postgresql'} onChange={(value) => setSettings({ ...settings, type: value || 'postgresql' })} data={[ { value: 'postgresql', label: 'PostgreSQL' }, @@ -171,6 +192,7 @@ export default function AdminDatabaseSection() { { value: 'mysql', label: 'MySQL' }, { value: 'mariadb', label: 'MariaDB' } ]} + disabled={!loginEnabled} />
@@ -183,9 +205,10 @@ export default function AdminDatabaseSection() { } description={t('admin.settings.database.hostName.description', 'Database server hostname (not used if custom URL is provided)')} - value={settings.hostName || ''} + value={settings?.hostName || ''} onChange={(e) => setSettings({ ...settings, hostName: e.target.value })} placeholder="localhost" + disabled={!loginEnabled} /> @@ -198,10 +221,11 @@ export default function AdminDatabaseSection() { } description={t('admin.settings.database.port.description', 'Database server port (not used if custom URL is provided)')} - value={settings.port || 5432} + value={settings?.port || 5432} onChange={(value) => setSettings({ ...settings, port: Number(value) })} min={1} max={65535} + disabled={!loginEnabled} /> @@ -214,9 +238,10 @@ export default function AdminDatabaseSection() { } description={t('admin.settings.database.name.description', 'Name of the database (not used if custom URL is provided)')} - value={settings.name || ''} + value={settings?.name || ''} onChange={(e) => setSettings({ ...settings, name: e.target.value })} placeholder="postgres" + disabled={!loginEnabled} /> @@ -229,9 +254,10 @@ export default function AdminDatabaseSection() { } description={t('admin.settings.database.username.description', 'Database authentication username')} - value={settings.username || ''} + value={settings?.username || ''} onChange={(e) => setSettings({ ...settings, username: e.target.value })} placeholder="postgres" + disabled={!loginEnabled} /> @@ -244,9 +270,10 @@ export default function AdminDatabaseSection() { } description={t('admin.settings.database.password.description', 'Database authentication password')} - value={settings.password || ''} + value={settings?.password || ''} onChange={(e) => setSettings({ ...settings, password: e.target.value })} placeholder="••••••••" + disabled={!loginEnabled} /> @@ -256,7 +283,7 @@ export default function AdminDatabaseSection() { {/* Save Button */} - diff --git a/frontend/src/core/components/shared/config/configSections/AdminEndpointsSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminEndpointsSection.tsx index 56e51381b..24cf8bd77 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminEndpointsSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminEndpointsSection.tsx @@ -6,6 +6,8 @@ import RestartConfirmationModal from '@app/components/shared/config/RestartConfi import { useRestartServer } from '@app/components/shared/config/useRestartServer'; import { useAdminSettings } from '@app/hooks/useAdminSettings'; import PendingBadge from '@app/components/shared/config/PendingBadge'; +import { useLoginRequired } from '@app/hooks/useLoginRequired'; +import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; interface EndpointsSettingsData { toRemove?: string[]; @@ -14,6 +16,7 @@ interface EndpointsSettingsData { export default function AdminEndpointsSection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -29,10 +32,17 @@ export default function AdminEndpointsSection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loginEnabled]); const handleSave = async () => { + if (!validateLoginEnabled()) { + return; + } + try { await saveSettings(); showRestartModal(); @@ -45,7 +55,10 @@ export default function AdminEndpointsSection() { } }; - if (loading) { + // Override loading state when login is disabled + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return ( @@ -102,6 +115,8 @@ export default function AdminEndpointsSection() { return ( + +
{t('admin.settings.endpoints.title', 'API Endpoints')} @@ -123,12 +138,16 @@ export default function AdminEndpointsSection() { } description={t('admin.settings.endpoints.toRemove.description', 'Select individual endpoints to disable')} value={settings.toRemove || []} - onChange={(value) => setSettings({ ...settings, toRemove: value })} + onChange={(value) => { + if (!loginEnabled) return; + setSettings({ ...settings, toRemove: value }); + }} data={commonEndpoints.map(endpoint => ({ value: endpoint, label: endpoint }))} searchable clearable placeholder="Select endpoints to disable" comboboxProps={{ zIndex: 1400 }} + disabled={!loginEnabled} />
@@ -142,12 +161,16 @@ export default function AdminEndpointsSection() { } description={t('admin.settings.endpoints.groupsToRemove.description', 'Select endpoint groups to disable')} value={settings.groupsToRemove || []} - onChange={(value) => setSettings({ ...settings, groupsToRemove: value })} + onChange={(value) => { + if (!loginEnabled) return; + setSettings({ ...settings, groupsToRemove: value }); + }} data={commonGroups.map(group => ({ value: group, label: group }))} searchable clearable placeholder="Select groups to disable" comboboxProps={{ zIndex: 1400 }} + disabled={!loginEnabled} /> @@ -160,7 +183,7 @@ export default function AdminEndpointsSection() { - diff --git a/frontend/src/core/components/shared/config/configSections/AdminFeaturesSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminFeaturesSection.tsx index 72e19be1a..20ec8ed73 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminFeaturesSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminFeaturesSection.tsx @@ -7,6 +7,8 @@ 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 FeaturesSettingsData { serverCertificate?: { @@ -19,6 +21,7 @@ interface FeaturesSettingsData { export default function AdminFeaturesSection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -69,10 +72,15 @@ export default function AdminFeaturesSection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + }, [loginEnabled]); const handleSave = async () => { + if (!validateLoginEnabled()) { + return; + } try { await saveSettings(); showRestartModal(); @@ -85,7 +93,9 @@ export default function AdminFeaturesSection() { } }; - if (loading) { + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return ( @@ -95,6 +105,7 @@ export default function AdminFeaturesSection() { return ( +
{t('admin.settings.features.title', 'Features')} @@ -124,10 +135,15 @@ export default function AdminFeaturesSection() { setSettings({ - ...settings, - serverCertificate: { ...settings.serverCertificate, enabled: e.target.checked } - })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ + ...settings, + serverCertificate: { ...settings.serverCertificate, enabled: e.target.checked } + }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -148,6 +164,7 @@ export default function AdminFeaturesSection() { serverCertificate: { ...settings.serverCertificate, organizationName: e.target.value } })} placeholder="Stirling-PDF" + disabled={!loginEnabled} />
@@ -167,6 +184,7 @@ export default function AdminFeaturesSection() { })} min={1} max={3650} + disabled={!loginEnabled} /> @@ -180,10 +198,15 @@ export default function AdminFeaturesSection() { setSettings({ - ...settings, - serverCertificate: { ...settings.serverCertificate, regenerateOnStartup: e.target.checked } - })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ + ...settings, + serverCertificate: { ...settings.serverCertificate, regenerateOnStartup: e.target.checked } + }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -193,7 +216,7 @@ export default function AdminFeaturesSection() { {/* Save Button */} - diff --git a/frontend/src/core/components/shared/config/configSections/AdminGeneralSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminGeneralSection.tsx index 4f354a8a9..4777b90c3 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminGeneralSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminGeneralSection.tsx @@ -7,6 +7,8 @@ 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 GeneralSettingsData { ui: { @@ -40,6 +42,7 @@ interface GeneralSettingsData { export default function AdminGeneralSection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -108,14 +111,14 @@ export default function AdminGeneralSection() { saveTransformer: (settings) => { const deltaSettings: Record = { // UI settings - 'ui.appNameNavbar': settings.ui.appNameNavbar, - 'ui.languages': settings.ui.languages, + 'ui.appNameNavbar': settings.ui?.appNameNavbar, + 'ui.languages': settings.ui?.languages, // System settings - 'system.defaultLocale': settings.system.defaultLocale, - 'system.showUpdate': settings.system.showUpdate, - 'system.showUpdateOnlyAdmin': settings.system.showUpdateOnlyAdmin, - 'system.customHTMLFiles': settings.system.customHTMLFiles, - 'system.fileUploadLimit': settings.system.fileUploadLimit, + 'system.defaultLocale': settings.system?.defaultLocale, + 'system.showUpdate': settings.system?.showUpdate, + 'system.showUpdateOnlyAdmin': settings.system?.showUpdateOnlyAdmin, + 'system.customHTMLFiles': settings.system?.customHTMLFiles, + 'system.fileUploadLimit': settings.system?.fileUploadLimit, // Premium custom metadata 'premium.proFeatures.customMetadata.autoUpdateMetadata': settings.customMetadata?.autoUpdateMetadata, 'premium.proFeatures.customMetadata.author': settings.customMetadata?.author, @@ -124,10 +127,10 @@ export default function AdminGeneralSection() { }; if (settings.customPaths) { - deltaSettings['system.customPaths.pipeline.watchedFoldersDir'] = settings.customPaths.pipeline?.watchedFoldersDir; - deltaSettings['system.customPaths.pipeline.finishedFoldersDir'] = settings.customPaths.pipeline?.finishedFoldersDir; - deltaSettings['system.customPaths.operations.weasyprint'] = settings.customPaths.operations?.weasyprint; - deltaSettings['system.customPaths.operations.unoconvert'] = settings.customPaths.operations?.unoconvert; + deltaSettings['system.customPaths.pipeline.watchedFoldersDir'] = settings.customPaths?.pipeline?.watchedFoldersDir; + deltaSettings['system.customPaths.pipeline.finishedFoldersDir'] = settings.customPaths?.pipeline?.finishedFoldersDir; + deltaSettings['system.customPaths.operations.weasyprint'] = settings.customPaths?.operations?.weasyprint; + deltaSettings['system.customPaths.operations.unoconvert'] = settings.customPaths?.operations?.unoconvert; } return { @@ -138,10 +141,22 @@ export default function AdminGeneralSection() { }); useEffect(() => { - fetchSettings(); - }, []); + // Only fetch real settings if login is enabled + if (loginEnabled) { + fetchSettings(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loginEnabled]); + + // Override loading state when login is disabled + const actualLoading = loginEnabled ? loading : false; const handleSave = async () => { + // Block save if login is disabled + if (!validateLoginEnabled()) { + return; + } + try { await saveSettings(); showRestartModal(); @@ -154,7 +169,7 @@ export default function AdminGeneralSection() { } }; - if (loading) { + if (actualLoading) { return ( @@ -164,6 +179,8 @@ export default function AdminGeneralSection() { return ( + +
{t('admin.settings.general.title', 'System Settings')} @@ -185,9 +202,10 @@ export default function AdminGeneralSection() { } description={t('admin.settings.general.appNameNavbar.description', 'The name displayed in the navigation bar')} - value={settings.ui.appNameNavbar || ''} + value={settings.ui?.appNameNavbar || ''} onChange={(e) => setSettings({ ...settings, ui: { ...settings.ui, appNameNavbar: e.target.value } })} placeholder="Stirling PDF" + disabled={!loginEnabled} />
@@ -200,7 +218,7 @@ export default function AdminGeneralSection() { } description={t('admin.settings.general.languages.description', 'Limit which languages are available (empty = all languages)')} - value={settings.ui.languages || []} + value={settings.ui?.languages || []} onChange={(value) => setSettings({ ...settings, ui: { ...settings.ui, languages: value } })} data={[ { value: 'de_DE', label: 'Deutsch' }, @@ -218,6 +236,7 @@ export default function AdminGeneralSection() { clearable placeholder="Select languages" comboboxProps={{ zIndex: 1400 }} + disabled={!loginEnabled} /> @@ -230,9 +249,10 @@ export default function AdminGeneralSection() { } description={t('admin.settings.general.defaultLocale.description', 'The default language for new users (e.g., en_US, es_ES)')} - value={settings.system.defaultLocale || ''} + value={ settings.system?.defaultLocale || ''} onChange={(e) => setSettings({ ...settings, system: { ...settings.system, defaultLocale: e.target.value } })} placeholder="en_US" + disabled={!loginEnabled} /> @@ -245,9 +265,10 @@ export default function AdminGeneralSection() { } description={t('admin.settings.general.fileUploadLimit.description', 'Maximum file upload size (e.g., 100MB, 1GB)')} - value={settings.system.fileUploadLimit || ''} + value={ settings.system?.fileUploadLimit || ''} onChange={(e) => setSettings({ ...settings, system: { ...settings.system, fileUploadLimit: e.target.value } })} placeholder="100MB" + disabled={!loginEnabled} /> @@ -260,8 +281,9 @@ export default function AdminGeneralSection() { setSettings({ ...settings, system: { ...settings.system, showUpdate: e.target.checked } })} + disabled={!loginEnabled} /> @@ -276,8 +298,9 @@ export default function AdminGeneralSection() { setSettings({ ...settings, system: { ...settings.system, showUpdateOnlyAdmin: e.target.checked } })} + disabled={!loginEnabled} /> @@ -292,8 +315,9 @@ export default function AdminGeneralSection() { setSettings({ ...settings, system: { ...settings.system, customHTMLFiles: e.target.checked } })} + disabled={!loginEnabled} /> @@ -326,6 +350,7 @@ export default function AdminGeneralSection() { autoUpdateMetadata: e.target.checked } })} + disabled={!loginEnabled} /> @@ -349,6 +374,7 @@ export default function AdminGeneralSection() { } })} placeholder="username" + disabled={!loginEnabled} /> @@ -370,6 +396,7 @@ export default function AdminGeneralSection() { } })} placeholder="Stirling-PDF" + disabled={!loginEnabled} /> @@ -391,6 +418,7 @@ export default function AdminGeneralSection() { } })} placeholder="Stirling-PDF" + disabled={!loginEnabled} />
@@ -429,6 +457,7 @@ export default function AdminGeneralSection() { } })} placeholder="/pipeline/watchedFolders" + disabled={!loginEnabled} /> @@ -453,6 +482,7 @@ export default function AdminGeneralSection() { } })} placeholder="/pipeline/finishedFolders" + disabled={!loginEnabled} /> @@ -479,6 +509,7 @@ export default function AdminGeneralSection() { } })} placeholder="/opt/venv/bin/weasyprint" + disabled={!loginEnabled} /> @@ -503,6 +534,7 @@ export default function AdminGeneralSection() { } })} placeholder="/opt/venv/bin/unoconvert" + disabled={!loginEnabled} />
@@ -510,7 +542,7 @@ export default function AdminGeneralSection() { {/* Save Button */} - diff --git a/frontend/src/core/components/shared/config/configSections/AdminLegalSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminLegalSection.tsx index f47e52a07..a998bb4b4 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminLegalSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminLegalSection.tsx @@ -7,6 +7,8 @@ import RestartConfirmationModal from '@app/components/shared/config/RestartConfi import { useRestartServer } from '@app/components/shared/config/useRestartServer'; import { useAdminSettings } from '@app/hooks/useAdminSettings'; import PendingBadge from '@app/components/shared/config/PendingBadge'; +import { useLoginRequired } from '@app/hooks/useLoginRequired'; +import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; interface LegalSettingsData { termsAndConditions?: string; @@ -18,6 +20,7 @@ interface LegalSettingsData { export default function AdminLegalSection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -33,10 +36,15 @@ export default function AdminLegalSection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + }, [loginEnabled]); const handleSave = async () => { + if (!validateLoginEnabled()) { + return; + } try { await saveSettings(); showRestartModal(); @@ -49,7 +57,9 @@ export default function AdminLegalSection() { } }; - if (loading) { + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return ( @@ -59,6 +69,7 @@ export default function AdminLegalSection() { return ( +
{t('admin.settings.legal.title', 'Legal Documents')} @@ -95,6 +106,7 @@ export default function AdminLegalSection() { value={settings.termsAndConditions || ''} onChange={(e) => setSettings({ ...settings, termsAndConditions: e.target.value })} placeholder="https://example.com/terms" + disabled={!loginEnabled} />
@@ -110,6 +122,7 @@ export default function AdminLegalSection() { value={settings.privacyPolicy || ''} onChange={(e) => setSettings({ ...settings, privacyPolicy: e.target.value })} placeholder="https://example.com/privacy" + disabled={!loginEnabled} /> @@ -125,6 +138,7 @@ export default function AdminLegalSection() { value={settings.accessibilityStatement || ''} onChange={(e) => setSettings({ ...settings, accessibilityStatement: e.target.value })} placeholder="https://example.com/accessibility" + disabled={!loginEnabled} /> @@ -140,6 +154,7 @@ export default function AdminLegalSection() { value={settings.cookiePolicy || ''} onChange={(e) => setSettings({ ...settings, cookiePolicy: e.target.value })} placeholder="https://example.com/cookies" + disabled={!loginEnabled} /> @@ -155,13 +170,14 @@ export default function AdminLegalSection() { value={settings.impressum || ''} onChange={(e) => setSettings({ ...settings, impressum: e.target.value })} placeholder="https://example.com/impressum" + disabled={!loginEnabled} />
- diff --git a/frontend/src/core/components/shared/config/configSections/AdminPremiumSection.tsx b/frontend/src/core/components/shared/config/configSections/AdminPremiumSection.tsx index 8e18696cf..5c5de2f21 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminPremiumSection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminPremiumSection.tsx @@ -7,6 +7,8 @@ import RestartConfirmationModal from '@app/components/shared/config/RestartConfi import { useRestartServer } from '@app/components/shared/config/useRestartServer'; import { useAdminSettings } from '@app/hooks/useAdminSettings'; import PendingBadge from '@app/components/shared/config/PendingBadge'; +import { useLoginRequired } from '@app/hooks/useLoginRequired'; +import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; interface PremiumSettingsData { key?: string; @@ -15,6 +17,7 @@ interface PremiumSettingsData { export default function AdminPremiumSection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -30,10 +33,15 @@ export default function AdminPremiumSection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + }, [loginEnabled]); const handleSave = async () => { + if (!validateLoginEnabled()) { + return; + } try { await saveSettings(); showRestartModal(); @@ -46,7 +54,9 @@ export default function AdminPremiumSection() { } }; - if (loading) { + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return ( @@ -56,6 +66,7 @@ export default function AdminPremiumSection() { return ( +
{t('admin.settings.premium.title', 'Premium & Enterprise')} @@ -98,6 +109,7 @@ export default function AdminPremiumSection() { value={settings.key || ''} onChange={(e) => setSettings({ ...settings, key: e.target.value })} placeholder="00000000-0000-0000-0000-000000000000" + disabled={!loginEnabled} />
@@ -111,7 +123,12 @@ export default function AdminPremiumSection() { setSettings({ ...settings, enabled: e.target.checked })} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, enabled: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -120,7 +137,7 @@ export default function AdminPremiumSection() { - diff --git a/frontend/src/core/components/shared/config/configSections/AdminPrivacySection.tsx b/frontend/src/core/components/shared/config/configSections/AdminPrivacySection.tsx index 3f4a360a9..5db1276b9 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminPrivacySection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminPrivacySection.tsx @@ -6,6 +6,8 @@ import RestartConfirmationModal from '@app/components/shared/config/RestartConfi import { useRestartServer } from '@app/components/shared/config/useRestartServer'; import { useAdminSettings } from '@app/hooks/useAdminSettings'; import PendingBadge from '@app/components/shared/config/PendingBadge'; +import { useLoginRequired } from '@app/hooks/useLoginRequired'; +import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; import apiClient from '@app/services/apiClient'; interface PrivacySettingsData { @@ -16,6 +18,7 @@ interface PrivacySettingsData { export default function AdminPrivacySection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled, getDisabledStyles } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -76,10 +79,17 @@ export default function AdminPrivacySection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loginEnabled]); const handleSave = async () => { + if (!validateLoginEnabled()) { + return; + } + try { await saveSettings(); showRestartModal(); @@ -92,7 +102,10 @@ export default function AdminPrivacySection() { } }; - if (loading) { + // Override loading state when login is disabled + const actualLoading = loginEnabled ? loading : false; + + if (actualLoading) { return ( @@ -102,6 +115,8 @@ export default function AdminPrivacySection() { return ( + +
{t('admin.settings.privacy.title', 'Privacy')} @@ -123,8 +138,13 @@ export default function AdminPrivacySection() {
setSettings({ ...settings, enableAnalytics: e.target.checked })} + checked={settings?.enableAnalytics || false} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, enableAnalytics: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -139,8 +159,13 @@ export default function AdminPrivacySection() { setSettings({ ...settings, metricsEnabled: e.target.checked })} + checked={settings?.metricsEnabled || false} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, metricsEnabled: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -162,8 +187,13 @@ export default function AdminPrivacySection() { setSettings({ ...settings, googleVisibility: e.target.checked })} + checked={settings?.googleVisibility || false} + onChange={(e) => { + if (!loginEnabled) return; + setSettings({ ...settings, googleVisibility: e.target.checked }); + }} + disabled={!loginEnabled} + styles={getDisabledStyles()} /> @@ -173,7 +203,7 @@ export default function AdminPrivacySection() { {/* Save Button */} - diff --git a/frontend/src/core/components/shared/config/configSections/AdminSecuritySection.tsx b/frontend/src/core/components/shared/config/configSections/AdminSecuritySection.tsx index d9540937b..7c4b6cd55 100644 --- a/frontend/src/core/components/shared/config/configSections/AdminSecuritySection.tsx +++ b/frontend/src/core/components/shared/config/configSections/AdminSecuritySection.tsx @@ -8,6 +8,8 @@ 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 SecuritySettingsData { enableLogin?: boolean; @@ -44,6 +46,7 @@ interface SecuritySettingsData { export default function AdminSecuritySection() { const { t } = useTranslation(); + const { loginEnabled, validateLoginEnabled } = useLoginRequired(); const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer(); const { @@ -157,10 +160,21 @@ export default function AdminSecuritySection() { }); useEffect(() => { - fetchSettings(); - }, []); + if (loginEnabled) { + fetchSettings(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loginEnabled]); + + // Override loading state when login is disabled + const actualLoading = loginEnabled ? loading : false; const handleSave = async () => { + // Block save if login is disabled + if (!validateLoginEnabled()) { + return; + } + try { await saveSettings(); showRestartModal(); @@ -173,7 +187,7 @@ export default function AdminSecuritySection() { } }; - if (loading) { + if (actualLoading) { return ( @@ -183,6 +197,8 @@ export default function AdminSecuritySection() { return ( + +
{t('admin.settings.security.title', 'Security')} @@ -204,8 +220,9 @@ export default function AdminSecuritySection() {
setSettings({ ...settings, enableLogin: e.target.checked })} + disabled={!loginEnabled} /> @@ -215,7 +232,7 @@ export default function AdminSecuritySection() {