Skip to content

Commit 2ba0963

Browse files
committed
Merge branch 'dev' into main
2 parents 164cb53 + 8613ab4 commit 2ba0963

9 files changed

Lines changed: 105 additions & 7 deletions

File tree

src/docs/openapi.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ function addCommonSchemas(target) {
4444
xray: { type: 'object', description: 'Xray-specific settings when `type=xray`.' },
4545
cascadeRole: { type: 'string', enum: ['standalone', 'portal', 'bridge'], default: 'standalone' },
4646
country: { type: 'string', example: 'DE' },
47+
comment: { type: 'string', maxLength: 500, example: 'Hetzner FSN1 — backup node', description: 'Free-form operator note shown in panel UI.' },
4748
rankingCoefficient: { type: 'number', example: 1 },
4849
},
4950
},
@@ -67,6 +68,7 @@ function addCommonSchemas(target) {
6768
xray: { type: 'object' },
6869
cascadeRole: { type: 'string' },
6970
country: { type: 'string' },
71+
comment: { type: 'string', maxLength: 500 },
7072
initScript: { type: 'string' },
7173
},
7274
},
@@ -858,6 +860,7 @@ These endpoints are not under \`/api\` and are not part of this specification:
858860
properties: {
859861
_id: { type: 'string', example: '64a1b2c3d4e5f6a7b8c9d0e1' },
860862
name: { type: 'string', example: 'Germany' },
863+
comment: { type: 'string', example: 'Hetzner FSN1 — backup node', description: 'Free-form operator note shown in panel UI.' },
861864
ip: { type: 'string', example: '1.2.3.4' },
862865
domain: { type: 'string', example: 'de.example.com' },
863866
port: { type: 'integer', example: 443 },

src/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@
174174
"flag": "Flag (emoji)",
175175
"flagPlaceholder": "🇩🇪",
176176
"flagHint": "Country flag emoji",
177+
"comment": "Comment",
178+
"commentPlaceholder": "Provider, location, notes…",
179+
"commentHint": "Free-form note shown in the node list and dashboard (up to 500 characters)",
177180
"port": "Main Port",
178181
"portRange": "Port Hopping Range",
179182
"portRangeHint": "Clients will connect to these ports",

src/locales/ru.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@
174174
"flag": "Флаг (эмодзи)",
175175
"flagPlaceholder": "🇩🇪",
176176
"flagHint": "Эмодзи флага страны",
177+
"comment": "Комментарий",
178+
"commentPlaceholder": "Провайдер, локация, заметки…",
179+
"commentHint": "Произвольная заметка, показывается в списке нод и на дашборде (до 500 символов)",
177180
"port": "Основной порт",
178181
"portRange": "Port Hopping диапазон",
179182
"portRangeHint": "Клиенты будут подключаться на эти порты",

src/models/hyNodeModel.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ const hyNodeSchema = new mongoose.Schema({
215215

216216
name: { type: String, required: true },
217217
flag: { type: String, default: '' },
218+
// Free-form operator note displayed in the node list and dashboard.
219+
// Trimmed and capped to avoid abuse / excessive payload size.
220+
comment: { type: String, default: '', trim: true, maxlength: 500 },
218221
ip: { type: String, required: true },
219222
domain: { type: String, default: '' },
220223
sni: { type: String, default: '' },

src/routes/nodes.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ router.post('/', requireScope('nodes:write'), async (req, res) => {
9696
const {
9797
name, ip, domain, sni, port, portRange, statsPort,
9898
groups, ssh, paths, settings, rankingCoefficient,
99-
type, xray, cascadeRole, country,
99+
type, xray, cascadeRole, country, comment,
100100
hopInterval, acme, masquerade, bandwidth,
101101
ignoreClientBandwidth, speedTest, disableUDP,
102102
udpIdleTimeout, sniff, quic, resolver, acl,
@@ -151,6 +151,7 @@ router.post('/', requireScope('nodes:write'), async (req, res) => {
151151
rankingCoefficient: rankingCoefficient || 1.0,
152152
cascadeRole: cascadeRole || 'standalone',
153153
country: country || '',
154+
comment: typeof comment === 'string' ? comment.trim().slice(0, 500) : '',
154155
initScript: req.body.initScript || '',
155156
active: true,
156157
status: 'offline',
@@ -189,7 +190,7 @@ router.put('/:id', requireScope('nodes:write'), async (req, res) => {
189190
const allowedUpdates = [
190191
'name', 'domain', 'sni', 'port', 'portRange', 'statsPort',
191192
'groups', 'ssh', 'paths', 'settings', 'active', 'rankingCoefficient',
192-
'type', 'xray', 'cascadeRole', 'country',
193+
'type', 'xray', 'cascadeRole', 'country', 'comment',
193194
'hopInterval', 'acme', 'masquerade', 'bandwidth',
194195
'ignoreClientBandwidth', 'speedTest', 'disableUDP',
195196
'udpIdleTimeout', 'sniff', 'quic', 'resolver', 'acl',
@@ -199,9 +200,15 @@ router.put('/:id', requireScope('nodes:write'), async (req, res) => {
199200
const updates = {};
200201
for (const key of allowedUpdates) {
201202
if (req.body[key] !== undefined) {
202-
updates[key] = key === 'ssh'
203-
? cryptoService.encryptSshCredentials(req.body[key])
204-
: req.body[key];
203+
if (key === 'ssh') {
204+
updates[key] = cryptoService.encryptSshCredentials(req.body[key]);
205+
} else if (key === 'comment') {
206+
updates[key] = typeof req.body[key] === 'string'
207+
? req.body[key].trim().slice(0, 500)
208+
: '';
209+
} else {
210+
updates[key] = req.body[key];
211+
}
205212
}
206213
}
207214

src/routes/panel/nodes.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ router.get('/', async (req, res) => {
7777
const { usersTotal, usersEnabled, nodesTotal, nodesOnline, trafficStats } = counts;
7878

7979
const nodes = await HyNode.find({ active: true })
80-
.select('name ip status onlineUsers maxOnlineUsers groups traffic type flag rankingCoefficient')
80+
.select('name ip status onlineUsers maxOnlineUsers groups traffic type flag rankingCoefficient comment')
8181
.populate('groups', 'name color')
8282
.sort({ rankingCoefficient: 1, name: 1 });
8383

@@ -300,6 +300,9 @@ router.post('/nodes', async (req, res) => {
300300
customConfig: req.body.customConfig || '',
301301
cascadeRole: req.body.cascadeRole || 'standalone',
302302
country: req.body.country || '',
303+
comment: typeof req.body.comment === 'string'
304+
? req.body.comment.trim().slice(0, 500)
305+
: '',
303306
initScript: req.body.initScript || '',
304307
obfs: {
305308
type: req.body['obfs.type'] || '',
@@ -541,6 +544,9 @@ router.post('/nodes/:id', async (req, res) => {
541544
flag: req.body.flag || '',
542545
cascadeRole: req.body.cascadeRole || 'standalone',
543546
country: req.body.country || '',
547+
comment: typeof req.body.comment === 'string'
548+
? req.body.comment.trim().slice(0, 500)
549+
: '',
544550
initScript: req.body.initScript || '',
545551
'ssh.port': parseInt(req.body['ssh.port']) || 22,
546552
'ssh.username': req.body['ssh.username'] || 'root',

views/dashboard.ejs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@
9494
style="width:14px;height:14px;vertical-align:middle;opacity:0.7;">
9595
</span>
9696
<code class="node-ip"><%= node.ip %></code>
97+
<% if (node.comment) { %>
98+
<span class="node-comment" title="<%= node.comment %>"><i class="ti ti-message-circle"></i> <%= node.comment %></span>
99+
<% } %>
97100
</div>
98101
</td>
99102
<td>
@@ -149,6 +152,11 @@
149152
style="width:13px;height:13px;vertical-align:middle;opacity:0.7;">
150153
</span>
151154
<code><%= node.ip %></code>
155+
<% if (node.comment) { %>
156+
<div class="node-comment node-comment-mobile" title="<%= node.comment %>">
157+
<i class="ti ti-message-circle"></i> <%= node.comment %>
158+
</div>
159+
<% } %>
152160
</div>
153161
<span class="status-indicator status-<%= node.status %>">
154162
<span class="status-dot"></span>
@@ -389,6 +397,30 @@
389397
color: var(--text-muted);
390398
font-size: 0.85em;
391399
}
400+
.node-comment {
401+
display: inline-block;
402+
max-width: 280px;
403+
margin-left: 6px;
404+
color: var(--text-muted);
405+
font-size: 11px;
406+
line-height: 1.3;
407+
white-space: nowrap;
408+
overflow: hidden;
409+
text-overflow: ellipsis;
410+
vertical-align: middle;
411+
}
412+
.node-comment .ti {
413+
font-size: 12px;
414+
opacity: 0.7;
415+
vertical-align: -1px;
416+
}
417+
.node-comment-mobile {
418+
display: block;
419+
margin: 4px 0 0;
420+
max-width: 100%;
421+
white-space: normal;
422+
word-break: break-word;
423+
}
392424
</style>
393425
394426
<script>

views/nodes.ejs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
<% if (node.domain) { %>
119119
<br><small class="text-muted"><%= node.domain %></small>
120120
<% } %>
121+
<% if (node.comment) { %>
122+
<br><small class="node-comment" title="<%= node.comment %>"><i class="ti ti-message-circle"></i> <%= node.comment %></small>
123+
<% } %>
121124
</div>
122125
</div>
123126
</td>
@@ -227,7 +230,13 @@
227230
<%= node.status %>
228231
</span>
229232
</div>
230-
233+
234+
<% if (node.comment) { %>
235+
<div class="node-comment node-comment-mobile" title="<%= node.comment %>">
236+
<i class="ti ti-message-circle"></i> <%= node.comment %>
237+
</div>
238+
<% } %>
239+
231240
<div class="mobile-node-stats">
232241
<div class="mobile-stat-item">
233242
<span class="mobile-stat-label"><%= t('common.online') %></span>
@@ -589,6 +598,29 @@
589598
.node-flag {
590599
font-size: 20px;
591600
}
601+
.node-comment {
602+
display: inline-block;
603+
max-width: 360px;
604+
color: var(--text-muted);
605+
font-size: 11px;
606+
line-height: 1.35;
607+
white-space: nowrap;
608+
overflow: hidden;
609+
text-overflow: ellipsis;
610+
vertical-align: middle;
611+
}
612+
.node-comment .ti {
613+
font-size: 12px;
614+
opacity: 0.7;
615+
vertical-align: -1px;
616+
}
617+
.node-comment-mobile {
618+
display: block;
619+
margin: 4px 0 6px;
620+
max-width: 100%;
621+
white-space: normal;
622+
word-break: break-word;
623+
}
592624
.btn-icon-danger:hover {
593625
background: var(--danger-bg);
594626
border-color: var(--danger);

views/partials/node-form/basic.ejs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@
2222
</div>
2323
</div>
2424

25+
<div class="form-group">
26+
<label for="comment"><%= t('nodes.comment') %></label>
27+
<input type="text" id="comment" name="comment"
28+
value="<%= node?.comment || '' %>"
29+
placeholder="<%= t('nodes.commentPlaceholder') %>"
30+
maxlength="500" autocomplete="off">
31+
<small><%= t('nodes.commentHint') %></small>
32+
</div>
33+
2534
<%# ========== 1b. Cascade Role & Country (Xray only) ========== %>
2635
<div class="form-section xray-only" id="cascadeRoleSection" style="<%= node?.type === 'xray' ? '' : 'display:none' %>">
2736
<h3><i class="ti ti-topology-star-3"></i> <%= t('nodes.cascadeRole') %></h3>

0 commit comments

Comments
 (0)