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

+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
}
}
}