import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { listUpstreamSources, createUpstreamSource, updateUpstreamSource, deleteUpstreamSource, testUpstreamSource, getCacheSettings, updateCacheSettings, } from '../api'; import { UpstreamSource, CacheSettings, SourceType, AuthType } from '../types'; import './AdminCachePage.css'; const SOURCE_TYPES: SourceType[] = ['npm', 'pypi', 'maven', 'docker', 'helm', 'nuget', 'deb', 'rpm', 'generic']; const AUTH_TYPES: AuthType[] = ['none', 'basic', 'bearer', 'api_key']; function AdminCachePage() { const { user, loading: authLoading } = useAuth(); const navigate = useNavigate(); // Upstream sources state const [sources, setSources] = useState([]); const [loadingSources, setLoadingSources] = useState(true); const [sourcesError, setSourcesError] = useState(null); // Cache settings state const [settings, setSettings] = useState(null); const [loadingSettings, setLoadingSettings] = useState(true); const [settingsError, setSettingsError] = useState(null); // Create/Edit form state const [showForm, setShowForm] = useState(false); const [editingSource, setEditingSource] = useState(null); const [formData, setFormData] = useState({ name: '', source_type: 'generic' as SourceType, url: '', enabled: true, is_public: true, auth_type: 'none' as AuthType, username: '', password: '', priority: 100, }); const [formError, setFormError] = useState(null); const [isSaving, setIsSaving] = useState(false); // Test result state const [testingId, setTestingId] = useState(null); const [testResults, setTestResults] = useState>({}); // Delete confirmation state const [deletingId, setDeletingId] = useState(null); // Settings update state const [updatingSettings, setUpdatingSettings] = useState(false); // Success message const [successMessage, setSuccessMessage] = useState(null); useEffect(() => { if (!authLoading && !user) { navigate('/login', { state: { from: '/admin/cache' } }); } }, [user, authLoading, navigate]); useEffect(() => { if (user && user.is_admin) { loadSources(); loadSettings(); } }, [user]); useEffect(() => { if (successMessage) { const timer = setTimeout(() => setSuccessMessage(null), 3000); return () => clearTimeout(timer); } }, [successMessage]); async function loadSources() { setLoadingSources(true); setSourcesError(null); try { const data = await listUpstreamSources(); setSources(data); } catch (err) { setSourcesError(err instanceof Error ? err.message : 'Failed to load sources'); } finally { setLoadingSources(false); } } async function loadSettings() { setLoadingSettings(true); setSettingsError(null); try { const data = await getCacheSettings(); setSettings(data); } catch (err) { setSettingsError(err instanceof Error ? err.message : 'Failed to load settings'); } finally { setLoadingSettings(false); } } function openCreateForm() { setEditingSource(null); setFormData({ name: '', source_type: 'generic', url: '', enabled: true, is_public: true, auth_type: 'none', username: '', password: '', priority: 100, }); setFormError(null); setShowForm(true); } function openEditForm(source: UpstreamSource) { setEditingSource(source); setFormData({ name: source.name, source_type: source.source_type, url: source.url, enabled: source.enabled, is_public: source.is_public, auth_type: source.auth_type, username: source.username || '', password: '', priority: source.priority, }); setFormError(null); setShowForm(true); } async function handleFormSubmit(e: React.FormEvent) { e.preventDefault(); if (!formData.name.trim()) { setFormError('Name is required'); return; } if (!formData.url.trim()) { setFormError('URL is required'); return; } setIsSaving(true); setFormError(null); try { if (editingSource) { // Update existing source await updateUpstreamSource(editingSource.id, { name: formData.name.trim(), source_type: formData.source_type, url: formData.url.trim(), enabled: formData.enabled, is_public: formData.is_public, auth_type: formData.auth_type, username: formData.username.trim() || undefined, password: formData.password || undefined, priority: formData.priority, }); setSuccessMessage('Source updated successfully'); } else { // Create new source await createUpstreamSource({ name: formData.name.trim(), source_type: formData.source_type, url: formData.url.trim(), enabled: formData.enabled, is_public: formData.is_public, auth_type: formData.auth_type, username: formData.username.trim() || undefined, password: formData.password || undefined, priority: formData.priority, }); setSuccessMessage('Source created successfully'); } setShowForm(false); await loadSources(); } catch (err) { setFormError(err instanceof Error ? err.message : 'Failed to save source'); } finally { setIsSaving(false); } } async function handleDelete(source: UpstreamSource) { if (!window.confirm(`Delete upstream source "${source.name}"? This cannot be undone.`)) { return; } setDeletingId(source.id); try { await deleteUpstreamSource(source.id); setSuccessMessage(`Source "${source.name}" deleted`); await loadSources(); } catch (err) { setSourcesError(err instanceof Error ? err.message : 'Failed to delete source'); } finally { setDeletingId(null); } } async function handleTest(source: UpstreamSource) { setTestingId(source.id); setTestResults((prev) => ({ ...prev, [source.id]: { success: true, message: 'Testing...' } })); try { const result = await testUpstreamSource(source.id); setTestResults((prev) => ({ ...prev, [source.id]: { success: result.success, message: result.success ? `Connected (${result.elapsed_ms}ms)` : result.error || `HTTP ${result.status_code}`, }, })); } catch (err) { setTestResults((prev) => ({ ...prev, [source.id]: { success: false, message: err instanceof Error ? err.message : 'Test failed', }, })); } finally { setTestingId(null); } } async function handleSettingsToggle(field: 'allow_public_internet' | 'auto_create_system_projects') { if (!settings) return; // Check if env override is active const isOverridden = (field === 'allow_public_internet' && settings.allow_public_internet_env_override !== null) || (field === 'auto_create_system_projects' && settings.auto_create_system_projects_env_override !== null); if (isOverridden) { alert('This setting is overridden by an environment variable and cannot be changed via UI.'); return; } setUpdatingSettings(true); try { const update = { [field]: !settings[field] }; const newSettings = await updateCacheSettings(update); setSettings(newSettings); setSuccessMessage(`Setting "${field}" updated`); } catch (err) { setSettingsError(err instanceof Error ? err.message : 'Failed to update settings'); } finally { setUpdatingSettings(false); } } if (authLoading) { return
Loading...
; } if (!user?.is_admin) { return (
Access denied. Admin privileges required.
); } return (

Cache Management

{successMessage &&
{successMessage}
} {/* Cache Settings Section */}

Global Settings

{loadingSettings ? (

Loading settings...

) : settingsError ? (
{settingsError}
) : settings ? (
) : null}
{/* Upstream Sources Section */}

Upstream Sources

{loadingSources ? (

Loading sources...

) : sourcesError ? (
{sourcesError}
) : sources.length === 0 ? (

No upstream sources configured.

) : ( {sources.map((source) => ( ))}
Name Type URL Priority Status Source Actions
{source.name} {source.is_public && Public} {source.source_type} {source.url} {source.priority} {source.enabled ? 'Enabled' : 'Disabled'} {source.source === 'env' ? ( ENV ) : ( 'Database' )} {source.source !== 'env' && ( <> )} {testResults[source.id] && ( {testResults[source.id].message} )}
)}
{/* Create/Edit Modal */} {showForm && (
setShowForm(false)}>
e.stopPropagation()}>

{editingSource ? 'Edit Upstream Source' : 'Add Upstream Source'}

{formError &&
{formError}
}
setFormData({ ...formData, name: e.target.value })} placeholder="e.g., npm-private" required />
setFormData({ ...formData, priority: parseInt(e.target.value) || 100 })} min="1" /> Lower = higher priority
setFormData({ ...formData, url: e.target.value })} placeholder="https://registry.example.com" required />
{formData.auth_type !== 'none' && (
{(formData.auth_type === 'basic' || formData.auth_type === 'api_key') && (
setFormData({ ...formData, username: e.target.value })} placeholder={formData.auth_type === 'api_key' ? 'X-API-Key' : 'username'} />
)}
setFormData({ ...formData, password: e.target.value })} placeholder={editingSource ? '(unchanged)' : ''} /> {editingSource && ( Leave empty to keep existing {formData.auth_type === 'bearer' ? 'token' : 'credentials'} )}
)}
)}
); } export default AdminCachePage;