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..4440458
--- /dev/null
+++ b/admin-compliance/app/api/sdk/v1/agent/consent-test/route.ts
@@ -0,0 +1,37 @@
+/**
+ * Consent Test API Proxy
+ * POST /api/sdk/v1/agent/consent-test → consent-tester:8094/scan
+ */
+
+import { NextRequest, NextResponse } from 'next/server'
+
+const CONSENT_TESTER_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_TESTER_URL}/scan`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body,
+ signal: AbortSignal.timeout(180000), // 3 min — 3 browser phases
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ return NextResponse.json(
+ { error: `Consent-Tester: ${response.status}`, detail: errorText },
+ { status: response.status }
+ )
+ }
+
+ return NextResponse.json(await response.json())
+ } 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/sdk/agent/_components/ConsentTestResult.tsx b/admin-compliance/app/sdk/agent/_components/ConsentTestResult.tsx
new file mode 100644
index 0000000..57385ae
--- /dev/null
+++ b/admin-compliance/app/sdk/agent/_components/ConsentTestResult.tsx
@@ -0,0 +1,166 @@
+'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
+ }
+}
+
+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 && (
+ <>
+
+
+ >
+ )}
+
+
+ {/* 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/page.tsx b/admin-compliance/app/sdk/agent/page.tsx
index 6c5391a..404fee3 100644
--- a/admin-compliance/app/sdk/agent/page.tsx
+++ b/admin-compliance/app/sdk/agent/page.tsx
@@ -6,9 +6,10 @@ import { AnalysisResult } from './_components/AnalysisResult'
import { AnalysisHistory } from './_components/AnalysisHistory'
import { FollowUpQuestions } from './_components/FollowUpQuestions'
import { ScanResult } from './_components/ScanResult'
+import { ConsentTestResult } from './_components/ConsentTestResult'
type AnalysisMode = 'pre_launch' | 'post_launch'
-type AnalysisTab = 'quick' | 'scan'
+type AnalysisTab = 'quick' | 'scan' | 'consent'
const MODES: { id: AnalysisMode; label: string; desc: string; icon: string }[] = [
{ id: 'pre_launch', label: 'Internes Dokument', desc: 'Vor Veroeffentlichung pruefen', icon: '📋' },
@@ -17,7 +18,8 @@ const MODES: { id: AnalysisMode; label: string; desc: string; icon: string }[] =
const TABS: { id: AnalysisTab; label: string; info: string }[] = [
{ id: 'quick', label: 'Schnellanalyse', info: 'Analysiert nur die eingegebene URL. Fuer einen umfassenden Check nutzen Sie den Website-Scan.' },
- { id: 'scan', label: 'Website-Scan', info: 'Scannt automatisch 5-10 Unterseiten (Startseite, Datenschutz, Impressum, AGB, Cookies) und gleicht erkannte Dienste mit der Datenschutzerklaerung ab.' },
+ { id: 'scan', label: 'Website-Scan', info: 'Scannt automatisch 5-10 Unterseiten und gleicht erkannte Dienste mit der Datenschutzerklaerung ab.' },
+ { id: 'consent', label: 'Cookie-Test', info: 'Testet mit echtem Browser was VOR und NACH Cookie-Einwilligung geladen wird. Erkennt Verstoesse gegen §25 TDDDG.' },
]
export default function AgentPage() {
@@ -28,6 +30,9 @@ export default function AgentPage() {
const [scanError, setScanError] = useState(null)
const [scanData, setScanData] = useState(null)
const [scanHistory, setScanHistory] = useState([])
+ const [consentLoading, setConsentLoading] = useState(false)
+ const [consentError, setConsentError] = useState(null)
+ const [consentData, setConsentData] = useState(null)
const { analyze, answerFollowUp, loading, error, result, history } = useAgentAnalysis()
const handleSubmit = async (e: React.FormEvent) => {
@@ -36,7 +41,7 @@ export default function AgentPage() {
if (tab === 'quick') {
analyze(url.trim(), mode)
- } else {
+ } else if (tab === 'scan') {
setScanLoading(true)
setScanError(null)
setScanData(null)
@@ -55,11 +60,28 @@ export default function AgentPage() {
} finally {
setScanLoading(false)
}
+ } else {
+ setConsentLoading(true)
+ setConsentError(null)
+ setConsentData(null)
+ try {
+ const res = await fetch('/api/sdk/v1/agent/consent-test', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ url: url.trim() }),
+ })
+ if (!res.ok) throw new Error(`Cookie-Test fehlgeschlagen: ${res.status}`)
+ setConsentData(await res.json())
+ } catch (e) {
+ setConsentError(e instanceof Error ? e.message : 'Unbekannter Fehler')
+ } finally {
+ setConsentLoading(false)
+ }
}
}
- const isLoading = tab === 'quick' ? loading : scanLoading
- const currentError = tab === 'quick' ? error : scanError
+ const isLoading = tab === 'quick' ? loading : tab === 'scan' ? scanLoading : consentLoading
+ const currentError = tab === 'quick' ? error : tab === 'scan' ? scanError : consentError
const currentTab = TABS.find(t => t.id === tab)!
return (
@@ -105,7 +127,7 @@ export default function AgentPage() {
{/* URL Input */}
@@ -143,6 +165,13 @@ export default function AgentPage() {
)}
+ {/* Consent Test Result */}
+ {tab === 'consent' && consentData && (
+
+
+
+ )}
+
{/* History */}
{tab === 'quick' && (
{ setUrl(r.url); analyze(r.url, mode) }} />
@@ -152,7 +181,7 @@ export default function AgentPage() {
Letzte Scans
{scanHistory.map((item, i) => (
-