Files
BAMS/backend/internal/api/handlers.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})
}