fix: SDK-Module Frontend-Backend-Mismatches beheben + fehlende Proxy-Routes

- P0: enableBackendSync=true in SDKProvider aktiviert (PostgreSQL State-Persistenz)
- P0: Source Policy 4 Tabs an Backend-Schema angepasst (is_active→active,
  data.logs→data.entries, is_allowed→allowed, rule_type→category, severity→action)
- P0: OperationsMatrixTab holt jetzt Sources+Operations separat und joint client-side
- P0: PIIRulesTab PII-Test auf client-side Regex umgestellt (kein Backend-Endpoint noetig)
- P1: GET Proxy-Routes fuer Import, Screening und UCCA [id] (GET+DELETE) erstellt
- P1: Compliance Scope ScopeOverviewTab/ScopeExportTab Prop-Interfaces erweitert
- P2: Company Profile speichert jetzt auch zum dedizierten Backend-Endpoint
- P2: UCCA Wizard von 5 auf 8 Steps erweitert (Rechtsgrundlage, Datentransfer, Vertraege)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-02 11:40:44 +01:00
parent e6d666b89b
commit 80a988dc58
12 changed files with 563 additions and 181 deletions

View File

@@ -6,17 +6,16 @@ interface OperationPermission {
id: string
source_id: string
operation: string
is_allowed: boolean
requires_citation: boolean
notes?: string
allowed: boolean
conditions?: string
}
interface SourceWithOperations {
id: string
domain: string
name: string
license: string
is_active: boolean
license?: string
active: boolean
operations: OperationPermission[]
}
@@ -44,11 +43,33 @@ export function OperationsMatrixTab({ apiBase }: OperationsMatrixTabProps) {
const fetchMatrix = async () => {
try {
setLoading(true)
const res = await fetch(`${apiBase}/v1/admin/operations-matrix`)
if (!res.ok) throw new Error('Fehler beim Laden')
const [sourcesRes, opsRes] = await Promise.all([
fetch(`${apiBase}/v1/admin/sources`),
fetch(`${apiBase}/v1/admin/operations-matrix`),
])
if (!sourcesRes.ok || !opsRes.ok) throw new Error('Fehler beim Laden')
const data = await res.json()
setSources(data.sources || [])
const sourcesData = await sourcesRes.json()
const opsData = await opsRes.json()
// Join: group operations by source_id
const opsBySource = new Map<string, OperationPermission[]>()
for (const op of opsData.operations || []) {
const list = opsBySource.get(op.source_id) || []
list.push(op)
opsBySource.set(op.source_id, list)
}
const joined: SourceWithOperations[] = (sourcesData.sources || []).map((s: any) => ({
id: s.id,
domain: s.domain,
name: s.name,
license: s.license,
active: s.active,
operations: opsBySource.get(s.id) || [],
}))
setSources(joined)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unbekannter Fehler')
} finally {
@@ -58,28 +79,26 @@ export function OperationsMatrixTab({ apiBase }: OperationsMatrixTabProps) {
const togglePermission = async (
source: SourceWithOperations,
operationId: string,
field: 'is_allowed' | 'requires_citation'
operationId: string
) => {
// Find the permission
const permission = source.operations.find((op) => op.operation === operationId)
if (!permission) return
// Block enabling training
if (operationId === 'training' && field === 'is_allowed' && !permission.is_allowed) {
if (operationId === 'training' && !permission.allowed) {
setError('Training mit externen Daten ist VERBOTEN und kann nicht aktiviert werden.')
return
}
const updateId = `${permission.id}-${field}`
const updateId = `${permission.id}-allowed`
setUpdating(updateId)
try {
const newValue = !permission[field]
const res = await fetch(`${apiBase}/v1/admin/operations/${permission.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ [field]: newValue }),
body: JSON.stringify({ allowed: !permission.allowed }),
})
if (!res.ok) {
@@ -174,7 +193,7 @@ export function OperationsMatrixTab({ apiBase }: OperationsMatrixTabProps) {
</tr>
) : (
sources.map((source) => (
<tr key={source.id} className={`hover:bg-slate-50 ${!source.is_active ? 'opacity-50' : ''}`}>
<tr key={source.id} className={`hover:bg-slate-50 ${!source.active ? 'opacity-50' : ''}`}>
<td className="px-4 py-3">
<div>
<div className="font-medium text-slate-800">{source.name}</div>
@@ -184,17 +203,17 @@ export function OperationsMatrixTab({ apiBase }: OperationsMatrixTabProps) {
{OPERATIONS.map((op) => {
const permission = source.operations.find((p) => p.operation === op.id)
const isTraining = op.id === 'training'
const isAllowed = permission?.is_allowed ?? false
const requiresCitation = permission?.requires_citation ?? false
const isUpdating = updating === `${permission?.id}-is_allowed` || updating === `${permission?.id}-requires_citation`
const isAllowed = permission?.allowed ?? false
const hasConditions = !!permission?.conditions
const isUpdating = updating === `${permission?.id}-allowed`
return (
<td key={op.id} className="px-4 py-3 text-center">
<div className="flex flex-col items-center gap-2">
{/* Is Allowed Toggle */}
{/* Allowed Toggle */}
<button
onClick={() => togglePermission(source, op.id, 'is_allowed')}
disabled={isTraining || isUpdating || !source.is_active}
onClick={() => togglePermission(source, op.id)}
disabled={isTraining || isUpdating || !source.active}
className={`w-10 h-10 flex items-center justify-center rounded transition-colors ${
isTraining
? 'bg-slate-800 text-white cursor-not-allowed'
@@ -219,20 +238,14 @@ export function OperationsMatrixTab({ apiBase }: OperationsMatrixTabProps) {
)}
</button>
{/* Citation Required Toggle (only for allowed non-training ops) */}
{isAllowed && !isTraining && (
<button
onClick={() => togglePermission(source, op.id, 'requires_citation')}
disabled={isUpdating || !source.is_active}
className={`px-2 py-1 text-xs rounded transition-colors ${
requiresCitation
? 'bg-amber-100 text-amber-700 hover:bg-amber-200'
: 'bg-slate-100 text-slate-500 hover:bg-slate-200'
} ${isUpdating ? 'opacity-50' : ''}`}
title={requiresCitation ? 'Zitation erforderlich - Klicken zum Aendern' : 'Klicken um Zitation zu erfordern'}
{/* Conditions indicator (read-only) */}
{isAllowed && !isTraining && hasConditions && (
<span
className="px-2 py-1 text-xs rounded bg-amber-100 text-amber-700"
title={permission?.conditions || ''}
>
{requiresCitation ? 'Cite ✓' : 'Cite'}
</button>
Bedingung
</span>
)}
</div>
</td>