Spaces:
Sleeping
Sleeping
| /** | |
| * Admin Dashboard Component | |
| * Comprehensive monitoring and management interface for Medical AI Platform | |
| * | |
| * Features: | |
| * - Real-time system health monitoring | |
| * - Pipeline statistics and performance metrics | |
| * - Model performance tracking | |
| * - Cache management | |
| * - Alert management | |
| * - Review queue management | |
| * - Compliance overview | |
| */ | |
| import React, { useState, useEffect } from 'react'; | |
| import { Activity, AlertCircle, BarChart3, Database, GitBranch, Shield, Clock, TrendingUp } from 'lucide-react'; | |
| interface DashboardData { | |
| status: string; | |
| timestamp: string; | |
| system: { | |
| uptime_seconds: number; | |
| uptime_human: string; | |
| error_rate: number; | |
| total_requests: number; | |
| error_threshold: number; | |
| status: string; | |
| }; | |
| pipeline: { | |
| total_jobs_processed: number; | |
| completed_jobs: number; | |
| failed_jobs: number; | |
| processing_jobs: number; | |
| success_rate: number; | |
| }; | |
| models: { | |
| total_registered: number; | |
| performance: Record<string, ModelPerformance>; | |
| }; | |
| synthesis: { | |
| total_syntheses: number; | |
| avg_confidence: number; | |
| requiring_review: number; | |
| avg_processing_time_ms: number; | |
| }; | |
| cache: { | |
| total_entries: number; | |
| hits: number; | |
| misses: number; | |
| hit_rate: number; | |
| evictions: number; | |
| memory_usage_mb: number; | |
| avg_retrieval_time_ms: number; | |
| cache_efficiency: number; | |
| }; | |
| alerts: { | |
| active_count: number; | |
| critical_count: number; | |
| recent: Alert[]; | |
| }; | |
| compliance: { | |
| hipaa_compliant: boolean; | |
| gdpr_compliant: boolean; | |
| audit_logging_active: boolean; | |
| phi_removal_active: boolean; | |
| encryption_enabled: boolean; | |
| }; | |
| components: Record<string, string>; | |
| } | |
| interface ModelPerformance { | |
| version: string; | |
| total_inferences: number; | |
| avg_latency_ms: number; | |
| error_rate: number; | |
| last_used: string; | |
| } | |
| interface Alert { | |
| alert_id: string; | |
| level: string; | |
| message: string; | |
| category: string; | |
| timestamp: string; | |
| resolved: boolean; | |
| details: Record<string, any>; | |
| } | |
| const AdminDashboard: React.FC = () => { | |
| const [dashboardData, setDashboardData] = useState<DashboardData | null>(null); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const [activeTab, setActiveTab] = useState<'overview' | 'models' | 'cache' | 'alerts'>('overview'); | |
| const [autoRefresh, setAutoRefresh] = useState(true); | |
| // Fetch dashboard data | |
| const fetchDashboard = async () => { | |
| try { | |
| const response = await fetch('/health/dashboard'); | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch dashboard data'); | |
| } | |
| const data = await response.json(); | |
| setDashboardData(data); | |
| setError(null); | |
| } catch (err) { | |
| setError(err instanceof Error ? err.message : 'Unknown error'); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| // Initial load and auto-refresh | |
| useEffect(() => { | |
| fetchDashboard(); | |
| if (autoRefresh) { | |
| const interval = setInterval(fetchDashboard, 10000); // Refresh every 10 seconds | |
| return () => clearInterval(interval); | |
| } | |
| }, [autoRefresh]); | |
| // Status badge component | |
| const StatusBadge: React.FC<{ status: string }> = ({ status }) => { | |
| const colors = { | |
| operational: 'bg-green-100 text-green-800', | |
| healthy: 'bg-green-100 text-green-800', | |
| degraded: 'bg-yellow-100 text-yellow-800', | |
| critical: 'bg-red-100 text-red-800', | |
| ready: 'bg-blue-100 text-blue-800', | |
| active: 'bg-blue-100 text-blue-800' | |
| }; | |
| const color = colors[status.toLowerCase() as keyof typeof colors] || 'bg-gray-100 text-gray-800'; | |
| return ( | |
| <span className={`px-2 py-1 rounded-full text-xs font-medium ${color}`}> | |
| {status.toUpperCase()} | |
| </span> | |
| ); | |
| }; | |
| // Metric card component | |
| const MetricCard: React.FC<{ | |
| title: string; | |
| value: string | number; | |
| subtitle?: string; | |
| icon: React.ReactNode; | |
| status?: 'good' | 'warning' | 'critical'; | |
| }> = ({ title, value, subtitle, icon, status }) => { | |
| const statusColors = { | |
| good: 'border-green-200 bg-green-50', | |
| warning: 'border-yellow-200 bg-yellow-50', | |
| critical: 'border-red-200 bg-red-50' | |
| }; | |
| const borderColor = status ? statusColors[status] : 'border-gray-200 bg-white'; | |
| return ( | |
| <div className={`p-4 rounded-lg border-2 ${borderColor}`}> | |
| <div className="flex items-center justify-between mb-2"> | |
| <span className="text-sm font-medium text-gray-600">{title}</span> | |
| <div className="text-gray-400">{icon}</div> | |
| </div> | |
| <div className="text-2xl font-bold text-gray-900">{value}</div> | |
| {subtitle && <div className="text-xs text-gray-500 mt-1">{subtitle}</div>} | |
| </div> | |
| ); | |
| }; | |
| // Resolve alert | |
| const resolveAlert = async (alertId: string) => { | |
| try { | |
| const response = await fetch(`/admin/alerts/${alertId}/resolve`, { | |
| method: 'POST' | |
| }); | |
| if (response.ok) { | |
| fetchDashboard(); // Refresh data | |
| } | |
| } catch (err) { | |
| console.error('Failed to resolve alert:', err); | |
| } | |
| }; | |
| // Clear cache | |
| const clearCache = async () => { | |
| if (!confirm('Are you sure you want to clear all cache entries? This may temporarily impact performance.')) { | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/admin/cache/clear', { | |
| method: 'POST' | |
| }); | |
| if (response.ok) { | |
| alert('Cache cleared successfully'); | |
| fetchDashboard(); | |
| } | |
| } catch (err) { | |
| alert('Failed to clear cache'); | |
| } | |
| }; | |
| if (loading) { | |
| return ( | |
| <div className="flex items-center justify-center min-h-screen"> | |
| <div className="text-center"> | |
| <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> | |
| <p className="text-gray-600">Loading dashboard...</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (error) { | |
| return ( | |
| <div className="flex items-center justify-center min-h-screen"> | |
| <div className="text-center"> | |
| <AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-4" /> | |
| <p className="text-gray-600">Error: {error}</p> | |
| <button | |
| onClick={fetchDashboard} | |
| className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700" | |
| > | |
| Retry | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (!dashboardData) { | |
| return null; | |
| } | |
| return ( | |
| <div className="min-h-screen bg-gray-50"> | |
| {/* Header */} | |
| <div className="bg-white border-b border-gray-200"> | |
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <h1 className="text-2xl font-bold text-gray-900">Medical AI Platform - Admin Dashboard</h1> | |
| <p className="text-sm text-gray-500 mt-1"> | |
| Real-time monitoring and system management | |
| </p> | |
| </div> | |
| <div className="flex items-center space-x-4"> | |
| <StatusBadge status={dashboardData.status} /> | |
| <label className="flex items-center space-x-2 text-sm text-gray-600"> | |
| <input | |
| type="checkbox" | |
| checked={autoRefresh} | |
| onChange={(e) => setAutoRefresh(e.target.checked)} | |
| className="rounded" | |
| /> | |
| <span>Auto-refresh</span> | |
| </label> | |
| <button | |
| onClick={fetchDashboard} | |
| className="px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm" | |
| > | |
| Refresh Now | |
| </button> | |
| </div> | |
| </div> | |
| {/* Tabs */} | |
| <div className="mt-4 flex space-x-4 border-b border-gray-200"> | |
| {['overview', 'models', 'cache', 'alerts'].map((tab) => ( | |
| <button | |
| key={tab} | |
| onClick={() => setActiveTab(tab as any)} | |
| className={`px-4 py-2 font-medium text-sm border-b-2 transition-colors ${ | |
| activeTab === tab | |
| ? 'border-blue-600 text-blue-600' | |
| : 'border-transparent text-gray-500 hover:text-gray-700' | |
| }`} | |
| > | |
| {tab.charAt(0).toUpperCase() + tab.slice(1)} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Content */} | |
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> | |
| {/* Overview Tab */} | |
| {activeTab === 'overview' && ( | |
| <div className="space-y-6"> | |
| {/* System Status Grid */} | |
| <div> | |
| <h2 className="text-lg font-semibold text-gray-900 mb-4">System Status</h2> | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| <MetricCard | |
| title="Uptime" | |
| value={dashboardData.system.uptime_human} | |
| subtitle={`${dashboardData.system.uptime_seconds.toLocaleString()}s`} | |
| icon={<Clock className="h-5 w-5" />} | |
| status="good" | |
| /> | |
| <MetricCard | |
| title="Error Rate" | |
| value={`${(dashboardData.system.error_rate * 100).toFixed(2)}%`} | |
| subtitle={`Threshold: ${(dashboardData.system.error_threshold * 100).toFixed(0)}%`} | |
| icon={<AlertCircle className="h-5 w-5" />} | |
| status={dashboardData.system.error_rate > dashboardData.system.error_threshold ? 'critical' : 'good'} | |
| /> | |
| <MetricCard | |
| title="Total Requests" | |
| value={dashboardData.system.total_requests.toLocaleString()} | |
| icon={<Activity className="h-5 w-5" />} | |
| /> | |
| <MetricCard | |
| title="Active Alerts" | |
| value={dashboardData.alerts.active_count} | |
| subtitle={`${dashboardData.alerts.critical_count} critical`} | |
| icon={<AlertCircle className="h-5 w-5" />} | |
| status={dashboardData.alerts.critical_count > 0 ? 'critical' : dashboardData.alerts.active_count > 0 ? 'warning' : 'good'} | |
| /> | |
| </div> | |
| </div> | |
| {/* Pipeline Statistics */} | |
| <div> | |
| <h2 className="text-lg font-semibold text-gray-900 mb-4">Pipeline Statistics</h2> | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| <MetricCard | |
| title="Total Jobs" | |
| value={dashboardData.pipeline.total_jobs_processed.toLocaleString()} | |
| icon={<BarChart3 className="h-5 w-5" />} | |
| /> | |
| <MetricCard | |
| title="Completed" | |
| value={dashboardData.pipeline.completed_jobs.toLocaleString()} | |
| icon={<TrendingUp className="h-5 w-5" />} | |
| status="good" | |
| /> | |
| <MetricCard | |
| title="Failed" | |
| value={dashboardData.pipeline.failed_jobs.toLocaleString()} | |
| icon={<AlertCircle className="h-5 w-5" />} | |
| status={dashboardData.pipeline.failed_jobs > 0 ? 'warning' : 'good'} | |
| /> | |
| <MetricCard | |
| title="Success Rate" | |
| value={`${(dashboardData.pipeline.success_rate * 100).toFixed(1)}%`} | |
| icon={<Activity className="h-5 w-5" />} | |
| status={dashboardData.pipeline.success_rate > 0.95 ? 'good' : dashboardData.pipeline.success_rate > 0.85 ? 'warning' : 'critical'} | |
| /> | |
| </div> | |
| </div> | |
| {/* Cache & Synthesis */} | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| {/* Cache Statistics */} | |
| <div className="bg-white p-6 rounded-lg border border-gray-200"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h3 className="text-lg font-semibold text-gray-900">Cache Performance</h3> | |
| <Database className="h-5 w-5 text-gray-400" /> | |
| </div> | |
| <div className="space-y-3"> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Hit Rate</span> | |
| <span className="text-sm font-medium">{(dashboardData.cache.hit_rate * 100).toFixed(1)}%</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Entries</span> | |
| <span className="text-sm font-medium">{dashboardData.cache.total_entries.toLocaleString()}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Memory Usage</span> | |
| <span className="text-sm font-medium">{dashboardData.cache.memory_usage_mb.toFixed(1)} MB</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Avg Retrieval</span> | |
| <span className="text-sm font-medium">{dashboardData.cache.avg_retrieval_time_ms.toFixed(2)} ms</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Synthesis Statistics */} | |
| <div className="bg-white p-6 rounded-lg border border-gray-200"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h3 className="text-lg font-semibold text-gray-900">Clinical Synthesis</h3> | |
| <GitBranch className="h-5 w-5 text-gray-400" /> | |
| </div> | |
| <div className="space-y-3"> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Total Syntheses</span> | |
| <span className="text-sm font-medium">{dashboardData.synthesis.total_syntheses.toLocaleString()}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Avg Confidence</span> | |
| <span className="text-sm font-medium">{(dashboardData.synthesis.avg_confidence * 100).toFixed(1)}%</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Requiring Review</span> | |
| <span className="text-sm font-medium">{dashboardData.synthesis.requiring_review.toLocaleString()}</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-sm text-gray-600">Avg Processing</span> | |
| <span className="text-sm font-medium">{dashboardData.synthesis.avg_processing_time_ms.toFixed(0)} ms</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Compliance Status */} | |
| <div className="bg-white p-6 rounded-lg border border-gray-200"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h3 className="text-lg font-semibold text-gray-900">Compliance Status</h3> | |
| <Shield className="h-5 w-5 text-gray-400" /> | |
| </div> | |
| <div className="grid grid-cols-2 md:grid-cols-5 gap-4"> | |
| {Object.entries(dashboardData.compliance).map(([key, value]) => ( | |
| <div key={key} className="text-center"> | |
| <div className={`text-2xl mb-1 ${value ? 'text-green-600' : 'text-red-600'}`}> | |
| {value ? '✓' : '✗'} | |
| </div> | |
| <div className="text-xs text-gray-600"> | |
| {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* Models Tab */} | |
| {activeTab === 'models' && ( | |
| <div> | |
| <h2 className="text-lg font-semibold text-gray-900 mb-4">Model Performance</h2> | |
| <div className="bg-white rounded-lg border border-gray-200 overflow-hidden"> | |
| <table className="min-w-full divide-y divide-gray-200"> | |
| <thead className="bg-gray-50"> | |
| <tr> | |
| <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Model ID</th> | |
| <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Version</th> | |
| <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Inferences</th> | |
| <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Avg Latency</th> | |
| <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Error Rate</th> | |
| <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Last Used</th> | |
| </tr> | |
| </thead> | |
| <tbody className="bg-white divide-y divide-gray-200"> | |
| {Object.entries(dashboardData.models.performance).map(([modelId, perf]) => ( | |
| <tr key={modelId}> | |
| <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{modelId}</td> | |
| <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.version}</td> | |
| <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.total_inferences.toLocaleString()}</td> | |
| <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.avg_latency_ms.toFixed(1)} ms</td> | |
| <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{(perf.error_rate * 100).toFixed(2)}%</td> | |
| <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.last_used}</td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| )} | |
| {/* Cache Tab */} | |
| {activeTab === 'cache' && ( | |
| <div> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h2 className="text-lg font-semibold text-gray-900">Cache Management</h2> | |
| <button | |
| onClick={clearCache} | |
| className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm" | |
| > | |
| Clear Cache | |
| </button> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> | |
| <MetricCard | |
| title="Cache Entries" | |
| value={dashboardData.cache.total_entries.toLocaleString()} | |
| icon={<Database className="h-5 w-5" />} | |
| /> | |
| <MetricCard | |
| title="Hit Rate" | |
| value={`${(dashboardData.cache.hit_rate * 100).toFixed(1)}%`} | |
| subtitle={`${dashboardData.cache.hits} hits, ${dashboardData.cache.misses} misses`} | |
| icon={<Activity className="h-5 w-5" />} | |
| status={dashboardData.cache.hit_rate > 0.7 ? 'good' : dashboardData.cache.hit_rate > 0.4 ? 'warning' : 'critical'} | |
| /> | |
| <MetricCard | |
| title="Memory Usage" | |
| value={`${dashboardData.cache.memory_usage_mb.toFixed(1)} MB`} | |
| subtitle={`${dashboardData.cache.evictions} evictions`} | |
| icon={<BarChart3 className="h-5 w-5" />} | |
| /> | |
| </div> | |
| <div className="bg-white p-6 rounded-lg border border-gray-200"> | |
| <h3 className="text-md font-semibold text-gray-900 mb-4">Cache Performance Analysis</h3> | |
| <div className="space-y-2"> | |
| {dashboardData.cache.hit_rate < 0.5 && ( | |
| <div className="p-3 bg-yellow-50 border border-yellow-200 rounded"> | |
| <p className="text-sm text-yellow-800"> | |
| ⚠ Low cache hit rate ({(dashboardData.cache.hit_rate * 100).toFixed(1)}%). | |
| Consider increasing cache size or TTL. | |
| </p> | |
| </div> | |
| )} | |
| {dashboardData.cache.hit_rate >= 0.8 && ( | |
| <div className="p-3 bg-green-50 border border-green-200 rounded"> | |
| <p className="text-sm text-green-800"> | |
| ✓ Excellent cache hit rate ({(dashboardData.cache.hit_rate * 100).toFixed(1)}%). | |
| Cache performing optimally. | |
| </p> | |
| </div> | |
| )} | |
| <div className="p-3 bg-blue-50 border border-blue-200 rounded"> | |
| <p className="text-sm text-blue-800"> | |
| Cache efficiency: {dashboardData.cache.cache_efficiency.toFixed(1)}% | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* Alerts Tab */} | |
| {activeTab === 'alerts' && ( | |
| <div> | |
| <h2 className="text-lg font-semibold text-gray-900 mb-4"> | |
| Active Alerts ({dashboardData.alerts.active_count}) | |
| </h2> | |
| {dashboardData.alerts.recent.length === 0 ? ( | |
| <div className="bg-white p-12 rounded-lg border border-gray-200 text-center"> | |
| <AlertCircle className="h-12 w-12 text-gray-300 mx-auto mb-4" /> | |
| <p className="text-gray-500">No active alerts</p> | |
| </div> | |
| ) : ( | |
| <div className="space-y-3"> | |
| {dashboardData.alerts.recent.map((alert) => ( | |
| <div | |
| key={alert.alert_id} | |
| className={`p-4 rounded-lg border-2 ${ | |
| alert.level === 'critical' | |
| ? 'border-red-200 bg-red-50' | |
| : alert.level === 'error' | |
| ? 'border-orange-200 bg-orange-50' | |
| : alert.level === 'warning' | |
| ? 'border-yellow-200 bg-yellow-50' | |
| : 'border-blue-200 bg-blue-50' | |
| }`} | |
| > | |
| <div className="flex items-start justify-between"> | |
| <div className="flex-1"> | |
| <div className="flex items-center space-x-2 mb-2"> | |
| <span className={`px-2 py-1 rounded text-xs font-medium ${ | |
| alert.level === 'critical' | |
| ? 'bg-red-200 text-red-800' | |
| : alert.level === 'error' | |
| ? 'bg-orange-200 text-orange-800' | |
| : alert.level === 'warning' | |
| ? 'bg-yellow-200 text-yellow-800' | |
| : 'bg-blue-200 text-blue-800' | |
| }`}> | |
| {alert.level.toUpperCase()} | |
| </span> | |
| <span className="text-xs text-gray-500">{alert.category}</span> | |
| <span className="text-xs text-gray-400"> | |
| {new Date(alert.timestamp).toLocaleString()} | |
| </span> | |
| </div> | |
| <p className="text-sm font-medium text-gray-900">{alert.message}</p> | |
| {Object.keys(alert.details).length > 0 && ( | |
| <pre className="mt-2 text-xs text-gray-600 bg-white/50 p-2 rounded"> | |
| {JSON.stringify(alert.details, null, 2)} | |
| </pre> | |
| )} | |
| </div> | |
| {!alert.resolved && ( | |
| <button | |
| onClick={() => resolveAlert(alert.alert_id)} | |
| className="ml-4 px-3 py-1 bg-white border border-gray-300 text-gray-700 rounded hover:bg-gray-50 text-sm" | |
| > | |
| Resolve | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default AdminDashboard; | |