mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-29 02:07:05 -05:00
Add comprehensive monitoring dashboard scaffolding: - Extend queryKeys with monitoring keys - Create hooks: useCombinedAlerts, useRecentActivity, useDatabaseHealth, useModerationHealth - Build UI components: SystemHealthStatus, CriticalAlertsPanel, MonitoringQuickStats, RecentActivityTimeline, MonitoringNavCards - Create MonitoringOverview page and integrate routing (MonitoringOverview route) - Wire AdminSidebar to include Monitoring navigation - Introduce supporting routing and admin layout hooks/utilities - Align with TanStack Query patterns and plan for auto-refresh and optimistic updates
142 lines
4.7 KiB
TypeScript
142 lines
4.7 KiB
TypeScript
import { Activity, AlertTriangle, CheckCircle2, XCircle } from 'lucide-react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useRunSystemMaintenance, type SystemHealthData } from '@/hooks/useSystemHealth';
|
|
import type { DatabaseHealth } from '@/hooks/admin/useDatabaseHealth';
|
|
|
|
interface SystemHealthStatusProps {
|
|
systemHealth?: SystemHealthData;
|
|
dbHealth?: DatabaseHealth;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
export function SystemHealthStatus({ systemHealth, dbHealth, isLoading }: SystemHealthStatusProps) {
|
|
const runMaintenance = useRunSystemMaintenance();
|
|
|
|
const getOverallStatus = () => {
|
|
if (isLoading) return 'checking';
|
|
if (!systemHealth) return 'unknown';
|
|
|
|
const hasCriticalIssues =
|
|
(systemHealth.orphaned_images_count || 0) > 0 ||
|
|
(systemHealth.failed_webhook_count || 0) > 0 ||
|
|
(systemHealth.critical_alerts_count || 0) > 0 ||
|
|
dbHealth?.status === 'unhealthy';
|
|
|
|
if (hasCriticalIssues) return 'unhealthy';
|
|
|
|
const hasWarnings =
|
|
dbHealth?.status === 'warning' ||
|
|
(systemHealth.high_alerts_count || 0) > 0;
|
|
|
|
if (hasWarnings) return 'warning';
|
|
|
|
return 'healthy';
|
|
};
|
|
|
|
const status = getOverallStatus();
|
|
|
|
const statusConfig = {
|
|
healthy: {
|
|
icon: CheckCircle2,
|
|
label: 'All Systems Operational',
|
|
color: 'text-green-500',
|
|
bgColor: 'bg-green-500/10',
|
|
borderColor: 'border-green-500/20',
|
|
},
|
|
warning: {
|
|
icon: AlertTriangle,
|
|
label: 'System Warning',
|
|
color: 'text-yellow-500',
|
|
bgColor: 'bg-yellow-500/10',
|
|
borderColor: 'border-yellow-500/20',
|
|
},
|
|
unhealthy: {
|
|
icon: XCircle,
|
|
label: 'Critical Issues Detected',
|
|
color: 'text-red-500',
|
|
bgColor: 'bg-red-500/10',
|
|
borderColor: 'border-red-500/20',
|
|
},
|
|
checking: {
|
|
icon: Activity,
|
|
label: 'Checking System Health...',
|
|
color: 'text-muted-foreground',
|
|
bgColor: 'bg-muted',
|
|
borderColor: 'border-border',
|
|
},
|
|
unknown: {
|
|
icon: AlertTriangle,
|
|
label: 'Unable to Determine Status',
|
|
color: 'text-muted-foreground',
|
|
bgColor: 'bg-muted',
|
|
borderColor: 'border-border',
|
|
},
|
|
};
|
|
|
|
const config = statusConfig[status];
|
|
const StatusIcon = config.icon;
|
|
|
|
const handleRunMaintenance = () => {
|
|
runMaintenance.mutate();
|
|
};
|
|
|
|
return (
|
|
<Card className={`${config.borderColor} border-2`}>
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Activity className="w-5 h-5" />
|
|
System Health
|
|
</CardTitle>
|
|
{(status === 'unhealthy' || status === 'warning') && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={handleRunMaintenance}
|
|
loading={runMaintenance.isPending}
|
|
loadingText="Running..."
|
|
>
|
|
Run Maintenance
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={`flex items-center gap-3 p-4 rounded-lg ${config.bgColor}`}>
|
|
<StatusIcon className={`w-8 h-8 ${config.color}`} />
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-semibold">{config.label}</span>
|
|
<Badge variant={status === 'healthy' ? 'default' : status === 'warning' ? 'secondary' : 'destructive'}>
|
|
{status.toUpperCase()}
|
|
</Badge>
|
|
</div>
|
|
{systemHealth && (
|
|
<div className="mt-2 grid grid-cols-2 sm:grid-cols-4 gap-2 text-sm">
|
|
<div>
|
|
<span className="text-muted-foreground">Orphaned Images:</span>
|
|
<span className="ml-1 font-medium">{systemHealth.orphaned_images_count || 0}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Failed Webhooks:</span>
|
|
<span className="ml-1 font-medium">{systemHealth.failed_webhook_count || 0}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Critical Alerts:</span>
|
|
<span className="ml-1 font-medium">{systemHealth.critical_alerts_count || 0}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">DB Errors (1h):</span>
|
|
<span className="ml-1 font-medium">{dbHealth?.recentErrors || 0}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|