feat(sdk): Kunden-Dokumente + CRA-Meldewesen, Screening aus Frontend genommen
CI / detect-changes (push) Successful in 16s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 15s
CI / validate-canonical-controls (push) Successful in 13s
CI / loc-budget (push) Successful in 25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m9s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 31s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 16s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 15s
CI / validate-canonical-controls (push) Successful in 13s
CI / loc-budget (push) Successful in 25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m9s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 31s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
- /sdk/dokumente: Kundensicht nur auf veroeffentlichte Rechtsdokumente (Ansehen + Download); Proxy mit Allow-List nur /public — Templates/Drafts/ Generator bleiben unerreichbar. - /sdk/cra-meldewesen: CRA Art. 14 Meldewesen (24h/72h/14d-Kaskade) mit Fristen-Tracking + ENISA-SRP-Export-Entwurf (kein Live-API). Backend: cra_meldewesen (pure, getestet) + cra_incident_store (schema-neutral ueber compliance_cra_documents) + /api/v1/cra/incidents (additiv, contract-safe). - Screening (Self-Scan) aus dem Frontend genommen: Flow-Stepper-Eintrag ausgeblendet (visibleWhen), Dashboard-Kachel + Import-Button entfernt. Repo-Scanning laeuft extern im Compliance-Scanner; Backend-Router bleibt vorerst gemountet (Contract-Stabilitaet). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
// CRA Art. 14 Meldewesen: incident reporting cascade (24h/72h/14d) to ENISA SRP.
|
||||
// Incidents are scoped to a CRA project; results of the cascade are exported as a
|
||||
// structured draft (no live ENISA API yet).
|
||||
|
||||
export interface Deadline {
|
||||
key: string
|
||||
label: string
|
||||
article: string
|
||||
due_at: string | null
|
||||
submitted_at: string | null
|
||||
status: 'submitted' | 'overdue' | 'due_soon' | 'pending'
|
||||
remaining_seconds: number | null
|
||||
}
|
||||
export interface Incident {
|
||||
id: string
|
||||
status: string
|
||||
summary: string
|
||||
product_name?: string
|
||||
product_version?: string
|
||||
manufacturer?: string
|
||||
kind?: string
|
||||
severity?: string
|
||||
aware_at?: string
|
||||
contact?: string
|
||||
impact?: string
|
||||
root_cause?: string
|
||||
corrective_measures?: string
|
||||
patch_available?: boolean
|
||||
personal_data_affected?: boolean
|
||||
deadlines: Deadline[]
|
||||
next_stage: string | null
|
||||
submissions?: Record<string, { submitted_at: string; report: Record<string, unknown> }>
|
||||
}
|
||||
export interface Meta {
|
||||
stages: { key: string; label: string; article: string; hours: number }[]
|
||||
severities: string[]
|
||||
kinds: string[]
|
||||
reporting_active_from: string
|
||||
}
|
||||
interface Project { id: string; name: string }
|
||||
|
||||
const j = (r: Response) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
|
||||
|
||||
export function useMeldewesen() {
|
||||
const [meta, setMeta] = useState<Meta | null>(null)
|
||||
const [projects, setProjects] = useState<Project[]>([])
|
||||
const [projectId, setProjectId] = useState('')
|
||||
const [incidents, setIncidents] = useState<Incident[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/sdk/v1/cra/incidents/meta').then(j).then(setMeta).catch(() => {})
|
||||
fetch('/api/sdk/v1/cra/projects')
|
||||
.then(j)
|
||||
.then((d) => {
|
||||
const list: Project[] = (d.projects || d || []).map((p: any) => ({ id: p.id, name: p.name || p.machine_name || p.id }))
|
||||
setProjects(list)
|
||||
if (list.length && !projectId) setProjectId(list[0].id)
|
||||
})
|
||||
.catch(() => {})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const loadIncidents = useCallback((pid: string) => {
|
||||
if (!pid) { setIncidents([]); return }
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
fetch(`/api/sdk/v1/cra/incidents?cra_project_id=${encodeURIComponent(pid)}`)
|
||||
.then(j)
|
||||
.then((d) => setIncidents(d.incidents || []))
|
||||
.catch((e) => setError(String(e?.message || e)))
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
useEffect(() => { loadIncidents(projectId) }, [projectId, loadIncidents])
|
||||
|
||||
const createIncident = useCallback(async (body: Record<string, unknown>) => {
|
||||
const r = await fetch('/api/sdk/v1/cra/incidents', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...body, cra_project_id: projectId }),
|
||||
})
|
||||
if (r.ok) loadIncidents(projectId)
|
||||
return r.ok
|
||||
}, [projectId, loadIncidents])
|
||||
|
||||
const submitStage = useCallback(async (incidentId: string, stage: string) => {
|
||||
const r = await fetch(`/api/sdk/v1/cra/incidents/${incidentId}/submit/${stage}`, { method: 'POST' })
|
||||
if (r.ok) loadIncidents(projectId)
|
||||
return r.ok
|
||||
}, [projectId, loadIncidents])
|
||||
|
||||
const patchIncident = useCallback(async (incidentId: string, patch: Record<string, unknown>) => {
|
||||
const r = await fetch(`/api/sdk/v1/cra/incidents/${incidentId}`, {
|
||||
method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(patch),
|
||||
})
|
||||
if (r.ok) loadIncidents(projectId)
|
||||
return r.ok
|
||||
}, [projectId, loadIncidents])
|
||||
|
||||
return {
|
||||
meta, projects, projectId, setProjectId, incidents, loading, error,
|
||||
createIncident, submitStage, patchIncident, reload: () => loadIncidents(projectId),
|
||||
}
|
||||
}
|
||||
|
||||
// Download the ENISA export draft for a stage as JSON.
|
||||
export async function downloadStageExport(incidentId: string, stage: string, summary: string): Promise<void> {
|
||||
const r = await fetch(`/api/sdk/v1/cra/incidents/${incidentId}/export/${stage}`)
|
||||
if (!r.ok) return
|
||||
const data = await r.json()
|
||||
const safe = (summary || 'meldung').replace(/[^\w\-äöüÄÖÜß ]/g, '').trim().replace(/\s+/g, '_').slice(0, 40)
|
||||
const blob = new Blob([JSON.stringify(data.report, null, 2)], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `ENISA_${stage}_${safe || 'meldung'}.json`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
a.remove()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
Reference in New Issue
Block a user