 c177363a19
			
		
	
	c177363a19
	
	
	
		
			
			🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			414 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 'use client'
 | |
| 
 | |
| import { useState, useEffect } from 'react'
 | |
| import { 
 | |
|   GlobeAltIcon,
 | |
|   ServerIcon,
 | |
|   ShieldCheckIcon,
 | |
|   ExclamationTriangleIcon,
 | |
|   CheckCircleIcon,
 | |
|   InformationCircleIcon
 | |
| } from '@heroicons/react/24/outline'
 | |
| 
 | |
| interface NetworkInterface {
 | |
|   name: string
 | |
|   ip: string
 | |
|   status: string
 | |
|   speed?: string
 | |
| }
 | |
| 
 | |
| interface NetworkConfig {
 | |
|   primaryInterface: string
 | |
|   primaryIP: string
 | |
|   bzzzPort: number
 | |
|   mcpPort: number
 | |
|   webUIPort: number
 | |
|   p2pPort: number
 | |
|   autoFirewall: boolean
 | |
|   allowedIPs: string[]
 | |
|   dnsServers: string[]
 | |
| }
 | |
| 
 | |
| interface NetworkConfigurationProps {
 | |
|   systemInfo: any
 | |
|   configData: any
 | |
|   onComplete: (data: any) => void
 | |
|   onBack?: () => void
 | |
|   isCompleted: boolean
 | |
| }
 | |
| 
 | |
| export default function NetworkConfiguration({ 
 | |
|   systemInfo, 
 | |
|   configData, 
 | |
|   onComplete, 
 | |
|   onBack, 
 | |
|   isCompleted 
 | |
| }: NetworkConfigurationProps) {
 | |
|   const [config, setConfig] = useState<NetworkConfig>({
 | |
|     primaryInterface: '',
 | |
|     primaryIP: '',
 | |
|     bzzzPort: 8080,
 | |
|     mcpPort: 3000,
 | |
|     webUIPort: 8080,
 | |
|     p2pPort: 7000,
 | |
|     autoFirewall: true,
 | |
|     allowedIPs: ['192.168.0.0/16', '10.0.0.0/8', '172.16.0.0/12'],
 | |
|     dnsServers: ['8.8.8.8', '8.8.4.4']
 | |
|   })
 | |
| 
 | |
|   const [errors, setErrors] = useState<string[]>([])
 | |
|   const [portConflicts, setPortConflicts] = useState<string[]>([])
 | |
| 
 | |
|   // Initialize with system info and existing config
 | |
|   useEffect(() => {
 | |
|     if (systemInfo?.network) {
 | |
|       setConfig(prev => ({
 | |
|         ...prev,
 | |
|         primaryInterface: systemInfo.network.interfaces?.[0] || prev.primaryInterface,
 | |
|         primaryIP: systemInfo.network.private_ips?.[0] || prev.primaryIP
 | |
|       }))
 | |
|     }
 | |
|     
 | |
|     if (configData.network) {
 | |
|       setConfig(prev => ({ ...prev, ...configData.network }))
 | |
|     }
 | |
|   }, [systemInfo, configData])
 | |
| 
 | |
|   // Validate configuration
 | |
|   useEffect(() => {
 | |
|     validateConfiguration()
 | |
|   }, [config])
 | |
| 
 | |
|   const validateConfiguration = () => {
 | |
|     const newErrors: string[] = []
 | |
|     const conflicts: string[] = []
 | |
| 
 | |
|     // Check for port conflicts
 | |
|     const ports = [config.bzzzPort, config.mcpPort, config.webUIPort, config.p2pPort]
 | |
|     const uniquePorts = new Set(ports)
 | |
|     if (uniquePorts.size !== ports.length) {
 | |
|       conflicts.push('Port numbers must be unique')
 | |
|     }
 | |
| 
 | |
|     // Check port ranges
 | |
|     ports.forEach((port, index) => {
 | |
|       const portNames = ['BZZZ API', 'MCP Server', 'Web UI', 'P2P Network']
 | |
|       if (port < 1024) {
 | |
|         newErrors.push(`${portNames[index]} port should be above 1024 to avoid requiring root privileges`)
 | |
|       }
 | |
|       if (port > 65535) {
 | |
|         newErrors.push(`${portNames[index]} port must be below 65536`)
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     // Validate IP addresses in allowed IPs
 | |
|     config.allowedIPs.forEach(ip => {
 | |
|       if (ip && !isValidCIDR(ip)) {
 | |
|         newErrors.push(`Invalid CIDR notation: ${ip}`)
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     // Validate DNS servers
 | |
|     config.dnsServers.forEach(dns => {
 | |
|       if (dns && !isValidIPAddress(dns)) {
 | |
|         newErrors.push(`Invalid DNS server IP: ${dns}`)
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     setErrors(newErrors)
 | |
|     setPortConflicts(conflicts)
 | |
|   }
 | |
| 
 | |
|   const isValidCIDR = (cidr: string): boolean => {
 | |
|     const regex = /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/
 | |
|     return regex.test(cidr)
 | |
|   }
 | |
| 
 | |
|   const isValidIPAddress = (ip: string): boolean => {
 | |
|     const regex = /^(\d{1,3}\.){3}\d{1,3}$/
 | |
|     if (!regex.test(ip)) return false
 | |
|     return ip.split('.').every(part => parseInt(part) >= 0 && parseInt(part) <= 255)
 | |
|   }
 | |
| 
 | |
|   const handlePortChange = (field: keyof NetworkConfig, value: string) => {
 | |
|     const numValue = parseInt(value) || 0
 | |
|     setConfig(prev => ({ ...prev, [field]: numValue }))
 | |
|   }
 | |
| 
 | |
|   const handleArrayChange = (field: 'allowedIPs' | 'dnsServers', index: number, value: string) => {
 | |
|     setConfig(prev => ({
 | |
|       ...prev,
 | |
|       [field]: prev[field].map((item, i) => i === index ? value : item)
 | |
|     }))
 | |
|   }
 | |
| 
 | |
|   const addArrayItem = (field: 'allowedIPs' | 'dnsServers') => {
 | |
|     setConfig(prev => ({
 | |
|       ...prev,
 | |
|       [field]: [...prev[field], '']
 | |
|     }))
 | |
|   }
 | |
| 
 | |
|   const removeArrayItem = (field: 'allowedIPs' | 'dnsServers', index: number) => {
 | |
|     setConfig(prev => ({
 | |
|       ...prev,
 | |
|       [field]: prev[field].filter((_, i) => i !== index)
 | |
|     }))
 | |
|   }
 | |
| 
 | |
|   const handleSubmit = (e: React.FormEvent) => {
 | |
|     e.preventDefault()
 | |
|     
 | |
|     if (errors.length === 0 && portConflicts.length === 0) {
 | |
|       onComplete({ network: config })
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const isFormValid = errors.length === 0 && portConflicts.length === 0
 | |
| 
 | |
|   return (
 | |
|     <form onSubmit={handleSubmit} className="space-y-6">
 | |
|       {/* Network Interface Selection */}
 | |
|       <div className="bg-gray-50 rounded-lg p-6">
 | |
|         <h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
 | |
|           <GlobeAltIcon className="h-6 w-6 text-bzzz-primary mr-2" />
 | |
|           Network Interface
 | |
|         </h3>
 | |
|         
 | |
|         {systemInfo?.network?.interfaces && (
 | |
|           <div className="space-y-3">
 | |
|             <label className="label">Primary Network Interface</label>
 | |
|             <select
 | |
|               value={config.primaryInterface}
 | |
|               onChange={(e) => setConfig(prev => ({ ...prev, primaryInterface: e.target.value }))}
 | |
|               className="input-field"
 | |
|             >
 | |
|               <option value="">Select network interface</option>
 | |
|               {systemInfo.network.interfaces.map((interfaceName: string, index: number) => (
 | |
|                 <option key={index} value={interfaceName}>
 | |
|                   {interfaceName} - {systemInfo.network.private_ips[index] || 'Unknown IP'}
 | |
|                 </option>
 | |
|               ))}
 | |
|             </select>
 | |
|             
 | |
|             {config.primaryInterface && (
 | |
|               <div className="text-sm text-gray-600">
 | |
|                 Primary IP: {systemInfo.network.private_ips?.[systemInfo.network.interfaces.indexOf(config.primaryInterface)] || 'Unknown'}
 | |
|               </div>
 | |
|             )}
 | |
|           </div>
 | |
|         )}
 | |
|       </div>
 | |
| 
 | |
|       {/* Port Configuration */}
 | |
|       <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|         <h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
 | |
|           <ServerIcon className="h-6 w-6 text-bzzz-primary mr-2" />
 | |
|           Port Configuration
 | |
|         </h3>
 | |
|         
 | |
|         <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 | |
|           <div>
 | |
|             <label className="label">BZZZ API Port</label>
 | |
|             <input
 | |
|               type="number"
 | |
|               value={config.bzzzPort}
 | |
|               onChange={(e) => handlePortChange('bzzzPort', e.target.value)}
 | |
|               min="1024"
 | |
|               max="65535"
 | |
|               className="input-field"
 | |
|             />
 | |
|             <p className="text-sm text-gray-600 mt-1">Main BZZZ HTTP API endpoint</p>
 | |
|           </div>
 | |
| 
 | |
|           <div>
 | |
|             <label className="label">MCP Server Port</label>
 | |
|             <input
 | |
|               type="number"
 | |
|               value={config.mcpPort}
 | |
|               onChange={(e) => handlePortChange('mcpPort', e.target.value)}
 | |
|               min="1024"
 | |
|               max="65535"
 | |
|               className="input-field"
 | |
|             />
 | |
|             <p className="text-sm text-gray-600 mt-1">Model Context Protocol server</p>
 | |
|           </div>
 | |
| 
 | |
|           <div>
 | |
|             <label className="label">Web UI Port</label>
 | |
|             <input
 | |
|               type="number"
 | |
|               value={config.webUIPort}
 | |
|               onChange={(e) => handlePortChange('webUIPort', e.target.value)}
 | |
|               min="1024"
 | |
|               max="65535"
 | |
|               className="input-field"
 | |
|             />
 | |
|             <p className="text-sm text-gray-600 mt-1">Web interface port</p>
 | |
|           </div>
 | |
| 
 | |
|           <div>
 | |
|             <label className="label">P2P Network Port</label>
 | |
|             <input
 | |
|               type="number"
 | |
|               value={config.p2pPort}
 | |
|               onChange={(e) => handlePortChange('p2pPort', e.target.value)}
 | |
|               min="1024"
 | |
|               max="65535"
 | |
|               className="input-field"
 | |
|             />
 | |
|             <p className="text-sm text-gray-600 mt-1">Peer-to-peer communication</p>
 | |
|           </div>
 | |
|         </div>
 | |
| 
 | |
|         {portConflicts.length > 0 && (
 | |
|           <div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg">
 | |
|             <div className="flex items-center">
 | |
|               <ExclamationTriangleIcon className="h-5 w-5 text-red-600 mr-2" />
 | |
|               <span className="text-red-800 font-medium">Port Conflicts</span>
 | |
|             </div>
 | |
|             {portConflicts.map((conflict, index) => (
 | |
|               <p key={index} className="text-red-700 text-sm mt-1">{conflict}</p>
 | |
|             ))}
 | |
|           </div>
 | |
|         )}
 | |
|       </div>
 | |
| 
 | |
|       {/* Security & Access Control */}
 | |
|       <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|         <h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
 | |
|           <ShieldCheckIcon className="h-6 w-6 text-bzzz-primary mr-2" />
 | |
|           Security & Access Control
 | |
|         </h3>
 | |
|         
 | |
|         <div className="space-y-4">
 | |
|           <div className="flex items-center">
 | |
|             <input
 | |
|               type="checkbox"
 | |
|               id="autoFirewall"
 | |
|               checked={config.autoFirewall}
 | |
|               onChange={(e) => setConfig(prev => ({ ...prev, autoFirewall: e.target.checked }))}
 | |
|               className="h-4 w-4 text-bzzz-primary focus:ring-bzzz-primary border-gray-300 rounded"
 | |
|             />
 | |
|             <label htmlFor="autoFirewall" className="ml-2 text-sm font-medium text-gray-700">
 | |
|               Automatically configure firewall rules
 | |
|             </label>
 | |
|           </div>
 | |
| 
 | |
|           <div>
 | |
|             <label className="label">Allowed IP Ranges (CIDR)</label>
 | |
|             {config.allowedIPs.map((ip, index) => (
 | |
|               <div key={index} className="flex items-center space-x-2 mb-2">
 | |
|                 <input
 | |
|                   type="text"
 | |
|                   value={ip}
 | |
|                   onChange={(e) => handleArrayChange('allowedIPs', index, e.target.value)}
 | |
|                   placeholder="192.168.1.0/24"
 | |
|                   className="input-field flex-1"
 | |
|                 />
 | |
|                 <button
 | |
|                   type="button"
 | |
|                   onClick={() => removeArrayItem('allowedIPs', index)}
 | |
|                   className="text-red-600 hover:text-red-800"
 | |
|                 >
 | |
|                   Remove
 | |
|                 </button>
 | |
|               </div>
 | |
|             ))}
 | |
|             <button
 | |
|               type="button"
 | |
|               onClick={() => addArrayItem('allowedIPs')}
 | |
|               className="text-bzzz-primary hover:text-bzzz-primary/80 text-sm"
 | |
|             >
 | |
|               + Add IP Range
 | |
|             </button>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       {/* DNS Configuration */}
 | |
|       <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|         <h3 className="text-lg font-medium text-gray-900 mb-4">DNS Configuration</h3>
 | |
|         
 | |
|         <div>
 | |
|           <label className="label">DNS Servers</label>
 | |
|           {config.dnsServers.map((dns, index) => (
 | |
|             <div key={index} className="flex items-center space-x-2 mb-2">
 | |
|               <input
 | |
|                 type="text"
 | |
|                 value={dns}
 | |
|                 onChange={(e) => handleArrayChange('dnsServers', index, e.target.value)}
 | |
|                 placeholder="8.8.8.8"
 | |
|                 className="input-field flex-1"
 | |
|               />
 | |
|               <button
 | |
|                 type="button"
 | |
|                 onClick={() => removeArrayItem('dnsServers', index)}
 | |
|                 className="text-red-600 hover:text-red-800"
 | |
|               >
 | |
|                 Remove
 | |
|               </button>
 | |
|             </div>
 | |
|           ))}
 | |
|           <button
 | |
|             type="button"
 | |
|             onClick={() => addArrayItem('dnsServers')}
 | |
|             className="text-bzzz-primary hover:text-bzzz-primary/80 text-sm"
 | |
|           >
 | |
|             + Add DNS Server
 | |
|           </button>
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       {/* Validation Errors */}
 | |
|       {errors.length > 0 && (
 | |
|         <div className="bg-red-50 border border-red-200 rounded-lg p-4">
 | |
|           <div className="flex items-center mb-2">
 | |
|             <ExclamationTriangleIcon className="h-5 w-5 text-red-600 mr-2" />
 | |
|             <span className="text-red-800 font-medium">Configuration Issues</span>
 | |
|           </div>
 | |
|           {errors.map((error, index) => (
 | |
|             <p key={index} className="text-red-700 text-sm">{error}</p>
 | |
|           ))}
 | |
|         </div>
 | |
|       )}
 | |
| 
 | |
|       {/* Configuration Summary */}
 | |
|       {isFormValid && (
 | |
|         <div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
 | |
|           <div className="flex items-center mb-2">
 | |
|             <InformationCircleIcon className="h-5 w-5 text-blue-600 mr-2" />
 | |
|             <span className="text-blue-800 font-medium">Configuration Summary</span>
 | |
|           </div>
 | |
|           <div className="text-blue-700 text-sm space-y-1">
 | |
|             <p>• Primary interface: {config.primaryInterface}</p>
 | |
|             <p>• BZZZ API will be available on port {config.bzzzPort}</p>
 | |
|             <p>• MCP server will run on port {config.mcpPort}</p>
 | |
|             <p>• Web UI will be accessible on port {config.webUIPort}</p>
 | |
|             <p>• P2P network will use port {config.p2pPort}</p>
 | |
|             {config.autoFirewall && <p>• Firewall rules will be configured automatically</p>}
 | |
|           </div>
 | |
|         </div>
 | |
|       )}
 | |
| 
 | |
|       {/* Action Buttons */}
 | |
|       <div className="flex justify-between pt-6 border-t border-gray-200">
 | |
|         <div>
 | |
|           {onBack && (
 | |
|             <button type="button" onClick={onBack} className="btn-outline">
 | |
|               Back
 | |
|             </button>
 | |
|           )}
 | |
|         </div>
 | |
|         
 | |
|         <button
 | |
|           type="submit"
 | |
|           disabled={!isFormValid}
 | |
|           className="btn-primary"
 | |
|         >
 | |
|           {isCompleted ? 'Continue' : 'Next: Security Setup'}
 | |
|         </button>
 | |
|       </div>
 | |
|     </form>
 | |
|   )
 | |
| } |