/** * Incident CRUD, Risk Assessment, Notifications, Measures, Timeline, Statistics */ import { Incident, IncidentListResponse, IncidentFilters, IncidentCreateRequest, IncidentUpdateRequest, IncidentStatistics, IncidentMeasure, TimelineEntry, RiskAssessmentRequest, AuthorityNotification, DataSubjectNotification, IncidentSeverity, IncidentStatus, IncidentCategory, } from './types' import { INCIDENTS_API_BASE, fetchWithTimeout, getAuthHeaders } from './api-helpers' // ============================================================================= // INCIDENT LIST & CRUD // ============================================================================= /** * Alle Vorfaelle abrufen mit optionalen Filtern */ export async function fetchIncidents(filters?: IncidentFilters): Promise { const params = new URLSearchParams() if (filters) { if (filters.status) { const statuses = Array.isArray(filters.status) ? filters.status : [filters.status] statuses.forEach(s => params.append('status', s)) } if (filters.severity) { const severities = Array.isArray(filters.severity) ? filters.severity : [filters.severity] severities.forEach(s => params.append('severity', s)) } if (filters.category) { const categories = Array.isArray(filters.category) ? filters.category : [filters.category] categories.forEach(c => params.append('category', c)) } if (filters.assignedTo) params.set('assignedTo', filters.assignedTo) if (filters.overdue !== undefined) params.set('overdue', String(filters.overdue)) if (filters.search) params.set('search', filters.search) if (filters.dateFrom) params.set('dateFrom', filters.dateFrom) if (filters.dateTo) params.set('dateTo', filters.dateTo) } const queryString = params.toString() const url = `${INCIDENTS_API_BASE}/api/v1/incidents${queryString ? `?${queryString}` : ''}` return fetchWithTimeout(url) } /** * Einzelnen Vorfall per ID abrufen */ export async function fetchIncident(id: string): Promise { return fetchWithTimeout(`${INCIDENTS_API_BASE}/api/v1/incidents/${id}`) } /** * Neuen Vorfall erstellen */ export async function createIncident(request: IncidentCreateRequest): Promise { return fetchWithTimeout(`${INCIDENTS_API_BASE}/api/v1/incidents`, { method: 'POST', body: JSON.stringify(request) }) } /** * Vorfall aktualisieren */ export async function updateIncident(id: string, update: IncidentUpdateRequest): Promise { return fetchWithTimeout(`${INCIDENTS_API_BASE}/api/v1/incidents/${id}`, { method: 'PUT', body: JSON.stringify(update) }) } /** * Vorfall loeschen (Soft Delete) */ export async function deleteIncident(id: string): Promise { await fetchWithTimeout(`${INCIDENTS_API_BASE}/api/v1/incidents/${id}`, { method: 'DELETE' }) } // ============================================================================= // RISK ASSESSMENT // ============================================================================= /** * Risikobewertung fuer einen Vorfall durchfuehren (Art. 33 DSGVO) */ export async function submitRiskAssessment( incidentId: string, assessment: RiskAssessmentRequest ): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/risk-assessment`, { method: 'POST', body: JSON.stringify(assessment) } ) } // ============================================================================= // AUTHORITY NOTIFICATION (Art. 33 DSGVO) // ============================================================================= /** * Meldeformular fuer die Aufsichtsbehoerde generieren */ export async function generateAuthorityForm(incidentId: string): Promise { const response = await fetch( `${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/authority-form/pdf`, { headers: getAuthHeaders() } ) if (!response.ok) { throw new Error(`PDF-Generierung fehlgeschlagen: ${response.statusText}`) } return response.blob() } /** * Meldung an die Aufsichtsbehoerde einreichen (Art. 33 DSGVO) */ export async function submitAuthorityNotification( incidentId: string, data: Partial ): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/authority-notification`, { method: 'POST', body: JSON.stringify(data) } ) } // ============================================================================= // DATA SUBJECT NOTIFICATION (Art. 34 DSGVO) // ============================================================================= /** * Betroffene Personen benachrichtigen (Art. 34 DSGVO) */ export async function sendDataSubjectNotification( incidentId: string, data: Partial ): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/data-subject-notification`, { method: 'POST', body: JSON.stringify(data) } ) } // ============================================================================= // MEASURES (Massnahmen) // ============================================================================= /** * Massnahme hinzufuegen (Sofort-, Korrektur- oder Praeventionsmassnahme) */ export async function addMeasure( incidentId: string, measure: Omit ): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/measures`, { method: 'POST', body: JSON.stringify(measure) } ) } /** * Massnahme aktualisieren */ export async function updateMeasure( measureId: string, update: Partial ): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/measures/${measureId}`, { method: 'PUT', body: JSON.stringify(update) } ) } /** * Massnahme als abgeschlossen markieren */ export async function completeMeasure(measureId: string): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/measures/${measureId}/complete`, { method: 'POST' } ) } // ============================================================================= // TIMELINE // ============================================================================= /** * Zeitleisteneintrag hinzufuegen */ export async function addTimelineEntry( incidentId: string, entry: Omit ): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/timeline`, { method: 'POST', body: JSON.stringify(entry) } ) } // ============================================================================= // CLOSE INCIDENT // ============================================================================= /** * Vorfall abschliessen mit Lessons Learned */ export async function closeIncident( incidentId: string, lessonsLearned: string ): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/incidents/${incidentId}/close`, { method: 'POST', body: JSON.stringify({ lessonsLearned }) } ) } // ============================================================================= // STATISTICS // ============================================================================= /** * Vorfall-Statistiken abrufen */ export async function fetchIncidentStatistics(): Promise { return fetchWithTimeout( `${INCIDENTS_API_BASE}/api/v1/incidents/statistics` ) } // ============================================================================= // SDK PROXY FUNCTION (mit Fallback auf Mock-Daten) // ============================================================================= /** * Fetch Incident-Liste via SDK-Proxy mit Fallback auf Mock-Daten */ export async function fetchSDKIncidentList(): Promise<{ incidents: Incident[]; statistics: IncidentStatistics }> { try { const res = await fetch('/api/sdk/v1/incidents', { headers: getAuthHeaders() }) if (!res.ok) { throw new Error(`HTTP ${res.status}`) } const data = await res.json() const incidents: Incident[] = data.incidents || [] const statistics = computeStatistics(incidents) return { incidents, statistics } } catch (error) { console.warn('SDK-Backend nicht erreichbar, verwende Mock-Daten:', error) // Import mock data lazily to keep this file lean const { createMockIncidents, createMockStatistics } = await import('./api-mock') const incidents = createMockIncidents() const statistics = createMockStatistics() return { incidents, statistics } } } /** * Statistiken lokal aus Incident-Liste berechnen */ function computeStatistics(incidents: Incident[]): IncidentStatistics { const countBy = (items: { [key: string]: unknown }[], field: string): Record => { const result: Record = {} items.forEach(item => { const key = String(item[field]) result[key] = (result[key] || 0) + 1 }) return result as Record } const statusCounts = countBy(incidents as unknown as { [key: string]: unknown }[], 'status') const severityCounts = countBy(incidents as unknown as { [key: string]: unknown }[], 'severity') const categoryCounts = countBy(incidents as unknown as { [key: string]: unknown }[], 'category') const openIncidents = incidents.filter(i => i.status !== 'closed').length const notificationsPending = incidents.filter(i => i.authorityNotification !== null && i.authorityNotification.status === 'pending' && i.status !== 'closed' ).length let totalResponseHours = 0 let respondedCount = 0 incidents.forEach(i => { if (i.riskAssessment && i.riskAssessment.assessedAt) { const detected = new Date(i.detectedAt).getTime() const assessed = new Date(i.riskAssessment.assessedAt).getTime() totalResponseHours += (assessed - detected) / (1000 * 60 * 60) respondedCount++ } }) return { totalIncidents: incidents.length, openIncidents, notificationsPending, averageResponseTimeHours: respondedCount > 0 ? Math.round(totalResponseHours / respondedCount * 10) / 10 : 0, bySeverity: { low: severityCounts['low'] || 0, medium: severityCounts['medium'] || 0, high: severityCounts['high'] || 0, critical: severityCounts['critical'] || 0 }, byCategory: { data_breach: categoryCounts['data_breach'] || 0, unauthorized_access: categoryCounts['unauthorized_access'] || 0, data_loss: categoryCounts['data_loss'] || 0, system_compromise: categoryCounts['system_compromise'] || 0, phishing: categoryCounts['phishing'] || 0, ransomware: categoryCounts['ransomware'] || 0, insider_threat: categoryCounts['insider_threat'] || 0, physical_breach: categoryCounts['physical_breach'] || 0, other: categoryCounts['other'] || 0 }, byStatus: { detected: statusCounts['detected'] || 0, assessment: statusCounts['assessment'] || 0, containment: statusCounts['containment'] || 0, notification_required: statusCounts['notification_required'] || 0, notification_sent: statusCounts['notification_sent'] || 0, remediation: statusCounts['remediation'] || 0, closed: statusCounts['closed'] || 0 } } }