website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97 lines
4.6 KiB
TypeScript
97 lines
4.6 KiB
TypeScript
import { CommunicationStats } from './types'
|
|
import { getStatusBadge, formatDuration } from './helpers'
|
|
|
|
export function MatrixCard({ stats }: { stats: CommunicationStats | null }) {
|
|
return (
|
|
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-slate-900">Matrix (Synapse)</h3>
|
|
<p className="text-sm text-slate-500">E2EE Messaging</p>
|
|
</div>
|
|
</div>
|
|
<span className={getStatusBadge(stats?.matrix.status || 'offline')}>
|
|
{stats?.matrix.status || 'offline'}
|
|
</span>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<div className="text-2xl font-bold text-slate-900">{stats?.matrix.total_users || 0}</div>
|
|
<div className="text-xs text-slate-500">Benutzer</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-2xl font-bold text-slate-900">{stats?.matrix.active_users || 0}</div>
|
|
<div className="text-xs text-slate-500">Aktiv</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-2xl font-bold text-slate-900">{stats?.matrix.total_rooms || 0}</div>
|
|
<div className="text-xs text-slate-500">Räume</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 pt-4 border-t border-slate-100">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Nachrichten heute</span>
|
|
<span className="font-medium">{stats?.matrix.messages_today || 0}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm mt-1">
|
|
<span className="text-slate-500">Diese Woche</span>
|
|
<span className="font-medium">{stats?.matrix.messages_this_week || 0}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function JitsiCard({ stats }: { stats: CommunicationStats | null }) {
|
|
return (
|
|
<div className="bg-white rounded-xl border border-slate-200 p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
|
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 className="font-semibold text-slate-900">Jitsi Meet</h3>
|
|
<p className="text-sm text-slate-500">Videokonferenzen</p>
|
|
</div>
|
|
</div>
|
|
<span className={getStatusBadge(stats?.jitsi.status || 'offline')}>
|
|
{stats?.jitsi.status || 'offline'}
|
|
</span>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<div className="text-2xl font-bold text-green-600">{stats?.jitsi.active_meetings || 0}</div>
|
|
<div className="text-xs text-slate-500">Live Calls</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-2xl font-bold text-slate-900">{stats?.jitsi.total_participants || 0}</div>
|
|
<div className="text-xs text-slate-500">Teilnehmer</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-2xl font-bold text-slate-900">{stats?.jitsi.meetings_today || 0}</div>
|
|
<div className="text-xs text-slate-500">Calls heute</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 pt-4 border-t border-slate-100">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Ø Dauer</span>
|
|
<span className="font-medium">{formatDuration(stats?.jitsi.average_duration_minutes || 0)}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm mt-1">
|
|
<span className="text-slate-500">Peak gleichzeitig</span>
|
|
<span className="font-medium">{stats?.jitsi.peak_concurrent_users || 0} Nutzer</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|