1619 lines
36 KiB
Go
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))
|
|
}
|