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>
This commit is contained in:
@@ -1,399 +0,0 @@
|
||||
/**
|
||||
* DSFA API Client
|
||||
*
|
||||
* API client functions for DSFA (Data Protection Impact Assessment) endpoints.
|
||||
*/
|
||||
|
||||
import type {
|
||||
DSFA,
|
||||
DSFAListResponse,
|
||||
DSFAStatsResponse,
|
||||
CreateDSFARequest,
|
||||
CreateDSFAFromAssessmentRequest,
|
||||
CreateDSFAFromAssessmentResponse,
|
||||
UpdateDSFASectionRequest,
|
||||
SubmitForReviewResponse,
|
||||
ApproveDSFARequest,
|
||||
DSFATriggerInfo,
|
||||
} from './types'
|
||||
|
||||
// =============================================================================
|
||||
// CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
const getBaseUrl = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// Browser environment
|
||||
return process.env.NEXT_PUBLIC_SDK_API_URL || '/api/sdk/v1'
|
||||
}
|
||||
// Server environment
|
||||
return process.env.SDK_API_URL || 'http://localhost:8080/api/sdk/v1'
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
async function handleResponse<T>(response: Response): Promise<T> {
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text()
|
||||
let errorMessage = `HTTP ${response.status}`
|
||||
try {
|
||||
const errorJson = JSON.parse(errorBody)
|
||||
errorMessage = errorJson.error || errorJson.message || errorMessage
|
||||
} catch {
|
||||
// Keep HTTP status message
|
||||
}
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
function getHeaders(): HeadersInit {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DSFA CRUD OPERATIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* List all DSFAs for the current tenant
|
||||
*/
|
||||
export async function listDSFAs(status?: string): Promise<DSFA[]> {
|
||||
const url = new URL(`${getBaseUrl()}/dsgvo/dsfas`, window.location.origin)
|
||||
if (status) {
|
||||
url.searchParams.set('status', status)
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
const data = await handleResponse<DSFAListResponse>(response)
|
||||
return data.dsfas || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single DSFA by ID
|
||||
*/
|
||||
export async function getDSFA(id: string): Promise<DSFA> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}`, {
|
||||
method: 'GET',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
return handleResponse<DSFA>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DSFA
|
||||
*/
|
||||
export async function createDSFA(data: CreateDSFARequest): Promise<DSFA> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
return handleResponse<DSFA>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing DSFA
|
||||
*/
|
||||
export async function updateDSFA(id: string, data: Partial<DSFA>): Promise<DSFA> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
return handleResponse<DSFA>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a DSFA
|
||||
*/
|
||||
export async function deleteDSFA(id: string): Promise<void> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete DSFA: ${response.statusText}`)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DSFA SECTION OPERATIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Update a specific section of a DSFA
|
||||
*/
|
||||
export async function updateDSFASection(
|
||||
id: string,
|
||||
sectionNumber: number,
|
||||
data: UpdateDSFASectionRequest
|
||||
): Promise<DSFA> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/sections/${sectionNumber}`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
return handleResponse<DSFA>(response)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DSFA WORKFLOW OPERATIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Submit a DSFA for DPO review
|
||||
*/
|
||||
export async function submitDSFAForReview(id: string): Promise<SubmitForReviewResponse> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/submit-for-review`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
return handleResponse<SubmitForReviewResponse>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve or reject a DSFA (DPO/CISO/GF action)
|
||||
*/
|
||||
export async function approveDSFA(id: string, data: ApproveDSFARequest): Promise<{ message: string }> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/approve`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
return handleResponse<{ message: string }>(response)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DSFA STATISTICS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Get DSFA statistics for the dashboard
|
||||
*/
|
||||
export async function getDSFAStats(): Promise<DSFAStatsResponse> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/stats`, {
|
||||
method: 'GET',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
return handleResponse<DSFAStatsResponse>(response)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// UCCA INTEGRATION
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Create a DSFA from a UCCA assessment (pre-filled)
|
||||
*/
|
||||
export async function createDSFAFromAssessment(
|
||||
assessmentId: string,
|
||||
data?: CreateDSFAFromAssessmentRequest
|
||||
): Promise<CreateDSFAFromAssessmentResponse> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/from-assessment/${assessmentId}`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data || {}),
|
||||
})
|
||||
|
||||
return handleResponse<CreateDSFAFromAssessmentResponse>(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a DSFA by its linked UCCA assessment ID
|
||||
*/
|
||||
export async function getDSFAByAssessment(assessmentId: string): Promise<DSFA | null> {
|
||||
try {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/by-assessment/${assessmentId}`, {
|
||||
method: 'GET',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (response.status === 404) {
|
||||
return null
|
||||
}
|
||||
|
||||
return handleResponse<DSFA>(response)
|
||||
} catch (error) {
|
||||
// Return null if DSFA not found
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a DSFA is required for a UCCA assessment
|
||||
*/
|
||||
export async function checkDSFARequired(assessmentId: string): Promise<DSFATriggerInfo> {
|
||||
const response = await fetch(`${getBaseUrl()}/ucca/assessments/${assessmentId}/dsfa-required`, {
|
||||
method: 'GET',
|
||||
headers: getHeaders(),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
return handleResponse<DSFATriggerInfo>(response)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EXPORT
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Export a DSFA as JSON
|
||||
*/
|
||||
export async function exportDSFAAsJSON(id: string): Promise<Blob> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/export?format=json`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Export failed: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.blob()
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a DSFA as PDF
|
||||
*/
|
||||
export async function exportDSFAAsPDF(id: string): Promise<Blob> {
|
||||
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/export/pdf`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/pdf',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`PDF export failed: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.blob()
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RISK & MITIGATION OPERATIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Add a risk to a DSFA
|
||||
*/
|
||||
export async function addDSFARisk(dsfaId: string, risk: {
|
||||
category: string
|
||||
description: string
|
||||
likelihood: 'low' | 'medium' | 'high'
|
||||
impact: 'low' | 'medium' | 'high'
|
||||
affected_data?: string[]
|
||||
}): Promise<DSFA> {
|
||||
const dsfa = await getDSFA(dsfaId)
|
||||
const newRisk = {
|
||||
id: crypto.randomUUID(),
|
||||
...risk,
|
||||
risk_level: calculateRiskLevelString(risk.likelihood, risk.impact),
|
||||
affected_data: risk.affected_data || [],
|
||||
}
|
||||
|
||||
const updatedRisks = [...(dsfa.risks || []), newRisk]
|
||||
return updateDSFA(dsfaId, { risks: updatedRisks } as Partial<DSFA>)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a risk from a DSFA
|
||||
*/
|
||||
export async function removeDSFARisk(dsfaId: string, riskId: string): Promise<DSFA> {
|
||||
const dsfa = await getDSFA(dsfaId)
|
||||
const updatedRisks = (dsfa.risks || []).filter(r => r.id !== riskId)
|
||||
return updateDSFA(dsfaId, { risks: updatedRisks } as Partial<DSFA>)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mitigation to a DSFA
|
||||
*/
|
||||
export async function addDSFAMitigation(dsfaId: string, mitigation: {
|
||||
risk_id: string
|
||||
description: string
|
||||
type: 'technical' | 'organizational' | 'legal'
|
||||
responsible_party: string
|
||||
}): Promise<DSFA> {
|
||||
const dsfa = await getDSFA(dsfaId)
|
||||
const newMitigation = {
|
||||
id: crypto.randomUUID(),
|
||||
...mitigation,
|
||||
status: 'planned' as const,
|
||||
residual_risk: 'medium' as const,
|
||||
}
|
||||
|
||||
const updatedMitigations = [...(dsfa.mitigations || []), newMitigation]
|
||||
return updateDSFA(dsfaId, { mitigations: updatedMitigations } as Partial<DSFA>)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update mitigation status
|
||||
*/
|
||||
export async function updateDSFAMitigationStatus(
|
||||
dsfaId: string,
|
||||
mitigationId: string,
|
||||
status: 'planned' | 'in_progress' | 'implemented' | 'verified'
|
||||
): Promise<DSFA> {
|
||||
const dsfa = await getDSFA(dsfaId)
|
||||
const updatedMitigations = (dsfa.mitigations || []).map(m => {
|
||||
if (m.id === mitigationId) {
|
||||
return {
|
||||
...m,
|
||||
status,
|
||||
...(status === 'implemented' && { implemented_at: new Date().toISOString() }),
|
||||
...(status === 'verified' && { verified_at: new Date().toISOString() }),
|
||||
}
|
||||
}
|
||||
return m
|
||||
})
|
||||
|
||||
return updateDSFA(dsfaId, { mitigations: updatedMitigations } as Partial<DSFA>)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function calculateRiskLevelString(
|
||||
likelihood: 'low' | 'medium' | 'high',
|
||||
impact: 'low' | 'medium' | 'high'
|
||||
): string {
|
||||
const matrix: Record<string, Record<string, string>> = {
|
||||
low: { low: 'low', medium: 'low', high: 'medium' },
|
||||
medium: { low: 'low', medium: 'medium', high: 'high' },
|
||||
high: { low: 'medium', medium: 'high', high: 'very_high' },
|
||||
}
|
||||
return matrix[likelihood]?.[impact] || 'medium'
|
||||
}
|
||||
Reference in New Issue
Block a user