fix(nodes): keep cloned nodes (shared panelGuid) in separate attribution buckets

#4983 keys online/inbound attribution by panelGuid, assuming it is globally unique. Cloned node servers ship an identical panelGuid in their copied settings, so the master collapsed several physical nodes into one bucket: GetMergedNodeTrees merged their online sets under one key and every inbound on those nodes (same origin_node_guid) read that merged set, so the inbound page showed online cross-attributed and counts inflated.

Fall back to the node-unique synthNodeGuid(node.Id) whenever a node's panelGuid is shared by another of the master's direct nodes. Applied consistently at originGuidFor (origin_node_guid write), the online-tree key plus a self-key remap for nodes that report a GUID-keyed tree, effectiveNodeGuid, and recountByGuid's inbound bucketing. sharedNodeGuids computes the collision set. Online now works without node changes; making panelGuids unique restores real-GUID identity and also fixes GUID-keyed IP attribution.
This commit is contained in:
MHSanaei
2026-06-22 14:39:15 +02:00
parent 4854f9c1b8
commit af941798c6
4 changed files with 163 additions and 18 deletions
+9 -1
View File
@@ -174,6 +174,7 @@ func (s *NodeService) recountByGuid(nodes []*model.Node, selfGuid string) {
if err := db.Table("inbounds").Select("id, node_id, origin_node_guid").Scan(&ibRows).Error; err != nil {
return
}
shared := sharedNodeGuids(nodes)
effByInbound := make(map[int]string, len(ibRows))
inboundCountByGuid := make(map[string]int)
ids := make([]int, 0, len(ibRows))
@@ -185,6 +186,13 @@ func (s *NodeService) recountByGuid(nodes []*model.Node, selfGuid string) {
} else {
guid = selfGuid
}
} else if r.NodeID != nil {
// Origin still holds a GUID two direct nodes share (cloned server,
// not yet re-attributed): bucket under the hosting node's unique id
// so the clones don't merge.
if _, dup := shared[guid]; dup {
guid = synthNodeGuid(*r.NodeID)
}
}
effByInbound[r.Id] = guid
inboundCountByGuid[guid]++
@@ -222,7 +230,7 @@ func (s *NodeService) recountByGuid(nodes []*model.Node, selfGuid string) {
onlineByGuid := s.onlineEmailsByGuid()
for _, n := range nodes {
guid := effectiveNodeGuid(n)
guid := effectiveNodeGuid(n, shared)
n.InboundCount = inboundCountByGuid[guid]
n.OnlineCount = len(onlineByGuid[guid])
n.DepletedCount = depletedByGuid[guid]