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

+28 -28
View File
@@ -5,8 +5,8 @@ import (
"math/rand"
"time"
"ezops/internal/config"
"ezops/internal/models"
"ezops/config"
"ezops/model"
"ezops/pkg/database"
"golang.org/x/crypto/bcrypt"
@@ -77,7 +77,7 @@ func generateMockData(db *gorm.DB) error {
}
func generateUsers(db *gorm.DB) error {
users := []models.User{
users := []model.User{
{Username: "admin", Email: "admin@ezops.local", RealName: "系统管理员", Phone: "13800138000", Status: 1, IsAdmin: true},
{Username: "ops01", Email: "ops01@ezops.local", RealName: "运维工程师1", Phone: "13800138001", Status: 1, IsAdmin: false},
{Username: "ops02", Email: "ops02@ezops.local", RealName: "运维工程师2", Phone: "13800138002", Status: 1, IsAdmin: false},
@@ -100,9 +100,9 @@ func generateServers(db *gorm.DB) error {
osTypes := []string{"CentOS 7.9", "CentOS 8.5", "Ubuntu 20.04", "Ubuntu 22.04", "Debian 11", "Rocky Linux 8.6"}
statuses := []string{"running", "stopped", "maintenance"}
servers := make([]models.Server, 0, 50)
servers := make([]model.Server, 0, 50)
for i := 0; i < 50; i++ {
server := models.Server{
server := model.Server{
Hostname: fmt.Sprintf("server-%03d", i+1),
IP: fmt.Sprintf("10.0.%d.%d", rand.Intn(256), rand.Intn(256)),
PublicIP: fmt.Sprintf("47.%d.%d.%d", rand.Intn(256), rand.Intn(256), rand.Intn(256)),
@@ -127,7 +127,7 @@ func generateServers(db *gorm.DB) error {
}
func generateCloudAccounts(db *gorm.DB) error {
accounts := []models.CloudAccount{
accounts := []model.CloudAccount{
{Name: "阿里云主账号", Provider: "aliyun", Status: "active", MonthlyCost: 28500},
{Name: "腾讯云主账号", Provider: "tencent", Status: "active", MonthlyCost: 15800},
{Name: "AWS 账号", Provider: "aws", Status: "active", MonthlyCost: 42000},
@@ -145,7 +145,7 @@ func generateCloudAccounts(db *gorm.DB) error {
}
func generateDomains(db *gorm.DB) error {
domains := []models.Domain{
domains := []model.Domain{
{Name: "ezops.local", Registrar: "aliyun", Status: "active", DNSProvider: "alidns"},
{Name: "api.ezops.local", Registrar: "aliyun", Status: "active", DNSProvider: "alidns"},
{Name: "app.ezops.local", Registrar: "tencent", Status: "active", DNSProvider: "dnspod"},
@@ -165,7 +165,7 @@ func generateDomains(db *gorm.DB) error {
}
func generateKubernetesClusters(db *gorm.DB) error {
clusters := []models.KubernetesCluster{
clusters := []model.KubernetesCluster{
{Name: "prod-cluster", Code: "prod", Version: "v1.28.0", Status: "healthy", NodeCount: 10, PodCount: 150, ServiceCount: 30, DeploymentCount: 15, APIEndpoint: "https://prod-api.ezops.local:6443", CPUUsage: 65.5, MemoryUsage: 72.3, Description: "生产环境 Kubernetes 集群"},
{Name: "staging-cluster", Code: "staging", Version: "v1.28.0", Status: "healthy", NodeCount: 5, PodCount: 80, ServiceCount: 15, DeploymentCount: 10, APIEndpoint: "https://staging-api.ezops.local:6443", CPUUsage: 45.2, MemoryUsage: 55.0, Description: "预发环境 Kubernetes 集群"},
{Name: "dev-cluster", Code: "dev", Version: "v1.27.0", Status: "warning", NodeCount: 3, PodCount: 45, ServiceCount: 8, DeploymentCount: 8, APIEndpoint: "https://dev-api.ezops.local:6443", CPUUsage: 80.1, MemoryUsage: 85.0, Description: "开发环境 Kubernetes 集群"},
@@ -179,7 +179,7 @@ func generateKubernetesClusters(db *gorm.DB) error {
for _, cluster := range clusters {
// 生成节点
for j := 0; j < cluster.NodeCount; j++ {
node := models.KubernetesNode{
node := model.KubernetesNode{
ClusterID: cluster.ID,
Name: fmt.Sprintf("%s-node-%d", cluster.Code, j+1),
IP: fmt.Sprintf("192.168.%d.%d", rand.Intn(256), rand.Intn(256)),
@@ -227,7 +227,7 @@ func generateKubernetesClusters(db *gorm.DB) error {
if available < 0 {
available = 0
}
dep := models.KubernetesDeployment{
dep := model.KubernetesDeployment{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: depNames[d],
@@ -253,7 +253,7 @@ func generateKubernetesClusters(db *gorm.DB) error {
}
for p := 0; p < numPods; p++ {
status := podStatuses[rand.Intn(len(podStatuses))]
pod := models.KubernetesPod{
pod := model.KubernetesPod{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1),
@@ -297,7 +297,7 @@ func generateKubernetesClusters(db *gorm.DB) error {
default:
ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]`
}
svc := models.KubernetesService{
svc := model.KubernetesService{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: svcNames[s],
@@ -323,7 +323,7 @@ func generateKubernetesClusters(db *gorm.DB) error {
numIngs := 2 + rand.Intn(3)
for ig := 0; ig < numIngs && ig < len(ingNames); ig++ {
hosts := fmt.Sprintf(`["%s.ezops.local"]`, ingNames[ig])
ing := models.KubernetesIngress{
ing := model.KubernetesIngress{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: ingNames[ig],
@@ -341,7 +341,7 @@ func generateKubernetesClusters(db *gorm.DB) error {
}
func generateAlertRules(db *gorm.DB) error {
rules := []models.AlertRule{
rules := []model.AlertRule{
{Name: "CPU 使用率告警", Type: "metric", Source: "prometheus", Expression: "cpu_usage > 80", Severity: "warning", Duration: 300, Enabled: true, Description: "CPU 使用率超过 80%"},
{Name: "CPU 使用率严重告警", Type: "metric", Source: "prometheus", Expression: "cpu_usage > 95", Severity: "critical", Duration: 120, Enabled: true, Description: "CPU 使用率超过 95%"},
{Name: "内存使用率告警", Type: "metric", Source: "prometheus", Expression: "memory_usage > 80", Severity: "warning", Duration: 300, Enabled: true, Description: "内存使用率超过 80%"},
@@ -356,7 +356,7 @@ func generateAlertRules(db *gorm.DB) error {
}
for i := 0; i < 20; i++ {
rule := rules[rand.Intn(len(rules))]
event := models.AlertEvent{
event := model.AlertEvent{
RuleID: rule.ID,
RuleName: rule.Name,
Severity: rule.Severity,
@@ -375,7 +375,7 @@ func generateAlertRules(db *gorm.DB) error {
}
func generateNotifyChannels(db *gorm.DB) error {
channels := []models.NotifyChannel{
channels := []model.NotifyChannel{
{Name: "运维邮件通知", Type: "email", Config: `{"recipients":["ops@ezops.com","admin@ezops.com"],"subject":"[AIOps告警]"}`, Enabled: true, TestStatus: "success"},
{Name: "短信通知", Type: "sms", Config: `{"phones":["13800138000","13900139000"],"template":"告警:{{alert_name}}"}`, Enabled: true, TestStatus: "success"},
{Name: "钉钉机器人", Type: "dingtalk", Config: `{"webhook":"https://oapi.dingtalk.com/robot/send?access_token=eZOps***","secret":"SEC***","at_all":false}`, Enabled: true, TestStatus: "success"},
@@ -392,7 +392,7 @@ func generateNotifyChannels(db *gorm.DB) error {
}
func generatePipelines(db *gorm.DB) error {
pipelines := []models.Pipeline{
pipelines := []model.Pipeline{
{Name: "前端构建", Code: "frontend-build", Type: "build", Source: "https://gitlab.com/ezops/frontend", Branch: "main", Enabled: true, Description: "前端项目构建流水线"},
{Name: "后端构建", Code: "backend-build", Type: "build", Source: "https://gitlab.com/ezops/backend", Branch: "main", Enabled: true, Description: "后端项目构建流水线"},
{Name: "部署预发", Code: "deploy-staging", Type: "deploy", Source: "https://gitlab.com/ezops/deploy", Branch: "staging", Enabled: true, Description: "部署到预发环境"},
@@ -406,7 +406,7 @@ func generatePipelines(db *gorm.DB) error {
}
for _, pipeline := range pipelines {
for j := 0; j < 5; j++ {
run := models.PipelineRun{
run := model.PipelineRun{
PipelineID: pipeline.ID,
BuildNumber: j + 1,
Status: []string{"success", "success", "success", "failed", "running"}[rand.Intn(5)],
@@ -431,7 +431,7 @@ func generateAppReleases(db *gorm.DB) error {
statuses := []string{"pending", "building", "deploying", "success", "success", "success", "failed", "rolled_back"}
authors := []string{"admin", "ops01", "dev01"}
releases := make([]models.AppRelease, 0, 10)
releases := make([]model.AppRelease, 0, 10)
for i := 0; i < 10; i++ {
app := apps[i%len(apps)]
env := environments[rand.Intn(len(environments))]
@@ -439,7 +439,7 @@ func generateAppReleases(db *gorm.DB) error {
minor := rand.Intn(20)
patch := rand.Intn(10)
release := models.AppRelease{
release := model.AppRelease{
AppName: app,
Version: fmt.Sprintf("v1.%d.%d", minor, patch),
Environment: env,
@@ -486,7 +486,7 @@ func generateAppReleases(db *gorm.DB) error {
}
func generateCronJobs(db *gorm.DB) error {
cronJobs := []models.CronJob{
cronJobs := []model.CronJob{
{Name: "数据库备份", CronExpr: "0 2 * * *", Type: "script", Content: "/scripts/backup-db.sh", Enabled: true, Description: "每日凌晨 2 点备份数据库"},
{Name: "清理日志", CronExpr: "0 3 * * 0", Type: "command", Content: "find /var/log -mtime +30 -delete", Enabled: true, Description: "每周日凌晨 3 点清理日志"},
{Name: "数据同步", CronExpr: "*/30 * * * *", Type: "script", Content: "/scripts/sync-data.sh", Enabled: true, Description: "每 30 分钟同步数据"},
@@ -503,7 +503,7 @@ func generateCronJobs(db *gorm.DB) error {
}
func generateScripts(db *gorm.DB) error {
scripts := []models.Script{
scripts := []model.Script{
{Name: "重启服务", Code: "restart-service", Type: "shell", Content: "#!/bin/bash\nsystemctl restart $1", Description: "重启服务脚本"},
{Name: "检查磁盘", Code: "check-disk", Type: "shell", Content: "#!/bin/bash\ndf -h", Description: "检查磁盘空间"},
{Name: "检查内存", Code: "check-memory", Type: "shell", Content: "#!/bin/bash\nfree -h", Description: "检查内存使用"},
@@ -520,7 +520,7 @@ func generateScripts(db *gorm.DB) error {
}
func generateApprovalTemplates(db *gorm.DB) error {
templates := []models.ApprovalTemplate{
templates := []model.ApprovalTemplate{
{Name: "服务器申请审批", Code: "server-request", Type: "server", Enabled: true, Description: "服务器资源申请审批流程"},
{Name: "发布审批", Code: "release-approval", Type: "release", Enabled: true, Description: "应用发布审批流程"},
{Name: "权限申请审批", Code: "permission-request", Type: "permission", Enabled: true, Description: "系统权限申请审批流程"},
@@ -535,7 +535,7 @@ func generateApprovalTemplates(db *gorm.DB) error {
}
func generateNavigations(db *gorm.DB) error {
groups := []models.NavigationGroup{
groups := []model.NavigationGroup{
{Name: "常用工具", Code: "tools", Icon: "Tool", Sort: 1, Enabled: true},
{Name: "监控平台", Code: "monitor", Icon: "Activity", Sort: 2, Enabled: true},
{Name: "开发工具", Code: "devtools", Icon: "Code", Sort: 3, Enabled: true},
@@ -546,7 +546,7 @@ func generateNavigations(db *gorm.DB) error {
return err
}
}
links := []models.NavigationLink{
links := []model.NavigationLink{
{GroupID: groups[0].ID, Name: "GitLab", URL: "https://gitlab.com", Icon: "GitBranch", Description: "代码仓库", Sort: 1},
{GroupID: groups[0].ID, Name: "Jenkins", URL: "https://jenkins.io", Icon: "Settings", Description: "CI/CD 工具", Sort: 2},
{GroupID: groups[0].ID, Name: "Nexus", URL: "https://www.sonatype.com/nexus-repository-oss", Icon: "Package", Description: "制品库", Sort: 3},
@@ -587,7 +587,7 @@ func generateBillings(db *gorm.DB) error {
}
now := time.Now()
var billings []models.CloudBilling
var billings []model.CloudBilling
// 生成最近60天每天的账单
for day := 59; day >= 0; day-- {
@@ -599,7 +599,7 @@ func generateBillings(db *gorm.DB) error {
for i := 0; i < count; i++ {
svc := svcs[rand.Intn(len(svcs))]
cost := 50 + rand.Float64()*500
billings = append(billings, models.CloudBilling{
billings = append(billings, model.CloudBilling{
CloudAccountID: &acc.ID,
AccountName: acc.Name,
Provider: acc.Provider,
@@ -621,7 +621,7 @@ func generateBillings(db *gorm.DB) error {
fmt.Println("Generated cloud billings:", len(billings))
// 生成预警规则
rules := []models.BillingAlertRule{
rules := []model.BillingAlertRule{
{Name: "每日总成本超500元", Metric: "daily", Threshold: 500, Operator: ">", Enabled: true},
{Name: "阿里云日费用超300元", Provider: "aliyun", Metric: "daily", Threshold: 300, Operator: ">", Enabled: true},
{Name: "月度总成本超1万元", Metric: "monthly", Threshold: 10000, Operator: ">", Enabled: true},
+11 -11
View File
@@ -9,8 +9,8 @@ import (
"math/rand"
"time"
"ezops/internal/config"
"ezops/internal/models"
"ezops/config"
"ezops/model"
"ezops/pkg/database"
)
@@ -23,7 +23,7 @@ func main() {
}
rand.Seed(time.Now().UnixNano())
var clusters []models.KubernetesCluster
var clusters []model.KubernetesCluster
db.Find(&clusters)
if len(clusters) == 0 {
fmt.Println("No clusters found — run mock.go first")
@@ -54,7 +54,7 @@ func main() {
for _, cluster := range clusters {
// Skip if pods already exist for this cluster
var podCount int64
db.Model(&models.KubernetesPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCount)
db.Model(&model.KubernetesPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCount)
if podCount > 0 {
fmt.Printf("Cluster %s already has %d pods, skipping\n", cluster.Name, podCount)
continue
@@ -79,7 +79,7 @@ func main() {
if available < 0 {
available = 0
}
db.Create(&models.KubernetesDeployment{
db.Create(&model.KubernetesDeployment{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: depNames[d],
@@ -94,7 +94,7 @@ func main() {
// Pods
numPods := 12 + rand.Intn(18)
for p := 0; p < numPods; p++ {
db.Create(&models.KubernetesPod{
db.Create(&model.KubernetesPod{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1),
@@ -126,7 +126,7 @@ func main() {
default:
ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]`
}
svc := models.KubernetesService{
svc := model.KubernetesService{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: svcNames[s],
@@ -150,7 +150,7 @@ func main() {
numIngs = len(ingNames)
}
for ig := 0; ig < numIngs; ig++ {
db.Create(&models.KubernetesIngress{
db.Create(&model.KubernetesIngress{
ClusterID: cluster.ID,
Namespace: namespaces[rand.Intn(len(namespaces))],
Name: ingNames[ig],
@@ -162,9 +162,9 @@ func main() {
// Update cluster counts to match actual data
var depCnt, podCnt, svcCnt int64
db.Model(&models.KubernetesDeployment{}).Where("cluster_id = ?", cluster.ID).Count(&depCnt)
db.Model(&models.KubernetesPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCnt)
db.Model(&models.KubernetesService{}).Where("cluster_id = ?", cluster.ID).Count(&svcCnt)
db.Model(&model.KubernetesDeployment{}).Where("cluster_id = ?", cluster.ID).Count(&depCnt)
db.Model(&model.KubernetesPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCnt)
db.Model(&model.KubernetesService{}).Where("cluster_id = ?", cluster.ID).Count(&svcCnt)
db.Model(&cluster).Updates(map[string]interface{}{
"deployment_count": depCnt,
})