← Назад
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> ); }