'use client' import { useState, useEffect } from 'react' interface AllowedSource { id: string domain: string name: string description?: string license?: string legal_basis?: string trust_boost: number source_type: string active: boolean metadata?: Record created_at: string updated_at?: string } interface SourcesTabProps { apiBase: string onUpdate?: () => void } const LICENSES = [ { value: 'DL-DE-BY-2.0', label: 'Datenlizenz Deutschland' }, { value: 'CC-BY', label: 'Creative Commons BY' }, { value: 'CC-BY-SA', label: 'Creative Commons BY-SA' }, { value: 'CC0', label: 'Public Domain' }, { value: '§5 UrhG', label: 'Amtliche Werke (§5 UrhG)' }, ] const BUNDESLAENDER = [ { value: '', label: 'Bundesebene' }, { value: 'NI', label: 'Niedersachsen' }, { value: 'BY', label: 'Bayern' }, { value: 'BW', label: 'Baden-Wuerttemberg' }, { value: 'NW', label: 'Nordrhein-Westfalen' }, { value: 'HE', label: 'Hessen' }, { value: 'SN', label: 'Sachsen' }, { value: 'BE', label: 'Berlin' }, { value: 'HH', label: 'Hamburg' }, ] export function SourcesTab({ apiBase, onUpdate }: SourcesTabProps) { const [sources, setSources] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Filters const [searchTerm, setSearchTerm] = useState('') const [licenseFilter, setLicenseFilter] = useState('') const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>('all') const [sourceTypeFilter, setSourceTypeFilter] = useState('') // Edit modal const [editingSource, setEditingSource] = useState(null) const [isNewSource, setIsNewSource] = useState(false) const [saving, setSaving] = useState(false) // New source form const [newSource, setNewSource] = useState({ domain: '', name: '', license: 'DL-DE-BY-2.0', legal_basis: '', trust_boost: 0.5, active: true, }) useEffect(() => { fetchSources() }, [licenseFilter, statusFilter, sourceTypeFilter]) const fetchSources = async () => { try { setLoading(true) const params = new URLSearchParams() if (licenseFilter) params.append('license', licenseFilter) if (statusFilter !== 'all') params.append('active_only', statusFilter === 'active' ? 'true' : 'false') if (sourceTypeFilter) params.append('source_type', sourceTypeFilter) const res = await fetch(`${apiBase}/sources?${params}`) if (!res.ok) throw new Error('Fehler beim Laden') const data = await res.json() setSources(data.sources || []) } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setLoading(false) } } const createSource = async () => { try { setSaving(true) const res = await fetch(`${apiBase}/sources`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newSource), }) if (!res.ok) throw new Error('Fehler beim Erstellen') setNewSource({ domain: '', name: '', license: 'DL-DE-BY-2.0', legal_basis: '', trust_boost: 0.5, active: true, }) setIsNewSource(false) fetchSources() onUpdate?.() } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setSaving(false) } } const updateSource = async () => { if (!editingSource) return try { setSaving(true) const res = await fetch(`${apiBase}/sources/${editingSource.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editingSource), }) if (!res.ok) throw new Error('Fehler beim Aktualisieren') setEditingSource(null) fetchSources() onUpdate?.() } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setSaving(false) } } const deleteSource = async (id: string) => { if (!confirm('Quelle wirklich loeschen? Diese Aktion wird im Audit-Log protokolliert.')) return try { const res = await fetch(`${apiBase}/sources/${id}`, { method: 'DELETE', }) if (!res.ok) throw new Error('Fehler beim Loeschen') fetchSources() onUpdate?.() } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } } const toggleSourceStatus = async (source: AllowedSource) => { try { const res = await fetch(`${apiBase}/sources/${source.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ active: !source.active }), }) if (!res.ok) throw new Error('Fehler beim Aendern des Status') fetchSources() onUpdate?.() } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } } const filteredSources = sources.filter((source) => { if (searchTerm) { const term = searchTerm.toLowerCase() if (!source.domain.toLowerCase().includes(term) && !source.name.toLowerCase().includes(term)) { return false } } return true }) return (
{/* Error Display */} {error && (
{error}
)} {/* Filters & Actions */}
setSearchTerm(e.target.value)} placeholder="Domain oder Name suchen..." className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
{/* Sources Table */} {loading ? (
Lade Quellen...
) : filteredSources.length === 0 ? (

Keine Quellen gefunden

Fuegen Sie neue Quellen zur Whitelist hinzu.

) : (
{filteredSources.map((source) => ( ))}
Domain Name Lizenz Trust Status Aktionen
{source.domain} {source.name} {source.license} {(source.trust_boost * 100).toFixed(0)}%
)} {/* New Source Modal */} {isNewSource && (

Neue Quelle hinzufuegen

setNewSource({ ...newSource, domain: e.target.value })} placeholder="z.B. nibis.de" className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
setNewSource({ ...newSource, name: e.target.value })} placeholder="z.B. NiBiS Bildungsserver" className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
setNewSource({ ...newSource, legal_basis: e.target.value })} placeholder="z.B. §5 UrhG (Amtliche Werke)" className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
setNewSource({ ...newSource, trust_boost: parseFloat(e.target.value) })} className="w-full" />
{(newSource.trust_boost * 100).toFixed(0)}%
)} {/* Edit Source Modal */} {editingSource && (

Quelle bearbeiten

setEditingSource({ ...editingSource, name: e.target.value })} className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
setEditingSource({ ...editingSource, legal_basis: e.target.value })} className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
setEditingSource({ ...editingSource, trust_boost: parseFloat(e.target.value) })} className="w-full" />
{(editingSource.trust_boost * 100).toFixed(0)}%
setEditingSource({ ...editingSource, active: e.target.checked })} className="w-4 h-4 text-purple-600" />
)}
) }