feat(cmp): Phase 2 — script blocking + cookie tracking

Migration 108: scripts_blocked, scripts_released, cookies_set JSONB columns.
Backend models/schema/service/serializer/routes extended.
Admin detail modal shows released scripts and set cookies with categories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-11 22:52:26 +02:00
parent 051890c370
commit 397de741c1
8 changed files with 73 additions and 0 deletions
@@ -223,6 +223,37 @@ export default function BannerConsentsTab() {
</div> </div>
</div> </div>
{/* Scripts & Cookies */}
{(detail.scripts_released?.length > 0 || detail.cookies_set?.length > 0) && (
<div className="border-t border-gray-100 pt-3">
<p className="text-xs font-semibold text-gray-700 mb-2">Scripts & Cookies</p>
{detail.scripts_released?.length > 0 && (
<div className="mb-2">
<span className="text-gray-500 text-xs">Freigegebene Scripts</span>
{detail.scripts_released.map((s, i) => (
<p key={i} className="text-xs text-gray-600 font-mono truncate">{s.src} <span className={`px-1 rounded ${categoryColors[s.category] || 'bg-gray-100'}`}>{s.category}</span></p>
))}
</div>
)}
{detail.scripts_blocked?.length > 0 && (
<div className="mb-2">
<span className="text-gray-500 text-xs">Blockierte Scripts</span>
{detail.scripts_blocked.map((s, i) => (
<p key={i} className="text-xs text-red-600 font-mono truncate">{s.src} <span className="px-1 rounded bg-red-100 text-red-700">{s.category}</span></p>
))}
</div>
)}
{detail.cookies_set?.length > 0 && (
<div>
<span className="text-gray-500 text-xs">Gesetzte Cookies</span>
{detail.cookies_set.map((c, i) => (
<p key={i} className="text-xs text-gray-600 font-mono">{c.name} <span className="text-gray-400">({c.domain})</span> <span className={`px-1 rounded ${categoryColors[c.category] || 'bg-gray-100'}`}>{c.category}</span></p>
))}
</div>
)}
</div>
)}
{/* Technische Details */} {/* Technische Details */}
<div className="border-t border-gray-100 pt-3"> <div className="border-t border-gray-100 pt-3">
<p className="text-xs font-semibold text-gray-700 mb-2">Technisch</p> <p className="text-xs font-semibold text-gray-700 mb-2">Technisch</p>
@@ -126,6 +126,10 @@ export interface BannerConsentRecord {
os: string | null os: string | null
screen_resolution: string | null screen_resolution: string | null
session_id: string | null session_id: string | null
// Script/Cookie-Tracking (Migration 108)
scripts_blocked: { src: string; category: string }[]
scripts_released: { src: string; category: string }[]
cookies_set: { name: string; domain: string; expiry_days: number; category: string }[]
expires_at: string | null expires_at: string | null
created_at: string | null created_at: string | null
updated_at: string | null updated_at: string | null
@@ -87,6 +87,9 @@ async def record_consent(
screen_resolution=body.screen_resolution, screen_resolution=body.screen_resolution,
session_id=body.session_id, session_id=body.session_id,
consent_scope=body.consent_scope, consent_scope=body.consent_scope,
scripts_blocked=body.scripts_blocked,
scripts_released=body.scripts_released,
cookies_set=body.cookies_set,
) )
@@ -50,6 +50,10 @@ class BannerConsentDB(Base):
os = Column(Text) os = Column(Text)
screen_resolution = Column(Text) screen_resolution = Column(Text)
session_id = Column(Text) session_id = Column(Text)
# Script/Cookie-Tracking (Migration 108)
scripts_blocked = Column(JSON, default=list)
scripts_released = Column(JSON, default=list)
cookies_set = Column(JSON, default=list)
expires_at = Column(DateTime) expires_at = Column(DateTime)
created_at = Column(DateTime, nullable=False, default=datetime.utcnow) created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
@@ -30,6 +30,10 @@ class ConsentCreate(BaseModel):
screen_resolution: Optional[str] = None screen_resolution: Optional[str] = None
session_id: Optional[str] = None session_id: Optional[str] = None
consent_scope: Optional[str] = None consent_scope: Optional[str] = None
# Script/Cookie-Tracking (Migration 108)
scripts_blocked: List[dict[str, Any]] = []
scripts_released: List[dict[str, Any]] = []
cookies_set: List[dict[str, Any]] = []
class SiteConfigCreate(BaseModel): class SiteConfigCreate(BaseModel):
@@ -41,6 +41,9 @@ def consent_to_dict(c: BannerConsentDB) -> dict[str, Any]:
"os": c.os, "os": c.os,
"screen_resolution": c.screen_resolution, "screen_resolution": c.screen_resolution,
"session_id": c.session_id, "session_id": c.session_id,
"scripts_blocked": c.scripts_blocked or [],
"scripts_released": c.scripts_released or [],
"cookies_set": c.cookies_set or [],
"expires_at": c.expires_at.isoformat() if c.expires_at else None, "expires_at": c.expires_at.isoformat() if c.expires_at else None,
"created_at": c.created_at.isoformat() if c.created_at else None, "created_at": c.created_at.isoformat() if c.created_at else None,
"updated_at": c.updated_at.isoformat() if c.updated_at else None, "updated_at": c.updated_at.isoformat() if c.updated_at else None,
@@ -181,6 +181,9 @@ class BannerConsentService:
screen_resolution: Optional[str] = None, screen_resolution: Optional[str] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
consent_scope: Optional[str] = None, consent_scope: Optional[str] = None,
scripts_blocked: Optional[list[dict]] = None,
scripts_released: Optional[list[dict]] = None,
cookies_set: Optional[list[dict]] = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Upsert a device consent row for (tenant, site, device_fingerprint). """Upsert a device consent row for (tenant, site, device_fingerprint).
@@ -213,6 +216,9 @@ class BannerConsentService:
"screen_resolution": screen_resolution, "screen_resolution": screen_resolution,
"session_id": session_id, "session_id": session_id,
"consent_scope": consent_scope or "domain", "consent_scope": consent_scope or "domain",
"scripts_blocked": scripts_blocked or [],
"scripts_released": scripts_released or [],
"cookies_set": cookies_set or [],
} }
existing = ( existing = (
@@ -0,0 +1,18 @@
-- Migration 108: Script- und Cookie-Tracking fuer Banner-Consents
-- Erfasst welche Scripts blockiert/freigegeben und welche Cookies gesetzt wurden.
-- Alle Felder JSONB + nullable → backward-compatible.
-- Scripts die VOR Consent blockiert waren
ALTER TABLE compliance_banner_consents
ADD COLUMN IF NOT EXISTS scripts_blocked JSONB DEFAULT '[]'::jsonb;
-- [{"src": "https://www.googletagmanager.com/gtag/js", "category": "analytics"}]
-- Scripts die NACH Consent freigegeben wurden
ALTER TABLE compliance_banner_consents
ADD COLUMN IF NOT EXISTS scripts_released JSONB DEFAULT '[]'::jsonb;
-- [{"src": "https://www.googletagmanager.com/gtag/js", "category": "analytics"}]
-- Cookies die NACH Consent gesetzt wurden
ALTER TABLE compliance_banner_consents
ADD COLUMN IF NOT EXISTS cookies_set JSONB DEFAULT '[]'::jsonb;
-- [{"name": "_ga", "domain": ".breakpilot.ai", "expiry_days": 730, "category": "analytics"}]