Some checks failed
API CI / API Lint (push) Has been cancelled
API CI / API Type Check (push) Has been cancelled
API CI / API Test (push) Has been cancelled
API CI / API Build (push) Has been cancelled
CD Pipeline / Deploy to Staging (push) Has been cancelled
CI Pipeline / Lint and Type Check (push) Has been cancelled
CI Pipeline / Test Backend (push) Has been cancelled
CI Pipeline / Test Frontend (push) Has been cancelled
CI Pipeline / Security Scan (push) Has been cancelled
Deploy to Staging / Deploy to Staging (push) Has been cancelled
Portal CI / Portal Lint (push) Has been cancelled
Portal CI / Portal Type Check (push) Has been cancelled
Portal CI / Portal Test (push) Has been cancelled
Portal CI / Portal Build (push) Has been cancelled
Test Suite / frontend-tests (push) Has been cancelled
Test Suite / api-tests (push) Has been cancelled
Test Suite / blockchain-tests (push) Has been cancelled
Type Check / type-check (map[directory:. name:root]) (push) Has been cancelled
Type Check / type-check (map[directory:api name:api]) (push) Has been cancelled
Type Check / type-check (map[directory:portal name:portal]) (push) Has been cancelled
API CI / Build Docker Image (push) Has been cancelled
CD Pipeline / Deploy to Production (push) Has been cancelled
CI Pipeline / Build (push) Has been cancelled
- root .eslintrc with recommended TS rules; eslint --fix import order project-wide - Replace any/unknown in lib clients (ArgoCD, K8s, Phoenix), hooks, and key components - Form labels: htmlFor+id; escape apostrophes; remove or gate console (error boundary keep) - Crossplane VM status typing; webhook test result interface; infrastructure/resources maps typed Made-with: Cursor
178 lines
7.3 KiB
TypeScript
178 lines
7.3 KiB
TypeScript
'use client';
|
|
|
|
import { Activity, CheckCircle, AlertCircle, XCircle, Globe, Server } from 'lucide-react';
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
|
import { useSystemHealth } from '@/hooks/useDashboardData';
|
|
|
|
type HealthSite = { status?: string };
|
|
type HealthResource = { status?: string };
|
|
|
|
export function SystemHealthTile() {
|
|
const { data, loading, error } = useSystemHealth();
|
|
|
|
// Calculate health data from API response
|
|
const healthData = data ? {
|
|
regions: {
|
|
total: data.sites?.length || 0,
|
|
healthy: data.sites?.filter((s: HealthSite) => s.status === 'ACTIVE').length || 0,
|
|
warning: data.sites?.filter((s: HealthSite) => s.status === 'MAINTENANCE').length || 0,
|
|
critical: data.sites?.filter((s: HealthSite) => s.status === 'INACTIVE').length || 0,
|
|
},
|
|
clusters: {
|
|
total: data.resources?.length || 0,
|
|
healthy: data.resources?.filter((r: HealthResource) => r.status === 'RUNNING').length || 0,
|
|
warning: data.resources?.filter((r: HealthResource) => r.status === 'PROVISIONING').length || 0,
|
|
critical: data.resources?.filter((r: HealthResource) => r.status === 'ERROR').length || 0,
|
|
},
|
|
nodes: {
|
|
total: data.resources?.length || 0,
|
|
healthy: data.resources?.filter((r: HealthResource) => r.status === 'RUNNING').length || 0,
|
|
warning: data.resources?.filter((r: HealthResource) => r.status === 'PROVISIONING').length || 0,
|
|
critical: data.resources?.filter((r: HealthResource) => r.status === 'ERROR').length || 0,
|
|
},
|
|
} : {
|
|
regions: { total: 0, healthy: 0, warning: 0, critical: 0 },
|
|
clusters: { total: 0, healthy: 0, warning: 0, critical: 0 },
|
|
nodes: { total: 0, healthy: 0, warning: 0, critical: 0 },
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Card className="bg-gray-800 border-gray-700">
|
|
<CardHeader>
|
|
<CardTitle className="text-white flex items-center gap-2">
|
|
<Activity className="h-5 w-5 text-orange-500" />
|
|
System Health Overview
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-center text-gray-400 py-4">Loading...</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Card className="bg-gray-800 border-gray-700">
|
|
<CardHeader>
|
|
<CardTitle className="text-white flex items-center gap-2">
|
|
<Activity className="h-5 w-5 text-orange-500" />
|
|
System Health Overview
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-center text-red-400 py-4">Error loading health data</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const overallHealth = healthData.regions.healthy / healthData.regions.total > 0.95 ? 'healthy' :
|
|
healthData.regions.healthy / healthData.regions.total > 0.85 ? 'warning' : 'critical';
|
|
|
|
return (
|
|
<Card className="bg-gray-800 border-gray-700">
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle className="text-white flex items-center gap-2">
|
|
<Activity className="h-5 w-5 text-orange-500" />
|
|
System Health Overview
|
|
</CardTitle>
|
|
<div className={`px-3 py-1 rounded-full text-xs font-semibold ${
|
|
overallHealth === 'healthy' ? 'bg-green-500/20 text-green-400' :
|
|
overallHealth === 'warning' ? 'bg-yellow-500/20 text-yellow-400' :
|
|
'bg-red-500/20 text-red-400'
|
|
}`}>
|
|
{overallHealth === 'healthy' ? 'Healthy' : overallHealth === 'warning' ? 'Warning' : 'Critical'}
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between p-3 bg-gray-900 rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<Globe className="h-5 w-5 text-blue-400" />
|
|
<div>
|
|
<p className="text-sm text-gray-400">Regions</p>
|
|
<p className="text-lg font-semibold text-white">{healthData.regions.total}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<div className="flex items-center gap-1 text-green-400">
|
|
<CheckCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.regions.healthy}</span>
|
|
</div>
|
|
{healthData.regions.warning > 0 && (
|
|
<div className="flex items-center gap-1 text-yellow-400">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.regions.warning}</span>
|
|
</div>
|
|
)}
|
|
{healthData.regions.critical > 0 && (
|
|
<div className="flex items-center gap-1 text-red-400">
|
|
<XCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.regions.critical}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-gray-900 rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<Server className="h-5 w-5 text-purple-400" />
|
|
<div>
|
|
<p className="text-sm text-gray-400">Clusters</p>
|
|
<p className="text-lg font-semibold text-white">{healthData.clusters.total}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<div className="flex items-center gap-1 text-green-400">
|
|
<CheckCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.clusters.healthy}</span>
|
|
</div>
|
|
{healthData.clusters.warning > 0 && (
|
|
<div className="flex items-center gap-1 text-yellow-400">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.clusters.warning}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between p-3 bg-gray-900 rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<Activity className="h-5 w-5 text-cyan-400" />
|
|
<div>
|
|
<p className="text-sm text-gray-400">Nodes</p>
|
|
<p className="text-lg font-semibold text-white">{healthData.nodes.total}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<div className="flex items-center gap-1 text-green-400">
|
|
<CheckCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.nodes.healthy}</span>
|
|
</div>
|
|
{healthData.nodes.warning > 0 && (
|
|
<div className="flex items-center gap-1 text-yellow-400">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.nodes.warning}</span>
|
|
</div>
|
|
)}
|
|
{healthData.nodes.critical > 0 && (
|
|
<div className="flex items-center gap-1 text-red-400">
|
|
<XCircle className="h-4 w-4" />
|
|
<span className="text-sm">{healthData.nodes.critical}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
|