All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 34s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s
Part 1 — RAG Corpus Versioning: - New DB table compliance_corpus_versions (migration 017) - Go CorpusVersionStore with CRUD operations - Assessment struct extended with corpus_version_id - API endpoints: GET /rag/corpus-status, /rag/corpus-versions/:collection - RAG routes (search, regulations) now registered in main.go - Ingestion script registers corpus versions after each run - Frontend staleness badge in SDK sidebar Part 3 — Source Policy Backend: - New FastAPI router with CRUD for allowed sources, PII rules, operations matrix, audit trail, stats, and compliance report - SQLAlchemy models for all source policy tables (migration 001) - Frontend API base corrected from edu-search:8088/8089 to backend-compliance:8002/api Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
203 lines
7.3 KiB
TypeScript
203 lines
7.3 KiB
TypeScript
'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 for backend-compliance
|
|
const getApiBase = () => {
|
|
if (typeof window === 'undefined') return 'http://localhost:8002/api'
|
|
const hostname = window.location.hostname
|
|
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
|
return 'http://localhost:8002/api'
|
|
}
|
|
return `https://${hostname}:8002/api`
|
|
}
|
|
|
|
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 { state } = useSDK()
|
|
const [activeTab, setActiveTab] = useState<TabId>('dashboard')
|
|
const [stats, setStats] = useState<PolicyStats | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [apiBase, setApiBase] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
const base = getApiBase()
|
|
setApiBase(base)
|
|
}, [])
|
|
|
|
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')
|
|
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: (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'sources',
|
|
name: 'Quellen',
|
|
icon: (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'operations',
|
|
name: 'Operations',
|
|
icon: (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'pii',
|
|
name: 'PII-Regeln',
|
|
icon: (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'audit',
|
|
name: 'Audit',
|
|
icon: (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
),
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<StepHeader stepId="source-policy" showProgress={true} />
|
|
|
|
{/* Error Display */}
|
|
{error && (
|
|
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 flex items-center justify-between">
|
|
<span>{error}</span>
|
|
<button onClick={() => setError(null)} className="text-red-500 hover:text-red-700">
|
|
×
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Stats Cards */}
|
|
{stats && (
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
|
<div className="text-2xl font-bold text-purple-600">{stats.active_policies}</div>
|
|
<div className="text-sm text-slate-500">Aktive Policies</div>
|
|
</div>
|
|
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
|
<div className="text-2xl font-bold text-green-600">{stats.allowed_sources}</div>
|
|
<div className="text-sm text-slate-500">Zugelassene Quellen</div>
|
|
</div>
|
|
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
|
<div className="text-2xl font-bold text-red-600">{stats.blocked_today}</div>
|
|
<div className="text-sm text-slate-500">Blockiert (heute)</div>
|
|
</div>
|
|
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
|
<div className="text-2xl font-bold text-blue-600">{stats.pii_rules}</div>
|
|
<div className="text-sm text-slate-500">PII-Regeln</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tabs */}
|
|
<div className="flex gap-2 flex-wrap">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={`px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2 ${
|
|
activeTab === tab.id
|
|
? 'bg-purple-600 text-white'
|
|
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
|
}`}
|
|
>
|
|
{tab.icon}
|
|
{tab.name}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
{apiBase === null ? (
|
|
<div className="text-center py-12 text-slate-500">Initialisiere...</div>
|
|
) : (
|
|
<>
|
|
{activeTab === 'dashboard' && (
|
|
<div className="text-center py-12 text-slate-500">
|
|
{loading ? 'Lade Dashboard...' : 'Dashboard-Ansicht - Wechseln Sie zu einem Tab fuer Details.'}
|
|
</div>
|
|
)}
|
|
{activeTab === 'sources' && <SourcesTab apiBase={apiBase} onUpdate={fetchStats} />}
|
|
{activeTab === 'operations' && <OperationsMatrixTab apiBase={apiBase} />}
|
|
{activeTab === 'pii' && <PIIRulesTab apiBase={apiBase} onUpdate={fetchStats} />}
|
|
{activeTab === 'audit' && <AuditTab apiBase={apiBase} />}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|