package controller import ( "fmt" "sort" "strconv" "time" "ezops/model" "github.com/gin-gonic/gin" ) func (h *KubernetesHandler) ListEvents(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Query("namespace") eventType := c.Query("type") // Normal or Warning events, err := client.ListEvents(ns) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type EventSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Type string `json:"type"` Reason string `json:"reason"` Message string `json:"message"` Object string `json:"object"` Count int `json:"count"` FirstTime string `json:"first_time"` LastTime string `json:"last_time"` } result := []EventSummary{} for _, e := range events.Items { if eventType != "" && e.Type != eventType { continue } object := fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name) if e.InvolvedObject.Namespace != "" { object = fmt.Sprintf("%s/%s/%s", e.InvolvedObject.Namespace, e.InvolvedObject.Kind, e.InvolvedObject.Name) } firstTime := ""; if !e.FirstTimestamp.IsZero() { firstTime = e.FirstTimestamp.Format(time.RFC3339) } lastTime := ""; if !e.LastTimestamp.IsZero() { lastTime = e.LastTimestamp.Format(time.RFC3339) } result = append(result, EventSummary{ Namespace: e.Metadata.Namespace, Name: e.Metadata.Name, Type: e.Type, Reason: e.Reason, Message: e.Message, Object: object, Count: int(e.Count), FirstTime: firstTime, LastTime: lastTime, }) } c.JSON(200, model.Response{Code: 200, Data: result}) } // ============================================================================ // Event Aggregation — 事件聚合统计 (M12) // ============================================================================ // GetEventsSummary returns aggregated event statistics for dashboard func (h *KubernetesHandler) GetEventsSummary(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}) return } ns := c.Query("namespace") events, err := client.ListEvents(ns) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } // Time window filter (hours) timeWindowHours := 0 if tw := c.Query("hours"); tw != "" { if v, err := strconv.Atoi(tw); err == nil && v > 0 { timeWindowHours = v } } now := time.Now() cutoff := time.Time{} if timeWindowHours > 0 { cutoff = now.Add(-time.Duration(timeWindowHours) * time.Hour) } // Aggregation maps totalCount := 0 warningCount := 0 normalCount := 0 byReason := map[string]int{} byNamespace := map[string]int{} byKind := map[string]int{} recentWarnings := []map[string]interface{}{} for _, e := range events.Items { // Time window filter if !cutoff.IsZero() && !e.LastTimestamp.IsZero() && e.LastTimestamp.Before(cutoff) { continue } totalCount++ count := int(e.Count) if count == 0 { count = 1 } nsName := e.Metadata.Namespace if nsName == "" { nsName = e.InvolvedObject.Namespace } if e.Type == "Warning" { warningCount += count byReason[e.Reason] += count byNamespace[nsName] += count byKind[e.InvolvedObject.Kind] += count // Collect recent warning events (top 10 by lastTimestamp) if len(recentWarnings) < 50 { recentWarnings = append(recentWarnings, map[string]interface{}{ "namespace": nsName, "type": e.Type, "reason": e.Reason, "message": e.Message, "object": fmt.Sprintf("%s/%s", e.InvolvedObject.Kind, e.InvolvedObject.Name), "count": count, "last_time": e.LastTimestamp.Format(time.RFC3339), "first_time": e.FirstTimestamp.Format(time.RFC3339), }) } } else { normalCount += count } } // Sort byReason by count desc → top reasons type ReasonStat struct { Reason string `json:"reason"` Count int `json:"count"` } reasonList := make([]ReasonStat, 0, len(byReason)) for r, cnt := range byReason { reasonList = append(reasonList, ReasonStat{Reason: r, Count: cnt}) } sort.Slice(reasonList, func(i, j int) bool { return reasonList[i].Count > reasonList[j].Count }) if len(reasonList) > 15 { reasonList = reasonList[:15] } // Sort byNamespace type NsStat struct { Namespace string `json:"namespace"` Count int `json:"count"` } nsList := make([]NsStat, 0, len(byNamespace)) for n, cnt := range byNamespace { nsList = append(nsList, NsStat{Namespace: n, Count: cnt}) } sort.Slice(nsList, func(i, j int) bool { return nsList[i].Count > nsList[j].Count }) if len(nsList) > 15 { nsList = nsList[:15] } // Sort byKind type KindStat struct { Kind string `json:"kind"` Count int `json:"count"` } kindList := make([]KindStat, 0, len(byKind)) for k, cnt := range byKind { kindList = append(kindList, KindStat{Kind: k, Count: cnt}) } sort.Slice(kindList, func(i, j int) bool { return kindList[i].Count > kindList[j].Count }) // Sort recentWarnings by last_time desc sort.Slice(recentWarnings, func(i, j int) bool { ti, _ := recentWarnings[i]["last_time"].(string) tj, _ := recentWarnings[j]["last_time"].(string) return ti > tj }) if len(recentWarnings) > 10 { recentWarnings = recentWarnings[:10] } result := map[string]interface{}{ "total": totalCount, "warning_count": warningCount, "normal_count": normalCount, "by_reason": reasonList, "by_namespace": nsList, "by_kind": kindList, "recent_warnings": recentWarnings, } c.JSON(200, model.Response{Code: 200, Data: result}) } // ============================================================================ // Capacity Analysis — CPU/Memory request/limit per Deployment // ============================================================================ // GetCapacityAnalysis returns resource analysis for all deployments