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