Files
supervisor-ui/components/ui/ConnectionStatus.tsx
Sebastian Krüger 25d9029d14
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 1m11s
feat: implement Phase 11 - Real-time Updates with SSE
Features added:
- Created SSE (Server-Sent Events) endpoint at /api/supervisor/events
  - Polls supervisor every 2 seconds for state changes
  - Sends process-update events when state changes detected
  - Sends heartbeat events to keep connection alive
  - Includes error handling with error events
- Created useEventSource hook for managing SSE connections
  - Automatic reconnection with exponential backoff
  - Configurable max reconnection attempts (default 10)
  - Connection status tracking (connecting, connected, disconnected, error)
  - Clean event listener management with proper cleanup
  - Heartbeat monitoring for connection health
- Created ConnectionStatus component
  - Visual status indicator with icons (Wifi, WifiOff, Loader, AlertCircle)
  - Color-coded states (green=connected, yellow=connecting, red=error)
  - Shows reconnection attempt count
  - Manual reconnect button when disconnected/error
- Integrated real-time updates into dashboard and processes pages
  - Auto-refresh process data when state changes occur
  - Connection status indicator in page headers
  - No manual refresh needed for live updates
- Implemented proper cleanup on unmount
  - EventSource properly closed
  - Reconnection timeouts cleared
  - No memory leaks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 19:54:14 +01:00

82 lines
2.2 KiB
TypeScript

'use client';
import { Wifi, WifiOff, Loader2, AlertCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { ConnectionStatus as Status } from '@/lib/hooks/useEventSource';
import { cn } from '@/lib/utils/cn';
interface ConnectionStatusProps {
status: Status;
reconnectAttempts?: number;
onReconnect?: () => void;
}
export function ConnectionStatus({ status, reconnectAttempts = 0, onReconnect }: ConnectionStatusProps) {
const getStatusConfig = () => {
switch (status) {
case 'connected':
return {
icon: Wifi,
text: 'Live',
color: 'text-success',
bgColor: 'bg-success/10',
borderColor: 'border-success/20',
};
case 'connecting':
return {
icon: Loader2,
text: 'Connecting...',
color: 'text-warning',
bgColor: 'bg-warning/10',
borderColor: 'border-warning/20',
animate: true,
};
case 'error':
return {
icon: AlertCircle,
text: reconnectAttempts > 0 ? `Retrying (${reconnectAttempts})` : 'Error',
color: 'text-destructive',
bgColor: 'bg-destructive/10',
borderColor: 'border-destructive/20',
};
case 'disconnected':
default:
return {
icon: WifiOff,
text: 'Offline',
color: 'text-muted-foreground',
bgColor: 'bg-muted',
borderColor: 'border-muted',
};
}
};
const config = getStatusConfig();
const Icon = config.icon;
return (
<div
className={cn(
'flex items-center gap-2 px-3 py-1.5 rounded-md border text-sm',
config.bgColor,
config.borderColor
)}
>
<Icon
className={cn('h-4 w-4', config.color, config.animate && 'animate-spin')}
/>
<span className={cn('font-medium', config.color)}>{config.text}</span>
{(status === 'error' || status === 'disconnected') && onReconnect && (
<Button
variant="ghost"
size="sm"
onClick={onReconnect}
className="h-6 px-2 ml-1"
>
Reconnect
</Button>
)}
</div>
);
}