feat(admin-v2): Katalogverwaltung ins Admin-Dashboard integrieren
Katalogverwaltung von /sdk/catalog-manager nach /dashboard/catalog-manager verschoben, damit sie im Admin-Dashboard mit Sidebar erscheint statt im SDK-Bereich. Shared Components extrahiert, SDK-Route bleibt funktionsfaehig. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* DSGVO (Datenschutz) and Compliance (Audit & GRC) are now separate
|
||||
*/
|
||||
|
||||
export type CategoryId = 'dsgvo' | 'compliance' | 'ai' | 'infrastructure' | 'education' | 'communication' | 'development' | 'sdk-docs'
|
||||
export type CategoryId = 'dsgvo' | 'compliance' | 'compliance-sdk' | 'ai' | 'infrastructure' | 'education' | 'communication' | 'development' | 'sdk-docs'
|
||||
|
||||
export interface NavModule {
|
||||
id: string
|
||||
@@ -260,6 +260,27 @@ export const navigation: NavCategory[] = [
|
||||
],
|
||||
},
|
||||
// =========================================================================
|
||||
// Compliance SDK - Datenschutz-Werkzeuge & Kataloge
|
||||
// =========================================================================
|
||||
{
|
||||
id: 'compliance-sdk',
|
||||
name: 'Compliance SDK',
|
||||
icon: 'database',
|
||||
color: '#8b5cf6', // Violet-500
|
||||
colorClass: 'compliance-sdk',
|
||||
description: 'SDK-Kataloge, Risiken & Massnahmen',
|
||||
modules: [
|
||||
{
|
||||
id: 'catalog-manager',
|
||||
name: 'Katalogverwaltung',
|
||||
href: '/dashboard/catalog-manager',
|
||||
description: 'SDK-Kataloge & Auswahltabellen',
|
||||
purpose: 'Zentrale Verwaltung aller Dropdown- und Auswahltabellen im SDK. Systemkataloge (Risiken, Massnahmen, Vorlagen) anzeigen und benutzerdefinierte Eintraege ergaenzen, bearbeiten und loeschen.',
|
||||
audience: ['DSB', 'Compliance Officer', 'Administratoren'],
|
||||
},
|
||||
],
|
||||
},
|
||||
// =========================================================================
|
||||
// KI & Automatisierung
|
||||
// =========================================================================
|
||||
{
|
||||
@@ -486,6 +507,15 @@ export const navigation: NavCategory[] = [
|
||||
audience: ['Lehrer', 'Entwickler'],
|
||||
oldAdminPath: '/admin/klausur-korrektur',
|
||||
},
|
||||
{
|
||||
id: 'companion',
|
||||
name: 'Companion',
|
||||
href: '/education/companion',
|
||||
description: 'Unterrichts-Timer & Phasen',
|
||||
purpose: 'Strukturierter Unterricht mit 5-Phasen-Modell (E-A-S-T-R). Visual Timer, Hausaufgaben-Tracking und Reflexion.',
|
||||
audience: ['Lehrer'],
|
||||
oldAdminPath: '/admin/companion',
|
||||
},
|
||||
],
|
||||
},
|
||||
// =========================================================================
|
||||
|
||||
665
admin-v2/lib/sdk/catalog-manager/catalog-registry.ts
Normal file
665
admin-v2/lib/sdk/catalog-manager/catalog-registry.ts
Normal file
@@ -0,0 +1,665 @@
|
||||
/**
|
||||
* SDK Catalog Manager - Central Registry
|
||||
*
|
||||
* Maps all SDK catalogs to a unified interface for browsing, searching, and CRUD.
|
||||
*/
|
||||
|
||||
import type {
|
||||
CatalogId,
|
||||
CatalogMeta,
|
||||
CatalogModule,
|
||||
CatalogEntry,
|
||||
CatalogStats,
|
||||
CatalogOverviewStats,
|
||||
CustomCatalogEntry,
|
||||
CustomCatalogs,
|
||||
} from './types'
|
||||
|
||||
// =============================================================================
|
||||
// CATALOG DATA IMPORTS
|
||||
// =============================================================================
|
||||
|
||||
import { RISK_CATALOG } from '../dsfa/risk-catalog'
|
||||
import { MITIGATION_LIBRARY } from '../dsfa/mitigation-library'
|
||||
import { AI_RISK_CATALOG } from '../dsfa/ai-risk-catalog'
|
||||
import { AI_MITIGATION_LIBRARY } from '../dsfa/ai-mitigation-library'
|
||||
import { PROHIBITED_AI_PRACTICES } from '../dsfa/prohibited-ai-practices'
|
||||
import { EU_BASE_FRAMEWORKS, NATIONAL_FRAMEWORKS } from '../dsfa/eu-legal-frameworks'
|
||||
import { GDPR_ENFORCEMENT_CASES } from '../dsfa/gdpr-enforcement-cases'
|
||||
import { WP248_CRITERIA, SDM_GOALS, DSFA_AUTHORITY_RESOURCES } from '../dsfa/types'
|
||||
import { VVT_BASELINE_CATALOG } from '../vvt-baseline-catalog'
|
||||
import { BASELINE_TEMPLATES } from '../loeschfristen-baseline-catalog'
|
||||
import { VENDOR_TEMPLATES, COUNTRY_RISK_PROFILES } from '../vendor-compliance/catalog/vendor-templates'
|
||||
import { LEGAL_BASIS_INFO, STANDARD_RETENTION_PERIODS } from '../vendor-compliance/catalog/legal-basis'
|
||||
|
||||
// =============================================================================
|
||||
// HELPER: Resolve localized text fields
|
||||
// =============================================================================
|
||||
|
||||
function resolveField(value: unknown): string {
|
||||
if (value === null || value === undefined) return ''
|
||||
if (typeof value === 'string') return value
|
||||
if (typeof value === 'number') return String(value)
|
||||
if (typeof value === 'object' && 'de' in (value as Record<string, unknown>)) {
|
||||
return String((value as Record<string, string>).de || '')
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SDM_GOALS as entries array (it's a Record, not an array)
|
||||
// =============================================================================
|
||||
|
||||
const SDM_GOALS_ENTRIES = Object.entries(SDM_GOALS).map(([key, val]) => ({
|
||||
id: key,
|
||||
name: val.name,
|
||||
description: val.description,
|
||||
article: val.article,
|
||||
}))
|
||||
|
||||
// =============================================================================
|
||||
// CATALOG REGISTRY
|
||||
// =============================================================================
|
||||
|
||||
export const CATALOG_REGISTRY: Record<CatalogId, CatalogMeta> = {
|
||||
'dsfa-risks': {
|
||||
id: 'dsfa-risks',
|
||||
name: 'DSFA Risikokatalog',
|
||||
description: 'Standardrisiken fuer Datenschutz-Folgenabschaetzungen',
|
||||
module: 'dsfa',
|
||||
icon: 'ShieldAlert',
|
||||
systemCount: RISK_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'category',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
|
||||
{ value: 'confidentiality', label: 'Vertraulichkeit' },
|
||||
{ value: 'integrity', label: 'Integritaet' },
|
||||
{ value: 'availability', label: 'Verfuegbarkeit' },
|
||||
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
|
||||
]},
|
||||
{ key: 'typicalLikelihood', label: 'Typische Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'typicalImpact', label: 'Typische Auswirkung', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'category'],
|
||||
},
|
||||
|
||||
'dsfa-mitigations': {
|
||||
id: 'dsfa-mitigations',
|
||||
name: 'DSFA Massnahmenbibliothek',
|
||||
description: 'Technische und organisatorische Massnahmen fuer DSFAs',
|
||||
module: 'dsfa',
|
||||
icon: 'Shield',
|
||||
systemCount: MITIGATION_LIBRARY.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true, placeholder: 'M-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
|
||||
{ value: 'technical', label: 'Technisch' },
|
||||
{ value: 'organizational', label: 'Organisatorisch' },
|
||||
{ value: 'legal', label: 'Rechtlich' },
|
||||
]},
|
||||
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'type', 'legalBasis'],
|
||||
},
|
||||
|
||||
'ai-risks': {
|
||||
id: 'ai-risks',
|
||||
name: 'KI-Risikokatalog',
|
||||
description: 'Spezifische Risiken fuer KI-Systeme',
|
||||
module: 'ai_act',
|
||||
icon: 'Bot',
|
||||
systemCount: AI_RISK_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'category',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-AI-XXX-01' },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
|
||||
{ value: 'confidentiality', label: 'Vertraulichkeit' },
|
||||
{ value: 'integrity', label: 'Integritaet' },
|
||||
{ value: 'availability', label: 'Verfuegbarkeit' },
|
||||
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
|
||||
]},
|
||||
{ key: 'typicalLikelihood', label: 'Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
{ key: 'typicalImpact', label: 'Auswirkung', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'category'],
|
||||
},
|
||||
|
||||
'ai-mitigations': {
|
||||
id: 'ai-mitigations',
|
||||
name: 'KI-Massnahmenbibliothek',
|
||||
description: 'Massnahmen fuer KI-spezifische Risiken',
|
||||
module: 'ai_act',
|
||||
icon: 'ShieldCheck',
|
||||
systemCount: AI_MITIGATION_LIBRARY.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
|
||||
{ value: 'technical', label: 'Technisch' },
|
||||
{ value: 'organizational', label: 'Organisatorisch' },
|
||||
{ value: 'legal', label: 'Rechtlich' },
|
||||
]},
|
||||
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
|
||||
{ value: 'low', label: 'Niedrig' },
|
||||
{ value: 'medium', label: 'Mittel' },
|
||||
{ value: 'high', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'type'],
|
||||
},
|
||||
|
||||
'prohibited-ai-practices': {
|
||||
id: 'prohibited-ai-practices',
|
||||
name: 'Verbotene KI-Praktiken',
|
||||
description: 'Absolut und bedingt verbotene KI-Anwendungen nach AI Act',
|
||||
module: 'ai_act',
|
||||
icon: 'Ban',
|
||||
systemCount: PROHIBITED_AI_PRACTICES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'severity',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'severity', label: 'Schwere', type: 'select', required: true, options: [
|
||||
{ value: 'absolute', label: 'Absolutes Verbot' },
|
||||
{ value: 'conditional', label: 'Bedingtes Verbot' },
|
||||
]},
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'title', 'description', 'severity', 'legalBasis'],
|
||||
},
|
||||
|
||||
'vvt-templates': {
|
||||
id: 'vvt-templates',
|
||||
name: 'VVT Baseline-Vorlagen',
|
||||
description: 'Vorlagen fuer Verarbeitungstaetigkeiten',
|
||||
module: 'vvt',
|
||||
icon: 'FileText',
|
||||
systemCount: VVT_BASELINE_CATALOG.length,
|
||||
allowCustom: true,
|
||||
idField: 'templateId',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'businessFunction',
|
||||
fields: [
|
||||
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'businessFunction', label: 'Geschaeftsbereich', type: 'select', required: true, options: [
|
||||
{ value: 'hr', label: 'Personal' },
|
||||
{ value: 'finance', label: 'Finanzen' },
|
||||
{ value: 'sales', label: 'Vertrieb' },
|
||||
{ value: 'marketing', label: 'Marketing' },
|
||||
{ value: 'support', label: 'Support' },
|
||||
{ value: 'it', label: 'IT' },
|
||||
{ value: 'other', label: 'Sonstige' },
|
||||
]},
|
||||
{ key: 'protectionLevel', label: 'Schutzniveau', type: 'select', required: false, options: [
|
||||
{ value: 'LOW', label: 'Niedrig' },
|
||||
{ value: 'MEDIUM', label: 'Mittel' },
|
||||
{ value: 'HIGH', label: 'Hoch' },
|
||||
]},
|
||||
],
|
||||
searchableFields: ['templateId', 'name', 'description', 'businessFunction'],
|
||||
},
|
||||
|
||||
'loeschfristen-templates': {
|
||||
id: 'loeschfristen-templates',
|
||||
name: 'Loeschfristen-Vorlagen',
|
||||
description: 'Baseline-Vorlagen fuer Aufbewahrungsfristen',
|
||||
module: 'vvt',
|
||||
icon: 'Clock',
|
||||
systemCount: BASELINE_TEMPLATES.length,
|
||||
allowCustom: true,
|
||||
idField: 'templateId',
|
||||
nameField: 'dataObjectName',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'retentionDriver',
|
||||
fields: [
|
||||
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
|
||||
{ key: 'dataObjectName', label: 'Datenobjekt', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'retentionDuration', label: 'Aufbewahrungsdauer', type: 'number', required: true, min: 0 },
|
||||
{ key: 'retentionUnit', label: 'Einheit', type: 'select', required: true, options: [
|
||||
{ value: 'days', label: 'Tage' },
|
||||
{ value: 'months', label: 'Monate' },
|
||||
{ value: 'years', label: 'Jahre' },
|
||||
]},
|
||||
{ key: 'deletionMethod', label: 'Loeschmethode', type: 'text', required: false },
|
||||
{ key: 'responsibleRole', label: 'Verantwortlich', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['templateId', 'dataObjectName', 'description', 'retentionDriver'],
|
||||
},
|
||||
|
||||
'vendor-templates': {
|
||||
id: 'vendor-templates',
|
||||
name: 'AV-Vorlagen',
|
||||
description: 'Vorlagen fuer Auftragsverarbeitungsvertraege',
|
||||
module: 'vendor',
|
||||
icon: 'Building2',
|
||||
systemCount: VENDOR_TEMPLATES.length,
|
||||
allowCustom: true,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'serviceCategory',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'serviceCategory', label: 'Kategorie', type: 'text', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'description', 'serviceCategory'],
|
||||
},
|
||||
|
||||
'country-risk-profiles': {
|
||||
id: 'country-risk-profiles',
|
||||
name: 'Laenderrisikoprofile',
|
||||
description: 'Datenschutz-Risikobewertung nach Laendern',
|
||||
module: 'vendor',
|
||||
icon: 'Globe',
|
||||
systemCount: COUNTRY_RISK_PROFILES.length,
|
||||
allowCustom: false,
|
||||
idField: 'code',
|
||||
nameField: 'name',
|
||||
categoryField: 'riskLevel',
|
||||
fields: [
|
||||
{ key: 'code', label: 'Laendercode', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'riskLevel', label: 'Risikostufe', type: 'select', required: true, options: [
|
||||
{ value: 'LOW', label: 'Niedrig' },
|
||||
{ value: 'MEDIUM', label: 'Mittel' },
|
||||
{ value: 'HIGH', label: 'Hoch' },
|
||||
{ value: 'VERY_HIGH', label: 'Sehr hoch' },
|
||||
]},
|
||||
{ key: 'isEU', label: 'EU-Mitglied', type: 'boolean', required: false },
|
||||
{ key: 'isEEA', label: 'EWR-Mitglied', type: 'boolean', required: false },
|
||||
{ key: 'hasAdequacyDecision', label: 'Angemessenheitsbeschluss', type: 'boolean', required: false },
|
||||
],
|
||||
searchableFields: ['code', 'name', 'riskLevel'],
|
||||
},
|
||||
|
||||
'legal-bases': {
|
||||
id: 'legal-bases',
|
||||
name: 'Rechtsgrundlagen',
|
||||
description: 'DSGVO Art. 6 und Art. 9 Rechtsgrundlagen',
|
||||
module: 'reference',
|
||||
icon: 'Scale',
|
||||
systemCount: LEGAL_BASIS_INFO.length,
|
||||
allowCustom: false,
|
||||
idField: 'type',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'article',
|
||||
fields: [
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'article', label: 'Artikel', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'isSpecialCategory', label: 'Besondere Kategorie (Art. 9)', type: 'boolean', required: false },
|
||||
],
|
||||
searchableFields: ['type', 'article', 'name', 'description'],
|
||||
},
|
||||
|
||||
'retention-periods': {
|
||||
id: 'retention-periods',
|
||||
name: 'Aufbewahrungsfristen',
|
||||
description: 'Gesetzliche Standard-Aufbewahrungsfristen',
|
||||
module: 'reference',
|
||||
icon: 'Timer',
|
||||
systemCount: STANDARD_RETENTION_PERIODS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'legalBasis', 'description'],
|
||||
},
|
||||
|
||||
'eu-legal-frameworks': {
|
||||
id: 'eu-legal-frameworks',
|
||||
name: 'EU-Rechtsrahmen',
|
||||
description: 'EU-weite Datenschutzgesetze und -verordnungen',
|
||||
module: 'reference',
|
||||
icon: 'Landmark',
|
||||
systemCount: EU_BASE_FRAMEWORKS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'type',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'fullName', label: 'Vollstaendiger Name', type: 'text', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'fullName', 'description', 'type'],
|
||||
},
|
||||
|
||||
'national-legal-frameworks': {
|
||||
id: 'national-legal-frameworks',
|
||||
name: 'Nationale Rechtsrahmen',
|
||||
description: 'Nationale Datenschutzgesetze der EU/EWR-Staaten',
|
||||
module: 'reference',
|
||||
icon: 'Flag',
|
||||
systemCount: NATIONAL_FRAMEWORKS.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'countryCode',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'countryCode', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'type', label: 'Typ', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'countryCode', 'description', 'type'],
|
||||
},
|
||||
|
||||
'gdpr-enforcement-cases': {
|
||||
id: 'gdpr-enforcement-cases',
|
||||
name: 'DSGVO-Bussgeldentscheidungen',
|
||||
description: 'Relevante Bussgeldentscheidungen als Referenz',
|
||||
module: 'reference',
|
||||
icon: 'Gavel',
|
||||
systemCount: GDPR_ENFORCEMENT_CASES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'company',
|
||||
descriptionField: 'description',
|
||||
categoryField: 'country',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'company', label: 'Unternehmen', type: 'text', required: true },
|
||||
{ key: 'country', label: 'Land', type: 'text', required: true },
|
||||
{ key: 'year', label: 'Jahr', type: 'number', required: true },
|
||||
{ key: 'fineOriginal', label: 'Bussgeld (EUR)', type: 'number', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'company', 'country', 'description'],
|
||||
},
|
||||
|
||||
'wp248-criteria': {
|
||||
id: 'wp248-criteria',
|
||||
name: 'WP248 Kriterien',
|
||||
description: 'Kriterien zur DSFA-Pflichtpruefung nach WP248',
|
||||
module: 'dsfa',
|
||||
icon: 'ClipboardCheck',
|
||||
systemCount: WP248_CRITERIA.length,
|
||||
allowCustom: false,
|
||||
idField: 'code',
|
||||
nameField: 'title',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'code', label: 'Code', type: 'text', required: true },
|
||||
{ key: 'title', label: 'Titel', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'gdprRef', label: 'DSGVO-Referenz', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['code', 'title', 'description', 'gdprRef'],
|
||||
},
|
||||
|
||||
'sdm-goals': {
|
||||
id: 'sdm-goals',
|
||||
name: 'SDM Gewaehrleistungsziele',
|
||||
description: 'Standard-Datenschutzmodell Gewaehrleistungsziele',
|
||||
module: 'dsfa',
|
||||
icon: 'Target',
|
||||
systemCount: SDM_GOALS_ENTRIES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'name',
|
||||
descriptionField: 'description',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true },
|
||||
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
|
||||
{ key: 'article', label: 'DSGVO-Artikel', type: 'text', required: false },
|
||||
],
|
||||
searchableFields: ['id', 'name', 'description', 'article'],
|
||||
},
|
||||
|
||||
'dsfa-authority-resources': {
|
||||
id: 'dsfa-authority-resources',
|
||||
name: 'Aufsichtsbehoerden-Ressourcen',
|
||||
description: 'DSFA-Ressourcen der deutschen Aufsichtsbehoerden',
|
||||
module: 'dsfa',
|
||||
icon: 'Building',
|
||||
systemCount: DSFA_AUTHORITY_RESOURCES.length,
|
||||
allowCustom: false,
|
||||
idField: 'id',
|
||||
nameField: 'shortName',
|
||||
descriptionField: 'name',
|
||||
categoryField: 'state',
|
||||
fields: [
|
||||
{ key: 'id', label: 'ID', type: 'text', required: true },
|
||||
{ key: 'shortName', label: 'Kurzname', type: 'text', required: true },
|
||||
{ key: 'name', label: 'Voller Name', type: 'text', required: true },
|
||||
{ key: 'state', label: 'Bundesland', type: 'text', required: true },
|
||||
],
|
||||
searchableFields: ['id', 'shortName', 'name', 'state'],
|
||||
},
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SYSTEM ENTRIES MAP
|
||||
// =============================================================================
|
||||
|
||||
const SYSTEM_ENTRIES_MAP: Record<CatalogId, Record<string, unknown>[]> = {
|
||||
'dsfa-risks': RISK_CATALOG as unknown as Record<string, unknown>[],
|
||||
'dsfa-mitigations': MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
|
||||
'ai-risks': AI_RISK_CATALOG as unknown as Record<string, unknown>[],
|
||||
'ai-mitigations': AI_MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
|
||||
'prohibited-ai-practices': PROHIBITED_AI_PRACTICES as unknown as Record<string, unknown>[],
|
||||
'vvt-templates': VVT_BASELINE_CATALOG as unknown as Record<string, unknown>[],
|
||||
'loeschfristen-templates': BASELINE_TEMPLATES as unknown as Record<string, unknown>[],
|
||||
'vendor-templates': VENDOR_TEMPLATES as unknown as Record<string, unknown>[],
|
||||
'country-risk-profiles': COUNTRY_RISK_PROFILES as unknown as Record<string, unknown>[],
|
||||
'legal-bases': LEGAL_BASIS_INFO as unknown as Record<string, unknown>[],
|
||||
'retention-periods': STANDARD_RETENTION_PERIODS as unknown as Record<string, unknown>[],
|
||||
'eu-legal-frameworks': EU_BASE_FRAMEWORKS as unknown as Record<string, unknown>[],
|
||||
'national-legal-frameworks': NATIONAL_FRAMEWORKS as unknown as Record<string, unknown>[],
|
||||
'gdpr-enforcement-cases': GDPR_ENFORCEMENT_CASES as unknown as Record<string, unknown>[],
|
||||
'wp248-criteria': WP248_CRITERIA as unknown as Record<string, unknown>[],
|
||||
'sdm-goals': SDM_GOALS_ENTRIES as unknown as Record<string, unknown>[],
|
||||
'dsfa-authority-resources': DSFA_AUTHORITY_RESOURCES as unknown as Record<string, unknown>[],
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENTRY CONVERTERS
|
||||
// =============================================================================
|
||||
|
||||
function systemEntryToCatalogEntry(
|
||||
catalogId: CatalogId,
|
||||
data: Record<string, unknown>,
|
||||
): CatalogEntry {
|
||||
const meta = CATALOG_REGISTRY[catalogId]
|
||||
const idValue = resolveField(data[meta.idField])
|
||||
const nameValue = resolveField(data[meta.nameField])
|
||||
const descValue = meta.descriptionField ? resolveField(data[meta.descriptionField]) : undefined
|
||||
const catValue = meta.categoryField ? resolveField(data[meta.categoryField]) : undefined
|
||||
|
||||
return {
|
||||
id: idValue || crypto.randomUUID(),
|
||||
catalogId,
|
||||
source: 'system',
|
||||
data,
|
||||
displayName: nameValue || idValue || '(Unbenannt)',
|
||||
displayDescription: descValue,
|
||||
category: catValue,
|
||||
}
|
||||
}
|
||||
|
||||
function customEntryToCatalogEntry(
|
||||
catalogId: CatalogId,
|
||||
entry: CustomCatalogEntry,
|
||||
): CatalogEntry {
|
||||
const meta = CATALOG_REGISTRY[catalogId]
|
||||
const nameValue = resolveField(entry.data[meta.nameField])
|
||||
const descValue = meta.descriptionField ? resolveField(entry.data[meta.descriptionField]) : undefined
|
||||
const catValue = meta.categoryField ? resolveField(entry.data[meta.categoryField]) : undefined
|
||||
|
||||
return {
|
||||
id: entry.id,
|
||||
catalogId,
|
||||
source: 'custom',
|
||||
data: entry.data,
|
||||
displayName: nameValue || '(Unbenannt)',
|
||||
displayDescription: descValue,
|
||||
category: catValue,
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PUBLIC API
|
||||
// =============================================================================
|
||||
|
||||
export function getSystemEntries(catalogId: CatalogId): CatalogEntry[] {
|
||||
const raw = SYSTEM_ENTRIES_MAP[catalogId] || []
|
||||
return raw.map(data => systemEntryToCatalogEntry(catalogId, data))
|
||||
}
|
||||
|
||||
export function getAllEntries(
|
||||
catalogId: CatalogId,
|
||||
customEntries: CustomCatalogEntry[] = [],
|
||||
): CatalogEntry[] {
|
||||
const system = getSystemEntries(catalogId)
|
||||
const custom = customEntries.map(e => customEntryToCatalogEntry(catalogId, e))
|
||||
return [...system, ...custom]
|
||||
}
|
||||
|
||||
export function getCatalogsByModule(module: CatalogModule): CatalogMeta[] {
|
||||
return Object.values(CATALOG_REGISTRY).filter(c => c.module === module)
|
||||
}
|
||||
|
||||
export function getCatalogStats(
|
||||
catalogId: CatalogId,
|
||||
customEntries: CustomCatalogEntry[] = [],
|
||||
): CatalogStats {
|
||||
const meta = CATALOG_REGISTRY[catalogId]
|
||||
return {
|
||||
catalogId,
|
||||
systemCount: meta.systemCount,
|
||||
customCount: customEntries.length,
|
||||
totalCount: meta.systemCount + customEntries.length,
|
||||
}
|
||||
}
|
||||
|
||||
export function getOverviewStats(customCatalogs: CustomCatalogs): CatalogOverviewStats {
|
||||
const modules: CatalogModule[] = ['dsfa', 'vvt', 'vendor', 'ai_act', 'reference']
|
||||
const byModule = {} as Record<CatalogModule, { catalogs: number; entries: number }>
|
||||
|
||||
let totalSystemEntries = 0
|
||||
let totalCustomEntries = 0
|
||||
|
||||
for (const mod of modules) {
|
||||
const cats = getCatalogsByModule(mod)
|
||||
let entries = 0
|
||||
for (const cat of cats) {
|
||||
const customCount = customCatalogs[cat.id]?.length ?? 0
|
||||
entries += cat.systemCount + customCount
|
||||
totalSystemEntries += cat.systemCount
|
||||
totalCustomEntries += customCount
|
||||
}
|
||||
byModule[mod] = { catalogs: cats.length, entries }
|
||||
}
|
||||
|
||||
return {
|
||||
totalCatalogs: Object.keys(CATALOG_REGISTRY).length,
|
||||
totalSystemEntries,
|
||||
totalCustomEntries,
|
||||
totalEntries: totalSystemEntries + totalCustomEntries,
|
||||
byModule,
|
||||
}
|
||||
}
|
||||
|
||||
export function searchCatalog(
|
||||
catalogId: CatalogId,
|
||||
query: string,
|
||||
customEntries: CustomCatalogEntry[] = [],
|
||||
): CatalogEntry[] {
|
||||
const allEntries = getAllEntries(catalogId, customEntries)
|
||||
const meta = CATALOG_REGISTRY[catalogId]
|
||||
const q = query.toLowerCase().trim()
|
||||
|
||||
if (!q) return allEntries
|
||||
|
||||
return allEntries
|
||||
.map(entry => {
|
||||
let score = 0
|
||||
for (const field of meta.searchableFields) {
|
||||
const value = resolveField(entry.data[field]).toLowerCase()
|
||||
if (value.includes(q)) {
|
||||
score += value.startsWith(q) ? 10 : 5
|
||||
}
|
||||
}
|
||||
// Also search display name
|
||||
if (entry.displayName.toLowerCase().includes(q)) {
|
||||
score += 15
|
||||
}
|
||||
return { entry, score }
|
||||
})
|
||||
.filter(r => r.score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.map(r => r.entry)
|
||||
}
|
||||
118
admin-v2/lib/sdk/catalog-manager/types.ts
Normal file
118
admin-v2/lib/sdk/catalog-manager/types.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* SDK Catalog Manager - Type Definitions
|
||||
*/
|
||||
|
||||
// All catalog IDs in the system
|
||||
export type CatalogId =
|
||||
| 'dsfa-risks'
|
||||
| 'dsfa-mitigations'
|
||||
| 'ai-risks'
|
||||
| 'ai-mitigations'
|
||||
| 'prohibited-ai-practices'
|
||||
| 'vvt-templates'
|
||||
| 'loeschfristen-templates'
|
||||
| 'vendor-templates'
|
||||
| 'country-risk-profiles'
|
||||
| 'legal-bases'
|
||||
| 'retention-periods'
|
||||
| 'eu-legal-frameworks'
|
||||
| 'national-legal-frameworks'
|
||||
| 'gdpr-enforcement-cases'
|
||||
| 'wp248-criteria'
|
||||
| 'sdm-goals'
|
||||
| 'dsfa-authority-resources'
|
||||
|
||||
// Module grouping
|
||||
export type CatalogModule = 'dsfa' | 'vvt' | 'vendor' | 'ai_act' | 'reference'
|
||||
|
||||
// Field types for dynamic forms
|
||||
export type CatalogFieldType = 'text' | 'textarea' | 'number' | 'select' | 'multiselect' | 'boolean' | 'tags'
|
||||
|
||||
export interface CatalogFieldSchema {
|
||||
key: string
|
||||
label: string
|
||||
type: CatalogFieldType
|
||||
required: boolean
|
||||
placeholder?: string
|
||||
description?: string
|
||||
options?: { value: string; label: string }[]
|
||||
helpText?: string
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
}
|
||||
|
||||
export interface CatalogMeta {
|
||||
id: CatalogId
|
||||
name: string
|
||||
description: string
|
||||
module: CatalogModule
|
||||
icon: string // lucide icon name
|
||||
systemCount: number
|
||||
allowCustom: boolean
|
||||
idField: string // which field is the unique ID (e.g. 'id', 'templateId', 'code')
|
||||
nameField: string // which field is the display name (e.g. 'title', 'name', 'dataObjectName')
|
||||
descriptionField?: string // which field holds description
|
||||
categoryField?: string // optional grouping field
|
||||
fields: CatalogFieldSchema[]
|
||||
searchableFields: string[]
|
||||
}
|
||||
|
||||
// A custom catalog entry added by the user
|
||||
export interface CustomCatalogEntry {
|
||||
id: string // Generated UUID
|
||||
catalogId: CatalogId
|
||||
data: Record<string, unknown>
|
||||
createdAt: string // ISO date
|
||||
updatedAt: string // ISO date
|
||||
createdBy?: string
|
||||
}
|
||||
|
||||
// All custom entries, keyed by CatalogId
|
||||
export type CustomCatalogs = Partial<Record<CatalogId, CustomCatalogEntry[]>>
|
||||
|
||||
// Combined view entry (system or custom)
|
||||
export interface CatalogEntry {
|
||||
id: string
|
||||
catalogId: CatalogId
|
||||
source: 'system' | 'custom'
|
||||
data: Record<string, unknown>
|
||||
displayName: string
|
||||
displayDescription?: string
|
||||
category?: string
|
||||
}
|
||||
|
||||
// Stats for a single catalog
|
||||
export interface CatalogStats {
|
||||
catalogId: CatalogId
|
||||
systemCount: number
|
||||
customCount: number
|
||||
totalCount: number
|
||||
}
|
||||
|
||||
// Stats for all catalogs
|
||||
export interface CatalogOverviewStats {
|
||||
totalCatalogs: number
|
||||
totalSystemEntries: number
|
||||
totalCustomEntries: number
|
||||
totalEntries: number
|
||||
byModule: Record<CatalogModule, { catalogs: number; entries: number }>
|
||||
}
|
||||
|
||||
// Module labels
|
||||
export const CATALOG_MODULE_LABELS: Record<CatalogModule, string> = {
|
||||
dsfa: 'DSFA & Risiken',
|
||||
vvt: 'VVT & Loeschfristen',
|
||||
vendor: 'Auftragsverarbeitung',
|
||||
ai_act: 'AI Act',
|
||||
reference: 'Referenzdaten',
|
||||
}
|
||||
|
||||
// Module icons (lucide names)
|
||||
export const CATALOG_MODULE_ICONS: Record<CatalogModule, string> = {
|
||||
dsfa: 'ShieldAlert',
|
||||
vvt: 'FileText',
|
||||
vendor: 'Building2',
|
||||
ai_act: 'Bot',
|
||||
reference: 'BookOpen',
|
||||
}
|
||||
Reference in New Issue
Block a user