'use client'; import { useEffect, useRef, useState, useCallback } from 'react'; import { useWebSocket } from '@/lib/websocket/context'; import type { WebSocketMessage, StepData, SteamOutputData, ErrorData, CompleteData } from '@/lib/schemas/websocket'; interface ServerCreationPopupProps { serverId: string; serverName: string; isOpen: boolean; onClose: () => void; onComplete?: (success: boolean, message: string) => void; } interface ConsoleEntry { id: string; timestamp: number; type: 'step' | 'steam_output' | 'error' | 'complete'; content: string; level: 'info' | 'success' | 'warning' | 'error'; } interface StepStatus { step: string; status: 'pending' | 'in_progress' | 'completed' | 'failed'; message: string; } const STEPS = [ { key: 'validation', label: 'Validation' }, { key: 'directory_creation', label: 'Directory Creation' }, { key: 'steam_download', label: 'Steam Download' }, { key: 'config_generation', label: 'Config Generation' }, { key: 'service_creation', label: 'Service Creation' }, { key: 'firewall_rules', label: 'Firewall Rules' }, { key: 'database_save', label: 'Database Save' }, { key: 'completed', label: 'Completed' } ]; export function ServerCreationPopup({ serverId, serverName, isOpen, onClose, onComplete }: ServerCreationPopupProps) { const [entries, setEntries] = useState([]); const [steps, setSteps] = useState>({}); const [isCompleted, setIsCompleted] = useState(false); const [completionResult, setCompletionResult] = useState<{ success: boolean; message: string; } | null>(null); const [isMinimized, setIsMinimized] = useState(false); const [isConsoleVisible, setIsConsoleVisible] = useState(true); const { associateWithServer, addMessageHandler, removeMessageHandler, connectionStatus, reconnect } = useWebSocket(); const consoleRef = useRef(null); const addEntry = useCallback((entry: Omit) => { const newEntry = { ...entry, id: `${Date.now()}-${Math.random()}` }; setEntries((prev) => [...prev, newEntry]); }, []); const scrollToBottom = useCallback(() => { if (consoleRef.current && !isMinimized && isConsoleVisible) { consoleRef.current.scrollTop = consoleRef.current.scrollHeight; } }, [isMinimized, isConsoleVisible]); useEffect(() => { scrollToBottom(); }, [entries, scrollToBottom]); useEffect(() => { if (serverId && isOpen) { associateWithServer(serverId); } }, [serverId, isOpen, associateWithServer]); const handleMessage = useCallback( (message: WebSocketMessage) => { if (message.server_id !== serverId) return; const timestamp = message.timestamp; switch (message.type) { case 'step': { const data = message.data as StepData; setSteps((prev) => { const updatedSteps = { ...prev }; const stepIndex = STEPS.findIndex((step) => step.key === data.step); if (stepIndex > 0) { for (let i = 0; i < stepIndex; i++) { const prevStepKey = STEPS[i].key; if (!updatedSteps[prevStepKey] || updatedSteps[prevStepKey].status === 'pending') { updatedSteps[prevStepKey] = { step: prevStepKey, status: 'completed', message: `${prevStepKey.replace('_', ' ')} completed` }; } } } updatedSteps[data.step] = { step: data.step, status: data.status, message: data.message }; return updatedSteps; }); let level: ConsoleEntry['level'] = 'info'; if (data.status === 'completed') level = 'success'; else if (data.status === 'failed') level = 'error'; else if (data.status === 'in_progress') level = 'warning'; addEntry({ timestamp, type: 'step', content: `[${data.step.toUpperCase()}] ${data.message}${data.error ? ` - ${data.error}` : ''}`, level }); break; } case 'steam_output': { const data = message.data as SteamOutputData; setSteps((prev) => { const updatedSteps = { ...prev }; if ( !updatedSteps['steam_download'] || updatedSteps['steam_download'].status === 'pending' ) { updatedSteps['steam_download'] = { step: 'steam_download', status: 'in_progress', message: 'Steam download in progress' }; } if (!updatedSteps['validation'] || updatedSteps['validation'].status === 'pending') { updatedSteps['validation'] = { step: 'validation', status: 'completed', message: 'Validation completed' }; } if ( !updatedSteps['directory_creation'] || updatedSteps['directory_creation'].status === 'pending' ) { updatedSteps['directory_creation'] = { step: 'directory_creation', status: 'completed', message: 'Directory creation completed' }; } return updatedSteps; }); addEntry({ timestamp, type: 'steam_output', content: data.output, level: data.is_error ? 'error' : 'info' }); break; } case 'error': { const data = message.data as ErrorData; setSteps((prev) => { const updatedSteps = { ...prev }; const currentStep = Object.values(updatedSteps).find( (step) => step.status === 'in_progress' ); if (currentStep) { updatedSteps[currentStep.step] = { ...currentStep, status: 'failed', message: `${currentStep.step.replace('_', ' ')} failed: ${data.error}` }; } return updatedSteps; }); addEntry({ timestamp, type: 'error', content: `ERROR: ${data.error}${data.details ? ` - ${data.details}` : ''}`, level: 'error' }); break; } case 'complete': { const data = message.data as CompleteData; setIsCompleted(true); setCompletionResult({ success: data.success, message: data.message }); addEntry({ timestamp, type: 'complete', content: `COMPLETED: ${data.message}`, level: data.success ? 'success' : 'error' }); onComplete?.(data.success, data.message); break; } } }, [serverId, addEntry, onComplete] ); useEffect(() => { if (isOpen) { addMessageHandler(handleMessage); return () => { removeMessageHandler(handleMessage); }; } }, [addMessageHandler, removeMessageHandler, handleMessage, isOpen]); const handleReconnect = async () => { try { await reconnect(); } catch (error) { console.error('Failed to reconnect:', error); } }; const getStepStatusIcon = (status: StepStatus['status']) => { switch (status) { case 'pending': return '⏳'; case 'in_progress': return '🔄'; case 'completed': return '✅'; case 'failed': return '❌'; } }; const getEntryClassName = (level: ConsoleEntry['level']) => { switch (level) { case 'success': return 'text-green-400'; case 'warning': return 'text-yellow-400'; case 'error': return 'text-red-400'; default: return 'text-gray-300'; } }; const getConnectionStatusIcon = () => { switch (connectionStatus) { case 'connected': return '🟢'; case 'connecting': return '🟡'; case 'disconnected': return '⚫'; case 'error': return '🔴'; } }; const getCurrentProgress = () => { const completedSteps = Object.values(steps).filter( (step) => step.status === 'completed' ).length; const totalSteps = STEPS.length; return { completed: completedSteps, total: totalSteps }; }; if (!isOpen) return null; if (isMinimized) { const progress = getCurrentProgress(); const isProgressing = !isCompleted && Object.values(steps).some((step) => step.status === 'in_progress'); return (
); } return (
🔧

{serverName}

{getConnectionStatusIcon()} {(connectionStatus === 'disconnected' || connectionStatus === 'error') && ( )}
{STEPS.map(({ key, label }) => { const stepStatus = steps[key]; return (
{getStepStatusIcon(stepStatus?.status || 'pending')} {label}
); })}
{isConsoleVisible && (
{entries.map((entry) => (
{new Date(entry.timestamp * 1000).toLocaleTimeString()} {' '} {entry.content}
))} {entries.length === 0 && (
Waiting for server creation to begin...
)}
)} {isCompleted && (
{completionResult?.message}
)}
); }