From 534e9549546a80695557d5e997819acef924ab3d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 02:33:36 +0200 Subject: [PATCH] feat(frontend): security tab base + TLS section (Pattern A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the security tab to the sibling-file rewrite. Visibility is paired with the stream tab — both gated on canEnableStream. The security selector is itself disabled when canEnableTls is false, and the reality option only appears when canEnableReality is true, mirroring the legacy modal's Radio.Group guards. onSecurityChange clears the previous branch's *Settings key and seeds the new branch from the schema's parsed defaults (the same trick the sockopt toggle uses). The security selector itself is rendered via a shouldUpdate closure so the on-change handler can write the cleaned streamSettings shape atomically without racing AntD's per-field sync. TLS section: serverName (the wire field — the legacy class calls it sni internally), cipherSuites (with the 13 named suites from TLS_CIPHER_OPTION), min/max version pair, uTLS fingerprint, ALPN multi-select, plus the three policy Switches. TLS certificates list, ECH controls, the full Reality sub-form, and the four API-call buttons (genRealityKeypair / genMldsa65 / getNewEchCert / randomizers) land in a follow-up commit. --- .../pages/inbounds/InboundFormModal.new.tsx | 131 +++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/inbounds/InboundFormModal.new.tsx b/frontend/src/pages/inbounds/InboundFormModal.new.tsx index d51e93f2..9459a290 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.new.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.new.tsx @@ -24,7 +24,12 @@ import { formValuesToWirePayload, } from '@/lib/xray/inbound-form-adapter'; import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults'; -import { canEnableStream, isSS2022 } from '@/lib/xray/protocol-capabilities'; +import { + canEnableReality, + canEnableStream, + canEnableTls, + isSS2022, +} from '@/lib/xray/protocol-capabilities'; import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks'; import { InboundFormBaseSchema, @@ -38,9 +43,13 @@ import { Protocols, SNIFFING_OPTION, TCP_CONGESTION_OPTION, + TLS_CIPHER_OPTION, + TLS_VERSION_OPTION, UTLS_FINGERPRINT, } from '@/schemas/primitives'; import { SockoptStreamSettingsSchema } from '@/schemas/protocols/stream/sockopt'; +import { TlsStreamSettingsSchema } from '@/schemas/protocols/security/tls'; +import { RealityStreamSettingsSchema } from '@/schemas/protocols/security/reality'; import DateTimePicker from '@/components/DateTimePicker'; import InputAddon from '@/components/InputAddon'; import type { DBInbound } from '@/models/dbinbound'; @@ -114,7 +123,20 @@ export default function InboundFormModalNew({ }); const mixedUdpOn = Form.useWatch(['settings', 'udp'], form) ?? false; const network = Form.useWatch(['streamSettings', 'network'], form) ?? ''; + const security = Form.useWatch(['streamSettings', 'security'], form) ?? 'none'; const streamEnabled = canEnableStream({ protocol }); + const tlsAllowed = canEnableTls({ protocol, streamSettings: { network, security } }); + const realityAllowed = canEnableReality({ protocol, streamSettings: { network, security } }); + + const onSecurityChange = (next: string) => { + const current = (form.getFieldValue('streamSettings') as Record) ?? {}; + const cleaned: Record = { ...current, security: next }; + delete cleaned.tlsSettings; + delete cleaned.realitySettings; + if (next === 'tls') cleaned.tlsSettings = TlsStreamSettingsSchema.parse({}); + if (next === 'reality') cleaned.realitySettings = RealityStreamSettingsSchema.parse({}); + form.setFieldValue('streamSettings', cleaned); + }; const xhttpMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'mode'], form); const xhttpObfsMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'xPaddingObfsMode'], form) ?? false; const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form); @@ -1361,6 +1383,108 @@ export default function InboundFormModalNew({ ); + const securityTab = ( + <> + + + prev.streamSettings?.security !== curr.streamSettings?.security + } + > + {({ getFieldValue }) => { + const sec = getFieldValue(['streamSettings', 'security']) ?? 'none'; + return ( + + ); + }} + + + + {security === 'tls' && ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} + + ); + const sniffingTab = ( <> @@ -1457,7 +1581,10 @@ export default function InboundFormModalNew({ ? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }] : []), ...(streamEnabled - ? [{ key: 'stream', label: t('pages.inbounds.streamTab'), children: streamTab }] + ? [ + { key: 'stream', label: t('pages.inbounds.streamTab'), children: streamTab }, + { key: 'security', label: t('pages.inbounds.securityTab'), children: securityTab }, + ] : []), { key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab }, ]} />