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>
92 lines
3.3 KiB
TypeScript
92 lines
3.3 KiB
TypeScript
import { CommunicationStats } from './types'
|
|
|
|
export function getStatusBadge(status: string) {
|
|
const baseClasses = 'px-3 py-1 rounded-full text-xs font-semibold uppercase'
|
|
switch (status) {
|
|
case 'online':
|
|
return `${baseClasses} bg-green-100 text-green-800`
|
|
case 'degraded':
|
|
return `${baseClasses} bg-yellow-100 text-yellow-800`
|
|
case 'offline':
|
|
return `${baseClasses} bg-red-100 text-red-800`
|
|
default:
|
|
return `${baseClasses} bg-slate-100 text-slate-600`
|
|
}
|
|
}
|
|
|
|
export function getRoomTypeBadge(type: string) {
|
|
const baseClasses = 'px-2 py-0.5 rounded text-xs font-medium'
|
|
switch (type) {
|
|
case 'class':
|
|
return `${baseClasses} bg-blue-100 text-blue-700`
|
|
case 'parent':
|
|
return `${baseClasses} bg-purple-100 text-purple-700`
|
|
case 'staff':
|
|
return `${baseClasses} bg-orange-100 text-orange-700`
|
|
default:
|
|
return `${baseClasses} bg-slate-100 text-slate-600`
|
|
}
|
|
}
|
|
|
|
export function formatDuration(minutes: number) {
|
|
if (minutes < 60) return `${Math.round(minutes)} Min.`
|
|
const hours = Math.floor(minutes / 60)
|
|
const mins = Math.round(minutes % 60)
|
|
return `${hours}h ${mins}m`
|
|
}
|
|
|
|
export function formatTimeAgo(dateStr: string) {
|
|
const date = new Date(dateStr)
|
|
const now = new Date()
|
|
const diffMs = now.getTime() - date.getTime()
|
|
const diffMins = Math.floor(diffMs / 60000)
|
|
|
|
if (diffMins < 1) return 'gerade eben'
|
|
if (diffMins < 60) return `vor ${diffMins} Min.`
|
|
if (diffMins < 1440) return `vor ${Math.floor(diffMins / 60)} Std.`
|
|
return `vor ${Math.floor(diffMins / 1440)} Tagen`
|
|
}
|
|
|
|
// Traffic estimation helpers for SysEleven planning
|
|
export function calculateEstimatedTraffic(stats: CommunicationStats | null, direction: 'in' | 'out'): number {
|
|
const messages = stats?.matrix?.messages_today || 0
|
|
const callMinutes = stats?.jitsi?.total_minutes_today || 0
|
|
const participants = stats?.jitsi?.total_participants || 0
|
|
|
|
// Estimates: ~2KB per message, ~1.5 Mbps per video participant
|
|
const messageTrafficMB = messages * 0.002
|
|
const videoTrafficMB = callMinutes * participants * 0.011 // ~660 KB/min per participant
|
|
|
|
if (direction === 'in') {
|
|
return messageTrafficMB * 0.3 + videoTrafficMB * 0.4
|
|
}
|
|
return messageTrafficMB * 0.7 + videoTrafficMB * 0.6
|
|
}
|
|
|
|
export function calculateHourlyEstimate(stats: CommunicationStats | null): number {
|
|
const activeParticipants = stats?.jitsi?.total_participants || 0
|
|
return activeParticipants * 0.675
|
|
}
|
|
|
|
export function calculateMonthlyEstimate(stats: CommunicationStats | null): number {
|
|
const dailyCallMinutes = stats?.jitsi?.total_minutes_today || 0
|
|
const avgParticipants = stats?.jitsi?.peak_concurrent_users || 1
|
|
const monthlyMinutes = dailyCallMinutes * 22
|
|
return (monthlyMinutes * avgParticipants * 11) / 1024
|
|
}
|
|
|
|
export function getResourceRecommendation(stats: CommunicationStats | null): string {
|
|
const peakUsers = stats?.jitsi?.peak_concurrent_users || 0
|
|
const monthlyGB = calculateMonthlyEstimate(stats)
|
|
|
|
if (monthlyGB < 10 || peakUsers < 5) {
|
|
return 'Starter (1 vCPU, 2GB RAM, 100GB Traffic)'
|
|
} else if (monthlyGB < 50 || peakUsers < 20) {
|
|
return 'Standard (2 vCPU, 4GB RAM, 500GB Traffic)'
|
|
} else if (monthlyGB < 200 || peakUsers < 50) {
|
|
return 'Professional (4 vCPU, 8GB RAM, 2TB Traffic)'
|
|
} else {
|
|
return 'Enterprise (8+ vCPU, 16GB+ RAM, Unlimited Traffic)'
|
|
}
|
|
}
|