Major updates and improvements to BZZZ system
- 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>
This commit is contained in:
326
deployments/bare-metal/config-ui/app/setup/page.tsx
Normal file
326
deployments/bare-metal/config-ui/app/setup/page.tsx
Normal file
@@ -0,0 +1,326 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { ChevronRightIcon, CheckCircleIcon } from '@heroicons/react/24/outline'
|
||||
import TermsAndConditions from './components/TermsAndConditions'
|
||||
import LicenseValidation from './components/LicenseValidation'
|
||||
import SystemDetection from './components/SystemDetection'
|
||||
import RepositoryConfiguration from './components/RepositoryConfiguration'
|
||||
import NetworkConfiguration from './components/NetworkConfiguration'
|
||||
import SecuritySetup from './components/SecuritySetup'
|
||||
import AIConfiguration from './components/AIConfiguration'
|
||||
import ServiceDeployment from './components/ServiceDeployment'
|
||||
import ClusterFormation from './components/ClusterFormation'
|
||||
import TestingValidation from './components/TestingValidation'
|
||||
|
||||
const SETUP_STEPS = [
|
||||
{
|
||||
id: 'terms',
|
||||
title: 'Terms & Conditions',
|
||||
description: 'Review and accept the software license agreement',
|
||||
component: TermsAndConditions,
|
||||
},
|
||||
{
|
||||
id: 'license',
|
||||
title: 'License Validation',
|
||||
description: 'Validate your CHORUS license key and email',
|
||||
component: LicenseValidation,
|
||||
},
|
||||
{
|
||||
id: 'detection',
|
||||
title: 'System Detection',
|
||||
description: 'Detect hardware and validate installation',
|
||||
component: SystemDetection,
|
||||
},
|
||||
{
|
||||
id: 'repository',
|
||||
title: 'Repository Setup',
|
||||
description: 'Configure Git repository for task management',
|
||||
component: RepositoryConfiguration,
|
||||
},
|
||||
{
|
||||
id: 'network',
|
||||
title: 'Network Configuration',
|
||||
description: 'Configure network and firewall settings',
|
||||
component: NetworkConfiguration,
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
title: 'Security Setup',
|
||||
description: 'Configure authentication and SSH access',
|
||||
component: SecuritySetup,
|
||||
},
|
||||
{
|
||||
id: 'ai',
|
||||
title: 'AI Integration',
|
||||
description: 'Configure OpenAI and Ollama/Parallama',
|
||||
component: AIConfiguration,
|
||||
},
|
||||
{
|
||||
id: 'deployment',
|
||||
title: 'Service Deployment',
|
||||
description: 'Deploy and configure CHORUS agent services',
|
||||
component: ServiceDeployment,
|
||||
},
|
||||
{
|
||||
id: 'cluster',
|
||||
title: 'Cluster Formation',
|
||||
description: 'Join or create CHORUS agent cluster',
|
||||
component: ClusterFormation,
|
||||
},
|
||||
{
|
||||
id: 'testing',
|
||||
title: 'Testing & Validation',
|
||||
description: 'Validate configuration and test connectivity',
|
||||
component: TestingValidation,
|
||||
},
|
||||
]
|
||||
|
||||
interface ConfigData {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export default function SetupPage() {
|
||||
const [currentStep, setCurrentStep] = useState(0)
|
||||
const [completedSteps, setCompletedSteps] = useState(new Set<number>())
|
||||
const [configData, setConfigData] = useState<ConfigData>({})
|
||||
const [systemInfo, setSystemInfo] = useState<any>(null)
|
||||
|
||||
// Load persisted data and system information on mount
|
||||
useEffect(() => {
|
||||
loadPersistedData()
|
||||
fetchSystemInfo()
|
||||
}, [])
|
||||
|
||||
// Save setup state to localStorage whenever it changes
|
||||
useEffect(() => {
|
||||
saveSetupState()
|
||||
}, [currentStep, completedSteps, configData])
|
||||
|
||||
const loadPersistedData = () => {
|
||||
try {
|
||||
const savedState = localStorage.getItem('chorus-setup-state')
|
||||
if (savedState) {
|
||||
const state = JSON.parse(savedState)
|
||||
setCurrentStep(state.currentStep || 0)
|
||||
setCompletedSteps(new Set(state.completedSteps || []))
|
||||
setConfigData(state.configData || {})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load persisted setup data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const saveSetupState = () => {
|
||||
try {
|
||||
const state = {
|
||||
currentStep,
|
||||
completedSteps: Array.from(completedSteps),
|
||||
configData,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
localStorage.setItem('chorus-setup-state', JSON.stringify(state))
|
||||
} catch (error) {
|
||||
console.error('Failed to save setup state:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const clearPersistedData = () => {
|
||||
try {
|
||||
localStorage.removeItem('chorus-setup-state')
|
||||
// Reset state to initial values
|
||||
setCurrentStep(0)
|
||||
setCompletedSteps(new Set<number>())
|
||||
setConfigData({})
|
||||
} catch (error) {
|
||||
console.error('Failed to clear persisted data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchSystemInfo = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/setup/system')
|
||||
if (response.ok) {
|
||||
const result = await response.json()
|
||||
setSystemInfo(result.system_info)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch system info:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleStepComplete = (stepIndex: number, data: any) => {
|
||||
console.log('Setup Page: Step complete', { stepIndex, data, currentConfigData: configData })
|
||||
setCompletedSteps(prev => new Set([...prev, stepIndex]))
|
||||
setConfigData(prev => {
|
||||
const newConfigData = { ...prev, ...data }
|
||||
console.log('Setup Page: Updated configData', { prev, data, newConfigData })
|
||||
return newConfigData
|
||||
})
|
||||
|
||||
// Auto-advance to next step
|
||||
if (stepIndex < SETUP_STEPS.length - 1) {
|
||||
setCurrentStep(stepIndex + 1)
|
||||
} else {
|
||||
// Setup is complete, clear persisted data after a delay
|
||||
setTimeout(() => {
|
||||
clearPersistedData()
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
|
||||
const handleStepBack = () => {
|
||||
if (currentStep > 0) {
|
||||
setCurrentStep(currentStep - 1)
|
||||
}
|
||||
}
|
||||
|
||||
const CurrentStepComponent = SETUP_STEPS[currentStep].component
|
||||
|
||||
// Check if we're resuming from saved data
|
||||
const isResuming = currentStep > 0 || completedSteps.size > 0 || Object.keys(configData).length > 0
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="heading-hero mb-3">
|
||||
CHORUS Agent Setup
|
||||
</h1>
|
||||
<p className="text-body">
|
||||
Configure your distributed agent orchestration platform in {SETUP_STEPS.length} simple steps.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Resume Setup Notification (Info Panel) */}
|
||||
{isResuming && (
|
||||
<div className="mb-8 panel panel-info p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-ocean-600 dark:text-ocean-300 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium panel-title">
|
||||
Setup Progress Restored
|
||||
</h3>
|
||||
<p className="text-small panel-body mt-1">
|
||||
Your previous setup progress has been restored. You're currently on step {currentStep + 1} of {SETUP_STEPS.length}.
|
||||
{completedSteps.size > 0 && ` You've completed ${completedSteps.size} step${completedSteps.size !== 1 ? 's' : ''}.`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={clearPersistedData}
|
||||
className="btn-text"
|
||||
>
|
||||
Start Over
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-12">
|
||||
{/* Progress Sidebar */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="card sticky top-8 setup-progress">
|
||||
<h2 className="heading-subsection mb-6">
|
||||
Setup Progress
|
||||
</h2>
|
||||
<nav className="space-y-2">
|
||||
{SETUP_STEPS.map((step, index) => {
|
||||
const isCompleted = completedSteps.has(index)
|
||||
const isCurrent = index === currentStep
|
||||
const isAccessible = index <= currentStep || completedSteps.has(index)
|
||||
|
||||
return (
|
||||
<button
|
||||
key={step.id}
|
||||
onClick={() => isAccessible && setCurrentStep(index)}
|
||||
disabled={!isAccessible}
|
||||
className={`w-full text-left progress-step ${
|
||||
isCurrent
|
||||
? 'progress-step-current'
|
||||
: isCompleted
|
||||
? 'progress-step-completed'
|
||||
: isAccessible
|
||||
? 'progress-step-accessible'
|
||||
: 'progress-step-disabled'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0 mr-3">
|
||||
{isCompleted ? (
|
||||
<CheckCircleIcon className="h-5 w-5 text-eucalyptus-600" />
|
||||
) : (
|
||||
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center text-xs font-medium ${
|
||||
isCurrent
|
||||
? 'border-chorus-secondary bg-chorus-secondary text-white'
|
||||
: 'border-gray-600 text-gray-500'
|
||||
}`}>
|
||||
{index + 1}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium truncate">
|
||||
{step.title}
|
||||
</div>
|
||||
<div className="text-xs opacity-75 truncate">
|
||||
{step.description}
|
||||
</div>
|
||||
</div>
|
||||
{isAccessible && !isCompleted && (
|
||||
<ChevronRightIcon className="h-4 w-4 opacity-50" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-chorus-border-defined">
|
||||
<div className="text-small mb-3">
|
||||
Progress: {completedSteps.size} of {SETUP_STEPS.length} steps
|
||||
</div>
|
||||
<div className="w-full bg-chorus-border-invisible rounded-sm h-2">
|
||||
<div
|
||||
className="bg-chorus-secondary h-2 rounded-sm transition-all duration-500"
|
||||
style={{ width: `${(completedSteps.size / SETUP_STEPS.length) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="lg:col-span-3">
|
||||
<div className="card">
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="heading-section">
|
||||
{SETUP_STEPS[currentStep].title}
|
||||
</h2>
|
||||
<div className="text-ghost">
|
||||
Step {currentStep + 1} of {SETUP_STEPS.length}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-body">
|
||||
{SETUP_STEPS[currentStep].description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CurrentStepComponent
|
||||
systemInfo={systemInfo}
|
||||
configData={configData}
|
||||
onComplete={(data: any) => handleStepComplete(currentStep, data)}
|
||||
onBack={currentStep > 0 ? handleStepBack : undefined}
|
||||
isCompleted={completedSteps.has(currentStep)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user