package tui import ( "bufio" "encoding/json" "fmt" "os" "os/exec" "strings" ) // App represents the TUI application type App struct { client *APIClient reader *bufio.Reader currentUser map[string]interface{} } // 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 "6": a.handleUserMenu() case "7": a.handleServiceMenu() 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, user, err := a.client.Login(username, password) if err != nil { return err } // Store current user info if user != nil { a.currentUser = user } else { // Fallback: get user info from API data, err := a.client.Get("/api/v1/users") if err == nil { var users []map[string]interface{} if json.Unmarshal(data, &users) == nil { for _, u := range users { if uname, ok := u["username"].(string); ok && uname == username { a.currentUser = u break } } } } } 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("6. User Management") fmt.Println("7. Service Management") 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. Create Pool") fmt.Println("3. Delete Pool") fmt.Println("4. Import Pool") fmt.Println("5. Export Pool") fmt.Println("6. List Available Pools") fmt.Println("7. Start Scrub") fmt.Println("8. Get Scrub Status") fmt.Println("9. List Datasets") fmt.Println("10. Create Dataset") fmt.Println("11. Delete Dataset") fmt.Println("12. List ZVOLs") fmt.Println("13. Create ZVOL") fmt.Println("14. Delete ZVOL") fmt.Println("15. List Disks") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listPools() case "2": a.createPool() case "3": a.deletePool() case "4": a.importPool() case "5": a.exportPool() case "6": a.listAvailablePools() case "7": a.startScrub() case "8": a.getScrubStatus() case "9": a.listDatasets() case "10": a.createDataset() case "11": a.deleteDataset() case "12": a.listZVOLs() case "13": a.createZVOL() case "14": a.deleteZVOL() case "15": 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) } } } // ===== ZFS Pool CRUD Operations ===== // createPool creates a new ZFS pool func (a *App) createPool() { name := a.readInput("Pool name: ") vdevsStr := a.readInput("VDEVs (comma-separated, e.g., /dev/sdb,/dev/sdc): ") vdevs := []string{} if vdevsStr != "" { vdevs = strings.Split(vdevsStr, ",") for i := range vdevs { vdevs[i] = strings.TrimSpace(vdevs[i]) } } reqBody := map[string]interface{}{ "name": name, "vdevs": vdevs, } data, err := a.client.Post("/api/v1/pools", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Pool created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if name, ok := result["name"].(string); ok { fmt.Printf("Pool: %s\n", name) } } } // deletePool deletes a ZFS pool func (a *App) deletePool() { a.listPools() name := a.readInput("Pool name to delete: ") confirm := a.readInput("WARNING: This will destroy the pool and all data! Type 'yes' to confirm: ") if confirm != "yes" { fmt.Println("Deletion cancelled.") return } err := a.client.Delete("/api/v1/pools/" + name) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Pool %s deleted successfully!\n", name) } // importPool imports a ZFS pool func (a *App) importPool() { a.listAvailablePools() name := a.readInput("Pool name to import: ") readonly := a.readInput("Import as read-only? (yes/no, default: no): ") reqBody := map[string]interface{}{ "name": name, } if readonly == "yes" { reqBody["options"] = map[string]string{ "readonly": "on", } } data, err := a.client.Post("/api/v1/pools/import", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Pool imported 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) } } } // exportPool exports a ZFS pool func (a *App) exportPool() { a.listPools() name := a.readInput("Pool name to export: ") forceStr := a.readInput("Force export? (yes/no, default: no): ") force := forceStr == "yes" reqBody := map[string]interface{}{ "force": force, } data, err := a.client.Post("/api/v1/pools/"+name+"/export", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Pool exported 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) } } } // listAvailablePools lists pools available for import func (a *App) listAvailablePools() { data, err := a.client.Get("/api/v1/pools/available") 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("\n=== Available Pools (for import) ===") if pools, ok := result["pools"].([]interface{}); ok { if len(pools) == 0 { fmt.Println("No pools available for import.") return } for i, pool := range pools { fmt.Printf("%d. %v\n", i+1, pool) } } else { fmt.Println("No pools available for import.") } } // startScrub starts a scrub operation on a pool func (a *App) startScrub() { a.listPools() name := a.readInput("Pool name to scrub: ") data, err := a.client.Post("/api/v1/pools/"+name+"/scrub", map[string]interface{}{}) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Scrub started 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) } } } // getScrubStatus gets scrub status for a pool func (a *App) getScrubStatus() { a.listPools() name := a.readInput("Pool name: ") data, err := a.client.Get("/api/v1/pools/" + name + "/scrub") if err != nil { fmt.Printf("Error: %v\n", err) return } var status map[string]interface{} if err := json.Unmarshal(data, &status); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Scrub Status ===") if state, ok := status["state"].(string); ok { fmt.Printf("State: %s\n", state) } if progress, ok := status["progress"].(float64); ok { fmt.Printf("Progress: %.2f%%\n", progress) } if elapsed, ok := status["elapsed"].(string); ok { fmt.Printf("Elapsed: %s\n", elapsed) } if remaining, ok := status["remaining"].(string); ok { fmt.Printf("Remaining: %s\n", remaining) } if speed, ok := status["speed"].(string); ok { fmt.Printf("Speed: %s\n", speed) } if errors, ok := status["errors"].(float64); ok { fmt.Printf("Errors: %.0f\n", errors) } } // createDataset creates a new dataset func (a *App) createDataset() { name := a.readInput("Dataset name (e.g., pool/dataset): ") quota := a.readInput("Quota (optional, e.g., 10G): ") compression := a.readInput("Compression (optional, e.g., lz4, zstd): ") reqBody := map[string]interface{}{ "name": name, } if quota != "" { reqBody["quota"] = quota } if compression != "" { reqBody["compression"] = compression } data, err := a.client.Post("/api/v1/datasets", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Dataset created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if name, ok := result["name"].(string); ok { fmt.Printf("Dataset: %s\n", name) } } } // deleteDataset deletes a dataset func (a *App) deleteDataset() { a.listDatasets() name := a.readInput("Dataset name to delete: ") confirm := a.readInput("Delete dataset? (yes/no): ") if confirm != "yes" { fmt.Println("Deletion cancelled.") return } err := a.client.Delete("/api/v1/datasets/" + name) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Dataset %s deleted successfully!\n", name) } // createZVOL creates a new ZVOL func (a *App) createZVOL() { name := a.readInput("ZVOL name (e.g., pool/zvol): ") sizeStr := a.readInput("Size (e.g., 10G): ") reqBody := map[string]interface{}{ "name": name, "size": sizeStr, } data, err := a.client.Post("/api/v1/zvols", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("ZVOL created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if name, ok := result["name"].(string); ok { fmt.Printf("ZVOL: %s\n", name) } } } // deleteZVOL deletes a ZVOL func (a *App) deleteZVOL() { a.listZVOLs() name := a.readInput("ZVOL name to delete: ") confirm := a.readInput("Delete ZVOL? (yes/no): ") if confirm != "yes" { fmt.Println("Deletion cancelled.") return } err := a.client.Delete("/api/v1/zvols/" + name) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("ZVOL %s deleted successfully!\n", name) } // ===== User Management ===== // handleUserMenu handles user management menu func (a *App) handleUserMenu() { for { fmt.Println("\n=== User Management ===") fmt.Println("1. List Users") fmt.Println("2. Create User") fmt.Println("3. Update User") fmt.Println("4. Delete User") fmt.Println("5. Change Password") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.listUsers() case "2": a.createUser() case "3": a.updateUser() case "4": a.deleteUser() case "5": a.changePassword() case "0": return default: fmt.Println("Invalid option.") } } } // listUsers lists all users func (a *App) listUsers() { data, err := a.client.Get("/api/v1/users") if err != nil { fmt.Printf("Error: %v\n", err) return } var users []map[string]interface{} if err := json.Unmarshal(data, &users); err != nil { fmt.Printf("Error parsing response: %v\n", err) return } fmt.Println("\n=== Users ===") if len(users) == 0 { fmt.Println("No users found.") return } for i, user := range users { fmt.Printf("%d. %s\n", i+1, user["username"]) if email, ok := user["email"].(string); ok && email != "" { fmt.Printf(" Email: %s\n", email) } if role, ok := user["role"].(string); ok { fmt.Printf(" Role: %s\n", role) } if active, ok := user["active"].(bool); ok { fmt.Printf(" Active: %v\n", active) } fmt.Println() } } // createUser creates a new user func (a *App) createUser() { username := a.readInput("Username: ") email := a.readInput("Email (optional): ") password := a.readPassword("Password: ") role := a.readInput("Role (Administrator/Operator/Viewer, default: Viewer): ") if role == "" { role = "Viewer" } reqBody := map[string]interface{}{ "username": username, "password": password, "role": role, } if email != "" { reqBody["email"] = email } data, err := a.client.Post("/api/v1/users", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("User created successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if username, ok := result["username"].(string); ok { fmt.Printf("User: %s\n", username) } } } // updateUser updates a user func (a *App) updateUser() { a.listUsers() userID := a.readInput("User ID: ") email := a.readInput("New email (press Enter to skip): ") role := a.readInput("New role (Administrator/Operator/Viewer, press Enter to skip): ") activeStr := a.readInput("Active (true/false, press Enter to skip): ") reqBody := map[string]interface{}{} if email != "" { reqBody["email"] = email } if role != "" { reqBody["role"] = role } if activeStr != "" { reqBody["active"] = activeStr == "true" } if len(reqBody) == 0 { fmt.Println("No changes specified.") return } data, err := a.client.Put("/api/v1/users/"+userID, reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("User updated successfully!") var result map[string]interface{} if err := json.Unmarshal(data, &result); err == nil { if username, ok := result["username"].(string); ok { fmt.Printf("User: %s\n", username) } } } // deleteUser deletes a user func (a *App) deleteUser() { a.listUsers() userID := a.readInput("User ID to delete: ") confirm := a.readInput("Delete user? (yes/no): ") if confirm != "yes" { fmt.Println("Deletion cancelled.") return } err := a.client.Delete("/api/v1/users/" + userID) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("User %s deleted successfully!\n", userID) } // changePassword changes the current user's password func (a *App) changePassword() { // Get user ID from current user or ask for username var userID string if a.currentUser != nil { if id, ok := a.currentUser["id"].(string); ok { userID = id } } if userID == "" { // Fallback: ask for username and look it up username := a.readInput("Your username: ") data, err := a.client.Get("/api/v1/users") if err != nil { fmt.Printf("Error getting users: %v\n", err) return } var users []map[string]interface{} if err := json.Unmarshal(data, &users); err != nil { fmt.Printf("Error parsing users: %v\n", err) return } for _, user := range users { if u, ok := user["username"].(string); ok && u == username { if id, ok := user["id"].(string); ok { userID = id break } } } if userID == "" { fmt.Printf("User '%s' not found.\n", username) return } } oldPassword := a.readPassword("Current password: ") newPassword := a.readPassword("New password: ") confirmPassword := a.readPassword("Confirm new password: ") if newPassword != confirmPassword { fmt.Println("Passwords do not match.") return } reqBody := map[string]interface{}{ "old_password": oldPassword, "new_password": newPassword, } data, err := a.client.Put("/api/v1/users/"+userID+"/password", reqBody) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Println("Password changed 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) } } } // ===== Service Management ===== // handleServiceMenu handles service management menu func (a *App) handleServiceMenu() { for { fmt.Println("\n=== Service Management ===") fmt.Println("1. Service Status") fmt.Println("2. Start Service") fmt.Println("3. Stop Service") fmt.Println("4. Restart Service") fmt.Println("5. Reload Service") fmt.Println("6. View Service Logs") fmt.Println("0. Back") fmt.Println() choice := a.readInput("Select option: ") switch choice { case "1": a.serviceStatus() case "2": a.serviceStart() case "3": a.serviceStop() case "4": a.serviceRestart() case "5": a.serviceReload() case "6": a.serviceLogs() case "0": return default: fmt.Println("Invalid option.") } } } // serviceStatus shows service status func (a *App) serviceStatus() { fmt.Println("\n=== Service Status ===") fmt.Println("Checking atlas-api service status...") // Use systemctl to check status cmd := exec.Command("systemctl", "status", "atlas-api", "--no-pager") output, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error checking status: %v\n", err) fmt.Println(string(output)) return } fmt.Println(string(output)) } // serviceStart starts the service func (a *App) serviceStart() { fmt.Println("\n=== Start Service ===") cmd := exec.Command("systemctl", "start", "atlas-api") output, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error starting service: %v\n", err) fmt.Println(string(output)) return } fmt.Println("Service started successfully!") } // serviceStop stops the service func (a *App) serviceStop() { fmt.Println("\n=== Stop Service ===") cmd := exec.Command("systemctl", "stop", "atlas-api") output, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error stopping service: %v\n", err) fmt.Println(string(output)) return } fmt.Println("Service stopped successfully!") } // serviceRestart restarts the service func (a *App) serviceRestart() { fmt.Println("\n=== Restart Service ===") cmd := exec.Command("systemctl", "restart", "atlas-api") output, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error restarting service: %v\n", err) fmt.Println(string(output)) return } fmt.Println("Service restarted successfully!") } // serviceReload reloads the service func (a *App) serviceReload() { fmt.Println("\n=== Reload Service ===") cmd := exec.Command("systemctl", "reload", "atlas-api") output, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error reloading service: %v\n", err) fmt.Println(string(output)) return } fmt.Println("Service reloaded successfully!") } // serviceLogs shows service logs func (a *App) serviceLogs() { linesStr := a.readInput("Number of log lines (default: 50): ") lines := "50" if linesStr != "" { lines = linesStr } fmt.Printf("\n=== Service Logs (last %s lines) ===\n", lines) cmd := exec.Command("journalctl", "-u", "atlas-api", "-n", lines, "--no-pager") output, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error viewing logs: %v\n", err) fmt.Println(string(output)) return } fmt.Println(string(output)) }