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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user