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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user