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:
504
deployments/bare-metal/BZZZ_WEB_UI_PLAN.md
Normal file
504
deployments/bare-metal/BZZZ_WEB_UI_PLAN.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# BZZZ Built-in Web Configuration UI Development Plan
|
||||
|
||||
## Clear Separation of Responsibilities
|
||||
|
||||
### chorus.services (Installer Host)
|
||||
- ✅ **Already Complete**: Hosts `install-chorus-enhanced.sh`
|
||||
- ✅ **Single Purpose**: One-line installation script download
|
||||
- ✅ **Working**: `curl -fsSL https://chorus.services/install.sh | sh`
|
||||
|
||||
### BZZZ (This Plan)
|
||||
- 🔄 **To Implement**: Built-in web UI for cluster configuration
|
||||
- 🎯 **Purpose**: Post-installation setup wizard when no config exists
|
||||
- 🌐 **Location**: Served by BZZZ itself at `:8080/setup`
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The BZZZ binary will serve its own web UI for configuration when:
|
||||
1. No configuration file exists (first run)
|
||||
2. User explicitly accesses `:8080/setup`
|
||||
3. Configuration is incomplete or invalid
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### Phase 1: Integrate Web UI into BZZZ Binary
|
||||
|
||||
#### 1.1 Embed Web UI in Go Binary
|
||||
**File**: `main.go` and new `pkg/webui/`
|
||||
|
||||
```go
|
||||
//go:embed web-ui/dist/*
|
||||
var webUIFiles embed.FS
|
||||
|
||||
func main() {
|
||||
// Check if configuration exists
|
||||
if !configExists() {
|
||||
log.Println("🌐 No configuration found, starting setup wizard...")
|
||||
startWebUI()
|
||||
}
|
||||
|
||||
// Normal BZZZ startup
|
||||
startBZZZServices()
|
||||
}
|
||||
|
||||
func startWebUI() {
|
||||
// Serve embedded web UI
|
||||
// Start configuration wizard
|
||||
// Block until configuration is complete
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 Extend Existing HTTP Server
|
||||
**File**: `api/http_server.go`
|
||||
|
||||
Add setup routes to existing server:
|
||||
```go
|
||||
func (h *HTTPServer) setupRoutes(router *mux.Router) {
|
||||
// Existing API routes
|
||||
api := router.PathPrefix("/api").Subrouter()
|
||||
// ... existing routes ...
|
||||
|
||||
// Setup wizard routes (only if no config exists)
|
||||
if !h.hasValidConfig() {
|
||||
h.setupWizardRoutes(router)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTPServer) setupWizardRoutes(router *mux.Router) {
|
||||
// Serve static web UI files
|
||||
router.PathPrefix("/setup").Handler(http.StripPrefix("/setup",
|
||||
http.FileServer(http.FS(webUIFiles))))
|
||||
|
||||
// Setup API endpoints
|
||||
setup := router.PathPrefix("/api/setup").Subrouter()
|
||||
setup.HandleFunc("/system", h.handleSystemDetection).Methods("GET")
|
||||
setup.HandleFunc("/repository", h.handleRepositoryConfig).Methods("GET", "POST")
|
||||
setup.HandleFunc("/cluster", h.handleClusterConfig).Methods("GET", "POST")
|
||||
setup.HandleFunc("/validate", h.handleValidateConfig).Methods("POST")
|
||||
setup.HandleFunc("/save", h.handleSaveConfig).Methods("POST")
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Configuration Management Integration
|
||||
|
||||
#### 2.1 Leverage Existing Config System
|
||||
**File**: `pkg/config/setup.go` (new)
|
||||
|
||||
```go
|
||||
type SetupManager struct {
|
||||
configPath string
|
||||
systemInfo *SystemInfo
|
||||
repoFactory *repository.DefaultProviderFactory
|
||||
taskCoordinator *coordinator.TaskCoordinator
|
||||
}
|
||||
|
||||
func (s *SetupManager) DetectSystem() (*SystemInfo, error) {
|
||||
// Hardware detection (GPU, CPU, memory)
|
||||
// Network interface discovery
|
||||
// Software prerequisites check
|
||||
return systemInfo, nil
|
||||
}
|
||||
|
||||
func (s *SetupManager) ValidateRepositoryConfig(config RepositoryConfig) error {
|
||||
// Use existing repository factory
|
||||
provider, err := s.repoFactory.CreateProvider(ctx, &repository.Config{
|
||||
Provider: config.Provider,
|
||||
BaseURL: config.BaseURL,
|
||||
AccessToken: config.Token,
|
||||
// ...
|
||||
})
|
||||
|
||||
// Test connectivity
|
||||
return provider.ListAvailableTasks()
|
||||
}
|
||||
|
||||
func (s *SetupManager) SaveConfiguration(config *SetupConfig) error {
|
||||
// Convert to existing config.Config format
|
||||
bzzzConfig := &config.Config{
|
||||
Node: config.NodeConfig,
|
||||
Repository: config.RepositoryConfig,
|
||||
Agent: config.AgentConfig,
|
||||
// ...
|
||||
}
|
||||
|
||||
// Save to YAML file
|
||||
return bzzzConfig.SaveToFile(s.configPath)
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 Repository Integration
|
||||
**File**: `api/setup_repository.go` (new)
|
||||
|
||||
```go
|
||||
func (h *HTTPServer) handleRepositoryConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
// Return current repository configuration
|
||||
config := h.setupManager.GetRepositoryConfig()
|
||||
json.NewEncoder(w).Encode(config)
|
||||
return
|
||||
}
|
||||
|
||||
// POST: Validate and save repository configuration
|
||||
var repoConfig RepositoryConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&repoConfig); err != nil {
|
||||
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate using existing repository factory
|
||||
if err := h.setupManager.ValidateRepositoryConfig(repoConfig); err != nil {
|
||||
http.Error(w, fmt.Sprintf("Repository validation failed: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Save configuration
|
||||
h.setupManager.SaveRepositoryConfig(repoConfig)
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Enhanced Web UI Components
|
||||
|
||||
#### 3.1 Complete Existing Components
|
||||
The config-ui framework exists but needs implementation:
|
||||
|
||||
**SystemDetection.tsx**:
|
||||
```typescript
|
||||
const SystemDetection = ({ onComplete, onBack }) => {
|
||||
const [systemInfo, setSystemInfo] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/setup/system')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
setSystemInfo(data);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>System Detection Results</h3>
|
||||
{systemInfo && (
|
||||
<div>
|
||||
<div>OS: {systemInfo.os}</div>
|
||||
<div>Architecture: {systemInfo.arch}</div>
|
||||
<div>GPUs: {systemInfo.gpus?.length || 0}</div>
|
||||
<div>Memory: {systemInfo.memory}</div>
|
||||
{systemInfo.gpus?.length > 1 && (
|
||||
<div className="alert alert-info">
|
||||
💡 Multi-GPU detected! Consider Parallama for optimal performance.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<button onClick={() => onComplete(systemInfo)}>Continue</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**RepositoryConfiguration.tsx**:
|
||||
```typescript
|
||||
const RepositoryConfiguration = ({ onComplete, onBack, configData }) => {
|
||||
const [provider, setProvider] = useState('gitea');
|
||||
const [config, setConfig] = useState({
|
||||
provider: 'gitea',
|
||||
baseURL: 'http://ironwood:3000',
|
||||
owner: '',
|
||||
repository: '',
|
||||
token: ''
|
||||
});
|
||||
const [validating, setValidating] = useState(false);
|
||||
|
||||
const validateConfig = async () => {
|
||||
setValidating(true);
|
||||
try {
|
||||
const response = await fetch('/api/setup/repository', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
onComplete(config);
|
||||
} else {
|
||||
const error = await response.text();
|
||||
alert(`Validation failed: ${error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Connection failed: ${error.message}`);
|
||||
}
|
||||
setValidating(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Repository Configuration</h3>
|
||||
<form onSubmit={(e) => { e.preventDefault(); validateConfig(); }}>
|
||||
<div>
|
||||
<label>Provider:</label>
|
||||
<select value={provider} onChange={(e) => setProvider(e.target.value)}>
|
||||
<option value="gitea">GITEA (Self-hosted)</option>
|
||||
<option value="github">GitHub</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{provider === 'gitea' && (
|
||||
<div>
|
||||
<label>GITEA URL:</label>
|
||||
<input
|
||||
value={config.baseURL}
|
||||
onChange={(e) => setConfig({...config, baseURL: e.target.value})}
|
||||
placeholder="http://ironwood:3000"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label>Repository Owner:</label>
|
||||
<input
|
||||
value={config.owner}
|
||||
onChange={(e) => setConfig({...config, owner: e.target.value})}
|
||||
placeholder="username or organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Repository Name:</label>
|
||||
<input
|
||||
value={config.repository}
|
||||
onChange={(e) => setConfig({...config, repository: e.target.value})}
|
||||
placeholder="repository name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Access Token:</label>
|
||||
<input
|
||||
type="password"
|
||||
value={config.token}
|
||||
onChange={(e) => setConfig({...config, token: e.target.value})}
|
||||
placeholder="access token"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="button" onClick={onBack}>Back</button>
|
||||
<button type="submit" disabled={validating}>
|
||||
{validating ? 'Validating...' : 'Validate & Continue'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Phase 4: Build Process Integration
|
||||
|
||||
#### 4.1 Web UI Build Integration
|
||||
**File**: `Makefile` or build script
|
||||
|
||||
```bash
|
||||
build-web-ui:
|
||||
cd install/config-ui && npm install && npm run build
|
||||
mkdir -p web-ui/dist
|
||||
cp -r install/config-ui/.next/static web-ui/dist/
|
||||
cp -r install/config-ui/out/* web-ui/dist/ 2>/dev/null || true
|
||||
|
||||
build-bzzz: build-web-ui
|
||||
go build -o bzzz main.go
|
||||
```
|
||||
|
||||
#### 4.2 Embed Files in Binary
|
||||
**File**: `pkg/webui/embed.go`
|
||||
|
||||
```go
|
||||
package webui
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed dist/*
|
||||
var webUIFiles embed.FS
|
||||
|
||||
func GetWebUIHandler() http.Handler {
|
||||
// Get the web-ui subdirectory
|
||||
webUIFS, err := fs.Sub(webUIFiles, "dist")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return http.FileServer(http.FS(webUIFS))
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Configuration Flow Integration
|
||||
|
||||
#### 5.1 Startup Logic
|
||||
**File**: `main.go`
|
||||
|
||||
```go
|
||||
func main() {
|
||||
configPath := getConfigPath()
|
||||
|
||||
// Check if configuration exists and is valid
|
||||
config, err := loadConfig(configPath)
|
||||
if err != nil || !isValidConfig(config) {
|
||||
fmt.Println("🌐 Starting BZZZ setup wizard...")
|
||||
fmt.Printf("📱 Open your browser to: http://localhost:8080/setup\n")
|
||||
|
||||
// Start HTTP server in setup mode
|
||||
startSetupMode(configPath)
|
||||
|
||||
// Wait for configuration to be saved
|
||||
waitForConfiguration(configPath)
|
||||
|
||||
// Reload configuration
|
||||
config, err = loadConfig(configPath)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load configuration after setup:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Normal BZZZ startup with configuration
|
||||
fmt.Println("🚀 Starting BZZZ with configuration...")
|
||||
startNormalMode(config)
|
||||
}
|
||||
|
||||
func startSetupMode(configPath string) {
|
||||
// Start HTTP server in setup mode
|
||||
setupManager := NewSetupManager(configPath)
|
||||
httpServer := api.NewHTTPServer(8080, nil, nil)
|
||||
httpServer.SetSetupManager(setupManager)
|
||||
|
||||
go func() {
|
||||
if err := httpServer.Start(); err != nil {
|
||||
log.Fatal("Failed to start setup server:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func waitForConfiguration(configPath string) {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if fileExists(configPath) {
|
||||
if config, err := loadConfig(configPath); err == nil && isValidConfig(config) {
|
||||
fmt.Println("✅ Configuration saved successfully!")
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-time.After(30 * time.Minute):
|
||||
log.Fatal("Setup timeout - no configuration provided within 30 minutes")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 Configuration Validation
|
||||
**File**: `pkg/config/validation.go`
|
||||
|
||||
```go
|
||||
func isValidConfig(config *Config) bool {
|
||||
// Check required fields
|
||||
if config.Node.ID == "" || config.Node.Role == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check repository configuration
|
||||
if config.Repository.Provider == "" || config.Repository.Config.Owner == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check agent configuration
|
||||
if config.Agent.ID == "" || len(config.Agent.Expertise) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Existing Systems
|
||||
|
||||
### ✅ **Leverage Without Changes**
|
||||
1. **HTTP Server** (`api/http_server.go`) - Extend with setup routes
|
||||
2. **Configuration System** (`pkg/config/`) - Use existing YAML structure
|
||||
3. **Repository Factory** (`repository/factory.go`) - Validate credentials
|
||||
4. **Task Coordinator** (`coordinator/task_coordinator.go`) - Agent management
|
||||
|
||||
### 🔧 **Minimal Extensions**
|
||||
1. **Setup Manager** - New component using existing systems
|
||||
2. **Web UI Routes** - Add to existing HTTP server
|
||||
3. **Embedded Files** - Add web UI to binary
|
||||
4. **Startup Logic** - Add configuration check to main.go
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
/home/tony/chorus/project-queues/active/BZZZ/
|
||||
├── main.go # Enhanced startup logic
|
||||
├── api/
|
||||
│ ├── http_server.go # Extended with setup routes
|
||||
│ ├── setup_handlers.go # New setup API handlers
|
||||
│ └── setup_repository.go # Repository configuration API
|
||||
├── pkg/
|
||||
│ ├── config/
|
||||
│ │ ├── config.go # Existing
|
||||
│ │ ├── setup.go # New setup manager
|
||||
│ │ └── validation.go # New validation logic
|
||||
│ └── webui/
|
||||
│ └── embed.go # New embedded web UI
|
||||
├── install/config-ui/ # Existing React app
|
||||
│ ├── app/setup/page.tsx # Existing wizard framework
|
||||
│ ├── app/setup/components/ # Complete component implementations
|
||||
│ └── package.json # Existing
|
||||
└── web-ui/ # Build output (embedded)
|
||||
└── dist/ # Generated by build process
|
||||
```
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
### Week 1: Core Integration
|
||||
- **Day 1**: Setup manager and configuration validation
|
||||
- **Day 2**: Extend HTTP server with setup routes
|
||||
- **Day 3**: Repository configuration API integration
|
||||
- **Day 4**: System detection and hardware profiling
|
||||
- **Day 5**: Build process and file embedding
|
||||
|
||||
### Week 2: Web UI Implementation
|
||||
- **Day 1-2**: Complete SystemDetection and RepositoryConfiguration components
|
||||
- **Day 3-4**: Cluster formation and validation components
|
||||
- **Day 5**: UI polish and error handling
|
||||
|
||||
### Week 3: Integration and Testing
|
||||
- **Day 1-2**: End-to-end configuration flow testing
|
||||
- **Day 3-4**: Multi-node cluster testing
|
||||
- **Day 5**: Documentation and deployment
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Technical
|
||||
- [ ] BZZZ binary serves web UI when no config exists
|
||||
- [ ] Complete 8-step setup wizard functional
|
||||
- [ ] Repository integration with GITEA/GitHub validation
|
||||
- [ ] Configuration saves to existing YAML format
|
||||
- [ ] Seamless transition from setup to normal operation
|
||||
|
||||
### User Experience
|
||||
- [ ] Zero-config startup launches web wizard
|
||||
- [ ] Intuitive setup flow with validation
|
||||
- [ ] Professional UI matching BZZZ branding
|
||||
- [ ] Clear error messages and recovery options
|
||||
- [ ] Mobile-responsive design
|
||||
|
||||
This plan keeps the clear separation between the chorus.services installer (already working) and BZZZ's built-in configuration system (to be implemented).
|
||||
Reference in New Issue
Block a user