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>
666 lines
26 KiB
TypeScript
666 lines
26 KiB
TypeScript
/**
|
|
* 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)
|
|
}
|