This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/components/sdk/tom-generator/steps/ArchitectureStep.tsx
BreakPilot Dev 660295e218 fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit
restores all missing components:

- Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education,
  infrastructure, communication, development, onboarding, rbac
- SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen,
  vendor-compliance, tom-generator, dsr, and more
- Developer portal (25 pages): API docs, SDK guides, frameworks
- All components, lib files, hooks, and types
- Updated package.json with all dependencies

The issue was caused by incomplete initial repository state - the full
admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2
but was never fully synced to the main admin-v2 directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 23:40:15 -08:00

461 lines
18 KiB
TypeScript

'use client'
// =============================================================================
// Step 3: Architecture & Hosting
// Hosting model, location, and provider configuration
// =============================================================================
import React, { useState, useEffect } from 'react'
import { useTOMGenerator } from '@/lib/sdk/tom-generator'
import {
ArchitectureProfile,
HostingModel,
HostingLocation,
MultiTenancy,
CloudProvider,
} from '@/lib/sdk/tom-generator/types'
// =============================================================================
// CONSTANTS
// =============================================================================
const HOSTING_MODELS: { value: HostingModel; label: string; description: string; icon: string }[] = [
{
value: 'ON_PREMISE',
label: 'On-Premise',
description: 'Eigenes Rechenzentrum oder Co-Location',
icon: '🏢',
},
{
value: 'PRIVATE_CLOUD',
label: 'Private Cloud',
description: 'Dedizierte Cloud-Infrastruktur',
icon: '☁️',
},
{
value: 'PUBLIC_CLOUD',
label: 'Public Cloud',
description: 'AWS, Azure, GCP oder andere',
icon: '🌐',
},
{
value: 'HYBRID',
label: 'Hybrid',
description: 'Kombination aus On-Premise und Cloud',
icon: '🔄',
},
]
const HOSTING_LOCATIONS: { value: HostingLocation; label: string; description: string }[] = [
{ value: 'DE', label: 'Deutschland', description: 'Rechenzentrum in Deutschland' },
{ value: 'EU', label: 'EU (nicht DE)', description: 'Innerhalb der EU, aber nicht in Deutschland' },
{ value: 'EEA', label: 'EWR', description: 'Europäischer Wirtschaftsraum' },
{ value: 'THIRD_COUNTRY_ADEQUATE', label: 'Drittland (Angemessenheit)', description: 'Mit Angemessenheitsbeschluss' },
{ value: 'THIRD_COUNTRY', label: 'Drittland (andere)', description: 'Ohne Angemessenheitsbeschluss' },
]
const MULTI_TENANCY_OPTIONS: { value: MultiTenancy; label: string; description: string }[] = [
{ value: 'SINGLE_TENANT', label: 'Single-Tenant', description: 'Dedizierte Instanz pro Kunde' },
{ value: 'MULTI_TENANT', label: 'Multi-Tenant', description: 'Geteilte Infrastruktur mit logischer Trennung' },
{ value: 'DEDICATED', label: 'Dedicated', description: 'Dedizierte Hardware, aber gemeinsame Software' },
]
const COMMON_CERTIFICATIONS = [
'ISO 27001',
'SOC 2 Type II',
'C5',
'TISAX',
'PCI DSS',
'HIPAA',
'FedRAMP',
]
// =============================================================================
// COMPONENT
// =============================================================================
export function ArchitectureStep() {
const { state, setArchitectureProfile, completeCurrentStep } = useTOMGenerator()
const [formData, setFormData] = useState<Partial<ArchitectureProfile>>({
hostingModel: 'PUBLIC_CLOUD',
hostingLocation: 'EU',
providers: [],
multiTenancy: 'MULTI_TENANT',
hasSubprocessors: false,
subprocessorCount: 0,
encryptionAtRest: false,
encryptionInTransit: false,
})
const [newProvider, setNewProvider] = useState<Partial<CloudProvider>>({
name: '',
location: 'EU',
certifications: [],
})
const [certificationInput, setCertificationInput] = useState('')
// Load existing data
useEffect(() => {
if (state.architectureProfile) {
setFormData(state.architectureProfile)
}
}, [state.architectureProfile])
// Handle provider addition
const addProvider = () => {
if (newProvider.name?.trim()) {
const provider: CloudProvider = {
name: newProvider.name.trim(),
location: newProvider.location || 'EU',
certifications: newProvider.certifications || [],
}
setFormData((prev) => ({
...prev,
providers: [...(prev.providers || []), provider],
}))
setNewProvider({ name: '', location: 'EU', certifications: [] })
}
}
const removeProvider = (index: number) => {
setFormData((prev) => ({
...prev,
providers: (prev.providers || []).filter((_, i) => i !== index),
}))
}
// Handle certification toggle
const toggleCertification = (cert: string) => {
setNewProvider((prev) => {
const current = prev.certifications || []
const updated = current.includes(cert)
? current.filter((c) => c !== cert)
: [...current, cert]
return { ...prev, certifications: updated }
})
}
// Handle submit
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const profile: ArchitectureProfile = {
hostingModel: formData.hostingModel || 'PUBLIC_CLOUD',
hostingLocation: formData.hostingLocation || 'EU',
providers: formData.providers || [],
multiTenancy: formData.multiTenancy || 'MULTI_TENANT',
hasSubprocessors: formData.hasSubprocessors || false,
subprocessorCount: formData.subprocessorCount || 0,
encryptionAtRest: formData.encryptionAtRest || false,
encryptionInTransit: formData.encryptionInTransit || false,
}
setArchitectureProfile(profile)
completeCurrentStep(profile)
}
const showProviderSection = formData.hostingModel !== 'ON_PREMISE'
return (
<form onSubmit={handleSubmit} className="space-y-8">
{/* Hosting Model */}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Hosting-Modell</h3>
<p className="text-sm text-gray-600 mb-4">
Wie wird Ihre Infrastruktur betrieben?
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{HOSTING_MODELS.map((model) => (
<label
key={model.value}
className={`relative flex items-start p-4 border rounded-lg cursor-pointer transition-all ${
formData.hostingModel === model.value
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<input
type="radio"
name="hostingModel"
value={model.value}
checked={formData.hostingModel === model.value}
onChange={(e) => setFormData((prev) => ({ ...prev, hostingModel: e.target.value as HostingModel }))}
className="sr-only"
/>
<span className="text-2xl mr-3">{model.icon}</span>
<div className="flex-1">
<span className="font-medium text-gray-900">{model.label}</span>
<p className="text-sm text-gray-500 mt-1">{model.description}</p>
</div>
{formData.hostingModel === model.value && (
<svg className="w-5 h-5 text-blue-500 absolute top-3 right-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
)}
</label>
))}
</div>
</div>
{/* Hosting Location */}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Primärer Hosting-Standort</h3>
<p className="text-sm text-gray-600 mb-4">
Wo werden die Daten primär gespeichert?
</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{HOSTING_LOCATIONS.map((location) => (
<label
key={location.value}
className={`relative flex items-start p-3 border rounded-lg cursor-pointer transition-all ${
formData.hostingLocation === location.value
? location.value.startsWith('THIRD_COUNTRY')
? 'border-amber-500 bg-amber-50'
: 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<input
type="radio"
name="hostingLocation"
value={location.value}
checked={formData.hostingLocation === location.value}
onChange={(e) => setFormData((prev) => ({ ...prev, hostingLocation: e.target.value as HostingLocation }))}
className="sr-only"
/>
<div className="flex-1">
<span className="font-medium text-gray-900">{location.label}</span>
<p className="text-xs text-gray-500 mt-0.5">{location.description}</p>
</div>
</label>
))}
</div>
{formData.hostingLocation?.startsWith('THIRD_COUNTRY') && (
<div className="mt-4 bg-amber-50 border border-amber-200 rounded-lg p-4">
<p className="text-sm text-amber-800">
<strong>Hinweis:</strong> Bei Hosting in Drittländern sind zusätzliche Garantien nach Art. 46 DSGVO
erforderlich (z.B. Standardvertragsklauseln).
</p>
</div>
)}
</div>
{/* Cloud Providers */}
{showProviderSection && (
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Cloud-Provider / Rechenzentren</h3>
<p className="text-sm text-gray-600 mb-4">
Fügen Sie Ihre genutzten Provider hinzu.
</p>
{/* Existing providers */}
{formData.providers && formData.providers.length > 0 && (
<div className="space-y-2 mb-4">
{formData.providers.map((provider, index) => (
<div
key={index}
className="flex items-center justify-between p-3 bg-gray-50 border rounded-lg"
>
<div>
<span className="font-medium text-gray-900">{provider.name}</span>
<span className="text-sm text-gray-500 ml-2">({provider.location})</span>
{provider.certifications.length > 0 && (
<div className="flex gap-1 mt-1">
{provider.certifications.map((cert) => (
<span
key={cert}
className="px-2 py-0.5 text-xs bg-green-100 text-green-800 rounded"
>
{cert}
</span>
))}
</div>
)}
</div>
<button
type="button"
onClick={() => removeProvider(index)}
className="text-gray-400 hover:text-red-500"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
))}
</div>
)}
{/* Add new provider */}
<div className="border rounded-lg p-4 bg-gray-50">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Provider-Name
</label>
<input
type="text"
value={newProvider.name || ''}
onChange={(e) => setNewProvider((prev) => ({ ...prev, name: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
placeholder="z.B. AWS, Azure, Hetzner"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Standort
</label>
<select
value={newProvider.location || 'EU'}
onChange={(e) => setNewProvider((prev) => ({ ...prev, location: e.target.value as HostingLocation }))}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
>
{HOSTING_LOCATIONS.map((loc) => (
<option key={loc.value} value={loc.value}>{loc.label}</option>
))}
</select>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
Zertifizierungen
</label>
<div className="flex flex-wrap gap-2">
{COMMON_CERTIFICATIONS.map((cert) => (
<button
key={cert}
type="button"
onClick={() => toggleCertification(cert)}
className={`px-3 py-1 text-sm rounded-full border transition-all ${
newProvider.certifications?.includes(cert)
? 'bg-green-100 border-green-300 text-green-800'
: 'bg-white border-gray-300 text-gray-600 hover:border-gray-400'
}`}
>
{cert}
</button>
))}
</div>
</div>
<button
type="button"
onClick={addProvider}
disabled={!newProvider.name?.trim()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
>
Provider hinzufügen
</button>
</div>
</div>
)}
{/* Multi-Tenancy */}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Mandantentrennung</h3>
<p className="text-sm text-gray-600 mb-4">
Wie ist die Trennung zwischen verschiedenen Mandanten/Kunden umgesetzt?
</p>
<div className="space-y-3">
{MULTI_TENANCY_OPTIONS.map((option) => (
<label
key={option.value}
className={`relative flex items-start p-4 border rounded-lg cursor-pointer transition-all ${
formData.multiTenancy === option.value
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<input
type="radio"
name="multiTenancy"
value={option.value}
checked={formData.multiTenancy === option.value}
onChange={(e) => setFormData((prev) => ({ ...prev, multiTenancy: e.target.value as MultiTenancy }))}
className="sr-only"
/>
<div className="flex-1">
<span className="font-medium text-gray-900">{option.label}</span>
<p className="text-sm text-gray-500 mt-1">{option.description}</p>
</div>
</label>
))}
</div>
</div>
{/* Subprocessors */}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Unterauftragsverarbeiter</h3>
<label className="flex items-center gap-3 cursor-pointer mb-4">
<input
type="checkbox"
checked={formData.hasSubprocessors || false}
onChange={(e) => setFormData((prev) => ({ ...prev, hasSubprocessors: e.target.checked }))}
className="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="text-gray-700">
Wir setzen Unterauftragsverarbeiter ein
</span>
</label>
{formData.hasSubprocessors && (
<div className="pl-8">
<label className="block text-sm font-medium text-gray-700 mb-1">
Anzahl der Unterauftragsverarbeiter
</label>
<input
type="number"
min="0"
value={formData.subprocessorCount || 0}
onChange={(e) => setFormData((prev) => ({ ...prev, subprocessorCount: parseInt(e.target.value) || 0 }))}
className="w-32 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
/>
</div>
)}
</div>
{/* Encryption */}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Verschlüsselung</h3>
<div className="space-y-3">
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={formData.encryptionAtRest || false}
onChange={(e) => setFormData((prev) => ({ ...prev, encryptionAtRest: e.target.checked }))}
className="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<div>
<span className="text-gray-700 font-medium">Verschlüsselung ruhender Daten (at rest)</span>
<p className="text-sm text-gray-500">Daten werden verschlüsselt gespeichert</p>
</div>
</label>
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={formData.encryptionInTransit || false}
onChange={(e) => setFormData((prev) => ({ ...prev, encryptionInTransit: e.target.checked }))}
className="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<div>
<span className="text-gray-700 font-medium">Transportverschlüsselung (in transit)</span>
<p className="text-sm text-gray-500">TLS/SSL für alle Datenübertragungen</p>
</div>
</label>
</div>
</div>
</form>
)
}
export default ArchitectureStep