'use client'
import React, { useState, useEffect } from 'react'
import { useSDK } from '@/lib/sdk'
// =============================================================================
// TYPES
// =============================================================================
interface SecurityItem {
id: string
title: string
description: string | null
type: 'vulnerability' | 'misconfiguration' | 'compliance' | 'hardening'
severity: 'critical' | 'high' | 'medium' | 'low'
status: 'open' | 'in-progress' | 'resolved' | 'accepted-risk'
source: string | null
cve: string | null
cvss: number | null
affected_asset: string | null
assigned_to: string | null
created_at: string
due_date: string | null
remediation: string | null
}
interface Stats {
open: number
in_progress: number
critical: number
high: number
overdue: number
total: number
}
interface NewItem {
title: string
description: string
type: string
severity: string
source: string
cve: string
cvss: string
affected_asset: string
assigned_to: string
remediation: string
}
const EMPTY_NEW_ITEM: NewItem = {
title: '',
description: '',
type: 'vulnerability',
severity: 'medium',
source: '',
cve: '',
cvss: '',
affected_asset: '',
assigned_to: '',
remediation: '',
}
// =============================================================================
// COMPONENTS
// =============================================================================
function SecurityItemCard({
item,
onEdit,
onDelete,
onStatusChange,
}: {
item: SecurityItem
onEdit: (item: SecurityItem) => void
onDelete: (id: string) => void
onStatusChange: (id: string, status: string) => void
}) {
const typeLabels = {
vulnerability: 'Schwachstelle',
misconfiguration: 'Fehlkonfiguration',
compliance: 'Compliance',
hardening: 'Haertung',
}
const typeColors = {
vulnerability: 'bg-red-100 text-red-700',
misconfiguration: 'bg-orange-100 text-orange-700',
compliance: 'bg-purple-100 text-purple-700',
hardening: 'bg-blue-100 text-blue-700',
}
const severityColors = {
critical: 'bg-red-500 text-white',
high: 'bg-orange-500 text-white',
medium: 'bg-yellow-500 text-white',
low: 'bg-green-500 text-white',
}
const statusColors = {
open: 'bg-blue-100 text-blue-700',
'in-progress': 'bg-yellow-100 text-yellow-700',
resolved: 'bg-green-100 text-green-700',
'accepted-risk': 'bg-gray-100 text-gray-600',
}
const statusLabels = {
open: 'Offen',
'in-progress': 'In Bearbeitung',
resolved: 'Behoben',
'accepted-risk': 'Akzeptiert',
}
const isOverdue = item.due_date && new Date(item.due_date) < new Date() && item.status !== 'resolved'
return (
{item.severity.toUpperCase()}
{typeLabels[item.type]}
{statusLabels[item.status]}
{item.title}
{item.description &&
{item.description}
}
{item.affected_asset && (
Betroffenes Asset:
{item.affected_asset}
)}
{item.source && (
Quelle:
{item.source}
)}
{item.cve && (
CVE:
{item.cve}
)}
{item.cvss !== null && (
CVSS:
= 9 ? 'text-red-600' :
item.cvss >= 7 ? 'text-orange-600' :
item.cvss >= 4 ? 'text-yellow-600' : 'text-green-600'
}`}>{item.cvss}
)}
{item.assigned_to && (
Zugewiesen:
{item.assigned_to}
)}
{item.due_date && (
Frist:
{new Date(item.due_date).toLocaleDateString('de-DE')}
{isOverdue && ' (ueberfaellig)'}
)}
{item.remediation && (
Empfohlene Massnahme:
{item.remediation}
)}
Erstellt: {new Date(item.created_at).toLocaleDateString('de-DE')}
{item.status !== 'resolved' && (
<>
>
)}
)
}
// =============================================================================
// MODAL
// =============================================================================
function ItemModal({
item,
onClose,
onSave,
}: {
item: NewItem
onClose: () => void
onSave: (data: NewItem) => void
}) {
const [form, setForm] = useState(item)
return (
Sicherheitsbefund erfassen
setForm(p => ({ ...p, title: e.target.value }))}
placeholder="Kurzbeschreibung des Befunds"
className="w-full border rounded px-3 py-2 text-sm"
/>
)
}
// =============================================================================
// MAIN PAGE
// =============================================================================
const API = '/api/sdk/v1/compliance/security-backlog'
export default function SecurityBacklogPage() {
const { state } = useSDK()
const [items, setItems] = useState([])
const [stats, setStats] = useState({ open: 0, in_progress: 0, critical: 0, high: 0, overdue: 0, total: 0 })
const [filter, setFilter] = useState('all')
const [loading, setLoading] = useState(true)
const [showModal, setShowModal] = useState(false)
const [editItem, setEditItem] = useState(null)
useEffect(() => {
loadData()
}, [])
async function loadData() {
setLoading(true)
try {
const [itemsRes, statsRes] = await Promise.all([
fetch(`${API}?limit=200`),
fetch(`${API}/stats`),
])
if (itemsRes.ok) {
const data = await itemsRes.json()
setItems(Array.isArray(data.items) ? data.items : [])
}
if (statsRes.ok) {
const data = await statsRes.json()
setStats(data)
}
} catch (err) {
console.error('Failed to load security backlog:', err)
} finally {
setLoading(false)
}
}
async function handleCreate(form: NewItem) {
try {
const res = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...form,
cvss: form.cvss ? parseFloat(form.cvss) : null,
}),
})
if (res.ok) {
const created = await res.json()
setItems(prev => [created, ...prev])
setStats(prev => ({ ...prev, open: prev.open + 1, total: prev.total + 1 }))
setShowModal(false)
}
} catch (err) {
console.error('Failed to create item:', err)
}
}
async function handleUpdate(form: NewItem) {
if (!editItem) return
try {
const res = await fetch(`${API}/${editItem.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...form,
cvss: form.cvss ? parseFloat(form.cvss) : null,
}),
})
if (res.ok) {
const updated = await res.json()
setItems(prev => prev.map(i => i.id === updated.id ? updated : i))
setEditItem(null)
}
} catch (err) {
console.error('Failed to update item:', err)
}
}
async function handleDelete(id: string) {
try {
const res = await fetch(`${API}/${id}`, { method: 'DELETE' })
if (res.ok || res.status === 204) {
setItems(prev => prev.filter(i => i.id !== id))
loadData() // refresh stats
}
} catch (err) {
console.error('Failed to delete item:', err)
}
}
async function handleStatusChange(id: string, status: string) {
try {
const res = await fetch(`${API}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status }),
})
if (res.ok) {
const updated = await res.json()
setItems(prev => prev.map(i => i.id === updated.id ? updated : i))
loadData() // refresh stats
}
} catch (err) {
console.error('Failed to update status:', err)
}
}
const filteredItems = filter === 'all'
? items
: items.filter(i => i.severity === filter || i.status === filter || i.type === filter)
return (
{/* Header */}
Security Backlog
Verwalten Sie Sicherheitsbefunde und verfolgen Sie deren Behebung
{/* Stats */}
Kritisch
{stats.critical}
Ueberfaellig
{stats.overdue}
{/* Critical Alert */}
{stats.critical > 0 && (
{stats.critical} kritische Schwachstelle(n) erfordern sofortige Aufmerksamkeit
Diese Befunde haben ein CVSS von 9.0 oder hoeher und sollten priorisiert werden.
)}
{/* Filter */}
Filter:
{['all', 'open', 'in-progress', 'critical', 'high', 'vulnerability', 'misconfiguration'].map(f => (
))}
{/* Items List */}
{loading ? (
Lade Sicherheitsbefunde...
) : (
{filteredItems
.sort((a, b) => {
const sOrder = { critical: 0, high: 1, medium: 2, low: 3 }
const stOrder = { open: 0, 'in-progress': 1, 'accepted-risk': 2, resolved: 3 }
const sd = sOrder[a.severity] - sOrder[b.severity]
if (sd !== 0) return sd
return stOrder[a.status] - stOrder[b.status]
})
.map(item => (
{ setEditItem(i); setShowModal(true) }}
onDelete={handleDelete}
onStatusChange={handleStatusChange}
/>
))}
{filteredItems.length === 0 && (
Keine Befunde gefunden
Passen Sie den Filter an oder erfassen Sie einen neuen Befund.
)}
)}
{/* Create/Edit Modal */}
{showModal && (
{ setShowModal(false); setEditItem(null) }}
onSave={editItem ? handleUpdate : handleCreate}
/>
)}
)
}