package service import ( "time" "github.com/mhsanaei/3x-ui/v3/internal/database" "github.com/mhsanaei/3x-ui/v3/internal/database/model" ) // ProbeCoverageRow is one agent's cf-probe retirement readiness // (item 7 B6): whether its heartbeats carry the probe block the // public page is fed from. The retirement checklist in the settings // tab renders from these rows — the operator verifies every node is // covered by the in-panel probe before standing the old cf-probe // fleet down, node by node. type ProbeCoverageRow struct { Name string `json:"name"` Online bool `json:"online"` HasProbe bool `json:"hasProbe"` ProbeAt int64 `json:"probeAt"` // unix ms of the last probe-carrying heartbeat, 0 = never } // ProbeAgentCoverage lists every agent with its probe coverage. The // display name follows the public view's rule (the bound node's name // first, the agent's own name otherwise); online is the public // view's 150s freshness window. func ProbeAgentCoverage() []ProbeCoverageRow { db := database.GetDB() var agents []model.AgentNode if err := db.Find(&agents).Error; err != nil { return []ProbeCoverageRow{} } nodeNames := map[int]string{} var nodes []model.Node if err := db.Find(&nodes).Error; err == nil { for _, n := range nodes { nodeNames[n.Id] = n.Name } } nowMs := time.Now().UnixMilli() out := make([]ProbeCoverageRow, 0, len(agents)) for _, a := range agents { name := a.Name if a.NodeID != nil { if bound, ok := nodeNames[*a.NodeID]; ok && bound != "" { name = bound } } out = append(out, ProbeCoverageRow{ Name: name, Online: a.LastSeen > 0 && nowMs-a.LastSeen <= 150_000, HasProbe: a.ProbeAt > 0, ProbeAt: a.ProbeAt, }) } return out }