From 448e8c97c2f1ecf591da466b9dd9db1ff9f62f3b Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 14 Jul 2026 23:32:03 +0200 Subject: [PATCH] fix(node): match prefixed central tags when filtering a selected-mode node snapshot FilterNodeSnapshot compared a node snapshot's inbound tags against the raw selected-tag list with an exact match, while its two siblings (SnapshotHasUnadoptedInbounds and the reconcile tagToCentral map) expand each selected tag to both its bare node-side form and its n- prefixed central form. A panel-created node inbound is recorded in the selected list under the central prefixed tag but reported by the node under the bare tag, so the exact match dropped it from every snapshot and the orphan sweep then deleted its central row one tick after creation. Expand the allowed set with the same prefix flip the siblings use. --- internal/web/service/node.go | 10 +++++++++- internal/web/service/node_test.go | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/internal/web/service/node.go b/internal/web/service/node.go index 2d59d0e9e..6fde81f50 100644 --- a/internal/web/service/node.go +++ b/internal/web/service/node.go @@ -533,9 +533,17 @@ func FilterNodeSnapshot(n *model.Node, snap *runtime.TrafficSnapshot) { if n == nil || snap == nil || n.InboundSyncMode != "selected" { return } - allowed := make(map[string]struct{}, len(n.InboundTags)) + prefix := nodeTagPrefix(&n.Id) + allowed := make(map[string]struct{}, len(n.InboundTags)*2) for _, tag := range n.InboundTags { allowed[tag] = struct{}{} + if prefix != "" { + if stripped, found := strings.CutPrefix(tag, prefix); found { + allowed[stripped] = struct{}{} + } else { + allowed[prefix+tag] = struct{}{} + } + } } filtered := make([]*model.Inbound, 0, len(snap.Inbounds)) for _, inbound := range snap.Inbounds { diff --git a/internal/web/service/node_test.go b/internal/web/service/node_test.go index 23f74fc0d..e3f255a26 100644 --- a/internal/web/service/node_test.go +++ b/internal/web/service/node_test.go @@ -212,3 +212,26 @@ func TestFilterNodeSnapshot(t *testing.T) { t.Fatalf("empty selection kept %d inbounds, want 0", len(none.Inbounds)) } } + +func TestFilterNodeSnapshotMatchesPrefixedSelectedTag(t *testing.T) { + snap := &runtime.TrafficSnapshot{Inbounds: []*model.Inbound{ + {Tag: "in-100-tcp"}, + {Tag: "in-443-tcp"}, + }} + FilterNodeSnapshot(&model.Node{ + Id: 5, + InboundSyncMode: "selected", + InboundTags: []string{"in-100-tcp", "n5-in-443-tcp"}, + }, snap) + + kept := make(map[string]bool, len(snap.Inbounds)) + for _, ib := range snap.Inbounds { + kept[ib.Tag] = true + } + if !kept["in-443-tcp"] { + t.Fatalf("node-side tag in-443-tcp filtered out despite the prefixed central tag being selected; kept=%v", kept) + } + if !kept["in-100-tcp"] { + t.Fatalf("bare selected tag in-100-tcp was dropped; kept=%v", kept) + } +}