fix: Complete AgentHealth improvements and build fixes

- Update Update scanner default from 15min to 12 hours (backend)
- Add 1 week and 2 week frequency options (frontend)
- Rename AgentScanners to AgentHealth component
- Add OS-aware package manager badges (APT, DNF, Windows/Winget, Docker)
- Fix all build errors (types, imports, storage metrics)
- Add useMemo optimization for enabled/auto-run counts
This commit is contained in:
Fimeg
2025-12-17 21:08:38 -05:00
parent 9effa967a1
commit a90692f1d8
11 changed files with 207 additions and 827 deletions

View File

@@ -1,16 +1,14 @@
package handlers
import (
"encoding/json"
"fmt"
"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"
"github.com/gorilla/mux"
"github.com/lib/pq"
)
// StorageMetricsHandler handles storage metrics endpoints
@@ -25,28 +23,21 @@ func NewStorageMetricsHandler(queries *queries.StorageMetricsQueries) *StorageMe
}
}
// ReportStorageMetrics handles POST /api/v1/agents/{id}/storage-metrics
func (h *StorageMetricsHandler) ReportStorageMetrics(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
agentIDStr := vars["id"]
// Parse agent ID
agentID, err := uuid.Parse(agentIDStr)
if err != nil {
http.Error(w, "Invalid agent ID", http.StatusBadRequest)
return
}
// 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 := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
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 {
http.Error(w, "Agent ID mismatch", http.StatusBadRequest)
c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID mismatch"})
return
}
@@ -68,58 +59,34 @@ func (h *StorageMetricsHandler) ReportStorageMetrics(w http.ResponseWriter, r *h
CreatedAt: time.Now(),
}
if err := h.queries.InsertStorageMetric(r.Context(), dbMetric); err != nil {
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)
http.Error(w, "Failed to insert storage metric", http.StatusInternalServerError)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to insert storage metric"})
return
}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
c.JSON(http.StatusOK, gin.H{
"status": "success",
"message": "Storage metrics reported successfully",
})
}
// GetStorageMetrics handles GET /api/v1/agents/{id}/storage-metrics
func (h *StorageMetricsHandler) GetStorageMetrics(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
agentIDStr := vars["id"]
// Parse agent ID
agentID, err := uuid.Parse(agentIDStr)
if err != nil {
http.Error(w, "Invalid agent ID", http.StatusBadRequest)
return
}
// Optional query parameters for pagination/limit
limit := parseIntQueryParam(r, "limit", 100)
offset := parseIntQueryParam(r, "offset", 0)
// 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 storage metrics
metrics, err := h.queries.GetStorageMetricsByAgentID(r.Context(), agentID, limit, offset)
metrics, err := h.queries.GetStorageMetricsByAgentID(c.Request.Context(), agentID, 100, 0)
if err != nil {
log.Printf("[ERROR] Failed to retrieve storage metrics for agent %s: %v\n", agentID, err)
http.Error(w, "Failed to retrieve storage metrics", http.StatusInternalServerError)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve storage metrics"})
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
c.JSON(http.StatusOK, gin.H{
"metrics": metrics,
"total": len(metrics),
})
}
// parseIntQueryParam safely parses integer query parameters with defaults
func parseIntQueryParam(r *http.Request, key string, defaultValue int) int {
if val := r.URL.Query().Get(key); val != "" {
var result int
if _, err := fmt.Sscanf(val, "%d", &result); err == nil && result > 0 {
return result
}
}
return defaultValue
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"fmt"
"time"
"github.com/Fimeg/RedFlag/aggregator-server/internal/models"
"github.com/google/uuid"
@@ -136,7 +135,7 @@ func (q *StorageMetricsQueries) GetLatestStorageMetrics(ctx context.Context, age
// GetStorageMetricsSummary returns summary statistics for an agent
func (q *StorageMetricsQueries) GetStorageMetricsSummary(ctx context.Context, agentID uuid.UUID) (map[string]interface{}, error) {
query := `
SELECT
SELECT
COUNT(*) as total_disks,
COUNT(CASE WHEN severity = 'critical' THEN 1 END) as critical_disks,
COUNT(CASE WHEN severity = 'important' THEN 1 END) as important_disks,
@@ -149,19 +148,38 @@ func (q *StorageMetricsQueries) GetStorageMetricsSummary(ctx context.Context, ag
AND created_at >= NOW() - INTERVAL '24 hours'
`
var summary map[string]interface{}
var (
totalDisks int
criticalDisks int
importantDisks int
avgUsedPercent sql.NullFloat64
maxUsedPercent sql.NullFloat64
firstCollectedAt sql.NullTime
lastCollectedAt sql.NullTime
)
err := q.db.QueryRowContext(ctx, query, agentID).Scan(
&summary["total_disks"],
&summary["critical_disks"],
&summary["important_disks"],
&summary["avg_used_percent"],
&summary["max_used_percent"],
&summary["first_collected_at"],
&summary["last_collected_at"],
&totalDisks,
&criticalDisks,
&importantDisks,
&avgUsedPercent,
&maxUsedPercent,
&firstCollectedAt,
&lastCollectedAt,
)
if err != nil {
return nil, fmt.Errorf("failed to get storage metrics summary: %w", err)
}
summary := map[string]interface{}{
"total_disks": totalDisks,
"critical_disks": criticalDisks,
"important_disks": importantDisks,
"avg_used_percent": avgUsedPercent.Float64,
"max_used_percent": maxUsedPercent.Float64,
"first_collected_at": firstCollectedAt.Time,
"last_collected_at": lastCollectedAt.Time,
}
return summary, nil
}
}