 c177363a19
			
		
	
	c177363a19
	
	
	
		
			
			🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			403 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 'use client'
 | |
| 
 | |
| import { useState, useEffect } from 'react'
 | |
| import { 
 | |
|   CpuChipIcon, 
 | |
|   ServerIcon, 
 | |
|   CircleStackIcon,
 | |
|   GlobeAltIcon,
 | |
|   CheckCircleIcon,
 | |
|   ExclamationTriangleIcon,
 | |
|   ArrowPathIcon
 | |
| } from '@heroicons/react/24/outline'
 | |
| 
 | |
| interface SystemInfo {
 | |
|   os: string
 | |
|   architecture: string
 | |
|   cpu_cores: number
 | |
|   memory_mb: number
 | |
|   gpus: Array<{
 | |
|     name: string
 | |
|     memory: string
 | |
|     driver: string
 | |
|     type: string
 | |
|   }>
 | |
|   network: {
 | |
|     hostname: string
 | |
|     interfaces: string[]
 | |
|     public_ip?: string
 | |
|     private_ips: string[]
 | |
|     docker_bridge?: string
 | |
|   }
 | |
|   storage: {
 | |
|     total_space_gb: number
 | |
|     free_space_gb: number
 | |
|     mount_path: string
 | |
|   }
 | |
|   docker: {
 | |
|     available: boolean
 | |
|     version?: string
 | |
|     compose_available: boolean
 | |
|     swarm_mode: boolean
 | |
|   }
 | |
| }
 | |
| 
 | |
| interface SystemDetectionProps {
 | |
|   systemInfo: SystemInfo | null
 | |
|   configData: any
 | |
|   onComplete: (data: any) => void
 | |
|   onBack?: () => void
 | |
|   isCompleted: boolean
 | |
| }
 | |
| 
 | |
| export default function SystemDetection({ 
 | |
|   systemInfo, 
 | |
|   configData, 
 | |
|   onComplete, 
 | |
|   onBack, 
 | |
|   isCompleted 
 | |
| }: SystemDetectionProps) {
 | |
|   const [loading, setLoading] = useState(!systemInfo)
 | |
|   const [refreshing, setRefreshing] = useState(false)
 | |
|   const [detectedInfo, setDetectedInfo] = useState<SystemInfo | null>(systemInfo)
 | |
| 
 | |
|   useEffect(() => {
 | |
|     if (!detectedInfo) {
 | |
|       refreshSystemInfo()
 | |
|     }
 | |
|   }, [])
 | |
| 
 | |
|   const refreshSystemInfo = async () => {
 | |
|     setRefreshing(true)
 | |
|     try {
 | |
|       const response = await fetch('/api/setup/system')
 | |
|       if (response.ok) {
 | |
|         const result = await response.json()
 | |
|         setDetectedInfo(result.system_info)
 | |
|       }
 | |
|     } catch (error) {
 | |
|       console.error('Failed to detect system info:', error)
 | |
|     } finally {
 | |
|       setLoading(false)
 | |
|       setRefreshing(false)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const handleContinue = () => {
 | |
|     if (detectedInfo) {
 | |
|       onComplete({
 | |
|         system: detectedInfo,
 | |
|         validated: true
 | |
|       })
 | |
|     }
 | |
|   }
 | |
| 
 | |
| 
 | |
|   const getStatusColor = (condition: boolean) => {
 | |
|     return condition ? 'text-green-600' : 'text-red-600'
 | |
|   }
 | |
| 
 | |
|   const getStatusIcon = (condition: boolean) => {
 | |
|     return condition ? CheckCircleIcon : ExclamationTriangleIcon
 | |
|   }
 | |
| 
 | |
|   if (loading) {
 | |
|     return (
 | |
|       <div className="flex items-center justify-center py-12">
 | |
|         <div className="text-center">
 | |
|           <ArrowPathIcon className="h-8 w-8 text-bzzz-primary animate-spin mx-auto mb-4" />
 | |
|           <p className="text-gray-600">Detecting system configuration...</p>
 | |
|         </div>
 | |
|       </div>
 | |
|     )
 | |
|   }
 | |
| 
 | |
|   if (!detectedInfo) {
 | |
|     return (
 | |
|       <div className="text-center py-12">
 | |
|         <ExclamationTriangleIcon className="h-12 w-12 text-red-500 mx-auto mb-4" />
 | |
|         <h3 className="text-lg font-medium text-gray-900 mb-2">
 | |
|           System Detection Failed
 | |
|         </h3>
 | |
|         <p className="text-gray-600 mb-4">
 | |
|           Unable to detect system configuration. Please try again.
 | |
|         </p>
 | |
|         <button
 | |
|           onClick={refreshSystemInfo}
 | |
|           disabled={refreshing}
 | |
|           className="btn-primary"
 | |
|         >
 | |
|           {refreshing ? 'Retrying...' : 'Retry Detection'}
 | |
|         </button>
 | |
|       </div>
 | |
|     )
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <div className="space-y-6">
 | |
|       {/* System Overview */}
 | |
|       <div className="bg-gray-50 rounded-lg p-6">
 | |
|         <div className="flex items-center justify-between mb-4">
 | |
|           <h3 className="text-lg font-medium text-gray-900">System Overview</h3>
 | |
|           <button
 | |
|             onClick={refreshSystemInfo}
 | |
|             disabled={refreshing}
 | |
|             className="text-bzzz-primary hover:text-bzzz-primary/80 transition-colors"
 | |
|           >
 | |
|             <ArrowPathIcon className={`h-5 w-5 ${refreshing ? 'animate-spin' : ''}`} />
 | |
|           </button>
 | |
|         </div>
 | |
|         
 | |
|         <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 | |
|           <div>
 | |
|             <div className="text-sm font-medium text-gray-700">Hostname</div>
 | |
|             <div className="text-lg text-gray-900">{detectedInfo.network.hostname}</div>
 | |
|           </div>
 | |
|           <div>
 | |
|             <div className="text-sm font-medium text-gray-700">Operating System</div>
 | |
|             <div className="text-lg text-gray-900">
 | |
|               {detectedInfo.os} ({detectedInfo.architecture})
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       {/* Hardware Information */}
 | |
|       <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
 | |
|         {/* CPU & Memory */}
 | |
|         <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|           <div className="flex items-center mb-4">
 | |
|             <CpuChipIcon className="h-6 w-6 text-bzzz-primary mr-2" />
 | |
|             <h3 className="text-lg font-medium text-gray-900">CPU & Memory</h3>
 | |
|           </div>
 | |
|           
 | |
|           <div className="space-y-3">
 | |
|             <div>
 | |
|               <div className="text-sm font-medium text-gray-700">CPU</div>
 | |
|               <div className="text-gray-900">
 | |
|                 {detectedInfo.cpu_cores} cores
 | |
|               </div>
 | |
|             </div>
 | |
|             <div>
 | |
|               <div className="text-sm font-medium text-gray-700">Memory</div>
 | |
|               <div className="text-gray-900">
 | |
|                 {Math.round(detectedInfo.memory_mb / 1024)} GB total
 | |
|               </div>
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
| 
 | |
|         {/* Storage */}
 | |
|         <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|           <div className="flex items-center mb-4">
 | |
|             <CircleStackIcon className="h-6 w-6 text-bzzz-primary mr-2" />
 | |
|             <h3 className="text-lg font-medium text-gray-900">Storage</h3>
 | |
|           </div>
 | |
|           
 | |
|           <div className="space-y-3">
 | |
|             <div>
 | |
|               <div className="text-sm font-medium text-gray-700">Disk Space</div>
 | |
|               <div className="text-gray-900">
 | |
|                 {detectedInfo.storage.total_space_gb} GB total, {' '}
 | |
|                 {detectedInfo.storage.free_space_gb} GB available
 | |
|               </div>
 | |
|             </div>
 | |
|             <div className="w-full bg-gray-200 rounded-full h-2">
 | |
|               <div 
 | |
|                 className="bg-bzzz-primary h-2 rounded-full"
 | |
|                 style={{ 
 | |
|                   width: `${((detectedInfo.storage.total_space_gb - detectedInfo.storage.free_space_gb) / detectedInfo.storage.total_space_gb) * 100}%` 
 | |
|                 }}
 | |
|               />
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       {/* GPU Information */}
 | |
|       {detectedInfo.gpus && detectedInfo.gpus.length > 0 && (
 | |
|         <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|           <div className="flex items-center mb-4">
 | |
|             <ServerIcon className="h-6 w-6 text-bzzz-primary mr-2" />
 | |
|             <h3 className="text-lg font-medium text-gray-900">
 | |
|               GPU Configuration ({detectedInfo.gpus.length} GPU{detectedInfo.gpus.length !== 1 ? 's' : ''})
 | |
|             </h3>
 | |
|           </div>
 | |
|           
 | |
|           <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 | |
|             {detectedInfo.gpus.map((gpu, index) => (
 | |
|               <div key={index} className="bg-gray-50 rounded-lg p-4">
 | |
|                 <div className="font-medium text-gray-900">{gpu.name}</div>
 | |
|                 <div className="text-sm text-gray-600">
 | |
|                   {gpu.type.toUpperCase()} • {gpu.memory} • {gpu.driver}
 | |
|                 </div>
 | |
|               </div>
 | |
|             ))}
 | |
|           </div>
 | |
|         </div>
 | |
|       )}
 | |
| 
 | |
|       {/* Network Information */}
 | |
|       <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|         <div className="flex items-center mb-4">
 | |
|           <GlobeAltIcon className="h-6 w-6 text-bzzz-primary mr-2" />
 | |
|           <h3 className="text-lg font-medium text-gray-900">Network Configuration</h3>
 | |
|         </div>
 | |
|         
 | |
|         <div className="space-y-3">
 | |
|           <div>
 | |
|             <div className="text-sm font-medium text-gray-700">Hostname</div>
 | |
|             <div className="text-gray-900">{detectedInfo.network.hostname}</div>
 | |
|           </div>
 | |
|           
 | |
|           {detectedInfo.network.private_ips && detectedInfo.network.private_ips.length > 0 && (
 | |
|             <div>
 | |
|               <div className="text-sm font-medium text-gray-700 mb-2">Private IP Addresses</div>
 | |
|               <div className="space-y-2">
 | |
|                 {detectedInfo.network.private_ips.map((ip, index) => (
 | |
|                   <div key={index} className="flex justify-between items-center text-sm">
 | |
|                     <span>{ip}</span>
 | |
|                     <span className="status-indicator status-online">active</span>
 | |
|                   </div>
 | |
|                 ))}
 | |
|               </div>
 | |
|             </div>
 | |
|           )}
 | |
|           
 | |
|           {detectedInfo.network.public_ip && (
 | |
|             <div>
 | |
|               <div className="text-sm font-medium text-gray-700">Public IP</div>
 | |
|               <div className="text-gray-900">{detectedInfo.network.public_ip}</div>
 | |
|             </div>
 | |
|           )}
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       {/* Software Requirements */}
 | |
|       <div className="bg-white border border-gray-200 rounded-lg p-6">
 | |
|         <h3 className="text-lg font-medium text-gray-900 mb-4">Software Requirements</h3>
 | |
|         
 | |
|         <div className="space-y-4">
 | |
|           {[
 | |
|             {
 | |
|               name: 'Docker',
 | |
|               installed: detectedInfo.docker.available,
 | |
|               version: detectedInfo.docker.version,
 | |
|               required: true
 | |
|             },
 | |
|             {
 | |
|               name: 'Docker Compose',
 | |
|               installed: detectedInfo.docker.compose_available,
 | |
|               version: undefined,
 | |
|               required: false
 | |
|             },
 | |
|             {
 | |
|               name: 'Docker Swarm',
 | |
|               installed: detectedInfo.docker.swarm_mode,
 | |
|               version: undefined,
 | |
|               required: false
 | |
|             }
 | |
|           ].map((software, index) => {
 | |
|             const StatusIcon = getStatusIcon(software.installed)
 | |
|             return (
 | |
|               <div key={index} className="flex items-center justify-between">
 | |
|                 <div className="flex items-center">
 | |
|                   <StatusIcon className={`h-5 w-5 mr-3 ${getStatusColor(software.installed)}`} />
 | |
|                   <div>
 | |
|                     <div className="font-medium text-gray-900">{software.name}</div>
 | |
|                     {software.version && (
 | |
|                       <div className="text-sm text-gray-600">Version: {software.version}</div>
 | |
|                     )}
 | |
|                   </div>
 | |
|                 </div>
 | |
|                 <div className="flex items-center">
 | |
|                   {software.required && (
 | |
|                     <span className="text-xs bg-bzzz-primary text-white px-2 py-1 rounded mr-2">
 | |
|                       Required
 | |
|                     </span>
 | |
|                   )}
 | |
|                   <span className={`text-sm font-medium ${getStatusColor(software.installed)}`}>
 | |
|                     {software.installed ? 'Installed' : 'Missing'}
 | |
|                   </span>
 | |
|                 </div>
 | |
|               </div>
 | |
|             )
 | |
|           })}
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       {/* System Validation */}
 | |
|       <div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
 | |
|         <h3 className="text-lg font-medium text-blue-900 mb-4">System Validation</h3>
 | |
|         
 | |
|         <div className="space-y-2">
 | |
|           {[
 | |
|             { 
 | |
|               check: 'Minimum memory (2GB required)', 
 | |
|               passed: detectedInfo.memory_mb >= 2048,
 | |
|               warning: detectedInfo.memory_mb < 4096
 | |
|             },
 | |
|             { 
 | |
|               check: 'Available disk space (10GB required)', 
 | |
|               passed: detectedInfo.storage.free_space_gb >= 10 
 | |
|             },
 | |
|             { 
 | |
|               check: 'Docker installed and running', 
 | |
|               passed: detectedInfo.docker.available 
 | |
|             }
 | |
|           ].map((validation, index) => {
 | |
|             const StatusIcon = getStatusIcon(validation.passed)
 | |
|             return (
 | |
|               <div key={index} className="flex items-center">
 | |
|                 <StatusIcon className={`h-4 w-4 mr-3 ${
 | |
|                   validation.passed 
 | |
|                     ? 'text-green-600' 
 | |
|                     : 'text-red-600'
 | |
|                 }`} />
 | |
|                 <span className={`text-sm ${
 | |
|                   validation.passed 
 | |
|                     ? 'text-green-800' 
 | |
|                     : 'text-red-800'
 | |
|                 }`}>
 | |
|                   {validation.check}
 | |
|                   {validation.warning && validation.passed && (
 | |
|                     <span className="text-yellow-600 ml-2">(Warning: Recommend 4GB+)</span>
 | |
|                   )}
 | |
|                 </span>
 | |
|               </div>
 | |
|             )
 | |
|           })}
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       {/* Action Buttons */}
 | |
|       <div className="flex justify-between pt-6 border-t border-gray-200">
 | |
|         <div>
 | |
|           {onBack && (
 | |
|             <button onClick={onBack} className="btn-outline">
 | |
|               Back
 | |
|             </button>
 | |
|           )}
 | |
|         </div>
 | |
|         
 | |
|         <div className="flex space-x-3">
 | |
|           <button
 | |
|             onClick={refreshSystemInfo}
 | |
|             disabled={refreshing}
 | |
|             className="btn-outline"
 | |
|           >
 | |
|             {refreshing ? 'Refreshing...' : 'Refresh'}
 | |
|           </button>
 | |
|           
 | |
|           <button
 | |
|             onClick={handleContinue}
 | |
|             className="btn-primary"
 | |
|             disabled={!detectedInfo.docker.available}
 | |
|           >
 | |
|             {isCompleted ? 'Continue' : 'Next: Repository Setup'}
 | |
|           </button>
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
|   )
 | |
| } |