snikhilesh commited on
Commit
f5c6b5e
·
verified ·
1 Parent(s): 13d5ab4

Upload enhanced frontend components

Browse files
Files changed (1) hide show
  1. AdminDashboard.tsx +602 -0
AdminDashboard.tsx ADDED
@@ -0,0 +1,602 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Admin Dashboard Component
3
+ * Comprehensive monitoring and management interface for Medical AI Platform
4
+ *
5
+ * Features:
6
+ * - Real-time system health monitoring
7
+ * - Pipeline statistics and performance metrics
8
+ * - Model performance tracking
9
+ * - Cache management
10
+ * - Alert management
11
+ * - Review queue management
12
+ * - Compliance overview
13
+ */
14
+
15
+ import React, { useState, useEffect } from 'react';
16
+ import { Activity, AlertCircle, BarChart3, Database, GitBranch, Shield, Clock, TrendingUp } from 'lucide-react';
17
+
18
+ interface DashboardData {
19
+ status: string;
20
+ timestamp: string;
21
+ system: {
22
+ uptime_seconds: number;
23
+ uptime_human: string;
24
+ error_rate: number;
25
+ total_requests: number;
26
+ error_threshold: number;
27
+ status: string;
28
+ };
29
+ pipeline: {
30
+ total_jobs_processed: number;
31
+ completed_jobs: number;
32
+ failed_jobs: number;
33
+ processing_jobs: number;
34
+ success_rate: number;
35
+ };
36
+ models: {
37
+ total_registered: number;
38
+ performance: Record<string, ModelPerformance>;
39
+ };
40
+ synthesis: {
41
+ total_syntheses: number;
42
+ avg_confidence: number;
43
+ requiring_review: number;
44
+ avg_processing_time_ms: number;
45
+ };
46
+ cache: {
47
+ total_entries: number;
48
+ hits: number;
49
+ misses: number;
50
+ hit_rate: number;
51
+ evictions: number;
52
+ memory_usage_mb: number;
53
+ avg_retrieval_time_ms: number;
54
+ cache_efficiency: number;
55
+ };
56
+ alerts: {
57
+ active_count: number;
58
+ critical_count: number;
59
+ recent: Alert[];
60
+ };
61
+ compliance: {
62
+ hipaa_compliant: boolean;
63
+ gdpr_compliant: boolean;
64
+ audit_logging_active: boolean;
65
+ phi_removal_active: boolean;
66
+ encryption_enabled: boolean;
67
+ };
68
+ components: Record<string, string>;
69
+ }
70
+
71
+ interface ModelPerformance {
72
+ version: string;
73
+ total_inferences: number;
74
+ avg_latency_ms: number;
75
+ error_rate: number;
76
+ last_used: string;
77
+ }
78
+
79
+ interface Alert {
80
+ alert_id: string;
81
+ level: string;
82
+ message: string;
83
+ category: string;
84
+ timestamp: string;
85
+ resolved: boolean;
86
+ details: Record<string, any>;
87
+ }
88
+
89
+ const AdminDashboard: React.FC = () => {
90
+ const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
91
+ const [loading, setLoading] = useState(true);
92
+ const [error, setError] = useState<string | null>(null);
93
+ const [activeTab, setActiveTab] = useState<'overview' | 'models' | 'cache' | 'alerts'>('overview');
94
+ const [autoRefresh, setAutoRefresh] = useState(true);
95
+
96
+ // Fetch dashboard data
97
+ const fetchDashboard = async () => {
98
+ try {
99
+ const response = await fetch('/health/dashboard');
100
+ if (!response.ok) {
101
+ throw new Error('Failed to fetch dashboard data');
102
+ }
103
+ const data = await response.json();
104
+ setDashboardData(data);
105
+ setError(null);
106
+ } catch (err) {
107
+ setError(err instanceof Error ? err.message : 'Unknown error');
108
+ } finally {
109
+ setLoading(false);
110
+ }
111
+ };
112
+
113
+ // Initial load and auto-refresh
114
+ useEffect(() => {
115
+ fetchDashboard();
116
+
117
+ if (autoRefresh) {
118
+ const interval = setInterval(fetchDashboard, 10000); // Refresh every 10 seconds
119
+ return () => clearInterval(interval);
120
+ }
121
+ }, [autoRefresh]);
122
+
123
+ // Status badge component
124
+ const StatusBadge: React.FC<{ status: string }> = ({ status }) => {
125
+ const colors = {
126
+ operational: 'bg-green-100 text-green-800',
127
+ healthy: 'bg-green-100 text-green-800',
128
+ degraded: 'bg-yellow-100 text-yellow-800',
129
+ critical: 'bg-red-100 text-red-800',
130
+ ready: 'bg-blue-100 text-blue-800',
131
+ active: 'bg-blue-100 text-blue-800'
132
+ };
133
+
134
+ const color = colors[status.toLowerCase() as keyof typeof colors] || 'bg-gray-100 text-gray-800';
135
+
136
+ return (
137
+ <span className={`px-2 py-1 rounded-full text-xs font-medium ${color}`}>
138
+ {status.toUpperCase()}
139
+ </span>
140
+ );
141
+ };
142
+
143
+ // Metric card component
144
+ const MetricCard: React.FC<{
145
+ title: string;
146
+ value: string | number;
147
+ subtitle?: string;
148
+ icon: React.ReactNode;
149
+ status?: 'good' | 'warning' | 'critical';
150
+ }> = ({ title, value, subtitle, icon, status }) => {
151
+ const statusColors = {
152
+ good: 'border-green-200 bg-green-50',
153
+ warning: 'border-yellow-200 bg-yellow-50',
154
+ critical: 'border-red-200 bg-red-50'
155
+ };
156
+
157
+ const borderColor = status ? statusColors[status] : 'border-gray-200 bg-white';
158
+
159
+ return (
160
+ <div className={`p-4 rounded-lg border-2 ${borderColor}`}>
161
+ <div className="flex items-center justify-between mb-2">
162
+ <span className="text-sm font-medium text-gray-600">{title}</span>
163
+ <div className="text-gray-400">{icon}</div>
164
+ </div>
165
+ <div className="text-2xl font-bold text-gray-900">{value}</div>
166
+ {subtitle && <div className="text-xs text-gray-500 mt-1">{subtitle}</div>}
167
+ </div>
168
+ );
169
+ };
170
+
171
+ // Resolve alert
172
+ const resolveAlert = async (alertId: string) => {
173
+ try {
174
+ const response = await fetch(`/admin/alerts/${alertId}/resolve`, {
175
+ method: 'POST'
176
+ });
177
+
178
+ if (response.ok) {
179
+ fetchDashboard(); // Refresh data
180
+ }
181
+ } catch (err) {
182
+ console.error('Failed to resolve alert:', err);
183
+ }
184
+ };
185
+
186
+ // Clear cache
187
+ const clearCache = async () => {
188
+ if (!confirm('Are you sure you want to clear all cache entries? This may temporarily impact performance.')) {
189
+ return;
190
+ }
191
+
192
+ try {
193
+ const response = await fetch('/admin/cache/clear', {
194
+ method: 'POST'
195
+ });
196
+
197
+ if (response.ok) {
198
+ alert('Cache cleared successfully');
199
+ fetchDashboard();
200
+ }
201
+ } catch (err) {
202
+ alert('Failed to clear cache');
203
+ }
204
+ };
205
+
206
+ if (loading) {
207
+ return (
208
+ <div className="flex items-center justify-center min-h-screen">
209
+ <div className="text-center">
210
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
211
+ <p className="text-gray-600">Loading dashboard...</p>
212
+ </div>
213
+ </div>
214
+ );
215
+ }
216
+
217
+ if (error) {
218
+ return (
219
+ <div className="flex items-center justify-center min-h-screen">
220
+ <div className="text-center">
221
+ <AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-4" />
222
+ <p className="text-gray-600">Error: {error}</p>
223
+ <button
224
+ onClick={fetchDashboard}
225
+ className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
226
+ >
227
+ Retry
228
+ </button>
229
+ </div>
230
+ </div>
231
+ );
232
+ }
233
+
234
+ if (!dashboardData) {
235
+ return null;
236
+ }
237
+
238
+ return (
239
+ <div className="min-h-screen bg-gray-50">
240
+ {/* Header */}
241
+ <div className="bg-white border-b border-gray-200">
242
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
243
+ <div className="flex items-center justify-between">
244
+ <div>
245
+ <h1 className="text-2xl font-bold text-gray-900">Medical AI Platform - Admin Dashboard</h1>
246
+ <p className="text-sm text-gray-500 mt-1">
247
+ Real-time monitoring and system management
248
+ </p>
249
+ </div>
250
+ <div className="flex items-center space-x-4">
251
+ <StatusBadge status={dashboardData.status} />
252
+ <label className="flex items-center space-x-2 text-sm text-gray-600">
253
+ <input
254
+ type="checkbox"
255
+ checked={autoRefresh}
256
+ onChange={(e) => setAutoRefresh(e.target.checked)}
257
+ className="rounded"
258
+ />
259
+ <span>Auto-refresh</span>
260
+ </label>
261
+ <button
262
+ onClick={fetchDashboard}
263
+ className="px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm"
264
+ >
265
+ Refresh Now
266
+ </button>
267
+ </div>
268
+ </div>
269
+
270
+ {/* Tabs */}
271
+ <div className="mt-4 flex space-x-4 border-b border-gray-200">
272
+ {['overview', 'models', 'cache', 'alerts'].map((tab) => (
273
+ <button
274
+ key={tab}
275
+ onClick={() => setActiveTab(tab as any)}
276
+ className={`px-4 py-2 font-medium text-sm border-b-2 transition-colors ${
277
+ activeTab === tab
278
+ ? 'border-blue-600 text-blue-600'
279
+ : 'border-transparent text-gray-500 hover:text-gray-700'
280
+ }`}
281
+ >
282
+ {tab.charAt(0).toUpperCase() + tab.slice(1)}
283
+ </button>
284
+ ))}
285
+ </div>
286
+ </div>
287
+ </div>
288
+
289
+ {/* Content */}
290
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
291
+ {/* Overview Tab */}
292
+ {activeTab === 'overview' && (
293
+ <div className="space-y-6">
294
+ {/* System Status Grid */}
295
+ <div>
296
+ <h2 className="text-lg font-semibold text-gray-900 mb-4">System Status</h2>
297
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
298
+ <MetricCard
299
+ title="Uptime"
300
+ value={dashboardData.system.uptime_human}
301
+ subtitle={`${dashboardData.system.uptime_seconds.toLocaleString()}s`}
302
+ icon={<Clock className="h-5 w-5" />}
303
+ status="good"
304
+ />
305
+ <MetricCard
306
+ title="Error Rate"
307
+ value={`${(dashboardData.system.error_rate * 100).toFixed(2)}%`}
308
+ subtitle={`Threshold: ${(dashboardData.system.error_threshold * 100).toFixed(0)}%`}
309
+ icon={<AlertCircle className="h-5 w-5" />}
310
+ status={dashboardData.system.error_rate > dashboardData.system.error_threshold ? 'critical' : 'good'}
311
+ />
312
+ <MetricCard
313
+ title="Total Requests"
314
+ value={dashboardData.system.total_requests.toLocaleString()}
315
+ icon={<Activity className="h-5 w-5" />}
316
+ />
317
+ <MetricCard
318
+ title="Active Alerts"
319
+ value={dashboardData.alerts.active_count}
320
+ subtitle={`${dashboardData.alerts.critical_count} critical`}
321
+ icon={<AlertCircle className="h-5 w-5" />}
322
+ status={dashboardData.alerts.critical_count > 0 ? 'critical' : dashboardData.alerts.active_count > 0 ? 'warning' : 'good'}
323
+ />
324
+ </div>
325
+ </div>
326
+
327
+ {/* Pipeline Statistics */}
328
+ <div>
329
+ <h2 className="text-lg font-semibold text-gray-900 mb-4">Pipeline Statistics</h2>
330
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
331
+ <MetricCard
332
+ title="Total Jobs"
333
+ value={dashboardData.pipeline.total_jobs_processed.toLocaleString()}
334
+ icon={<BarChart3 className="h-5 w-5" />}
335
+ />
336
+ <MetricCard
337
+ title="Completed"
338
+ value={dashboardData.pipeline.completed_jobs.toLocaleString()}
339
+ icon={<TrendingUp className="h-5 w-5" />}
340
+ status="good"
341
+ />
342
+ <MetricCard
343
+ title="Failed"
344
+ value={dashboardData.pipeline.failed_jobs.toLocaleString()}
345
+ icon={<AlertCircle className="h-5 w-5" />}
346
+ status={dashboardData.pipeline.failed_jobs > 0 ? 'warning' : 'good'}
347
+ />
348
+ <MetricCard
349
+ title="Success Rate"
350
+ value={`${(dashboardData.pipeline.success_rate * 100).toFixed(1)}%`}
351
+ icon={<Activity className="h-5 w-5" />}
352
+ status={dashboardData.pipeline.success_rate > 0.95 ? 'good' : dashboardData.pipeline.success_rate > 0.85 ? 'warning' : 'critical'}
353
+ />
354
+ </div>
355
+ </div>
356
+
357
+ {/* Cache & Synthesis */}
358
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
359
+ {/* Cache Statistics */}
360
+ <div className="bg-white p-6 rounded-lg border border-gray-200">
361
+ <div className="flex items-center justify-between mb-4">
362
+ <h3 className="text-lg font-semibold text-gray-900">Cache Performance</h3>
363
+ <Database className="h-5 w-5 text-gray-400" />
364
+ </div>
365
+ <div className="space-y-3">
366
+ <div className="flex justify-between">
367
+ <span className="text-sm text-gray-600">Hit Rate</span>
368
+ <span className="text-sm font-medium">{(dashboardData.cache.hit_rate * 100).toFixed(1)}%</span>
369
+ </div>
370
+ <div className="flex justify-between">
371
+ <span className="text-sm text-gray-600">Entries</span>
372
+ <span className="text-sm font-medium">{dashboardData.cache.total_entries.toLocaleString()}</span>
373
+ </div>
374
+ <div className="flex justify-between">
375
+ <span className="text-sm text-gray-600">Memory Usage</span>
376
+ <span className="text-sm font-medium">{dashboardData.cache.memory_usage_mb.toFixed(1)} MB</span>
377
+ </div>
378
+ <div className="flex justify-between">
379
+ <span className="text-sm text-gray-600">Avg Retrieval</span>
380
+ <span className="text-sm font-medium">{dashboardData.cache.avg_retrieval_time_ms.toFixed(2)} ms</span>
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ {/* Synthesis Statistics */}
386
+ <div className="bg-white p-6 rounded-lg border border-gray-200">
387
+ <div className="flex items-center justify-between mb-4">
388
+ <h3 className="text-lg font-semibold text-gray-900">Clinical Synthesis</h3>
389
+ <GitBranch className="h-5 w-5 text-gray-400" />
390
+ </div>
391
+ <div className="space-y-3">
392
+ <div className="flex justify-between">
393
+ <span className="text-sm text-gray-600">Total Syntheses</span>
394
+ <span className="text-sm font-medium">{dashboardData.synthesis.total_syntheses.toLocaleString()}</span>
395
+ </div>
396
+ <div className="flex justify-between">
397
+ <span className="text-sm text-gray-600">Avg Confidence</span>
398
+ <span className="text-sm font-medium">{(dashboardData.synthesis.avg_confidence * 100).toFixed(1)}%</span>
399
+ </div>
400
+ <div className="flex justify-between">
401
+ <span className="text-sm text-gray-600">Requiring Review</span>
402
+ <span className="text-sm font-medium">{dashboardData.synthesis.requiring_review.toLocaleString()}</span>
403
+ </div>
404
+ <div className="flex justify-between">
405
+ <span className="text-sm text-gray-600">Avg Processing</span>
406
+ <span className="text-sm font-medium">{dashboardData.synthesis.avg_processing_time_ms.toFixed(0)} ms</span>
407
+ </div>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ {/* Compliance Status */}
413
+ <div className="bg-white p-6 rounded-lg border border-gray-200">
414
+ <div className="flex items-center justify-between mb-4">
415
+ <h3 className="text-lg font-semibold text-gray-900">Compliance Status</h3>
416
+ <Shield className="h-5 w-5 text-gray-400" />
417
+ </div>
418
+ <div className="grid grid-cols-2 md:grid-cols-5 gap-4">
419
+ {Object.entries(dashboardData.compliance).map(([key, value]) => (
420
+ <div key={key} className="text-center">
421
+ <div className={`text-2xl mb-1 ${value ? 'text-green-600' : 'text-red-600'}`}>
422
+ {value ? '✓' : '✗'}
423
+ </div>
424
+ <div className="text-xs text-gray-600">
425
+ {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
426
+ </div>
427
+ </div>
428
+ ))}
429
+ </div>
430
+ </div>
431
+ </div>
432
+ )}
433
+
434
+ {/* Models Tab */}
435
+ {activeTab === 'models' && (
436
+ <div>
437
+ <h2 className="text-lg font-semibold text-gray-900 mb-4">Model Performance</h2>
438
+ <div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
439
+ <table className="min-w-full divide-y divide-gray-200">
440
+ <thead className="bg-gray-50">
441
+ <tr>
442
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Model ID</th>
443
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Version</th>
444
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Inferences</th>
445
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Avg Latency</th>
446
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Error Rate</th>
447
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Last Used</th>
448
+ </tr>
449
+ </thead>
450
+ <tbody className="bg-white divide-y divide-gray-200">
451
+ {Object.entries(dashboardData.models.performance).map(([modelId, perf]) => (
452
+ <tr key={modelId}>
453
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{modelId}</td>
454
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.version}</td>
455
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.total_inferences.toLocaleString()}</td>
456
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.avg_latency_ms.toFixed(1)} ms</td>
457
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{(perf.error_rate * 100).toFixed(2)}%</td>
458
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{perf.last_used}</td>
459
+ </tr>
460
+ ))}
461
+ </tbody>
462
+ </table>
463
+ </div>
464
+ </div>
465
+ )}
466
+
467
+ {/* Cache Tab */}
468
+ {activeTab === 'cache' && (
469
+ <div>
470
+ <div className="flex items-center justify-between mb-4">
471
+ <h2 className="text-lg font-semibold text-gray-900">Cache Management</h2>
472
+ <button
473
+ onClick={clearCache}
474
+ className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm"
475
+ >
476
+ Clear Cache
477
+ </button>
478
+ </div>
479
+
480
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
481
+ <MetricCard
482
+ title="Cache Entries"
483
+ value={dashboardData.cache.total_entries.toLocaleString()}
484
+ icon={<Database className="h-5 w-5" />}
485
+ />
486
+ <MetricCard
487
+ title="Hit Rate"
488
+ value={`${(dashboardData.cache.hit_rate * 100).toFixed(1)}%`}
489
+ subtitle={`${dashboardData.cache.hits} hits, ${dashboardData.cache.misses} misses`}
490
+ icon={<Activity className="h-5 w-5" />}
491
+ status={dashboardData.cache.hit_rate > 0.7 ? 'good' : dashboardData.cache.hit_rate > 0.4 ? 'warning' : 'critical'}
492
+ />
493
+ <MetricCard
494
+ title="Memory Usage"
495
+ value={`${dashboardData.cache.memory_usage_mb.toFixed(1)} MB`}
496
+ subtitle={`${dashboardData.cache.evictions} evictions`}
497
+ icon={<BarChart3 className="h-5 w-5" />}
498
+ />
499
+ </div>
500
+
501
+ <div className="bg-white p-6 rounded-lg border border-gray-200">
502
+ <h3 className="text-md font-semibold text-gray-900 mb-4">Cache Performance Analysis</h3>
503
+ <div className="space-y-2">
504
+ {dashboardData.cache.hit_rate < 0.5 && (
505
+ <div className="p-3 bg-yellow-50 border border-yellow-200 rounded">
506
+ <p className="text-sm text-yellow-800">
507
+ ⚠ Low cache hit rate ({(dashboardData.cache.hit_rate * 100).toFixed(1)}%).
508
+ Consider increasing cache size or TTL.
509
+ </p>
510
+ </div>
511
+ )}
512
+ {dashboardData.cache.hit_rate >= 0.8 && (
513
+ <div className="p-3 bg-green-50 border border-green-200 rounded">
514
+ <p className="text-sm text-green-800">
515
+ ✓ Excellent cache hit rate ({(dashboardData.cache.hit_rate * 100).toFixed(1)}%).
516
+ Cache performing optimally.
517
+ </p>
518
+ </div>
519
+ )}
520
+ <div className="p-3 bg-blue-50 border border-blue-200 rounded">
521
+ <p className="text-sm text-blue-800">
522
+ Cache efficiency: {dashboardData.cache.cache_efficiency.toFixed(1)}%
523
+ </p>
524
+ </div>
525
+ </div>
526
+ </div>
527
+ </div>
528
+ )}
529
+
530
+ {/* Alerts Tab */}
531
+ {activeTab === 'alerts' && (
532
+ <div>
533
+ <h2 className="text-lg font-semibold text-gray-900 mb-4">
534
+ Active Alerts ({dashboardData.alerts.active_count})
535
+ </h2>
536
+ {dashboardData.alerts.recent.length === 0 ? (
537
+ <div className="bg-white p-12 rounded-lg border border-gray-200 text-center">
538
+ <AlertCircle className="h-12 w-12 text-gray-300 mx-auto mb-4" />
539
+ <p className="text-gray-500">No active alerts</p>
540
+ </div>
541
+ ) : (
542
+ <div className="space-y-3">
543
+ {dashboardData.alerts.recent.map((alert) => (
544
+ <div
545
+ key={alert.alert_id}
546
+ className={`p-4 rounded-lg border-2 ${
547
+ alert.level === 'critical'
548
+ ? 'border-red-200 bg-red-50'
549
+ : alert.level === 'error'
550
+ ? 'border-orange-200 bg-orange-50'
551
+ : alert.level === 'warning'
552
+ ? 'border-yellow-200 bg-yellow-50'
553
+ : 'border-blue-200 bg-blue-50'
554
+ }`}
555
+ >
556
+ <div className="flex items-start justify-between">
557
+ <div className="flex-1">
558
+ <div className="flex items-center space-x-2 mb-2">
559
+ <span className={`px-2 py-1 rounded text-xs font-medium ${
560
+ alert.level === 'critical'
561
+ ? 'bg-red-200 text-red-800'
562
+ : alert.level === 'error'
563
+ ? 'bg-orange-200 text-orange-800'
564
+ : alert.level === 'warning'
565
+ ? 'bg-yellow-200 text-yellow-800'
566
+ : 'bg-blue-200 text-blue-800'
567
+ }`}>
568
+ {alert.level.toUpperCase()}
569
+ </span>
570
+ <span className="text-xs text-gray-500">{alert.category}</span>
571
+ <span className="text-xs text-gray-400">
572
+ {new Date(alert.timestamp).toLocaleString()}
573
+ </span>
574
+ </div>
575
+ <p className="text-sm font-medium text-gray-900">{alert.message}</p>
576
+ {Object.keys(alert.details).length > 0 && (
577
+ <pre className="mt-2 text-xs text-gray-600 bg-white/50 p-2 rounded">
578
+ {JSON.stringify(alert.details, null, 2)}
579
+ </pre>
580
+ )}
581
+ </div>
582
+ {!alert.resolved && (
583
+ <button
584
+ onClick={() => resolveAlert(alert.alert_id)}
585
+ className="ml-4 px-3 py-1 bg-white border border-gray-300 text-gray-700 rounded hover:bg-gray-50 text-sm"
586
+ >
587
+ Resolve
588
+ </button>
589
+ )}
590
+ </div>
591
+ </div>
592
+ ))}
593
+ </div>
594
+ )}
595
+ </div>
596
+ )}
597
+ </div>
598
+ </div>
599
+ );
600
+ };
601
+
602
+ export default AdminDashboard;