import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { getPyPICacheStatus, getPyPICacheFailedTasks, getPyPICacheActiveTasks, retryPyPICacheTask, retryAllPyPICacheTasks, } from '../api'; import { PyPICacheStatus, PyPICacheTask, PyPICacheActiveTask } from '../types'; import './AdminJobsPage.css'; function AdminJobsPage() { const { user, loading: authLoading } = useAuth(); const navigate = useNavigate(); // PyPI cache status const [cacheStatus, setCacheStatus] = useState(null); const [failedTasks, setFailedTasks] = useState([]); const [activeTasks, setActiveTasks] = useState([]); const [loadingStatus, setLoadingStatus] = useState(true); const [statusError, setStatusError] = useState(null); // Action states const [retryingPackage, setRetryingPackage] = useState(null); const [retryingAll, setRetryingAll] = useState(false); const [successMessage, setSuccessMessage] = useState(null); // Auto-refresh const [autoRefresh, setAutoRefresh] = useState(true); useEffect(() => { if (!authLoading && !user) { navigate('/login', { state: { from: '/admin/jobs' } }); } }, [user, authLoading, navigate]); const loadData = useCallback(async () => { if (!user?.is_admin) return; setStatusError(null); try { const [status, failed, active] = await Promise.all([ getPyPICacheStatus(), getPyPICacheFailedTasks(100), getPyPICacheActiveTasks(50), ]); setCacheStatus(status); setFailedTasks(failed); setActiveTasks(active); } catch (err) { setStatusError(err instanceof Error ? err.message : 'Failed to load status'); } finally { setLoadingStatus(false); } }, [user]); useEffect(() => { if (user?.is_admin) { loadData(); } }, [user, loadData]); // Auto-refresh every 5 seconds when enabled useEffect(() => { if (!autoRefresh || !user?.is_admin) return; const interval = setInterval(() => { loadData(); }, 5000); return () => clearInterval(interval); }, [autoRefresh, user, loadData]); useEffect(() => { if (successMessage) { const timer = setTimeout(() => setSuccessMessage(null), 3000); return () => clearTimeout(timer); } }, [successMessage]); async function handleRetryPackage(packageName: string) { setRetryingPackage(packageName); try { const result = await retryPyPICacheTask(packageName); setSuccessMessage(result.message); await loadData(); } catch (err) { setStatusError(err instanceof Error ? err.message : 'Failed to retry'); } finally { setRetryingPackage(null); } } async function handleRetryAll() { if (!window.confirm('Retry all failed tasks? This will re-queue all failed packages.')) { return; } setRetryingAll(true); try { const result = await retryAllPyPICacheTasks(); setSuccessMessage(result.message); await loadData(); } catch (err) { setStatusError(err instanceof Error ? err.message : 'Failed to retry all'); } finally { setRetryingAll(false); } } if (authLoading) { return
Loading...
; } if (!user?.is_admin) { return (
Access denied. Admin privileges required.
); } const totalJobs = cacheStatus ? cacheStatus.pending + cacheStatus.in_progress + cacheStatus.completed + cacheStatus.failed : 0; return (

Background Jobs

{successMessage &&
{successMessage}
} {statusError &&
{statusError}
} {/* PyPI Cache Jobs Section */}

PyPI Cache Jobs

{failedTasks.length > 0 && ( )}
{loadingStatus && !cacheStatus ? (

Loading job status...

) : ( <> {/* Status Cards */}
{cacheStatus?.pending ?? 0}
Pending
{cacheStatus?.in_progress ?? 0}
In Progress
{cacheStatus?.completed ?? 0}
Completed
{cacheStatus?.failed ?? 0}
Failed
{totalJobs === 0 ? (

No cache jobs yet. Jobs are created when packages are downloaded through the PyPI proxy.

) : ( <> {/* Active Workers Table */} {activeTasks.length > 0 && (

Active Workers ({activeTasks.length})

{activeTasks.map((task) => ( ))}
Package Version Depth Attempt Started
{task.package} {task.version_constraint || '*'} {task.depth} {task.attempts + 1} {task.started_at ? new Date(task.started_at).toLocaleTimeString() : '-'}
)} {/* Failed Tasks Table */} {failedTasks.length > 0 && ( <>

Failed Tasks

{failedTasks.map((task) => ( ))}
Package Error Attempts Depth Failed At Actions
{task.package} {task.error || 'Unknown error'} {task.attempts} {task.depth} {task.failed_at ? new Date(task.failed_at).toLocaleString() : '-'}
)} {/* Success message when nothing is pending/in-progress/failed */} {failedTasks.length === 0 && activeTasks.length === 0 && cacheStatus?.pending === 0 && (

All jobs completed successfully.

)} {/* In progress message */} {activeTasks.length === 0 && failedTasks.length === 0 && (cacheStatus?.pending ?? 0) > 0 && (

Jobs queued for processing...

)} )} )}
{/* Placeholder for future job types */}

NPM Cache Jobs Coming Soon

NPM proxy support is planned for a future release.

); } export default AdminJobsPage;