b53b36fdc5
- 5 tabs: Schnellanalyse, Website-Scan, Cookie-Test, Vergleich, Login-Test - PDF download button in ScanResult - CompareResult: side-by-side compliance comparison table - AuthTestResult: 5 post-login checks with legal refs - API proxies: /scans/pdf, /compare, /authenticated-scan - Compare: textarea for 2-5 URLs, parallel scanning Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
|
|
interface SiteResult {
|
|
url: string
|
|
domain: string
|
|
risk_level: string
|
|
risk_score: number
|
|
findings_count: number
|
|
services_count: number
|
|
has_impressum: boolean
|
|
has_datenschutz: boolean
|
|
has_cookie_banner: boolean
|
|
has_google_fonts: boolean
|
|
scan_status: string
|
|
}
|
|
|
|
const RISK_COLOR: Record<string, string> = {
|
|
MINIMAL: 'text-green-700 bg-green-50',
|
|
LOW: 'text-yellow-700 bg-yellow-50',
|
|
LIMITED: 'text-orange-700 bg-orange-50',
|
|
HIGH: 'text-red-700 bg-red-50',
|
|
UNACCEPTABLE: 'text-red-900 bg-red-100',
|
|
}
|
|
|
|
export function CompareResult({ sites }: { sites: SiteResult[] }) {
|
|
if (!sites.length) return null
|
|
|
|
const checks = [
|
|
{ key: 'has_datenschutz', label: 'Datenschutzerklaerung' },
|
|
{ key: 'has_impressum', label: 'Impressum' },
|
|
{ key: 'has_cookie_banner', label: 'Cookie-Banner' },
|
|
{ key: 'has_google_fonts', label: 'Google Fonts (lokal?)' },
|
|
]
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-sm border-collapse">
|
|
<thead>
|
|
<tr className="bg-gray-50">
|
|
<th className="text-left px-3 py-2 text-xs font-medium text-gray-500 w-44">Pruefung</th>
|
|
{sites.map((s, i) => (
|
|
<th key={i} className="text-center px-3 py-2 text-xs font-medium text-gray-700">
|
|
{s.domain}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-100">
|
|
<tr>
|
|
<td className="px-3 py-2 text-gray-600">Risiko-Score</td>
|
|
{sites.map((s, i) => (
|
|
<td key={i} className="px-3 py-2 text-center">
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${RISK_COLOR[s.risk_level] || 'text-gray-600 bg-gray-50'}`}>
|
|
{s.risk_level || '?'} ({s.risk_score}/100)
|
|
</span>
|
|
</td>
|
|
))}
|
|
</tr>
|
|
<tr>
|
|
<td className="px-3 py-2 text-gray-600">Findings</td>
|
|
{sites.map((s, i) => (
|
|
<td key={i} className={`px-3 py-2 text-center font-medium ${s.findings_count > 0 ? 'text-red-700' : 'text-green-700'}`}>
|
|
{s.findings_count}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
<tr>
|
|
<td className="px-3 py-2 text-gray-600">Dienste erkannt</td>
|
|
{sites.map((s, i) => (
|
|
<td key={i} className="px-3 py-2 text-center text-gray-700">{s.services_count}</td>
|
|
))}
|
|
</tr>
|
|
{checks.map(check => (
|
|
<tr key={check.key}>
|
|
<td className="px-3 py-2 text-gray-600">{check.label}</td>
|
|
{sites.map((s, i) => {
|
|
const val = (s as any)[check.key]
|
|
const isInverted = check.key === 'has_google_fonts'
|
|
const good = isInverted ? !val : val
|
|
return (
|
|
<td key={i} className={`px-3 py-2 text-center font-medium ${good ? 'text-green-600' : 'text-red-600'}`}>
|
|
{good ? '✓' : '✗'}
|
|
</td>
|
|
)
|
|
})}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|