diff --git a/admin-compliance/app/sdk/agent/page.tsx b/admin-compliance/app/sdk/agent/page.tsx index cb36083..6c5391a 100644 --- a/admin-compliance/app/sdk/agent/page.tsx +++ b/admin-compliance/app/sdk/agent/page.tsx @@ -15,9 +15,9 @@ const MODES: { id: AnalysisMode; label: string; desc: string; icon: string }[] = { id: 'post_launch', label: 'Live-Website', desc: 'Bereits online analysieren', icon: '🌐' }, ] -const TABS: { id: AnalysisTab; label: string; desc: string }[] = [ - { id: 'quick', label: 'Schnellanalyse', desc: 'Einzelne Seite klassifizieren + bewerten' }, - { id: 'scan', label: 'Website-Scan', desc: 'Mehrere Seiten scannen + Dienstleister abgleichen' }, +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.' }, ] export default function AgentPage() { @@ -27,6 +27,7 @@ export default function AgentPage() { const [scanLoading, setScanLoading] = useState(false) const [scanError, setScanError] = useState(null) const [scanData, setScanData] = useState(null) + const [scanHistory, setScanHistory] = useState([]) const { analyze, answerFollowUp, loading, error, result, history } = useAgentAnalysis() const handleSubmit = async (e: React.FormEvent) => { @@ -46,7 +47,9 @@ export default function AgentPage() { body: JSON.stringify({ url: url.trim(), mode }), }) if (!res.ok) throw new Error(`Scan fehlgeschlagen: ${res.status}`) - setScanData(await res.json()) + const data = await res.json() + setScanData(data) + setScanHistory(prev => [{ url: url.trim(), ...data, scanned_at: new Date().toISOString() }, ...prev].slice(0, 20)) } catch (e) { setScanError(e instanceof Error ? e.message : 'Unbekannter Fehler') } finally { @@ -57,6 +60,7 @@ export default function AgentPage() { const isLoading = tab === 'quick' ? loading : scanLoading const currentError = tab === 'quick' ? error : scanError + const currentTab = TABS.find(t => t.id === tab)! return (
@@ -82,17 +86,20 @@ export default function AgentPage() { ))}
- {/* Tab Selection */} -
- {TABS.map(t => ( - - ))} + {/* Tab Selection + Info */} +
+
+ {TABS.map(t => ( + + ))} +
+

{currentTab.info}

{/* URL Input */} @@ -136,10 +143,32 @@ export default function AgentPage() {
)} - {/* History (quick only) */} + {/* History */} {tab === 'quick' && ( { setUrl(r.url); analyze(r.url, mode) }} /> )} + {tab === 'scan' && scanHistory.length > 0 && ( +
+

Letzte Scans

+
+ {scanHistory.map((item, i) => ( + + ))} +
+
+ )} ) } diff --git a/backend-compliance/compliance/api/agent_analyze_routes.py b/backend-compliance/compliance/api/agent_analyze_routes.py index 51399b0..ac6ceb5 100644 --- a/backend-compliance/compliance/api/agent_analyze_routes.py +++ b/backend-compliance/compliance/api/agent_analyze_routes.py @@ -105,7 +105,7 @@ async def analyze_url(req: AnalyzeRequest): email_result = send_email( recipient=req.recipient, subject=f"[{mode_label}] Compliance-Finding: {classification} — {req.url[:60]}", - body_html=f"
{summary}
", + body_html=summary, ) return AnalyzeResponse( @@ -349,53 +349,77 @@ def _risk_to_escalation(risk_level: str) -> str: return mapping.get(risk_level.upper() if risk_level else "", "E0") +DOC_TYPE_LABELS = { + "privacy_policy": "Datenschutzerklaerung", + "cookie_banner": "Cookie-Banner", + "terms_of_service": "AGB", + "imprint": "Impressum", + "dpa": "Auftragsverarbeitung (AVV)", + "other": "Sonstiges", +} + +RISK_COLORS = { + "MINIMAL": ("#16a34a", "Niedrig"), + "LOW": ("#ca8a04", "Gering"), + "LIMITED": ("#ea580c", "Mittel"), + "HIGH": ("#dc2626", "Hoch"), + "UNACCEPTABLE": ("#991b1b", "Kritisch"), +} + + def _build_summary( url: str, classification: str, assessment: dict, role: str, findings_str: list[str], controls_str: list[str], mode: str = "post_launch", ) -> str: - """Build a German manager summary, adapted to pre/post-launch context.""" + """Build HTML summary for email and frontend.""" risk = assessment.get("risk_level", "unbekannt") score = assessment.get("risk_score", 0) recommendation = assessment.get("recommendation", "") dsfa = assessment.get("dsfa_recommended", False) is_live = mode == "post_launch" + risk_color, risk_label = RISK_COLORS.get(risk, ("#6b7280", risk)) + doc_label = DOC_TYPE_LABELS.get(classification, classification) - findings_text = "\n".join(f"- {f}" for f in findings_str[:5]) if findings_str else "Keine" - controls_text = "\n".join(f"- {c}" for c in controls_str[:5]) if controls_str else "Keine" - - mode_header = ( - "PRUEFUNG LIVE-WEBSITE — Das Dokument ist bereits oeffentlich zugaenglich." + mode_banner = ( + '
' + 'LIVE-WEBSITE — Das Dokument ist bereits oeffentlich zugaenglich.
' if is_live else - "INTERNE PRUEFUNG — Das Dokument ist noch nicht veroeffentlicht." + '
' + 'INTERNE PRUEFUNG — Dokument noch nicht veroeffentlicht.
' ) - parts = [ - mode_header, - "", - f"Dokumenttyp: {classification}", - f"Quelle: {url}", - f"Risikobewertung: {risk} ({score}/100)", - f"Zustaendig: {role}", - f"DSFA empfohlen: {'Ja' if dsfa else 'Nein'}", - "", - f"Findings:\n{findings_text}", - "", - f"Erforderliche Massnahmen:\n{controls_text}", - ] + findings_html = "".join(f'
  • {f}
  • ' for f in findings_str[:8]) if findings_str else '
  • Keine
  • ' + controls_html = "".join(f'
  • {c}
  • ' for c in controls_str[:8]) if controls_str else '
  • Keine
  • ' + warning = "" if is_live and findings_str: - parts.extend([ - "", - "ACHTUNG: Diese Maengel sind bereits oeffentlich sichtbar. " - "Sofortige Nachbesserung empfohlen um Abmahnrisiken zu minimieren.", - ]) + warning = ( + '
    ' + '⚠ ACHTUNG: Diese Maengel sind bereits oeffentlich sichtbar. ' + 'Sofortige Nachbesserung empfohlen um Abmahnrisiken zu minimieren.
    ' + ) elif not is_live and controls_str: - parts.extend([ - "", - "Empfehlung: Implementieren Sie die erforderlichen Kontrollen vor der Veroeffentlichung.", - ]) + warning = ( + '
    ' + 'Empfehlung: Implementieren Sie die erforderlichen Kontrollen vor der Veroeffentlichung.
    ' + ) - if recommendation: - parts.extend(["", f"Weitere Empfehlung: {recommendation}"]) - return "\n".join(parts) + rec_html = f'

    {recommendation}

    ' if recommendation else "" + + return f""" + {mode_banner} + + + + + + +
    Dokumenttyp{doc_label}
    Quelle{url}
    Risikobewertung{risk_label} ({score}/100)
    Zustaendig{role}
    DSFA empfohlen{'Ja' if dsfa else 'Nein'}
    +

    Findings

    +
      {findings_html}
    +

    Erforderliche Massnahmen

    +
      {controls_html}
    + {warning} + {rec_html} + """