'use client' /** * AuditReportTab — rendert den deterministischen Audit-Textreport eines * Snapshots (Sektionen aus /report, kein Re-Crawl) + Download als PDF/Markdown. * Bewusst ohne Markdown-Lib + ohne dangerouslySetInnerHTML (Befundtexte können * Site-Inhalte enthalten → XSS-sicher über React-Textknoten). */ import React, { useEffect, useState } from 'react' type Section = { title: string; level: number; body?: string } type Report = { meta?: Record; sections?: Section[]; totals?: Record } function Inline({ text }: { text: string }) { // **fett** sicher rendern; _kursiv_-Marker entfernen. const parts = text.split(/\*\*(.+?)\*\*/g) return <>{parts.map((p, i) => (i % 2 ? {p} : {p.replace(/_/g, '')}))} } function Body({ body }: { body: string }) { const out: React.ReactNode[] = [] let bullets: string[] = [] const flush = (k: string) => { if (bullets.length) { const items = bullets out.push() bullets = [] } } body.split('\n').map(l => l.trim()).filter(Boolean).forEach((l, i) => { if (l.startsWith('- ')) { bullets.push(l.slice(2)) } else { flush('p' + i); out.push(

) } }) flush('end') return
{out}
} export function AuditReportTab({ snapshotId }: { snapshotId: string }) { const [rep, setRep] = useState(null) const [md, setMd] = useState('') const [loading, setLoading] = useState(true) const [err, setErr] = useState(null) useEffect(() => { let cancelled = false fetch(`/api/sdk/v1/agent/snapshots/${snapshotId}/report`) .then(r => r.json()) .then(d => { if (cancelled) return if (d?.error) setErr(d.error) else { setRep(d.report); setMd(d.markdown || '') } }) .catch(e => { if (!cancelled) setErr(String(e)) }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } }, [snapshotId]) const downloadMd = () => { const blob = new Blob([md], { type: 'text/markdown' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url; a.download = 'audit-report.md'; a.click() URL.revokeObjectURL(url) } if (loading) return
Bericht wird erstellt…
if (err || !rep) return
{err || 'Kein Bericht verfügbar.'}
return (
PDF herunterladen
{(rep.sections || []).map((s, i) => s.level <= 2 ? (

{s.title}

{s.body && }
) : (

{s.title}

{s.body && }
))}
) }