Files
3x-ui-agent/internal/web/service/probe_coverage.go
T

58 lines
1.7 KiB
Go

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
}