39cb6afc23
CookieFindings: Umschalter [Nach Fehlertyp] (je Typ: Maßnahme + betroffene Cookies + Ticket-Text) ↔ [Matrix] (Cookie×Typ, ✗ Handlung / ⚠ Hinweis). Trennung FINDINGS (zu beheben) vs HINWEISE (neutral, gegen DSE zu prüfen). Backend: kind-Klassifikation (third_country/eu_alternative=hinweis); Drittland- Remediation neutral formuliert (pro Verarbeiter prüfen, keine 'in DSE benennen'- Befehle, da DSE-Abdeckung wie BMWs 'in der Regel SCC' oft unzureichend). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
4.2 KiB
TypeScript
120 lines
4.2 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* CookieLibraryPanel — Pro-Cookie-Abgleich gegen die Knowledge-Library:
|
|
* findet als „notwendig" deklarierte Tracker + fehlende Zwecke und zeigt je
|
|
* Befund die Abstellmaßnahme. Lädt aus dem Snapshot (kein Re-Crawl).
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react'
|
|
|
|
import { CookieFindings } from './CookieFindings'
|
|
|
|
export interface CookieFinding {
|
|
vendor: string
|
|
cookie: string
|
|
type: string
|
|
severity: string
|
|
declared: string
|
|
library_purpose: string
|
|
remediation: string
|
|
kind?: string
|
|
control?: { control_id?: string | null; regulation?: string; article?: string }
|
|
}
|
|
|
|
interface CheckData {
|
|
summary?: { checked?: number; in_library?: number; findings?: number }
|
|
findings?: CookieFinding[]
|
|
storage_inventory?: {
|
|
total?: number
|
|
by_type?: Record<string, number>
|
|
real_cookies?: number
|
|
other_storage?: number
|
|
}
|
|
drift?: {
|
|
declared_count?: number
|
|
browser_count?: number
|
|
high_findings?: number
|
|
low_findings?: number
|
|
}
|
|
}
|
|
|
|
const STORAGE_LABEL: Record<string, string> = {
|
|
cookie: 'Cookies', local_storage: 'Local Storage',
|
|
session_storage: 'Session Storage', indexeddb: 'IndexedDB',
|
|
framework_storage: 'Framework-Storage',
|
|
}
|
|
|
|
// Pure, testbar.
|
|
export function CookieFindingList({ data }: { data: CheckData }) {
|
|
const findings = data.findings || []
|
|
const s = data.summary || {}
|
|
const inv = data.storage_inventory
|
|
const drift = data.drift
|
|
const driftShown =
|
|
!!drift && ((drift.declared_count ?? 0) + (drift.browser_count ?? 0)) > 0
|
|
return (
|
|
<div className="space-y-3">
|
|
{(driftShown || (inv && (inv.total ?? 0) > 0)) && (
|
|
<div className="border rounded-lg overflow-hidden">
|
|
{driftShown && (
|
|
<div className="px-4 py-2.5 bg-amber-50 border-b text-xs text-amber-900">
|
|
<span className="font-semibold">Richtlinie ↔ Realität:</span>{' '}
|
|
<strong>{drift!.declared_count ?? 0}</strong> in der Cookie-Richtlinie
|
|
dokumentiert · <strong>{drift!.browser_count ?? 0}</strong> im Browser geladen
|
|
{(drift!.high_findings ?? 0) > 0 && (
|
|
<> · <strong className="text-red-700">{drift!.high_findings} undokumentiert geladen</strong></>
|
|
)}
|
|
{(drift!.low_findings ?? 0) > 0 && (
|
|
<> · {drift!.low_findings} dokumentiert, aber nicht geladen</>
|
|
)}
|
|
</div>
|
|
)}
|
|
{inv && (inv.total ?? 0) > 0 && (
|
|
<div className="px-4 py-2.5 bg-blue-50 text-xs text-blue-900">
|
|
<span className="font-semibold">Storage-Inventar:</span>{' '}
|
|
{inv.total} als „Cookies" gelistet →{' '}
|
|
<strong>{inv.real_cookies} echte Cookies</strong>
|
|
{(inv.other_storage ?? 0) > 0 && (
|
|
<> + <strong className="text-amber-700">{inv.other_storage} andere Endgeräte-Speicher</strong></>
|
|
)}
|
|
{inv.by_type && (
|
|
<span className="text-blue-700 ml-1">
|
|
({Object.entries(inv.by_type)
|
|
.map(([k, n]) => `${n} ${STORAGE_LABEL[k] || k}`)
|
|
.join(' · ')})
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="text-[11px] text-gray-400">
|
|
{s.in_library ?? 0}/{s.checked ?? 0} Cookies in der Library erkannt
|
|
</div>
|
|
<CookieFindings findings={findings} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function CookieLibraryPanel(
|
|
{ snapshotId, data: provided }: { snapshotId: string; data?: CheckData },
|
|
) {
|
|
const [data, setData] = useState<CheckData | null>(provided ?? null)
|
|
const [loading, setLoading] = useState(!provided)
|
|
|
|
useEffect(() => {
|
|
if (provided) { setData(provided); setLoading(false); return }
|
|
let cancelled = false
|
|
fetch(`/api/sdk/v1/agent/snapshots/${snapshotId}/cookie-check`)
|
|
.then(r => r.json())
|
|
.then(d => { if (!cancelled) setData(d) })
|
|
.catch(() => { if (!cancelled) setData({ findings: [] }) })
|
|
.finally(() => { if (!cancelled) setLoading(false) })
|
|
return () => { cancelled = true }
|
|
}, [snapshotId, provided])
|
|
|
|
if (loading) return <div className="text-xs text-gray-400">Library-Abgleich läuft…</div>
|
|
return <CookieFindingList data={data || {}} />
|
|
}
|