Scope B (Windows agent): - probe.go split into shared core + probe_linux.go (verbatim Linux sampler, build-tagged) + probe_windows.go (Win32 sampler: GetSystemTimes CPU diff, GlobalMemoryStatusEx, fixed-drive df totals, GetIfTable2 counted set, MachineGuid boot identity, registry facts; load and TCP/UDP counts stay zero - x/sys v0.47.0 exports no wrapper) - update intents are acknowledged and skipped on Windows (the release assets are Linux binaries; Windows nodes update via install-agent.ps1) - cmd/xui-agent-windows: SCM service host (svc.Run + mgr install/ uninstall/start/stop) mirroring main.go's agent lifecycle wiring - install-agent.ps1: downloads the exe, writes probeOnly agent.json, registers and starts the service, rerun-safe Scope C (version display): - config.GetAgentDisplayVersion (and the master getter) compose from the bare number so a V-form injection no longer doubles the letter (AV2.1.0 -> A2.1.0) - node list view repairs stored AV-prefixed reports at assembly so deployed agents read uniformly without an update Scope D (production fix): - share-link reality sni: when the inbound has a sing-box fragment, sni is the fragment's tls.server_name - the only name the sing-box REALITY server serves. The previous rule picked a uniformly random serverNames entry per generation, which xray tolerated but sing-box refuses as an invalid connection. sid keeps its random pick: the converter carries the whole shortIds list into the fragment, so every listed sid is served.
96 lines
3.4 KiB
Go
96 lines
3.4 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
// The panel-version cell must read as the bare A-form across the
|
|
// fleet: pre-fix agents report the doubled "AV2.1.0" and are repaired
|
|
// at view assembly; legacy build versions keep their "agent " prefix.
|
|
func TestAgentVersionCell(t *testing.T) {
|
|
cases := map[string]string{
|
|
"A2.1.0": "A2.1.0",
|
|
"A1.0.0": "A1.0.0",
|
|
"AV2.1.0": "A2.1.0",
|
|
"AV2.0.1": "A2.0.1",
|
|
"agent AV2.0.3": "A2.0.3",
|
|
"3.7.0-agent.1": "agent 3.7.0-agent.1",
|
|
"agent A2.1.0": "A2.1.0",
|
|
" A2.1.0 ": "A2.1.0",
|
|
"some-dev-build": "agent some-dev-build",
|
|
}
|
|
for in, want := range cases {
|
|
if got := agentVersionCell(in); got != want {
|
|
t.Errorf("agentVersionCell(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// P13: annotateAgentTelemetry overlays the agent-reported heartbeat
|
|
// telemetry onto agent node views only — panel version becomes
|
|
// "agent <version>", xray version/uptime/latency/CPU/mem/IPs take the
|
|
// reported values. Traditional rows and never-reporting agents are
|
|
// left untouched.
|
|
func TestAnnotateAgentTelemetry(t *testing.T) {
|
|
setupConflictDB(t)
|
|
db := database.GetDB()
|
|
|
|
agentNode := &model.Node{Id: 7401, Name: "agent-node", Address: ""}
|
|
tradNode := &model.Node{Id: 7402, Name: "trad-node", Address: "198.51.100.2", PanelVersion: "3.4.2", XrayVersion: "26.6.27"}
|
|
silentNode := &model.Node{Id: 7403, Name: "silent-agent-node", Address: ""}
|
|
for _, n := range []*model.Node{agentNode, tradNode, silentNode} {
|
|
if err := db.Create(n).Error; err != nil {
|
|
t.Fatalf("create node %d: %v", n.Id, err)
|
|
}
|
|
}
|
|
reporting := &model.AgentNode{
|
|
TokenHash: "hash-reporting",
|
|
Name: "agent-node", NodeID: &agentNode.Id,
|
|
AgentVersion: "3.7.0-agent.1", XrayVersion: "26.6.27",
|
|
SingboxVersion: "1.13.21",
|
|
UptimeSecs: 90061, LatencyMs: 12, CpuPct: 3.5, MemPct: 41.2,
|
|
IPv4: "203.0.113.7", IPv6: "2001:db8::7", TelemetryAt: 1700000000000,
|
|
}
|
|
silent := &model.AgentNode{TokenHash: "hash-silent", Name: "silent-agent-node", NodeID: &silentNode.Id}
|
|
// The traditional node has no agent row at all — by the codebase's
|
|
// definition (any binding makes a node agent-managed) it must pass
|
|
// through the overlay untouched.
|
|
for _, ag := range []*model.AgentNode{reporting, silent} {
|
|
if err := db.Create(ag).Error; err != nil {
|
|
t.Fatalf("create agent row: %v", err)
|
|
}
|
|
}
|
|
|
|
views := toNodeViews([]*model.Node{agentNode, tradNode, silentNode})
|
|
annotateNodeTypes(views)
|
|
annotateAgentTelemetry(views)
|
|
byID := map[int]*NodeView{}
|
|
for _, v := range views {
|
|
byID[v.Id] = v
|
|
}
|
|
|
|
av := byID[7401]
|
|
if av.NodeType != "agent" {
|
|
t.Fatalf("node 7401 type = %q, want agent", av.NodeType)
|
|
}
|
|
if av.XrayVersion != "26.6.27" || av.SingboxVersion != "1.13.21" || av.PanelVersion != "agent 3.7.0-agent.1" ||
|
|
av.UptimeSecs != 90061 || av.LatencyMs != 12 ||
|
|
av.CpuPct != 3.5 || av.MemPct != 41.2 ||
|
|
av.Ipv4 != "203.0.113.7" || av.Ipv6 != "2001:db8::7" {
|
|
t.Fatalf("agent view not overlaid: %+v", av)
|
|
}
|
|
|
|
tv := byID[7402]
|
|
if tv.PanelVersion != "3.4.2" || tv.XrayVersion != "26.6.27" || tv.Ipv4 != "" || tv.Ipv6 != "" {
|
|
t.Fatalf("traditional view mutated: %+v", tv)
|
|
}
|
|
|
|
sv := byID[7403]
|
|
if sv.XrayVersion != "" || sv.PanelVersion != "" || sv.Ipv4 != "" || sv.UptimeSecs != 0 {
|
|
t.Fatalf("silent agent view should stay empty: %+v", sv)
|
|
}
|
|
}
|