This commit is contained in:
11
install.sh
11
install.sh
@@ -438,7 +438,16 @@ MAINGO
|
||||
chmod 755 "$INSTALL_DIR/bin/atlas-api"
|
||||
chmod 755 "$INSTALL_DIR/bin/atlas-tui"
|
||||
|
||||
echo -e "${GREEN}Binaries built successfully${NC}"
|
||||
# Create symlinks in /usr/local/bin for global access
|
||||
echo -e "${GREEN}Creating symlinks for global access...${NC}"
|
||||
ln -sf "$INSTALL_DIR/bin/atlas-api" /usr/local/bin/atlas-api
|
||||
ln -sf "$INSTALL_DIR/bin/atlas-tui" /usr/local/bin/atlas-tui
|
||||
chmod 755 /usr/local/bin/atlas-api
|
||||
chmod 755 /usr/local/bin/atlas-tui
|
||||
|
||||
echo -e "${GREEN}Binaries built and installed successfully${NC}"
|
||||
echo " Binaries available at: $INSTALL_DIR/bin/"
|
||||
echo " Global commands: atlas-api, atlas-tui"
|
||||
}
|
||||
|
||||
# Create systemd service
|
||||
|
||||
@@ -5,13 +5,15 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// App represents the TUI application
|
||||
type App struct {
|
||||
client *APIClient
|
||||
reader *bufio.Reader
|
||||
client *APIClient
|
||||
reader *bufio.Reader
|
||||
currentUser map[string]interface{}
|
||||
}
|
||||
|
||||
// NewApp creates a new TUI application
|
||||
@@ -47,6 +49,10 @@ func (a *App) Run() error {
|
||||
a.handleSystemMenu()
|
||||
case "5":
|
||||
a.handleBackupMenu()
|
||||
case "6":
|
||||
a.handleUserMenu()
|
||||
case "7":
|
||||
a.handleServiceMenu()
|
||||
case "0", "q", "exit":
|
||||
fmt.Println("Goodbye!")
|
||||
return nil
|
||||
@@ -67,11 +73,30 @@ func (a *App) login() error {
|
||||
username := a.readInput("Username: ")
|
||||
password := a.readPassword("Password: ")
|
||||
|
||||
token, err := a.client.Login(username, 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
|
||||
@@ -100,6 +125,8 @@ func (a *App) showMainMenu() {
|
||||
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()
|
||||
}
|
||||
@@ -109,9 +136,20 @@ func (a *App) handleZFSMenu() {
|
||||
for {
|
||||
fmt.Println("\n=== ZFS Management ===")
|
||||
fmt.Println("1. List Pools")
|
||||
fmt.Println("2. List Datasets")
|
||||
fmt.Println("3. List ZVOLs")
|
||||
fmt.Println("4. List Disks")
|
||||
fmt.Println("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()
|
||||
|
||||
@@ -121,10 +159,32 @@ func (a *App) handleZFSMenu() {
|
||||
case "1":
|
||||
a.listPools()
|
||||
case "2":
|
||||
a.listDatasets()
|
||||
a.createPool()
|
||||
case "3":
|
||||
a.listZVOLs()
|
||||
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
|
||||
@@ -887,3 +947,672 @@ func (a *App) restoreBackup() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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))
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ func (c *APIClient) SetToken(token string) {
|
||||
c.token = token
|
||||
}
|
||||
|
||||
// Login authenticates with the API
|
||||
func (c *APIClient) Login(username, password string) (string, error) {
|
||||
// Login authenticates with the API and returns user info
|
||||
func (c *APIClient) Login(username, password string) (string, map[string]interface{}, error) {
|
||||
reqBody := map[string]string{
|
||||
"username": username,
|
||||
"password": password,
|
||||
@@ -40,39 +40,46 @@ func (c *APIClient) Login(username, password string) (string, error) {
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", c.baseURL+"/api/v1/auth/login", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("request failed: %w", err)
|
||||
return "", nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("login failed: %s", string(body))
|
||||
return "", nil, fmt.Errorf("login failed: %s", string(body))
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
token, ok := result["token"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("no token in response")
|
||||
return "", nil, fmt.Errorf("no token in response")
|
||||
}
|
||||
|
||||
c.SetToken(token)
|
||||
return token, nil
|
||||
|
||||
// Extract user info if available
|
||||
var user map[string]interface{}
|
||||
if u, ok := result["user"].(map[string]interface{}); ok {
|
||||
user = u
|
||||
}
|
||||
|
||||
return token, user, nil
|
||||
}
|
||||
|
||||
// Get performs a GET request
|
||||
@@ -163,3 +170,38 @@ func (c *APIClient) Delete(path string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put performs a PUT request
|
||||
func (c *APIClient) Put(path string, data interface{}) ([]byte, error) {
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", c.baseURL+path, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if c.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user