519 lines
15 KiB
Go
519 lines
15 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/bams/backend/internal/services"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
func handleHealth() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
jsonResponse(w, http.StatusOK, map[string]interface{}{
|
|
"status": "ok",
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
}
|
|
|
|
func handleDashboard(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
// Get dashboard data from all services
|
|
dashboard := map[string]interface{}{
|
|
"disk": map[string]interface{}{
|
|
"total_capacity": 0,
|
|
"used_capacity": 0,
|
|
"repositories": 0,
|
|
},
|
|
"tape": map[string]interface{}{
|
|
"library_status": "unknown",
|
|
"drives_active": 0,
|
|
"total_slots": 0,
|
|
"loaded_tapes": 0,
|
|
},
|
|
"iscsi": map[string]interface{}{
|
|
"targets": 0,
|
|
"sessions": 0,
|
|
},
|
|
"bacula": map[string]interface{}{
|
|
"status": "unknown",
|
|
},
|
|
"alerts": []map[string]interface{}{},
|
|
}
|
|
|
|
// Populate from services
|
|
repos, _ := sm.Disk.ListRepositories()
|
|
if repos != nil {
|
|
dashboard["disk"].(map[string]interface{})["repositories"] = len(repos)
|
|
}
|
|
|
|
library, _ := sm.Tape.GetLibrary()
|
|
if library != nil {
|
|
dashboard["tape"].(map[string]interface{})["library_status"] = library.Status
|
|
dashboard["tape"].(map[string]interface{})["drives_active"] = library.ActiveDrives
|
|
dashboard["tape"].(map[string]interface{})["total_slots"] = library.TotalSlots
|
|
}
|
|
|
|
targets, _ := sm.ISCSI.ListTargets()
|
|
if targets != nil {
|
|
dashboard["iscsi"].(map[string]interface{})["targets"] = len(targets)
|
|
}
|
|
|
|
sessions, _ := sm.ISCSI.ListSessions()
|
|
if sessions != nil {
|
|
dashboard["iscsi"].(map[string]interface{})["sessions"] = len(sessions)
|
|
}
|
|
|
|
baculaStatus, _ := sm.Bacula.GetStatus()
|
|
if baculaStatus != nil {
|
|
dashboard["bacula"].(map[string]interface{})["status"] = baculaStatus.Status
|
|
}
|
|
|
|
jsonResponse(w, http.StatusOK, dashboard)
|
|
}
|
|
}
|
|
|
|
// Disk repository handlers
|
|
func handleListRepositories(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
repos, err := sm.Disk.ListRepositories()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, repos)
|
|
}
|
|
}
|
|
|
|
func handleCreateRepository(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Size string `json:"size"`
|
|
Type string `json:"type"` // "lvm" or "zfs"
|
|
VGName string `json:"vg_name,omitempty"`
|
|
PoolName string `json:"pool_name,omitempty"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
// Validate input
|
|
if err := validateRepositoryName(req.Name); err != nil {
|
|
jsonError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if err := validateSize(req.Size); err != nil {
|
|
jsonError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if req.Type != "lvm" && req.Type != "zfs" {
|
|
jsonError(w, http.StatusBadRequest, "type must be 'lvm' or 'zfs'")
|
|
return
|
|
}
|
|
if req.Type == "lvm" && req.VGName == "" {
|
|
jsonError(w, http.StatusBadRequest, "vg_name is required for LVM repositories")
|
|
return
|
|
}
|
|
if req.Type == "zfs" && req.PoolName == "" {
|
|
jsonError(w, http.StatusBadRequest, "pool_name is required for ZFS repositories")
|
|
return
|
|
}
|
|
|
|
repo, err := sm.Disk.CreateRepository(req.Name, req.Size, req.Type, req.VGName, req.PoolName)
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusCreated, repo)
|
|
}
|
|
}
|
|
|
|
func handleGetRepository(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
repo, err := sm.Disk.GetRepository(vars["id"])
|
|
if err != nil {
|
|
jsonError(w, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, repo)
|
|
}
|
|
}
|
|
|
|
func handleDeleteRepository(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
if err := sm.Disk.DeleteRepository(vars["id"]); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusNoContent, nil)
|
|
}
|
|
}
|
|
|
|
// Tape library handlers
|
|
func handleGetLibrary(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
library, err := sm.Tape.GetLibrary()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, library)
|
|
}
|
|
}
|
|
|
|
func handleInventory(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if err := sm.Tape.RunInventory(); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, map[string]string{"status": "inventory_started"})
|
|
}
|
|
}
|
|
|
|
func handleListDrives(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
drives, err := sm.Tape.ListDrives()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, drives)
|
|
}
|
|
}
|
|
|
|
func handleLoadTape(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
var req struct {
|
|
Slot int `json:"slot"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
if err := sm.Tape.LoadTape(vars["id"], req.Slot); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, map[string]string{"status": "load_started"})
|
|
}
|
|
}
|
|
|
|
func handleUnloadTape(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
var req struct {
|
|
Slot int `json:"slot"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
if err := sm.Tape.UnloadTape(vars["id"], req.Slot); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, map[string]string{"status": "unload_started"})
|
|
}
|
|
}
|
|
|
|
func handleListSlots(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
slots, err := sm.Tape.ListSlots()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, slots)
|
|
}
|
|
}
|
|
|
|
// iSCSI target handlers
|
|
func handleListTargets(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
targets, err := sm.ISCSI.ListTargets()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, targets)
|
|
}
|
|
}
|
|
|
|
func handleCreateTarget(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
IQN string `json:"iqn"`
|
|
Portals []string `json:"portals"`
|
|
Initiators []string `json:"initiators"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
// Validate input
|
|
if err := validateIQN(req.IQN); err != nil {
|
|
jsonError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if len(req.Portals) == 0 {
|
|
jsonError(w, http.StatusBadRequest, "at least one portal is required")
|
|
return
|
|
}
|
|
for _, portal := range req.Portals {
|
|
if err := validatePortal(portal); err != nil {
|
|
jsonError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
target, err := sm.ISCSI.CreateTarget(req.IQN, req.Portals, req.Initiators)
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusCreated, target)
|
|
}
|
|
}
|
|
|
|
func handleGetTarget(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
target, err := sm.ISCSI.GetTarget(vars["id"])
|
|
if err != nil {
|
|
jsonError(w, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, target)
|
|
}
|
|
}
|
|
|
|
func handleUpdateTarget(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
var req struct {
|
|
Portals []string `json:"portals"`
|
|
Initiators []string `json:"initiators"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
target, err := sm.ISCSI.UpdateTarget(vars["id"], req.Portals, req.Initiators)
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, target)
|
|
}
|
|
}
|
|
|
|
func handleDeleteTarget(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
if err := sm.ISCSI.DeleteTarget(vars["id"]); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusNoContent, nil)
|
|
}
|
|
}
|
|
|
|
func handleApplyTarget(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
if err := sm.ISCSI.ApplyTarget(vars["id"]); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, map[string]string{"status": "applied"})
|
|
}
|
|
}
|
|
|
|
func handleListSessions(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
sessions, err := sm.ISCSI.ListSessions()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, sessions)
|
|
}
|
|
}
|
|
|
|
func handleAddLUN(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
var req struct {
|
|
LUNNumber int `json:"lun_number"`
|
|
DevicePath string `json:"device_path"`
|
|
Type string `json:"type"` // "disk" or "tape"
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonError(w, http.StatusBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.Type != "disk" && req.Type != "tape" {
|
|
jsonError(w, http.StatusBadRequest, "type must be 'disk' or 'tape'")
|
|
return
|
|
}
|
|
|
|
if err := sm.ISCSI.AddLUN(vars["id"], req.LUNNumber, req.DevicePath, req.Type); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
// Return updated target
|
|
target, err := sm.ISCSI.GetTarget(vars["id"])
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, target)
|
|
}
|
|
}
|
|
|
|
func handleRemoveLUN(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
lunNumber := 0
|
|
if _, err := fmt.Sscanf(vars["lun"], "%d", &lunNumber); err != nil {
|
|
jsonError(w, http.StatusBadRequest, "invalid LUN number")
|
|
return
|
|
}
|
|
|
|
if err := sm.ISCSI.RemoveLUN(vars["id"], lunNumber); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
// Return updated target
|
|
target, err := sm.ISCSI.GetTarget(vars["id"])
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, target)
|
|
}
|
|
}
|
|
|
|
// Bacula handlers
|
|
func handleBaculaStatus(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
status, err := sm.Bacula.GetStatus()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, status)
|
|
}
|
|
}
|
|
|
|
func handleGetBaculaConfig(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
config, err := sm.Bacula.GetConfig()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, config)
|
|
}
|
|
}
|
|
|
|
func handleGenerateBaculaConfig(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
config, err := sm.Bacula.GenerateConfig()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, config)
|
|
}
|
|
}
|
|
|
|
func handleBaculaInventory(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if err := sm.Bacula.RunInventory(); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, map[string]string{"status": "inventory_started"})
|
|
}
|
|
}
|
|
|
|
func handleBaculaRestart(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if err := sm.Bacula.Restart(); err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, map[string]string{"status": "restart_started"})
|
|
}
|
|
}
|
|
|
|
// Logs and diagnostics handlers
|
|
func handleGetLogs(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
lines := 100
|
|
if linesStr := r.URL.Query().Get("lines"); linesStr != "" {
|
|
if n, err := strconv.Atoi(linesStr); err == nil {
|
|
lines = n
|
|
}
|
|
}
|
|
logs, err := sm.Logs.GetLogs(vars["service"], lines)
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, logs)
|
|
}
|
|
}
|
|
|
|
func handleStreamLogs(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
// WebSocket streaming would be implemented here
|
|
jsonError(w, http.StatusNotImplemented, "WebSocket streaming not yet implemented")
|
|
}
|
|
}
|
|
|
|
func handleDownloadBundle(sm *services.ServiceManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
bundle, err := sm.Logs.GenerateSupportBundle()
|
|
if err != nil {
|
|
jsonError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/zip")
|
|
w.Header().Set("Content-Disposition", "attachment; filename=bams-support-bundle.zip")
|
|
w.Write(bundle)
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
func jsonResponse(w http.ResponseWriter, status int, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
if data != nil {
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
}
|
|
|
|
func jsonError(w http.ResponseWriter, status int, message string) {
|
|
jsonResponse(w, status, map[string]string{"error": message})
|
|
}
|