'use client' /** * Source Policy Management Page (SDK Version) * * Whitelist-based data source management for compliance RAG corpus. * Controls which legal sources may be used, PII rules, and audit trail. */ import { useState, useEffect } from 'react' import { useSDK } from '@/lib/sdk' import StepHeader from '@/components/sdk/StepHeader/StepHeader' import { SourcesTab } from '@/components/sdk/source-policy/SourcesTab' import { OperationsMatrixTab } from '@/components/sdk/source-policy/OperationsMatrixTab' import { PIIRulesTab } from '@/components/sdk/source-policy/PIIRulesTab' import { AuditTab } from '@/components/sdk/source-policy/AuditTab' // API base URL — now uses Next.js proxy routes const API_BASE = '/api/sdk/v1/source-policy' interface PolicyStats { active_policies: number allowed_sources: number pii_rules: number blocked_today: number blocked_total: number } type TabId = 'dashboard' | 'sources' | 'operations' | 'pii' | 'audit' | 'blocked' interface BlockedContent { id: string content_type: string pattern: string reason: string blocked_at: string source?: string } export default function SourcePolicyPage() { const { state } = useSDK() const [activeTab, setActiveTab] = useState('dashboard') const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [blockedContent, setBlockedContent] = useState([]) const [blockedLoading, setBlockedLoading] = useState(false) useEffect(() => { fetchStats() }, []) useEffect(() => { if (activeTab === 'blocked') { fetchBlockedContent() } }, [activeTab]) const fetchBlockedContent = async () => { setBlockedLoading(true) try { const res = await fetch(`${API_BASE}/blocked-content`) if (res.ok) { const data = await res.json() setBlockedContent(Array.isArray(data) ? data : (data.items || [])) } } catch { // silently ignore — empty state shown } finally { setBlockedLoading(false) } } const handleRemoveBlocked = async (id: string) => { try { await fetch(`${API_BASE}/blocked-content/${id}`, { method: 'DELETE' }) setBlockedContent(prev => prev.filter(item => item.id !== id)) } catch { // ignore } } const fetchStats = async () => { try { setLoading(true) const res = await fetch(`${API_BASE}/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') 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: ( ), }, { id: 'blocked', name: 'Blockierte Inhalte', icon: ( ), }, ] return (
{/* 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 */} <> {activeTab === 'dashboard' && stats && (

Quellen-Uebersicht

Zugelassene Quellen {stats.allowed_sources}
Aktive Policies {stats.active_policies}
0 ? Math.min((stats.active_policies / stats.allowed_sources) * 100, 100) : 0}%` }} />

Datenschutz-Regeln

PII-Regeln aktiv {stats.pii_rules}
Blockiert (heute) 0 ? 'text-red-600' : 'text-green-600'}`}> {stats.blocked_today}
Blockiert (gesamt) {stats.blocked_total}

Compliance-Status

Quellen konfiguriert
{stats.allowed_sources > 0 ? 'Ja' : 'Nein'}
PII-Schutz aktiv
{stats.pii_rules > 0 ? 'Ja' : 'Nein'}
Policies definiert
{stats.active_policies > 0 ? 'Ja' : 'Nein'}
)} {activeTab === 'dashboard' && !stats && loading && (
Lade Dashboard...
)} {activeTab === 'sources' && } {activeTab === 'operations' && } {activeTab === 'pii' && } {activeTab === 'audit' && } {activeTab === 'blocked' && (

Blockierte Inhalte

{blockedLoading ? (
Lade blockierte Inhalte...
) : blockedContent.length === 0 ? (

Keine blockierten Inhalte vorhanden.

) : ( {blockedContent.map(item => ( ))}
Typ Muster / Pattern Grund Blockiert am Quelle
{item.content_type} {item.pattern} {item.reason} {new Date(item.blocked_at).toLocaleDateString('de-DE')} {item.source || '—'}
)}
)}
) }