package tui import ( "bufio" "encoding/json" "fmt" "os" "strings" ) // App represents the TUI application type App struct { client *APIClient reader *bufio.Reader } // NewApp creates a new TUI application func NewApp(client *APIClient) *App { return &App{ client: client, reader: bufio.NewReader(os.Stdin), } } // Run starts the TUI application func (a *App) Run() error { // Check if authenticated if a.client.token == "" { if err := a.login(); err != nil { return fmt.Errorf("login failed: %w", err) } } // Main menu loop for { a.showMainMenu() choice := a.readInput("Select option: ") switch choice { case "1": a.handleZFSMenu() case "2": a.handleStorageMenu() case "3": a.handleSnapshotMenu() case "4": a.handleSystemMenu() case "5": a.handleBackupMenu() case "0", "q", "exit": fmt.Println("Goodbye!") return nil default: fmt.Println("Invalid option. Please try again.") } } } // Cleanup performs cleanup operations func (a *App) Cleanup() { fmt.Println("\nCleaning up...") } // login handles user authentication func (a *App) login() error { fmt.Println("=== AtlasOS Login ===") username := a.readInput("Username: ") password := a.readPassword("Password: ") token, err := a.client.Login(username, password) if err != nil { return err } fmt.Println("Login successful!") _ = token return nil } // readInput reads a line of input func (a *App) readInput(prompt string) string { fmt.Print(prompt) input, _ := a.reader.ReadString('\n') return strings.TrimSpace(input) } // readPassword reads a password (without echoing) func (a *App) readPassword(prompt string) string { fmt.Print(prompt) // Simple implementation - in production, use a library that hides input input, _ := a.reader.ReadString('\n') return strings.TrimSpace(input) } // showMainMenu displays the main menu func (a *App) showMainMenu() { fmt.Println("\n=== AtlasOS Terminal Interface ===") fmt.Println("1. ZFS Management") fmt.Println("2. Storage Services") fmt.Println("3. Snapshots") fmt.Println("4. System Information") fmt.Println("5. Backup & Restore") fmt.Println("0. Exit") fmt.Println() } // handleZFSMenu handles ZFS management menu func (a *App) handleZFSMenu() { for { fmt.Println("\n=== ZFS Management ===") fmt.Println("1. List Pools") fmt.Println("2. List Datasets") fmt.Println("3. List ZVOLs") fmt.Println("4. List Disks") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listPools() case "2": a.listDatasets() case "3": a.listZVOLs() case "4": a.listDisks() case "0": return default: fmt.Println("Invalid option.") } } } // handleStorageMenu handles storage services menu func (a *App) handleStorageMenu() { for { fmt.Println("\n=== Storage Services ===") fmt.Println("1. SMB Shares") fmt.Println("2. NFS Exports") fmt.Println("3. iSCSI Targets") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.handleSMBMenu() case "2": a.handleNFSMenu() case "3": a.handleISCSIMenu() case "0": return default: fmt.Println("Invalid option.") } } } // handleSnapshotMenu handles snapshot management menu func (a *App) handleSnapshotMenu() { for { fmt.Println("\n=== Snapshot Management ===") fmt.Println("1. List Snapshots") fmt.Println("2. Create Snapshot") fmt.Println("3. List Snapshot Policies") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listSnapshots() case "2": a.createSnapshot() case "3": a.listSnapshotPolicies() case "0": return default: fmt.Println("Invalid option.") } } } // handleSystemMenu handles system information menu func (a *App) handleSystemMenu() { for { fmt.Println("\n=== System Information ===") fmt.Println("1. System Info") fmt.Println("2. Health Check") fmt.Println("3. Dashboard") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.showSystemInfo() case "2": a.showHealthCheck() case "3": a.showDashboard() case "0": return default: fmt.Println("Invalid option.") } } } // handleBackupMenu handles backup and restore menu func (a *App) handleBackupMenu() { for { fmt.Println("\n=== Backup & Restore ===") fmt.Println("1. List Backups") fmt.Println("2. Create Backup") fmt.Println("3. Restore Backup") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listBackups() case "2": a.createBackup() case "3": a.restoreBackup() case "0": return default: fmt.Println("Invalid option.") } } } // listPools lists all ZFS pools func (a *App) listPools() { data, err := a.client.Get("/api/v1/pools") if err != nil { fmt.Printf("Error: %v\n", err) return } var pools []map[string]interface{} if err := json.Unmarshal(data, &pools); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== ZFS Pools ===") if len(pools) == 0 { fmt.Println("No pools found.") return } for i, pool := range pools { fmt.Printf("%d. %s\n", i+1, pool["name"]) if size, ok := pool["size"].(string); ok { fmt.Printf(" Size: %s\n", size) } if used, ok := pool["used"].(string); ok { fmt.Printf(" Used: %s\n", used) } fmt.Println() } } // listDatasets lists all datasets func (a *App) listDatasets() { data, err := a.client.Get("/api/v1/datasets") if err != nil { fmt.Printf("Error: %v\n", err) return } var datasets []map[string]interface{} if err := json.Unmarshal(data, &datasets); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Datasets ===") if len(datasets) == 0 { fmt.Println("No datasets found.") return } for i, ds := range datasets { fmt.Printf("%d. %s\n", i+1, ds["name"]) if mountpoint, ok := ds["mountpoint"].(string); ok && mountpoint != "" { fmt.Printf(" Mountpoint: %s\n", mountpoint) } fmt.Println() } } // listZVOLs lists all ZVOLs func (a *App) listZVOLs() { data, err := a.client.Get("/api/v1/zvols") if err != nil { fmt.Printf("Error: %v\n", err) return } var zvols []map[string]interface{} if err := json.Unmarshal(data, &zvols); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== ZVOLs ===") if len(zvols) == 0 { fmt.Println("No ZVOLs found.") return } for i, zvol := range zvols { fmt.Printf("%d. %s\n", i+1, zvol["name"]) if size, ok := zvol["size"].(float64); ok { fmt.Printf(" Size: %.2f bytes\n", size) } fmt.Println() } } // listDisks lists available disks func (a *App) listDisks() { data, err := a.client.Get("/api/v1/disks") if err != nil { fmt.Printf("Error: %v\n", err) return } var disks []map[string]interface{} if err := json.Unmarshal(data, &disks); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Available Disks ===") if len(disks) == 0 { fmt.Println("No disks found.") return } for i, disk := range disks { fmt.Printf("%d. %s\n", i+1, disk["name"]) if size, ok := disk["size"].(string); ok { fmt.Printf(" Size: %s\n", size) } if model, ok := disk["model"].(string); ok { fmt.Printf(" Model: %s\n", model) } fmt.Println() } } // listSnapshots lists all snapshots func (a *App) listSnapshots() { data, err := a.client.Get("/api/v1/snapshots") if err != nil { fmt.Printf("Error: %v\n", err) return } var snapshots []map[string]interface{} if err := json.Unmarshal(data, &snapshots); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Snapshots ===") if len(snapshots) == 0 { fmt.Println("No snapshots found.") return } for i, snap := range snapshots { fmt.Printf("%d. %s\n", i+1, snap["name"]) if dataset, ok := snap["dataset"].(string); ok { fmt.Printf(" Dataset: %s\n", dataset) } fmt.Println() } } // createSnapshot creates a new snapshot func (a *App) createSnapshot() { dataset := a.readInput("Dataset name: ") name := a.readInput("Snapshot name: ") reqBody := map[string]interface{}{ "dataset": dataset, "name": name, } data, err := a.client.Post("/api/v1/snapshots", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } var result map[string]interface{} if err := json.Unmarshal(data, &result); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("Snapshot created successfully!") if name, ok := result["name"].(string); ok { fmt.Printf("Snapshot: %s\n", name) } } // listSnapshotPolicies lists snapshot policies func (a *App) listSnapshotPolicies() { data, err := a.client.Get("/api/v1/snapshot-policies") if err != nil { fmt.Printf("Error: %v\n", err) return } var policies []map[string]interface{} if err := json.Unmarshal(data, &policies); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Snapshot Policies ===") if len(policies) == 0 { fmt.Println("No policies found.") return } for i, policy := range policies { fmt.Printf("%d. Dataset: %s\n", i+1, policy["dataset"]) fmt.Printf(" Frequent: %v, Hourly: %v, Daily: %v\n", policy["frequent"], policy["hourly"], policy["daily"]) fmt.Println() } } // handleSMBMenu handles SMB shares menu func (a *App) handleSMBMenu() { for { fmt.Println("\n=== SMB Shares ===") fmt.Println("1. List Shares") fmt.Println("2. Create Share") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listSMBShares() case "2": a.createSMBShare() case "0": return default: fmt.Println("Invalid option.") } } } // listSMBShares lists SMB shares func (a *App) listSMBShares() { data, err := a.client.Get("/api/v1/shares/smb") if err != nil { fmt.Printf("Error: %v\n", err) return } var shares []map[string]interface{} if err := json.Unmarshal(data, &shares); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== SMB Shares ===") if len(shares) == 0 { fmt.Println("No shares found.") return } for i, share := range shares { fmt.Printf("%d. %s\n", i+1, share["name"]) if path, ok := share["path"].(string); ok { fmt.Printf(" Path: %s\n", path) } fmt.Println() } } // createSMBShare creates a new SMB share func (a *App) createSMBShare() { name := a.readInput("Share name: ") dataset := a.readInput("Dataset: ") path := a.readInput("Path (optional, press Enter to auto-detect): ") description := a.readInput("Description (optional): ") reqBody := map[string]interface{}{ "name": name, "dataset": dataset, } if path != "" { reqBody["path"] = path } if description != "" { reqBody["description"] = description } data, err := a.client.Post("/api/v1/shares/smb", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("SMB share created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if name, ok := result["name"].(string); ok { fmt.Printf("Share: %s\n", name) } } } // handleNFSMenu handles NFS exports menu func (a *App) handleNFSMenu() { for { fmt.Println("\n=== NFS Exports ===") fmt.Println("1. List Exports") fmt.Println("2. Create Export") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listNFSExports() case "2": a.createNFSExport() case "0": return default: fmt.Println("Invalid option.") } } } // listNFSExports lists NFS exports func (a *App) listNFSExports() { data, err := a.client.Get("/api/v1/exports/nfs") if err != nil { fmt.Printf("Error: %v\n", err) return } var exports []map[string]interface{} if err := json.Unmarshal(data, &exports); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== NFS Exports ===") if len(exports) == 0 { fmt.Println("No exports found.") return } for i, export := range exports { fmt.Printf("%d. Path: %s\n", i+1, export["path"]) if clients, ok := export["clients"].([]interface{}); ok { fmt.Printf(" Clients: %v\n", clients) } fmt.Println() } } // createNFSExport creates a new NFS export func (a *App) createNFSExport() { dataset := a.readInput("Dataset: ") path := a.readInput("Path (optional, press Enter to auto-detect): ") clientsStr := a.readInput("Clients (comma-separated, e.g., 192.168.1.0/24,*): ") clients := []string{} if clientsStr != "" { clients = strings.Split(clientsStr, ",") for i := range clients { clients[i] = strings.TrimSpace(clients[i]) } } reqBody := map[string]interface{}{ "dataset": dataset, "clients": clients, } if path != "" { reqBody["path"] = path } data, err := a.client.Post("/api/v1/exports/nfs", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("NFS export created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if path, ok := result["path"].(string); ok { fmt.Printf("Export: %s\n", path) } } } // handleISCSIMenu handles iSCSI targets menu func (a *App) handleISCSIMenu() { for { fmt.Println("\n=== iSCSI Targets ===") fmt.Println("1. List Targets") fmt.Println("2. Create Target") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listISCSITargets() case "2": a.createISCSITarget() case "0": return default: fmt.Println("Invalid option.") } } } // listISCSITargets lists iSCSI targets func (a *App) listISCSITargets() { data, err := a.client.Get("/api/v1/iscsi/targets") if err != nil { fmt.Printf("Error: %v\n", err) return } var targets []map[string]interface{} if err := json.Unmarshal(data, &targets); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== iSCSI Targets ===") if len(targets) == 0 { fmt.Println("No targets found.") return } for i, target := range targets { fmt.Printf("%d. %s\n", i+1, target["iqn"]) if luns, ok := target["luns"].([]interface{}); ok { fmt.Printf(" LUNs: %d\n", len(luns)) } fmt.Println() } } // createISCSITarget creates a new iSCSI target func (a *App) createISCSITarget() { iqn := a.readInput("IQN (e.g., iqn.2024-12.com.atlas:target1): ") reqBody := map[string]interface{}{ "iqn": iqn, } data, err := a.client.Post("/api/v1/iscsi/targets", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("iSCSI target created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if iqn, ok := result["iqn"].(string); ok { fmt.Printf("Target: %s\n", iqn) } } } // showSystemInfo displays system information func (a *App) showSystemInfo() { data, err := a.client.Get("/api/v1/system/info") if err != nil { fmt.Printf("Error: %v\n", err) return } var info map[string]interface{} if err := json.Unmarshal(data, &info); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== System Information ===") if version, ok := info["version"].(string); ok { fmt.Printf("Version: %s\n", version) } if uptime, ok := info["uptime"].(string); ok { fmt.Printf("Uptime: %s\n", uptime) } if goVersion, ok := info["go_version"].(string); ok { fmt.Printf("Go Version: %s\n", goVersion) } if numGoroutines, ok := info["num_goroutines"].(float64); ok { fmt.Printf("Goroutines: %.0f\n", numGoroutines) } if services, ok := info["services"].(map[string]interface{}); ok { fmt.Println("\nServices:") for name, service := range services { if svc, ok := service.(map[string]interface{}); ok { if status, ok := svc["status"].(string); ok { fmt.Printf(" %s: %s\n", name, status) } } } } } // showHealthCheck displays health check information func (a *App) showHealthCheck() { data, err := a.client.Get("/health") if err != nil { fmt.Printf("Error: %v\n", err) return } var health map[string]interface{} if err := json.Unmarshal(data, &health); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Health Check ===") if status, ok := health["status"].(string); ok { fmt.Printf("Status: %s\n", status) } if checks, ok := health["checks"].(map[string]interface{}); ok { fmt.Println("\nComponent Checks:") for name, status := range checks { fmt.Printf(" %s: %v\n", name, status) } } } // showDashboard displays dashboard information func (a *App) showDashboard() { data, err := a.client.Get("/api/v1/dashboard") if err != nil { fmt.Printf("Error: %v\n", err) return } var dashboard map[string]interface{} if err := json.Unmarshal(data, &dashboard); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Dashboard ===") if pools, ok := dashboard["pools"].([]interface{}); ok { fmt.Printf("Pools: %d\n", len(pools)) } if datasets, ok := dashboard["datasets"].([]interface{}); ok { fmt.Printf("Datasets: %d\n", len(datasets)) } if smbShares, ok := dashboard["smb_shares"].([]interface{}); ok { fmt.Printf("SMB Shares: %d\n", len(smbShares)) } if nfsExports, ok := dashboard["nfs_exports"].([]interface{}); ok { fmt.Printf("NFS Exports: %d\n", len(nfsExports)) } if iscsiTargets, ok := dashboard["iscsi_targets"].([]interface{}); ok { fmt.Printf("iSCSI Targets: %d\n", len(iscsiTargets)) } } // listBackups lists all backups func (a *App) listBackups() { data, err := a.client.Get("/api/v1/backups") if err != nil { fmt.Printf("Error: %v\n", err) return } var backups []map[string]interface{} if err := json.Unmarshal(data, &backups); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Backups ===") if len(backups) == 0 { fmt.Println("No backups found.") return } for i, backup := range backups { fmt.Printf("%d. %s\n", i+1, backup["id"]) if createdAt, ok := backup["created_at"].(string); ok { fmt.Printf(" Created: %s\n", createdAt) } if desc, ok := backup["description"].(string); ok && desc != "" { fmt.Printf(" Description: %s\n", desc) } fmt.Println() } } // createBackup creates a new backup func (a *App) createBackup() { description := a.readInput("Description (optional): ") reqBody := map[string]interface{}{} if description != "" { reqBody["description"] = description } data, err := a.client.Post("/api/v1/backups", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Backup created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if id, ok := result["id"].(string); ok { fmt.Printf("Backup ID: %s\n", id) } } } // restoreBackup restores a backup func (a *App) restoreBackup() { a.listBackups() backupID := a.readInput("Backup ID: ") confirm := a.readInput("Restore backup? This will overwrite current configuration. (yes/no): ") if confirm != "yes" { fmt.Println("Restore cancelled.") return } reqBody := map[string]interface{}{ "dry_run": false, } data, err := a.client.Post("/api/v1/backups/"+backupID+"/restore", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Backup restored successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if msg, ok := result["message"].(string); ok { fmt.Printf("%s\n", msg) } } }