fix(panel-go): random ports roll once — sticky across edits (round-2 #7)

NormalizeNode re-selected ports whenever the request merely CONTAINED
port/serverId keys; the browser form always submits them (port=null in
random mode), so every save re-rolled both entry and exit ports. Now an
assigned port only moves on an explicit addressing change: mode switch to
random, a different fixed port value, or a server move where the port is
taken (fixed mode surfaces the 409). random->fixed pins the current port.

Regression: portlock_test.go (UI-shape payload saved twice, rename-only,
mode switches, fixed change/conflict, server move free/taken).
Golden re-captured: diff confined to nodePatchUiStyle/nodePatchJobs/
nodesAfterPatch/backup port leaves returning to the seed ports.
This commit is contained in:
aaaamoooo
2026-10-21 11:17:59 +08:00
parent 85763ebdb7
commit 3de4c4945f
3 changed files with 248 additions and 22 deletions
+18 -18
View File
@@ -1,6 +1,6 @@
{
"meta": {
"capturedAt": "2026-10-20T20:44:51.343Z",
"capturedAt": "2026-10-20T20:48:49.288Z",
"note": "Go panel golden baseline; see README.md in this directory."
},
"sections": {
@@ -1312,7 +1312,7 @@
"createdAt": "<ts>",
"entryReady": true,
"error": null,
"exitPort": "<port>#6",
"exitPort": "<port>#1",
"exitPortMode": "random",
"exitProtocol": "shadowsocks-2022-aes256",
"exitReady": true,
@@ -1329,7 +1329,7 @@
"name": "N1-renamed",
"networkMode": "ipv4",
"pendingDelete": false,
"port": "<port>#7",
"port": "<port>#2",
"portMode": "random",
"protocol": "vless-reality-vision",
"reality": {
@@ -1388,7 +1388,7 @@
"inbounds": [
{
"listen": "198.18.187.62",
"listen_port": "<port>#7",
"listen_port": "<port>#2",
"tag": "ng-<nod>#2-in",
"tls": {
"enabled": true,
@@ -1432,7 +1432,7 @@
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
"server": "exit.example.com",
"server_port": "<port>#6",
"server_port": "<port>#1",
"tag": "ng-<nod>#2-exit",
"type": "shadowsocks"
}
@@ -1476,7 +1476,7 @@
"inbounds": [
{
"listen": "198.18.187.62",
"listen_port": "<port>#7",
"listen_port": "<port>#2",
"tag": "ng-<nod>#2-in",
"tls": {
"enabled": true,
@@ -1520,7 +1520,7 @@
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
"server": "exit.example.com",
"server_port": "<port>#6",
"server_port": "<port>#1",
"tag": "ng-<nod>#2-exit",
"type": "shadowsocks"
}
@@ -1563,7 +1563,7 @@
"inbounds": [
{
"listen": "198.18.187.61",
"listen_port": "<port>#6",
"listen_port": "<port>#1",
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
"tag": "ng-<nod>#2-exit-in",
@@ -1614,7 +1614,7 @@
"inbounds": [
{
"listen": "198.18.187.61",
"listen_port": "<port>#6",
"listen_port": "<port>#1",
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
"tag": "ng-<nod>#2-exit-in",
@@ -1655,7 +1655,7 @@
"body": {
"nodeClients": [
{
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#7?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
"createdAt": "<ts>",
"customerId": "<cus>#1",
"id": "<ncl>#1",
@@ -1664,7 +1664,7 @@
"updatedAt": "<ts>"
},
{
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#7?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
"createdAt": "<ts>",
"customerId": "<cus>#2",
"id": "<ncl>#2",
@@ -1701,7 +1701,7 @@
"createdAt": "<ts>",
"entryReady": true,
"error": null,
"exitPort": "<port>#6",
"exitPort": "<port>#1",
"exitPortMode": "random",
"exitProtocol": "shadowsocks-2022-aes256",
"exitReady": true,
@@ -1722,7 +1722,7 @@
"<cus>#1"
],
"pendingDelete": false,
"port": "<port>#7",
"port": "<port>#2",
"portMode": "random",
"protocol": "vless-reality-vision",
"reality": {
@@ -2583,7 +2583,7 @@
"inbounds": [
{
"listen": "198.18.187.62",
"listen_port": "<port>#7",
"listen_port": "<port>#2",
"tag": "ng-<nod>#2-in",
"tls": {
"enabled": true,
@@ -2627,7 +2627,7 @@
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
"server": "exit.example.com",
"server_port": "<port>#6",
"server_port": "<port>#1",
"tag": "ng-<nod>#2-exit",
"type": "shadowsocks"
}
@@ -2665,7 +2665,7 @@
"inbounds": [
{
"listen": "198.18.187.61",
"listen_port": "<port>#6",
"listen_port": "<port>#1",
"method": "2022-blake3-aes-256-gcm",
"password": "<secret>#11",
"tag": "ng-<nod>#2-exit-in",
@@ -2780,7 +2780,7 @@
"password": "<secret>#11",
"socksUser": "ng_5b8dd6b991"
},
"exitPort": "<port>#6",
"exitPort": "<port>#1",
"exitPortMode": "random",
"exitProtocol": "shadowsocks-2022-aes256",
"exitReady": true,
@@ -2797,7 +2797,7 @@
"name": "N1-renamed",
"networkMode": "ipv4",
"pendingDelete": false,
"port": "<port>#7",
"port": "<port>#2",
"portMode": "random",
"protocol": "vless-reality-vision",
"reality": {