OutboundFormModal.tsx 100 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128
  1. import { useEffect, useMemo, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import {
  4. Button,
  5. Form,
  6. Input,
  7. InputNumber,
  8. Modal,
  9. Radio,
  10. Select,
  11. Space,
  12. Switch,
  13. Tabs,
  14. message,
  15. } from 'antd';
  16. import { DeleteOutlined, MinusOutlined, PlusOutlined, SyncOutlined } from '@ant-design/icons';
  17. import FinalMaskForm from '@/components/FinalMaskForm';
  18. import HeaderMapEditor from '@/components/HeaderMapEditor';
  19. import InputAddon from '@/components/InputAddon';
  20. import JsonEditor from '@/components/JsonEditor';
  21. import { Wireguard } from '@/utils';
  22. import {
  23. formValuesToWirePayload,
  24. rawOutboundToFormValues,
  25. } from '@/lib/xray/outbound-form-adapter';
  26. import { parseOutboundLink } from '@/lib/xray/outbound-link-parser';
  27. import {
  28. OutboundFormBaseSchema,
  29. ShadowsocksOutboundFormSettingsSchema,
  30. TrojanOutboundFormSettingsSchema,
  31. VlessOutboundFormSettingsSchema,
  32. VmessOutboundFormSettingsSchema,
  33. type OutboundFormValues,
  34. } from '@/schemas/forms/outbound-form';
  35. import {
  36. ALPN_OPTION,
  37. Address_Port_Strategy,
  38. DNSRuleActions,
  39. MODE_OPTION,
  40. OutboundDomainStrategies,
  41. OutboundProtocols as Protocols,
  42. SNIFFING_OPTION,
  43. TCP_CONGESTION_OPTION,
  44. TLS_FLOW_CONTROL,
  45. USERS_SECURITY,
  46. UTLS_FINGERPRINT,
  47. WireguardDomainStrategy,
  48. } from '@/schemas/primitives';
  49. import {
  50. canEnableReality,
  51. canEnableStream,
  52. canEnableTls,
  53. canEnableTlsFlow,
  54. } from '@/lib/xray/protocol-capabilities';
  55. import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
  56. import { antdRule } from '@/utils/zodForm';
  57. import './OutboundFormModal.css';
  58. // Pattern A rewrite of OutboundFormModal. Built as a sibling `.new.tsx`
  59. // file so the build stays green section-by-section. The atomic swap at
  60. // the end of the rewrite replaces the legacy file in one commit
  61. // (per Core Decision 7 in the migration spec).
  62. interface OutboundFormModalProps {
  63. open: boolean;
  64. outbound: Record<string, unknown> | null;
  65. existingTags: string[];
  66. onClose: () => void;
  67. onConfirm: (outbound: Record<string, unknown>) => void;
  68. }
  69. const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p }));
  70. const SECURITY_OPTIONS = Object.values(USERS_SECURITY).map((v) => ({ value: v, label: v }));
  71. const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL).map((v) => ({ value: v, label: v }));
  72. const SS_METHOD_OPTIONS = SSMethodSchema.options.map((v) => ({ value: v, label: v }));
  73. const MODE_OPTIONS = Object.values(MODE_OPTION).map((v) => ({ value: v, label: v }));
  74. const UTLS_OPTIONS = Object.values(UTLS_FINGERPRINT).map((v) => ({ value: v, label: v }));
  75. const ALPN_OPTIONS = Object.values(ALPN_OPTION).map((v) => ({ value: v, label: v }));
  76. const ADDRESS_PORT_STRATEGY_OPTIONS = Object.values(Address_Port_Strategy).map((v) => ({
  77. value: v,
  78. label: v,
  79. }));
  80. // canEnableMux mirrors the adapter's helper but lives here so the modal
  81. // can show/hide the Mux section without going through the adapter.
  82. const MUX_PROTOCOLS = new Set<string>(['vmess', 'vless', 'trojan', 'shadowsocks', 'http', 'socks']);
  83. function isMuxAllowed(protocol: string, flow: string, network: string): boolean {
  84. if (!MUX_PROTOCOLS.has(protocol)) return false;
  85. if (protocol === 'vless' && flow) return false;
  86. if (network === 'xhttp') return false;
  87. return true;
  88. }
  89. const NETWORK_OPTIONS: { value: string; label: string }[] = [
  90. { value: 'tcp', label: 'TCP (RAW)' },
  91. { value: 'kcp', label: 'mKCP' },
  92. { value: 'ws', label: 'WebSocket' },
  93. { value: 'grpc', label: 'gRPC' },
  94. { value: 'httpupgrade', label: 'HTTPUpgrade' },
  95. { value: 'xhttp', label: 'XHTTP' },
  96. ];
  97. // Hysteria appends an extra `hysteria` network branch to the selector
  98. // — only when the parent protocol is hysteria. Wire-side this matches
  99. // the legacy modal's `isHysteria ? [...NETWORKS, 'hysteria'] : NETWORKS`.
  100. const HYSTERIA_NETWORK_OPTION = { value: 'hysteria', label: 'Hysteria' };
  101. // Per-network bootstrap. Mirrors the legacy class constructors so the
  102. // initial state for each transport matches what xray-core expects.
  103. function newStreamSlice(network: string): Record<string, unknown> {
  104. switch (network) {
  105. case 'tcp':
  106. return { network: 'tcp', tcpSettings: { header: { type: 'none' } } };
  107. case 'kcp':
  108. return {
  109. network: 'kcp',
  110. kcpSettings: {
  111. mtu: 1350, tti: 20, uplinkCapacity: 5, downlinkCapacity: 20,
  112. cwndMultiplier: 1, maxSendingWindow: 2097152,
  113. },
  114. };
  115. case 'ws':
  116. return {
  117. network: 'ws',
  118. wsSettings: { path: '/', host: '', headers: {}, heartbeatPeriod: 0 },
  119. };
  120. case 'grpc':
  121. return {
  122. network: 'grpc',
  123. grpcSettings: { serviceName: '', authority: '', multiMode: false },
  124. };
  125. case 'httpupgrade':
  126. return {
  127. network: 'httpupgrade',
  128. httpupgradeSettings: { path: '/', host: '', headers: {} },
  129. };
  130. case 'xhttp':
  131. return {
  132. network: 'xhttp',
  133. xhttpSettings: {
  134. path: '/', host: '', mode: '', headers: [],
  135. xPaddingBytes: '100-1000', scMaxEachPostBytes: '1000000',
  136. },
  137. };
  138. case 'hysteria':
  139. return {
  140. network: 'hysteria',
  141. hysteriaSettings: {
  142. version: 2,
  143. auth: '',
  144. udpIdleTimeout: 60,
  145. },
  146. };
  147. default:
  148. return { network: 'tcp', tcpSettings: { header: { type: 'none' } } };
  149. }
  150. }
  151. // Protocols whose form schema carries a flat connect target — these all
  152. // get the shared "server" sub-block (address + port) at the top of the
  153. // protocol section. Wireguard has an address but no port. DNS/freedom/
  154. // blackhole/loopback have no connect target.
  155. const SERVER_PROTOCOLS = new Set<string>([
  156. 'vmess', 'vless', 'trojan', 'shadowsocks', 'socks', 'http', 'hysteria',
  157. ]);
  158. function buildAddModeValues(): OutboundFormValues {
  159. return rawOutboundToFormValues({});
  160. }
  161. export default function OutboundFormModal({
  162. open,
  163. outbound: outboundProp,
  164. existingTags,
  165. onClose,
  166. onConfirm,
  167. }: OutboundFormModalProps) {
  168. const { t } = useTranslation();
  169. const [messageApi, messageContextHolder] = message.useMessage();
  170. const [form] = Form.useForm<OutboundFormValues>();
  171. const [activeKey, setActiveKey] = useState('1');
  172. const [jsonText, setJsonText] = useState('');
  173. const [jsonDirty, setJsonDirty] = useState(false);
  174. const [linkInput, setLinkInput] = useState('');
  175. // Parse a share link (vmess:// / vless:// / trojan:// / ss:// /
  176. // hysteria2://) and replace form state with the result. The current
  177. // tag is preserved when the parsed link doesn't carry one.
  178. function importLink() {
  179. const link = linkInput.trim();
  180. if (!link) return;
  181. const parsed = parseOutboundLink(link);
  182. if (!parsed) {
  183. messageApi.error('Wrong Link!');
  184. return;
  185. }
  186. const currentTag = form.getFieldValue('tag') as string | undefined;
  187. if (!parsed.tag && currentTag) parsed.tag = currentTag;
  188. const next = rawOutboundToFormValues(parsed);
  189. form.resetFields();
  190. form.setFieldsValue(next);
  191. setJsonText(JSON.stringify(formValuesToWirePayload(next), null, 2));
  192. setJsonDirty(false);
  193. setLinkInput('');
  194. messageApi.success('Link imported successfully');
  195. setActiveKey('1');
  196. }
  197. const isEdit = outboundProp != null;
  198. const title = isEdit
  199. ? `${t('edit')} ${t('pages.xray.Outbounds')}`
  200. : `+ ${t('pages.xray.Outbounds')}`;
  201. const okText = isEdit ? t('pages.clients.submitEdit') : t('create');
  202. useEffect(() => {
  203. if (!open) return;
  204. const initial = outboundProp
  205. ? rawOutboundToFormValues(outboundProp)
  206. : buildAddModeValues();
  207. form.resetFields();
  208. form.setFieldsValue(initial);
  209. setActiveKey('1');
  210. setJsonText(JSON.stringify(formValuesToWirePayload(initial), null, 2));
  211. setJsonDirty(false);
  212. }, [open, outboundProp, form]);
  213. const tag = Form.useWatch('tag', form) ?? '';
  214. const protocol = (Form.useWatch('protocol', form) ?? 'vless') as string;
  215. // preserve: true — without it useWatch only reflects values whose
  216. // Form.Item is currently mounted. The streamSettings selectors live
  217. // INSIDE `{streamAllowed && network && (...)}`, so the moment that
  218. // conditional gates them out, useWatch returns undefined, the gate
  219. // keeps returning false, and the stream block never renders even
  220. // though streamSettings is in the form store.
  221. const network = (Form.useWatch(['streamSettings', 'network'], { form, preserve: true }) ?? '') as string;
  222. const security = (Form.useWatch(['streamSettings', 'security'], { form, preserve: true }) ?? 'none') as string;
  223. const streamAllowed = canEnableStream({ protocol });
  224. const tlsAllowed = canEnableTls({ protocol, streamSettings: { network, security } });
  225. const realityAllowed = canEnableReality({ protocol, streamSettings: { network, security } });
  226. const tlsFlowAllowed = canEnableTlsFlow({ protocol, streamSettings: { network, security } });
  227. // Seed streamSettings when the user picks a protocol that supports
  228. // streams but the form does not yet have a stream slice (new outbound,
  229. // or wire payload arrived without streamSettings).
  230. useEffect(() => {
  231. if (!streamAllowed) return;
  232. if (network) return;
  233. form.setFieldValue('streamSettings', { ...newStreamSlice('tcp'), security: 'none' });
  234. // eslint-disable-next-line react-hooks/exhaustive-deps
  235. }, [streamAllowed, network]);
  236. // Wireguard pubKey is a UI-only field derived from secretKey on every
  237. // edit. The legacy modal did the same on every keystroke. We re-derive
  238. // here so paste-in secret keys immediately surface the matching pub.
  239. const wgSecretKey = Form.useWatch(['settings', 'secretKey'], form) as string | undefined;
  240. useEffect(() => {
  241. if (protocol !== 'wireguard') return;
  242. const sk = (wgSecretKey ?? '').trim();
  243. if (!sk) {
  244. form.setFieldValue(['settings', 'pubKey'], '');
  245. return;
  246. }
  247. try {
  248. const { publicKey } = Wireguard.generateKeypair(sk);
  249. form.setFieldValue(['settings', 'pubKey'], publicKey);
  250. } catch {
  251. form.setFieldValue(['settings', 'pubKey'], '');
  252. }
  253. // eslint-disable-next-line react-hooks/exhaustive-deps
  254. }, [protocol, wgSecretKey]);
  255. // Switching protocol resets the settings sub-object to fresh defaults
  256. // so leftover fields from the previous protocol do not bleed through.
  257. // The adapter's rawOutboundToFormValues seeds whatever the new protocol
  258. // expects (vless flat shape, vmess flat shape, wireguard with secretKey
  259. // placeholder, etc.).
  260. function onValuesChange(changed: Partial<OutboundFormValues>) {
  261. if ('protocol' in changed && changed.protocol) {
  262. const next = rawOutboundToFormValues({ protocol: changed.protocol });
  263. form.setFieldValue('settings', next.settings);
  264. }
  265. }
  266. // Security change cascade: swap the security sub-key so the DU branch
  267. // matches. Seed default field values when entering tls/reality so the
  268. // sub-forms render without `undefined` field references.
  269. function onSecurityChange(next: string) {
  270. const stream = form.getFieldValue('streamSettings') ?? {};
  271. const cleaned = { ...stream } as Record<string, unknown>;
  272. delete cleaned.tlsSettings;
  273. delete cleaned.realitySettings;
  274. if (next === 'tls') {
  275. cleaned.tlsSettings = {
  276. serverName: '',
  277. alpn: [],
  278. fingerprint: '',
  279. echConfigList: '',
  280. verifyPeerCertByName: '',
  281. pinnedPeerCertSha256: '',
  282. };
  283. } else if (next === 'reality') {
  284. cleaned.realitySettings = {
  285. publicKey: '',
  286. fingerprint: 'chrome',
  287. serverName: '',
  288. shortId: '',
  289. spiderX: '',
  290. mldsa65Verify: '',
  291. };
  292. }
  293. cleaned.security = next;
  294. form.setFieldValue('streamSettings', cleaned);
  295. }
  296. // Network change cascade: swap the per-network sub-key (tcpSettings,
  297. // wsSettings, etc.) so the DU branch matches. Preserve security if
  298. // the new network supports it, otherwise force back to 'none'.
  299. function onNetworkChange(next: string) {
  300. const currentSecurity = form.getFieldValue(['streamSettings', 'security']) ?? 'none';
  301. const stillAllowed = canEnableTls({ protocol, streamSettings: { network: next, security: currentSecurity } });
  302. const stillReality = canEnableReality({ protocol, streamSettings: { network: next, security: currentSecurity } });
  303. const newSecurity =
  304. currentSecurity === 'tls' && !stillAllowed
  305. ? 'none'
  306. : currentSecurity === 'reality' && !stillReality
  307. ? 'none'
  308. : currentSecurity;
  309. form.setFieldValue('streamSettings', { ...newStreamSlice(next), security: newSecurity });
  310. }
  311. const duplicateTag = useMemo(() => {
  312. const myTag = tag.trim();
  313. if (!myTag) return false;
  314. if (isEdit && (outboundProp?.tag as string | undefined) === myTag) return false;
  315. return (existingTags || []).includes(myTag);
  316. }, [tag, existingTags, isEdit, outboundProp]);
  317. // Bridge form ↔ JSON tab: when leaving the JSON tab back to Basic, push
  318. // any edits into form state. When entering JSON tab, snapshot current
  319. // form values so the user sees the live shape.
  320. function applyJsonToForm(): boolean {
  321. if (!jsonDirty) return true;
  322. const raw = jsonText.trim();
  323. if (!raw) return true;
  324. let parsed: Record<string, unknown>;
  325. try {
  326. parsed = JSON.parse(raw) as Record<string, unknown>;
  327. } catch (e) {
  328. messageApi.error(`JSON: ${(e as Error).message}`);
  329. return false;
  330. }
  331. const next = rawOutboundToFormValues(parsed);
  332. form.resetFields();
  333. form.setFieldsValue(next);
  334. setJsonDirty(false);
  335. return true;
  336. }
  337. function onTabChange(key: string) {
  338. if (document.activeElement instanceof HTMLElement) {
  339. document.activeElement.blur();
  340. }
  341. if (key === '2') {
  342. const values = form.getFieldsValue(true) as OutboundFormValues;
  343. setJsonText(JSON.stringify(formValuesToWirePayload(values), null, 2));
  344. setJsonDirty(false);
  345. setActiveKey(key);
  346. return;
  347. }
  348. if (key === '1' && activeKey === '2') {
  349. if (!applyJsonToForm()) return;
  350. }
  351. setActiveKey(key);
  352. }
  353. async function onOk() {
  354. if (activeKey === '2' && !applyJsonToForm()) return;
  355. let values: OutboundFormValues;
  356. try {
  357. values = await form.validateFields();
  358. } catch {
  359. return;
  360. }
  361. if (duplicateTag) {
  362. messageApi.error('Tag already used by another outbound');
  363. return;
  364. }
  365. onConfirm(formValuesToWirePayload(values));
  366. }
  367. return (
  368. <>
  369. {messageContextHolder}
  370. <Modal
  371. open={open}
  372. title={title}
  373. okText={okText}
  374. cancelText={t('close')}
  375. mask={{ closable: false }}
  376. width={780}
  377. onOk={onOk}
  378. onCancel={onClose}
  379. destroyOnHidden
  380. >
  381. <Form
  382. form={form}
  383. colon={false}
  384. labelCol={{ md: { span: 8 } }}
  385. wrapperCol={{ md: { span: 14 } }}
  386. onValuesChange={onValuesChange}
  387. >
  388. <Tabs
  389. activeKey={activeKey}
  390. onChange={onTabChange}
  391. items={[
  392. {
  393. key: '1',
  394. label: t('pages.xray.basicTemplate'),
  395. children: (
  396. <>
  397. <Form.Item
  398. label={t('protocol')}
  399. name="protocol"
  400. rules={[antdRule(OutboundFormBaseSchema.shape.tag, t)]}
  401. >
  402. <Select options={PROTOCOL_OPTIONS} />
  403. </Form.Item>
  404. <Form.Item
  405. label="Tag"
  406. name="tag"
  407. validateStatus={duplicateTag ? 'warning' : undefined}
  408. help={duplicateTag ? 'Tag already used by another outbound' : undefined}
  409. rules={[
  410. { required: true, message: 'Tag is required' },
  411. ]}
  412. >
  413. <Input placeholder="unique-tag" />
  414. </Form.Item>
  415. <Form.Item label="Send through" name="sendThrough">
  416. <Input placeholder="local IP" />
  417. </Form.Item>
  418. {/* Shared connect target (address + port) for protocols
  419. whose form schema carries them flat at settings root.
  420. Hidden for freedom/blackhole/dns/loopback/wireguard. */}
  421. {SERVER_PROTOCOLS.has(protocol) && (
  422. <>
  423. <Form.Item
  424. label={t('pages.inbounds.address')}
  425. name={['settings', 'address']}
  426. rules={[{ required: true, message: 'Address is required' }]}
  427. >
  428. <Input />
  429. </Form.Item>
  430. <Form.Item
  431. label={t('pages.inbounds.port')}
  432. name={['settings', 'port']}
  433. rules={[{ required: true, message: 'Port is required' }]}
  434. >
  435. <InputNumber min={1} max={65535} style={{ width: '100%' }} />
  436. </Form.Item>
  437. </>
  438. )}
  439. {(protocol === 'vmess' || protocol === 'vless') && (
  440. <Form.Item
  441. label="ID"
  442. name={['settings', 'id']}
  443. rules={[antdRule(VmessOutboundFormSettingsSchema.shape.id, t)]}
  444. >
  445. <Input placeholder="UUID" />
  446. </Form.Item>
  447. )}
  448. {protocol === 'vmess' && (
  449. <Form.Item
  450. label={t('security')}
  451. name={['settings', 'security']}
  452. rules={[antdRule(VmessOutboundFormSettingsSchema.shape.security, t)]}
  453. >
  454. <Select options={SECURITY_OPTIONS} />
  455. </Form.Item>
  456. )}
  457. {protocol === 'vless' && (
  458. <>
  459. <Form.Item
  460. label={t('encryption')}
  461. name={['settings', 'encryption']}
  462. rules={[antdRule(VlessOutboundFormSettingsSchema.shape.encryption, t)]}
  463. >
  464. <Input />
  465. </Form.Item>
  466. <Form.Item label="Reverse tag" name={['settings', 'reverseTag']}>
  467. <Input placeholder="optional" />
  468. </Form.Item>
  469. </>
  470. )}
  471. {(protocol === 'trojan' || protocol === 'shadowsocks') && (
  472. <Form.Item
  473. label={t('password')}
  474. name={['settings', 'password']}
  475. rules={[
  476. antdRule(
  477. protocol === 'trojan'
  478. ? TrojanOutboundFormSettingsSchema.shape.password
  479. : ShadowsocksOutboundFormSettingsSchema.shape.password,
  480. t,
  481. ),
  482. ]}
  483. >
  484. <Input />
  485. </Form.Item>
  486. )}
  487. {protocol === 'shadowsocks' && (
  488. <>
  489. <Form.Item
  490. label={t('encryption')}
  491. name={['settings', 'method']}
  492. rules={[antdRule(SSMethodSchema, t)]}
  493. >
  494. <Select options={SS_METHOD_OPTIONS} />
  495. </Form.Item>
  496. <Form.Item
  497. label="UDP over TCP"
  498. name={['settings', 'uot']}
  499. valuePropName="checked"
  500. >
  501. <Switch />
  502. </Form.Item>
  503. <Form.Item label="UoT version" name={['settings', 'UoTVersion']}>
  504. <InputNumber min={1} max={2} />
  505. </Form.Item>
  506. </>
  507. )}
  508. {(protocol === 'socks' || protocol === 'http') && (
  509. <>
  510. <Form.Item label={t('username')} name={['settings', 'user']}>
  511. <Input />
  512. </Form.Item>
  513. <Form.Item label={t('password')} name={['settings', 'pass']}>
  514. <Input />
  515. </Form.Item>
  516. </>
  517. )}
  518. {protocol === 'hysteria' && (
  519. <Form.Item label="Version" name={['settings', 'version']}>
  520. <InputNumber min={2} max={2} disabled />
  521. </Form.Item>
  522. )}
  523. {protocol === 'loopback' && (
  524. <Form.Item label="Inbound tag" name={['settings', 'inboundTag']}>
  525. <Input placeholder="inbound tag used in routing rules" />
  526. </Form.Item>
  527. )}
  528. {protocol === 'blackhole' && (
  529. <Form.Item label="Response type" name={['settings', 'type']}>
  530. <Select
  531. options={[
  532. { value: '', label: '(empty)' },
  533. { value: 'none', label: 'none' },
  534. { value: 'http', label: 'http' },
  535. ]}
  536. />
  537. </Form.Item>
  538. )}
  539. {protocol === 'dns' && (
  540. <>
  541. <Form.Item label="Rewrite network" name={['settings', 'rewriteNetwork']}>
  542. <Select
  543. allowClear
  544. placeholder="(unchanged)"
  545. options={[
  546. { value: 'udp', label: 'udp' },
  547. { value: 'tcp', label: 'tcp' },
  548. ]}
  549. />
  550. </Form.Item>
  551. <Form.Item label="Rewrite address" name={['settings', 'rewriteAddress']}>
  552. <Input placeholder="(unchanged) e.g. 1.1.1.1" />
  553. </Form.Item>
  554. <Form.Item label="Rewrite port" name={['settings', 'rewritePort']}>
  555. <InputNumber min={0} max={65535} style={{ width: '100%' }} />
  556. </Form.Item>
  557. <Form.Item label="User level" name={['settings', 'userLevel']}>
  558. <InputNumber min={0} style={{ width: '100%' }} />
  559. </Form.Item>
  560. <Form.List name={['settings', 'rules']}>
  561. {(fields, { add, remove }) => (
  562. <>
  563. <Form.Item label="Rules">
  564. <Button
  565. size="small"
  566. type="primary"
  567. icon={<PlusOutlined />}
  568. onClick={() => add({ action: 'direct', qtype: '', domain: '' })}
  569. />
  570. </Form.Item>
  571. {fields.map((field, index) => (
  572. <div key={field.key}>
  573. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  574. <div className="item-heading">
  575. <span>Rule {index + 1}</span>
  576. <DeleteOutlined
  577. className="danger-icon"
  578. onClick={() => remove(field.name)}
  579. />
  580. </div>
  581. </Form.Item>
  582. <Form.Item label="Action" name={[field.name, 'action']}>
  583. <Select
  584. options={DNSRuleActions.map((a) => ({ value: a, label: a }))}
  585. />
  586. </Form.Item>
  587. <Form.Item label="QType" name={[field.name, 'qtype']}>
  588. <Input placeholder="1,3,23-24" />
  589. </Form.Item>
  590. <Form.Item label={t('domainName')} name={[field.name, 'domain']}>
  591. <Input placeholder="domain:example.com" />
  592. </Form.Item>
  593. </div>
  594. ))}
  595. </>
  596. )}
  597. </Form.List>
  598. </>
  599. )}
  600. {protocol === 'freedom' && (
  601. <>
  602. <Form.Item label="Strategy" name={['settings', 'domainStrategy']}>
  603. <Select
  604. options={[
  605. { value: '', label: `(${t('none')})` },
  606. ...OutboundDomainStrategies.map((s) => ({ value: s, label: s })),
  607. ]}
  608. />
  609. </Form.Item>
  610. <Form.Item label="Redirect" name={['settings', 'redirect']}>
  611. <Input />
  612. </Form.Item>
  613. <Form.Item label="Fragment" shouldUpdate noStyle>
  614. {() => {
  615. const fragment = (form.getFieldValue(['settings', 'fragment']) ?? {}) as {
  616. packets?: string;
  617. length?: string;
  618. interval?: string;
  619. maxSplit?: string;
  620. };
  621. const enabled = !!(fragment.length || fragment.interval || fragment.maxSplit);
  622. return (
  623. <>
  624. <Form.Item label="Fragment">
  625. <Switch
  626. checked={enabled}
  627. onChange={(checked) => {
  628. form.setFieldValue(
  629. ['settings', 'fragment'],
  630. checked
  631. ? {
  632. packets: 'tlshello',
  633. length: '100-200',
  634. interval: '10-20',
  635. maxSplit: '300-400',
  636. }
  637. : { packets: '', length: '', interval: '', maxSplit: '' },
  638. );
  639. }}
  640. />
  641. </Form.Item>
  642. {enabled && (
  643. <>
  644. <Form.Item
  645. label="Packets"
  646. name={['settings', 'fragment', 'packets']}
  647. >
  648. <Select
  649. options={[
  650. { value: '1-3', label: '1-3' },
  651. { value: 'tlshello', label: 'tlshello' },
  652. ]}
  653. />
  654. </Form.Item>
  655. <Form.Item label="Length" name={['settings', 'fragment', 'length']}>
  656. <Input />
  657. </Form.Item>
  658. <Form.Item
  659. label="Interval"
  660. name={['settings', 'fragment', 'interval']}
  661. >
  662. <Input />
  663. </Form.Item>
  664. <Form.Item
  665. label="Max Split"
  666. name={['settings', 'fragment', 'maxSplit']}
  667. >
  668. <Input />
  669. </Form.Item>
  670. </>
  671. )}
  672. </>
  673. );
  674. }}
  675. </Form.Item>
  676. <Form.List name={['settings', 'noises']}>
  677. {(fields, { add, remove }) => (
  678. <>
  679. <Form.Item label="Noises">
  680. <Switch
  681. checked={fields.length > 0}
  682. onChange={(checked) => {
  683. if (checked) {
  684. add({
  685. type: 'rand',
  686. packet: '10-20',
  687. delay: '10-16',
  688. applyTo: 'ip',
  689. });
  690. } else {
  691. // remove() with no arg is not supported;
  692. // walk fields in reverse and drop each.
  693. for (let i = fields.length - 1; i >= 0; i--) {
  694. remove(fields[i].name);
  695. }
  696. }
  697. }}
  698. />
  699. {fields.length > 0 && (
  700. <Button
  701. size="small"
  702. type="primary"
  703. className="ml-8"
  704. icon={<PlusOutlined />}
  705. onClick={() =>
  706. add({
  707. type: 'rand',
  708. packet: '10-20',
  709. delay: '10-16',
  710. applyTo: 'ip',
  711. })
  712. }
  713. />
  714. )}
  715. </Form.Item>
  716. {fields.map((field, index) => (
  717. <div key={field.key}>
  718. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  719. <div className="item-heading">
  720. <span>Noise {index + 1}</span>
  721. {fields.length > 1 && (
  722. <DeleteOutlined
  723. className="danger-icon"
  724. onClick={() => remove(field.name)}
  725. />
  726. )}
  727. </div>
  728. </Form.Item>
  729. <Form.Item label="Type" name={[field.name, 'type']}>
  730. <Select
  731. options={['rand', 'base64', 'str', 'hex'].map((v) => ({
  732. value: v,
  733. label: v,
  734. }))}
  735. />
  736. </Form.Item>
  737. <Form.Item label="Packet" name={[field.name, 'packet']}>
  738. <Input />
  739. </Form.Item>
  740. <Form.Item label="Delay (ms)" name={[field.name, 'delay']}>
  741. <Input />
  742. </Form.Item>
  743. <Form.Item label="Apply to" name={[field.name, 'applyTo']}>
  744. <Select
  745. options={['ip', 'ipv4', 'ipv6'].map((v) => ({
  746. value: v,
  747. label: v,
  748. }))}
  749. />
  750. </Form.Item>
  751. </div>
  752. ))}
  753. </>
  754. )}
  755. </Form.List>
  756. <Form.List name={['settings', 'finalRules']}>
  757. {(fields, { add, remove }) => (
  758. <>
  759. <Form.Item label="Final Rules">
  760. <Button
  761. size="small"
  762. type="primary"
  763. icon={<PlusOutlined />}
  764. onClick={() =>
  765. add({
  766. action: 'allow',
  767. network: '',
  768. port: '',
  769. ip: [],
  770. blockDelay: '',
  771. })
  772. }
  773. />
  774. <span className="ml-8" style={{ opacity: 0.6 }}>
  775. Override Xray&apos;s default private-IP block
  776. </span>
  777. </Form.Item>
  778. {fields.map((field, index) => (
  779. <div key={field.key}>
  780. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  781. <div className="item-heading">
  782. <span>Rule {index + 1}</span>
  783. <DeleteOutlined
  784. className="danger-icon"
  785. onClick={() => remove(field.name)}
  786. />
  787. </div>
  788. </Form.Item>
  789. <Form.Item label="Action" name={[field.name, 'action']}>
  790. <Select
  791. options={['allow', 'block'].map((v) => ({
  792. value: v,
  793. label: v,
  794. }))}
  795. />
  796. </Form.Item>
  797. <Form.Item label="Network" name={[field.name, 'network']}>
  798. <Select
  799. allowClear
  800. placeholder="(any)"
  801. options={['tcp', 'udp', 'tcp,udp'].map((v) => ({
  802. value: v,
  803. label: v,
  804. }))}
  805. />
  806. </Form.Item>
  807. <Form.Item label="Port" name={[field.name, 'port']}>
  808. <Input placeholder="e.g. 80,443 or 1000-2000" />
  809. </Form.Item>
  810. <Form.Item label="IP / CIDR / geoip" name={[field.name, 'ip']}>
  811. <Select
  812. mode="tags"
  813. tokenSeparators={[',', ' ']}
  814. placeholder="e.g. 10.0.0.0/8, geoip:private"
  815. />
  816. </Form.Item>
  817. <Form.Item shouldUpdate noStyle>
  818. {() => {
  819. const ruleAction = form.getFieldValue([
  820. 'settings',
  821. 'finalRules',
  822. field.name,
  823. 'action',
  824. ]);
  825. if (ruleAction !== 'block') return null;
  826. return (
  827. <Form.Item
  828. label="Block delay (ms)"
  829. name={[field.name, 'blockDelay']}
  830. >
  831. <Input placeholder="optional: 5000-10000" />
  832. </Form.Item>
  833. );
  834. }}
  835. </Form.Item>
  836. </div>
  837. ))}
  838. </>
  839. )}
  840. </Form.List>
  841. </>
  842. )}
  843. {protocol === 'vless' && (
  844. <Form.Item shouldUpdate noStyle>
  845. {() => {
  846. const reverseTag = form.getFieldValue(['settings', 'reverseTag']);
  847. if (!reverseTag) return null;
  848. const sniff = (form.getFieldValue(['settings', 'reverseSniffing']) ?? {}) as {
  849. enabled?: boolean;
  850. };
  851. return (
  852. <>
  853. <Form.Item
  854. label="Reverse Sniffing"
  855. name={['settings', 'reverseSniffing', 'enabled']}
  856. valuePropName="checked"
  857. >
  858. <Switch />
  859. </Form.Item>
  860. {sniff.enabled && (
  861. <>
  862. <Form.Item
  863. wrapperCol={{ md: { span: 14, offset: 8 } }}
  864. name={['settings', 'reverseSniffing', 'destOverride']}
  865. >
  866. <Select
  867. mode="multiple"
  868. className="sniffing-options"
  869. options={Object.entries(SNIFFING_OPTION).map(([k, v]) => ({
  870. value: v,
  871. label: k,
  872. }))}
  873. />
  874. </Form.Item>
  875. <Form.Item
  876. label="Metadata Only"
  877. name={['settings', 'reverseSniffing', 'metadataOnly']}
  878. valuePropName="checked"
  879. >
  880. <Switch />
  881. </Form.Item>
  882. <Form.Item
  883. label="Route Only"
  884. name={['settings', 'reverseSniffing', 'routeOnly']}
  885. valuePropName="checked"
  886. >
  887. <Switch />
  888. </Form.Item>
  889. <Form.Item
  890. label="IPs Excluded"
  891. name={['settings', 'reverseSniffing', 'ipsExcluded']}
  892. >
  893. <Select
  894. mode="tags"
  895. tokenSeparators={[',']}
  896. placeholder="IP/CIDR/geoip:*"
  897. />
  898. </Form.Item>
  899. <Form.Item
  900. label="Domains Excluded"
  901. name={['settings', 'reverseSniffing', 'domainsExcluded']}
  902. >
  903. <Select
  904. mode="tags"
  905. tokenSeparators={[',']}
  906. placeholder="domain:*"
  907. />
  908. </Form.Item>
  909. </>
  910. )}
  911. </>
  912. );
  913. }}
  914. </Form.Item>
  915. )}
  916. {protocol === 'wireguard' && (
  917. <>
  918. <Form.Item label={t('pages.inbounds.address')} name={['settings', 'address']}>
  919. <Input placeholder="comma-separated, e.g. 10.0.0.1,fd00::1" />
  920. </Form.Item>
  921. <Form.Item
  922. label={
  923. <>
  924. {t('pages.inbounds.privatekey')}
  925. <SyncOutlined
  926. className="random-icon"
  927. onClick={() => {
  928. const pair = Wireguard.generateKeypair();
  929. form.setFieldValue(['settings', 'secretKey'], pair.privateKey);
  930. form.setFieldValue(['settings', 'pubKey'], pair.publicKey);
  931. }}
  932. />
  933. </>
  934. }
  935. name={['settings', 'secretKey']}
  936. >
  937. <Input />
  938. </Form.Item>
  939. <Form.Item label={t('pages.inbounds.publicKey')} name={['settings', 'pubKey']}>
  940. <Input disabled />
  941. </Form.Item>
  942. <Form.Item label="Domain strategy" name={['settings', 'domainStrategy']}>
  943. <Select
  944. options={[
  945. { value: '', label: `(${t('none')})` },
  946. ...WireguardDomainStrategy.map((s) => ({ value: s, label: s })),
  947. ]}
  948. />
  949. </Form.Item>
  950. <Form.Item label="MTU" name={['settings', 'mtu']}>
  951. <InputNumber min={0} />
  952. </Form.Item>
  953. <Form.Item label="Workers" name={['settings', 'workers']}>
  954. <InputNumber min={0} />
  955. </Form.Item>
  956. <Form.Item
  957. label="No-kernel TUN"
  958. name={['settings', 'noKernelTun']}
  959. valuePropName="checked"
  960. >
  961. <Switch />
  962. </Form.Item>
  963. <Form.Item label="Reserved" name={['settings', 'reserved']}>
  964. <Input placeholder="comma-separated bytes, e.g. 1,2,3" />
  965. </Form.Item>
  966. <Form.List name={['settings', 'peers']}>
  967. {(fields, { add, remove }) => (
  968. <>
  969. <Form.Item label="Peers">
  970. <Button
  971. size="small"
  972. type="primary"
  973. icon={<PlusOutlined />}
  974. onClick={() =>
  975. add({
  976. publicKey: '',
  977. psk: '',
  978. allowedIPs: ['0.0.0.0/0', '::/0'],
  979. endpoint: '',
  980. keepAlive: 0,
  981. })
  982. }
  983. />
  984. </Form.Item>
  985. {fields.map((field, index) => (
  986. <div key={field.key}>
  987. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  988. <div className="item-heading">
  989. <span>Peer {index + 1}</span>
  990. {fields.length > 1 && (
  991. <DeleteOutlined
  992. className="danger-icon"
  993. onClick={() => remove(field.name)}
  994. />
  995. )}
  996. </div>
  997. </Form.Item>
  998. <Form.Item label="Endpoint" name={[field.name, 'endpoint']}>
  999. <Input />
  1000. </Form.Item>
  1001. <Form.Item
  1002. label={t('pages.inbounds.publicKey')}
  1003. name={[field.name, 'publicKey']}
  1004. >
  1005. <Input />
  1006. </Form.Item>
  1007. <Form.Item label="PSK" name={[field.name, 'psk']}>
  1008. <Input />
  1009. </Form.Item>
  1010. <Form.Item label="Allowed IPs">
  1011. <Form.List name={[field.name, 'allowedIPs']}>
  1012. {(ipFields, { add: addIp, remove: removeIp }) => (
  1013. <>
  1014. {ipFields.map((ipField, ipIdx) => (
  1015. <Space.Compact
  1016. key={ipField.key}
  1017. block
  1018. style={{ marginBottom: 4 }}
  1019. >
  1020. <Form.Item noStyle name={ipField.name}>
  1021. <Input />
  1022. </Form.Item>
  1023. {ipFields.length > 1 && (
  1024. <InputAddon onClick={() => removeIp(ipIdx)}>
  1025. <MinusOutlined />
  1026. </InputAddon>
  1027. )}
  1028. </Space.Compact>
  1029. ))}
  1030. <Button
  1031. size="small"
  1032. icon={<PlusOutlined />}
  1033. onClick={() => addIp('')}
  1034. />
  1035. </>
  1036. )}
  1037. </Form.List>
  1038. </Form.Item>
  1039. <Form.Item label="Keep alive" name={[field.name, 'keepAlive']}>
  1040. <InputNumber min={0} />
  1041. </Form.Item>
  1042. </div>
  1043. ))}
  1044. </>
  1045. )}
  1046. </Form.List>
  1047. </>
  1048. )}
  1049. {streamAllowed && network && (
  1050. <>
  1051. <Form.Item
  1052. label={t('transmission')}
  1053. name={['streamSettings', 'network']}
  1054. >
  1055. <Select
  1056. value={network}
  1057. onChange={onNetworkChange}
  1058. options={
  1059. protocol === 'hysteria'
  1060. ? [...NETWORK_OPTIONS, HYSTERIA_NETWORK_OPTION]
  1061. : NETWORK_OPTIONS
  1062. }
  1063. />
  1064. </Form.Item>
  1065. {network === 'tcp' && (
  1066. <Form.Item shouldUpdate noStyle>
  1067. {() => {
  1068. const type =
  1069. form.getFieldValue([
  1070. 'streamSettings',
  1071. 'tcpSettings',
  1072. 'header',
  1073. 'type',
  1074. ]) ?? 'none';
  1075. return (
  1076. <>
  1077. <Form.Item label={`HTTP ${t('camouflage')}`}>
  1078. <Switch
  1079. checked={type === 'http'}
  1080. onChange={(checked) =>
  1081. form.setFieldValue(
  1082. ['streamSettings', 'tcpSettings', 'header'],
  1083. checked
  1084. ? {
  1085. type: 'http',
  1086. request: {
  1087. version: '1.1',
  1088. method: 'GET',
  1089. path: ['/'],
  1090. headers: {},
  1091. },
  1092. response: {
  1093. version: '1.1',
  1094. status: '200',
  1095. reason: 'OK',
  1096. headers: {},
  1097. },
  1098. }
  1099. : { type: 'none' },
  1100. )
  1101. }
  1102. />
  1103. </Form.Item>
  1104. {type === 'http' && (
  1105. <>
  1106. <Form.Item
  1107. label="Request method"
  1108. name={[
  1109. 'streamSettings', 'tcpSettings', 'header',
  1110. 'request', 'method',
  1111. ]}
  1112. >
  1113. <Input placeholder="GET" />
  1114. </Form.Item>
  1115. <Form.Item
  1116. label="Request version"
  1117. name={[
  1118. 'streamSettings', 'tcpSettings', 'header',
  1119. 'request', 'version',
  1120. ]}
  1121. >
  1122. <Input placeholder="1.1" />
  1123. </Form.Item>
  1124. <Form.Item
  1125. label={t('host')}
  1126. name={[
  1127. 'streamSettings',
  1128. 'tcpSettings',
  1129. 'header',
  1130. 'request',
  1131. 'headers',
  1132. 'Host',
  1133. ]}
  1134. normalize={(v: unknown) =>
  1135. typeof v === 'string'
  1136. ? v.split(',').map((s) => s.trim()).filter(Boolean)
  1137. : Array.isArray(v) ? v : []
  1138. }
  1139. getValueProps={(v: unknown) => ({
  1140. value: Array.isArray(v) ? v.join(',') : '',
  1141. })}
  1142. >
  1143. <Input placeholder="example.com,cdn.example.com" />
  1144. </Form.Item>
  1145. <Form.Item
  1146. label={t('path')}
  1147. name={[
  1148. 'streamSettings',
  1149. 'tcpSettings',
  1150. 'header',
  1151. 'request',
  1152. 'path',
  1153. ]}
  1154. normalize={(v: unknown) =>
  1155. typeof v === 'string'
  1156. ? v.split(',').map((s) => s.trim()).filter(Boolean)
  1157. : Array.isArray(v) ? v : ['/']
  1158. }
  1159. getValueProps={(v: unknown) => ({
  1160. value: Array.isArray(v) ? v.join(',') : '/',
  1161. })}
  1162. >
  1163. <Input placeholder="/,/api,/static" />
  1164. </Form.Item>
  1165. <Form.Item
  1166. label="Request headers"
  1167. name={[
  1168. 'streamSettings', 'tcpSettings', 'header',
  1169. 'request', 'headers',
  1170. ]}
  1171. >
  1172. <HeaderMapEditor mode="v2" />
  1173. </Form.Item>
  1174. <Form.Item
  1175. label="Response version"
  1176. name={[
  1177. 'streamSettings', 'tcpSettings', 'header',
  1178. 'response', 'version',
  1179. ]}
  1180. >
  1181. <Input placeholder="1.1" />
  1182. </Form.Item>
  1183. <Form.Item
  1184. label="Response status"
  1185. name={[
  1186. 'streamSettings', 'tcpSettings', 'header',
  1187. 'response', 'status',
  1188. ]}
  1189. >
  1190. <Input placeholder="200" />
  1191. </Form.Item>
  1192. <Form.Item
  1193. label="Response reason"
  1194. name={[
  1195. 'streamSettings', 'tcpSettings', 'header',
  1196. 'response', 'reason',
  1197. ]}
  1198. >
  1199. <Input placeholder="OK" />
  1200. </Form.Item>
  1201. <Form.Item
  1202. label="Response headers"
  1203. name={[
  1204. 'streamSettings', 'tcpSettings', 'header',
  1205. 'response', 'headers',
  1206. ]}
  1207. >
  1208. <HeaderMapEditor mode="v2" />
  1209. </Form.Item>
  1210. </>
  1211. )}
  1212. </>
  1213. );
  1214. }}
  1215. </Form.Item>
  1216. )}
  1217. {network === 'kcp' && (
  1218. <>
  1219. <Form.Item label="MTU" name={['streamSettings', 'kcpSettings', 'mtu']}>
  1220. <InputNumber min={0} />
  1221. </Form.Item>
  1222. <Form.Item label="TTI (ms)" name={['streamSettings', 'kcpSettings', 'tti']}>
  1223. <InputNumber min={0} />
  1224. </Form.Item>
  1225. <Form.Item
  1226. label="Uplink (MB/s)"
  1227. name={['streamSettings', 'kcpSettings', 'uplinkCapacity']}
  1228. >
  1229. <InputNumber min={0} />
  1230. </Form.Item>
  1231. <Form.Item
  1232. label="Downlink (MB/s)"
  1233. name={['streamSettings', 'kcpSettings', 'downlinkCapacity']}
  1234. >
  1235. <InputNumber min={0} />
  1236. </Form.Item>
  1237. <Form.Item
  1238. label="CWND multiplier"
  1239. name={['streamSettings', 'kcpSettings', 'cwndMultiplier']}
  1240. >
  1241. <InputNumber min={1} />
  1242. </Form.Item>
  1243. <Form.Item
  1244. label="Max sending window"
  1245. name={['streamSettings', 'kcpSettings', 'maxSendingWindow']}
  1246. >
  1247. <InputNumber min={0} />
  1248. </Form.Item>
  1249. </>
  1250. )}
  1251. {network === 'ws' && (
  1252. <>
  1253. <Form.Item label={t('host')} name={['streamSettings', 'wsSettings', 'host']}>
  1254. <Input />
  1255. </Form.Item>
  1256. <Form.Item label={t('path')} name={['streamSettings', 'wsSettings', 'path']}>
  1257. <Input />
  1258. </Form.Item>
  1259. <Form.Item
  1260. label="Heartbeat (s)"
  1261. name={['streamSettings', 'wsSettings', 'heartbeatPeriod']}
  1262. >
  1263. <InputNumber min={0} />
  1264. </Form.Item>
  1265. <Form.Item
  1266. label="Headers"
  1267. name={['streamSettings', 'wsSettings', 'headers']}
  1268. >
  1269. <HeaderMapEditor mode="v1" />
  1270. </Form.Item>
  1271. </>
  1272. )}
  1273. {network === 'grpc' && (
  1274. <>
  1275. <Form.Item
  1276. label="Service name"
  1277. name={['streamSettings', 'grpcSettings', 'serviceName']}
  1278. >
  1279. <Input />
  1280. </Form.Item>
  1281. <Form.Item
  1282. label="Authority"
  1283. name={['streamSettings', 'grpcSettings', 'authority']}
  1284. >
  1285. <Input />
  1286. </Form.Item>
  1287. <Form.Item
  1288. label="Multi mode"
  1289. name={['streamSettings', 'grpcSettings', 'multiMode']}
  1290. valuePropName="checked"
  1291. >
  1292. <Switch />
  1293. </Form.Item>
  1294. </>
  1295. )}
  1296. {network === 'httpupgrade' && (
  1297. <>
  1298. <Form.Item
  1299. label={t('host')}
  1300. name={['streamSettings', 'httpupgradeSettings', 'host']}
  1301. >
  1302. <Input />
  1303. </Form.Item>
  1304. <Form.Item
  1305. label={t('path')}
  1306. name={['streamSettings', 'httpupgradeSettings', 'path']}
  1307. >
  1308. <Input />
  1309. </Form.Item>
  1310. <Form.Item
  1311. label="Headers"
  1312. name={['streamSettings', 'httpupgradeSettings', 'headers']}
  1313. >
  1314. <HeaderMapEditor mode="v1" />
  1315. </Form.Item>
  1316. </>
  1317. )}
  1318. {network === 'xhttp' && (
  1319. <>
  1320. <Form.Item
  1321. label={t('host')}
  1322. name={['streamSettings', 'xhttpSettings', 'host']}
  1323. >
  1324. <Input />
  1325. </Form.Item>
  1326. <Form.Item
  1327. label={t('path')}
  1328. name={['streamSettings', 'xhttpSettings', 'path']}
  1329. >
  1330. <Input />
  1331. </Form.Item>
  1332. <Form.Item
  1333. label="Mode"
  1334. name={['streamSettings', 'xhttpSettings', 'mode']}
  1335. >
  1336. <Select options={MODE_OPTIONS} />
  1337. </Form.Item>
  1338. <Form.Item
  1339. label="Padding Bytes"
  1340. name={['streamSettings', 'xhttpSettings', 'xPaddingBytes']}
  1341. >
  1342. <Input />
  1343. </Form.Item>
  1344. <Form.Item
  1345. label="Headers"
  1346. name={['streamSettings', 'xhttpSettings', 'headers']}
  1347. >
  1348. <HeaderMapEditor mode="v1" />
  1349. </Form.Item>
  1350. {/* Padding obfs sub-section: gated by a Switch.
  1351. When on, four extra knobs (key/header/placement/
  1352. method) tune how Xray injects random padding to
  1353. disguise the post body shape. */}
  1354. <Form.Item
  1355. label="Padding obfs mode"
  1356. name={['streamSettings', 'xhttpSettings', 'xPaddingObfsMode']}
  1357. valuePropName="checked"
  1358. >
  1359. <Switch />
  1360. </Form.Item>
  1361. <Form.Item shouldUpdate noStyle>
  1362. {() => {
  1363. const obfs = !!form.getFieldValue([
  1364. 'streamSettings', 'xhttpSettings', 'xPaddingObfsMode',
  1365. ]);
  1366. if (!obfs) return null;
  1367. return (
  1368. <>
  1369. <Form.Item
  1370. label="Padding key"
  1371. name={['streamSettings', 'xhttpSettings', 'xPaddingKey']}
  1372. >
  1373. <Input placeholder="x_padding" />
  1374. </Form.Item>
  1375. <Form.Item
  1376. label="Padding header"
  1377. name={['streamSettings', 'xhttpSettings', 'xPaddingHeader']}
  1378. >
  1379. <Input placeholder="X-Padding" />
  1380. </Form.Item>
  1381. <Form.Item
  1382. label="Padding placement"
  1383. name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
  1384. >
  1385. <Select
  1386. options={[
  1387. { value: '', label: 'Default (queryInHeader)' },
  1388. { value: 'queryInHeader', label: 'queryInHeader' },
  1389. { value: 'header', label: 'header' },
  1390. { value: 'cookie', label: 'cookie' },
  1391. { value: 'query', label: 'query' },
  1392. ]}
  1393. />
  1394. </Form.Item>
  1395. <Form.Item
  1396. label="Padding method"
  1397. name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
  1398. >
  1399. <Select
  1400. options={[
  1401. { value: '', label: 'Default (repeat-x)' },
  1402. { value: 'repeat-x', label: 'repeat-x' },
  1403. { value: 'tokenish', label: 'tokenish' },
  1404. ]}
  1405. />
  1406. </Form.Item>
  1407. </>
  1408. );
  1409. }}
  1410. </Form.Item>
  1411. <Form.Item
  1412. label="Uplink HTTP method"
  1413. name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
  1414. >
  1415. <Form.Item shouldUpdate noStyle>
  1416. {() => {
  1417. const mode = form.getFieldValue([
  1418. 'streamSettings', 'xhttpSettings', 'mode',
  1419. ]);
  1420. return (
  1421. <Select
  1422. options={[
  1423. { value: '', label: 'Default (POST)' },
  1424. { value: 'POST', label: 'POST' },
  1425. { value: 'PUT', label: 'PUT' },
  1426. { value: 'GET', label: 'GET (packet-up only)', disabled: mode !== 'packet-up' },
  1427. ]}
  1428. />
  1429. );
  1430. }}
  1431. </Form.Item>
  1432. </Form.Item>
  1433. {/* Session + sequence + uplinkData placements:
  1434. three orthogonal slots Xray uses to thread
  1435. request metadata through the transport
  1436. (path / header / cookie / query). Key field
  1437. only matters when placement is not 'path'. */}
  1438. <Form.Item
  1439. label="Session placement"
  1440. name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
  1441. >
  1442. <Select
  1443. options={[
  1444. { value: '', label: 'Default (path)' },
  1445. { value: 'path', label: 'path' },
  1446. { value: 'header', label: 'header' },
  1447. { value: 'cookie', label: 'cookie' },
  1448. { value: 'query', label: 'query' },
  1449. ]}
  1450. />
  1451. </Form.Item>
  1452. <Form.Item shouldUpdate noStyle>
  1453. {() => {
  1454. const placement = form.getFieldValue([
  1455. 'streamSettings', 'xhttpSettings', 'sessionPlacement',
  1456. ]);
  1457. if (!placement || placement === 'path') return null;
  1458. return (
  1459. <Form.Item
  1460. label="Session key"
  1461. name={['streamSettings', 'xhttpSettings', 'sessionKey']}
  1462. >
  1463. <Input placeholder="x_session" />
  1464. </Form.Item>
  1465. );
  1466. }}
  1467. </Form.Item>
  1468. <Form.Item
  1469. label="Sequence placement"
  1470. name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
  1471. >
  1472. <Select
  1473. options={[
  1474. { value: '', label: 'Default (path)' },
  1475. { value: 'path', label: 'path' },
  1476. { value: 'header', label: 'header' },
  1477. { value: 'cookie', label: 'cookie' },
  1478. { value: 'query', label: 'query' },
  1479. ]}
  1480. />
  1481. </Form.Item>
  1482. <Form.Item shouldUpdate noStyle>
  1483. {() => {
  1484. const placement = form.getFieldValue([
  1485. 'streamSettings', 'xhttpSettings', 'seqPlacement',
  1486. ]);
  1487. if (!placement || placement === 'path') return null;
  1488. return (
  1489. <Form.Item
  1490. label="Sequence key"
  1491. name={['streamSettings', 'xhttpSettings', 'seqKey']}
  1492. >
  1493. <Input placeholder="x_seq" />
  1494. </Form.Item>
  1495. );
  1496. }}
  1497. </Form.Item>
  1498. {/* Mode-conditional sub-sections. */}
  1499. <Form.Item shouldUpdate noStyle>
  1500. {() => {
  1501. const mode = form.getFieldValue([
  1502. 'streamSettings', 'xhttpSettings', 'mode',
  1503. ]);
  1504. if (mode !== 'packet-up') return null;
  1505. return (
  1506. <>
  1507. <Form.Item
  1508. label="Min upload interval (ms)"
  1509. name={['streamSettings', 'xhttpSettings', 'scMinPostsIntervalMs']}
  1510. >
  1511. <Input placeholder="30" />
  1512. </Form.Item>
  1513. <Form.Item
  1514. label="Max upload size (bytes)"
  1515. name={['streamSettings', 'xhttpSettings', 'scMaxEachPostBytes']}
  1516. >
  1517. <Input placeholder="1000000" />
  1518. </Form.Item>
  1519. <Form.Item
  1520. label="Uplink data placement"
  1521. name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
  1522. >
  1523. <Select
  1524. options={[
  1525. { value: '', label: 'Default (body)' },
  1526. { value: 'body', label: 'body' },
  1527. { value: 'header', label: 'header' },
  1528. { value: 'cookie', label: 'cookie' },
  1529. { value: 'query', label: 'query' },
  1530. ]}
  1531. />
  1532. </Form.Item>
  1533. <Form.Item shouldUpdate noStyle>
  1534. {() => {
  1535. const place = form.getFieldValue([
  1536. 'streamSettings', 'xhttpSettings', 'uplinkDataPlacement',
  1537. ]);
  1538. if (!place || place === 'body') return null;
  1539. return (
  1540. <>
  1541. <Form.Item
  1542. label="Uplink data key"
  1543. name={['streamSettings', 'xhttpSettings', 'uplinkDataKey']}
  1544. >
  1545. <Input placeholder="x_data" />
  1546. </Form.Item>
  1547. <Form.Item
  1548. label="Uplink chunk size"
  1549. name={['streamSettings', 'xhttpSettings', 'uplinkChunkSize']}
  1550. >
  1551. <InputNumber
  1552. min={0}
  1553. placeholder="0 (unlimited)"
  1554. style={{ width: '100%' }}
  1555. />
  1556. </Form.Item>
  1557. </>
  1558. );
  1559. }}
  1560. </Form.Item>
  1561. </>
  1562. );
  1563. }}
  1564. </Form.Item>
  1565. <Form.Item shouldUpdate noStyle>
  1566. {() => {
  1567. const mode = form.getFieldValue([
  1568. 'streamSettings', 'xhttpSettings', 'mode',
  1569. ]);
  1570. if (mode !== 'stream-up' && mode !== 'stream-one') return null;
  1571. return (
  1572. <Form.Item
  1573. label="No gRPC header"
  1574. name={['streamSettings', 'xhttpSettings', 'noGRPCHeader']}
  1575. valuePropName="checked"
  1576. >
  1577. <Switch />
  1578. </Form.Item>
  1579. );
  1580. }}
  1581. </Form.Item>
  1582. {/* XMUX is the connection-multiplexing layer
  1583. xHTTP uses to fan out parallel requests over
  1584. a small pool of upstream connections. UI-only
  1585. toggle (enableXmux) hides the 6 nested knobs
  1586. when off. */}
  1587. <Form.Item
  1588. label="XMUX"
  1589. name={['streamSettings', 'xhttpSettings', 'enableXmux']}
  1590. valuePropName="checked"
  1591. >
  1592. <Switch />
  1593. </Form.Item>
  1594. <Form.Item shouldUpdate noStyle>
  1595. {() => {
  1596. if (!form.getFieldValue([
  1597. 'streamSettings', 'xhttpSettings', 'enableXmux',
  1598. ])) return null;
  1599. return (
  1600. <>
  1601. <Form.Item
  1602. label="Max concurrency"
  1603. name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConcurrency']}
  1604. >
  1605. <Input placeholder="16-32" />
  1606. </Form.Item>
  1607. <Form.Item
  1608. label="Max connections"
  1609. name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConnections']}
  1610. >
  1611. <Input placeholder="0" />
  1612. </Form.Item>
  1613. <Form.Item
  1614. label="Max reuse times"
  1615. name={['streamSettings', 'xhttpSettings', 'xmux', 'cMaxReuseTimes']}
  1616. >
  1617. <Input />
  1618. </Form.Item>
  1619. <Form.Item
  1620. label="Max request times"
  1621. name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxRequestTimes']}
  1622. >
  1623. <Input placeholder="600-900" />
  1624. </Form.Item>
  1625. <Form.Item
  1626. label="Max reusable secs"
  1627. name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxReusableSecs']}
  1628. >
  1629. <Input placeholder="1800-3000" />
  1630. </Form.Item>
  1631. <Form.Item
  1632. label="Keep alive period"
  1633. name={['streamSettings', 'xhttpSettings', 'xmux', 'hKeepAlivePeriod']}
  1634. >
  1635. <InputNumber min={0} style={{ width: '100%' }} />
  1636. </Form.Item>
  1637. </>
  1638. );
  1639. }}
  1640. </Form.Item>
  1641. </>
  1642. )}
  1643. {network === 'hysteria' && (
  1644. <>
  1645. <Form.Item
  1646. label="Auth password"
  1647. name={['streamSettings', 'hysteriaSettings', 'auth']}
  1648. >
  1649. <Input />
  1650. </Form.Item>
  1651. <Form.Item
  1652. label="UDP idle timeout (s)"
  1653. name={['streamSettings', 'hysteriaSettings', 'udpIdleTimeout']}
  1654. >
  1655. <InputNumber min={1} style={{ width: '100%' }} />
  1656. </Form.Item>
  1657. </>
  1658. )}
  1659. </>
  1660. )}
  1661. {tlsFlowAllowed && (
  1662. <Form.Item label="Flow" name={['settings', 'flow']}>
  1663. <Select
  1664. allowClear
  1665. placeholder={t('none')}
  1666. options={FLOW_OPTIONS}
  1667. />
  1668. </Form.Item>
  1669. )}
  1670. {/* Vision seed knobs only meaningful for the exact
  1671. xtls-rprx-vision flow, on TCP+(tls|reality). The
  1672. legacy class gated this on `canEnableVisionSeed()`
  1673. — same condition encoded inline here. */}
  1674. <Form.Item shouldUpdate noStyle>
  1675. {() => {
  1676. const flow =
  1677. (form.getFieldValue(['settings', 'flow']) ?? '') as string;
  1678. if (!(tlsFlowAllowed && flow === 'xtls-rprx-vision')) return null;
  1679. return (
  1680. <>
  1681. <Form.Item label="Vision testpre" name={['settings', 'testpre']}>
  1682. <InputNumber min={0} style={{ width: '100%' }} />
  1683. </Form.Item>
  1684. <Form.Item
  1685. label="Vision testseed"
  1686. name={['settings', 'testseed']}
  1687. normalize={(v: unknown) =>
  1688. Array.isArray(v)
  1689. ? v
  1690. .map((x) => Number(x))
  1691. .filter((n) => Number.isInteger(n) && n > 0)
  1692. : []
  1693. }
  1694. >
  1695. <Select
  1696. mode="tags"
  1697. tokenSeparators={[',', ' ']}
  1698. placeholder="four positive integers"
  1699. />
  1700. </Form.Item>
  1701. </>
  1702. );
  1703. }}
  1704. </Form.Item>
  1705. {streamAllowed && network && (
  1706. <Form.Item label={t('security')}>
  1707. <Radio.Group
  1708. value={security}
  1709. buttonStyle="solid"
  1710. onChange={(e) => onSecurityChange(e.target.value as string)}
  1711. >
  1712. <Radio.Button value="none">{t('none')}</Radio.Button>
  1713. {tlsAllowed && <Radio.Button value="tls">TLS</Radio.Button>}
  1714. {realityAllowed && <Radio.Button value="reality">Reality</Radio.Button>}
  1715. </Radio.Group>
  1716. </Form.Item>
  1717. )}
  1718. {security === 'tls' && tlsAllowed && (
  1719. <>
  1720. <Form.Item
  1721. label="SNI"
  1722. name={['streamSettings', 'tlsSettings', 'serverName']}
  1723. >
  1724. <Input placeholder="server name" />
  1725. </Form.Item>
  1726. <Form.Item
  1727. label="uTLS"
  1728. name={['streamSettings', 'tlsSettings', 'fingerprint']}
  1729. >
  1730. <Select
  1731. allowClear
  1732. placeholder={t('none')}
  1733. options={UTLS_OPTIONS}
  1734. />
  1735. </Form.Item>
  1736. <Form.Item
  1737. label="ALPN"
  1738. name={['streamSettings', 'tlsSettings', 'alpn']}
  1739. >
  1740. <Select mode="multiple" options={ALPN_OPTIONS} />
  1741. </Form.Item>
  1742. <Form.Item
  1743. label="ECH"
  1744. name={['streamSettings', 'tlsSettings', 'echConfigList']}
  1745. >
  1746. <Input />
  1747. </Form.Item>
  1748. <Form.Item
  1749. label="Verify peer name"
  1750. name={['streamSettings', 'tlsSettings', 'verifyPeerCertByName']}
  1751. >
  1752. <Input placeholder="cloudflare-dns.com" />
  1753. </Form.Item>
  1754. <Form.Item
  1755. label="Pinned SHA256"
  1756. name={['streamSettings', 'tlsSettings', 'pinnedPeerCertSha256']}
  1757. >
  1758. <Input placeholder="base64 SHA256" />
  1759. </Form.Item>
  1760. </>
  1761. )}
  1762. {security === 'reality' && realityAllowed && (
  1763. <>
  1764. <Form.Item
  1765. label="SNI"
  1766. name={['streamSettings', 'realitySettings', 'serverName']}
  1767. >
  1768. <Input />
  1769. </Form.Item>
  1770. <Form.Item
  1771. label="uTLS"
  1772. name={['streamSettings', 'realitySettings', 'fingerprint']}
  1773. >
  1774. <Select options={UTLS_OPTIONS} />
  1775. </Form.Item>
  1776. <Form.Item
  1777. label="Short ID"
  1778. name={['streamSettings', 'realitySettings', 'shortId']}
  1779. >
  1780. <Input />
  1781. </Form.Item>
  1782. <Form.Item
  1783. label="SpiderX"
  1784. name={['streamSettings', 'realitySettings', 'spiderX']}
  1785. >
  1786. <Input />
  1787. </Form.Item>
  1788. <Form.Item
  1789. label={t('pages.inbounds.publicKey')}
  1790. name={['streamSettings', 'realitySettings', 'publicKey']}
  1791. >
  1792. <Input.TextArea autoSize={{ minRows: 2 }} />
  1793. </Form.Item>
  1794. <Form.Item
  1795. label="mldsa65 verify"
  1796. name={['streamSettings', 'realitySettings', 'mldsa65Verify']}
  1797. >
  1798. <Input.TextArea autoSize={{ minRows: 2 }} />
  1799. </Form.Item>
  1800. </>
  1801. )}
  1802. {((streamAllowed && network) || !streamAllowed) && (
  1803. <Form.Item shouldUpdate noStyle>
  1804. {() => {
  1805. const hasSockopt = !!form.getFieldValue([
  1806. 'streamSettings',
  1807. 'sockopt',
  1808. ]);
  1809. return (
  1810. <>
  1811. <Form.Item label="Sockopts">
  1812. <Switch
  1813. checked={hasSockopt}
  1814. onChange={(checked) => {
  1815. form.setFieldValue(
  1816. ['streamSettings', 'sockopt'],
  1817. checked
  1818. ? {
  1819. acceptProxyProtocol: false,
  1820. tcpFastOpen: false,
  1821. mark: 0,
  1822. tproxy: 'off',
  1823. tcpMptcp: false,
  1824. penetrate: false,
  1825. domainStrategy: 'UseIP',
  1826. tcpMaxSeg: 1440,
  1827. dialerProxy: '',
  1828. tcpKeepAliveInterval: 0,
  1829. tcpKeepAliveIdle: 300,
  1830. tcpUserTimeout: 10000,
  1831. tcpcongestion: 'bbr',
  1832. V6Only: false,
  1833. tcpWindowClamp: 600,
  1834. interfaceName: '',
  1835. trustedXForwardedFor: [],
  1836. }
  1837. : undefined,
  1838. );
  1839. }}
  1840. />
  1841. </Form.Item>
  1842. {hasSockopt && (
  1843. <>
  1844. <Form.Item
  1845. label="Dialer proxy"
  1846. name={['streamSettings', 'sockopt', 'dialerProxy']}
  1847. >
  1848. <Input />
  1849. </Form.Item>
  1850. <Form.Item
  1851. label="Domain strategy"
  1852. name={['streamSettings', 'sockopt', 'domainStrategy']}
  1853. >
  1854. <Select
  1855. options={ADDRESS_PORT_STRATEGY_OPTIONS}
  1856. />
  1857. </Form.Item>
  1858. <Form.Item
  1859. label="Keep alive interval"
  1860. name={['streamSettings', 'sockopt', 'tcpKeepAliveInterval']}
  1861. >
  1862. <InputNumber min={0} />
  1863. </Form.Item>
  1864. <Form.Item
  1865. label="TCP Fast Open"
  1866. name={['streamSettings', 'sockopt', 'tcpFastOpen']}
  1867. valuePropName="checked"
  1868. >
  1869. <Switch />
  1870. </Form.Item>
  1871. <Form.Item
  1872. label="Multipath TCP"
  1873. name={['streamSettings', 'sockopt', 'tcpMptcp']}
  1874. valuePropName="checked"
  1875. >
  1876. <Switch />
  1877. </Form.Item>
  1878. <Form.Item
  1879. label="Penetrate"
  1880. name={['streamSettings', 'sockopt', 'penetrate']}
  1881. valuePropName="checked"
  1882. >
  1883. <Switch />
  1884. </Form.Item>
  1885. <Form.Item
  1886. label="Mark (fwmark)"
  1887. name={['streamSettings', 'sockopt', 'mark']}
  1888. >
  1889. <InputNumber min={0} />
  1890. </Form.Item>
  1891. <Form.Item
  1892. label="Interface"
  1893. name={['streamSettings', 'sockopt', 'interfaceName']}
  1894. >
  1895. <Input />
  1896. </Form.Item>
  1897. <Form.Item
  1898. label="TProxy"
  1899. name={['streamSettings', 'sockopt', 'tproxy']}
  1900. >
  1901. <Select
  1902. options={[
  1903. { value: 'off', label: 'off' },
  1904. { value: 'redirect', label: 'redirect' },
  1905. { value: 'tproxy', label: 'tproxy' },
  1906. ]}
  1907. />
  1908. </Form.Item>
  1909. <Form.Item
  1910. label="TCP congestion"
  1911. name={['streamSettings', 'sockopt', 'tcpcongestion']}
  1912. >
  1913. <Select
  1914. options={Object.values(TCP_CONGESTION_OPTION).map((v) => ({
  1915. value: v,
  1916. label: v,
  1917. }))}
  1918. />
  1919. </Form.Item>
  1920. <Form.Item
  1921. label="IPv6 only"
  1922. name={['streamSettings', 'sockopt', 'V6Only']}
  1923. valuePropName="checked"
  1924. >
  1925. <Switch />
  1926. </Form.Item>
  1927. <Form.Item
  1928. label="Accept proxy protocol"
  1929. name={['streamSettings', 'sockopt', 'acceptProxyProtocol']}
  1930. valuePropName="checked"
  1931. >
  1932. <Switch />
  1933. </Form.Item>
  1934. <Form.Item
  1935. label="TCP user timeout (ms)"
  1936. name={['streamSettings', 'sockopt', 'tcpUserTimeout']}
  1937. >
  1938. <InputNumber min={0} style={{ width: '100%' }} />
  1939. </Form.Item>
  1940. <Form.Item
  1941. label="TCP keep-alive idle (s)"
  1942. name={['streamSettings', 'sockopt', 'tcpKeepAliveIdle']}
  1943. >
  1944. <InputNumber min={0} style={{ width: '100%' }} />
  1945. </Form.Item>
  1946. <Form.Item
  1947. label="TCP max segment"
  1948. name={['streamSettings', 'sockopt', 'tcpMaxSeg']}
  1949. >
  1950. <InputNumber min={0} style={{ width: '100%' }} />
  1951. </Form.Item>
  1952. <Form.Item
  1953. label="TCP window clamp"
  1954. name={['streamSettings', 'sockopt', 'tcpWindowClamp']}
  1955. >
  1956. <InputNumber min={0} style={{ width: '100%' }} />
  1957. </Form.Item>
  1958. <Form.Item
  1959. label="Trusted X-Forwarded-For"
  1960. name={['streamSettings', 'sockopt', 'trustedXForwardedFor']}
  1961. >
  1962. <Select
  1963. mode="tags"
  1964. tokenSeparators={[',', ' ']}
  1965. placeholder="trusted-proxy.example,10.0.0.0/8"
  1966. />
  1967. </Form.Item>
  1968. </>
  1969. )}
  1970. </>
  1971. );
  1972. }}
  1973. </Form.Item>
  1974. )}
  1975. <FinalMaskForm
  1976. name={['streamSettings', 'finalmask']}
  1977. network={network}
  1978. protocol={protocol}
  1979. form={form}
  1980. />
  1981. {(() => {
  1982. const flow = (form.getFieldValue(['settings', 'flow']) ?? '') as string;
  1983. if (!isMuxAllowed(protocol, flow, network)) return null;
  1984. return (
  1985. <Form.Item shouldUpdate noStyle>
  1986. {() => {
  1987. const muxEnabled = !!form.getFieldValue(['mux', 'enabled']);
  1988. return (
  1989. <>
  1990. <Form.Item
  1991. label={t('pages.settings.mux')}
  1992. name={['mux', 'enabled']}
  1993. valuePropName="checked"
  1994. >
  1995. <Switch />
  1996. </Form.Item>
  1997. {muxEnabled && (
  1998. <>
  1999. <Form.Item
  2000. label="Concurrency"
  2001. name={['mux', 'concurrency']}
  2002. >
  2003. <InputNumber min={-1} max={1024} />
  2004. </Form.Item>
  2005. <Form.Item
  2006. label="xudp concurrency"
  2007. name={['mux', 'xudpConcurrency']}
  2008. >
  2009. <InputNumber min={-1} max={1024} />
  2010. </Form.Item>
  2011. <Form.Item
  2012. label="xudp UDP 443"
  2013. name={['mux', 'xudpProxyUDP443']}
  2014. >
  2015. <Select
  2016. options={['reject', 'allow', 'skip'].map((v) => ({
  2017. value: v,
  2018. label: v,
  2019. }))}
  2020. />
  2021. </Form.Item>
  2022. </>
  2023. )}
  2024. </>
  2025. );
  2026. }}
  2027. </Form.Item>
  2028. );
  2029. })()}
  2030. </>
  2031. ),
  2032. },
  2033. {
  2034. key: '2',
  2035. label: 'JSON',
  2036. children: (
  2037. <Space orientation="vertical" size={10} style={{ width: '100%', marginTop: 10 }}>
  2038. <Input.Search
  2039. value={linkInput}
  2040. placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"
  2041. enterButton="Import"
  2042. onChange={(e) => setLinkInput(e.target.value)}
  2043. onSearch={importLink}
  2044. />
  2045. <JsonEditor
  2046. value={jsonText}
  2047. onChange={(next) => {
  2048. setJsonText(next);
  2049. setJsonDirty(true);
  2050. }}
  2051. minHeight="360px"
  2052. maxHeight="600px"
  2053. />
  2054. </Space>
  2055. ),
  2056. },
  2057. ]}
  2058. />
  2059. </Form>
  2060. </Modal>
  2061. </>
  2062. );
  2063. }