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

+78 -78
View File
@@ -4,8 +4,8 @@ import (
"fmt"
"time"
"ezops/internal/config"
"ezops/internal/models"
"ezops/config"
"ezops/model"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
@@ -58,82 +58,82 @@ func New(cfg config.DatabaseConfig) (*gorm.DB, error) {
// 避免 GORM + MySQL 8.0 的 "Can't DROP" 索引问题导致整个启动失败
func AutoMigrate(db *gorm.DB) error {
allModels := []interface{}{
&models.User{},
&models.Role{},
&models.Permission{},
&models.Server{},
&models.Cloud{},
&models.CloudAccount{},
&models.Region{},
&models.Domain{},
&models.KubernetesCluster{},
&models.KubernetesNode{},
&models.KubernetesPod{},
&models.KubernetesDeployment{},
&models.KubernetesService{},
&models.KubernetesIngress{},
&models.AlertRule{},
&models.AlertEvent{},
&models.NotifyChannel{},
&models.MetricData{},
&models.Pipeline{},
&models.PipelineRun{},
&models.AppRelease{},
&models.CronJob{},
&models.Script{},
&models.TaskExecution{},
&models.ApprovalTemplate{},
&models.ApprovalProcess{},
&models.ApprovalStep{},
&models.NavigationGroup{},
&models.NavigationLink{},
&models.AuditLog{},
&models.LDAPConfig{},
&models.ResourceModel{},
&models.CloudBilling{},
&models.BillingAlertRule{},
&models.AgentReport{},
&models.AgentTask{},
&models.AgentRule{},
&models.AgentAlert{},
&models.AnsiblePlaybook{},
&models.AnsibleJob{},
&models.AnsibleInventory{},
&models.LogSource{},
&models.LogEntry{},
&models.TopologyLayout{}, // 拓扑布局保存 // Logan.Li
&models.TerminalSession{}, // 终端会话录制(终端审计) // Logan.Li
&models.LLMConfig{}, // 大语言模型配置(AI 智能助手) // Logan.Li
&models.ChatHistory{}, // 对话历史记录(按用户隔离) // Logan.Li
&models.MetricsAggregate{}, // 指标时序聚合(Redis → MySQL)// Logan.Li
&models.MonitorTarget{}, // 监控目标(主机/中间件/设备/站点) // Logan.Li
&models.MonitorMetric{}, // 监控指标定义 // Logan.Li
&models.MetricValue{}, // 指标当前值 // Logan.Li
&models.MetricHistory{}, // 指标历史时序 // Logan.Li
&models.HostGroup{}, // 主机组 // Logan.Li
&models.SNMPDevice{}, // SNMP设备 // Logan.Li
&models.SNMPTemplate{}, // SNMP模板 // Logan.Li
&models.SiteTask{}, // 站点拨测任务 // Logan.Li
&models.SiteResult{}, // 拨测结果 // Logan.Li
&models.SiteAlert{}, // 拨测告警 // Logan.Li
&models.PrometheusDataSource{}, // Prometheus 数据源配置 // Logan.Li
&models.ZabbixDataSource{}, // Zabbix 数据源配置 // Logan.Li
&models.Credential{}, // 凭据管理(SSH密钥/API Token) // Logan.Li
&models.PipelineRepo{}, // Git 仓库认证 // Logan.Li
&models.PipelineRegistry{}, // 镜像仓库认证 // Logan.Li
&models.DeployEnvironment{}, // 发布环境分组 // Logan.Li
&models.DeployPipelineMember{}, // 流水线成员 // Logan.Li
&models.DeployUserGroup{}, // 用户组 // Logan.Li
&models.DeployUserGroupMember{}, // 用户组成员 // Logan.Li
&models.ItemRole{}, // Jenkins-style Item Role (regex pipeline matching) // Logan.Li
&models.UserItemRole{}, // User ↔ ItemRole association // Logan.Li
&models.DeployTool{}, // 工具配置(懒加载/预装/禁用) // Logan.Li
&models.AlertCondition{}, // 告警条件 // Logan.Li
&models.AlertNotifyTemplate{}, // 告警通知模板 // Logan.Li
&models.AlertUpgrade{}, // 告警升级策略 // Logan.Li
&models.AlertUpgradeRecord{}, // 告警升级记录 // Logan.Li
&models.AlertSilence{}, // 告警静默 // Logan.Li
&models.HealthProbe{}, // 健康探针配置 // Logan.Li
&model.User{},
&model.Role{},
&model.Permission{},
&model.Server{},
&model.Cloud{},
&model.CloudAccount{},
&model.Region{},
&model.Domain{},
&model.KubernetesCluster{},
&model.KubernetesNode{},
&model.KubernetesPod{},
&model.KubernetesDeployment{},
&model.KubernetesService{},
&model.KubernetesIngress{},
&model.AlertRule{},
&model.AlertEvent{},
&model.NotifyChannel{},
&model.MetricData{},
&model.Pipeline{},
&model.PipelineRun{},
&model.AppRelease{},
&model.CronJob{},
&model.Script{},
&model.TaskExecution{},
&model.ApprovalTemplate{},
&model.ApprovalProcess{},
&model.ApprovalStep{},
&model.NavigationGroup{},
&model.NavigationLink{},
&model.AuditLog{},
&model.LDAPConfig{},
&model.ResourceModel{},
&model.CloudBilling{},
&model.BillingAlertRule{},
&model.AgentReport{},
&model.AgentTask{},
&model.AgentRule{},
&model.AgentAlert{},
&model.AnsiblePlaybook{},
&model.AnsibleJob{},
&model.AnsibleInventory{},
&model.LogSource{},
&model.LogEntry{},
&model.TopologyLayout{}, // 拓扑布局保存 // Logan.Li
&model.TerminalSession{}, // 终端会话录制(终端审计) // Logan.Li
&model.LLMConfig{}, // 大语言模型配置(AI 智能助手) // Logan.Li
&model.ChatHistory{}, // 对话历史记录(按用户隔离) // Logan.Li
&model.MetricsAggregate{}, // 指标时序聚合(Redis → MySQL)// Logan.Li
&model.MonitorTarget{}, // 监控目标(主机/中间件/设备/站点) // Logan.Li
&model.MonitorMetric{}, // 监控指标定义 // Logan.Li
&model.MetricValue{}, // 指标当前值 // Logan.Li
&model.MetricHistory{}, // 指标历史时序 // Logan.Li
&model.HostGroup{}, // 主机组 // Logan.Li
&model.SNMPDevice{}, // SNMP设备 // Logan.Li
&model.SNMPTemplate{}, // SNMP模板 // Logan.Li
&model.SiteTask{}, // 站点拨测任务 // Logan.Li
&model.SiteResult{}, // 拨测结果 // Logan.Li
&model.SiteAlert{}, // 拨测告警 // Logan.Li
&model.PrometheusDataSource{}, // Prometheus 数据源配置 // Logan.Li
&model.ZabbixDataSource{}, // Zabbix 数据源配置 // Logan.Li
&model.Credential{}, // 凭据管理(SSH密钥/API Token) // Logan.Li
&model.PipelineRepo{}, // Git 仓库认证 // Logan.Li
&model.PipelineRegistry{}, // 镜像仓库认证 // Logan.Li
&model.DeployEnvironment{}, // 发布环境分组 // Logan.Li
&model.DeployPipelineMember{}, // 流水线成员 // Logan.Li
&model.DeployUserGroup{}, // 用户组 // Logan.Li
&model.DeployUserGroupMember{}, // 用户组成员 // Logan.Li
&model.ItemRole{}, // Jenkins-style Item Role (regex pipeline matching) // Logan.Li
&model.UserItemRole{}, // User ↔ ItemRole association // Logan.Li
&model.DeployTool{}, // 工具配置(懒加载/预装/禁用) // Logan.Li
&model.AlertCondition{}, // 告警条件 // Logan.Li
&model.AlertNotifyTemplate{}, // 告警通知模板 // Logan.Li
&model.AlertUpgrade{}, // 告警升级策略 // Logan.Li
&model.AlertUpgradeRecord{}, // 告警升级记录 // Logan.Li
&model.AlertSilence{}, // 告警静默 // Logan.Li
&model.HealthProbe{}, // 健康探针配置 // Logan.Li
}
var lastErr error
+25 -25
View File
@@ -12,7 +12,7 @@ import (
"sync"
"time"
"ezops/internal/models"
"ezops/model"
"gorm.io/gorm"
)
@@ -49,7 +49,7 @@ func (ti *ToolInstaller) ensureOnce(name string) *sync.Once {
// Used by the manual "安装" button in the tool config UI.
// The version parameter overrides DefaultVersion if non-empty.
func (ti *ToolInstaller) ForceInstall(toolName string, version string) error {
var tool models.DeployTool
var tool model.DeployTool
if err := ti.db.Where("name = ?", toolName).First(&tool).Error; err != nil {
return fmt.Errorf("tool %s not found in DB: %w", toolName, err)
}
@@ -67,7 +67,7 @@ func (ti *ToolInstaller) ForceInstall(toolName string, version string) error {
// Returns nil if the tool is ready, or an error if installation failed.
func (ti *ToolInstaller) EnsureAvailable(toolName string) error {
// Find tool in DB
var tool models.DeployTool
var tool model.DeployTool
if err := ti.db.Where("name = ?", toolName).First(&tool).Error; err != nil {
// Tool not in DB — try to find it by path
if _, lookErr := exec.LookPath(toolName); lookErr == nil {
@@ -81,22 +81,22 @@ func (ti *ToolInstaller) EnsureAvailable(toolName string) error {
}
// Check if already installed
if tool.InstallMethod == models.ToolPreinstall || tool.InstallMethod == models.ToolLazy {
if tool.InstallMethod == model.ToolPreinstall || tool.InstallMethod == model.ToolLazy {
if inst, err := ti.checkInstalled(tool); err == nil {
// Update status if needed
if tool.Status != models.ToolInstalled {
ti.updateStatus(tool.ID, models.ToolInstalled, inst, "")
if tool.Status != model.ToolInstalled {
ti.updateStatus(tool.ID, model.ToolInstalled, inst, "")
}
return nil
}
}
switch tool.InstallMethod {
case models.ToolPreinstall:
case model.ToolPreinstall:
return fmt.Errorf("tool %s should be preinstalled but not found", toolName)
case models.ToolDisabled:
case model.ToolDisabled:
return fmt.Errorf("tool %s is disabled", toolName)
case models.ToolLazy:
case model.ToolLazy:
// Use sync.Once to prevent concurrent installs
var installErr error
once := ti.ensureOnce(toolName)
@@ -116,7 +116,7 @@ func (ti *ToolInstaller) EnsureAvailable(toolName string) error {
}
// checkInstalled runs the tool's check command and returns the version string.
func (ti *ToolInstaller) checkInstalled(tool models.DeployTool) (string, error) {
func (ti *ToolInstaller) checkInstalled(tool model.DeployTool) (string, error) {
// First try LookPath
if tool.BinaryName != "" {
if _, err := exec.LookPath(tool.BinaryName); err != nil {
@@ -138,11 +138,11 @@ func (ti *ToolInstaller) checkInstalled(tool models.DeployTool) (string, error)
}
// installTool performs the actual installation.
func (ti *ToolInstaller) installTool(tool *models.DeployTool) error {
func (ti *ToolInstaller) installTool(tool *model.DeployTool) error {
log.Printf("[ToolInstaller] Installing %s (method=%s)...", tool.Name, tool.InstallMethod)
// Mark as installing
ti.updateStatus(tool.ID, models.ToolInstalling, "", "")
ti.updateStatus(tool.ID, model.ToolInstalling, "", "")
var err error
if tool.InstallScript != "" {
@@ -156,7 +156,7 @@ func (ti *ToolInstaller) installTool(tool *models.DeployTool) error {
if err != nil {
errMsg := err.Error()
log.Printf("[ToolInstaller] ERROR installing %s: %s", tool.Name, errMsg)
ti.updateStatus(tool.ID, models.ToolFailed, "", errMsg)
ti.updateStatus(tool.ID, model.ToolFailed, "", errMsg)
return fmt.Errorf("failed to install %s: %w", tool.Name, err)
}
@@ -164,17 +164,17 @@ func (ti *ToolInstaller) installTool(tool *models.DeployTool) error {
ver, checkErr := ti.checkInstalled(*tool)
if checkErr != nil {
log.Printf("[ToolInstaller] WARNING: %s installed but check failed: %v", tool.Name, checkErr)
ti.updateStatus(tool.ID, models.ToolInstalled, "unknown", "")
ti.updateStatus(tool.ID, model.ToolInstalled, "unknown", "")
} else {
log.Printf("[ToolInstaller] %s installed successfully: %s", tool.Name, ver)
ti.updateStatus(tool.ID, models.ToolInstalled, ver, "")
ti.updateStatus(tool.ID, model.ToolInstalled, ver, "")
}
return nil
}
// runInstallScript executes the tool's install script in a subshell.
func (ti *ToolInstaller) runInstallScript(tool *models.DeployTool) error {
func (ti *ToolInstaller) runInstallScript(tool *model.DeployTool) error {
// Write script to temp file
tmpFile, err := os.CreateTemp("", fmt.Sprintf("tool-install-%s-*.sh", tool.Name))
if err != nil {
@@ -203,7 +203,7 @@ func (ti *ToolInstaller) runInstallScript(tool *models.DeployTool) error {
// downloadBinary downloads a binary file to the install path.
// Uses multi-mirror fallback: tries each mirror URL in order, with HEAD verification
// to skip dead mirrors before attempting a full download.
func (ti *ToolInstaller) downloadBinary(tool *models.DeployTool) error {
func (ti *ToolInstaller) downloadBinary(tool *model.DeployTool) error {
version := tool.DefaultVersion
if version == "" {
// Try to get latest version
@@ -244,7 +244,7 @@ func (ti *ToolInstaller) downloadBinary(tool *models.DeployTool) error {
}
// tryDownload attempts a single download from a URL.
func (ti *ToolInstaller) tryDownload(url string, tool *models.DeployTool) error {
func (ti *ToolInstaller) tryDownload(url string, tool *model.DeployTool) error {
// Use a client with timeout for the download phase.
// Large files (kubectl ~50MB) can take minutes on slow networks.
client := &http.Client{Timeout: 30 * time.Minute}
@@ -307,7 +307,7 @@ func (ti *ToolInstaller) fetchVersion(url string) (string, error) {
}
// updateStatus updates the tool's status in the database.
func (ti *ToolInstaller) updateStatus(toolID uint, status models.ToolStatus, version string, errMsg string) {
func (ti *ToolInstaller) updateStatus(toolID uint, status model.ToolStatus, version string, errMsg string) {
now := time.Now()
updates := map[string]interface{}{
"status": status,
@@ -319,29 +319,29 @@ func (ti *ToolInstaller) updateStatus(toolID uint, status models.ToolStatus, ver
if errMsg != "" {
updates["error_msg"] = errMsg
}
ti.db.Model(&models.DeployTool{}).Where("id = ?", toolID).Updates(updates)
ti.db.Model(&model.DeployTool{}).Where("id = ?", toolID).Updates(updates)
}
// SeedTools inserts predefined tools into the database if the table is empty.
func (ti *ToolInstaller) SeedTools() {
var count int64
ti.db.Model(&models.DeployTool{}).Count(&count)
ti.db.Model(&model.DeployTool{}).Count(&count)
if count > 0 {
return
}
tools := models.PredefinedTools()
tools := model.PredefinedTools()
now := time.Now()
for i := range tools {
tools[i].CreatedAt = now
tools[i].UpdatedAt = now
// Check if preinstalled tools are actually available
if tools[i].InstallMethod == models.ToolPreinstall {
if tools[i].InstallMethod == model.ToolPreinstall {
if ver, err := ti.checkInstalled(tools[i]); err == nil {
tools[i].Status = models.ToolInstalled
tools[i].Status = model.ToolInstalled
tools[i].InstalledVer = ver
} else {
tools[i].Status = models.ToolNotInstalled
tools[i].Status = model.ToolNotInstalled
}
}
}