'use client'; import { useState, useCallback, useRef, useEffect } from 'react'; import { useProcesses } from '@/lib/hooks/useSupervisor'; import { ProcessCard } from '@/components/process/ProcessCard'; import { GroupView } from '@/components/groups/GroupView'; import { GroupSelector } from '@/components/groups/GroupSelector'; import { BatchActions } from '@/components/process/BatchActions'; import { ProcessFilters } from '@/components/process/ProcessFilters'; import { RefreshCw, AlertCircle, CheckSquare, Keyboard } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useKeyboardShortcuts } from '@/lib/hooks/useKeyboardShortcuts'; import { KeyboardShortcutsHelp } from '@/components/ui/KeyboardShortcutsHelp'; import { ConnectionStatus } from '@/components/ui/ConnectionStatus'; import { useEventSource } from '@/lib/hooks/useEventSource'; import type { ProcessInfo } from '@/lib/supervisor/types'; export default function ProcessesPage() { const [viewMode, setViewMode] = useState<'flat' | 'grouped'>('flat'); const [selectedProcesses, setSelectedProcesses] = useState>(new Set()); const [filteredProcesses, setFilteredProcesses] = useState([]); const [showShortcutsHelp, setShowShortcutsHelp] = useState(false); const [focusedIndex, setFocusedIndex] = useState(-1); const [realtimeEnabled, setRealtimeEnabled] = useState(true); const searchInputRef = useRef(null); const { data: processes, isLoading, isError, refetch } = useProcesses(); // Real-time updates via Server-Sent Events const { status: connectionStatus, reconnectAttempts, reconnect } = useEventSource( '/api/supervisor/events', { enabled: realtimeEnabled && !isLoading && !isError, onMessage: (message) => { if (message.event === 'process-update') { // Invalidate and refetch process data refetch(); } }, onConnect: () => { console.log('SSE connected'); }, onDisconnect: () => { console.log('SSE disconnected'); }, } ); const handleFilterChange = useCallback((filtered: ProcessInfo[]) => { setFilteredProcesses(filtered); }, []); const handleSelectionChange = (processId: string, selected: boolean) => { setSelectedProcesses((prev) => { const newSet = new Set(prev); if (selected) { newSet.add(processId); } else { newSet.delete(processId); } return newSet; }); }; const handleSelectAll = () => { const displayedProcesses = filteredProcesses.length > 0 ? filteredProcesses : (processes || []); if (displayedProcesses) { if (selectedProcesses.size === displayedProcesses.length) { setSelectedProcesses(new Set()); } else { setSelectedProcesses(new Set(displayedProcesses.map((p) => `${p.group}:${p.name}`))); } } }; const handleClearSelection = () => { setSelectedProcesses(new Set()); }; // Get displayedProcesses for keyboard navigation const displayedProcesses = filteredProcesses.length > 0 || !processes ? filteredProcesses : (processes || []); // Keyboard shortcuts useKeyboardShortcuts({ shortcuts: [ { key: '/', description: 'Focus search', action: () => { // Find and focus the search input const searchInput = document.querySelector('input[type="text"]') as HTMLInputElement; if (searchInput) { searchInput.focus(); } }, }, { key: 'r', description: 'Refresh', action: () => { refetch(); }, }, { key: 'a', description: 'Select all', action: () => { if (viewMode === 'flat') { handleSelectAll(); } }, enabled: viewMode === 'flat' && displayedProcesses.length > 0, }, { key: 'Escape', description: 'Clear selection', action: () => { if (selectedProcesses.size > 0) { handleClearSelection(); } setFocusedIndex(-1); }, }, { key: '?', shift: true, description: 'Show keyboard shortcuts', action: () => { setShowShortcutsHelp(true); }, }, { key: 'j', description: 'Next process', action: () => { if (viewMode === 'flat' && displayedProcesses.length > 0) { setFocusedIndex((prev) => { const next = prev + 1; return next >= displayedProcesses.length ? 0 : next; }); } }, enabled: viewMode === 'flat' && displayedProcesses.length > 0, }, { key: 'k', description: 'Previous process', action: () => { if (viewMode === 'flat' && displayedProcesses.length > 0) { setFocusedIndex((prev) => { const next = prev - 1; return next < 0 ? displayedProcesses.length - 1 : next; }); } }, enabled: viewMode === 'flat' && displayedProcesses.length > 0, }, { key: ' ', description: 'Toggle selection', action: () => { if (viewMode === 'flat' && focusedIndex >= 0 && focusedIndex < displayedProcesses.length) { const process = displayedProcesses[focusedIndex]; const fullName = `${process.group}:${process.name}`; handleSelectionChange(fullName, !selectedProcesses.has(fullName)); } }, enabled: viewMode === 'flat' && focusedIndex >= 0 && displayedProcesses.length > 0, }, ], }); // Auto-select focused process useEffect(() => { if (focusedIndex >= 0 && focusedIndex < displayedProcesses.length) { const process = displayedProcesses[focusedIndex]; const fullName = `${process.group}:${process.name}`; const element = document.querySelector(`[data-process-id="${fullName}"]`); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } }, [focusedIndex, displayedProcesses]); if (isLoading) { return (

Processes

{[1, 2, 3, 4, 5, 6].map((i) => (
))}
); } if (isError) { return (

Processes

Failed to load processes

Could not connect to Supervisor. Please check your configuration.

); } return (
{/* Header - responsive layout */}

Processes

{displayedProcesses.length} of {processes?.length ?? 0} processes {displayedProcesses.length !== (processes?.length ?? 0) && ' (filtered)'}

{/* Controls - stack on mobile, row on desktop */}
{viewMode === 'flat' && displayedProcesses.length > 0 && ( )}
{/* Filters */} {processes && processes.length > 0 && ( )} {/* Process Display */} {processes && processes.length === 0 ? (

No processes configured

) : displayedProcesses.length === 0 ? (

No processes match the current filters

) : viewMode === 'grouped' ? ( ) : (
{displayedProcesses.map((process, index) => { const fullName = `${process.group}:${process.name}`; const isFocused = index === focusedIndex; return (
); })}
)} {/* Keyboard Shortcuts Help */} {showShortcutsHelp && ( setShowShortcutsHelp(false)} /> )}
); }