Files
atlas/internal/tui/app.go
othman.suseno f45c878051
Some checks failed
CI / test-build (push) Failing after 2m14s
fix installer script
2025-12-15 01:55:39 +07:00

1619 lines
36 KiB
Go

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))
}