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:
attacker committed 2026-06-25 20:16:30 +08:00
1 parent e57718998a
commit 77c234b060
314 files changed
+10050 -4065

No files matched your search

+146
View File
@@ -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