import { IconActivity as Activity, IconAlertCircle as AlertCircle, IconCircleCheck as CheckCircle2, IconLoader2 as Loader2, IconPower as Power, IconRefresh as RefreshCw, } from '@tabler/icons-react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; import { getAuthHeaders } from '@/lib/api'; import { cn } from '@/lib/utils'; const APP_VERSION = import.meta.env.VITE_APP_VERSION || '0.1.0'; // VITE_ENVIRONMENT is set during deployment (qa, staging, prod, etc.) // Falls back to Vite's MODE for local development const ENVIRONMENT = import.meta.env.VITE_ENVIRONMENT || import.meta.env.MODE || 'development'; interface ServiceStatus { name: string; container: string; status: 'healthy' | 'unhealthy' | 'unknown'; latencyMs?: number; error?: string; } interface ServicesStatusResponse { summary: { healthy: number; unhealthy: number; total: number; }; services: ServiceStatus[]; timestamp: string; } async function fetchServicesStatus(): Promise { const headers = await getAuthHeaders(); const response = await fetch('/api/_internal/services/status', { headers }); if (!response.ok) { throw new Error('Failed to fetch services status'); } return response.json(); } async function restartService(container: string): Promise<{ success: boolean; error?: string }> { const headers = await getAuthHeaders(); const response = await fetch(`/api/_internal/services/status/restart/${container}`, { method: 'POST', headers, }); return response.json(); } async function restartAllServices(): Promise<{ success: boolean }> { const headers = await getAuthHeaders(); const response = await fetch('/api/_internal/services/status/restart', { method: 'POST', headers, }); return response.json(); } function StatusIcon({ status }: { status: ServiceStatus['status'] }) { switch (status) { case 'healthy': return ; case 'unhealthy': return ; default: return ; } } function StatusBadge({ status }: { status: ServiceStatus['status'] }) { return ( {status} ); } export function ServicesStatus() { const queryClient = useQueryClient(); const [restartingContainers, setRestartingContainers] = useState>(new Set()); const { data, isLoading, isFetching, error, refetch } = useQuery({ queryKey: ['services-status'], queryFn: fetchServicesStatus, refetchInterval: 30000, // Refresh every 30 seconds staleTime: 10000, }); // Track if we're refreshing with a minimum display time // This ensures the spinner is visible even for very fast responses const [showRefreshing, setShowRefreshing] = useState(false); const isActuallyRefreshing = isFetching && !isLoading; useEffect(() => { if (isActuallyRefreshing) { setShowRefreshing(true); } else if (showRefreshing) { // Keep showing for minimum 500ms after fetch completes const timer = setTimeout(() => setShowRefreshing(false), 500); return () => clearTimeout(timer); } }, [isActuallyRefreshing, showRefreshing]); const isRefreshing = showRefreshing; const restartMutation = useMutation({ mutationFn: restartService, onMutate: (container) => { setRestartingContainers((prev) => new Set(prev).add(container)); }, onSettled: (_, __, container) => { setRestartingContainers((prev) => { const next = new Set(prev); next.delete(container); return next; }); // Refetch status after restart queryClient.invalidateQueries({ queryKey: ['services-status'] }); }, }); const restartAllMutation = useMutation({ mutationFn: restartAllServices, onMutate: () => { if (data?.services) { setRestartingContainers(new Set(data.services.map((s) => s.container))); } }, onSettled: () => { setRestartingContainers(new Set()); // Refetch status after restart queryClient.invalidateQueries({ queryKey: ['services-status'] }); }, }); const handleRestart = (container: string) => { restartMutation.mutate(container); }; const handleRestartAll = () => { restartAllMutation.mutate(); }; const isAnyRestarting = restartingContainers.size > 0 || restartAllMutation.isPending; if (isLoading) { return ( Services Status Loading service status...
); } if (error || !data) { return ( Services Status Unable to fetch service status

Could not connect to the services status endpoint.

); } const { summary, services } = data; return (
Services Status {summary.healthy} of {summary.total} services healthy {isRefreshing && ( Refreshing... )}

{ENVIRONMENT}

v{APP_VERSION}

{services.map((service) => { const isRestarting = restartingContainers.has(service.container); return (
{isRestarting ? ( ) : ( )}

{service.name}

{isRestarting ? ( Restarting... ) : isRefreshing ? ( ) : service.latencyMs !== undefined && service.status === 'healthy' ? ( {service.latencyMs}ms ) : service.error ? ( {service.error} ) : null} {isRestarting ? null : }
); })}
); }