diff --git a/admin-compliance/app/api/sdk/v1/agent/authenticated-scan/route.ts b/admin-compliance/app/api/sdk/v1/agent/authenticated-scan/route.ts new file mode 100644 index 0000000..6094940 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/agent/authenticated-scan/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from 'next/server' +const CONSENT_URL = process.env.CONSENT_TESTER_URL || 'http://bp-compliance-consent-tester:8094' + +export async function POST(request: NextRequest) { + try { + const body = await request.text() + const response = await fetch(`${CONSENT_URL}/authenticated-scan`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + signal: AbortSignal.timeout(120000), + }) + if (!response.ok) { + return NextResponse.json({ error: `Auth-Test: ${response.status}` }, { status: response.status }) + } + return NextResponse.json(await response.json()) + } catch (error) { + return NextResponse.json({ error: 'Auth-Test fehlgeschlagen' }, { status: 503 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/agent/compare/route.ts b/admin-compliance/app/api/sdk/v1/agent/compare/route.ts new file mode 100644 index 0000000..7db5f22 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/agent/compare/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from 'next/server' +const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002' + +export async function POST(request: NextRequest) { + try { + const body = await request.text() + const response = await fetch(`${BACKEND_URL}/api/compliance/agent/compare`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + signal: AbortSignal.timeout(300000), + }) + if (!response.ok) { + return NextResponse.json({ error: `Backend: ${response.status}` }, { status: response.status }) + } + return NextResponse.json(await response.json()) + } catch (error) { + return NextResponse.json({ error: 'Vergleich fehlgeschlagen' }, { status: 503 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts b/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts new file mode 100644 index 0000000..92b7ed7 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts @@ -0,0 +1,142 @@ +/** + * Consent Test API Proxy + * POST /api/sdk/v1/agent/consent-test → consent-tester:8094/scan → email via backend + */ + +import { NextRequest, NextResponse } from 'next/server' + +const CONSENT_TESTER_URL = process.env.CONSENT_TESTER_URL || 'http://bp-compliance-consent-tester:8094' +const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002' + +interface Violation { service: string; severity: string; text: string; legal_ref: string } + +function buildEmailHtml(data: any): string { + const url = data.url || '' + const banner = data.banner_detected ? data.banner_provider : 'Nicht erkannt' + const phases = data.phases || {} + const summary = data.summary || {} + + const sev = (s: string) => s === 'CRITICAL' + ? 'KRITISCH' + : 'HOCH' + + const violationRows = (violations: Violation[]) => violations.length === 0 + ? '✓ Keine Verstoesse' + : violations.map(v => + `${sev(v.severity)}${v.service}${v.text}
${v.legal_ref}` + ).join('') + + const undocRows = (items: string[]) => items.length === 0 + ? '' + : items.map(s => `⚠${s}Nicht in Cookie-Policy dokumentiert`).join('') + + return ` +
+
+

Cookie-Consent-Test

+

${url}

+
+ +
+ + + + + +
Cookie-Banner${data.banner_detected ? '✓ ' + banner : '✗ Nicht erkannt'}
Kritische Verstoesse${summary.critical || 0}
Hohe Verstoesse${summary.high || 0}
Undokumentiert${summary.undocumented || 0}
+ +

+ 🔍 Phase A: Vor Einwilligung +

+

Was laedt OHNE dass der Nutzer etwas geklickt hat?

+ ${violationRows(phases.before_consent?.violations || [])}
+ + ${data.banner_detected ? ` +

+ 🚫 Phase B: Nach Ablehnung +

+

Was laedt NACHDEM der Nutzer "Nur notwendige" geklickt hat?

+ ${violationRows(phases.after_reject?.violations || [])}
+ +

+ ✅ Phase C: Nach Zustimmung +

+

Was laedt NACHDEM der Nutzer "Alle akzeptieren" geklickt hat?

+ ${undocRows(phases.after_accept?.undocumented || [])}
+ ${(phases.after_accept?.undocumented?.length || 0) === 0 ? '

✓ Alle Dienste dokumentiert

' : ''} + ` : ` +
+ Kein Cookie-Banner erkannt. + Alle Tracking-Dienste laden ohne Einwilligung — Verstoss gegen §25 TDDDG. +
+ `} + + ${(summary.critical || 0) > 0 ? ` +
+ ⚠ KRITISCH: Tracking-Dienste laden trotz Ablehnung. + Dies ist ein schwerer Verstoss gegen §25 TDDDG und kann als Dark Pattern gewertet werden. + Sofortige Korrektur der Cookie-Banner-Konfiguration empfohlen. +
+ ` : ''} +
+ +
+

+ Automatisch erstellt vom BreakPilot Compliance Agent (Playwright + Chromium) +

+
+
+ ` +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const url = body.url + + // Step 1: Run consent test + const response = await fetch(`${CONSENT_TESTER_URL}/scan`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(180000), + }) + + if (!response.ok) { + const errorText = await response.text() + return NextResponse.json( + { error: `Consent-Tester: ${response.status}`, detail: errorText }, + { status: response.status } + ) + } + + const data = await response.json() + + // Step 2: Send email with phase-structured findings + try { + const total = (data.summary?.total_violations || 0) + const severity = (data.summary?.critical || 0) > 0 ? 'KRITISCH' : total > 0 ? 'FINDINGS' : 'OK' + await fetch(`${BACKEND_URL}/api/compliance/agent/notify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + recipient: body.recipient || 'dsb@breakpilot.local', + subject: `[COOKIE-TEST] [${severity}] ${url} — ${total} Verstoesse`, + body_html: buildEmailHtml({ ...data, url }), + role: total > 0 ? 'Datenschutzbeauftragter' : 'Kein Handlungsbedarf', + }), + signal: AbortSignal.timeout(10000), + }) + } catch (emailErr) { + console.warn('Email send failed (non-blocking):', emailErr) + } + + return NextResponse.json(data) + } catch (error) { + console.error('Consent test proxy error:', error) + return NextResponse.json( + { error: 'Cookie-Test fehlgeschlagen oder Timeout' }, + { status: 503 } + ) + } +} diff --git a/admin-compliance/app/api/sdk/v1/agent/notify/route.ts b/admin-compliance/app/api/sdk/v1/agent/notify/route.ts new file mode 100644 index 0000000..896f517 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/agent/notify/route.ts @@ -0,0 +1,30 @@ +/** + * Agent Notify API Proxy + * POST /api/sdk/v1/agent/notify → backend-compliance /api/compliance/agent/notify + */ + +import { NextRequest, NextResponse } from 'next/server' + +const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002' + +export async function POST(request: NextRequest) { + try { + const body = await request.text() + const response = await fetch(`${BACKEND_URL}/api/compliance/agent/notify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + signal: AbortSignal.timeout(15000), + }) + + if (!response.ok) { + const errorText = await response.text() + return NextResponse.json({ error: errorText }, { status: response.status }) + } + + return NextResponse.json(await response.json()) + } catch (error) { + console.error('Agent notify proxy error:', error) + return NextResponse.json({ error: 'Email-Versand fehlgeschlagen' }, { status: 503 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/agent/scan/route.ts b/admin-compliance/app/api/sdk/v1/agent/scan/route.ts index ff3e2de..7ec48d0 100644 --- a/admin-compliance/app/api/sdk/v1/agent/scan/route.ts +++ b/admin-compliance/app/api/sdk/v1/agent/scan/route.ts @@ -18,7 +18,11 @@ export async function POST(request: NextRequest) { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, +<<<<<<< HEAD signal: AbortSignal.timeout(30000), // 30s — just needs to start the job +======= + signal: AbortSignal.timeout(300000), // 5 min — multi-page scan + LLM calls +>>>>>>> feat/zeroclaw-compliance-agent }) if (!response.ok) { diff --git a/admin-compliance/app/api/sdk/v1/agent/scans/pdf/route.ts b/admin-compliance/app/api/sdk/v1/agent/scans/pdf/route.ts new file mode 100644 index 0000000..eabc01b --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/agent/scans/pdf/route.ts @@ -0,0 +1,36 @@ +/** + * PDF Export Proxy + * POST /api/sdk/v1/agent/scans/pdf → backend /api/compliance/agent/scans/pdf + */ + +import { NextRequest, NextResponse } from 'next/server' + +const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002' + +export async function POST(request: NextRequest) { + try { + const body = await request.text() + + const response = await fetch(`${BACKEND_URL}/api/compliance/agent/scans/pdf`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + signal: AbortSignal.timeout(30000), + }) + + if (!response.ok) { + return NextResponse.json({ error: 'PDF generation failed' }, { status: response.status }) + } + + const pdfBytes = await response.arrayBuffer() + return new NextResponse(pdfBytes, { + headers: { + 'Content-Type': 'application/pdf', + 'Content-Disposition': 'attachment; filename="compliance-report.pdf"', + }, + }) + } catch (error) { + console.error('PDF proxy error:', error) + return NextResponse.json({ error: 'PDF generation failed' }, { status: 503 }) + } +} diff --git a/admin-compliance/app/sdk/_components/PresetSection.tsx b/admin-compliance/app/sdk/_components/PresetSection.tsx new file mode 100644 index 0000000..9b2a5bb --- /dev/null +++ b/admin-compliance/app/sdk/_components/PresetSection.tsx @@ -0,0 +1,92 @@ +'use client' + +import { useState } from 'react' +import Link from 'next/link' +import { COMPANY_PROFILE_PRESETS, type CompanyProfilePreset } from '@/lib/sdk/company-profile-presets' +import { DOC_LABELS, CATEGORY_COLORS } from './doc-labels' + +export function PresetSection({ projectId }: { projectId?: string }) { + const [selectedPreset, setSelectedPreset] = useState(null) + + // Group recommended docs by category + const groupedDocs = selectedPreset + ? selectedPreset.recommendedDocs.reduce>((acc, docType) => { + const info = DOC_LABELS[docType] + if (!info) return acc + if (!acc[info.category]) acc[info.category] = [] + acc[info.category].push(info.label) + return acc + }, {}) + : null + + return ( +
+
+

Schnellstart: Welcher Unternehmenstyp sind Sie?

+

+ Waehlen Sie Ihre Branche — wir zeigen Ihnen welche Dokumente Sie benoetigen. +

+
+ + {/* Preset Cards */} +
+ {COMPANY_PROFILE_PRESETS.map((preset) => ( + + ))} +
+ + {/* Document Preview — shown when a preset is selected */} + {selectedPreset && groupedDocs && ( +
+
+
+

+ {selectedPreset.icon} {selectedPreset.label} — Ihre Dokumente +

+

+ {selectedPreset.recommendedDocs.length} Dokumente werden fuer Sie vorbereitet +

+
+ + Jetzt starten + +
+ +
+ {Object.entries(groupedDocs).map(([category, docs]) => ( +
+ + {category} + + {docs.map((doc) => ( +
+ {doc} +
+ ))} +
+ ))} +
+
+ )} +
+ ) +} diff --git a/admin-compliance/app/sdk/_components/doc-labels.ts b/admin-compliance/app/sdk/_components/doc-labels.ts new file mode 100644 index 0000000..4331f70 --- /dev/null +++ b/admin-compliance/app/sdk/_components/doc-labels.ts @@ -0,0 +1,131 @@ +/** + * Complete mapping of all document template types to display labels and categories. + * Used by PresetSection to show categorized document previews. + */ + +export const DOC_LABELS: Record = { + // ── Website ────────────────────────────────────────────────────── + privacy_policy: { label: 'Datenschutzerklaerung', category: 'Website' }, + impressum: { label: 'Impressum', category: 'Website' }, + cookie_policy: { label: 'Cookie-Richtlinie', category: 'Website' }, + cookie_banner: { label: 'Cookie-Banner-Texte', category: 'Website' }, + + // ── Vertraege ──────────────────────────────────────────────────── + agb: { label: 'AGB', category: 'Vertraege' }, + dpa: { label: 'AVV (Auftragsverarbeitung)', category: 'Vertraege' }, + nda: { label: 'Geheimhaltungsvereinbarung', category: 'Vertraege' }, + sla: { label: 'Service Level Agreement', category: 'Vertraege' }, + terms_of_use: { label: 'Nutzungsbedingungen', category: 'Vertraege' }, + cloud_service_agreement: { label: 'Cloud-Vertrag', category: 'Vertraege' }, + data_usage_clause: { label: 'Datennutzungsklausel', category: 'Vertraege' }, + + // ── Plattform ──────────────────────────────────────────────────── + community_guidelines: { label: 'Community Guidelines', category: 'Plattform' }, + acceptable_use: { label: 'Acceptable Use Policy', category: 'Plattform' }, + media_content_policy: { label: 'Medien-Richtlinie', category: 'Plattform' }, + copyright_policy: { label: 'Urheberrechtsrichtlinie', category: 'Plattform' }, + + // ── E-Commerce ─────────────────────────────────────────────────── + widerruf: { label: 'Widerrufsbelehrung', category: 'E-Commerce' }, + + // ── HR / Personal ──────────────────────────────────────────────── + employee_dsi: { label: 'Mitarbeiter-DSI', category: 'HR' }, + applicant_dsi: { label: 'Bewerber-DSI', category: 'HR' }, + whistleblower_policy: { label: 'Whistleblower-Richtlinie', category: 'HR' }, + employee_security_policy: { label: 'Mitarbeiter-Sicherheitsrichtlinie', category: 'HR' }, + security_awareness_policy: { label: 'Security-Awareness-Richtlinie', category: 'HR' }, + remote_work_policy: { label: 'Remote-Work-Richtlinie', category: 'HR' }, + offboarding_policy: { label: 'Offboarding-Richtlinie', category: 'HR' }, + + // ── Datenschutz (DSGVO) ────────────────────────────────────────── + tom_documentation: { label: 'TOM-Dokumentation', category: 'Datenschutz' }, + vvt_register: { label: 'Verarbeitungsverzeichnis', category: 'Datenschutz' }, + loeschkonzept: { label: 'Loeschkonzept', category: 'Datenschutz' }, + dsfa: { label: 'Datenschutz-Folgenabschaetzung', category: 'Datenschutz' }, + pflichtenregister: { label: 'Pflichtenregister', category: 'Datenschutz' }, + data_protection_concept: { label: 'Datenschutzkonzept', category: 'Datenschutz' }, + consent_texts: { label: 'Einwilligungstexte', category: 'Datenschutz' }, + informationspflichten: { label: 'Informationspflichten', category: 'Datenschutz' }, + verpflichtungserklaerung: { label: 'Verpflichtungserklaerung', category: 'Datenschutz' }, + social_media_dsi: { label: 'Social-Media-DSI', category: 'Datenschutz' }, + video_conference_dsi: { label: 'Videokonferenz-DSI', category: 'Datenschutz' }, + + // ── Daten-Policies ─────────────────────────────────────────────── + data_protection_policy: { label: 'Datenschutzrichtlinie', category: 'Daten-Governance' }, + data_classification_policy: { label: 'Datenklassifizierung', category: 'Daten-Governance' }, + data_retention_policy: { label: 'Aufbewahrungsrichtlinie', category: 'Daten-Governance' }, + data_transfer_policy: { label: 'Datentransfer-Richtlinie', category: 'Daten-Governance' }, + privacy_incident_policy: { label: 'Datenschutzvorfall-Richtlinie', category: 'Daten-Governance' }, + + // ── Betroffenenrechte ──────────────────────────────────────────── + dsr_process_art15: { label: 'Auskunftsrecht (Art. 15)', category: 'Betroffenenrechte' }, + dsr_process_art16: { label: 'Berichtigungsrecht (Art. 16)', category: 'Betroffenenrechte' }, + dsr_process_art17: { label: 'Loeschungsrecht (Art. 17)', category: 'Betroffenenrechte' }, + dsr_process_art18: { label: 'Einschraenkungsrecht (Art. 18)', category: 'Betroffenenrechte' }, + dsr_process_art19: { label: 'Mitteilungspflicht (Art. 19)', category: 'Betroffenenrechte' }, + dsr_process_art20: { label: 'Datenportabilitaet (Art. 20)', category: 'Betroffenenrechte' }, + dsr_process_art21: { label: 'Widerspruchsrecht (Art. 21)', category: 'Betroffenenrechte' }, + + // ── IT-Sicherheit (Konzepte) ───────────────────────────────────── + it_security_concept: { label: 'IT-Sicherheitskonzept', category: 'IT-Sicherheit' }, + backup_recovery_concept: { label: 'Backup- & Recovery-Konzept', category: 'IT-Sicherheit' }, + logging_concept: { label: 'Logging-Konzept', category: 'IT-Sicherheit' }, + incident_response_plan: { label: 'Incident-Response-Plan', category: 'IT-Sicherheit' }, + access_control_concept: { label: 'Zugriffskonzept', category: 'IT-Sicherheit' }, + risk_management_concept: { label: 'Risikomanagement-Konzept', category: 'IT-Sicherheit' }, + isms_manual: { label: 'ISMS-Handbuch', category: 'IT-Sicherheit' }, + + // ── IT-Sicherheit (Policies) ───────────────────────────────────── + information_security_policy: { label: 'Informationssicherheitsrichtlinie', category: 'IT-Policies' }, + access_control_policy: { label: 'Zugriffskontrollrichtlinie', category: 'IT-Policies' }, + password_policy: { label: 'Passwortrichtlinie', category: 'IT-Policies' }, + encryption_policy: { label: 'Verschluesselungsrichtlinie', category: 'IT-Policies' }, + logging_policy: { label: 'Protokollierungsrichtlinie', category: 'IT-Policies' }, + backup_policy: { label: 'Datensicherungsrichtlinie', category: 'IT-Policies' }, + incident_response_policy: { label: 'Incident-Response-Richtlinie', category: 'IT-Policies' }, + change_management_policy: { label: 'Change-Management-Richtlinie', category: 'IT-Policies' }, + patch_management_policy: { label: 'Patch-Management-Richtlinie', category: 'IT-Policies' }, + asset_management_policy: { label: 'Asset-Management-Richtlinie', category: 'IT-Policies' }, + cloud_security_policy: { label: 'Cloud-Security-Richtlinie', category: 'IT-Policies' }, + devsecops_policy: { label: 'DevSecOps-Richtlinie', category: 'IT-Policies' }, + secrets_management_policy: { label: 'Secrets-Management-Richtlinie', category: 'IT-Policies' }, + vulnerability_management_policy: { label: 'Schwachstellenmanagement', category: 'IT-Policies' }, + + // ── Lieferanten / Drittanbieter ────────────────────────────────── + vendor_risk_management_policy: { label: 'Lieferanten-Risikomanagement', category: 'Lieferanten' }, + third_party_security_policy: { label: 'Drittanbieter-Sicherheit', category: 'Lieferanten' }, + supplier_security_policy: { label: 'Lieferanten-Anforderungen', category: 'Lieferanten' }, + transfer_impact_assessment: { label: 'Transfer Impact Assessment', category: 'Lieferanten' }, + scc_companion: { label: 'SCC-Begleitdokument', category: 'Lieferanten' }, + + // ── BCM / Notfall ──────────────────────────────────────────────── + business_continuity_policy: { label: 'Business-Continuity', category: 'BCM' }, + disaster_recovery_policy: { label: 'Disaster-Recovery', category: 'BCM' }, + crisis_management_policy: { label: 'Krisenmanagement', category: 'BCM' }, + + // ── KI / Cyber ─────────────────────────────────────────────────── + ai_usage_policy: { label: 'KI-Nutzungsrichtlinie', category: 'KI & Cyber' }, + cybersecurity_policy: { label: 'Cybersecurity-Richtlinie (CRA)', category: 'KI & Cyber' }, + byod_policy: { label: 'BYOD-Richtlinie', category: 'KI & Cyber' }, + + // ── SOP ────────────────────────────────────────────────────────── + standard_operating_procedure: { label: 'Standard Operating Procedure', category: 'Prozesse' }, +} + +export const CATEGORY_COLORS: Record = { + Website: 'bg-blue-50 text-blue-700', + Vertraege: 'bg-purple-50 text-purple-700', + Plattform: 'bg-indigo-50 text-indigo-700', + 'E-Commerce': 'bg-green-50 text-green-700', + HR: 'bg-amber-50 text-amber-700', + Datenschutz: 'bg-red-50 text-red-700', + 'Daten-Governance': 'bg-rose-50 text-rose-700', + Betroffenenrechte: 'bg-fuchsia-50 text-fuchsia-700', + 'IT-Sicherheit': 'bg-gray-100 text-gray-700', + 'IT-Policies': 'bg-slate-100 text-slate-700', + Lieferanten: 'bg-orange-50 text-orange-700', + BCM: 'bg-yellow-50 text-yellow-700', + 'KI & Cyber': 'bg-cyan-50 text-cyan-700', + Marketing: 'bg-pink-50 text-pink-700', + Prozesse: 'bg-teal-50 text-teal-700', +} diff --git a/admin-compliance/app/sdk/agent/_components/AuthTestResult.tsx b/admin-compliance/app/sdk/agent/_components/AuthTestResult.tsx new file mode 100644 index 0000000..10b364f --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/AuthTestResult.tsx @@ -0,0 +1,73 @@ +'use client' + +import React from 'react' + +interface AuthCheck { + found: boolean + text: string + legal_ref: string +} + +interface AuthData { + url: string + authenticated: boolean + login_error: string + checks: Record + findings_count: number +} + +const CHECK_LABELS: Record = { + cancel_subscription: { label: 'Kuendigungsbutton (2 Klicks)', icon: '🚫' }, + delete_account: { label: 'Konto loeschen', icon: '🗑️' }, + export_data: { label: 'Daten exportieren', icon: '📥' }, + consent_settings: { label: 'Einwilligungen widerrufen', icon: '⚙️' }, + profile_visible: { label: 'Profildaten einsehen', icon: '👤' }, +} + +export function AuthTestResult({ data }: { data: AuthData }) { + if (!data.authenticated) { + return ( +
+

Login fehlgeschlagen

+

{data.login_error || 'Credentials oder Formular nicht erkannt'}

+
+ ) + } + + return ( +
+
+ + Erfolgreich eingeloggt + 0 ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`}> + {data.findings_count} fehlende Funktionen + +
+ +
+ {Object.entries(data.checks).map(([key, check]) => { + const info = CHECK_LABELS[key] || { label: key, icon: '❓' } + return ( +
+ {info.icon} +
+

+ {check.found ? '✓' : '✗'} {info.label} +

+ {check.text &&

{check.text}

} +
+ {check.legal_ref} +
+ ) + })} +
+ + {data.findings_count > 0 && ( +
+ {data.findings_count} Pflichtfunktion(en) fehlen. Der Nutzer kann seine Rechte + nach DSGVO nicht vollstaendig ausueben. +
+ )} +
+ ) +} diff --git a/admin-compliance/app/sdk/agent/_components/CompareResult.tsx b/admin-compliance/app/sdk/agent/_components/CompareResult.tsx new file mode 100644 index 0000000..8f23b00 --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/CompareResult.tsx @@ -0,0 +1,96 @@ +'use client' + +import React from 'react' + +interface SiteResult { + url: string + domain: string + risk_level: string + risk_score: number + findings_count: number + services_count: number + has_impressum: boolean + has_datenschutz: boolean + has_cookie_banner: boolean + has_google_fonts: boolean + scan_status: string +} + +const RISK_COLOR: Record = { + MINIMAL: 'text-green-700 bg-green-50', + LOW: 'text-yellow-700 bg-yellow-50', + LIMITED: 'text-orange-700 bg-orange-50', + HIGH: 'text-red-700 bg-red-50', + UNACCEPTABLE: 'text-red-900 bg-red-100', +} + +export function CompareResult({ sites }: { sites: SiteResult[] }) { + if (!sites.length) return null + + const checks = [ + { key: 'has_datenschutz', label: 'Datenschutzerklaerung' }, + { key: 'has_impressum', label: 'Impressum' }, + { key: 'has_cookie_banner', label: 'Cookie-Banner' }, + { key: 'has_google_fonts', label: 'Google Fonts (lokal?)' }, + ] + + return ( +
+
+ + + + + {sites.map((s, i) => ( + + ))} + + + + + + {sites.map((s, i) => ( + + ))} + + + + {sites.map((s, i) => ( + + ))} + + + + {sites.map((s, i) => ( + + ))} + + {checks.map(check => ( + + + {sites.map((s, i) => { + const val = (s as any)[check.key] + const isInverted = check.key === 'has_google_fonts' + const good = isInverted ? !val : val + return ( + + ) + })} + + ))} + +
Pruefung + {s.domain} +
Risiko-Score + + {s.risk_level || '?'} ({s.risk_score}/100) + +
Findings 0 ? 'text-red-700' : 'text-green-700'}`}> + {s.findings_count} +
Dienste erkannt{s.services_count}
{check.label} + {good ? '✓' : '✗'} +
+
+
+ ) +} diff --git a/admin-compliance/app/sdk/agent/_components/ConsentTestResult.tsx b/admin-compliance/app/sdk/agent/_components/ConsentTestResult.tsx new file mode 100644 index 0000000..c2dbd7c --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/ConsentTestResult.tsx @@ -0,0 +1,248 @@ +'use client' + +import React from 'react' + +interface Violation { + service: string + severity: string + text: string + legal_ref: string +} + +interface PhaseData { + scripts: string[] + cookies: string[] + tracking_services?: string[] + new_tracking?: string[] + violations?: Violation[] + undocumented?: string[] +} + +interface ConsentData { + banner_detected: boolean + banner_provider: string + phases: { + before_consent: PhaseData + after_reject: PhaseData + after_accept: PhaseData + } + summary: { + critical: number + high: number + undocumented: number + total_violations: number + category_violations?: number + categories_tested?: number + } + banner_checks?: { + has_impressum_link: boolean + has_dse_link: boolean + violations: { service: string; severity: string; text: string; legal_ref: string }[] + } + category_tests?: { + category: string + category_label: string + tracking_services: string[] + violations: { service: string; severity: string; text: string }[] + }[] +} + +const SEV = { + CRITICAL: { bg: 'bg-red-100 border-red-300', text: 'text-red-800', badge: 'bg-red-600' }, + HIGH: { bg: 'bg-orange-100 border-orange-300', text: 'text-orange-800', badge: 'bg-orange-500' }, +} + +function PhaseCard({ title, icon, data, type }: { + title: string; icon: string; data: PhaseData; type: 'before' | 'reject' | 'accept' +}) { + const violations = data.violations || [] + const tracking = data.tracking_services || data.new_tracking || [] + const undocumented = data.undocumented || [] + const hasProblem = violations.length > 0 || undocumented.length > 0 + + return ( +
+

+ {icon} {title} +

+ + {/* Violations */} + {violations.map((v, i) => ( +
+
+ + {v.severity} + + + {v.service} + +
+

{v.text}

+

{v.legal_ref}

+
+ ))} + + {/* Undocumented (Phase C only) */} + {undocumented.map((s, i) => ( +
+ ✗ {s} — nicht in Cookie-Policy dokumentiert +
+ ))} + + {/* Tracking services (no violations) */} + {violations.length === 0 && undocumented.length === 0 && tracking.length > 0 && ( +
+ {tracking.map((t, i) =>
✓ {t} — {type === 'accept' ? 'mit Consent OK' : 'erkannt'}
)} +
+ )} + + {violations.length === 0 && undocumented.length === 0 && tracking.length === 0 && ( +

✓ Keine Tracking-Dienste erkannt

+ )} + + {/* Cookie/Script count */} +
+ {data.scripts?.length || 0} Scripts + {data.cookies?.length || 0} Cookies +
+
+ ) +} + +export function ConsentTestResult({ data }: { data: ConsentData }) { + const s = data.summary + + return ( +
+ {/* Header */} +
+
+ + + Cookie-Banner: {data.banner_detected ? data.banner_provider : 'Nicht erkannt'} + +
+
+ {s.critical > 0 && ( + + {s.critical} Kritisch + + )} + {s.high > 0 && ( + + {s.high} Hoch + + )} + {s.total_violations === 0 && ( + + Keine Verstoesse + + )} +
+
+ + {/* Three Phases */} +
+ + {data.banner_detected && ( + <> + + + + )} +
+ + {/* Banner Text Checks */} + {data.banner_checks && (data.banner_checks.violations?.length > 0 || data.banner_checks.has_impressum_link !== undefined) && ( +
+

+ 📝 Banner-Text Pruefung +

+
+ + {data.banner_checks.has_impressum_link ? '✓' : '✗'} Impressum-Link + + + {data.banner_checks.has_dse_link ? '✓' : '✗'} DSE-Link + +
+ {data.banner_checks.violations?.map((v: any, i: number) => { + const isHigh = v.severity === 'HIGH' || v.severity === 'CRITICAL' + return ( +
+
+ + {v.severity} + +
+

{v.text}

+

{v.legal_ref}

+
+
+
+ ) + })} + {(!data.banner_checks.violations || data.banner_checks.violations.length === 0) && ( +

✓ Keine Banner-Text-Verstoesse erkannt

+ )} +
+ )} + + {/* Category Tests (Phase D-F) */} + {data.category_tests && data.category_tests.length > 0 && ( +
+

Kategorie-Tests ({data.category_tests.length})

+ {data.category_tests.map((ct, i) => { + const hasViolations = ct.violations.length > 0 + return ( +
+

+ 🔀 Nur "{ct.category_label}" +

+ {ct.violations.length > 0 ? ( + ct.violations.map((v, vi) => ( +
+ FALSCH + {v.text} +
+ )) + ) : ( +
+ {ct.tracking_services.length > 0 ? ( + ct.tracking_services.map((s, si) =>
✓ {s} — korrekte Kategorie
) + ) : ( +
✓ Keine Tracking-Dienste geladen — korrekt
+ )} +
+ )} +
+ ) + })} +
+ )} + + {/* No banner warning */} + {!data.banner_detected && ( +
+ Kein Cookie-Banner erkannt. Alle erkannten Tracking-Dienste laden ohne + Einwilligung — dies ist ein Verstoss gegen §25 TDDDG. +
+ )} +
+ ) +} diff --git a/admin-compliance/app/sdk/agent/_components/ScanResult.tsx b/admin-compliance/app/sdk/agent/_components/ScanResult.tsx index 36308fc..d0dbca3 100644 --- a/admin-compliance/app/sdk/agent/_components/ScanResult.tsx +++ b/admin-compliance/app/sdk/agent/_components/ScanResult.tsx @@ -1,6 +1,7 @@ 'use client' import React, { useState } from 'react' +import { TextReference } from './TextReference' interface ServiceInfo { name: string @@ -14,11 +15,27 @@ interface ServiceInfo { status: string } +interface TextRef { + found: boolean + source_url: string + document_type: string + section_heading: string + section_number: string + parent_section: string + paragraph_index: number + original_text: string + issue: string + correction_type: string + correction_text: string + insert_after: string +} + interface ScanFinding { code: string severity: string text: string correction: string +<<<<<<< HEAD doc_title: string } @@ -30,6 +47,9 @@ interface DiscoveredDocument { word_count: number completeness_pct: number findings_count: number +======= + text_reference: TextRef | null +>>>>>>> feat/zeroclaw-compliance-agent } interface ScanData { @@ -249,7 +269,12 @@ export function ScanResult({ data }: { data: ScanData }) {

{f.text}

- {f.correction && ( + {/* Text Reference (original text + position + correction) */} + {f.text_reference && ( + + )} + {/* Fallback: correction without text reference */} + {!f.text_reference && f.correction && (
)} +<<<<<<< HEAD {/* Email Status */} {data.email_status && ( @@ -280,6 +306,37 @@ export function ScanResult({ data }: { data: ScanData }) { E-Mail: {data.email_status === 'sent' ? 'Gesendet' : data.email_status} )} +======= + {/* PDF Export Button */} +
+ +
+>>>>>>> feat/zeroclaw-compliance-agent ) } diff --git a/admin-compliance/app/sdk/agent/_components/TextReference.tsx b/admin-compliance/app/sdk/agent/_components/TextReference.tsx new file mode 100644 index 0000000..da7b4cb --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/TextReference.tsx @@ -0,0 +1,108 @@ +'use client' + +import React, { useState } from 'react' + +interface TextRef { + found: boolean + source_url: string + document_type: string + section_heading: string + section_number: string + parent_section: string + paragraph_index: number + original_text: string + issue: string + correction_type: string + correction_text: string + insert_after: string +} + +const ISSUE_LABELS: Record = { + missing: { label: 'Fehlt in der DSE', color: 'text-red-700 bg-red-50' }, + incomplete: { label: 'Unvollstaendig', color: 'text-yellow-700 bg-yellow-50' }, + incorrect: { label: 'Fehlerhaft', color: 'text-orange-700 bg-orange-50' }, +} + +const CORRECTION_LABELS: Record = { + insert: 'Neuen Abschnitt einfuegen', + append: 'Am Ende des Absatzes ergaenzen', + replace: 'Absatz ersetzen', +} + +export function TextReference({ ref, correction }: { ref: TextRef; correction?: string }) { + const [showCorrection, setShowCorrection] = useState(false) + const issue = ISSUE_LABELS[ref.issue] || null + const correctionText = correction || ref.correction_text + + return ( +
+ {/* Original Text Block */} +
+

+ 📄 Originaltextblock: +

+
+ {ref.found ? ( +

{ref.original_text || '(Textinhalt konnte nicht extrahiert werden)'}

+ ) : ( +

Nicht vorhanden — Eintrag fehlt in der {ref.document_type}.

+ )} +
+
+ + {/* Position */} +
+

+ 📍 Position: +

+
+ {ref.found ? ( + <> + {ref.section_heading || 'Abschnitt unbekannt'} + {ref.section_number && (Nr. {ref.section_number})} + {ref.parent_section && in: {ref.parent_section}} + {ref.paragraph_index > 0 && | Absatz {ref.paragraph_index}} + + ) : ref.insert_after ? ( + {CORRECTION_LABELS[ref.correction_type] || 'Einfuegen'} nach Abschnitt "{ref.insert_after}" + ) : ( + Neuen Abschnitt in der {ref.document_type} anlegen + )} + {ref.source_url && ( +
in: {ref.source_url}
+ )} +
+
+ + {/* Correction */} + {correctionText && ( +
+ + {showCorrection && ( +
+ {issue && ( + + {CORRECTION_LABELS[ref.correction_type] || issue.label} + + )} +
{correctionText}
+ +
+ )} +
+ )} +
+ ) +} diff --git a/admin-compliance/app/sdk/agent/page.tsx b/admin-compliance/app/sdk/agent/page.tsx index f8d78f6..9aeba40 100644 --- a/admin-compliance/app/sdk/agent/page.tsx +++ b/admin-compliance/app/sdk/agent/page.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react' import { ScanResult } from './_components/ScanResult' +<<<<<<< HEAD import { DocCheckTab } from './_components/DocCheckTab' import { BannerCheckTab } from './_components/BannerCheckTab' import { ImpressumCheckTab } from './_components/ImpressumCheckTab' @@ -31,6 +32,43 @@ export default function AgentPage() { if (typeof window === 'undefined') return [] try { return JSON.parse(localStorage.getItem('agent-scan-history') || '[]') } catch { return [] } }) +======= +import { ConsentTestResult } from './_components/ConsentTestResult' +import { CompareResult } from './_components/CompareResult' +import { AuthTestResult } from './_components/AuthTestResult' + +type Mode = 'pre_launch' | 'post_launch' +type Tab = 'quick' | 'scan' | 'consent' | 'compare' | 'auth' + +const MODES = [ + { id: 'pre_launch' as Mode, label: 'Internes Dokument', desc: 'Vor Veroeffentlichung', icon: '📋' }, + { id: 'post_launch' as Mode, label: 'Live-Website', desc: 'Bereits online', icon: '🌐' }, +] + +const TABS = [ + { id: 'quick' as Tab, label: 'Schnellanalyse', info: 'Einzelne URL klassifizieren und bewerten.' }, + { id: 'scan' as Tab, label: 'Website-Scan', info: '5-10 Seiten scannen, Dienstleister abgleichen, Pflichtinhalte pruefen.' }, + { id: 'consent' as Tab, label: 'Cookie-Test', info: 'Testet mit Browser was VOR und NACH Cookie-Einwilligung geladen wird.' }, + { id: 'compare' as Tab, label: 'Vergleich', info: '2-5 Websites parallel scannen und Compliance vergleichen.' }, + { id: 'auth' as Tab, label: 'Login-Test', info: 'Nach Login pruefen: Kuendigung, Daten loeschen, Export, Einwilligungen.' }, +] + +export default function AgentPage() { + const [url, setUrl] = useState('') + const [urls, setUrls] = useState('') + const [mode, setMode] = useState('post_launch') + const [tab, setTab] = useState('quick') + const [scanLoading, setScanLoading] = useState(false) + const [scanError, setScanError] = useState(null) + const [scanData, setScanData] = useState(null) + const [scanHistory, setScanHistory] = useState([]) + const [consentData, setConsentData] = useState(null) + const [compareData, setCompareData] = useState(null) + const [authData, setAuthData] = useState(null) + const [authUser, setAuthUser] = useState('') + const [authPass, setAuthPass] = useState('') + const { analyze, answerFollowUp, loading, error, result, history } = useAgentAnalysis() +>>>>>>> feat/zeroclaw-compliance-agent React.useEffect(() => { localStorage.setItem('agent-scan-url', url) }, [url]) React.useEffect(() => { localStorage.setItem('agent-scan-tab', tab) }, [tab]) @@ -91,6 +129,7 @@ export default function AgentPage() { const handleScan = async (e: React.FormEvent) => { e.preventDefault() +<<<<<<< HEAD if (!url.trim()) return setScanLoading(true) setScanError(null) @@ -131,6 +170,51 @@ export default function AgentPage() { } catch (e) { setScanError(e instanceof Error ? e.message : 'Unbekannter Fehler') setScanProgress('') +======= + setScanLoading(true) + setScanError(null) + + try { + if (tab === 'quick') { + setScanLoading(false) + analyze(url.trim(), mode) + return + } + + let endpoint = '' + let body: any = {} + + if (tab === 'scan') { + endpoint = '/api/sdk/v1/agent/scan' + body = { url: url.trim(), mode } + } else if (tab === 'consent') { + endpoint = '/api/sdk/v1/agent/consent-test' + body = { url: url.trim() } + } else if (tab === 'compare') { + endpoint = '/api/sdk/v1/agent/compare' + body = { urls: urls.split('\n').map(u => u.trim()).filter(Boolean), mode } + } else if (tab === 'auth') { + endpoint = '/api/sdk/v1/agent/authenticated-scan' + body = { url: url.trim(), username: authUser, password: authPass } + } + + const res = await fetch(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + if (!res.ok) throw new Error(`Fehlgeschlagen: ${res.status}`) + const data = await res.json() + + if (tab === 'scan') { + setScanData(data) + setScanHistory(prev => [{ url: url.trim(), ...data, scanned_at: new Date().toISOString() }, ...prev].slice(0, 20)) + } else if (tab === 'consent') setConsentData(data) + else if (tab === 'compare') setCompareData(data) + else if (tab === 'auth') setAuthData(data) + } catch (e) { + setScanError(e instanceof Error ? e.message : 'Fehler') +>>>>>>> feat/zeroclaw-compliance-agent } finally { setScanLoading(false) } @@ -158,6 +242,7 @@ export default function AgentPage() {

Compliance Agent

+<<<<<<< HEAD

Analysiere Webseiten und Dokumente auf DSGVO-Konformitaet.

@@ -281,10 +366,93 @@ export default function AgentPage() { ))}
+======= +

Analysiere Dokumente und Webseiten auf DSGVO-Konformitaet.

+ + + {/* Mode */} +
+ {MODES.map(m => ( + + ))} +
+ + {/* Tabs */} +
+
+ {TABS.map(t => ( + + ))} +
+

{TABS.find(t => t.id === tab)?.info}

+
+ + {/* Input */} +
+ {tab === 'compare' ? ( +