mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
feat(frontend): security tab base + TLS section (Pattern A)
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.
This commit is contained in:
parent
6f0bcaf97d
commit
534e954954
1 changed files with 129 additions and 2 deletions
|
|
@ -24,7 +24,12 @@ import {
|
||||||
formValuesToWirePayload,
|
formValuesToWirePayload,
|
||||||
} from '@/lib/xray/inbound-form-adapter';
|
} from '@/lib/xray/inbound-form-adapter';
|
||||||
import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults';
|
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 { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
|
||||||
import {
|
import {
|
||||||
InboundFormBaseSchema,
|
InboundFormBaseSchema,
|
||||||
|
|
@ -38,9 +43,13 @@ import {
|
||||||
Protocols,
|
Protocols,
|
||||||
SNIFFING_OPTION,
|
SNIFFING_OPTION,
|
||||||
TCP_CONGESTION_OPTION,
|
TCP_CONGESTION_OPTION,
|
||||||
|
TLS_CIPHER_OPTION,
|
||||||
|
TLS_VERSION_OPTION,
|
||||||
UTLS_FINGERPRINT,
|
UTLS_FINGERPRINT,
|
||||||
} from '@/schemas/primitives';
|
} from '@/schemas/primitives';
|
||||||
import { SockoptStreamSettingsSchema } from '@/schemas/protocols/stream/sockopt';
|
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 DateTimePicker from '@/components/DateTimePicker';
|
||||||
import InputAddon from '@/components/InputAddon';
|
import InputAddon from '@/components/InputAddon';
|
||||||
import type { DBInbound } from '@/models/dbinbound';
|
import type { DBInbound } from '@/models/dbinbound';
|
||||||
|
|
@ -114,7 +123,20 @@ export default function InboundFormModalNew({
|
||||||
});
|
});
|
||||||
const mixedUdpOn = Form.useWatch(['settings', 'udp'], form) ?? false;
|
const mixedUdpOn = Form.useWatch(['settings', 'udp'], form) ?? false;
|
||||||
const network = Form.useWatch(['streamSettings', 'network'], form) ?? '';
|
const network = Form.useWatch(['streamSettings', 'network'], form) ?? '';
|
||||||
|
const security = Form.useWatch(['streamSettings', 'security'], form) ?? 'none';
|
||||||
const streamEnabled = canEnableStream({ protocol });
|
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<string, unknown>) ?? {};
|
||||||
|
const cleaned: Record<string, unknown> = { ...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 xhttpMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'mode'], form);
|
||||||
const xhttpObfsMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'xPaddingObfsMode'], form) ?? false;
|
const xhttpObfsMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'xPaddingObfsMode'], form) ?? false;
|
||||||
const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form);
|
const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form);
|
||||||
|
|
@ -1361,6 +1383,108 @@ export default function InboundFormModalNew({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const securityTab = (
|
||||||
|
<>
|
||||||
|
<Form.Item label={t('pages.inbounds.securityTab')}>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
shouldUpdate={(prev, curr) =>
|
||||||
|
prev.streamSettings?.security !== curr.streamSettings?.security
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ getFieldValue }) => {
|
||||||
|
const sec = getFieldValue(['streamSettings', 'security']) ?? 'none';
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={sec}
|
||||||
|
disabled={!tlsAllowed}
|
||||||
|
onChange={onSecurityChange}
|
||||||
|
style={{ width: 180 }}
|
||||||
|
>
|
||||||
|
<Select.Option value="none">none</Select.Option>
|
||||||
|
<Select.Option value="tls">tls</Select.Option>
|
||||||
|
{realityAllowed && <Select.Option value="reality">reality</Select.Option>}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{security === 'tls' && (
|
||||||
|
<>
|
||||||
|
<Form.Item name={['streamSettings', 'tlsSettings', 'serverName']} label="SNI">
|
||||||
|
<Input placeholder="Server Name Indication" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={['streamSettings', 'tlsSettings', 'cipherSuites']} label="Cipher Suites">
|
||||||
|
<Select>
|
||||||
|
<Select.Option value="">Auto</Select.Option>
|
||||||
|
{Object.entries(TLS_CIPHER_OPTION).map(([k, v]) => (
|
||||||
|
<Select.Option key={v} value={v}>{k}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Min/Max Version">
|
||||||
|
<Space.Compact block>
|
||||||
|
<Form.Item name={['streamSettings', 'tlsSettings', 'minVersion']} noStyle>
|
||||||
|
<Select style={{ width: '50%' }}>
|
||||||
|
{Object.values(TLS_VERSION_OPTION).map((v) => (
|
||||||
|
<Select.Option key={v} value={v}>{v}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={['streamSettings', 'tlsSettings', 'maxVersion']} noStyle>
|
||||||
|
<Select style={{ width: '50%' }}>
|
||||||
|
{Object.values(TLS_VERSION_OPTION).map((v) => (
|
||||||
|
<Select.Option key={v} value={v}>{v}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Space.Compact>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'tlsSettings', 'settings', 'fingerprint']}
|
||||||
|
label="uTLS"
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Select.Option value="">None</Select.Option>
|
||||||
|
{Object.values(UTLS_FINGERPRINT).map((fp) => (
|
||||||
|
<Select.Option key={fp} value={fp}>{fp}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={['streamSettings', 'tlsSettings', 'alpn']} label="ALPN">
|
||||||
|
<Select mode="multiple" tokenSeparators={[',']} style={{ width: '100%' }}>
|
||||||
|
{Object.values(ALPN_OPTION).map((a) => (
|
||||||
|
<Select.Option key={a} value={a}>{a}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'tlsSettings', 'rejectUnknownSni']}
|
||||||
|
label="Reject Unknown SNI"
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'tlsSettings', 'disableSystemRoot']}
|
||||||
|
label="Disable System Root"
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'tlsSettings', 'enableSessionResumption']}
|
||||||
|
label="Session Resumption"
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const sniffingTab = (
|
const sniffingTab = (
|
||||||
<>
|
<>
|
||||||
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
|
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
|
||||||
|
|
@ -1457,7 +1581,10 @@ export default function InboundFormModalNew({
|
||||||
? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }]
|
? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }]
|
||||||
: []),
|
: []),
|
||||||
...(streamEnabled
|
...(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 },
|
{ key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab },
|
||||||
]} />
|
]} />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue