import React, { useState } from 'react'; import { Stack, Button, TextInput, Alert, Text } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { ServerConfig, SSOProviderConfig } from '@app/services/connectionModeService'; import { connectionModeService } from '@app/services/connectionModeService'; import LocalIcon from '@app/components/shared/LocalIcon'; interface ServerSelectionProps { onSelect: (config: ServerConfig) => void; loading: boolean; } export const ServerSelection: React.FC = ({ onSelect, loading }) => { const { t } = useTranslation(); const [customUrl, setCustomUrl] = useState(''); const [testing, setTesting] = useState(false); const [testError, setTestError] = useState(null); const [securityDisabled, setSecurityDisabled] = useState(false); const serverUrl = localStorage.getItem('server_url') || ''; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Normalize and validate URL let url = customUrl.trim().replace(/\/+$/, '') || serverUrl; if (!url) { setTestError(t('setup.server.error.emptyUrl', 'Please enter a server URL')); return; } // Auto-add https:// if no protocol specified if (!url.startsWith('http://') && !url.startsWith('https://')) { console.log('[ServerSelection] No protocol specified, adding https://'); url = `https://${url}`; setCustomUrl(url); // Update the input field } // Validate URL format try { const urlObj = new URL(url); console.log('[ServerSelection] Valid URL:', { protocol: urlObj.protocol, hostname: urlObj.hostname, port: urlObj.port, pathname: urlObj.pathname, }); } catch (err) { console.error('[ServerSelection] Invalid URL format:', err); setTestError(t('setup.server.error.invalidUrl', 'Invalid URL format. Please enter a valid URL like https://your-server.com')); return; } // Test connection before proceeding setTesting(true); setTestError(null); setSecurityDisabled(false); console.log(`[ServerSelection] Testing connection to: ${url}`); try { const testResult = await connectionModeService.testConnection(url); if (!testResult.success) { console.error('[ServerSelection] Connection test failed:', testResult); setTestError(testResult.error || t('setup.server.error.unreachable', 'Could not connect to server')); setTesting(false); return; } console.log('[ServerSelection] ✅ Connection test successful'); // Fetch OAuth providers and check if login is enabled const enabledProviders: SSOProviderConfig[] = []; let loginMethod = 'all'; // Default to 'all' (allows both SSO and username/password) try { console.log('[ServerSelection] Fetching login configuration...'); const response = await fetch(`${url}/api/v1/proprietary/ui-data/login`); // Check if security is disabled (status 403, 401, or 404 - endpoint doesn't exist) if (!response.ok) { console.warn(`[ServerSelection] Login config request failed with status ${response.status}`); if (response.status === 403 || response.status === 401 || response.status === 404) { console.log('[ServerSelection] Security/SSO not configured on this server (or endpoint does not exist)'); setSecurityDisabled(true); setTesting(false); return; } // Other error statuses - show generic error setTestError( t('setup.server.error.configFetch', 'Failed to fetch server configuration (status {{status}})', { status: response.status }) ); setTesting(false); return; } const data = await response.json(); console.log('[ServerSelection] Login UI data:', data); // Check if the response indicates security is disabled if (data.enableLogin === false || data.securityEnabled === false) { console.log('[ServerSelection] Security is explicitly disabled in config'); setSecurityDisabled(true); setTesting(false); return; } // Extract loginMethod from response loginMethod = data.loginMethod || 'all'; console.log('[ServerSelection] Login method:', loginMethod); // Extract provider IDs from authorization URLs // Example: "/oauth2/authorization/google" → "google" const providerEntries = Object.entries(data.providerList || {}); console.log('[ServerSelection] providerList from API:', data.providerList); providerEntries.forEach(([path, label]) => { const id = path.split('/').pop(); console.log('[ServerSelection] Processing provider path:', path, '→ id:', id); if (!id) { console.warn('[ServerSelection] Skipping provider with empty id:', path); return; } enabledProviders.push({ id, path, label: typeof label === 'string' ? label : undefined, }); }); console.log('[ServerSelection] ✅ Detected OAuth providers:', enabledProviders); console.log('[ServerSelection] Login method:', loginMethod); } catch (err) { console.error('[ServerSelection] ❌ Failed to fetch login configuration:', err); // Check if it's a security disabled error if (err instanceof Error && (err.message.includes('403') || err.message.includes('401'))) { console.log('[ServerSelection] Security is disabled (error-based detection)'); setSecurityDisabled(true); setTesting(false); return; } // For any other error (network, CORS, invalid JSON, etc.), show error and don't proceed const errorMessage = err instanceof Error ? err.message : 'Unknown error'; console.error('[ServerSelection] Configuration fetch error details:', errorMessage); setTestError( t('setup.server.error.configFetchError', 'Failed to fetch server configuration: {{error}}', { error: errorMessage }) ); setTesting(false); return; } // Connection successful — persist URL so it pre-fills on next sign-in localStorage.setItem('server_url', url); console.log('[ServerSelection] ✅ Server selection complete, proceeding to login'); onSelect({ url, enabledOAuthProviders: enabledProviders.length > 0 ? enabledProviders : undefined, loginMethod, }); } catch (error) { console.error('[ServerSelection] ❌ Unexpected error during connection test:', error); setTestError( error instanceof Error ? error.message : t('setup.server.error.testFailed', 'Connection test failed') ); } finally { setTesting(false); } }; return (
{ setCustomUrl(e.target.value); setTestError(null); setSecurityDisabled(false); }} disabled={loading || testing} error={testError} description={t( 'setup.server.url.description', 'Enter the full URL of your self-hosted Stirling PDF server' )} /> {securityDisabled && ( } title={t('setup.server.error.securityDisabled.title', 'Login Not Enabled')} > {t('setup.server.error.securityDisabled.body', 'This server does not have login enabled. To connect to this server, you must enable authentication:')}
  1. {t('setup.server.error.securityDisabled.step1', 'Set DOCKER_ENABLE_SECURITY=true in your environment')}
  2. {t('setup.server.error.securityDisabled.step2', 'Or set security.enableLogin=true in settings.yml')}
  3. {t('setup.server.error.securityDisabled.step3', 'Restart the server')}
)} {serverUrl && (
)}
); };