medical-report-analyzer / AdminDashboard.tsx
snikhilesh's picture
Upload enhanced frontend components
f5c6b5e verified
/**
* 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;