Files
2026-07-09 14:16:56 +08:00

274 lines
7.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controller
import (
"fmt"
"sort"
"strconv"
"time"
"ezops/model"
k8sclient "ezops/pkg/kubernetesclient"
"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"`
ObjectKind string `json:"object_kind"`
ObjectName string `json:"object_name"`
Source string `json:"source"`
Count int `json:"count"`
FirstTime string `json:"first_time"`
LastTime string `json:"last_time"`
EventTime string `json:"event_time"`
}
result := []EventSummary{}
for _, e := range events.Items {
if eventType != "" && e.Type != eventType {
continue
}
objectKind := e.InvolvedObject.Kind
objectName := e.InvolvedObject.Name
object := fmt.Sprintf("%s/%s", objectKind, objectName)
if e.InvolvedObject.Namespace != "" {
object = fmt.Sprintf("%s/%s/%s", e.InvolvedObject.Namespace, objectKind, objectName)
}
firstTime, lastTime, eventTime := formatEventTimes(e)
count := int(e.Count)
if count <= 0 {
count = 1
}
result = append(result, EventSummary{
Namespace: e.Metadata.Namespace, Name: e.Metadata.Name, Type: e.Type, Reason: e.Reason,
Message: e.Message, Object: object, ObjectKind: objectKind, ObjectName: objectName,
Source: formatEventSource(e), Count: count,
FirstTime: firstTime, LastTime: lastTime, EventTime: eventTime,
})
}
sort.Slice(result, func(i, j int) bool { return result[i].LastTime > result[j].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 {
firstTime, lastTime, eventTime := formatEventTimes(e)
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),
"object_kind": e.InvolvedObject.Kind,
"object_name": e.InvolvedObject.Name,
"source": formatEventSource(e),
"count": count,
"last_time": lastTime,
"first_time": firstTime,
"event_time": eventTime,
})
}
} 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})
}
// formatEventSource 格式化事件来源(component / reportingComponent)// Logan.Li
func formatEventSource(e k8sclient.EventItem) string {
if e.ReportingComponent != "" {
if e.ReportingInstance != "" {
return e.ReportingComponent + "/" + e.ReportingInstance
}
return e.ReportingComponent
}
src := e.Source.Component
if e.Source.Host != "" {
if src != "" {
return src + "@" + e.Source.Host
}
return e.Source.Host
}
return src
}
// formatEventTimes 提取首次/最后/事件时间(RFC3339)// Logan.Li
func formatEventTimes(e k8sclient.EventItem) (first, last, eventTime string) {
if !e.FirstTimestamp.IsZero() {
first = e.FirstTimestamp.Format(time.RFC3339)
}
if !e.LastTimestamp.IsZero() {
last = e.LastTimestamp.Format(time.RFC3339)
}
if !e.EventTime.IsZero() {
eventTime = e.EventTime.Format(time.RFC3339)
}
if last == "" && eventTime != "" {
last = eventTime
}
if first == "" && eventTime != "" {
first = eventTime
}
return first, last, eventTime
}
// ============================================================================
// Capacity Analysis — CPU/Memory request/limit per Deployment
// ============================================================================
// GetCapacityAnalysis returns resource analysis for all deployments