diff --git a/internal/app.go b/internal/app.go index 08b7f38..b026d77 100644 --- a/internal/app.go +++ b/internal/app.go @@ -255,7 +255,6 @@ func setupApi(app *orz.App, components *AppComponents) { adminApi.GET("/agents/:id/tamper/config", components.TamperHandler.GetTamperConfig) adminApi.PUT("/agents/:id/tamper/config", components.TamperHandler.UpdateTamperConfig) adminApi.GET("/agents/:id/tamper/events", components.TamperHandler.GetTamperEvents) - adminApi.GET("/agents/:id/tamper/alerts", components.TamperHandler.GetTamperAlerts) // SSH 登录监控管理(管理员功能) adminApi.GET("/agents/:id/ssh-login/config", components.SSHLoginHandler.GetConfig) @@ -307,21 +306,18 @@ func setupApi(app *orz.App, components *AppComponents) { func autoMigrate(database *gorm.DB) error { // 自动迁移数据库表 return database.AutoMigrate( - &models.Agent{}, // 探针 - &models.ApiKey{}, // ApiKey - &models.HostMetric{}, // 保留主机静态信息表 - &models.AuditResult{}, // 审计历史 - &models.Property{}, // 系统属性 - &models.AlertRecord{}, // 告警记录 - &models.AlertState{}, // 告警状态 - &models.MonitorTask{}, // 服务监控 - &models.TamperProtectConfig{}, // 防篡改配置 - &models.TamperEvent{}, // 防篡改事件 - &models.TamperAlert{}, // 防篡改告警 - &models.DDNSConfig{}, // DDNS 配置 - &models.DDNSRecord{}, // DDNS 记录 - &models.SSHLoginConfig{}, // SSH 登录监控配置 - &models.SSHLoginEvent{}, // SSH 登录事件 + &models.Agent{}, // 探针 + &models.ApiKey{}, // ApiKey + &models.HostMetric{}, // 保留主机静态信息表 + &models.AuditResult{}, // 审计历史 + &models.Property{}, // 系统属性 + &models.AlertRecord{}, // 告警记录 + &models.AlertState{}, // 告警状态 + &models.MonitorTask{}, // 服务监控 + &models.TamperEvent{}, // 防篡改事件 + &models.DDNSConfig{}, // DDNS 配置 + &models.DDNSRecord{}, // DDNS 记录 + &models.SSHLoginEvent{}, // SSH 登录事件 ) } diff --git a/internal/handler/agent_handler.go b/internal/handler/agent_handler.go index cb2d97f..71d8a52 100644 --- a/internal/handler/agent_handler.go +++ b/internal/handler/agent_handler.go @@ -184,15 +184,6 @@ func (h *AgentHandler) handleWebSocketMessage(ctx context.Context, agentID strin } return h.tamperService.CreateEvent(agentID, eventData.Path, eventData.Operation, eventData.Details, eventData.Timestamp) - case protocol.MessageTypeTamperAlert: - // 防篡改告警 - var alertData protocol.TamperAlertData - if err := json.Unmarshal(data, &alertData); err != nil { - h.logger.Error("failed to unmarshal tamper alert", zap.Error(err)) - return err - } - return h.tamperService.CreateAlert(agentID, alertData.Path, alertData.Details, alertData.Restored, alertData.Timestamp) - case protocol.MessageTypeDDNSIPReport: // DDNS IP 上报 - 异步处理,避免阻塞 WebSocket 消息循环 var ipReport protocol.DDNSIPReportData @@ -238,8 +229,7 @@ func (h *AgentHandler) handleWebSocketMessage(ctx context.Context, agentID strin } else { h.logger.Error("tamper protect config apply failed", zap.String("agentID", agentID), - zap.String("message", protectResp.Message), - zap.String("error", protectResp.Error)) + zap.String("message", protectResp.Message)) } return nil @@ -551,17 +541,19 @@ func (h *AgentHandler) GetAgents(c echo.Context) error { "status": agent.Status, "lastSeenAt": agent.LastSeenAt, "visibility": agent.Visibility, - // 流量统计相关字段 - "trafficLimit": agent.TrafficLimit, - "trafficUsed": agent.TrafficUsed, - "trafficResetDay": agent.TrafficResetDay, - "trafficPeriodStart": agent.TrafficPeriodStart, - "trafficBaselineRecv": agent.TrafficBaselineRecv, - "trafficAlertSent80": agent.TrafficAlertSent80, - "trafficAlertSent90": agent.TrafficAlertSent90, - "trafficAlertSent100": agent.TrafficAlertSent100, } + // 添加流量统计数据 + trafficStats := agent.TrafficStats.Data() + item["trafficLimit"] = trafficStats.Limit + item["trafficUsed"] = trafficStats.Used + item["trafficResetDay"] = trafficStats.ResetDay + item["trafficPeriodStart"] = trafficStats.PeriodStart + item["trafficBaselineRecv"] = trafficStats.BaselineRecv + item["trafficAlertSent80"] = trafficStats.AlertSent80 + item["trafficAlertSent90"] = trafficStats.AlertSent90 + item["trafficAlertSent100"] = trafficStats.AlertSent100 + // 获取最新指标数据 metrics, ok := h.metricService.GetLatestMetrics(agent.ID) if ok { diff --git a/internal/handler/ssh_login_handler.go b/internal/handler/ssh_login_handler.go index 4095955..a40feb4 100644 --- a/internal/handler/ssh_login_handler.go +++ b/internal/handler/ssh_login_handler.go @@ -63,8 +63,7 @@ func (h *SSHLoginHandler) UpdateConfig(c echo.Context) error { } var req struct { - Enabled bool `json:"enabled"` - RecordFailed bool `json:"recordFailed"` + Enabled bool `json:"enabled"` } if err := c.Bind(&req); err != nil { @@ -73,7 +72,7 @@ func (h *SSHLoginHandler) UpdateConfig(c echo.Context) error { }) } - config, configSent, err := h.service.UpdateConfig(c.Request().Context(), agentID, req.Enabled, req.RecordFailed) + config, configSent, err := h.service.UpdateConfig(c.Request().Context(), agentID, req.Enabled) if err != nil { h.logger.Error("更新SSH登录监控配置失败", zap.Error(err)) return c.JSON(http.StatusInternalServerError, map[string]string{ diff --git a/internal/handler/tamper_handler.go b/internal/handler/tamper_handler.go index 108319f..975d2b3 100644 --- a/internal/handler/tamper_handler.go +++ b/internal/handler/tamper_handler.go @@ -33,7 +33,8 @@ func (h *TamperHandler) UpdateTamperConfig(c echo.Context) error { } var req struct { - Paths []string `json:"paths"` + Enabled bool `json:"enabled"` + Paths []string `json:"paths"` } if err := c.Bind(&req); err != nil { @@ -43,7 +44,7 @@ func (h *TamperHandler) UpdateTamperConfig(c echo.Context) error { }) } - config, err := h.tamperService.UpdateConfig(agentID, req.Paths) + config, err := h.tamperService.UpdateConfig(agentID, req.Enabled, req.Paths) if err != nil { h.logger.Error("更新防篡改配置失败", zap.Error(err), zap.String("agentId", agentID)) return c.JSON(http.StatusInternalServerError, map[string]interface{}{ @@ -126,36 +127,3 @@ func (h *TamperHandler) GetTamperEvents(c echo.Context) error { }, }) } - -// GetTamperAlerts 获取探针的防篡改告警 -// GET /api/agents/:id/tamper/alerts -func (h *TamperHandler) GetTamperAlerts(c echo.Context) error { - agentID := c.Param("id") - if agentID == "" { - return c.JSON(http.StatusBadRequest, map[string]interface{}{ - "success": false, - "message": "探针ID不能为空", - }) - } - - // 获取分页参数 - pageNum, _ := strconv.Atoi(c.QueryParam("pageNum")) - pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) - - alerts, total, err := h.tamperService.GetAlertsByAgentID(agentID, pageNum, pageSize) - if err != nil { - h.logger.Error("获取防篡改告警失败", zap.Error(err), zap.String("agentId", agentID)) - return c.JSON(http.StatusInternalServerError, map[string]interface{}{ - "success": false, - "message": "获取告警失败", - }) - } - - return c.JSON(http.StatusOK, map[string]interface{}{ - "success": true, - "data": map[string]interface{}{ - "items": alerts, - "total": total, - }, - }) -} diff --git a/internal/models/agent.go b/internal/models/agent.go index 40d428c..5735517 100644 --- a/internal/models/agent.go +++ b/internal/models/agent.go @@ -20,14 +20,40 @@ type Agent struct { UpdatedAt int64 `json:"updatedAt" gorm:"autoUpdateTime:milli"` // 更新时间(时间戳毫秒) // 流量统计相关字段 - TrafficLimit uint64 `json:"trafficLimit"` // 流量限额(字节), 0表示不限制 - TrafficUsed uint64 `json:"trafficUsed"` // 当前周期已使用流量(字节) - TrafficResetDay int `json:"trafficResetDay"` // 流量重置日期(1-31), 0表示不自动重置 - TrafficPeriodStart int64 `json:"trafficPeriodStart"` // 当前周期开始时间(时间戳毫秒) - TrafficBaselineRecv uint64 `json:"trafficBaselineRecv"` // 当前周期流量基线(BytesRecvTotal) - TrafficAlertSent80 bool `json:"trafficAlertSent80"` // 是否已发送80%告警 - TrafficAlertSent90 bool `json:"trafficAlertSent90"` // 是否已发送90%告警 - TrafficAlertSent100 bool `json:"trafficAlertSent100"` // 是否已发送100%告警 + TrafficStats datatypes.JSONType[TrafficStatsData] `json:"trafficStats,omitempty"` // 流量统计 + + // 防篡改保护配置 + TamperProtectConfig datatypes.JSONType[TamperProtectConfigData] `json:"tamperProtectConfig,omitempty"` // 防篡改保护配置 + + // SSH登录监控配置 + SSHLoginConfig datatypes.JSONType[SSHLoginConfigData] `json:"sshLoginConfig,omitempty"` // SSH登录监控配置 +} + +// TrafficStatsData 流量统计数据 +type TrafficStatsData struct { + Limit uint64 `json:"limit"` // 流量限额(字节), 0表示不限制 + Used uint64 `json:"used"` // 当前周期已使用流量(字节) + ResetDay int `json:"resetDay"` // 流量重置日期(1-31), 0表示不自动重置 + PeriodStart int64 `json:"periodStart"` // 当前周期开始时间(时间戳毫秒) + BaselineRecv uint64 `json:"baselineRecv"` // 当前周期流量基线(BytesRecvTotal) + AlertSent80 bool `json:"alertSent80"` // 是否已发送80%告警 + AlertSent90 bool `json:"alertSent90"` // 是否已发送90%告警 + AlertSent100 bool `json:"alertSent100"` // 是否已发送100%告警 +} + +// TamperProtectConfigData 防篡改保护配置数据 +type TamperProtectConfigData struct { + Enabled bool `json:"enabled"` // 是否启用 + Paths []string `json:"paths"` // 受保护的目录列表 + ApplyStatus string `json:"applyStatus,omitempty"` // 配置应用状态: success/failed/pending + ApplyMessage string `json:"applyMessage,omitempty"` // 应用结果消息 +} + +// SSHLoginConfigData SSH登录监控配置数据 +type SSHLoginConfigData struct { + Enabled bool `json:"enabled"` // 是否启用 + ApplyStatus string `json:"applyStatus,omitempty"` // 配置应用状态: success/failed/pending + ApplyMessage string `json:"applyMessage,omitempty"` // 应用结果消息 } func (Agent) TableName() string { diff --git a/internal/models/ssh_login.go b/internal/models/ssh_login.go index a71f45f..f55dfa2 100644 --- a/internal/models/ssh_login.go +++ b/internal/models/ssh_login.go @@ -1,37 +1,5 @@ package models -import ( - "time" - - "gorm.io/gorm" -) - -// SSHLoginConfig SSH登录监控配置 -type SSHLoginConfig struct { - ID string `gorm:"primaryKey" json:"id"` // 配置ID (UUID) - AgentID string `gorm:"uniqueIndex;not null" json:"agentId"` // 探针ID(唯一) - Enabled bool `json:"enabled"` // 是否启用 - RecordFailed bool `json:"recordFailed"` // 是否记录失败登录 - ApplyStatus string `json:"applyStatus,omitempty"` // 配置应用状态: success/failed/pending - ApplyMessage string `json:"applyMessage,omitempty"` // 应用结果消息 - ApplyError string `json:"applyError,omitempty"` // 应用错误信息 - LastAppliedAt int64 `json:"lastAppliedAt,omitempty"` // 最后应用时间(毫秒) - CreatedAt int64 `json:"createdAt"` // 创建时间(毫秒) - UpdatedAt int64 `json:"updatedAt" gorm:"autoUpdateTime:milli"` // 更新时间(毫秒) -} - -func (SSHLoginConfig) TableName() string { - return "ssh_login_configs" -} - -// BeforeCreate GORM钩子:设置创建时间 -func (s *SSHLoginConfig) BeforeCreate(tx *gorm.DB) error { - if s.CreatedAt == 0 { - s.CreatedAt = time.Now().UnixMilli() - } - return nil -} - // SSHLoginEvent SSH登录事件 type SSHLoginEvent struct { ID string `gorm:"primaryKey" json:"id"` // 事件ID (UUID) @@ -50,11 +18,3 @@ type SSHLoginEvent struct { func (SSHLoginEvent) TableName() string { return "ssh_login_events" } - -// BeforeCreate GORM钩子:设置创建时间 -func (s *SSHLoginEvent) BeforeCreate(tx *gorm.DB) error { - if s.CreatedAt == 0 { - s.CreatedAt = time.Now().UnixMilli() - } - return nil -} diff --git a/internal/models/tamper.go b/internal/models/tamper.go index 89ae9e1..f9d29ee 100644 --- a/internal/models/tamper.go +++ b/internal/models/tamper.go @@ -1,20 +1,5 @@ package models -import "gorm.io/datatypes" - -// TamperProtectConfig 防篡改保护配置 -type TamperProtectConfig struct { - ID string `gorm:"primaryKey" json:"id"` // 配置ID (UUID) - AgentID string `gorm:"index;not null" json:"agentId"` // 探针ID - Paths datatypes.JSONSlice[string] `json:"paths"` // 受保护的目录列表 - CreatedAt int64 `json:"createdAt"` // 创建时间(时间戳毫秒) - UpdatedAt int64 `json:"updatedAt" gorm:"autoUpdateTime:milli"` // 更新时间(时间戳毫秒) -} - -func (TamperProtectConfig) TableName() string { - return "tamper_protect_configs" -} - // TamperEvent 防篡改事件 type TamperEvent struct { ID string `gorm:"primaryKey" json:"id"` // 事件ID (UUID) @@ -29,18 +14,3 @@ type TamperEvent struct { func (TamperEvent) TableName() string { return "tamper_events" } - -// TamperAlert 防篡改属性告警 -type TamperAlert struct { - ID string `gorm:"primaryKey" json:"id"` // 告警ID (UUID) - AgentID string `gorm:"index;not null" json:"agentId"` // 探针ID - Path string `gorm:"index" json:"path"` // 被篡改的路径 - Details string `json:"details"` // 详细信息 - Restored bool `json:"restored"` // 是否已自动恢复 - Timestamp int64 `gorm:"index" json:"timestamp"` // 检测时间(时间戳毫秒) - CreatedAt int64 `json:"createdAt"` // 记录创建时间(时间戳毫秒) -} - -func (TamperAlert) TableName() string { - return "tamper_alerts" -} diff --git a/internal/protocol/message.go b/internal/protocol/message.go index 74acf2a..b476375 100644 --- a/internal/protocol/message.go +++ b/internal/protocol/message.go @@ -59,7 +59,6 @@ const ( // 防篡改消息 MessageTypeTamperProtect MessageType = "tamper_protect" MessageTypeTamperEvent MessageType = "tamper_event" - MessageTypeTamperAlert MessageType = "tamper_alert" // DDNS 消息 MessageTypeDDNSConfig MessageType = "ddns_config" MessageTypeDDNSIPReport MessageType = "ddns_ip_report" @@ -323,15 +322,15 @@ type TamperProtectResponse struct { Paths []string `json:"paths"` // 当前保护的目录列表 Added []string `json:"added,omitempty"` // 新增的目录 Removed []string `json:"removed,omitempty"` // 移除的目录 - Error string `json:"error,omitempty"` // 错误信息 } // TamperEventData 防篡改事件数据 type TamperEventData struct { - Path string `json:"path"` // 被修改的路径 - Operation string `json:"operation"` // 操作类型: write, remove, rename, chmod, create - Timestamp int64 `json:"timestamp"` // 事件时间(毫秒) - Details string `json:"details"` // 详细信息 + Path string `json:"path"` // 被修改的路径 + Operation string `json:"operation"` // 操作类型: write, remove, rename, chmod, create, attr_tamper + Timestamp int64 `json:"timestamp"` // 事件时间(毫秒) + Details string `json:"details"` // 详细信息 + Restored bool `json:"restored,omitempty"` // 是否已自动恢复(仅用于 attr_tamper 操作) } // TamperAlertData 防篡改属性告警数据 @@ -687,10 +686,9 @@ type SSHLoginConfig struct { // SSHLoginConfigResult SSH登录监控配置应用结果(Agent 反馈) type SSHLoginConfigResult struct { - Success bool `json:"success"` // 配置应用是否成功 - Enabled bool `json:"enabled"` // 当前启用状态 - Message string `json:"message"` // 结果描述信息 - Error string `json:"error,omitempty"` // 错误信息(如果失败) + Success bool `json:"success"` // 配置应用是否成功 + Enabled bool `json:"enabled"` // 当前启用状态 + Message string `json:"message"` // 结果描述信息 } // SSHLoginEvent SSH登录事件 diff --git a/internal/repo/agent_repo.go b/internal/repo/agent_repo.go index c55a985..1e3aa74 100644 --- a/internal/repo/agent_repo.go +++ b/internal/repo/agent_repo.go @@ -222,17 +222,57 @@ func (r *AgentRepo) GetAllTags(ctx context.Context) ([]string, error) { // FindAgentsWithTrafficReset 查询配置了流量自动重置的探针 func (r *AgentRepo) FindAgentsWithTrafficReset(ctx context.Context) ([]models.Agent, error) { + var allAgents []models.Agent + err := r.db.WithContext(ctx).Find(&allAgents).Error + if err != nil { + return nil, err + } + + // 在应用层过滤配置了流量重置的探针 var agents []models.Agent - err := r.db.WithContext(ctx). - Where("traffic_reset_day > 0"). - Find(&agents).Error - return agents, err + for _, agent := range allAgents { + stats := agent.TrafficStats.Data() + if stats.ResetDay > 0 { + agents = append(agents, agent) + } + } + return agents, nil } -// UpdateTrafficStats 批量更新流量统计字段 -func (r *AgentRepo) UpdateTrafficStats(ctx context.Context, agentID string, updates map[string]interface{}) error { - return r.db.WithContext(ctx). - Model(&models.Agent{}). - Where("id = ?", agentID). - Updates(updates).Error +// === 配置管理 === + +// GetTamperProtectConfig 获取防篡改保护配置 +func (r *AgentRepo) GetTamperProtectConfig(ctx context.Context, agentID string) (*models.TamperProtectConfigData, error) { + var agent models.Agent + err := r.db.WithContext(ctx).Select("tamper_protect_config").Where("id = ?", agentID).First(&agent).Error + if err != nil { + return nil, err + } + data := agent.TamperProtectConfig.Data() + return &data, nil +} + +// UpdateTamperProtectConfig 更新防篡改保护配置 +func (r *AgentRepo) UpdateTamperProtectConfig(ctx context.Context, agentID string, config *models.TamperProtectConfigData) error { + return r.db.WithContext(ctx).Model(&models.Agent{}). + Where("id = ?", agentID). + Update("tamper_protect_config", config).Error +} + +// GetSSHLoginConfig 获取SSH登录监控配置 +func (r *AgentRepo) GetSSHLoginConfig(ctx context.Context, agentID string) (*models.SSHLoginConfigData, error) { + var agent models.Agent + err := r.db.WithContext(ctx).Select("ssh_login_config").Where("id = ?", agentID).First(&agent).Error + if err != nil { + return nil, err + } + data := agent.SSHLoginConfig.Data() + return &data, nil +} + +// UpdateSSHLoginConfig 更新SSH登录监控配置 +func (r *AgentRepo) UpdateSSHLoginConfig(ctx context.Context, agentID string, config *models.SSHLoginConfigData) error { + return r.db.WithContext(ctx).Model(&models.Agent{}). + Where("id = ?", agentID). + Update("ssh_login_config", config).Error } diff --git a/internal/repo/ssh_login_repo.go b/internal/repo/ssh_login_repo.go index d609c4f..27c7442 100644 --- a/internal/repo/ssh_login_repo.go +++ b/internal/repo/ssh_login_repo.go @@ -18,54 +18,6 @@ func NewSSHLoginRepo(db *gorm.DB) *SSHLoginRepo { return &SSHLoginRepo{db: db} } -// === 配置相关 === - -// GetConfigByAgentID 获取探针的配置 -func (r *SSHLoginRepo) GetConfigByAgentID(agentID string) (*models.SSHLoginConfig, error) { - var config models.SSHLoginConfig - err := r.db.Where("agent_id = ?", agentID).First(&config).Error - if err == gorm.ErrRecordNotFound { - return nil, nil - } - return &config, err -} - -// UpsertConfig 创建或更新配置 -func (r *SSHLoginRepo) UpsertConfig(config *models.SSHLoginConfig) error { - // 检查是否已存在 - existing, err := r.GetConfigByAgentID(config.AgentID) - if err != nil { - return err - } - - if existing != nil { - // 更新 - return r.db.Model(&models.SSHLoginConfig{}). - Where("agent_id = ?", config.AgentID). - Updates(map[string]interface{}{ - "enabled": config.Enabled, - "record_failed": config.RecordFailed, - }).Error - } - - // 创建 - config.ID = uuid.New().String() - config.CreatedAt = time.Now().UnixMilli() - return r.db.Create(config).Error -} - -// DeleteConfigByAgentID 删除探针的配置 -func (r *SSHLoginRepo) DeleteConfigByAgentID(agentID string) error { - return r.db.Where("agent_id = ?", agentID).Delete(&models.SSHLoginConfig{}).Error -} - -// UpdateConfigStatus 更新配置应用状态 -func (r *SSHLoginRepo) UpdateConfigStatus(agentID string, updates map[string]interface{}) error { - return r.db.Model(&models.SSHLoginConfig{}). - Where("agent_id = ?", agentID). - Updates(updates).Error -} - // === 事件相关 === // CreateEvent 创建事件记录 diff --git a/internal/repo/tamper_repo.go b/internal/repo/tamper_repo.go index 345f707..c34a494 100644 --- a/internal/repo/tamper_repo.go +++ b/internal/repo/tamper_repo.go @@ -13,26 +13,6 @@ func NewTamperRepo(db *gorm.DB) *TamperRepo { return &TamperRepo{db: db} } -// GetConfigByAgentID 根据探针ID获取防篡改配置 -func (r *TamperRepo) GetConfigByAgentID(agentID string) (*models.TamperProtectConfig, error) { - var config models.TamperProtectConfig - err := r.db.Where("agent_id = ?", agentID).First(&config).Error - if err != nil { - return nil, err - } - return &config, nil -} - -// SaveConfig 保存或更新防篡改配置 -func (r *TamperRepo) SaveConfig(config *models.TamperProtectConfig) error { - return r.db.Save(config).Error -} - -// DeleteConfig 删除防篡改配置 -func (r *TamperRepo) DeleteConfig(agentID string) error { - return r.db.Where("agent_id = ?", agentID).Delete(&models.TamperProtectConfig{}).Error -} - // CreateEvent 创建防篡改事件 func (r *TamperRepo) CreateEvent(event *models.TamperEvent) error { return r.db.Create(event).Error @@ -57,36 +37,7 @@ func (r *TamperRepo) GetEventsByAgentID(agentID string, limit, offset int) ([]mo return events, total, err } -// CreateAlert 创建防篡改告警 -func (r *TamperRepo) CreateAlert(alert *models.TamperAlert) error { - return r.db.Create(alert).Error -} - -// GetAlertsByAgentID 获取探针的防篡改告警(分页) -func (r *TamperRepo) GetAlertsByAgentID(agentID string, limit, offset int) ([]models.TamperAlert, int64, error) { - var alerts []models.TamperAlert - var total int64 - - query := r.db.Model(&models.TamperAlert{}).Where("agent_id = ?", agentID) - - if err := query.Count(&total).Error; err != nil { - return nil, 0, err - } - - err := query.Order("timestamp DESC"). - Limit(limit). - Offset(offset). - Find(&alerts).Error - - return alerts, total, err -} - // DeleteOldEvents 删除旧的事件记录(保留最近N天) func (r *TamperRepo) DeleteOldEvents(beforeTimestamp int64) error { return r.db.Where("timestamp < ?", beforeTimestamp).Delete(&models.TamperEvent{}).Error } - -// DeleteOldAlerts 删除旧的告警记录(保留最近N天) -func (r *TamperRepo) DeleteOldAlerts(beforeTimestamp int64) error { - return r.db.Where("timestamp < ?", beforeTimestamp).Delete(&models.TamperAlert{}).Error -} diff --git a/internal/service/agent_service.go b/internal/service/agent_service.go index 6ecb2b2..cf9576c 100644 --- a/internal/service/agent_service.go +++ b/internal/service/agent_service.go @@ -13,6 +13,7 @@ import ( "github.com/dushixiang/pika/internal/repo" "github.com/go-orz/orz" "go.uber.org/zap" + "gorm.io/datatypes" "gorm.io/gorm" ) @@ -480,21 +481,23 @@ func (s *AgentService) UpdateTrafficConfig(ctx context.Context, agentID string, } now := time.Now().UnixMilli() - oldResetDay := agent.TrafficResetDay + stats := agent.TrafficStats.Data() + oldResetDay := stats.ResetDay - agent.TrafficLimit = limit - agent.TrafficResetDay = resetDay + stats.Limit = limit + stats.ResetDay = resetDay // 如果是首次设置或修改重置日期,重置流量统计 - if agent.TrafficPeriodStart == 0 || resetDay != oldResetDay { - agent.TrafficUsed = 0 - agent.TrafficPeriodStart = now - agent.TrafficBaselineRecv = 0 // 下次上报时会设置正确的基线 - agent.TrafficAlertSent80 = false - agent.TrafficAlertSent90 = false - agent.TrafficAlertSent100 = false + if stats.PeriodStart == 0 || resetDay != oldResetDay { + stats.Used = 0 + stats.PeriodStart = now + stats.BaselineRecv = 0 // 下次上报时会设置正确的基线 + stats.AlertSent80 = false + stats.AlertSent90 = false + stats.AlertSent100 = false } + agent.TrafficStats = datatypes.NewJSONType(stats) agent.UpdatedAt = now return s.AgentRepo.UpdateById(ctx, &agent) } @@ -506,32 +509,34 @@ func (s *AgentService) GetTrafficStats(ctx context.Context, agentID string) (*Tr return nil, err } + trafficData := agent.TrafficStats.Data() + stats := &TrafficStats{ - TrafficLimit: agent.TrafficLimit, - TrafficUsed: agent.TrafficUsed, - TrafficResetDay: agent.TrafficResetDay, - PeriodStart: agent.TrafficPeriodStart, + TrafficLimit: trafficData.Limit, + TrafficUsed: trafficData.Used, + TrafficResetDay: trafficData.ResetDay, + PeriodStart: trafficData.PeriodStart, AlertsSent: TrafficAlerts{ - Sent80: agent.TrafficAlertSent80, - Sent90: agent.TrafficAlertSent90, - Sent100: agent.TrafficAlertSent100, + Sent80: trafficData.AlertSent80, + Sent90: trafficData.AlertSent90, + Sent100: trafficData.AlertSent100, }, } // 计算使用百分比 - if agent.TrafficLimit > 0 { - stats.TrafficUsedPercent = float64(agent.TrafficUsed) / float64(agent.TrafficLimit) * 100 - if agent.TrafficUsed < agent.TrafficLimit { - stats.TrafficRemaining = agent.TrafficLimit - agent.TrafficUsed + if trafficData.Limit > 0 { + stats.TrafficUsedPercent = float64(trafficData.Used) / float64(trafficData.Limit) * 100 + if trafficData.Used < trafficData.Limit { + stats.TrafficRemaining = trafficData.Limit - trafficData.Used } else { stats.TrafficRemaining = 0 } } // 计算下次重置日期和剩余天数 - if agent.TrafficResetDay > 0 && agent.TrafficPeriodStart > 0 { - periodStart := time.UnixMilli(agent.TrafficPeriodStart) - nextReset := calculateNextResetDate(periodStart, agent.TrafficResetDay) + if trafficData.ResetDay > 0 && trafficData.PeriodStart > 0 { + periodStart := time.UnixMilli(trafficData.PeriodStart) + nextReset := calculateNextResetDate(periodStart, trafficData.ResetDay) stats.PeriodEnd = nextReset.UnixMilli() stats.DaysUntilReset = int(time.Until(nextReset).Hours() / 24) if stats.DaysUntilReset < 0 { @@ -550,22 +555,23 @@ func (s *AgentService) ResetAgentTraffic(ctx context.Context, agentID string) er } now := time.Now().UnixMilli() + stats := agent.TrafficStats.Data() - updates := map[string]interface{}{ - "traffic_used": 0, - "traffic_baseline_recv": 0, // 下次上报时会设置正确的基线 - "traffic_period_start": now, - "traffic_alert_sent80": false, - "traffic_alert_sent90": false, - "traffic_alert_sent100": false, - "updated_at": now, - } + stats.Used = 0 + stats.BaselineRecv = 0 // 下次上报时会设置正确的基线 + stats.PeriodStart = now + stats.AlertSent80 = false + stats.AlertSent90 = false + stats.AlertSent100 = false + + agent.TrafficStats = datatypes.NewJSONType(stats) + agent.UpdatedAt = now s.logger.Info("探针流量已重置", zap.String("agentId", agentID), zap.String("agentName", agent.Name)) - return s.AgentRepo.UpdateTrafficStats(ctx, agentID, updates) + return s.AgentRepo.UpdateById(ctx, &agent) } // CheckAndResetTraffic 检查并重置所有到期的探针流量(定时任务调用) @@ -599,12 +605,13 @@ func (s *AgentService) CheckAndResetTraffic(ctx context.Context) error { // shouldResetTraffic 判断是否需要重置流量 func (s *AgentService) shouldResetTraffic(agent *models.Agent, now time.Time) bool { - if agent.TrafficResetDay == 0 || agent.TrafficPeriodStart == 0 { + stats := agent.TrafficStats.Data() + if stats.ResetDay == 0 || stats.PeriodStart == 0 { return false } - periodStart := time.UnixMilli(agent.TrafficPeriodStart) - nextReset := calculateNextResetDate(periodStart, agent.TrafficResetDay) + periodStart := time.UnixMilli(stats.PeriodStart) + nextReset := calculateNextResetDate(periodStart, stats.ResetDay) return now.After(nextReset) || now.Equal(nextReset) } diff --git a/internal/service/ssh_login_service.go b/internal/service/ssh_login_service.go index 7b6cfec..2d849c7 100644 --- a/internal/service/ssh_login_service.go +++ b/internal/service/ssh_login_service.go @@ -38,8 +38,8 @@ func NewSSHLoginService(logger *zap.Logger, db *gorm.DB, wsManager *websocket.Ma // === 配置管理 === // GetConfig 获取探针的配置 -func (s *SSHLoginService) GetConfig(agentID string) (*models.SSHLoginConfig, error) { - config, err := s.repo.GetConfigByAgentID(agentID) +func (s *SSHLoginService) GetConfig(agentID string) (*models.SSHLoginConfigData, error) { + config, err := s.agentRepo.GetSSHLoginConfig(context.Background(), agentID) if err != nil { return nil, err } @@ -48,7 +48,7 @@ func (s *SSHLoginService) GetConfig(agentID string) (*models.SSHLoginConfig, err // UpdateConfig 更新配置并下发到 Agent // 返回: config - 配置对象, configSent - 是否成功下发到 Agent, error - 错误信息 -func (s *SSHLoginService) UpdateConfig(ctx context.Context, agentID string, enabled, recordFailed bool) (*models.SSHLoginConfig, bool, error) { +func (s *SSHLoginService) UpdateConfig(ctx context.Context, agentID string, enabled bool) (*models.SSHLoginConfigData, bool, error) { // 检查探针是否存在 agent, err := s.agentRepo.FindById(ctx, agentID) if err != nil { @@ -56,20 +56,18 @@ func (s *SSHLoginService) UpdateConfig(ctx context.Context, agentID string, enab } // 保存配置到数据库 - config := &models.SSHLoginConfig{ - AgentID: agentID, - Enabled: enabled, - RecordFailed: recordFailed, + config := &models.SSHLoginConfigData{ + Enabled: enabled, } - if err := s.repo.UpsertConfig(config); err != nil { + if err := s.agentRepo.UpdateSSHLoginConfig(ctx, agentID, config); err != nil { return nil, false, err } // 下发配置到 Agent configSent := false if agent.Status == 1 { // 仅在探针在线时尝试下发 - if err := s.sendConfigToAgent(agentID, enabled, recordFailed); err != nil { + if err := s.sendConfigToAgent(agentID, enabled); err != nil { s.logger.Warn("下发SSH登录监控配置到Agent失败", zap.Error(err), zap.String("agentId", agentID)) } else { s.logger.Info("成功下发SSH登录监控配置", zap.String("agentId", agentID), zap.Bool("enabled", enabled)) @@ -83,10 +81,9 @@ func (s *SSHLoginService) UpdateConfig(ctx context.Context, agentID string, enab } // sendConfigToAgent 下发配置到 Agent -func (s *SSHLoginService) sendConfigToAgent(agentID string, enabled, recordFailed bool) error { +func (s *SSHLoginService) sendConfigToAgent(agentID string, enabled bool) error { configData := protocol.SSHLoginConfig{ - Enabled: enabled, - RecordFailed: recordFailed, + Enabled: enabled, } message := protocol.OutboundMessage{ @@ -105,7 +102,7 @@ func (s *SSHLoginService) sendConfigToAgent(agentID string, enabled, recordFaile // HandleConfigResult 处理 Agent 上报的配置应用结果 func (s *SSHLoginService) HandleConfigResult(agentID string, result protocol.SSHLoginConfigResult) error { // 获取现有配置 - config, err := s.repo.GetConfigByAgentID(agentID) + config, err := s.agentRepo.GetSSHLoginConfig(context.Background(), agentID) if err != nil { return fmt.Errorf("获取配置失败: %w", err) } @@ -120,14 +117,10 @@ func (s *SSHLoginService) HandleConfigResult(agentID string, result protocol.SSH status = "failed" } - updates := map[string]interface{}{ - "apply_status": status, - "apply_message": result.Message, - "apply_error": result.Error, - "last_applied_at": getCurrentTimestamp(), - } + config.ApplyStatus = status + config.ApplyMessage = result.Message - if err := s.repo.UpdateConfigStatus(agentID, updates); err != nil { + if err := s.agentRepo.UpdateSSHLoginConfig(context.Background(), agentID, config); err != nil { s.logger.Error("更新配置应用状态失败", zap.Error(err), zap.String("agentId", agentID)) return err } @@ -141,8 +134,7 @@ func (s *SSHLoginService) HandleConfigResult(agentID string, result protocol.SSH } else { s.logger.Warn("Agent应用SSH登录监控配置失败", zap.String("agentId", agentID), - zap.String("message", result.Message), - zap.String("error", result.Error)) + zap.String("message", result.Message)) } return nil @@ -158,7 +150,7 @@ func getCurrentTimestamp() int64 { // HandleEvent 处理 Agent 上报的事件 func (s *SSHLoginService) HandleEvent(agentID string, eventData protocol.SSHLoginEvent) error { // 检查是否启用监控 - config, err := s.repo.GetConfigByAgentID(agentID) + config, err := s.agentRepo.GetSSHLoginConfig(context.Background(), agentID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } @@ -169,11 +161,6 @@ func (s *SSHLoginService) HandleEvent(agentID string, eventData protocol.SSHLogi return nil } - // 如果不记录失败登录且状态为失败,忽略 - if !config.RecordFailed && eventData.Status == "failed" { - return nil - } - // 去重检查(避免 Agent 重启时重复上报) existing, err := s.repo.FindEventByTimestamp(agentID, eventData.Timestamp, 5000) // 5秒容差 if err != nil { diff --git a/internal/service/tamper_service.go b/internal/service/tamper_service.go index 5a8f3a1..1e2652a 100644 --- a/internal/service/tamper_service.go +++ b/internal/service/tamper_service.go @@ -1,6 +1,7 @@ package service import ( + "context" "encoding/json" "errors" "time" @@ -11,12 +12,12 @@ import ( "github.com/dushixiang/pika/internal/websocket" "github.com/google/uuid" "go.uber.org/zap" - "gorm.io/datatypes" "gorm.io/gorm" ) type TamperService struct { logger *zap.Logger + agentRepo *repo.AgentRepo tamperRepo *repo.TamperRepo wsManager *websocket.Manager } @@ -24,14 +25,15 @@ type TamperService struct { func NewTamperService(logger *zap.Logger, db *gorm.DB, wsManager *websocket.Manager) *TamperService { return &TamperService{ logger: logger, + agentRepo: repo.NewAgentRepo(db), tamperRepo: repo.NewTamperRepo(db), wsManager: wsManager, } } // GetConfigByAgentID 获取探针的防篡改配置 -func (s *TamperService) GetConfigByAgentID(agentID string) (*models.TamperProtectConfig, error) { - config, err := s.tamperRepo.GetConfigByAgentID(agentID) +func (s *TamperService) GetConfigByAgentID(agentID string) (*models.TamperProtectConfigData, error) { + config, err := s.agentRepo.GetTamperProtectConfig(context.Background(), agentID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil // 没有配置不算错误 @@ -42,11 +44,9 @@ func (s *TamperService) GetConfigByAgentID(agentID string) (*models.TamperProtec } // UpdateConfig 更新探针的防篡改配置 -func (s *TamperService) UpdateConfig(agentID string, paths []string) (*models.TamperProtectConfig, error) { - now := time.Now().UnixMilli() - +func (s *TamperService) UpdateConfig(agentID string, enabled bool, paths []string) (*models.TamperProtectConfigData, error) { // 查找现有配置 - config, err := s.tamperRepo.GetConfigByAgentID(agentID) + config, err := s.agentRepo.GetTamperProtectConfig(context.Background(), agentID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } @@ -60,23 +60,14 @@ func (s *TamperService) UpdateConfig(agentID string, paths []string) (*models.Ta // 计算新增和移除的路径 added, removed := s.calculatePathDiff(oldPaths, paths) - if config == nil { - // 创建新配置 - config = &models.TamperProtectConfig{ - ID: uuid.New().String(), - AgentID: agentID, - Paths: datatypes.NewJSONSlice(paths), - CreatedAt: now, - UpdatedAt: now, - } - } else { - // 更新现有配置 - config.Paths = datatypes.NewJSONSlice(paths) - config.UpdatedAt = now + // 创建或更新配置 + newConfig := &models.TamperProtectConfigData{ + Enabled: enabled, + Paths: paths, } // 保存配置到数据库 - if err := s.tamperRepo.SaveConfig(config); err != nil { + if err := s.agentRepo.UpdateTamperProtectConfig(context.Background(), agentID, newConfig); err != nil { return nil, err } @@ -96,7 +87,7 @@ func (s *TamperService) UpdateConfig(agentID string, paths []string) (*models.Ta zap.Int("totalPaths", len(paths))) } - return config, nil + return newConfig, nil } // calculatePathDiff 计算路径的新增和移除 @@ -154,11 +145,6 @@ func (s *TamperService) sendConfigToAgent(agentID string, added, removed []strin return s.wsManager.SendToClient(agentID, msgBytes) } -// DeleteConfig 删除探针的防篡改配置 -func (s *TamperService) DeleteConfig(agentID string) error { - return s.tamperRepo.DeleteConfig(agentID) -} - // CreateEvent 创建防篡改事件 func (s *TamperService) CreateEvent(agentID, path, operation, details string, timestamp int64) error { event := &models.TamperEvent{ @@ -189,44 +175,10 @@ func (s *TamperService) GetEventsByAgentID(agentID string, pageNum, pageSize int return s.tamperRepo.GetEventsByAgentID(agentID, pageSize, offset) } -// CreateAlert 创建防篡改告警 -func (s *TamperService) CreateAlert(agentID, path, details string, restored bool, timestamp int64) error { - alert := &models.TamperAlert{ - ID: uuid.New().String(), - AgentID: agentID, - Path: path, - Details: details, - Restored: restored, - Timestamp: timestamp, - CreatedAt: time.Now().UnixMilli(), - } - return s.tamperRepo.CreateAlert(alert) -} - -// GetAlertsByAgentID 获取探针的防篡改告警 -func (s *TamperService) GetAlertsByAgentID(agentID string, pageNum, pageSize int) ([]models.TamperAlert, int64, error) { - if pageNum < 1 { - pageNum = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 100 { - pageSize = 100 - } - - offset := (pageNum - 1) * pageSize - return s.tamperRepo.GetAlertsByAgentID(agentID, pageSize, offset) -} - // CleanupOldRecords 清理旧记录(保留最近30天) func (s *TamperService) CleanupOldRecords() error { // 30天前的时间戳 threshold := time.Now().AddDate(0, 0, -30).UnixMilli() - if err := s.tamperRepo.DeleteOldEvents(threshold); err != nil { - return err - } - - return s.tamperRepo.DeleteOldAlerts(threshold) + return s.tamperRepo.DeleteOldEvents(threshold) } diff --git a/internal/service/traffic_service.go b/internal/service/traffic_service.go index 8d05bae..c3394dd 100644 --- a/internal/service/traffic_service.go +++ b/internal/service/traffic_service.go @@ -8,6 +8,7 @@ import ( "github.com/dushixiang/pika/internal/models" "github.com/dushixiang/pika/internal/repo" "go.uber.org/zap" + "gorm.io/datatypes" "gorm.io/gorm" ) @@ -32,66 +33,73 @@ func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string, return err } + // 获取流量统计数据 + stats := agent.TrafficStats.Data() + // 如果未配置流量限制,跳过更新 - if agent.TrafficLimit == 0 && agent.TrafficResetDay == 0 { + if stats.Limit == 0 && stats.ResetDay == 0 { return nil } // 初始化基线(首次统计) - if agent.TrafficBaselineRecv == 0 { - agent.TrafficBaselineRecv = currentRecvTotal - agent.TrafficUsed = 0 - if agent.TrafficPeriodStart == 0 { - agent.TrafficPeriodStart = time.Now().UnixMilli() + if stats.BaselineRecv == 0 { + stats.BaselineRecv = currentRecvTotal + stats.Used = 0 + if stats.PeriodStart == 0 { + stats.PeriodStart = time.Now().UnixMilli() } + agent.TrafficStats = datatypes.NewJSONType(stats) return s.agentRepo.UpdateById(ctx, &agent) } // 检测计数器重置(探针重启) - if currentRecvTotal < agent.TrafficBaselineRecv { + if currentRecvTotal < stats.BaselineRecv { s.logger.Warn("检测到流量计数器重置", zap.String("agentId", agentID), - zap.Uint64("baseline", agent.TrafficBaselineRecv), + zap.Uint64("baseline", stats.BaselineRecv), zap.Uint64("current", currentRecvTotal)) - agent.TrafficBaselineRecv = currentRecvTotal - // 保持 TrafficUsed 不变,避免丢失已统计的流量 + stats.BaselineRecv = currentRecvTotal + // 保持 Used 不变,避免丢失已统计的流量 } else { // 计算使用量 - agent.TrafficUsed = currentRecvTotal - agent.TrafficBaselineRecv + stats.Used = currentRecvTotal - stats.BaselineRecv } // 检查告警(如果配置了限额) - if agent.TrafficLimit > 0 { - s.checkTrafficAlerts(ctx, &agent) + if stats.Limit > 0 { + s.checkTrafficAlerts(ctx, &agent, &stats) } + // 保存更新后的统计数据 + agent.TrafficStats = datatypes.NewJSONType(stats) + // 更新数据库 return s.agentRepo.UpdateById(ctx, &agent) } // checkTrafficAlerts 检查并发送流量告警 -func (s *TrafficService) checkTrafficAlerts(ctx context.Context, agent *models.Agent) { - usagePercent := float64(agent.TrafficUsed) / float64(agent.TrafficLimit) * 100 +func (s *TrafficService) checkTrafficAlerts(ctx context.Context, agent *models.Agent, stats *models.TrafficStatsData) { + usagePercent := float64(stats.Used) / float64(stats.Limit) * 100 // 100% 告警 - if usagePercent >= 100 && !agent.TrafficAlertSent100 { - s.sendTrafficAlert(ctx, agent, 100, usagePercent) - agent.TrafficAlertSent100 = true + if usagePercent >= 100 && !stats.AlertSent100 { + s.sendTrafficAlert(ctx, agent, stats, 100, usagePercent) + stats.AlertSent100 = true } // 90% 告警 - if usagePercent >= 90 && !agent.TrafficAlertSent90 { - s.sendTrafficAlert(ctx, agent, 90, usagePercent) - agent.TrafficAlertSent90 = true + if usagePercent >= 90 && !stats.AlertSent90 { + s.sendTrafficAlert(ctx, agent, stats, 90, usagePercent) + stats.AlertSent90 = true } // 80% 告警 - if usagePercent >= 80 && !agent.TrafficAlertSent80 { - s.sendTrafficAlert(ctx, agent, 80, usagePercent) - agent.TrafficAlertSent80 = true + if usagePercent >= 80 && !stats.AlertSent80 { + s.sendTrafficAlert(ctx, agent, stats, 80, usagePercent) + stats.AlertSent80 = true } } // sendTrafficAlert 发送流量告警 -func (s *TrafficService) sendTrafficAlert(ctx context.Context, agent *models.Agent, threshold int, actualPercent float64) { +func (s *TrafficService) sendTrafficAlert(ctx context.Context, agent *models.Agent, stats *models.TrafficStatsData, threshold int, actualPercent float64) { level := "info" if threshold == 100 { level = "critical" @@ -106,8 +114,8 @@ func (s *TrafficService) sendTrafficAlert(ctx context.Context, agent *models.Age AlertType: "traffic", Message: fmt.Sprintf("流量使用已达到%d%%,当前使用%.2f%%(%s/%s)", threshold, actualPercent, - formatBytes(agent.TrafficUsed), - formatBytes(agent.TrafficLimit)), + formatBytes(stats.Used), + formatBytes(stats.Limit)), Threshold: float64(threshold), ActualValue: actualPercent, Level: level, diff --git a/pkg/agent/service/agent.go b/pkg/agent/service/agent.go index 3373e75..b30b61e 100644 --- a/pkg/agent/service/agent.go +++ b/pkg/agent/service/agent.go @@ -235,11 +235,6 @@ func (a *Agent) runOnce(ctx context.Context, onConnected func()) error { a.tamperEventLoop(ctx, conn, done) }) - // 启动防篡改属性告警监控 - wg.Go(func() { - a.tamperAlertLoop(ctx, conn, done) - }) - // 启动 SSH 登录事件监控 wg.Go(func() { a.sshLoginEventLoop(ctx, conn, done) @@ -626,7 +621,7 @@ func (a *Agent) handleTamperProtect(data json.RawMessage) { var tamperProtectConfig protocol.TamperProtectConfig if err := json.Unmarshal(data, &tamperProtectConfig); err != nil { slog.Warn("解析防篡改保护配置失败", "error", err) - a.sendTamperProtectResponse(false, "解析配置失败", nil, nil, nil, err.Error()) + a.sendTamperProtectResponse(false, fmt.Sprintf("解析配置失败: %v", err), nil, nil, nil) return } @@ -641,7 +636,7 @@ func (a *Agent) handleTamperProtect(data json.RawMessage) { // 如果没有新增也没有移除,不需要做任何操作 if len(tamperProtectConfig.Added) == 0 && len(tamperProtectConfig.Removed) == 0 { slog.Info("配置无变化,跳过更新") - a.sendTamperProtectResponse(true, "配置无变化", a.tamperProtector.GetProtectedPaths(), []string{}, []string{}, "") + a.sendTamperProtectResponse(true, "", a.tamperProtector.GetProtectedPaths(), []string{}, []string{}) return } @@ -653,9 +648,9 @@ func (a *Agent) handleTamperProtect(data json.RawMessage) { slog.Warn("应用增量更新失败", "error", err) // 即使有错误也返回部分成功的结果 if result != nil { - a.sendTamperProtectResponse(false, "部分更新失败", result.Current, result.Added, result.Removed, err.Error()) + a.sendTamperProtectResponse(false, fmt.Sprintf("应用增量更新失败: %v", err), result.Current, result.Added, result.Removed) } else { - a.sendTamperProtectResponse(false, "更新失败", nil, nil, nil, err.Error()) + a.sendTamperProtectResponse(false, fmt.Sprintf("应用增量更新失败: %v", err), nil, nil, nil) } return } @@ -664,11 +659,11 @@ func (a *Agent) handleTamperProtect(data json.RawMessage) { message := fmt.Sprintf("防篡改保护已更新: 新增 %d 个, 移除 %d 个, 当前保护 %d 个目录", len(result.Added), len(result.Removed), len(result.Current)) slog.Info(message) - a.sendTamperProtectResponse(true, message, result.Current, result.Added, result.Removed, "") + a.sendTamperProtectResponse(true, message, result.Current, result.Added, result.Removed) } // sendTamperProtectResponse 发送防篡改保护响应 -func (a *Agent) sendTamperProtectResponse(success bool, message string, paths []string, added []string, removed []string, errMsg string) { +func (a *Agent) sendTamperProtectResponse(success bool, message string, paths []string, added []string, removed []string) { conn := a.getActiveConn() if conn == nil { return @@ -680,7 +675,6 @@ func (a *Agent) sendTamperProtectResponse(success bool, message string, paths [] Paths: paths, Added: added, Removed: removed, - Error: errMsg, } if err := conn.WriteJSON(protocol.OutboundMessage{ @@ -691,9 +685,10 @@ func (a *Agent) sendTamperProtectResponse(success bool, message string, paths [] } } -// tamperEventLoop 防篡改事件监控循环 +// tamperEventLoop 防篡改事件监控循环(包含事件和告警) func (a *Agent) tamperEventLoop(ctx context.Context, conn *safeConn, done chan struct{}) { eventCh := a.tamperProtector.GetEvents() + alertCh := a.tamperProtector.GetAlerts() for { select { @@ -718,40 +713,27 @@ func (a *Agent) tamperEventLoop(ctx context.Context, conn *safeConn, done chan s } else { slog.Info("已上报防篡改事件", "path", event.Path, "operation", event.Operation) } - } - } -} - -// tamperAlertLoop 防篡改属性告警监控循环 -func (a *Agent) tamperAlertLoop(ctx context.Context, conn *safeConn, done chan struct{}) { - alertCh := a.tamperProtector.GetAlerts() - - for { - select { - case <-done: - return - case <-ctx.Done(): - return case alert := <-alertCh: - // 发送属性篡改告警到服务端 - alertData := protocol.TamperAlertData{ + // 将防篡改告警转换为事件格式发送 + eventData := protocol.TamperEventData{ Path: alert.Path, + Operation: "attr_tamper", // 使用特殊的操作类型标识属性篡改 Timestamp: alert.Timestamp.UnixMilli(), Details: alert.Details, Restored: alert.Restored, } if err := conn.WriteJSON(protocol.OutboundMessage{ - Type: protocol.MessageTypeTamperAlert, - Data: alertData, + Type: protocol.MessageTypeTamperEvent, + Data: eventData, }); err != nil { - slog.Warn("发送属性篡改告警失败", "error", err) + slog.Warn("发送防篡改告警失败", "error", err) } else { status := "未恢复" if alert.Restored { status = "已恢复" } - slog.Info("已上报属性篡改告警", "path", alert.Path, "status", status) + slog.Info("已上报防篡改告警", "path", alert.Path, "status", status) } } } @@ -868,42 +850,41 @@ func (a *Agent) handleUninstall() { // handleSSHLoginConfig 处理 SSH 登录监控配置 func (a *Agent) handleSSHLoginConfig(conn *websocket.Conn, data json.RawMessage) { - var config protocol.SSHLoginConfig - if err := json.Unmarshal(data, &config); err != nil { + var sshLoginConfig protocol.SSHLoginConfig + if err := json.Unmarshal(data, &sshLoginConfig); err != nil { slog.Warn("解析SSH登录监控配置失败", "error", err) // 发送失败结果 - a.sendSSHLoginConfigResult(conn, false, false, "解析配置失败", err.Error()) + a.sendSSHLoginConfigResult(conn, false, false, err.Error()) return } - slog.Info("收到SSH登录监控配置", "enabled", config.Enabled, "recordFailed", config.RecordFailed) + slog.Info("收到SSH登录监控配置", "enabled", sshLoginConfig.Enabled, "recordFailed", sshLoginConfig.RecordFailed) // 应用配置 ctx := context.Background() - if err := a.sshMonitor.Start(ctx, config); err != nil { + if err := a.sshMonitor.Start(ctx, sshLoginConfig); err != nil { slog.Warn("应用SSH登录监控配置失败", "error", err) // 发送失败结果,包含详细错误信息 - a.sendSSHLoginConfigResult(conn, false, config.Enabled, "应用配置失败", err.Error()) + a.sendSSHLoginConfigResult(conn, false, sshLoginConfig.Enabled, err.Error()) return } // 发送成功结果 message := "配置已成功应用" - if config.Enabled { + if sshLoginConfig.Enabled { message = "SSH登录监控已启用" } else { message = "SSH登录监控已禁用" } - a.sendSSHLoginConfigResult(conn, true, config.Enabled, message, "") + a.sendSSHLoginConfigResult(conn, true, sshLoginConfig.Enabled, message) } // sendSSHLoginConfigResult 发送 SSH 登录监控配置应用结果 -func (a *Agent) sendSSHLoginConfigResult(conn *websocket.Conn, success bool, enabled bool, message string, errorMsg string) { +func (a *Agent) sendSSHLoginConfigResult(conn *websocket.Conn, success bool, enabled bool, message string) { result := protocol.SSHLoginConfigResult{ Success: success, Enabled: enabled, Message: message, - Error: errorMsg, } msg := protocol.OutboundMessage{ diff --git a/web/src/api/tamper.ts b/web/src/api/tamper.ts index 1e58970..c3c3d31 100644 --- a/web/src/api/tamper.ts +++ b/web/src/api/tamper.ts @@ -4,7 +4,10 @@ import qs from 'qs'; export interface TamperConfig { id: string; agentId: string; + enabled: boolean; paths: string[]; + applyStatus?: string; + applyMessage?: string; createdAt: number; updatedAt: number; } @@ -45,10 +48,10 @@ export const getTamperConfig = (agentId: string) => { }; // 更新防篡改配置 -export const updateTamperConfig = (agentId: string, paths: string[]) => { +export const updateTamperConfig = (agentId: string, enabled: boolean, paths: string[]) => { return request.put<{ success: boolean; message: string; data: TamperConfig }>( `/admin/agents/${agentId}/tamper/config`, - {paths} + {enabled, paths} ); }; @@ -69,3 +72,8 @@ export const getTamperAlerts = (agentId: string, pageIndex: number = 1, pageSize `/admin/agents/${agentId}/tamper/alerts?${paramStr}`, ); }; + +// 删除防篡改事件 +export const deleteTamperEvents = (agentId: string) => { + return request.delete<{ success: boolean; message: string }>(`/admin/agents/${agentId}/tamper/events`); +}; diff --git a/web/src/pages/Agents/SSHLoginConfig.tsx b/web/src/pages/Agents/SSHLoginConfig.tsx index b9300af..33318aa 100644 --- a/web/src/pages/Agents/SSHLoginConfig.tsx +++ b/web/src/pages/Agents/SSHLoginConfig.tsx @@ -1,8 +1,10 @@ -import React, {useEffect, useState} from 'react'; -import {Alert, App, Button, Card, Form, Switch} from 'antd'; +import React, {useEffect} from 'react'; +import {Alert, App, Button, Card, Form, Space, Switch} from 'antd'; import {Save, Terminal} from 'lucide-react'; +import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import type {SSHLoginConfig as SSHLoginConfigType} from '@/types'; import {getSSHLoginConfig, updateSSHLoginConfig} from '@/api/agent'; +import {getErrorMessage} from '@/lib/utils'; interface SSHLoginConfigProps { agentId: string; @@ -11,56 +13,47 @@ interface SSHLoginConfigProps { const SSHLoginConfig: React.FC = ({agentId}) => { const {message} = App.useApp(); const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [saving, setSaving] = useState(false); - const [config, setConfig] = useState(null); + const queryClient = useQueryClient(); - // 加载配置 - const loadConfig = async () => { - try { - setLoading(true); - const cfg = await getSSHLoginConfig(agentId); - setConfig(cfg); - // 设置表单值 - form.setFieldsValue({ - enabled: cfg.enabled || false, - recordFailed: cfg.recordFailed !== undefined ? cfg.recordFailed : true, + // 获取 SSH 登录监控配置 + const {data: config, isLoading} = useQuery({ + queryKey: ['sshLoginConfig', agentId], + queryFn: () => getSSHLoginConfig(agentId), + }); + + // 保存配置 mutation + const saveMutation = useMutation({ + mutationFn: async () => { + const values = form.getFieldsValue(); + return updateSSHLoginConfig(agentId, { + enabled: values.enabled, + recordFailed: values.recordFailed, }); - } catch (error) { - console.error('Failed to load SSH login config:', error); - // 发生错误时使用默认配置 + }, + onSuccess: () => { + message.success('配置已保存'); + queryClient.invalidateQueries({queryKey: ['sshLoginConfig', agentId]}); + }, + onError: (error: unknown) => { + console.error('Failed to save SSH login config:', error); + message.error(getErrorMessage(error, '配置保存失败')); + }, + }); + + // 初始化表单值 + useEffect(() => { + if (config) { + form.setFieldsValue({ + enabled: config.enabled || false, + recordFailed: config.recordFailed !== undefined ? config.recordFailed : true, + }); + } else { form.setFieldsValue({ enabled: false, recordFailed: true, }); - } finally { - setLoading(false); } - }; - - // 保存配置 - const handleSaveConfig = async () => { - try { - setSaving(true); - const values = form.getFieldsValue(); - await updateSSHLoginConfig(agentId, { - enabled: values.enabled, - recordFailed: values.recordFailed, - }); - // 显示来自后端的消息 - message.success('配置已保存'); - await loadConfig(); - } catch (error: any) { - console.error('Failed to save SSH login config:', error); - message.error(error.response?.data?.error || '配置保存失败'); - } finally { - setSaving(false); - } - }; - - useEffect(() => { - loadConfig(); - }, [agentId]); + }, [config, form]); return ( = ({agentId}) => { } - loading={loading} + loading={isLoading} > -
- + - - + + + - - + + +
+ + {config?.applyStatus && ( + - - + )} +
); }; diff --git a/web/src/pages/Agents/SSHLoginEvents.tsx b/web/src/pages/Agents/SSHLoginEvents.tsx index 717514b..8c902f3 100644 --- a/web/src/pages/Agents/SSHLoginEvents.tsx +++ b/web/src/pages/Agents/SSHLoginEvents.tsx @@ -3,8 +3,10 @@ import {App, Button, Tag, Tooltip} from 'antd'; import {Terminal} from 'lucide-react'; import type {ActionType, ProColumns} from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table'; +import {useMutation} from '@tanstack/react-query'; import type {SSHLoginEvent} from '@/types'; import {deleteSSHLoginEvents, getSSHLoginEvents} from '@/api/agent'; +import {getErrorMessage} from '@/lib/utils'; import dayjs from 'dayjs'; interface SSHLoginEventsProps { @@ -112,6 +114,19 @@ const SSHLoginEvents: React.FC = ({agentId}) => { }, ]; + // 删除所有事件 mutation + const deleteMutation = useMutation({ + mutationFn: () => deleteSSHLoginEvents(agentId), + onSuccess: () => { + message.success('所有事件已删除'); + actionRef.current?.reload(); + }, + onError: (error: unknown) => { + console.error('Failed to delete SSH login events:', error); + message.error(getErrorMessage(error, '删除失败')); + }, + }); + // 删除所有事件 const handleDeleteAllEvents = () => { modal.confirm({ @@ -120,16 +135,7 @@ const SSHLoginEvents: React.FC = ({agentId}) => { okText: '确定删除', okType: 'danger', cancelText: '取消', - onOk: async () => { - try { - await deleteSSHLoginEvents(agentId); - message.success('所有事件已删除'); - actionRef.current?.reload(); - } catch (error: any) { - console.error('Failed to delete SSH login events:', error); - message.error(error.response?.data?.error || '删除失败'); - } - }, + onOk: () => deleteMutation.mutate(), }); }; diff --git a/web/src/pages/Agents/TamperProtection.tsx b/web/src/pages/Agents/TamperProtection.tsx index 3aa2adc..2a7319e 100644 --- a/web/src/pages/Agents/TamperProtection.tsx +++ b/web/src/pages/Agents/TamperProtection.tsx @@ -1,385 +1,45 @@ -import React, {useEffect, useState} from 'react'; -import {AlertTriangle, FileWarning, Plus, RefreshCw, Save, Shield, Trash2} from 'lucide-react'; -import { - getTamperAlerts, - getTamperConfig, - getTamperEvents, - type TamperAlert, - type TamperConfig, - type TamperEvent, - updateTamperConfig -} from '@/api/tamper.ts'; -import {App} from "antd"; +import React, {useState} from 'react'; +import TamperProtectionConfig from './TamperProtectionConfig'; +import TamperProtectionEvents from './TamperProtectionEvents'; interface TamperProtectionProps { agentId: string; } const TamperProtection: React.FC = ({agentId}) => { - const [activeTab, setActiveTab] = useState<'config' | 'events' | 'alerts'>('config'); - const [config, setConfig] = useState(null); - const [events, setEvents] = useState([]); - const [alerts, setAlerts] = useState([]); - const [loading, setLoading] = useState(false); - const [saving, setSaving] = useState(false); - let {message} = App.useApp(); - - // 配置编辑状态 - const [editPaths, setEditPaths] = useState([]); - const [newPath, setNewPath] = useState(''); - - // 分页状态 - const [eventsPage, setEventsPage] = useState(1); - const [eventsTotal, setEventsTotal] = useState(0); - const [alertsPage, setAlertsPage] = useState(1); - const [alertsTotal, setAlertsTotal] = useState(0); - - // 加载配置 - const loadConfig = async () => { - try { - setLoading(true); - const response = await getTamperConfig(agentId); - if (response.data.success && response.data.data) { - setConfig(response.data.data); - setEditPaths(response.data.data.paths || []); - } else { - setEditPaths([]); - } - } catch (error) { - console.error('Failed to load tamper config:', error); - } finally { - setLoading(false); - } - }; - - // 加载事件 - const loadEvents = async (page: number = 1) => { - try { - setLoading(true); - const response = await getTamperEvents(agentId, page, 20); - if (response.data.success) { - setEvents(response.data.data.items || []); - setEventsTotal(response.data.data.total || 0); - setEventsPage(page); - } - } catch (error) { - console.error('Failed to load tamper events:', error); - } finally { - setLoading(false); - } - }; - - // 加载告警 - const loadAlerts = async (page: number = 1) => { - try { - setLoading(true); - const response = await getTamperAlerts(agentId, page, 20); - if (response.data.success) { - setAlerts(response.data.data.items || []); - setAlertsTotal(response.data.data.total || 0); - setAlertsPage(page); - } - } catch (error) { - console.error('Failed to load tamper alerts:', error); - } finally { - setLoading(false); - } - }; - - // 保存配置 - const handleSaveConfig = async () => { - try { - setSaving(true); - const response = await updateTamperConfig(agentId, editPaths); - if (response.data.success) { - message.success('配置保存成功!'); - await loadConfig(); - } - } catch (error) { - console.error('Failed to save config:', error); - message.error('配置保存失败,请重试。'); - } finally { - setSaving(false); - } - }; - - // 添加路径 - const handleAddPath = () => { - if (newPath.trim() && !editPaths.includes(newPath.trim())) { - setEditPaths([...editPaths, newPath.trim()]); - setNewPath(''); - } - }; - - // 删除路径 - const handleRemovePath = (path: string) => { - setEditPaths(editPaths.filter(p => p !== path)); - }; - - // 初始化加载 - useEffect(() => { - if (activeTab === 'config') { - loadConfig(); - } else if (activeTab === 'events') { - loadEvents(1); - } else if (activeTab === 'alerts') { - loadAlerts(1); - } - }, [activeTab, agentId]); - - // 格式化时间 - const formatTime = (timestamp: number) => { - return new Date(timestamp).toLocaleString('zh-CN'); - }; + const [activeTab, setActiveTab] = useState<'config' | 'events'>('config'); return ( -
- {/* 标签页导航 */} -
+
+ {/* Tab 切换 */} +
-
- {/* 配置标签页 */} - {activeTab === 'config' && ( -
-
-

- - 防篡改保护通过设置目录的不可变属性来防止文件被修改、删除或重命名。配置更新后将实时同步到探针。 -

-
+ {/* 配置面板 */} + {activeTab === 'config' && } - {/* 添加路径 */} -
- setNewPath(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleAddPath()} - placeholder="输入要保护的目录路径,如 /etc/nginx" - className="flex-1 rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200" - /> - -
- - {/* 路径列表 */} -
- {editPaths.length === 0 ? ( -
- -

暂未配置保护目录

-
- ) : ( - editPaths.map((path, index) => ( -
- {path} - -
- )) - )} -
- - {/* 保存按钮 */} -
- - -
-
- )} - - {/* 事件标签页 */} - {activeTab === 'events' && ( -
-
- 共 {eventsTotal} 条事件记录 -
- - {events.length === 0 ? ( -
- -

暂无文件事件

-
- ) : ( -
- {events.map((event) => ( -
-
-
-
- - {event.operation} - - {event.path} -
-

{event.details}

-
- {formatTime(event.timestamp)} -
-
- ))} -
- )} - - {/* 分页 */} - {eventsTotal > 20 && ( -
- - - {eventsPage} / {Math.ceil(eventsTotal / 20)} - - -
- )} -
- )} - - {/* 告警标签页 */} - {activeTab === 'alerts' && ( -
-
- 共 {alertsTotal} 条告警记录 -
- - {alerts.length === 0 ? ( -
- -

暂无属性告警

-
- ) : ( -
- {alerts.map((alert) => ( -
-
-
-
- - {alert.path} -
-

{alert.details}

-

- {alert.restored ? '✓ 已自动恢复' : '✗ 恢复失败'} -

-
- {formatTime(alert.timestamp)} -
-
- ))} -
- )} - - {/* 分页 */} - {alertsTotal > 20 && ( -
- - - {alertsPage} / {Math.ceil(alertsTotal / 20)} - - -
- )} -
- )} + {/* 事件列表面板 */} + {activeTab === 'events' && }
); }; diff --git a/web/src/pages/Agents/TamperProtectionConfig.tsx b/web/src/pages/Agents/TamperProtectionConfig.tsx new file mode 100644 index 0000000..32e8d56 --- /dev/null +++ b/web/src/pages/Agents/TamperProtectionConfig.tsx @@ -0,0 +1,190 @@ +import React, {useEffect, useState} from 'react'; +import {Alert, App, Button, Card, Form, Input, List, Space, Switch} from 'antd'; +import {Plus, Save, Shield, Trash2} from 'lucide-react'; +import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; +import {getTamperConfig, updateTamperConfig} from '@/api/tamper'; +import {getErrorMessage} from '@/lib/utils'; + +interface TamperProtectionConfigProps { + agentId: string; +} + +const TamperProtectionConfig: React.FC = ({agentId}) => { + const {message} = App.useApp(); + const [form] = Form.useForm(); + const queryClient = useQueryClient(); + const [editPaths, setEditPaths] = useState([]); + const [newPath, setNewPath] = useState(''); + + // 获取防篡改配置 + const {data: config, isLoading} = useQuery({ + queryKey: ['tamperConfig', agentId], + queryFn: async () => { + const response = await getTamperConfig(agentId); + if (response.data.success && response.data.data) { + return response.data.data; + } + return null; + }, + }); + + // 保存配置 mutation + const saveMutation = useMutation({ + mutationFn: async () => { + const values = form.getFieldsValue(); + return updateTamperConfig(agentId, values.enabled, editPaths); + }, + onSuccess: () => { + message.success('配置已保存'); + queryClient.invalidateQueries({queryKey: ['tamperConfig', agentId]}); + }, + onError: (error: unknown) => { + console.error('Failed to save config:', error); + message.error(getErrorMessage(error, '配置保存失败')); + }, + }); + + // 添加路径 + const handleAddPath = () => { + if (newPath.trim() && !editPaths.includes(newPath.trim())) { + setEditPaths([...editPaths, newPath.trim()]); + setNewPath(''); + } + }; + + // 删除路径 + const handleRemovePath = (path: string) => { + setEditPaths(editPaths.filter(p => p !== path)); + }; + + // 初始化表单值 + useEffect(() => { + if (config) { + setEditPaths(config.paths || []); + form.setFieldsValue({ + enabled: config.enabled || false, + }); + } else { + setEditPaths([]); + form.setFieldsValue({ + enabled: false, + }); + } + }, [config, form]); + + return ( + + + 防篡改保护配置 +
+ } + extra={ + + } + loading={isLoading} + > + + } + /> + +
+ + + +
+ +
+
受保护的目录列表
+ + setNewPath(e.target.value)} + onPressEnter={handleAddPath} + /> + + + + {editPaths.length === 0 ? ( +
+ +

暂未配置保护目录

+
+ ) : ( + ( + } + onClick={() => handleRemovePath(path)} + />, + ]} + > + {path} + + )} + /> + )} +
+ + {config?.applyStatus && ( + + )} +
+ + ); +}; + +export default TamperProtectionConfig; diff --git a/web/src/pages/Agents/TamperProtectionEvents.tsx b/web/src/pages/Agents/TamperProtectionEvents.tsx new file mode 100644 index 0000000..b94053d --- /dev/null +++ b/web/src/pages/Agents/TamperProtectionEvents.tsx @@ -0,0 +1,183 @@ +import React, {useRef} from 'react'; +import {App, Button, Tag, Tooltip} from 'antd'; +import {FileWarning} from 'lucide-react'; +import type {ActionType, ProColumns} from '@ant-design/pro-table'; +import ProTable from '@ant-design/pro-table'; +import {useMutation} from '@tanstack/react-query'; +import {deleteTamperEvents, getTamperEvents, type TamperEvent} from '@/api/tamper'; +import {getErrorMessage} from '@/lib/utils'; +import dayjs from 'dayjs'; + +interface TamperProtectionEventsProps { + agentId: string; +} + +const TamperProtectionEvents: React.FC = ({agentId}) => { + const {message, modal} = App.useApp(); + const actionRef = useRef(null); + + // 定义表格列 + const columns: ProColumns[] = [ + { + title: '时间', + dataIndex: 'timestamp', + key: 'timestamp', + width: 180, + valueType: 'dateTime', + hideInSearch: true, + render: (_, record) => ( + + {dayjs(record.timestamp).format('YYYY-MM-DD HH:mm:ss')} + + ), + }, + { + title: '操作类型', + dataIndex: 'operation', + key: 'operation', + width: 120, + valueType: 'select', + valueEnum: { + CREATE: {text: '创建', status: 'Processing'}, + MODIFY: {text: '修改', status: 'Warning'}, + DELETE: {text: '删除', status: 'Error'}, + RENAME: {text: '重命名', status: 'Default'}, + CHMOD: {text: '权限变更', status: 'Default'}, + }, + render: (_, record) => { + const operationColors: Record = { + CREATE: 'blue', + MODIFY: 'orange', + DELETE: 'red', + RENAME: 'purple', + CHMOD: 'cyan', + }; + return ( + + {record.operation} + + ); + }, + }, + { + title: '文件路径', + dataIndex: 'path', + key: 'path', + ellipsis: true, + render: (_, record) => ( + + {record.path} + + ), + }, + { + title: '详细信息', + dataIndex: 'details', + key: 'details', + hideInSearch: true, + ellipsis: true, + render: (_, record) => ( + record.details ? ( + + {record.details} + + ) : '-' + ), + }, + ]; + + // 删除所有事件 mutation + const deleteMutation = useMutation({ + mutationFn: () => deleteTamperEvents(agentId), + onSuccess: () => { + message.success('所有事件已删除'); + actionRef.current?.reload(); + }, + onError: (error: unknown) => { + console.error('Failed to delete tamper events:', error); + message.error(getErrorMessage(error, '删除失败')); + }, + }); + + // 删除所有事件 + const handleDeleteAllEvents = () => { + modal.confirm({ + title: '确认删除', + content: '确定要删除该探针的所有防篡改事件吗?此操作不可恢复。', + okText: '确定删除', + okType: 'danger', + cancelText: '取消', + onOk: () => deleteMutation.mutate(), + }); + }; + + return ( + + columns={columns} + actionRef={actionRef} + cardBordered + request={async (params) => { + try { + const response = await getTamperEvents( + agentId, + params.current || 1, + params.pageSize || 20 + ); + if (response.data.success) { + return { + data: response.data.data.items || [], + success: true, + total: response.data.data.total || 0, + }; + } + return { + data: [], + success: false, + total: 0, + }; + } catch (error) { + console.error('Failed to load tamper events:', error); + return { + data: [], + success: false, + total: 0, + }; + } + }} + rowKey="id" + search={{ + labelWidth: 'auto', + }} + pagination={{ + pageSize: 20, + showSizeChanger: true, + showTotal: (total) => `共 ${total} 条`, + }} + dateFormatter="string" + headerTitle="文件事件" + toolBarRender={() => [ + + + , + ]} + locale={{ + emptyText: ( +
+ +

暂无防篡改事件

+

+ 请先在"保护配置"中启用保护功能并配置目录 +

+
+ ), + }} + /> + ); +}; + +export default TamperProtectionEvents; diff --git a/web/src/types/index.ts b/web/src/types/index.ts index 4808fa8..f2c685f 100644 --- a/web/src/types/index.ts +++ b/web/src/types/index.ts @@ -483,6 +483,8 @@ export interface SSHLoginConfig { agentId: string; enabled: boolean; recordFailed: boolean; + applyStatus?: string; // 配置应用状态: success/failed/pending + applyMessage?: string; // 应用结果消息 createdAt?: number; updatedAt?: number; }