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:
@@ -1161,7 +1161,7 @@ export default function CompanyProfilePage() {
|
||||
}
|
||||
}
|
||||
|
||||
const completeAndSaveProfile = () => {
|
||||
const completeAndSaveProfile = async () => {
|
||||
const completeProfile: CompanyProfile = {
|
||||
...formData,
|
||||
isComplete: true,
|
||||
@@ -1170,6 +1170,41 @@ export default function CompanyProfilePage() {
|
||||
|
||||
setCompanyProfile(completeProfile)
|
||||
dispatch({ type: 'COMPLETE_STEP', payload: 'company-profile' })
|
||||
|
||||
// Also persist to dedicated backend endpoint
|
||||
try {
|
||||
await fetch('/api/sdk/v1/company-profile', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
company_name: formData.companyName || '',
|
||||
legal_form: formData.legalForm || 'GmbH',
|
||||
industry: formData.industry || '',
|
||||
founded_year: formData.foundedYear || null,
|
||||
business_model: formData.businessModel || 'B2B',
|
||||
offerings: formData.offerings || [],
|
||||
company_size: formData.companySize || 'small',
|
||||
employee_count: formData.employeeCount || '',
|
||||
annual_revenue: formData.annualRevenue || '',
|
||||
headquarters_country: formData.headquartersCountry || 'DE',
|
||||
headquarters_city: formData.headquartersCity || '',
|
||||
has_international_locations: formData.hasInternationalLocations || false,
|
||||
international_countries: formData.internationalCountries || [],
|
||||
target_markets: formData.targetMarkets || [],
|
||||
primary_jurisdiction: formData.primaryJurisdiction || 'DE',
|
||||
is_data_controller: formData.isDataController ?? true,
|
||||
is_data_processor: formData.isDataProcessor ?? false,
|
||||
uses_ai: formData.usesAI ?? false,
|
||||
ai_use_cases: formData.aiUseCases || [],
|
||||
dpo_name: formData.dpoName || '',
|
||||
dpo_email: formData.dpoEmail || '',
|
||||
is_complete: true,
|
||||
}),
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to save company profile to backend:', err)
|
||||
}
|
||||
|
||||
goToNextStep()
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,12 @@ import { AssessmentResultCard } from '@/components/sdk/use-case-assessment/Asses
|
||||
const WIZARD_STEPS = [
|
||||
{ id: 1, title: 'Grundlegendes', description: 'Titel und Beschreibung' },
|
||||
{ id: 2, title: 'Datenkategorien', description: 'Welche Daten werden verarbeitet?' },
|
||||
{ id: 3, title: 'Automatisierung', description: 'Grad der Automatisierung' },
|
||||
{ id: 4, title: 'Hosting & Modell', description: 'Technische Details' },
|
||||
{ id: 5, title: 'Datenhaltung', description: 'Aufbewahrung und Speicherung' },
|
||||
{ id: 3, title: 'Verarbeitungszweck', description: 'Rechtsgrundlage und Zweck' },
|
||||
{ id: 4, title: 'Automatisierung', description: 'Grad der Automatisierung' },
|
||||
{ id: 5, title: 'Hosting & Modell', description: 'Technische Details' },
|
||||
{ id: 6, title: 'Datentransfer', description: 'Internationaler Datentransfer' },
|
||||
{ id: 7, title: 'Datenhaltung', description: 'Aufbewahrung und Speicherung' },
|
||||
{ id: 8, title: 'Vertraege', description: 'Compliance und Vereinbarungen' },
|
||||
]
|
||||
|
||||
const DOMAINS = [
|
||||
@@ -70,9 +73,20 @@ export default function NewUseCasePage() {
|
||||
model_finetune: false,
|
||||
model_training: false,
|
||||
model_inference: true,
|
||||
// Retention
|
||||
// Legal Basis (Step 3)
|
||||
legal_basis: 'consent' as 'consent' | 'contract' | 'legitimate_interest' | 'legal_obligation' | 'vital_interest' | 'public_interest',
|
||||
// Data Transfer (Step 6)
|
||||
international_transfer: false,
|
||||
transfer_countries: [] as string[],
|
||||
transfer_mechanism: 'none' as 'none' | 'scc' | 'bcr' | 'adequacy' | 'derogation',
|
||||
// Retention (Step 7)
|
||||
retention_days: 90,
|
||||
retention_purpose: '',
|
||||
// Contracts (Step 8)
|
||||
has_dpa: false,
|
||||
has_aia_documentation: false,
|
||||
has_risk_assessment: false,
|
||||
subprocessors: '',
|
||||
})
|
||||
|
||||
const updateForm = (updates: Partial<typeof form>) => {
|
||||
@@ -113,10 +127,22 @@ export default function NewUseCasePage() {
|
||||
training: form.model_training,
|
||||
inference: form.model_inference,
|
||||
},
|
||||
legal_basis: form.legal_basis,
|
||||
international_transfer: {
|
||||
enabled: form.international_transfer,
|
||||
countries: form.transfer_countries,
|
||||
mechanism: form.transfer_mechanism,
|
||||
},
|
||||
retention: {
|
||||
days: form.retention_days,
|
||||
purpose: form.retention_purpose,
|
||||
},
|
||||
contracts: {
|
||||
has_dpa: form.has_dpa,
|
||||
has_aia_documentation: form.has_aia_documentation,
|
||||
has_risk_assessment: form.has_risk_assessment,
|
||||
subprocessors: form.subprocessors,
|
||||
},
|
||||
store_raw_text: true,
|
||||
}
|
||||
|
||||
@@ -276,6 +302,29 @@ export default function NewUseCasePage() {
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 3: Verarbeitungszweck & Rechtsgrundlage */}
|
||||
{currentStep === 3 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Verarbeitungszweck & Rechtsgrundlage</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Rechtsgrundlage (Art. 6 DSGVO)</label>
|
||||
<select
|
||||
value={form.legal_basis}
|
||||
onChange={e => updateForm({ legal_basis: e.target.value as typeof form.legal_basis })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
|
||||
>
|
||||
<option value="consent">Einwilligung (Art. 6 Abs. 1a)</option>
|
||||
<option value="contract">Vertragserfullung (Art. 6 Abs. 1b)</option>
|
||||
<option value="legal_obligation">Rechtliche Verpflichtung (Art. 6 Abs. 1c)</option>
|
||||
<option value="vital_interest">Lebenswichtige Interessen (Art. 6 Abs. 1d)</option>
|
||||
<option value="public_interest">Oeffentliches Interesse (Art. 6 Abs. 1e)</option>
|
||||
<option value="legitimate_interest">Berechtigtes Interesse (Art. 6 Abs. 1f)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-medium text-gray-700 mt-4">Zweck der Verarbeitung</h3>
|
||||
{[
|
||||
@@ -301,8 +350,8 @@ export default function NewUseCasePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 3: Automatisierung */}
|
||||
{currentStep === 3 && (
|
||||
{/* Step 4: Automatisierung */}
|
||||
{currentStep === 4 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Grad der Automatisierung</h2>
|
||||
{[
|
||||
@@ -335,8 +384,8 @@ export default function NewUseCasePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 4: Hosting & Modell */}
|
||||
{currentStep === 4 && (
|
||||
{/* Step 5: Hosting & Modell */}
|
||||
{currentStep === 5 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Technische Details</h2>
|
||||
<div>
|
||||
@@ -390,8 +439,59 @@ export default function NewUseCasePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 5: Datenhaltung */}
|
||||
{currentStep === 5 && (
|
||||
{/* Step 6: Internationaler Datentransfer */}
|
||||
{currentStep === 6 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Internationaler Datentransfer</h2>
|
||||
|
||||
<label className="flex items-start gap-3 p-4 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.international_transfer}
|
||||
onChange={e => updateForm({ international_transfer: e.target.checked })}
|
||||
className="mt-1 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">Daten werden in Drittlaender uebermittelt</div>
|
||||
<div className="text-sm text-gray-500">Ausserhalb des EWR (z.B. USA, UK, Schweiz)</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{form.international_transfer && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ziellaender</label>
|
||||
<input
|
||||
type="text"
|
||||
value={form.transfer_countries.join(', ')}
|
||||
onChange={e => updateForm({ transfer_countries: e.target.value.split(',').map(s => s.trim()).filter(Boolean) })}
|
||||
placeholder="z.B. USA, UK, CH"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Kommagetrennte Laenderkuerzel</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Transfer-Mechanismus</label>
|
||||
<select
|
||||
value={form.transfer_mechanism}
|
||||
onChange={e => updateForm({ transfer_mechanism: e.target.value as typeof form.transfer_mechanism })}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
|
||||
>
|
||||
<option value="none">Noch nicht festgelegt</option>
|
||||
<option value="adequacy">Angemessenheitsbeschluss</option>
|
||||
<option value="scc">Standardvertragsklauseln (SCC)</option>
|
||||
<option value="bcr">Binding Corporate Rules (BCR)</option>
|
||||
<option value="derogation">Ausnahmeregelung (Art. 49 DSGVO)</option>
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 7: Datenhaltung */}
|
||||
{currentStep === 7 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Datenhaltung & Aufbewahrung</h2>
|
||||
<div>
|
||||
@@ -420,6 +520,43 @@ export default function NewUseCasePage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 8: Vertraege & Compliance */}
|
||||
{currentStep === 8 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Vertraege & Compliance-Dokumentation</h2>
|
||||
|
||||
{[
|
||||
{ key: 'has_dpa', label: 'Auftragsverarbeitungsvertrag (AVV/DPA)', desc: 'Vertrag mit KI-Anbieter / Subprozessor nach Art. 28 DSGVO' },
|
||||
{ key: 'has_aia_documentation', label: 'AI Act Dokumentation', desc: 'Risikoklassifizierung und technische Dokumentation nach EU AI Act' },
|
||||
{ key: 'has_risk_assessment', label: 'Risikobewertung / DSFA', desc: 'Datenschutz-Folgenabschaetzung nach Art. 35 DSGVO' },
|
||||
].map(item => (
|
||||
<label key={item.key} className="flex items-start gap-3 p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form[item.key as keyof typeof form] as boolean}
|
||||
onChange={e => updateForm({ [item.key]: e.target.checked })}
|
||||
className="mt-1 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{item.label}</div>
|
||||
<div className="text-sm text-gray-500">{item.desc}</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Subprozessoren</label>
|
||||
<textarea
|
||||
value={form.subprocessors}
|
||||
onChange={e => updateForm({ subprocessors: e.target.value })}
|
||||
rows={3}
|
||||
placeholder="z.B. OpenAI (USA, SCC), Hetzner Cloud (DE)..."
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
@@ -431,7 +568,7 @@ export default function NewUseCasePage() {
|
||||
{currentStep === 1 ? 'Abbrechen' : 'Zurueck'}
|
||||
</button>
|
||||
|
||||
{currentStep < 5 ? (
|
||||
{currentStep < 8 ? (
|
||||
<button
|
||||
onClick={() => setCurrentStep(currentStep + 1)}
|
||||
disabled={currentStep === 1 && !form.title}
|
||||
|
||||
Reference in New Issue
Block a user