diff --git a/internal/app.go b/internal/app.go index 269bf57..7e1073f 100644 --- a/internal/app.go +++ b/internal/app.go @@ -445,7 +445,7 @@ func startTrafficResetCheck(ctx context.Context, components *AppComponents, logg logger.Info("流量重置检查任务已停止") return case <-ticker.C: - if err := components.AgentService.CheckAndResetTraffic(ctx); err != nil { + if err := components.TrafficService.CheckAndResetTraffic(ctx); err != nil { logger.Error("流量重置检查失败", zap.Error(err)) } } diff --git a/internal/handler/agent_admin_handler.go b/internal/handler/agent_admin_handler.go index 2ff597a..5479de6 100644 --- a/internal/handler/agent_admin_handler.go +++ b/internal/handler/agent_admin_handler.go @@ -268,7 +268,9 @@ func (h *AgentHandler) UpdateTrafficConfig(c echo.Context) error { var req struct { Enabled bool `json:"enabled"` + Type string `json:"type"` // 流量类型: recv/send/both Limit uint64 `json:"limit"` + Used uint64 `json:"used"` // 手动设置已使用流量(可选) ResetDay int `json:"resetDay"` } @@ -277,7 +279,7 @@ func (h *AgentHandler) UpdateTrafficConfig(c echo.Context) error { } ctx := c.Request().Context() - if err := h.agentService.UpdateTrafficConfig(ctx, agentID, req.Enabled, req.Limit, req.ResetDay); err != nil { + if err := h.trafficService.UpdateTrafficConfig(ctx, agentID, req.Enabled, req.Type, req.Limit, req.ResetDay, req.Used); err != nil { return err } @@ -289,7 +291,7 @@ func (h *AgentHandler) GetTrafficStats(c echo.Context) error { agentID := c.Param("id") ctx := c.Request().Context() - stats, err := h.agentService.GetTrafficStats(ctx, agentID) + stats, err := h.trafficService.GetTrafficStats(ctx, agentID) if err != nil { return err } @@ -302,7 +304,7 @@ func (h *AgentHandler) ResetAgentTraffic(c echo.Context) error { agentID := c.Param("id") ctx := c.Request().Context() - if err := h.agentService.ResetAgentTraffic(ctx, agentID); err != nil { + if err := h.trafficService.ResetAgentTraffic(ctx, agentID); err != nil { return err } diff --git a/internal/handler/agent_handler.go b/internal/handler/agent_handler.go index 6c8781a..6595021 100644 --- a/internal/handler/agent_handler.go +++ b/internal/handler/agent_handler.go @@ -10,6 +10,7 @@ import ( type AgentHandler struct { logger *zap.Logger agentService *service.AgentService + trafficService *service.TrafficService metricService *service.MetricService monitorSvc *service.MonitorService tamperService *service.TamperService @@ -19,13 +20,14 @@ type AgentHandler struct { upgrader websocket.Upgrader } -func NewAgentHandler(logger *zap.Logger, agentService *service.AgentService, metricService *service.MetricService, - monitorService *service.MonitorService, tamperService *service.TamperService, ddnsService *service.DDNSService, - sshLoginService *service.SSHLoginService, wsManager *ws.Manager) *AgentHandler { +func NewAgentHandler(logger *zap.Logger, agentService *service.AgentService, trafficService *service.TrafficService, + metricService *service.MetricService, monitorService *service.MonitorService, tamperService *service.TamperService, + ddnsService *service.DDNSService, sshLoginService *service.SSHLoginService, wsManager *ws.Manager) *AgentHandler { h := &AgentHandler{ logger: logger, agentService: agentService, + trafficService: trafficService, metricService: metricService, monitorSvc: monitorService, tamperService: tamperService, diff --git a/internal/models/agent.go b/internal/models/agent.go index 7d25419..f8001ca 100644 --- a/internal/models/agent.go +++ b/internal/models/agent.go @@ -34,11 +34,13 @@ type Agent struct { // TrafficStatsData 流量统计数据 type TrafficStatsData struct { Enabled bool `json:"enabled"` // 是否启用 + Type string `json:"type"` // 统计类型: "recv"进站, "send"出站, "both"全部 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) + BaselineSend uint64 `json:"baselineSend"` // 当前周期流量基线(BytesSentTotal) AlertSent80 bool `json:"alertSent80"` // 是否已发送80%告警 AlertSent90 bool `json:"alertSent90"` // 是否已发送90%告警 AlertSent100 bool `json:"alertSent100"` // 是否已发送100%告警 diff --git a/internal/service/agent_service.go b/internal/service/agent_service.go index 8a96376..993b611 100644 --- a/internal/service/agent_service.go +++ b/internal/service/agent_service.go @@ -13,7 +13,6 @@ import ( "github.com/dushixiang/pika/internal/repo" "github.com/go-orz/orz" "go.uber.org/zap" - "gorm.io/datatypes" "gorm.io/gorm" ) @@ -478,222 +477,3 @@ func (s *AgentService) InitStatus(ctx context.Context) error { } return nil } - -// UpdateTrafficConfig 更新流量配置 -func (s *AgentService) UpdateTrafficConfig(ctx context.Context, agentID string, enabled bool, limit uint64, resetDay int) error { - if resetDay < 0 || resetDay > 31 { - return fmt.Errorf("重置日期必须在0-31之间") - } - - agent, err := s.AgentRepo.FindById(ctx, agentID) - if err != nil { - return err - } - - now := time.Now().UnixMilli() - stats := agent.TrafficStats.Data() - oldResetDay := stats.ResetDay - oldEnabled := stats.Enabled - - // 设置配置 - stats.Enabled = enabled - stats.Limit = limit - stats.ResetDay = resetDay - - // 如果是首次启用、禁用后重新启用,或修改重置日期,重置流量统计 - if (!oldEnabled && stats.Enabled) || (stats.Enabled && resetDay != oldResetDay) || (stats.Enabled && stats.PeriodStart == 0) { - stats.Used = 0 - stats.PeriodStart = now - stats.BaselineRecv = 0 // 下次上报时会设置正确的基线 - stats.AlertSent80 = false - stats.AlertSent90 = false - stats.AlertSent100 = false - } - - // 如果禁用流量统计,清空相关数据 - if !stats.Enabled { - stats.Used = 0 - stats.PeriodStart = 0 - 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) -} - -// GetTrafficStats 获取流量统计信息 -func (s *AgentService) GetTrafficStats(ctx context.Context, agentID string) (*TrafficStats, error) { - agent, err := s.AgentRepo.FindById(ctx, agentID) - if err != nil { - return nil, err - } - - trafficData := agent.TrafficStats.Data() - - stats := &TrafficStats{ - Enabled: trafficData.Enabled, - Limit: trafficData.Limit, - Used: trafficData.Used, - ResetDay: trafficData.ResetDay, - PeriodStart: trafficData.PeriodStart, - Alerts: TrafficAlerts{ - Sent80: trafficData.AlertSent80, - Sent90: trafficData.AlertSent90, - Sent100: trafficData.AlertSent100, - }, - } - - // 计算使用百分比 - if trafficData.Limit > 0 { - stats.UsedPercent = float64(trafficData.Used) / float64(trafficData.Limit) * 100 - if trafficData.Used < trafficData.Limit { - stats.Remaining = trafficData.Limit - trafficData.Used - } else { - stats.Remaining = 0 - } - } - - // 计算下次重置日期和剩余天数 - 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 { - stats.DaysUntilReset = 0 - } - } - - return stats, nil -} - -// ResetAgentTraffic 重置探针流量 -func (s *AgentService) ResetAgentTraffic(ctx context.Context, agentID string) error { - agent, err := s.AgentRepo.FindById(ctx, agentID) - if err != nil { - return err - } - - now := time.Now().UnixMilli() - stats := agent.TrafficStats.Data() - - 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.UpdateById(ctx, &agent) -} - -// CheckAndResetTraffic 检查并重置所有到期的探针流量(定时任务调用) -func (s *AgentService) CheckAndResetTraffic(ctx context.Context) error { - agents, err := s.AgentRepo.FindAgentsWithTrafficReset(ctx) - if err != nil { - return err - } - - now := time.Now() - resetCount := 0 - - for _, agent := range agents { - if s.shouldResetTraffic(&agent, now) { - if err := s.ResetAgentTraffic(ctx, agent.ID); err != nil { - s.logger.Error("重置探针流量失败", - zap.String("agentId", agent.ID), - zap.Error(err)) - continue - } - resetCount++ - } - } - - if resetCount > 0 { - s.logger.Info("流量重置检查完成", zap.Int("重置数量", resetCount)) - } - - return nil -} - -// shouldResetTraffic 判断是否需要重置流量 -func (s *AgentService) shouldResetTraffic(agent *models.Agent, now time.Time) bool { - stats := agent.TrafficStats.Data() - // 流量统计未启用、未配置重置日期或周期未开始时不重置 - if !stats.Enabled || stats.ResetDay == 0 || stats.PeriodStart == 0 { - return false - } - - periodStart := time.UnixMilli(stats.PeriodStart) - nextReset := calculateNextResetDate(periodStart, stats.ResetDay) - - return now.After(nextReset) || now.Equal(nextReset) -} - -// calculateNextResetDate 计算下次重置日期 -func calculateNextResetDate(periodStart time.Time, resetDay int) time.Time { - year, month, day := periodStart.Date() - location := periodStart.Location() - - // 先计算当月的重置日期 - currentMonthLastDay := time.Date(year, month+1, 0, 0, 0, 0, 0, location).Day() - currentMonthResetDay := resetDay - if resetDay > currentMonthLastDay { - currentMonthResetDay = currentMonthLastDay - } - currentMonthReset := time.Date(year, month, currentMonthResetDay, 0, 0, 0, 0, location) - - // 如果当月的重置日期还没到(在 periodStart 之后或同一天),返回当月重置日期 - if day < currentMonthResetDay || (day == currentMonthResetDay && periodStart.Before(currentMonthReset)) { - return currentMonthReset - } - - // 当月重置日期已过,计算下个月的重置日期 - nextMonth := month + 1 - nextYear := year - if nextMonth > 12 { - nextMonth = 1 - nextYear++ - } - - // 处理月末日期(如2月没有31号) - nextMonthLastDay := time.Date(nextYear, nextMonth+1, 0, 0, 0, 0, 0, location).Day() - nextMonthResetDay := resetDay - if resetDay > nextMonthLastDay { - nextMonthResetDay = nextMonthLastDay - } - - return time.Date(nextYear, nextMonth, nextMonthResetDay, 0, 0, 0, 0, location) -} - -// TrafficStats 流量统计信息 -type TrafficStats struct { - Enabled bool `json:"enabled"` - Limit uint64 `json:"limit"` - Used uint64 `json:"used"` - UsedPercent float64 `json:"usedPercent"` - Remaining uint64 `json:"remaining"` - ResetDay int `json:"resetDay"` - PeriodStart int64 `json:"periodStart"` - PeriodEnd int64 `json:"periodEnd"` - DaysUntilReset int `json:"daysUntilReset"` - Alerts TrafficAlerts `json:"alerts"` -} - -// TrafficAlerts 流量告警状态 -type TrafficAlerts struct { - Sent80 bool `json:"sent80"` - Sent90 bool `json:"sent90"` - Sent100 bool `json:"sent100"` -} diff --git a/internal/service/metric_service.go b/internal/service/metric_service.go index 4162786..c9c52bb 100644 --- a/internal/service/metric_service.go +++ b/internal/service/metric_service.go @@ -128,7 +128,7 @@ func (s *MetricService) HandleMetricData(ctx context.Context, agentID string, me TotalInterfaces: len(networkDataList), } // 更新流量统计 - if err := s.trafficService.UpdateAgentTraffic(ctx, agentID, totalRecvTotal); err != nil { + if err := s.trafficService.UpdateAgentTraffic(ctx, agentID, totalRecvTotal, totalSentTotal); err != nil { s.logger.Error("更新探针流量统计失败", zap.String("agentId", agentID), zap.Error(err)) diff --git a/internal/service/traffic_service.go b/internal/service/traffic_service.go index b221aa0..f97e22d 100644 --- a/internal/service/traffic_service.go +++ b/internal/service/traffic_service.go @@ -27,7 +27,7 @@ func NewTrafficService(logger *zap.Logger, db *gorm.DB) *TrafficService { } // UpdateAgentTraffic 更新探针流量统计(每次上报网络指标时调用) -func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string, currentRecvTotal uint64) error { +func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string, currentRecvTotal, currentSentTotal uint64) error { agent, err := s.agentRepo.FindById(ctx, agentID) if err != nil { return err @@ -41,9 +41,15 @@ func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string, return nil } + // 设置默认类型 + if stats.Type == "" { + stats.Type = "recv" // 默认只统计进站流量,向后兼容 + } + // 初始化基线(首次统计) - if stats.BaselineRecv == 0 { + if stats.BaselineRecv == 0 && stats.BaselineSend == 0 { stats.BaselineRecv = currentRecvTotal + stats.BaselineSend = currentSentTotal stats.Used = 0 if stats.PeriodStart == 0 { stats.PeriodStart = time.Now().UnixMilli() @@ -53,16 +59,38 @@ func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string, } // 检测计数器重置(探针重启) - if currentRecvTotal < stats.BaselineRecv { + recvReset := currentRecvTotal < stats.BaselineRecv + sendReset := currentSentTotal < stats.BaselineSend + + if recvReset || sendReset { s.logger.Warn("检测到流量计数器重置", zap.String("agentId", agentID), - zap.Uint64("baseline", stats.BaselineRecv), - zap.Uint64("current", currentRecvTotal)) - stats.BaselineRecv = currentRecvTotal + zap.Uint64("baselineRecv", stats.BaselineRecv), + zap.Uint64("currentRecv", currentRecvTotal), + zap.Uint64("baselineSend", stats.BaselineSend), + zap.Uint64("currentSend", currentSentTotal)) + + if recvReset { + stats.BaselineRecv = currentRecvTotal + } + if sendReset { + stats.BaselineSend = currentSentTotal + } // 保持 Used 不变,避免丢失已统计的流量 } else { - // 计算使用量 - stats.Used = currentRecvTotal - stats.BaselineRecv + // 根据配置的类型计算使用量 + switch stats.Type { + case "recv": // 只统计进站流量 + stats.Used = currentRecvTotal - stats.BaselineRecv + case "send": // 只统计出站流量 + stats.Used = currentSentTotal - stats.BaselineSend + case "both": // 统计全部流量 + recvUsed := currentRecvTotal - stats.BaselineRecv + sendUsed := currentSentTotal - stats.BaselineSend + stats.Used = recvUsed + sendUsed + default: // 默认只统计进站流量 + stats.Used = currentRecvTotal - stats.BaselineRecv + } } // 检查告警(如果配置了限额) @@ -153,3 +181,252 @@ func formatBytes(bytes uint64) string { } return fmt.Sprintf("%.2f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp]) } + +// UpdateTrafficConfig 更新流量配置 +// used: 如果大于0,则使用该值作为已使用流量;如果为0,则保持当前值或重置 +func (s *TrafficService) UpdateTrafficConfig(ctx context.Context, agentID string, enabled bool, trafficType string, limit uint64, resetDay int, used uint64) error { + if resetDay < 0 || resetDay > 31 { + return fmt.Errorf("重置日期必须在0-31之间") + } + + // 验证流量类型 + if trafficType != "" && trafficType != "recv" && trafficType != "send" && trafficType != "both" { + return fmt.Errorf("流量类型必须是 recv、send 或 both") + } + + agent, err := s.agentRepo.FindById(ctx, agentID) + if err != nil { + return err + } + + now := time.Now().UnixMilli() + stats := agent.TrafficStats.Data() + oldResetDay := stats.ResetDay + oldEnabled := stats.Enabled + oldType := stats.Type + + // 设置配置 + stats.Enabled = enabled + stats.Limit = limit + stats.ResetDay = resetDay + + // 设置流量类型(默认为 recv) + if trafficType != "" { + stats.Type = trafficType + } else if stats.Type == "" { + stats.Type = "recv" // 默认只统计进站流量 + } + + // 如果是首次启用、禁用后重新启用、修改重置日期或修改流量类型,重置流量统计 + if (!oldEnabled && stats.Enabled) || (stats.Enabled && resetDay != oldResetDay) || (stats.Enabled && stats.PeriodStart == 0) || (stats.Enabled && oldType != stats.Type && oldType != "") { + stats.Used = 0 + stats.PeriodStart = now + stats.BaselineRecv = 0 // 下次上报时会设置正确的基线 + stats.BaselineSend = 0 + stats.AlertSent80 = false + stats.AlertSent90 = false + stats.AlertSent100 = false + } + + // 如果提供了 used 参数且大于 0,则使用该值 + if used > 0 { + stats.Used = used + } + + // 如果禁用流量统计,清空相关数据 + if !stats.Enabled { + stats.Used = 0 + stats.PeriodStart = 0 + stats.BaselineRecv = 0 + stats.BaselineSend = 0 + stats.AlertSent80 = false + stats.AlertSent90 = false + stats.AlertSent100 = false + } + + agent.TrafficStats = datatypes.NewJSONType(stats) + agent.UpdatedAt = now + return s.agentRepo.UpdateById(ctx, &agent) +} + +// GetTrafficStats 获取流量统计信息 +func (s *TrafficService) GetTrafficStats(ctx context.Context, agentID string) (*TrafficStats, error) { + agent, err := s.agentRepo.FindById(ctx, agentID) + if err != nil { + return nil, err + } + + trafficData := agent.TrafficStats.Data() + + // 设置默认类型 + trafficType := trafficData.Type + if trafficType == "" { + trafficType = "recv" // 默认只统计进站流量 + } + + stats := &TrafficStats{ + Enabled: trafficData.Enabled, + Type: trafficType, + Limit: trafficData.Limit, + Used: trafficData.Used, + ResetDay: trafficData.ResetDay, + PeriodStart: trafficData.PeriodStart, + Alerts: TrafficAlerts{ + Sent80: trafficData.AlertSent80, + Sent90: trafficData.AlertSent90, + Sent100: trafficData.AlertSent100, + }, + } + + // 计算使用百分比 + if trafficData.Limit > 0 { + stats.UsedPercent = float64(trafficData.Used) / float64(trafficData.Limit) * 100 + if trafficData.Used < trafficData.Limit { + stats.Remaining = trafficData.Limit - trafficData.Used + } else { + stats.Remaining = 0 + } + } + + // 计算下次重置日期和剩余天数 + 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 { + stats.DaysUntilReset = 0 + } + } + + return stats, nil +} + +// ResetAgentTraffic 重置探针流量 +func (s *TrafficService) ResetAgentTraffic(ctx context.Context, agentID string) error { + agent, err := s.agentRepo.FindById(ctx, agentID) + if err != nil { + return err + } + + now := time.Now().UnixMilli() + stats := agent.TrafficStats.Data() + + stats.Used = 0 + stats.BaselineRecv = 0 // 下次上报时会设置正确的基线 + stats.BaselineSend = 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.UpdateById(ctx, &agent) +} + +// CheckAndResetTraffic 检查并重置所有到期的探针流量(定时任务调用) +func (s *TrafficService) CheckAndResetTraffic(ctx context.Context) error { + agents, err := s.agentRepo.FindAgentsWithTrafficReset(ctx) + if err != nil { + return err + } + + now := time.Now() + resetCount := 0 + + for _, agent := range agents { + if s.shouldResetTraffic(&agent, now) { + if err := s.ResetAgentTraffic(ctx, agent.ID); err != nil { + s.logger.Error("重置探针流量失败", + zap.String("agentId", agent.ID), + zap.Error(err)) + continue + } + resetCount++ + } + } + + if resetCount > 0 { + s.logger.Info("流量重置检查完成", zap.Int("重置数量", resetCount)) + } + + return nil +} + +// shouldResetTraffic 判断是否需要重置流量 +func (s *TrafficService) shouldResetTraffic(agent *models.Agent, now time.Time) bool { + stats := agent.TrafficStats.Data() + // 流量统计未启用、未配置重置日期或周期未开始时不重置 + if !stats.Enabled || stats.ResetDay == 0 || stats.PeriodStart == 0 { + return false + } + + periodStart := time.UnixMilli(stats.PeriodStart) + nextReset := calculateNextResetDate(periodStart, stats.ResetDay) + + return now.After(nextReset) || now.Equal(nextReset) +} + +// calculateNextResetDate 计算下次重置日期 +func calculateNextResetDate(periodStart time.Time, resetDay int) time.Time { + year, month, day := periodStart.Date() + location := periodStart.Location() + + // 先计算当月的重置日期 + currentMonthLastDay := time.Date(year, month+1, 0, 0, 0, 0, 0, location).Day() + currentMonthResetDay := resetDay + if resetDay > currentMonthLastDay { + currentMonthResetDay = currentMonthLastDay + } + currentMonthReset := time.Date(year, month, currentMonthResetDay, 0, 0, 0, 0, location) + + // 如果当月的重置日期还没到(在 periodStart 之后或同一天),返回当月重置日期 + if day < currentMonthResetDay || (day == currentMonthResetDay && periodStart.Before(currentMonthReset)) { + return currentMonthReset + } + + // 当月重置日期已过,计算下个月的重置日期 + nextMonth := month + 1 + nextYear := year + if nextMonth > 12 { + nextMonth = 1 + nextYear++ + } + + // 处理月末日期(如2月没有31号) + nextMonthLastDay := time.Date(nextYear, nextMonth+1, 0, 0, 0, 0, 0, location).Day() + nextMonthResetDay := resetDay + if resetDay > nextMonthLastDay { + nextMonthResetDay = nextMonthLastDay + } + + return time.Date(nextYear, nextMonth, nextMonthResetDay, 0, 0, 0, 0, location) +} + +// TrafficStats 流量统计信息 +type TrafficStats struct { + Enabled bool `json:"enabled"` + Type string `json:"type"` // 统计类型: recv/send/both + Limit uint64 `json:"limit"` + Used uint64 `json:"used"` + UsedPercent float64 `json:"usedPercent"` + Remaining uint64 `json:"remaining"` + ResetDay int `json:"resetDay"` + PeriodStart int64 `json:"periodStart"` + PeriodEnd int64 `json:"periodEnd"` + DaysUntilReset int `json:"daysUntilReset"` + Alerts TrafficAlerts `json:"alerts"` +} + +// TrafficAlerts 流量告警状态 +type TrafficAlerts struct { + Sent80 bool `json:"sent80"` + Sent90 bool `json:"sent90"` + Sent100 bool `json:"sent100"` +} diff --git a/internal/wire.go b/internal/wire.go index c8438e5..a34b8b2 100644 --- a/internal/wire.go +++ b/internal/wire.go @@ -74,6 +74,7 @@ type AppComponents struct { SSHLoginHandler *handler.SSHLoginHandler AgentService *service.AgentService + TrafficService *service.TrafficService MetricService *service.MetricService AlertService *service.AlertService PropertyService *service.PropertyService diff --git a/internal/wire_gen.go b/internal/wire_gen.go index 1de7d3c..2caea58 100644 --- a/internal/wire_gen.go +++ b/internal/wire_gen.go @@ -41,7 +41,7 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App tamperService := service.NewTamperService(logger, db, manager) ddnsService := service.NewDDNSService(logger, db, propertyService, manager) sshLoginService := service.NewSSHLoginService(logger, db, manager, geoIPService) - agentHandler := handler.NewAgentHandler(logger, agentService, metricService, monitorService, tamperService, ddnsService, sshLoginService, manager) + agentHandler := handler.NewAgentHandler(logger, agentService, trafficService, metricService, monitorService, tamperService, ddnsService, sshLoginService, manager) apiKeyHandler := handler.NewApiKeyHandler(logger, apiKeyService) notifier := service.NewNotifier(logger) alertService := service.NewAlertService(logger, db, propertyService, monitorService, notifier) @@ -64,6 +64,7 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App DDNSHandler: ddnsHandler, SSHLoginHandler: sshLoginHandler, AgentService: agentService, + TrafficService: trafficService, MetricService: metricService, AlertService: alertService, PropertyService: propertyService, @@ -94,6 +95,7 @@ type AppComponents struct { SSHLoginHandler *handler.SSHLoginHandler AgentService *service.AgentService + TrafficService *service.TrafficService MetricService *service.MetricService AlertService *service.AlertService PropertyService *service.PropertyService diff --git a/web/src/admin/pages/Agents/AgentDetail.tsx b/web/src/admin/pages/Agents/AgentDetail.tsx index 6f615f8..f93c108 100644 --- a/web/src/admin/pages/Agents/AgentDetail.tsx +++ b/web/src/admin/pages/Agents/AgentDetail.tsx @@ -119,7 +119,7 @@ const AgentDetail = () => { {agent?.name} {agent?.id} {agent?.hostname} - {agent?.ip} + {agent?.ip} {agent?.os} diff --git a/web/src/admin/pages/Agents/TrafficStats.tsx b/web/src/admin/pages/Agents/TrafficStats.tsx index 63306f9..aad6e34 100644 --- a/web/src/admin/pages/Agents/TrafficStats.tsx +++ b/web/src/admin/pages/Agents/TrafficStats.tsx @@ -16,6 +16,7 @@ const TrafficStats: React.FC = ({agentId}) => { const [form] = Form.useForm(); const queryClient = useQueryClient(); const [enabled, setEnabled] = useState(false); + const [editingUsed, setEditingUsed] = useState(false); // 获取流量统计 const {data: stats, isLoading} = useQuery({ @@ -32,14 +33,18 @@ const TrafficStats: React.FC = ({agentId}) => { mutationFn: async () => { const values = form.getFieldsValue(); const limitBytes = enabled ? (values.trafficLimit || 0) * 1024 * 1024 * 1024 : 0; + const usedBytes = editingUsed && values.trafficUsed ? values.trafficUsed * 1024 * 1024 * 1024 : 0; return updateTrafficConfig(agentId, { enabled: enabled, + type: values.trafficType || 'recv', limit: limitBytes, + used: usedBytes, resetDay: enabled ? (values.trafficResetDay || 0) : 0, }); }, onSuccess: () => { message.success('配置已保存'); + setEditingUsed(false); queryClient.invalidateQueries({queryKey: ['trafficStats', agentId]}); }, onError: (error: unknown) => { @@ -80,13 +85,17 @@ const TrafficStats: React.FC = ({agentId}) => { // 从服务器返回的 enabled 字段读取启用状态 setEnabled(stats.enabled); form.setFieldsValue({ + trafficType: stats.type || 'recv', trafficLimit: stats.limit > 0 ? stats.limit / (1024 * 1024 * 1024) : 0, + trafficUsed: stats.used > 0 ? stats.used / (1024 * 1024 * 1024) : 0, trafficResetDay: stats.resetDay || 0, }); } else { setEnabled(false); form.setFieldsValue({ + trafficType: 'recv', trafficLimit: 0, + trafficUsed: 0, trafficResetDay: 0, }); } @@ -217,7 +226,9 @@ const TrafficStats: React.FC = ({agentId}) => { form={form} layout="vertical" initialValues={{ + trafficType: 'recv', trafficLimit: 0, + trafficUsed: 0, trafficResetDay: 0, }} > @@ -235,6 +246,22 @@ const TrafficStats: React.FC = ({agentId}) => { {enabled && ( <> + +