'use client' /** * University Crawler Control Panel * * Admin interface for managing the multi-phase university crawling system. */ import { useState, useEffect, useCallback } from 'react' import AdminLayout from '@/components/admin/AdminLayout' import { CrawlQueueItem, OrchestratorStatus, University, API_BASE, phaseConfig, } from './_components/types' import { QueueList } from './_components/QueueList' export default function UniCrawlerPage() { const [status, setStatus] = useState(null) const [queue, setQueue] = useState([]) const [universities, setUniversities] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [success, setSuccess] = useState(null) const [selectedUniversity, setSelectedUniversity] = useState('') const [priority, setPriority] = useState(5) const fetchStatus = useCallback(async () => { try { const res = await fetch(`${API_BASE}?action=status`) if (res.ok) setStatus(await res.json()) } catch (err) { console.error('Failed to fetch status:', err) } }, []) const fetchQueue = useCallback(async () => { try { const res = await fetch(`${API_BASE}?action=queue`) if (res.ok) { const data = await res.json(); setQueue(data.queue || []) } } catch (err) { console.error('Failed to fetch queue:', err) } }, []) const fetchUniversities = useCallback(async () => { try { const res = await fetch(`${API_BASE}?action=universities`) if (res.ok) { const data = await res.json() const unis = data.universities ?? data ?? [] setUniversities(Array.isArray(unis) ? unis : []) } } catch (err) { console.error('Failed to fetch universities:', err) } }, []) useEffect(() => { fetchStatus(); fetchQueue(); fetchUniversities() const interval = setInterval(() => { fetchStatus(); fetchQueue() }, 5000) return () => clearInterval(interval) }, [fetchStatus, fetchQueue, fetchUniversities]) const handleStart = async () => { setLoading(true); setError(null) try { const res = await fetch(`${API_BASE}?action=start`, { method: 'POST' }) if (res.ok) { setSuccess('Orchestrator gestartet'); fetchStatus() } else { const data = await res.json(); setError(data.error || 'Start fehlgeschlagen') } } catch { setError('Verbindungsfehler') } finally { setLoading(false) } } const handleStop = async () => { setLoading(true); setError(null) try { const res = await fetch(`${API_BASE}?action=stop`, { method: 'POST' }) if (res.ok) { setSuccess('Orchestrator gestoppt'); fetchStatus() } else { const data = await res.json(); setError(data.error || 'Stop fehlgeschlagen') } } catch { setError('Verbindungsfehler') } finally { setLoading(false) } } const handleAddToQueue = async (e: React.FormEvent) => { e.preventDefault() if (!selectedUniversity) return setLoading(true); setError(null) try { const res = await fetch(`${API_BASE}?action=queue`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ university_id: selectedUniversity, priority, initiated_by: 'admin-ui' }) }) if (res.ok) { setSuccess('Universitaet zur Queue hinzugefuegt'); setSelectedUniversity(''); setPriority(5); fetchQueue() } else { const data = await res.json(); setError(data.error || 'Hinzufuegen fehlgeschlagen') } } catch { setError('Verbindungsfehler') } finally { setLoading(false) } } const handleRemove = async (universityId: string) => { if (!confirm('Wirklich aus der Queue entfernen?')) return try { const res = await fetch(`${API_BASE}?university_id=${universityId}`, { method: 'DELETE' }); if (res.ok) fetchQueue() } catch (err) { console.error('Remove failed:', err) } } const handlePause = async (universityId: string) => { try { const res = await fetch(`${API_BASE}?action=pause&university_id=${universityId}`, { method: 'POST' }); if (res.ok) fetchQueue() } catch (err) { console.error('Pause failed:', err) } } const handleResume = async (universityId: string) => { try { const res = await fetch(`${API_BASE}?action=resume&university_id=${universityId}`, { method: 'POST' }); if (res.ok) fetchQueue() } catch (err) { console.error('Resume failed:', err) } } useEffect(() => { if (success) { const t = setTimeout(() => setSuccess(null), 5000); return () => clearTimeout(t) } }, [success]) useEffect(() => { if (error) { const t = setTimeout(() => setError(null), 5000); return () => clearTimeout(t) } }, [error]) return ( {error &&
{error}
} {success &&
{success}
}
{/* Status Card */}

Orchestrator Status

{status?.is_running ? 'Laeuft' : 'Gestoppt'}
In Queue:{status?.queue_length || 0}
Heute abgeschlossen:{status?.completed_today || 0}
Gesamt verarbeitet:{status?.total_processed || 0}
{status?.last_activity && (
Letzte Aktivitaet:{new Date(status.last_activity).toLocaleTimeString('de-DE')}
)}
{status?.current_university && (

Aktuelle Verarbeitung

{status.current_university.university_name}

{phaseConfig[status.current_phase].label} {status.current_university.progress_percent}%
)}
{/* Add to Queue Form */}

Zur Queue hinzufuegen

setPriority(Number(e.target.value))} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />

Hoeher = dringender

) }