v221 ⑦ public views: node name prefers the public remark

buildProbeNodeView now sources its 'name' from probeDisplayName:
the node's public remark (probe_meta.public_remark) when non-empty
after trimming, else the node name as before. Contract field name
unchanged, theme packages untouched, both audiences (anonymous and
admin frames share the builder); the panel's internal node list is
a different surface and keeps the plain node name.

Tests: display-name unit cases (remark / empty / whitespace) and
view-level assertions for both audiences; the public contract
test's seeded expectation updated to the remark (its fixture sets
public_remark='shown publicly' on node 'PubNode').
This commit is contained in:
Muse
2026-10-23 15:02:12 +00:00
parent 93c1620c15
commit a13179ed9b
3 changed files with 57 additions and 2 deletions
+3 -1
View File
@@ -323,7 +323,9 @@ func TestProbePublicContract(t *testing.T) {
assertKeys(t, keySet(t, nodeBody), anonNodeKeys)
var node map[string]any
_ = json.Unmarshal(nodeBody, &node)
if node["id"] != float64(pubID) || node["name"] != "PubNode" || node["online"] != true ||
// V2.2.1 ⑦: the public view's name is the public remark when one
// is set (the seed sets "shown publicly"), not the node name.
if node["id"] != float64(pubID) || node["name"] != "shown publicly" || node["online"] != true ||
node["month_rx"] != float64(33) || node["month_used"] != float64(77) ||
node["total_tx"] != float64(222) || node["price"] != float64(10) {
t.Fatalf("anon node values wrong: %v", node)
+14 -1
View File
@@ -268,6 +268,19 @@ func expiresInDays(expires *string, now time.Time) any {
return days
}
// probeDisplayName is the name a node carries in the public views
// (V2.2.1 ⑦): the operator's public remark when one is set — it is
// the label written for public consumption — else the node name. The
// contract field stays "name"; themes and the public API keep their
// shape, only the value's source changes. The panel's internal node
// list is a different surface and keeps the plain node name.
func probeDisplayName(name, publicRemark string) string {
if r := strings.TrimSpace(publicRemark); r != "" {
return r
}
return name
}
// buildProbeNodeView renders one node for one audience. The public
// shape is built by whitelist (hub node_view): address, hostname and
// the private remark exist only in the admin (full) rendering.
@@ -298,7 +311,7 @@ func buildProbeNodeView(src probeNodeSource, full bool) map[string]any {
view := map[string]any{
"id": a.Id,
"name": src.name,
"name": probeDisplayName(src.name, m.PublicRemark),
"country": m.CountryPin,
"group": m.GroupName,
"public_remark": m.PublicRemark,
@@ -0,0 +1,40 @@
package service
import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/web/service/probe"
)
// V2.2.1 ⑦: the public views' node name is the operator's public
// remark when one is set, and the node name otherwise — the panel's
// own list (a different surface) is untouched.
func TestProbeDisplayNamePrefersPublicRemark(t *testing.T) {
if got := probeDisplayName("node-name", "东京 01"); got != "东京 01" {
t.Fatalf("with remark = %q, want the remark", got)
}
if got := probeDisplayName("node-name", ""); got != "node-name" {
t.Fatalf("empty remark = %q, want node name", got)
}
if got := probeDisplayName("node-name", " "); got != "node-name" {
t.Fatalf("whitespace-only remark = %q, want node name", got)
}
}
func TestBuildProbeNodeViewNameSource(t *testing.T) {
withRemark := probeNodeSource{
name: "internal-name",
meta: probe.Meta{PublicRemark: "公开名", Public: true},
}
if got := buildProbeNodeView(withRemark, false)["name"]; got != "公开名" {
t.Fatalf("anonymous view name = %v, want the public remark", got)
}
if got := buildProbeNodeView(withRemark, true)["name"]; got != "公开名" {
t.Fatalf("admin view name = %v, want the public remark", got)
}
without := probeNodeSource{name: "internal-name", meta: probe.Meta{Public: true}}
if got := buildProbeNodeView(without, false)["name"]; got != "internal-name" {
t.Fatalf("view name without remark = %v, want the node name", got)
}
}