From 6f0bcaf97db2a6a7e033d0ee2e35321216e2ea37 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 02:30:09 +0200 Subject: [PATCH] feat(frontend): stream tab external-proxy + sockopt sections (Pattern A) External Proxy: Switch driven by externalProxy array length. Toggling on seeds one row with the window hostname + the inbound's current port; toggling off clears the array. Each row is a Form.List item with forceTls/dest/port/remark inline, and a nested SNI/Fingerprint/ALPN row that conditionally renders on forceTls === 'tls' via a shouldUpdate-closure that watches the per-row forceTls path. Sockopt: Switch driven by whether the sockopt object exists in form state. Toggling on calls SockoptStreamSettingsSchema.parse({}) so every default the schema declares (mark=0, tproxy='off', domainStrategy='UseIP', tcpcongestion='bbr', etc.) flows into the form; toggling off sets to undefined. Renders the seventeen sockopt fields directly bound to ['streamSettings', 'sockopt', X] paths. Option lists pull from the primitives const dictionaries (UTLS_FINGERPRINT, ALPN_OPTION, DOMAIN_STRATEGY_OPTION, TCP_CONGESTION_OPTION) rather than the schema's .options to keep one source of truth for UI label strings. --- .../pages/inbounds/InboundFormModal.new.tsx | 254 +++++++++++++++++- 1 file changed, 253 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/inbounds/InboundFormModal.new.tsx b/frontend/src/pages/inbounds/InboundFormModal.new.tsx index 35006924..d51e93f2 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.new.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.new.tsx @@ -32,7 +32,15 @@ import { type InboundFormValues, } from '@/schemas/forms/inbound-form'; import { antdRule } from '@/utils/zodForm'; -import { Protocols, SNIFFING_OPTION } from '@/schemas/primitives'; +import { + ALPN_OPTION, + DOMAIN_STRATEGY_OPTION, + Protocols, + SNIFFING_OPTION, + TCP_CONGESTION_OPTION, + UTLS_FINGERPRINT, +} from '@/schemas/primitives'; +import { SockoptStreamSettingsSchema } from '@/schemas/protocols/stream/sockopt'; import DateTimePicker from '@/components/DateTimePicker'; import InputAddon from '@/components/InputAddon'; import type { DBInbound } from '@/models/dbinbound'; @@ -112,6 +120,38 @@ export default function InboundFormModalNew({ const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form); const xhttpSeqPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'seqPlacement'], form); const xhttpUplinkPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'uplinkDataPlacement'], form); + const externalProxyArr = Form.useWatch(['streamSettings', 'externalProxy'], form); + const externalProxyOn = Array.isArray(externalProxyArr) && externalProxyArr.length > 0; + const sockoptValue = Form.useWatch(['streamSettings', 'sockopt'], form); + const sockoptOn = !!sockoptValue && typeof sockoptValue === 'object' && Object.keys(sockoptValue as object).length > 0; + + const toggleExternalProxy = (on: boolean) => { + if (on) { + const port = (form.getFieldValue('port') as number) ?? 443; + form.setFieldValue(['streamSettings', 'externalProxy'], [{ + forceTls: 'same', + dest: typeof window !== 'undefined' ? window.location.hostname : '', + port, + remark: '', + sni: '', + fingerprint: '', + alpn: [], + }]); + } else { + form.setFieldValue(['streamSettings', 'externalProxy'], []); + } + }; + + const toggleSockopt = (on: boolean) => { + if (on) { + form.setFieldValue( + ['streamSettings', 'sockopt'], + SockoptStreamSettingsSchema.parse({}), + ); + } else { + form.setFieldValue(['streamSettings', 'sockopt'], undefined); + } + }; const wgSecretKey = Form.useWatch(['settings', 'secretKey'], form); const wgPubKey = typeof wgSecretKey === 'string' && wgSecretKey.length > 0 ? Wireguard.generateKeypair(wgSecretKey).publicKey @@ -1106,6 +1146,218 @@ export default function InboundFormModalNew({ )} + + + + + {externalProxyOn && ( + + {(fields, { add, remove }) => ( + <> + + + + + {fields.map((field) => ( +
+ + + + + + + + + + + + + + remove(field.name)}> + + + + + prev.streamSettings?.externalProxy?.[field.name]?.forceTls + !== curr.streamSettings?.externalProxy?.[field.name]?.forceTls + } + > + {({ getFieldValue }) => { + const ft = getFieldValue([ + 'streamSettings', 'externalProxy', field.name, 'forceTls', + ]); + if (ft !== 'tls') return null; + return ( + + + + + + + + + + + + ); + }} + +
+ ))} +
+ + )} +
+ )} + + + + + {sockoptOn && ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} );