package backup import ( "fmt" "net/http" "github.com/atlasos/calypso/internal/common/logger" "github.com/gin-gonic/gin" ) // Handler handles backup-related API requests type Handler struct { service *Service logger *logger.Logger } // NewHandler creates a new backup handler func NewHandler(service *Service, log *logger.Logger) *Handler { return &Handler{ service: service, logger: log, } } // ListJobs lists backup jobs with optional filters func (h *Handler) ListJobs(c *gin.Context) { opts := ListJobsOptions{ Status: c.Query("status"), JobType: c.Query("job_type"), ClientName: c.Query("client_name"), JobName: c.Query("job_name"), } // Parse pagination var limit, offset int if limitStr := c.Query("limit"); limitStr != "" { if _, err := fmt.Sscanf(limitStr, "%d", &limit); err == nil { opts.Limit = limit } } if offsetStr := c.Query("offset"); offsetStr != "" { if _, err := fmt.Sscanf(offsetStr, "%d", &offset); err == nil { opts.Offset = offset } } jobs, totalCount, err := h.service.ListJobs(c.Request.Context(), opts) if err != nil { h.logger.Error("Failed to list jobs", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list jobs"}) return } if jobs == nil { jobs = []Job{} } c.JSON(http.StatusOK, gin.H{ "jobs": jobs, "total": totalCount, "limit": opts.Limit, "offset": opts.Offset, }) } // GetJob retrieves a job by ID func (h *Handler) GetJob(c *gin.Context) { id := c.Param("id") job, err := h.service.GetJob(c.Request.Context(), id) if err != nil { if err.Error() == "job not found" { c.JSON(http.StatusNotFound, gin.H{"error": "job not found"}) return } h.logger.Error("Failed to get job", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get job"}) return } c.JSON(http.StatusOK, job) } // CreateJob creates a new backup job func (h *Handler) CreateJob(c *gin.Context) { var req CreateJobRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Validate job type validJobTypes := map[string]bool{ "Backup": true, "Restore": true, "Verify": true, "Copy": true, "Migrate": true, } if !validJobTypes[req.JobType] { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid job_type"}) return } // Validate job level validJobLevels := map[string]bool{ "Full": true, "Incremental": true, "Differential": true, "Since": true, } if !validJobLevels[req.JobLevel] { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid job_level"}) return } job, err := h.service.CreateJob(c.Request.Context(), req) if err != nil { h.logger.Error("Failed to create job", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create job"}) return } c.JSON(http.StatusCreated, job) } // ExecuteBconsoleCommand executes a bconsole command func (h *Handler) ExecuteBconsoleCommand(c *gin.Context) { var req struct { Command string `json:"command" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "command is required"}) return } output, err := h.service.ExecuteBconsoleCommand(c.Request.Context(), req.Command) if err != nil { h.logger.Error("Failed to execute bconsole command", "error", err, "command", req.Command) c.JSON(http.StatusInternalServerError, gin.H{ "error": "failed to execute command", "output": output, "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "output": output, }) } // ListClients lists all backup clients with optional filters func (h *Handler) ListClients(c *gin.Context) { opts := ListClientsOptions{} // Parse enabled filter if enabledStr := c.Query("enabled"); enabledStr != "" { enabled := enabledStr == "true" opts.Enabled = &enabled } // Parse search query opts.Search = c.Query("search") clients, err := h.service.ListClients(c.Request.Context(), opts) if err != nil { h.logger.Error("Failed to list clients", "error", err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "failed to list clients", "details": err.Error(), }) return } if clients == nil { clients = []Client{} } c.JSON(http.StatusOK, gin.H{ "clients": clients, "total": len(clients), }) } // GetDashboardStats returns dashboard statistics func (h *Handler) GetDashboardStats(c *gin.Context) { stats, err := h.service.GetDashboardStats(c.Request.Context()) if err != nil { h.logger.Error("Failed to get dashboard stats", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get dashboard stats"}) return } c.JSON(http.StatusOK, stats) } // ListStoragePools lists all storage pools func (h *Handler) ListStoragePools(c *gin.Context) { pools, err := h.service.ListStoragePools(c.Request.Context()) if err != nil { h.logger.Error("Failed to list storage pools", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list storage pools"}) return } if pools == nil { pools = []StoragePool{} } h.logger.Info("Listed storage pools", "count", len(pools)) c.JSON(http.StatusOK, gin.H{ "pools": pools, "total": len(pools), }) } // ListStorageVolumes lists all storage volumes func (h *Handler) ListStorageVolumes(c *gin.Context) { poolName := c.Query("pool_name") volumes, err := h.service.ListStorageVolumes(c.Request.Context(), poolName) if err != nil { h.logger.Error("Failed to list storage volumes", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list storage volumes"}) return } if volumes == nil { volumes = []StorageVolume{} } c.JSON(http.StatusOK, gin.H{ "volumes": volumes, "total": len(volumes), }) } // ListStorageDaemons lists all storage daemons func (h *Handler) ListStorageDaemons(c *gin.Context) { daemons, err := h.service.ListStorageDaemons(c.Request.Context()) if err != nil { h.logger.Error("Failed to list storage daemons", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list storage daemons"}) return } if daemons == nil { daemons = []StorageDaemon{} } c.JSON(http.StatusOK, gin.H{ "daemons": daemons, "total": len(daemons), }) } // CreateStoragePool creates a new storage pool func (h *Handler) CreateStoragePool(c *gin.Context) { var req CreatePoolRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } pool, err := h.service.CreateStoragePool(c.Request.Context(), req) if err != nil { h.logger.Error("Failed to create storage pool", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, pool) } // DeleteStoragePool deletes a storage pool func (h *Handler) DeleteStoragePool(c *gin.Context) { idStr := c.Param("id") if idStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "pool ID is required"}) return } var poolID int if _, err := fmt.Sscanf(idStr, "%d", &poolID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pool ID"}) return } err := h.service.DeleteStoragePool(c.Request.Context(), poolID) if err != nil { h.logger.Error("Failed to delete storage pool", "error", err, "pool_id", poolID) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "pool deleted successfully"}) } // CreateStorageVolume creates a new storage volume func (h *Handler) CreateStorageVolume(c *gin.Context) { var req CreateVolumeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } volume, err := h.service.CreateStorageVolume(c.Request.Context(), req) if err != nil { h.logger.Error("Failed to create storage volume", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, volume) } // UpdateStorageVolume updates a storage volume func (h *Handler) UpdateStorageVolume(c *gin.Context) { idStr := c.Param("id") if idStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "volume ID is required"}) return } var volumeID int if _, err := fmt.Sscanf(idStr, "%d", &volumeID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid volume ID"}) return } var req UpdateVolumeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } volume, err := h.service.UpdateStorageVolume(c.Request.Context(), volumeID, req) if err != nil { h.logger.Error("Failed to update storage volume", "error", err, "volume_id", volumeID) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, volume) } // DeleteStorageVolume deletes a storage volume func (h *Handler) DeleteStorageVolume(c *gin.Context) { idStr := c.Param("id") if idStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "volume ID is required"}) return } var volumeID int if _, err := fmt.Sscanf(idStr, "%d", &volumeID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid volume ID"}) return } err := h.service.DeleteStorageVolume(c.Request.Context(), volumeID) if err != nil { h.logger.Error("Failed to delete storage volume", "error", err, "volume_id", volumeID) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "volume deleted successfully"}) } // ListMedia lists all media from bconsole "list media" command func (h *Handler) ListMedia(c *gin.Context) { media, err := h.service.ListMedia(c.Request.Context()) if err != nil { h.logger.Error("Failed to list media", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if media == nil { media = []Media{} } h.logger.Info("Listed media", "count", len(media)) c.JSON(http.StatusOK, gin.H{ "media": media, "total": len(media), }) }