'use client' /** * Source Policy Management Page * * Whitelist-based data source management for edu-search-service. * For auditors: Full audit trail for all changes. */ import { useState, useEffect } from 'react' import { PagePurpose } from '@/components/common/PagePurpose' import { SourcesTab } from './components/SourcesTab' import { OperationsMatrixTab } from './components/OperationsMatrixTab' import { PIIRulesTab } from './components/PIIRulesTab' import { AuditTab } from './components/AuditTab' // API base URL for edu-search-service // Uses nginx HTTPS proxy on port 8089 when accessed remotely const getApiBase = () => { if (typeof window === 'undefined') return 'http://localhost:8088' const hostname = window.location.hostname if (hostname === 'localhost' || hostname === '127.0.0.1') { return 'http://localhost:8088' } // Use nginx HTTPS proxy on port 8089 (proxies to edu-search-service:8088) return `https://${hostname}:8089` } interface PolicyStats { active_policies: number allowed_sources: number pii_rules: number blocked_today: number blocked_total: number } type TabId = 'dashboard' | 'sources' | 'operations' | 'pii' | 'audit' export default function SourcePolicyPage() { const [activeTab, setActiveTab] = useState('dashboard') const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [apiBase, setApiBase] = useState(null) useEffect(() => { // Set API base on client side - only runs in browser const base = getApiBase() setApiBase(base) }, []) useEffect(() => { // Only fetch when apiBase has been set by the first useEffect if (apiBase !== null) { fetchStats() } }, [apiBase]) const fetchStats = async () => { try { setLoading(true) const res = await fetch(`${apiBase}/v1/admin/policy-stats`) if (!res.ok) { throw new Error('Fehler beim Laden der Statistiken') } const data = await res.json() setStats(data) } catch (err) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') // Set default stats on error setStats({ active_policies: 0, allowed_sources: 0, pii_rules: 0, blocked_today: 0, blocked_total: 0, }) } finally { setLoading(false) } } const tabs: { id: TabId; name: string; icon: JSX.Element }[] = [ { id: 'dashboard', name: 'Dashboard', icon: ( ), }, { id: 'sources', name: 'Quellen', icon: ( ), }, { id: 'operations', name: 'Operations', icon: ( ), }, { id: 'pii', name: 'PII-Regeln', icon: ( ), }, { id: 'audit', name: 'Audit', icon: ( ), }, ] return (
{/* Page Purpose */} {/* Error Display */} {error && (
{error}
)} {/* Stats Cards */} {stats && (
{stats.active_policies}
Aktive Policies
{stats.allowed_sources}
Zugelassene Quellen
{stats.blocked_today}
Blockiert (heute)
{stats.pii_rules}
PII-Regeln
)} {/* Tabs */}
{tabs.map((tab) => ( ))}
{/* Tab Content */} {apiBase === null ? (
Initialisiere...
) : ( <> {activeTab === 'dashboard' && ( )} {activeTab === 'sources' && } {activeTab === 'operations' && } {activeTab === 'pii' && } {activeTab === 'audit' && } )}
) } // Dashboard Tab Component function DashboardTab({ stats, loading, apiBase, }: { stats: PolicyStats | null loading: boolean apiBase: string }) { const [complianceCheck, setComplianceCheck] = useState({ url: '', operation: 'lookup', }) const [checkResult, setCheckResult] = useState(null) const [checking, setChecking] = useState(false) const runComplianceCheck = async () => { if (!complianceCheck.url) return try { setChecking(true) const res = await fetch(`${apiBase}/v1/admin/check-compliance`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(complianceCheck), }) const data = await res.json() setCheckResult(data) } catch (err) { setCheckResult({ error: 'Fehler bei der Pruefung' }) } finally { setChecking(false) } } if (loading) { return
Lade Dashboard...
} return (
{/* Important Notice */}

Training mit externen Daten: VERBOTEN

Gemaess unserer Datenschutz-Policy ist das Training von KI-Modellen mit gecrawlten Daten strengstens untersagt. Diese Einschraenkung kann nicht ueber die UI geaendert werden.

{/* Quick Compliance Check */}

Schnell-Pruefung

Pruefen Sie, ob eine URL in der Whitelist enthalten ist und welche Operationen erlaubt sind.

setComplianceCheck({ ...complianceCheck, url: e.target.value })} placeholder="https://nibis.de/beispiel-seite" className="flex-1 px-4 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" />
{checkResult && (
{checkResult.is_allowed ? ( <> Erlaubt ) : ( <> Blockiert: {checkResult.block_reason || 'Nicht in Whitelist'} )}
{checkResult.source && (

Quelle: {checkResult.source.name}

Lizenz: {checkResult.license}

{checkResult.requires_citation && (

Zitation erforderlich

)}
)}
)}
{/* Operations Matrix by Source Type */}

Zulaessige Operationen nach Quellentyp

Uebersicht welche Operationen fuer welche Datenquellen erlaubt sind.

{[ { source: 'Landes-Open-Data-Portale (alle Laender)', data: 'SMD', lookup: 'yes', rag: 'yes', training: 'no', export: 'warn', basis: 'DL-DE-BY-2.0', note: 'Namensnennung, Quellenlink, Zweckbindung' }, { source: 'Landes-Open-Data-Portale', data: 'PBD', lookup: 'no', rag: 'no', training: 'no', export: 'no', basis: '—', note: 'Technisch filtern (Schema-Block)' }, { source: 'Regelwerke / Schulordnungen (Ministerien)', data: 'DOK', lookup: 'yes', rag: 'yes', training: 'warn', export: 'warn', basis: 'UrhG §5 / CC / DL', note: 'Nur amtliche Texte, Versions-Hash' }, { source: 'GovData', data: 'SMD', lookup: 'yes', rag: 'yes', training: 'no', export: 'warn', basis: 'DL-DE-BY-2.0', note: 'Bundesweiter Fallback' }, { source: 'Einzelschul-Websites', data: 'SMD', lookup: 'warn', rag: 'no', training: 'no', export: 'no', basis: '§60d greift nicht', note: 'Nur manuell, kein Crawling' }, { source: 'Private Schulverzeichnisse', data: 'SMD', lookup: 'no', rag: 'no', training: 'no', export: 'no', basis: 'Datenbankrecht', note: 'Nicht zulaessig' }, { source: 'Vom Lehrer eingegebene Daten', data: 'SMD', lookup: 'yes', rag: 'yes', training: 'warn', export: 'warn', basis: 'Art. 6(1)b DSGVO', note: 'Zweckbindung, Namespace' }, { source: 'Vom Lehrer hochgeladene Dokumente', data: 'DOK', lookup: 'yes', rag: 'yes', training: 'no', export: 'no', basis: 'Art. 6(1)b DSGVO', note: 'Kein Training, nur Session-RAG' }, ].map((row, idx) => ( ))}
Quelle / Typ Daten Lookup RAG Training Export Rechtsgrundlage Auflagen / Controls
{row.source} {row.data} {row.lookup === 'yes' ? '✅' : row.lookup === 'warn' ? '⚠️' : '❌'} {row.rag === 'yes' ? '✅' : row.rag === 'warn' ? '⚠️' : '❌'} {row.training === 'yes' ? '✅' : row.training === 'warn' ? '⚠️' : '❌'} {row.export === 'yes' ? '✅' : row.export === 'warn' ? '⚠️' : '❌'} {row.basis} {row.note}
{/* Legend and Explanation */}

Geltungsbereich der Matrix

{/* Datenarten */}
Datenarten
SMD = Schul-Metadaten (Name, Nummer, Schulform, Ort, Traeger) PBD = Personenbezogene Daten (Leitung, E-Mail, Telefon) DOK = Regelwerke / Ordnungen / Lehrplaene
{/* Verarbeitungsarten mit aufklappbarer Erklärung */}
Verarbeitungsarten (Details anzeigen)
Lookup = Auswahl / Validierung / Anzeige

Daten werden abgerufen und dem Nutzer angezeigt, z.B. bei der Schulauswahl im Onboarding oder zur Validierung eingegebener Schulnummern. Keine dauerhafte Speicherung oder Weiterverarbeitung.

RAG = Retrieval-Index (Kontext, Zitierquelle)

Daten werden in einen Vektor-Index aufgenommen und koennen als Kontext fuer KI-Antworten herangezogen werden. Die Quelle wird zitiert. Keine Veraenderung der Modelgewichte.

Training = Modellanpassung / Fine-Tuning

Daten fliessen in das Training oder Fine-Tuning eines KI-Modells ein und veraendern dessen Gewichte permanent. Grundsaetzlich VERBOTEN fuer externe Daten gemaess unserer Datenschutz-Policy.

Export = Weitergabe / Download / API

Daten werden an Dritte weitergegeben, zum Download bereitgestellt oder ueber eine API ausgegeben. Erfordert Pruefung der Lizenzbedingungen und ggf. Namensnennung.

{/* KI Use-Case Risk Matrix */}

KI-Use-Case Risikomatrix

Zulaessigkeit von KI-Anwendungsfaellen nach Datenquelle.

{[ { useCase: 'Schul-Auswahl / Onboarding', openData: 'yes', rules: 'na', uploads: 'na', risk: 'low' }, { useCase: 'Erwartungshorizont-Suche', openData: 'na', rules: 'yes', uploads: 'warn', risk: 'medium' }, { useCase: 'Klausur-Korrektur (RAG)', openData: 'na', rules: 'warn', uploads: 'yes', risk: 'medium' }, { useCase: 'Modell-Training', openData: 'no', rules: 'warn', uploads: 'no', risk: 'high' }, { useCase: 'Auto-Schulerkennung', openData: 'no', rules: 'no', uploads: 'no', risk: 'high' }, ].map((row, idx) => ( ))}
KI-Use-Case Open-Data SMD Regelwerke Lehrer-Uploads Risiko
{row.useCase} {row.openData === 'yes' ? '✅' : row.openData === 'warn' ? '⚠️' : row.openData === 'no' ? '❌' : '—'} {row.rules === 'yes' ? '✅' : row.rules === 'warn' ? '⚠️' : row.rules === 'no' ? '❌' : '—'} {row.uploads === 'yes' ? '✅' : row.uploads === 'warn' ? '⚠️' : row.uploads === 'no' ? '❌' : '—'} {row.risk === 'low' ? 'Niedrig' : row.risk === 'medium' ? 'Mittel' : 'Hoch'}
{/* Licenses Info */}

Unterstuetzte Lizenzen

DL-DE-BY-2.0
Datenlizenz Deutschland - Namensnennung
Attribution erforderlich
CC-BY
Creative Commons Attribution
Attribution erforderlich
CC-BY-SA
CC Attribution-ShareAlike
Attribution + ShareAlike
CC0
Public Domain
Keine Attribution noetig
§5 UrhG
Amtliche Werke
Quellenangabe erforderlich
{/* Technische Controls fuer Attribution */}

Technische Controls fuer Attribution

Massnahmen zur Sicherstellung der lizenzkonformen Quellenangabe im System.

{[ { id: 'CTRL-SRC-001', name: 'Attribution bei Schulsuche', description: 'Bei jedem Suchergebnis aus Open-Data-Portalen wird die Datenquelle, Lizenz und ein Link zum Bereitsteller angezeigt.', status: 'implemented', location: 'studio-v2/components/SchoolSearch.tsx', }, { id: 'CTRL-SRC-002', name: 'Attribution bei RAG-Ergebnissen', description: 'Pro EH-Vorschlag werden Dokumentname, Herausgeber und Lizenz angezeigt. Bei Einfuegen in Gutachten wird Zitation automatisch ergaenzt.', status: 'implemented', location: 'studio-v2/components/korrektur/EHSuggestionPanel.tsx', }, { id: 'CTRL-SRC-003', name: 'Export-Attribution', description: 'Bei PDF-Export wird ein Quellenverzeichnis am Ende eingefuegt. Bei Daten-Export werden Attribution-Metadaten mitgeliefert.', status: 'planned', location: 'klausur-service/export', }, { id: 'CTRL-SRC-004', name: 'Attribution-Audit-Trail', description: 'Logging welche Quellen fuer welche Outputs verwendet wurden. Nachweis fuer Auditoren ueber policy_audit_log.', status: 'planned', location: 'edu-search-service/internal/policy/audit.go', }, ].map((ctrl) => (
{ctrl.id} {ctrl.name}

{ctrl.description}

{ctrl.location}

{ctrl.status === 'implemented' ? 'Implementiert' : 'Geplant'}
))}
{/* Erlaubte Referenz-Domains */}

Erlaubte Referenz-Domains (Audit-Dokumentation)

Domains, auf die das System zu Referenz- und Compliance-Zwecken zugreifen darf. Diese Zugriffe dienen ausschliesslich der rechtssicheren Klassifikation und Dokumentation.

{[ { domain: 'govdata.de', reason: 'Der Zugriff auf govdata.de ist dauerhaft erlaubt, da es sich um ein amtliches Open-Data-Portal mit klarer Lizenz handelt. Die Nutzung erfolgt ausschliesslich zu Recherche- und Referenzzwecken, nicht fuer KI-Training.', type: 'Datenquelle', }, { domain: 'creativecommons.org', reason: 'Der Zugriff auf creativecommons.org ist dauerhaft erlaubt, da es sich um offizielle Lizenztexte handelt, die fuer die rechtssichere Klassifikation und Nutzung von Open-Data-Quellen erforderlich sind.', type: 'Lizenz-Referenz', }, { domain: 'wiki.creativecommons.org', reason: 'Der Zugriff auf wiki.creativecommons.org ist dauerhaft erlaubt, da es sich um offizielle Lizenzdokumentation handelt, die zur rechtssicheren Klassifikation von Datenquellen erforderlich ist.', type: 'Lizenz-Dokumentation', }, { domain: 'gesetze-im-internet.de', reason: 'Der Zugriff auf gesetze-im-internet.de ist dauerhaft erlaubt, da es sich um amtliche, urheberrechtsfreie Rechtsquellen (§5 UrhG) handelt, die zur rechtlichen Einordnung und Compliance-Dokumentation erforderlich sind.', type: 'Rechtsquelle', }, { domain: 'nibis.de', reason: 'Der Zugriff auf nibis.de (Niedersaechsischer Bildungsserver) ist dauerhaft erlaubt fuer den Abruf von Kerncurricula und Erwartungshorizonten. Die Nutzung erfolgt unter DL-DE-BY-2.0 mit Attribution.', type: 'Bildungsquelle', }, { domain: 'kmk.org', reason: 'Der Zugriff auf kmk.org (Kultusministerkonferenz) ist dauerhaft erlaubt, da KMK-Beschluesse als amtliche Werke nach §5 UrhG frei nutzbar sind. Quellenangabe erforderlich.', type: 'Amtliche Quelle', }, ].map((item, idx) => (
{item.domain} {item.type}

"{item.reason}"

))}
Fuer Auditoren: Diese Statements dokumentieren die rechtliche Grundlage fuer den Systemzugriff auf externe Domains. Alle Zugriffe werden im Audit-Log protokolliert.
{/* Bundesweite Quellen */}

Bundesweite Quellen

Uebergreifende Open-Data-Portale und amtliche Quellen auf Bundesebene.

Quelle Typ Lizenz Einsatz
GovData Bund-ODP DL-DE-BY-2.0 Aggregation / Fallback
Statistische Landesaemter Amtlich variabel Plausibilisierung
{/* Bundeslaender Open Data Portale */}

Bundeslaender Open Data Portale

Zulaessige Landes-Open-Data-Portale fuer Schulstammdaten und Bildungsinformationen.

{[ { bl: 'BW', name: 'Baden-Wuerttemberg', source: 'Open Data Baden-Wuerttemberg', license: 'DL-DE-BY-2.0', note: 'Schulverzeichnisse ueber Ministerium / Kommunen' }, { bl: 'BY', name: 'Bayern', source: 'Open Data Bayern', license: 'DL-DE-BY-2.0', note: 'Amtliche Schulnummern, Standorte' }, { bl: 'BE', name: 'Berlin', source: 'Datenportal Berlin', license: 'CC-BY', note: 'Sehr gut gepflegte Schulstammdaten' }, { bl: 'BB', name: 'Brandenburg', source: 'Daten Brandenburg', license: 'DL-DE-BY-2.0', note: 'Kommunale Ergaenzungen pruefen' }, { bl: 'HB', name: 'Bremen', source: 'Open Data Bremen', license: 'CC-BY', note: 'Kleine Datenmenge, sauber' }, { bl: 'HH', name: 'Hamburg', source: 'Transparenzportal Hamburg', license: 'DL-DE-BY-2.0', note: 'Sehr gute Metadaten' }, { bl: 'HE', name: 'Hessen', source: 'Open Data Hessen', license: 'DL-DE-BY-2.0', note: 'Schultraegerdaten' }, { bl: 'MV', name: 'Mecklenburg-Vorpommern', source: 'Open Data MV', license: 'DL-DE-BY-2.0', note: 'Teilweise CSV/Excel' }, { bl: 'NI', name: 'Niedersachsen', source: 'Open Data Niedersachsen', license: 'DL-DE-BY-2.0', note: 'Ergaenzend: NIBIS nur Regelwerke, nicht Personen' }, { bl: 'NW', name: 'Nordrhein-Westfalen', source: 'Open.NRW', license: 'DL-DE-BY-2.0', note: 'Umfangreich, kommunale Qualitaet pruefen' }, { bl: 'RP', name: 'Rheinland-Pfalz', source: 'Open Data Rheinland-Pfalz', license: 'DL-DE-BY-2.0', note: 'Schulformen & Standorte' }, { bl: 'SL', name: 'Saarland', source: 'Open Data Saarland', license: 'DL-DE-BY-2.0', note: 'Klein, aber zulaessig' }, { bl: 'SN', name: 'Sachsen', source: 'Datenportal Sachsen', license: 'DL-DE-BY-2.0', note: 'Gute Pflege' }, { bl: 'ST', name: 'Sachsen-Anhalt', source: 'Open Data Sachsen-Anhalt', license: 'DL-DE-BY-2.0', note: 'CSV/JSON verfuegbar' }, { bl: 'SH', name: 'Schleswig-Holstein', source: 'Open Data Schleswig-Holstein', license: 'DL-DE-BY-2.0', note: 'Einheitliche IDs' }, { bl: 'TH', name: 'Thueringen', source: 'Open Data Thueringen', license: 'DL-DE-BY-2.0', note: 'Kommunale Ergaenzungen' }, ].map((item, idx) => ( ))}
Bundesland Zulaessige Quelle Lizenz Hinweise
{item.bl} {item.name} {item.source} {item.license} {item.note}
Hinweis: Alle Landes-ODP sind vom Typ "Landes-ODP" und erfordern Attribution gemaess der jeweiligen Lizenz.
) }