- Updated configuration and deployment files - Improved system architecture and components - Enhanced documentation and testing - Fixed various issues and added new features 🤖 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>
|
|
)
|
|
} |