feat(audit): F1+F2 告警静默规则标记完成 + 审计日志CSV导出实现
F1 告警静默规则 [done]: - 后端 alert_silence.go: CRUD + Toggle (onetime/daily/weekly 三模式) - 告警引擎 isSilenced 集成 (alert_engine.go) - 前端 SilenceRuleTab + SilenceDialog - 路由 /alerts/silences 已注册 F2 审计日志导出 [done]: - 重写 ExportAuditLogs: 真正的 CSV 导出 (encoding/csv + UTF-8 BOM) - 支持 module/action/username/startTime/endTime 筛选 - 导出限制 10000 条防溢出,自动记录审计日志 - JSON 格式向后兼容 (format=json) - 前端 exportLogs 传递查询参数 同时包含累积的前期工作: - 目录重构 internal/ -> controller/model/router (v0.55-v0.57) - 后端分层架构 dto/repository/service/utils - 审计模块全功能 (终端审计/操作审计/登录日志) - asciicast v2 录制格式 - SFTP 文件浏览器 - StatStrip 紧凑统计条 - 在线会话自动刷新 - LiveMonitor WebSocket 诊断修复 验证: go build ✅ | npm run build ✅ | Docker 部署 ✅ | 10 回归端点 ✅ | CSV 导出 ✅ | 静默 CRUD ✅
This commit is contained in:
1 parent
e57718998a
commit
77c234b060
314 files changed
+10050
-4065
No files matched your search
@@ -0,0 +1,146 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"ezops/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (h *KubernetesHandler) GetMultiClusterComparison(c *gin.Context) {
|
||||
// 1. 获取所有集群
|
||||
var clusters []model.KubernetesCluster
|
||||
h.db.Find(&clusters)
|
||||
|
||||
type ClusterComparison struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
NodeCount int `json:"node_count"`
|
||||
PodCount int `json:"pod_count"`
|
||||
DeploymentCount int `json:"deployment_count"`
|
||||
ServiceCount int `json:"service_count"`
|
||||
CPUUsage float64 `json:"cpu_usage"`
|
||||
MemoryUsage float64 `json:"memory_usage"`
|
||||
APIServer string `json:"api_server"`
|
||||
LastSyncAt *time.Time `json:"last_sync_at"`
|
||||
|
||||
// Node-level aggregated data from kubernetes_nodes table
|
||||
TotalCPUCores int `json:"total_cpu_cores"`
|
||||
TotalMemoryMB int64 `json:"total_memory_mb"`
|
||||
MasterCount int `json:"master_count"`
|
||||
WorkerCount int `json:"worker_count"`
|
||||
HealthyNodes int `json:"healthy_nodes"`
|
||||
UnhealthyNodes int `json:"unhealthy_nodes"`
|
||||
}
|
||||
|
||||
// 2. 获取所有节点的聚合数据(按 cluster_id 分组)
|
||||
type NodeAgg struct {
|
||||
ClusterID uint
|
||||
NodeCount int
|
||||
MasterCount int
|
||||
WorkerCount int
|
||||
HealthyNodes int
|
||||
UnhealthyNodes int
|
||||
TotalCPU int
|
||||
TotalMemory int64
|
||||
AvgCPUUsage float64
|
||||
AvgMemUsage float64
|
||||
}
|
||||
nodeAggMap := map[uint]*NodeAgg{}
|
||||
{
|
||||
var nodes []model.KubernetesNode
|
||||
h.db.Find(&nodes)
|
||||
for _, n := range nodes {
|
||||
agg, ok := nodeAggMap[n.ClusterID]
|
||||
if !ok {
|
||||
agg = &NodeAgg{ClusterID: n.ClusterID}
|
||||
nodeAggMap[n.ClusterID] = agg
|
||||
}
|
||||
agg.NodeCount++
|
||||
agg.TotalCPU += n.CPU
|
||||
agg.TotalMemory += n.Memory
|
||||
if n.CPUUsage > 0 || n.MemoryUsage > 0 {
|
||||
if agg.AvgCPUUsage == 0 { agg.AvgCPUUsage = n.CPUUsage } else { agg.AvgCPUUsage = (agg.AvgCPUUsage*float64(agg.NodeCount-1) + n.CPUUsage) / float64(agg.NodeCount) }
|
||||
if agg.AvgMemUsage == 0 { agg.AvgMemUsage = n.MemoryUsage } else { agg.AvgMemUsage = (agg.AvgMemUsage*float64(agg.NodeCount-1) + n.MemoryUsage) / float64(agg.NodeCount) }
|
||||
}
|
||||
if n.Role == "master" {
|
||||
agg.MasterCount++
|
||||
} else {
|
||||
agg.WorkerCount++
|
||||
}
|
||||
if n.Status == "Ready" {
|
||||
agg.HealthyNodes++
|
||||
} else {
|
||||
agg.UnhealthyNodes++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 构建对比数据
|
||||
comparisons := []ClusterComparison{}
|
||||
totalNodes, totalPods, totalDeployments, totalServices := 0, 0, 0, 0
|
||||
for _, cl := range clusters {
|
||||
cc := ClusterComparison{
|
||||
ID: cl.ID,
|
||||
Name: cl.Name,
|
||||
Code: cl.Code,
|
||||
Status: cl.Status,
|
||||
Version: cl.Version,
|
||||
NodeCount: cl.NodeCount,
|
||||
PodCount: cl.PodCount,
|
||||
DeploymentCount: cl.DeploymentCount,
|
||||
ServiceCount: cl.ServiceCount,
|
||||
CPUUsage: cl.CPUUsage,
|
||||
MemoryUsage: cl.MemoryUsage,
|
||||
APIServer: cl.APIServer,
|
||||
LastSyncAt: cl.LastSyncAt,
|
||||
}
|
||||
// 优先用 node 级别的实时聚合数据
|
||||
if agg, ok := nodeAggMap[cl.ID]; ok && agg.NodeCount > 0 {
|
||||
cc.TotalCPUCores = agg.TotalCPU
|
||||
cc.TotalMemoryMB = agg.TotalMemory
|
||||
cc.MasterCount = agg.MasterCount
|
||||
cc.WorkerCount = agg.WorkerCount
|
||||
cc.HealthyNodes = agg.HealthyNodes
|
||||
cc.UnhealthyNodes = agg.UnhealthyNodes
|
||||
// 如果集群级别的 cpu_usage 为 0,使用节点平均值
|
||||
if cc.CPUUsage == 0 && agg.AvgCPUUsage > 0 {
|
||||
cc.CPUUsage = agg.AvgCPUUsage
|
||||
}
|
||||
if cc.MemoryUsage == 0 && agg.AvgMemUsage > 0 {
|
||||
cc.MemoryUsage = agg.AvgMemUsage
|
||||
}
|
||||
}
|
||||
totalNodes += cc.NodeCount
|
||||
totalPods += cc.PodCount
|
||||
totalDeployments += cc.DeploymentCount
|
||||
totalServices += cc.ServiceCount
|
||||
comparisons = append(comparisons, cc)
|
||||
}
|
||||
|
||||
// 4. 返回结果
|
||||
c.JSON(200, model.Response{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: gin.H{
|
||||
"clusters": comparisons,
|
||||
"totals": gin.H{
|
||||
"cluster_count": len(clusters),
|
||||
"node_count": totalNodes,
|
||||
"pod_count": totalPods,
|
||||
"deployment_count": totalDeployments,
|
||||
"service_count": totalServices,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RBAC — ServiceAccount / Role / RoleBinding (code-based)
|
||||
// ============================================================================
|
||||
|
||||
// ListServiceAccountsNS lists ServiceAccounts in a namespace
|
||||
Reference in new issue
Block a user