← Назадimport { authFetch } from '../lib/api';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Activity, RefreshCw, Clock, Zap } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button';
import type { HealthStatus as HealthStatusType } from '../types';
const API_BASE = '/api';
export function HealthStatus() {
const queryClient = useQueryClient();
// Fetch latest health status
const { data, isLoading, error } = useQuery<HealthStatusType>({
queryKey: ['health', 'status'],
queryFn: async () => {
const response = await authFetch(`${API_BASE}/health/status`);
if (!response.ok) throw new Error('Failed to fetch health status');
return response.json();
},
refetchInterval: 30000, // Refetch every 30 seconds
});
// Manual health check mutation
const runCheckMutation = useMutation({
mutationFn: async () => {
const response = await authFetch(`${API_BASE}/health/check`, {
method: 'POST',
});
if (!response.ok) throw new Error('Failed to run health check');
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['health'] });
},
});
const getStatusEmoji = (status: string) => {
switch (status) {
case 'healthy':
return '🟢';
case 'warning':
return '🟡';
case 'error':
return '🔴';
default:
return '⚪';
}
};
const getProjectName = (project: string) => {
const names: Record<string, string> = {
piewell: 'Piewell.com',
'futures-screener': 'Futures Screener',
'bender-bot': 'Bender Bot',
dashboard: 'Dashboard',
system: 'System',
};
return names[project] || project;
};
const formatTimestamp = (timestamp?: string) => {
if (!timestamp) return 'Never';
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours}h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays}d ago`;
};
if (isLoading) {
return (
<Card className="bg-slate-800/50 border-slate-700">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center gap-2">
<Activity className="h-5 w-5 text-blue-400" />
Health Status
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-sm text-slate-400">Loading health status...</div>
</CardContent>
</Card>
);
}
if (error || !data || data.status === 'no_data') {
return (
<Card className="bg-slate-800/50 border-slate-700">
<CardHeader className="pb-3">
<CardTitle className="text-lg flex items-center gap-2">
<Activity className="h-5 w-5 text-blue-400" />
Health Status
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-sm text-slate-400 mb-3">
{error ? 'Failed to load health status' : 'No health checks run yet'}
</div>
<Button
onClick={() => runCheckMutation.mutate()}
disabled={runCheckMutation.isPending}
size="sm"
className="bg-blue-600 hover:bg-blue-700"
>
<RefreshCw className={`h-4 w-4 mr-2 ${runCheckMutation.isPending ? 'animate-spin' : ''}`} />
Run Health Check
</Button>
</CardContent>
</Card>
);
}
const healthyCount = data.checks.filter((c) => c.status === 'healthy').length;
const warningCount = data.checks.filter((c) => c.status === 'warning').length;
const errorCount = data.checks.filter((c) => c.status === 'error').length;
return (
<Card className="bg-slate-800/50 border-slate-700">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-lg flex items-center gap-2">
<Activity className="h-5 w-5 text-blue-400" />
Health Status
</CardTitle>
<Button
onClick={() => runCheckMutation.mutate()}
disabled={runCheckMutation.isPending}
size="sm"
variant="ghost"
className="border border-slate-600 hover:bg-slate-700"
>
<RefreshCw className={`h-4 w-4 ${runCheckMutation.isPending ? 'animate-spin' : ''}`} />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-3">
{/* Summary */}
<div className="grid grid-cols-3 gap-3 pb-3 border-b border-slate-700">
<div className="text-center">
<div className="text-2xl font-bold text-green-500">{healthyCount}</div>
<div className="text-xs text-slate-400">Healthy</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-yellow-500">{warningCount}</div>
<div className="text-xs text-slate-400">Warning</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-red-500">{errorCount}</div>
<div className="text-xs text-slate-400">Error</div>
</div>
</div>
{/* Project Status List */}
<div className="space-y-2">
{data.checks.map((check) => (
<div
key={check.project}
className="flex items-center justify-between p-2 rounded bg-slate-900/50 hover:bg-slate-900/70 transition-colors"
>
<div className="flex items-center gap-2 flex-1">
<span className="text-lg">{getStatusEmoji(check.status)}</span>
<div className="flex-1">
<div className="text-sm font-medium">{getProjectName(check.project)}</div>
{check.details.error && (
<div className="text-xs text-red-400">{check.details.error}</div>
)}
{check.details.warnings && check.details.warnings.length > 0 && (
<div className="text-xs text-yellow-400">
{check.details.warnings.join(', ')}
</div>
)}
</div>
</div>
<div className="flex items-center gap-3 text-xs text-slate-400">
<div className="flex items-center gap-1">
<Zap className="h-3 w-3" />
{check.responseTime}ms
</div>
<div className="flex items-center gap-1">
<Clock className="h-3 w-3" />
{formatTimestamp(check.lastCheck)}
</div>
</div>
</div>
))}
</div>
{/* Last Update */}
<div className="pt-2 text-xs text-slate-500 text-center border-t border-slate-700">
Last updated: {formatTimestamp(data.timestamp)} • Auto-refresh every 30 sec
</div>
</CardContent>
</Card>
);
}