feat(cookie): Findings bearbeitbar — gruppiert nach Typ + Matrix + Hinweise-Split
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>
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { CookieFindings } from './CookieFindings'
|
||||
|
||||
export interface CookieFinding {
|
||||
vendor: string
|
||||
cookie: string
|
||||
@@ -16,6 +18,7 @@ export interface CookieFinding {
|
||||
declared: string
|
||||
library_purpose: string
|
||||
remediation: string
|
||||
kind?: string
|
||||
control?: { control_id?: string | null; regulation?: string; article?: string }
|
||||
}
|
||||
|
||||
@@ -36,21 +39,6 @@ interface CheckData {
|
||||
}
|
||||
}
|
||||
|
||||
const SEV_COLOR: Record<string, string> = {
|
||||
HIGH: 'bg-red-100 text-red-700',
|
||||
MEDIUM: 'bg-amber-100 text-amber-700',
|
||||
LOW: 'bg-blue-100 text-blue-700',
|
||||
}
|
||||
const TYPE_LABEL: Record<string, string> = {
|
||||
tracker_as_necessary: 'Tracker als „notwendig" deklariert',
|
||||
missing_purpose: 'Zweck fehlt',
|
||||
excessive_lifetime: 'Speicherdauer zu lang',
|
||||
vague_duration: 'Speicherdauer nicht konkret',
|
||||
missing_retention: 'Keine Speicherdauer/Löschfrist',
|
||||
third_country: 'Drittland-Transfer',
|
||||
eu_alternative: 'EU-Alternative verfügbar',
|
||||
storage_transparency: 'Speichertyp nicht transparent',
|
||||
}
|
||||
const STORAGE_LABEL: Record<string, string> = {
|
||||
cookie: 'Cookies', local_storage: 'Local Storage',
|
||||
session_storage: 'Session Storage', indexeddb: 'IndexedDB',
|
||||
@@ -66,75 +54,45 @@ export function CookieFindingList({ data }: { data: CheckData }) {
|
||||
const driftShown =
|
||||
!!drift && ((drift.declared_count ?? 0) + (drift.browser_count ?? 0)) > 0
|
||||
return (
|
||||
<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 border-b 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 className="px-4 py-2.5 bg-slate-50 border-b text-sm font-semibold text-gray-800">
|
||||
Library-Abgleich — {findings.length} Befund{findings.length !== 1 ? 'e' : ''}
|
||||
<span className="ml-2 text-xs font-normal text-gray-400">
|
||||
{s.in_library ?? 0}/{s.checked ?? 0} Cookies in der Library erkannt
|
||||
</span>
|
||||
</div>
|
||||
{findings.length === 0 ? (
|
||||
<div className="px-4 py-3 text-sm text-green-700">
|
||||
Keine Abweichungen gegen die Library.
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-gray-100 max-h-96 overflow-auto">
|
||||
{findings.map((f, i) => (
|
||||
<div key={i} className="px-4 py-2.5 space-y-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className={`text-[10px] font-semibold px-1.5 py-0.5 rounded ${SEV_COLOR[f.severity] || 'bg-gray-100 text-gray-600'}`}>
|
||||
{f.severity}
|
||||
</span>
|
||||
<code className="text-xs text-gray-700">{f.cookie}</code>
|
||||
<span className="text-xs text-gray-400">· {f.vendor}</span>
|
||||
<span className="text-[10px] text-gray-500 ml-auto">
|
||||
{TYPE_LABEL[f.type] || f.type}
|
||||
</span>
|
||||
</div>
|
||||
{f.library_purpose && (
|
||||
<div className="text-xs text-gray-500">Library-Zweck: {f.library_purpose}</div>
|
||||
<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></>
|
||||
)}
|
||||
<div className="text-xs text-gray-700">{f.remediation}</div>
|
||||
{f.control?.regulation && f.control.regulation !== '—' && (
|
||||
<div className="text-[10px] text-gray-400">
|
||||
Rechtsgrundlage: {f.control.regulation} {f.control.article}
|
||||
{f.control.control_id && ` · Control ${f.control.control_id}`}
|
||||
</div>
|
||||
{(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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user