package handlers import ( "log" "net/http" "time" "github.com/Fimeg/RedFlag/aggregator-server/internal/database/queries" "github.com/Fimeg/RedFlag/aggregator-server/internal/models" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // StorageMetricsHandler handles storage metrics endpoints type StorageMetricsHandler struct { queries *queries.StorageMetricsQueries } // NewStorageMetricsHandler creates a new storage metrics handler func NewStorageMetricsHandler(queries *queries.StorageMetricsQueries) *StorageMetricsHandler { return &StorageMetricsHandler{ queries: queries, } } // ReportStorageMetrics handles POST /api/v1/agents/:id/storage-metrics func (h *StorageMetricsHandler) ReportStorageMetrics(c *gin.Context) { // Get agent ID from context (set by middleware) agentID := c.MustGet("agent_id").(uuid.UUID) // Parse request body var req models.StorageMetricRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } // Validate agent ID matches if req.AgentID != agentID { c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID mismatch"}) return } // Insert storage metrics with error isolation for _, metric := range req.Metrics { dbMetric := models.StorageMetric{ ID: uuid.New(), AgentID: req.AgentID, Mountpoint: metric.Mountpoint, Device: metric.Device, DiskType: metric.DiskType, Filesystem: metric.Filesystem, TotalBytes: metric.TotalBytes, UsedBytes: metric.UsedBytes, AvailableBytes: metric.AvailableBytes, UsedPercent: metric.UsedPercent, Severity: metric.Severity, Metadata: metric.Metadata, CreatedAt: time.Now(), } if err := h.queries.InsertStorageMetric(c.Request.Context(), dbMetric); err != nil { log.Printf("[ERROR] Failed to insert storage metric for agent %s: %v\n", agentID, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to insert storage metric"}) return } } c.JSON(http.StatusOK, gin.H{ "status": "success", "message": "Storage metrics reported successfully", }) } // StorageMetricResponse represents the response format for storage metrics type StorageMetricResponse struct { ID uuid.UUID `json:"id"` AgentID uuid.UUID `json:"agent_id"` Mountpoint string `json:"mountpoint"` Device string `json:"device"` DiskType string `json:"disk_type"` Filesystem string `json:"filesystem"` Total int64 `json:"total"` // Changed from total_bytes Used int64 `json:"used"` // Changed from used_bytes Available int64 `json:"available"` // Changed from available_bytes UsedPercent float64 `json:"used_percent"` Severity string `json:"severity"` IsRoot bool `json:"is_root"` IsLargest bool `json:"is_largest"` Metadata map[string]interface{} `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at"` } // GetStorageMetrics handles GET /api/v1/agents/:id/storage-metrics func (h *StorageMetricsHandler) GetStorageMetrics(c *gin.Context) { // Get agent ID from context (set by middleware) agentID := c.MustGet("agent_id").(uuid.UUID) // Get the latest storage metrics (one per mountpoint) latestMetrics, err := h.queries.GetLatestStorageMetrics(c.Request.Context(), agentID) if err != nil { log.Printf("[ERROR] Failed to retrieve storage metrics for agent %s: %v\n", agentID, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve storage metrics"}) return } // Transform to response format var responseMetrics []StorageMetricResponse for _, metric := range latestMetrics { // Check if this is the root mountpoint isRoot := metric.Mountpoint == "/" // Create response with fields matching frontend expectations responseMetric := StorageMetricResponse{ ID: metric.ID, AgentID: metric.AgentID, Mountpoint: metric.Mountpoint, Device: metric.Device, DiskType: metric.DiskType, Filesystem: metric.Filesystem, Total: metric.TotalBytes, // Map total_bytes -> total Used: metric.UsedBytes, // Map used_bytes -> used Available: metric.AvailableBytes, // Map available_bytes -> available UsedPercent: metric.UsedPercent, Severity: metric.Severity, IsRoot: isRoot, IsLargest: false, // Will be determined below Metadata: metric.Metadata, CreatedAt: metric.CreatedAt, } responseMetrics = append(responseMetrics, responseMetric) } // Determine which disk is the largest if len(responseMetrics) > 0 { var maxSize int64 var maxIndex int for i, metric := range responseMetrics { if metric.Total > maxSize { maxSize = metric.Total maxIndex = i } } // Mark the largest disk responseMetrics[maxIndex].IsLargest = true } c.JSON(http.StatusOK, gin.H{ "metrics": responseMetrics, "total": len(responseMetrics), }) }