fix: Restore all files lost during destructive rebase

A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -0,0 +1,482 @@
'use client'
/**
* PCA Platform Admin Page
*
* Manages bot detection, session monitoring, and payment settings
* for the Person-Corporate-Agent platform.
*/
import { useState, useEffect, useCallback } from 'react'
import AdminLayout from '@/components/admin/AdminLayout'
// Types
interface SessionMetrics {
session_id: string
score: number
dwell_ratio: number
scroll_depth: number
clicks: number
mouse_moves: number
action: string
last_update: string
}
interface ServiceStatus {
status: 'healthy' | 'unhealthy' | 'offline'
service: string
version: string
uptime?: string
}
interface PCAConfig {
thresholds: {
score_pass: number
score_challenge: number
}
weights: {
dwell_ratio: number
scroll_score: number
pointer_variance: number
click_rate: number
}
step_up: {
methods: string[]
primary: string
}
}
interface Stats {
activeSessions: number
humanScore: number
botScore: number
challengesIssued: number
paymentsReceived: number
totalRevenue: string
}
export default function PCAAdminPage() {
const [serviceStatus, setServiceStatus] = useState<ServiceStatus | null>(null)
const [config, setConfig] = useState<PCAConfig | null>(null)
const [sessions, setSessions] = useState<SessionMetrics[]>([])
const [stats, setStats] = useState<Stats>({
activeSessions: 0,
humanScore: 0,
botScore: 0,
challengesIssued: 0,
paymentsReceived: 0,
totalRevenue: '0.00 EUR'
})
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<'overview' | 'sessions' | 'config' | 'payments'>('overview')
// Fetch service status
const fetchStatus = useCallback(async () => {
try {
const response = await fetch('/api/admin/pca?action=status')
if (response.ok) {
const data = await response.json()
setServiceStatus(data)
setError(null)
} else {
setServiceStatus({ status: 'offline', service: 'pca-heuristic-service', version: 'unknown' })
}
} catch {
setServiceStatus({ status: 'offline', service: 'pca-heuristic-service', version: 'unknown' })
}
}, [])
// Fetch config
const fetchConfig = useCallback(async () => {
try {
const response = await fetch('/api/admin/pca?action=config')
if (response.ok) {
const data = await response.json()
setConfig(data)
}
} catch {
// Use defaults
setConfig({
thresholds: { score_pass: 0.7, score_challenge: 0.4 },
weights: { dwell_ratio: 0.30, scroll_score: 0.25, pointer_variance: 0.20, click_rate: 0.25 },
step_up: { methods: ['webauthn', 'pow'], primary: 'webauthn' }
})
}
}, [])
// Fetch sessions (mock for now)
const fetchSessions = useCallback(async () => {
// Mock data - in production would fetch from backend
setSessions([
{ session_id: 'pca_abc123', score: 0.85, dwell_ratio: 0.92, scroll_depth: 65, clicks: 12, mouse_moves: 234, action: 'allow', last_update: new Date().toISOString() },
{ session_id: 'pca_def456', score: 0.32, dwell_ratio: 0.15, scroll_depth: 5, clicks: 0, mouse_moves: 3, action: 'challenge', last_update: new Date().toISOString() },
{ session_id: 'pca_ghi789', score: 0.71, dwell_ratio: 0.78, scroll_depth: 45, clicks: 8, mouse_moves: 156, action: 'allow', last_update: new Date().toISOString() },
])
}, [])
useEffect(() => {
const init = async () => {
setLoading(true)
await Promise.all([fetchStatus(), fetchConfig(), fetchSessions()])
setLoading(false)
}
init()
// Refresh every 10 seconds
const interval = setInterval(() => {
fetchStatus()
fetchSessions()
}, 10000)
return () => clearInterval(interval)
}, [fetchStatus, fetchConfig, fetchSessions])
// Score color helper
const getScoreColor = (score: number) => {
if (score >= 0.7) return 'text-green-600 bg-green-100'
if (score >= 0.4) return 'text-yellow-600 bg-yellow-100'
return 'text-red-600 bg-red-100'
}
const getScoreLabel = (score: number) => {
if (score >= 0.7) return 'Human'
if (score >= 0.4) return 'Unsicher'
return 'Bot'
}
return (
<AdminLayout title="PCA Platform" description="Bot-Erkennung & Monetarisierung verwalten">
{/* Loading State */}
{loading && (
<div className="flex items-center justify-center py-12">
<svg className="w-8 h-8 animate-spin text-primary-600" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
<span className="ml-3 text-slate-600">Lade PCA Status...</span>
</div>
)}
{/* Error State */}
{error && !loading && (
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center gap-3">
<span className="text-red-500 text-xl"></span>
<div>
<h4 className="font-medium text-red-800">{error}</h4>
<button onClick={fetchStatus} className="mt-2 text-sm text-red-700 underline hover:no-underline">
Erneut versuchen
</button>
</div>
</div>
</div>
)}
{/* Service Status Banner */}
{!loading && serviceStatus && (
<div className={`mb-6 rounded-lg p-4 ${
serviceStatus.status === 'healthy' ? 'bg-green-50 border border-green-200' :
serviceStatus.status === 'offline' ? 'bg-red-50 border border-red-200' :
'bg-yellow-50 border border-yellow-200'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className={`w-3 h-3 rounded-full ${
serviceStatus.status === 'healthy' ? 'bg-green-500' :
serviceStatus.status === 'offline' ? 'bg-red-500' : 'bg-yellow-500'
}`} />
<div>
<span className="font-medium">{serviceStatus.service}</span>
<span className="text-sm text-slate-500 ml-2">v{serviceStatus.version}</span>
</div>
</div>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
serviceStatus.status === 'healthy' ? 'bg-green-100 text-green-700' :
serviceStatus.status === 'offline' ? 'bg-red-100 text-red-700' : 'bg-yellow-100 text-yellow-700'
}`}>
{serviceStatus.status === 'healthy' ? 'Online' :
serviceStatus.status === 'offline' ? 'Offline' : 'Degraded'}
</span>
</div>
</div>
)}
{/* Tabs */}
{!loading && (
<div className="border-b border-slate-200 mb-6">
<nav className="flex gap-4">
{[
{ id: 'overview', label: 'Übersicht', icon: '📊' },
{ id: 'sessions', label: 'Sessions', icon: '👥' },
{ id: 'config', label: 'Konfiguration', icon: '⚙️' },
{ id: 'payments', label: 'Payments', icon: '💳' },
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as typeof activeTab)}
className={`px-4 py-3 font-medium border-b-2 transition-colors ${
activeTab === tab.id
? 'border-primary-500 text-primary-600'
: 'border-transparent text-slate-500 hover:text-slate-700'
}`}
>
<span className="mr-2">{tab.icon}</span>
{tab.label}
</button>
))}
</nav>
</div>
)}
{/* Overview Tab */}
{!loading && activeTab === 'overview' && (
<div className="space-y-6">
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-white rounded-lg border border-slate-200 p-4">
<div className="text-2xl font-bold text-slate-800">{stats.activeSessions}</div>
<div className="text-sm text-slate-500">Aktive Sessions</div>
</div>
<div className="bg-white rounded-lg border border-slate-200 p-4">
<div className="text-2xl font-bold text-green-600">{stats.humanScore}%</div>
<div className="text-sm text-slate-500">Human Score Avg</div>
</div>
<div className="bg-white rounded-lg border border-slate-200 p-4">
<div className="text-2xl font-bold text-yellow-600">{stats.challengesIssued}</div>
<div className="text-sm text-slate-500">Challenges ausgestellt</div>
</div>
<div className="bg-white rounded-lg border border-slate-200 p-4">
<div className="text-2xl font-bold text-primary-600">{stats.totalRevenue}</div>
<div className="text-sm text-slate-500">Umsatz (Bot-Zugriffe)</div>
</div>
</div>
{/* Quick Actions */}
<div className="bg-white rounded-lg border border-slate-200 p-6">
<h3 className="text-lg font-semibold mb-4">Quick Actions</h3>
<div className="flex flex-wrap gap-3">
<button className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
SDK herunterladen
</button>
<button className="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors">
Demo öffnen
</button>
<button className="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors">
Dokumentation
</button>
</div>
</div>
{/* Heuristic Weights Visualization */}
{config && (
<div className="bg-white rounded-lg border border-slate-200 p-6">
<h3 className="text-lg font-semibold mb-4">Heuristik-Gewichtung</h3>
<div className="space-y-3">
{[
{ key: 'dwell_ratio', label: 'Verweildauer', value: config.weights.dwell_ratio },
{ key: 'scroll_score', label: 'Scroll-Verhalten', value: config.weights.scroll_score },
{ key: 'pointer_variance', label: 'Maus-Bewegungen', value: config.weights.pointer_variance },
{ key: 'click_rate', label: 'Klick-Muster', value: config.weights.click_rate },
].map(metric => (
<div key={metric.key} className="flex items-center gap-4">
<div className="w-32 text-sm text-slate-600">{metric.label}</div>
<div className="flex-1 bg-slate-100 rounded-full h-4">
<div
className="bg-primary-500 h-4 rounded-full transition-all"
style={{ width: `${metric.value * 100}%` }}
/>
</div>
<div className="w-12 text-right text-sm font-medium">{(metric.value * 100).toFixed(0)}%</div>
</div>
))}
</div>
</div>
)}
</div>
)}
{/* Sessions Tab */}
{!loading && activeTab === 'sessions' && (
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
<div className="p-4 border-b border-slate-200 flex justify-between items-center">
<h3 className="font-semibold">Aktive Sessions</h3>
<button onClick={fetchSessions} className="text-sm text-primary-600 hover:underline">
Aktualisieren
</button>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-slate-50">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Session ID</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Score</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Verweildauer</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Scroll</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Klicks</th>
<th className="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase">Aktion</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-200">
{sessions.map(session => (
<tr key={session.session_id} className="hover:bg-slate-50">
<td className="px-4 py-3 font-mono text-sm">{session.session_id}</td>
<td className="px-4 py-3">
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getScoreColor(session.score)}`}>
{session.score.toFixed(2)} - {getScoreLabel(session.score)}
</span>
</td>
<td className="px-4 py-3 text-sm">{(session.dwell_ratio * 100).toFixed(0)}%</td>
<td className="px-4 py-3 text-sm">{session.scroll_depth}%</td>
<td className="px-4 py-3 text-sm">{session.clicks}</td>
<td className="px-4 py-3">
<span className={`px-2 py-1 rounded text-xs font-medium ${
session.action === 'allow' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'
}`}>
{session.action === 'allow' ? 'Erlaubt' : 'Challenge'}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Config Tab */}
{!loading && activeTab === 'config' && config && (
<div className="space-y-6">
{/* Thresholds */}
<div className="bg-white rounded-lg border border-slate-200 p-6">
<h3 className="text-lg font-semibold mb-4">Score-Schwellenwerte</h3>
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Pass Score (Human erkannt)
</label>
<input
type="number"
value={config.thresholds.score_pass}
step="0.1"
min="0"
max="1"
className="w-full px-3 py-2 border border-slate-300 rounded-lg"
readOnly
/>
<p className="mt-1 text-xs text-slate-500">Score ab dem ein Besucher als Mensch gilt</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
Challenge Score
</label>
<input
type="number"
value={config.thresholds.score_challenge}
step="0.1"
min="0"
max="1"
className="w-full px-3 py-2 border border-slate-300 rounded-lg"
readOnly
/>
<p className="mt-1 text-xs text-slate-500">Score unter dem Step-Up erforderlich ist</p>
</div>
</div>
</div>
{/* Step-Up Methods */}
<div className="bg-white rounded-lg border border-slate-200 p-6">
<h3 className="text-lg font-semibold mb-4">Step-Up Methoden</h3>
<div className="space-y-3">
{config.step_up.methods.map(method => (
<div key={method} className="flex items-center justify-between p-3 bg-slate-50 rounded-lg">
<div className="flex items-center gap-3">
<span className="text-xl">{method === 'webauthn' ? '🔐' : '⚡'}</span>
<div>
<div className="font-medium">{method === 'webauthn' ? 'WebAuthn' : 'Proof-of-Work'}</div>
<div className="text-sm text-slate-500">
{method === 'webauthn' ? 'Biometrische Authentifizierung' : 'Rechenintensive Challenge'}
</div>
</div>
</div>
{method === config.step_up.primary && (
<span className="px-2 py-1 bg-primary-100 text-primary-700 rounded text-xs font-medium">
Primär
</span>
)}
</div>
))}
</div>
</div>
{/* ai-access.json Preview */}
<div className="bg-white rounded-lg border border-slate-200 p-6">
<h3 className="text-lg font-semibold mb-4">ai-access.json</h3>
<pre className="bg-slate-900 text-slate-100 p-4 rounded-lg overflow-x-auto text-sm">
{`{
"thresholds": {
"score_pass": ${config.thresholds.score_pass},
"score_challenge": ${config.thresholds.score_challenge}
},
"weights": {
"dwell_ratio": ${config.weights.dwell_ratio},
"scroll_score": ${config.weights.scroll_score},
"pointer_variance": ${config.weights.pointer_variance},
"click_rate": ${config.weights.click_rate}
},
"step_up": {
"methods": ${JSON.stringify(config.step_up.methods)},
"primary": "${config.step_up.primary}"
},
"pca_roles": {
"Person": { "access": "allow", "price": null },
"Corporate": { "access": "allow", "price": null },
"Agent": { "access": "charge", "price": "0.001 EUR" }
}
}`}
</pre>
</div>
</div>
)}
{/* Payments Tab */}
{!loading && activeTab === 'payments' && (
<div className="space-y-6">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex items-center gap-3">
<span className="text-yellow-500 text-xl">🚧</span>
<div>
<h4 className="font-medium text-yellow-800">Payment Gateway in Entwicklung</h4>
<p className="text-sm text-yellow-700 mt-1">
HTTP 402 Payment Required und Stablecoin-Integration werden in einer zukünftigen Version verfügbar sein.
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg border border-slate-200 p-6">
<h3 className="text-lg font-semibold mb-4">Geplante Features</h3>
<ul className="space-y-3">
<li className="flex items-center gap-3">
<span className="w-6 h-6 rounded-full bg-slate-200 flex items-center justify-center text-xs">1</span>
<span>Micropayments per Request (Pay-per-Crawl)</span>
</li>
<li className="flex items-center gap-3">
<span className="w-6 h-6 rounded-full bg-slate-200 flex items-center justify-center text-xs">2</span>
<span>Stablecoin Support (USDC, EURC)</span>
</li>
<li className="flex items-center gap-3">
<span className="w-6 h-6 rounded-full bg-slate-200 flex items-center justify-center text-xs">3</span>
<span>Bitcoin Lightning Integration</span>
</li>
<li className="flex items-center gap-3">
<span className="w-6 h-6 rounded-full bg-slate-200 flex items-center justify-center text-xs">4</span>
<span>Agent Wallet Verwaltung</span>
</li>
</ul>
</div>
</div>
)}
</AdminLayout>
)
}