From c47a905ad235929bad39cd2e338f6ff2db70ca60 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 11 Jun 2026 23:32:24 +0200 Subject: [PATCH] fix(inbound): offer node share-address strategy only when a node exists The `node` share-address strategy resolves to an address only when the inbound can live on a node; for a local inbound it is always empty and behaves like `listen`. Drop the `node` option from the picker unless an enabled, node-eligible node exists, and coerce the value to `listen` otherwise so the Select never shows an option that does nothing. --- .../pages/inbounds/form/InboundFormModal.tsx | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx index 6cd021292..2389e1385 100644 --- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx @@ -176,6 +176,10 @@ export default function InboundFormModal({ const selectableNodes = (availableNodes || []).filter((n) => n.enable); const protocol = (Form.useWatch('protocol', form) ?? '') as string; const isNodeEligible = NODE_ELIGIBLE_PROTOCOLS.has(protocol); + // The `node` share-address strategy only means something when the inbound can + // actually live on a node — otherwise the node address it would resolve to is + // always empty. Offer it only then; `listen`/`custom` work for local inbounds. + const nodeShareOptionAvailable = selectableNodes.length > 0 && isNodeEligible; const sniffingEnabled = Form.useWatch(['sniffing', 'enabled'], form) ?? false; const vlessEncryption = Form.useWatch(['settings', 'encryption'], form) ?? ''; const ssMethod = Form.useWatch(['settings', 'method'], form); @@ -370,6 +374,18 @@ export default function InboundFormModal({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, wPort, wNodeId, protocol, network, mixedUdpOn, wSsNetwork, wTunnelNetwork]); + // Keep the strategy value inside the visible option set: when `node` isn't + // offered (no node, or a protocol that can't deploy to one) fall back to + // `listen`, which yields the same link for a local inbound. Mirrors how the + // protocol reset drops a nodeId that no longer applies. + useEffect(() => { + if (!open) return; + if (!nodeShareOptionAvailable && shareAddrStrategy === 'node') { + form.setFieldValue('shareAddrStrategy', 'listen'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, nodeShareOptionAvailable, shareAddrStrategy]); + // Why: protocol picker reset cascades through the form — clearing the // settings DU branch and dropping a nodeId that no longer applies. The // legacy modal did this imperatively in onProtocolChange; here we hook @@ -532,10 +548,12 @@ export default function InboundFormModal({ extra={t('pages.inbounds.form.shareAddrStrategyHelp')} >