mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-03 00:47:14 +00:00
fix(online): scope per-inbound online to inbounds that carried traffic
Multi-inbound clients showed online on every inbound they were attached to. Xray's user-level traffic stat aggregates across all inbounds a client belongs to, so the email signal alone can't say which inbound was used. Pair it with the inbound-level traffic signal under the same 20s grace and gate the per-inbound rollup on it: a client only shows online on inbounds that actually moved bytes this window. Remote nodes report no per-inbound activity and stay ungated (no regression). Adds GetActiveInboundsByNode, the activeInbounds WS field and POST /panel/api/clients/activeInbounds. Fixes #4859
This commit is contained in:
+31
-5
@@ -24,7 +24,7 @@ func assertSameSet(t *testing.T, label string, got, want []string) {
|
||||
// client online on one node must not be reported online on any other node.
|
||||
func TestGetOnlineClientsByNodeScopesPerNode(t *testing.T) {
|
||||
p := newOnlineTestProcess()
|
||||
p.RefreshLocalOnline([]string{"user1"}, 1000, 20000)
|
||||
p.RefreshLocalOnline([]string{"user1"}, nil, 1000, 20000)
|
||||
p.SetNodeOnlineClients(3, []string{"user1", "user2"})
|
||||
p.SetNodeOnlineClients(5, []string{"user3"})
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestGetOnlineClientsByNodeOmitsEmptyGroups(t *testing.T) {
|
||||
// client-centric / total-count views) still merges every node and dedupes.
|
||||
func TestGetOnlineClientsUnionDedupes(t *testing.T) {
|
||||
p := newOnlineTestProcess()
|
||||
p.RefreshLocalOnline([]string{"user1"}, 1000, 20000)
|
||||
p.RefreshLocalOnline([]string{"user1"}, nil, 1000, 20000)
|
||||
p.SetNodeOnlineClients(3, []string{"user1", "user2"})
|
||||
|
||||
assertSameSet(t, "union", p.GetOnlineClients(), []string{"user1", "user2"})
|
||||
@@ -76,18 +76,18 @@ func TestRefreshLocalOnlineGraceWindow(t *testing.T) {
|
||||
p := newOnlineTestProcess()
|
||||
const grace = 20000
|
||||
|
||||
p.RefreshLocalOnline([]string{"user1"}, 1000, grace)
|
||||
p.RefreshLocalOnline([]string{"user1"}, nil, 1000, grace)
|
||||
if got := p.GetOnlineClientsByNode()[localNodeKey]; !slices.Contains(got, "user1") {
|
||||
t.Fatalf("user1 should be online right after activity, got %v", got)
|
||||
}
|
||||
|
||||
p.RefreshLocalOnline([]string{"user2"}, 11000, grace)
|
||||
p.RefreshLocalOnline([]string{"user2"}, nil, 11000, grace)
|
||||
got := p.GetOnlineClientsByNode()[localNodeKey]
|
||||
if !slices.Contains(got, "user1") || !slices.Contains(got, "user2") {
|
||||
t.Fatalf("both within grace window, got %v", got)
|
||||
}
|
||||
|
||||
p.RefreshLocalOnline(nil, 22000, grace)
|
||||
p.RefreshLocalOnline(nil, nil, 22000, grace)
|
||||
got = p.GetOnlineClientsByNode()[localNodeKey]
|
||||
if slices.Contains(got, "user1") {
|
||||
t.Errorf("user1 (idle 21s, past grace) should have aged out, got %v", got)
|
||||
@@ -97,6 +97,32 @@ func TestRefreshLocalOnlineGraceWindow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetActiveInboundsByNodeTracksGraceWindow pins the fix for issue #4859: a
|
||||
// multi-inbound client must only count as online on inbounds that actually
|
||||
// carried traffic. The active-inbound signal honours the same grace window as
|
||||
// the online-email signal, and only this panel's tags report under key 0.
|
||||
func TestGetActiveInboundsByNodeTracksGraceWindow(t *testing.T) {
|
||||
p := newOnlineTestProcess()
|
||||
const grace = 20000
|
||||
|
||||
p.RefreshLocalOnline([]string{"alice"}, []string{"inbound-a"}, 1000, grace)
|
||||
got := p.GetActiveInboundsByNode()[localNodeKey]
|
||||
assertSameSet(t, "active after first poll", got, []string{"inbound-a"})
|
||||
|
||||
p.RefreshLocalOnline([]string{"alice"}, []string{"inbound-b"}, 11000, grace)
|
||||
got = p.GetActiveInboundsByNode()[localNodeKey]
|
||||
assertSameSet(t, "both within grace", got, []string{"inbound-a", "inbound-b"})
|
||||
|
||||
p.RefreshLocalOnline(nil, nil, 22000, grace)
|
||||
got = p.GetActiveInboundsByNode()[localNodeKey]
|
||||
assertSameSet(t, "inbound-a (idle 21s, past grace) aged out, inbound-b kept", got, []string{"inbound-b"})
|
||||
|
||||
p.RefreshLocalOnline(nil, nil, 40000, grace)
|
||||
if _, ok := p.GetActiveInboundsByNode()[localNodeKey]; ok {
|
||||
t.Errorf("all inbounds idle past grace, key 0 should be absent: %v", p.GetActiveInboundsByNode())
|
||||
}
|
||||
}
|
||||
|
||||
// TestClearNodeOnlineClientsDropsNode mirrors a failed node probe: the node's
|
||||
// clients must disappear from the per-node map immediately.
|
||||
func TestClearNodeOnlineClientsDropsNode(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user