'use client' import { useState, useEffect } from 'react' import { ServerIcon, ExclamationTriangleIcon, CheckCircleIcon, XCircleIcon, PlayIcon, StopIcon, TrashIcon, DocumentTextIcon, ArrowPathIcon, CloudArrowDownIcon, Cog6ToothIcon, XMarkIcon, ComputerDesktopIcon } from '@heroicons/react/24/outline' interface Machine { id: string hostname: string ip: string os: string osVersion: string sshStatus: 'unknown' | 'connected' | 'failed' | 'testing' deployStatus: 'not_deployed' | 'installing' | 'running' | 'stopped' | 'error' selected: boolean lastSeen?: string deployProgress?: number deployStep?: string systemInfo?: { cpu: number memory: number disk: number } } interface ServiceDeploymentProps { systemInfo: any configData: any onComplete: (data: any) => void onBack?: () => void isCompleted: boolean } export default function ServiceDeployment({ systemInfo, configData, onComplete, onBack, isCompleted }: ServiceDeploymentProps) { const [machines, setMachines] = useState([]) const [isDiscovering, setIsDiscovering] = useState(false) const [discoveryProgress, setDiscoveryProgress] = useState(0) const [discoveryStatus, setDiscoveryStatus] = useState('') const [showLogs, setShowLogs] = useState(null) const [deploymentLogs, setDeploymentLogs] = useState<{[key: string]: string[]}>({}) const [showConsole, setShowConsole] = useState(null) const [consoleLogs, setConsoleLogs] = useState<{[key: string]: string[]}>({}) const [config, setConfig] = useState({ deploymentMethod: 'systemd', autoStart: true, healthCheckInterval: 30, selectedMachines: [] as string[] }) // Initialize with current machine useEffect(() => { const currentMachine: Machine = { id: 'localhost', hostname: systemInfo?.network?.hostname || 'localhost', ip: configData?.network?.primaryIP || '127.0.0.1', os: systemInfo?.os || 'linux', osVersion: 'Current Host', sshStatus: 'connected', deployStatus: 'running', // Already running since we're in setup selected: true, systemInfo: { cpu: systemInfo?.cpu_cores || 0, memory: Math.round((systemInfo?.memory_mb || 0) / 1024), disk: systemInfo?.storage?.free_space_gb || 0 } } setMachines([currentMachine]) setConfig(prev => ({ ...prev, selectedMachines: ['localhost'] })) }, [systemInfo, configData]) const discoverMachines = async () => { setIsDiscovering(true) setDiscoveryProgress(0) setDiscoveryStatus('Initializing network scan...') try { // Simulate progress updates during discovery const progressInterval = setInterval(() => { setDiscoveryProgress(prev => { const newProgress = prev + 10 if (newProgress <= 30) { setDiscoveryStatus('Scanning network subnet...') } else if (newProgress <= 60) { setDiscoveryStatus('Checking SSH accessibility...') } else if (newProgress <= 90) { setDiscoveryStatus('Gathering system information...') } else { setDiscoveryStatus('Finalizing discovery...') } return Math.min(newProgress, 95) }) }, 200) const response = await fetch('/api/setup/discover-machines', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subnet: configData?.network?.allowedIPs?.[0] || '192.168.1.0/24', sshKey: configData?.security?.sshPublicKey }) }) clearInterval(progressInterval) setDiscoveryProgress(100) if (response.ok) { const result = await response.json() setDiscoveryStatus(`Found ${result.machines?.length || 0} machines`) const discoveredMachines: Machine[] = result.machines.map((m: any) => ({ id: m.ip, hostname: m.hostname || 'Unknown', ip: m.ip, os: m.os || 'unknown', osVersion: m.os_version || 'Unknown', sshStatus: 'unknown', deployStatus: 'not_deployed', selected: false, lastSeen: new Date().toISOString(), systemInfo: m.system_info })) // Merge with existing machines (keep localhost) setMachines(prev => { const localhost = prev.find(m => m.id === 'localhost') return localhost ? [localhost, ...discoveredMachines] : discoveredMachines }) } else { setDiscoveryStatus('Discovery failed - check network configuration') } } catch (error) { console.error('Discovery failed:', error) setDiscoveryStatus('Discovery error - network unreachable') } finally { setTimeout(() => { setIsDiscovering(false) setDiscoveryProgress(0) setDiscoveryStatus('') }, 2000) } } const testSSHConnection = async (machineId: string) => { setMachines(prev => prev.map(m => m.id === machineId ? { ...m, sshStatus: 'testing' } : m )) try { const machine = machines.find(m => m.id === machineId) const response = await fetch('/api/setup/test-ssh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ip: machine?.ip, sshKey: configData?.security?.sshPrivateKey, sshUsername: configData?.security?.sshUsername || 'ubuntu', sshPassword: configData?.security?.sshPassword, sshPort: configData?.security?.sshPort || 22 }) }) const result = await response.json() setMachines(prev => prev.map(m => m.id === machineId ? { ...m, sshStatus: result.success ? 'connected' : 'failed', os: result.os || m.os, osVersion: result.os_version || m.osVersion, systemInfo: result.system_info || m.systemInfo } : m )) } catch (error) { setMachines(prev => prev.map(m => m.id === machineId ? { ...m, sshStatus: 'failed' } : m )) } } const deployToMachine = async (machineId: string) => { setMachines(prev => prev.map(m => m.id === machineId ? { ...m, deployStatus: 'installing', deployProgress: 0, deployStep: 'Initializing deployment...' } : m )) const logs: string[] = [] const consoleLogs: string[] = [`🚀 Starting deployment to ${machines.find(m => m.id === machineId)?.hostname} (${machines.find(m => m.id === machineId)?.ip})`] setDeploymentLogs(prev => ({ ...prev, [machineId]: logs })) setConsoleLogs(prev => ({ ...prev, [machineId]: consoleLogs })) // Open console if not already showing if (!showConsole) { setShowConsole(machineId) } // Real-time console logging helper const addConsoleLog = (message: string) => { const timestamp = new Date().toLocaleTimeString() const logMessage = `[${timestamp}] ${message}` setConsoleLogs(prev => ({ ...prev, [machineId]: [...(prev[machineId] || []), logMessage] })) } // Simulate progress updates const progressSteps = [ { progress: 10, step: 'Establishing SSH connection...' }, { progress: 30, step: 'Copying BZZZ binary...' }, { progress: 60, step: 'Creating systemd service...' }, { progress: 80, step: 'Starting service...' }, { progress: 100, step: 'Deployment complete!' } ] const updateProgress = (stepIndex: number) => { if (stepIndex < progressSteps.length) { const { progress, step } = progressSteps[stepIndex] setMachines(prev => prev.map(m => m.id === machineId ? { ...m, deployProgress: progress, deployStep: step } : m )) logs.push(`📦 ${step}`) addConsoleLog(`📦 ${step}`) setDeploymentLogs(prev => ({ ...prev, [machineId]: [...(prev[machineId] || []), `📦 ${step}`] })) } } try { const machine = machines.find(m => m.id === machineId) addConsoleLog(`🚀 Starting deployment to ${machine?.hostname}...`) addConsoleLog(`📡 Sending deployment request to backend API...`) // Set initial progress setMachines(prev => prev.map(m => m.id === machineId ? { ...m, deployProgress: 10, deployStep: 'Contacting backend API...' } : m )) const response = await fetch('/api/setup/deploy-service', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ip: machine?.ip, sshKey: configData?.security?.sshPrivateKey, sshUsername: configData?.security?.sshUsername || 'ubuntu', sshPassword: configData?.security?.sshPassword, sshPort: configData?.security?.sshPort || 22, config: { ports: { api: configData?.network?.bzzzPort || 8080, mcp: configData?.network?.mcpPort || 3000, webui: configData?.network?.webUIPort || 8080, p2p: configData?.network?.p2pPort || 7000 }, security: configData?.security, autoStart: config.autoStart } }) }) const result = await response.json() addConsoleLog(`📨 Received response from backend API`) if (result.success) { setMachines(prev => prev.map(m => m.id === machineId ? { ...m, deployStatus: 'running', deployProgress: 100, deployStep: 'Running' } : m )) logs.push('✅ Deployment completed successfully') addConsoleLog('✅ Deployment completed successfully!') // Show actual backend steps if provided if (result.steps) { result.steps.forEach((step: string) => { logs.push(step) addConsoleLog(`📋 ${step}`) }) } addConsoleLog(`🎉 CHORUS:agents service is now running on ${machine?.hostname}`) } else { setMachines(prev => prev.map(m => m.id === machineId ? { ...m, deployStatus: 'error', deployProgress: 0, deployStep: 'Failed' } : m )) logs.push(`❌ Deployment failed: ${result.error}`) addConsoleLog(`❌ Deployment failed: ${result.error}`) addConsoleLog(`💡 Note: This was a real backend error, not simulated progress`) } } catch (error) { setMachines(prev => prev.map(m => m.id === machineId ? { ...m, deployStatus: 'error', deployProgress: 0, deployStep: 'Error' } : m )) logs.push(`❌ Deployment error: ${error}`) addConsoleLog(`❌ Deployment error: ${error}`) } setDeploymentLogs(prev => ({ ...prev, [machineId]: logs })) } const toggleMachineSelection = (machineId: string) => { setMachines(prev => prev.map(m => m.id === machineId ? { ...m, selected: !m.selected } : m )) setConfig(prev => ({ ...prev, selectedMachines: machines .map(m => m.id === machineId ? { ...m, selected: !m.selected } : m) .filter(m => m.selected) .map(m => m.id) })) } const deployToSelected = async () => { const selectedMachines = machines.filter(m => m.selected && m.sshStatus === 'connected') for (const machine of selectedMachines) { if (machine.deployStatus === 'not_deployed') { await deployToMachine(machine.id) } } } const removeMachine = (machineId: string) => { // Don't allow removing localhost if (machineId === 'localhost') return setMachines(prev => prev.filter(m => m.id !== machineId)) setConfig(prev => ({ ...prev, selectedMachines: prev.selectedMachines.filter(id => id !== machineId) })) // Clean up logs for removed machine setDeploymentLogs(prev => { const { [machineId]: removed, ...rest } = prev return rest }) } const getStatusIcon = (status: string) => { switch (status) { case 'connected': return case 'failed': return case 'testing': return case 'running': return case 'installing': return case 'error': return case 'stopped': return default: return } } const handleSubmit = (e: React.FormEvent) => { e.preventDefault() onComplete({ deployment: { ...config, machines: machines.filter(m => m.selected).map(m => ({ id: m.id, ip: m.ip, hostname: m.hostname, deployStatus: m.deployStatus })) } }) } return (
{/* OS Support Caution */}

Operating System Support

CHORUS:agents automated deployment supports Linux distributions that use systemd by default (Ubuntu 16+, CentOS 7+, Debian 8+, RHEL 7+, etc.). For other operating systems or init systems, you'll need to manually deploy the CHORUS:agents binary and configure services on your cluster.

{/* Network Discovery */}

Machine Discovery

Scan network subnet: {configData?.network?.allowedIPs?.[0] || '192.168.1.0/24'}

{/* Discovery Progress */} {isDiscovering && (
{discoveryStatus} {discoveryProgress}%
)}
{/* Machine Table */}

Cluster Machines

{machines.map((machine) => ( ))}
Select Machine Operating System IP Address SSH Status Deploy Status Actions Remove
toggleMachineSelection(machine.id)} className="h-4 w-4 text-bzzz-primary focus:ring-bzzz-primary border-gray-300 rounded" />
{machine.hostname}
{machine.systemInfo && (
{machine.systemInfo.cpu} cores • {machine.systemInfo.memory}GB RAM • {machine.systemInfo.disk}GB disk
)}
{machine.os}
{machine.osVersion}
{machine.ip}
{getStatusIcon(machine.sshStatus)} {machine.sshStatus.replace('_', ' ')}
{getStatusIcon(machine.deployStatus)}
{machine.deployStatus.replace('_', ' ')}
{machine.deployStatus === 'installing' && (
{machine.deployStep || 'Deploying...'}
{machine.deployProgress || 0}%
)}
{machine.id !== 'localhost' && machine.sshStatus !== 'connected' && ( )} {machine.sshStatus === 'connected' && machine.deployStatus === 'not_deployed' && ( )} {machine.sshStatus === 'connected' && machine.deployStatus === 'error' && ( )} {machine.deployStatus !== 'not_deployed' && ( <> )} {machine.id !== 'localhost' && ( )}
{machines.length === 0 && (

No machines discovered yet. Click "Discover Machines" to scan your network.

)}
{/* Deployment Configuration */}

Deployment Configuration

setConfig(prev => ({ ...prev, healthCheckInterval: parseInt(e.target.value) }))} min="10" max="300" className="input-field" />
{/* Logs Modal */} {showLogs && (

Deployment Logs - {machines.find(m => m.id === showLogs)?.hostname}

{deploymentLogs[showLogs]?.map((log, index) => (
{log}
)) ||
No logs available
}
)} {/* Virtual Console Modal */} {showConsole && (

SSH Console - {machines.find(m => m.id === showConsole)?.hostname}

({machines.find(m => m.id === showConsole)?.ip})
{consoleLogs[showConsole]?.length > 0 ? ( consoleLogs[showConsole].map((log, index) => (
{log}
)) ) : (
Waiting for deployment to start...
)} {/* Blinking cursor */}
💡 This console shows real-time deployment progress and SSH operations
{(() => { const machine = machines.find(m => m.id === showConsole) return machine?.sshStatus === 'connected' && machine?.deployStatus === 'error' && ( ) })()}
)}
{onBack && ( )}
) }