snikhilesh's picture
Deploy backend with monitoring infrastructure - Complete Medical AI Platform
13d5ab4 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Medical AI Platform - Admin Dashboard</title>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@keyframes spin {
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
</style>
</head>
<body class="bg-gray-50">
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
// Lucide icons as SVG components
const Activity = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
</svg>
);
const AlertCircle = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
);
const BarChart3 = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 3v18h18"></path>
<path d="M18 17V9"></path>
<path d="M13 17V5"></path>
<path d="M8 17v-3"></path>
</svg>
);
const Database = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
</svg>
);
const Clock = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
);
const Shield = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
</svg>
);
const TrendingUp = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
<polyline points="17 6 23 6 23 12"></polyline>
</svg>
);
const GitBranch = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="6" y1="3" x2="6" y2="15"></line>
<circle cx="18" cy="6" r="3"></circle>
<circle cx="6" cy="18" r="3"></circle>
<path d="M18 9a9 9 0 0 1-9 9"></path>
</svg>
);
const AdminDashboard = () => {
const [dashboardData, setDashboardData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [activeTab, setActiveTab] = useState('overview');
const [autoRefresh, setAutoRefresh] = useState(true);
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.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchDashboard();
if (autoRefresh) {
const interval = setInterval(fetchDashboard, 10000);
return () => clearInterval(interval);
}
}, [autoRefresh]);
const StatusBadge = ({ 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()] || 'bg-gray-100 text-gray-800';
return <span className={`px-2 py-1 rounded-full text-xs font-medium ${color}`}>{status?.toUpperCase()}</span>;
};
const MetricCard = ({ 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>
);
};
const resolveAlert = async (alertId) => {
try {
await fetch(`/admin/alerts/${alertId}/resolve`, { method: 'POST' });
fetchDashboard();
} catch (err) {
console.error('Failed to resolve alert:', err);
}
};
const clearCache = async () => {
if (!confirm('Clear all cache entries? This may temporarily impact performance.')) return;
try {
await fetch('/admin/cache/clear', { method: 'POST' });
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 />
<p className="text-gray-600 mt-4">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">
<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>
<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)}
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>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
{activeTab === 'overview' && (
<div className="space-y-6">
<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 />} 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 />} status={dashboardData.system.error_rate > dashboardData.system.error_threshold ? 'critical' : 'good'} />
<MetricCard title="Total Requests" value={dashboardData.system.total_requests.toLocaleString()} icon={<Activity />} />
<MetricCard title="Active Alerts" value={dashboardData.alerts.active_count} subtitle={`${dashboardData.alerts.critical_count} critical`} icon={<AlertCircle />} status={dashboardData.alerts.critical_count > 0 ? 'critical' : dashboardData.alerts.active_count > 0 ? 'warning' : 'good'} />
</div>
</div>
<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 />} />
<MetricCard title="Completed" value={dashboardData.pipeline.completed_jobs.toLocaleString()} icon={<TrendingUp />} status="good" />
<MetricCard title="Failed" value={dashboardData.pipeline.failed_jobs.toLocaleString()} icon={<AlertCircle />} status={dashboardData.pipeline.failed_jobs > 0 ? 'warning' : 'good'} />
<MetricCard title="Success Rate" value={`${(dashboardData.pipeline.success_rate * 100).toFixed(1)}%`} icon={<Activity />} status={dashboardData.pipeline.success_rate > 0.95 ? 'good' : dashboardData.pipeline.success_rate > 0.85 ? 'warning' : 'critical'} />
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<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 />
</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>
<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 />
</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>
<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 />
</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>
)}
{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-x-auto">
<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>
)}
{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 />} />
<MetricCard title="Hit Rate" value={`${(dashboardData.cache.hit_rate * 100).toFixed(1)}%`} subtitle={`${dashboardData.cache.hits} hits, ${dashboardData.cache.misses} misses`} icon={<Activity />} 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 />} />
</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>
)}
{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 />
<p className="text-gray-500 mt-4">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>
</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>
);
};
ReactDOM.render(<AdminDashboard />, document.getElementById('root'));
</script>
</body>
</html>