OutboundFormModal.tsx 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471
  1. import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import {
  4. Button,
  5. Form,
  6. Input,
  7. InputNumber,
  8. message,
  9. Modal,
  10. Radio,
  11. Select,
  12. Space,
  13. Switch,
  14. Tabs,
  15. Checkbox,
  16. } from 'antd';
  17. import { SyncOutlined, PlusOutlined, MinusOutlined, DeleteOutlined } from '@ant-design/icons';
  18. import { Wireguard } from '@/utils';
  19. import InputAddon from '@/components/InputAddon';
  20. import {
  21. Outbound,
  22. Protocols,
  23. SSMethods,
  24. TLS_FLOW_CONTROL,
  25. UTLS_FINGERPRINT,
  26. ALPN_OPTION,
  27. SNIFFING_OPTION,
  28. USERS_SECURITY,
  29. OutboundDomainStrategies,
  30. WireguardDomainStrategy,
  31. Address_Port_Strategy,
  32. MODE_OPTION,
  33. DNSRuleActions,
  34. } from '@/models/outbound';
  35. import FinalMaskForm from '@/components/FinalMaskForm';
  36. import JsonEditor from '@/components/JsonEditor';
  37. import './OutboundFormModal.css';
  38. interface OutboundFormModalProps {
  39. open: boolean;
  40. outbound: Record<string, unknown> | null;
  41. existingTags: string[];
  42. onClose: () => void;
  43. onConfirm: (outbound: Record<string, unknown>) => void;
  44. }
  45. const PROTOCOL_OPTIONS = Object.values(Protocols) as string[];
  46. const SECURITY_OPTIONS = Object.values(USERS_SECURITY) as string[];
  47. const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL) as string[];
  48. const UTLS_OPTIONS = Object.values(UTLS_FINGERPRINT) as string[];
  49. const ALPN_OPTIONS = Object.values(ALPN_OPTION) as string[];
  50. const NETWORKS = ['tcp', 'kcp', 'ws', 'grpc', 'httpupgrade', 'xhttp'];
  51. const NETWORK_LABELS: Record<string, string> = {
  52. tcp: 'TCP (RAW)',
  53. kcp: 'mKCP',
  54. ws: 'WebSocket',
  55. grpc: 'gRPC',
  56. httpupgrade: 'HTTPUpgrade',
  57. xhttp: 'XHTTP',
  58. };
  59. export default function OutboundFormModal({
  60. open,
  61. outbound: outboundProp,
  62. existingTags,
  63. onClose,
  64. onConfirm,
  65. }: OutboundFormModalProps) {
  66. const { t } = useTranslation();
  67. const [messageApi, messageContextHolder] = message.useMessage();
  68. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  69. const outboundRef = useRef<any>(null);
  70. const [, setTick] = useState(0);
  71. const [activeKey, setActiveKey] = useState('1');
  72. const [linkInput, setLinkInput] = useState('');
  73. const [advancedJson, setAdvancedJson] = useState('');
  74. const revertingTabRef = useRef(false);
  75. const isEdit = outboundProp != null;
  76. const refresh = useCallback(() => setTick((n) => n + 1), []);
  77. const primeAdvancedJson = useCallback(() => {
  78. const ob = outboundRef.current;
  79. if (!ob) {
  80. setAdvancedJson('');
  81. return;
  82. }
  83. try {
  84. setAdvancedJson(JSON.stringify(ob.toJson(), null, 2));
  85. } catch {
  86. setAdvancedJson('');
  87. }
  88. }, []);
  89. useEffect(() => {
  90. if (!open) return;
  91. outboundRef.current = outboundProp
  92. ? Outbound.fromJson(outboundProp)
  93. : new Outbound();
  94. setActiveKey('1');
  95. setLinkInput('');
  96. primeAdvancedJson();
  97. refresh();
  98. // eslint-disable-next-line react-hooks/exhaustive-deps
  99. }, [open, outboundProp]);
  100. function applyAdvancedJsonToForm(): boolean {
  101. const raw = advancedJson.trim();
  102. if (!raw) return true;
  103. const ob = outboundRef.current;
  104. let currentJson = '';
  105. try {
  106. currentJson = JSON.stringify(ob?.toJson() ?? {}, null, 2);
  107. } catch {
  108. /* ignore */
  109. }
  110. if (raw === currentJson.trim()) return true;
  111. let parsed;
  112. try {
  113. parsed = JSON.parse(raw);
  114. } catch (e) {
  115. messageApi.error(`JSON: ${(e as Error).message}`);
  116. return false;
  117. }
  118. try {
  119. const fallbackTag = ob?.tag;
  120. const next = Outbound.fromJson(parsed);
  121. if (!next.tag && fallbackTag) next.tag = fallbackTag;
  122. outboundRef.current = next;
  123. refresh();
  124. return true;
  125. } catch (e) {
  126. messageApi.error(`JSON: ${(e as Error).message}`);
  127. return false;
  128. }
  129. }
  130. function onTabChange(key: string) {
  131. if (document.activeElement instanceof HTMLElement) {
  132. document.activeElement.blur();
  133. }
  134. if (revertingTabRef.current) {
  135. revertingTabRef.current = false;
  136. setActiveKey(key);
  137. return;
  138. }
  139. const prev = activeKey;
  140. if (key === '2') {
  141. primeAdvancedJson();
  142. setActiveKey(key);
  143. } else if (key === '1' && prev === '2') {
  144. if (!applyAdvancedJsonToForm()) {
  145. revertingTabRef.current = true;
  146. setActiveKey('2');
  147. } else {
  148. setActiveKey(key);
  149. }
  150. } else {
  151. setActiveKey(key);
  152. }
  153. }
  154. const ob = outboundRef.current;
  155. const proto = ob?.protocol;
  156. const isVMess = proto === Protocols.VMess;
  157. const isVLESS = proto === Protocols.VLESS;
  158. const isVMessOrVLess = isVMess || isVLESS;
  159. const isTrojan = proto === Protocols.Trojan;
  160. const isShadowsocks = proto === Protocols.Shadowsocks;
  161. const isFreedom = proto === Protocols.Freedom;
  162. const isBlackhole = proto === Protocols.Blackhole;
  163. const isDNS = proto === Protocols.DNS;
  164. const isWireguard = proto === Protocols.Wireguard;
  165. const isHysteria = proto === Protocols.Hysteria;
  166. const isLoopback = proto === Protocols.Loopback;
  167. function onProtocolChange(next: string) {
  168. if (!ob) return;
  169. ob.protocol = next;
  170. refresh();
  171. }
  172. function streamNetworkChange(next: string) {
  173. if (!ob?.stream) return;
  174. ob.stream.network = next;
  175. if (!ob.canEnableTls()) ob.stream.security = 'none';
  176. refresh();
  177. }
  178. function regenerateWgKeys() {
  179. if (!ob?.settings) return;
  180. const pair = Wireguard.generateKeypair();
  181. ob.settings.secretKey = pair.privateKey;
  182. ob.settings.pubKey = pair.publicKey;
  183. refresh();
  184. }
  185. const duplicateTag = useMemo(() => {
  186. if (!ob?.tag) return false;
  187. const myTag = ob.tag.trim();
  188. if (!myTag) return false;
  189. if (isEdit && (outboundProp?.tag as string | undefined) === myTag) return false;
  190. return (existingTags || []).includes(myTag);
  191. }, [ob?.tag, existingTags, isEdit, outboundProp]);
  192. const tagEmpty = !ob?.tag?.trim();
  193. const tagValidateStatus: 'error' | 'warning' | 'success' = tagEmpty
  194. ? 'error'
  195. : duplicateTag
  196. ? 'warning'
  197. : 'success';
  198. const tagHelp = tagEmpty
  199. ? 'Tag is required'
  200. : duplicateTag
  201. ? 'Tag already used by another outbound'
  202. : '';
  203. function onOk() {
  204. if (!ob) return;
  205. if (activeKey === '2' && !applyAdvancedJsonToForm()) return;
  206. if (!ob.tag?.trim()) {
  207. messageApi.error('Tag is required');
  208. return;
  209. }
  210. if (duplicateTag) {
  211. messageApi.error('Tag already used by another outbound');
  212. return;
  213. }
  214. onConfirm(ob.toJson());
  215. }
  216. function convertLink() {
  217. const link = linkInput.trim();
  218. if (!link) return;
  219. try {
  220. const next = Outbound.fromLink(link);
  221. if (!next) {
  222. messageApi.error('Wrong Link!');
  223. return;
  224. }
  225. outboundRef.current = next;
  226. primeAdvancedJson();
  227. setLinkInput('');
  228. messageApi.success('Link imported successfully...');
  229. setActiveKey('1');
  230. refresh();
  231. } catch (e) {
  232. messageApi.error(`Link parse: ${(e as Error).message}`);
  233. }
  234. }
  235. const title = isEdit
  236. ? `${t('edit')} ${t('pages.xray.Outbounds')}`
  237. : `+ ${t('pages.xray.Outbounds')}`;
  238. const okText = isEdit ? t('pages.clients.submitEdit') : t('create');
  239. if (!ob) {
  240. return (
  241. <>
  242. {messageContextHolder}
  243. <Modal open={open} title={title} footer={null} onCancel={onClose} />
  244. </>
  245. );
  246. }
  247. return (
  248. <>
  249. {messageContextHolder}
  250. <Modal
  251. open={open}
  252. title={title}
  253. okText={okText}
  254. cancelText={t('close')}
  255. mask={{ closable: false }}
  256. width={780}
  257. onOk={onOk}
  258. onCancel={onClose}
  259. >
  260. <Tabs
  261. activeKey={activeKey}
  262. onChange={onTabChange}
  263. items={[
  264. {
  265. key: '1',
  266. label: t('pages.xray.basicTemplate'),
  267. children: (
  268. <>
  269. <Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 14 } }}>
  270. <Form.Item label={t('protocol')}>
  271. <Select
  272. value={proto}
  273. onChange={onProtocolChange}
  274. options={PROTOCOL_OPTIONS.map((p) => ({ value: p, label: p }))}
  275. />
  276. </Form.Item>
  277. <Form.Item label="Tag" validateStatus={tagValidateStatus} help={tagHelp} hasFeedback>
  278. <Input
  279. value={ob.tag}
  280. placeholder="unique-tag"
  281. onChange={(e) => {
  282. ob.tag = e.target.value;
  283. refresh();
  284. }}
  285. />
  286. </Form.Item>
  287. <Form.Item label="Send through">
  288. <Input
  289. value={ob.sendThrough || ''}
  290. placeholder="local IP"
  291. onChange={(e) => {
  292. ob.sendThrough = e.target.value;
  293. refresh();
  294. }}
  295. />
  296. </Form.Item>
  297. {isFreedom && <FreedomFields ob={ob} refresh={refresh} />}
  298. {isBlackhole && (
  299. <Form.Item label="Response Type">
  300. <Select
  301. value={ob.settings.type || ''}
  302. onChange={(v) => { ob.settings.type = v; refresh(); }}
  303. options={[
  304. { value: '', label: '(empty)' },
  305. { value: 'none', label: 'none' },
  306. { value: 'http', label: 'http' },
  307. ]}
  308. />
  309. </Form.Item>
  310. )}
  311. {isLoopback && (
  312. <Form.Item label="Inbound tag">
  313. <Input
  314. value={ob.settings.inboundTag || ''}
  315. placeholder="inbound tag using in routing rules"
  316. onChange={(e) => { ob.settings.inboundTag = e.target.value; refresh(); }}
  317. />
  318. </Form.Item>
  319. )}
  320. {isDNS && <DnsFields ob={ob} refresh={refresh} t={t} />}
  321. {isWireguard && <WireguardFields ob={ob} refresh={refresh} regenerate={regenerateWgKeys} t={t} />}
  322. {ob.hasAddressPort() && (
  323. <>
  324. <Form.Item label={t('pages.inbounds.address')}>
  325. <Input
  326. value={ob.settings.address || ''}
  327. onChange={(e) => { ob.settings.address = e.target.value; refresh(); }}
  328. />
  329. </Form.Item>
  330. <Form.Item label={t('pages.inbounds.port')}>
  331. <InputNumber
  332. value={ob.settings.port || 0}
  333. min={1}
  334. max={65535}
  335. onChange={(v) => { ob.settings.port = Number(v) || 0; refresh(); }}
  336. />
  337. </Form.Item>
  338. </>
  339. )}
  340. {isVMessOrVLess && (
  341. <VMessVLessFields ob={ob} refresh={refresh} isVMess={isVMess} isVLESS={isVLESS} t={t} />
  342. )}
  343. {(isTrojan || isShadowsocks) && (
  344. <Form.Item label={t('password')}>
  345. <Input
  346. value={ob.settings.password || ''}
  347. onChange={(e) => { ob.settings.password = e.target.value; refresh(); }}
  348. />
  349. </Form.Item>
  350. )}
  351. {isShadowsocks && (
  352. <>
  353. <Form.Item label={t('encryption')}>
  354. <Select
  355. value={ob.settings.method}
  356. onChange={(v) => { ob.settings.method = v; refresh(); }}
  357. options={Object.entries(SSMethods).map(([k, v]) => ({ value: v as string, label: k }))}
  358. />
  359. </Form.Item>
  360. <Form.Item label="UDP over TCP">
  361. <Switch checked={!!ob.settings.uot} onChange={(v) => { ob.settings.uot = v; refresh(); }} />
  362. </Form.Item>
  363. <Form.Item label="UoT version">
  364. <InputNumber
  365. value={ob.settings.UoTVersion ?? 1}
  366. min={1}
  367. max={2}
  368. onChange={(v) => { ob.settings.UoTVersion = Number(v) || 1; refresh(); }}
  369. />
  370. </Form.Item>
  371. </>
  372. )}
  373. {ob.hasUsername() && (
  374. <>
  375. <Form.Item label={t('username')}>
  376. <Input
  377. value={ob.settings.user || ''}
  378. onChange={(e) => { ob.settings.user = e.target.value; refresh(); }}
  379. />
  380. </Form.Item>
  381. <Form.Item label={t('password')}>
  382. <Input
  383. value={ob.settings.pass || ''}
  384. onChange={(e) => { ob.settings.pass = e.target.value; refresh(); }}
  385. />
  386. </Form.Item>
  387. </>
  388. )}
  389. {isHysteria && (
  390. <Form.Item label="Version">
  391. <InputNumber value={ob.settings.version || 2} min={2} max={2} disabled />
  392. </Form.Item>
  393. )}
  394. {ob.canEnableStream() && (
  395. <StreamFields ob={ob} refresh={refresh} streamNetworkChange={streamNetworkChange} isHysteria={isHysteria} t={t} />
  396. )}
  397. {ob.canEnableTls() && <TlsFields ob={ob} refresh={refresh} t={t} />}
  398. {ob.stream && <SockoptFields ob={ob} refresh={refresh} />}
  399. {ob.canEnableMux() && <MuxFields ob={ob} refresh={refresh} t={t} />}
  400. </Form>
  401. {ob.stream && ob.canEnableStream() && (
  402. <FinalMaskForm stream={ob.stream} protocol={proto} onChange={refresh} />
  403. )}
  404. </>
  405. ),
  406. },
  407. {
  408. key: '2',
  409. label: 'JSON',
  410. children: (
  411. <Space orientation="vertical" size={10} style={{ width: '100%', marginTop: 10 }}>
  412. <Input.Search
  413. value={linkInput}
  414. placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"
  415. enterButton="Convert"
  416. onChange={(e) => setLinkInput(e.target.value)}
  417. onSearch={convertLink}
  418. />
  419. <JsonEditor
  420. value={advancedJson}
  421. onChange={setAdvancedJson}
  422. minHeight="360px"
  423. maxHeight="600px"
  424. />
  425. </Space>
  426. ),
  427. },
  428. ]}
  429. />
  430. </Modal>
  431. </>
  432. );
  433. }
  434. type OB = Outbound;
  435. interface FieldProps {
  436. ob: OB;
  437. refresh: () => void;
  438. }
  439. interface TFieldProps extends FieldProps {
  440. t: (k: string) => string;
  441. }
  442. function FreedomFields({ ob, refresh }: FieldProps) {
  443. const fragment = (ob.settings.fragment || {}) as Record<string, string>;
  444. const noises = (ob.settings.noises || []) as Array<{ type: string; packet: string; delay: string; applyTo: string }>;
  445. const finalRules = (ob.settings.finalRules || []) as Array<{ action: string; network?: string; port?: string; ip?: string[]; blockDelay?: string }>;
  446. return (
  447. <>
  448. <Form.Item label="Strategy">
  449. <Select
  450. value={ob.settings.domainStrategy}
  451. onChange={(v) => { ob.settings.domainStrategy = v; refresh(); }}
  452. options={(OutboundDomainStrategies as string[]).map((s) => ({ value: s, label: s }))}
  453. />
  454. </Form.Item>
  455. <Form.Item label="Redirect">
  456. <Input
  457. value={ob.settings.redirect || ''}
  458. onChange={(e) => { ob.settings.redirect = e.target.value; refresh(); }}
  459. />
  460. </Form.Item>
  461. <Form.Item label="Fragment">
  462. <Switch
  463. checked={!!ob.settings.fragment && Object.keys(ob.settings.fragment).length > 0}
  464. onChange={(checked) => {
  465. ob.settings.fragment = checked
  466. ? { packets: 'tlshello', length: '100-200', interval: '10-20', maxSplit: '300-400' }
  467. : {};
  468. refresh();
  469. }}
  470. />
  471. </Form.Item>
  472. {ob.settings.fragment && Object.keys(ob.settings.fragment).length > 0 && (
  473. <>
  474. <Form.Item label="Packets">
  475. <Select
  476. value={fragment.packets}
  477. onChange={(v) => { (ob.settings.fragment as Record<string, string>).packets = v; refresh(); }}
  478. options={[
  479. { value: '1-3', label: '1-3' },
  480. { value: 'tlshello', label: 'tlshello' },
  481. ]}
  482. />
  483. </Form.Item>
  484. {(['length', 'interval', 'maxSplit'] as const).map((field) => (
  485. <Form.Item key={field} label={field === 'maxSplit' ? 'Max Split' : field[0].toUpperCase() + field.slice(1)}>
  486. <Input
  487. value={fragment[field] || ''}
  488. onChange={(e) => { (ob.settings.fragment as Record<string, string>)[field] = e.target.value; refresh(); }}
  489. />
  490. </Form.Item>
  491. ))}
  492. </>
  493. )}
  494. <Form.Item label="Noises">
  495. <Switch
  496. checked={noises.length > 0}
  497. onChange={(checked) => {
  498. ob.settings.noises = checked ? [new Outbound.FreedomSettings.Noise()] : [];
  499. refresh();
  500. }}
  501. />
  502. {noises.length > 0 && (
  503. <Button
  504. size="small"
  505. type="primary"
  506. className="ml-8"
  507. icon={<PlusOutlined />}
  508. onClick={() => { (ob.settings.noises as unknown[]).push(new Outbound.FreedomSettings.Noise()); refresh(); }}
  509. />
  510. )}
  511. </Form.Item>
  512. {noises.map((noise, index) => (
  513. <div key={index}>
  514. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  515. <div className="item-heading">
  516. <span>Noise {index + 1}</span>
  517. {noises.length > 1 && (
  518. <DeleteOutlined
  519. className="danger-icon"
  520. onClick={() => { (ob.settings.noises as unknown[]).splice(index, 1); refresh(); }}
  521. />
  522. )}
  523. </div>
  524. </Form.Item>
  525. <Form.Item label="Type">
  526. <Select
  527. value={noise.type}
  528. onChange={(v) => { noise.type = v; refresh(); }}
  529. options={['rand', 'base64', 'str', 'hex'].map((x) => ({ value: x, label: x }))}
  530. />
  531. </Form.Item>
  532. <Form.Item label="Packet">
  533. <Input value={noise.packet} onChange={(e) => { noise.packet = e.target.value; refresh(); }} />
  534. </Form.Item>
  535. <Form.Item label="Delay (ms)">
  536. <Input value={noise.delay} onChange={(e) => { noise.delay = e.target.value; refresh(); }} />
  537. </Form.Item>
  538. <Form.Item label="Apply to">
  539. <Select
  540. value={noise.applyTo}
  541. onChange={(v) => { noise.applyTo = v; refresh(); }}
  542. options={['ip', 'ipv4', 'ipv6'].map((x) => ({ value: x, label: x }))}
  543. />
  544. </Form.Item>
  545. </div>
  546. ))}
  547. <Form.Item label="Final Rules">
  548. <Button
  549. size="small"
  550. type="primary"
  551. icon={<PlusOutlined />}
  552. onClick={() => { ob.settings.addFinalRule('allow'); refresh(); }}
  553. />
  554. <span className="ml-8" style={{ opacity: 0.6 }}>
  555. Override Xray&apos;s default private-IP block (needed for LAN access through proxy)
  556. </span>
  557. </Form.Item>
  558. {finalRules.map((rule, index) => (
  559. <div key={`fr-${index}`}>
  560. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  561. <div className="item-heading">
  562. <span>Rule {index + 1}</span>
  563. <DeleteOutlined
  564. className="danger-icon"
  565. onClick={() => { ob.settings.delFinalRule(index); refresh(); }}
  566. />
  567. </div>
  568. </Form.Item>
  569. <Form.Item label="Action">
  570. <Select
  571. value={rule.action}
  572. onChange={(v) => { rule.action = v; refresh(); }}
  573. options={['allow', 'block'].map((x) => ({ value: x, label: x }))}
  574. />
  575. </Form.Item>
  576. <Form.Item label="Network">
  577. <Select
  578. value={rule.network}
  579. allowClear
  580. placeholder="(any)"
  581. onChange={(v) => { rule.network = v; refresh(); }}
  582. options={['tcp', 'udp', 'tcp,udp'].map((x) => ({ value: x, label: x }))}
  583. />
  584. </Form.Item>
  585. <Form.Item label="Port">
  586. <Input
  587. value={rule.port}
  588. placeholder="e.g. 80,443 or 1000-2000"
  589. onChange={(e) => { rule.port = e.target.value; refresh(); }}
  590. />
  591. </Form.Item>
  592. <Form.Item label="IP / CIDR / geoip">
  593. <Select
  594. mode="tags"
  595. value={rule.ip || []}
  596. tokenSeparators={[',', ' ']}
  597. placeholder="e.g. 10.0.0.0/8, geoip:private, ext:cn.dat:cn"
  598. onChange={(v) => { rule.ip = v as string[]; refresh(); }}
  599. />
  600. </Form.Item>
  601. {rule.action === 'block' && (
  602. <Form.Item label="Block delay (ms)">
  603. <Input
  604. value={rule.blockDelay}
  605. placeholder="optional: 5000-10000"
  606. onChange={(e) => { rule.blockDelay = e.target.value; refresh(); }}
  607. />
  608. </Form.Item>
  609. )}
  610. </div>
  611. ))}
  612. </>
  613. );
  614. }
  615. function DnsFields({ ob, refresh, t }: TFieldProps) {
  616. const rules = (ob.settings.rules || []) as Array<{ action: string; qtype?: string; domain?: string }>;
  617. return (
  618. <>
  619. <Form.Item label="Rewrite network">
  620. <Select
  621. value={ob.settings.rewriteNetwork}
  622. allowClear
  623. placeholder="(unchanged)"
  624. onChange={(v) => { ob.settings.rewriteNetwork = v; refresh(); }}
  625. options={['udp', 'tcp'].map((x) => ({ value: x, label: x }))}
  626. />
  627. </Form.Item>
  628. <Form.Item label="Rewrite address">
  629. <Input
  630. value={ob.settings.rewriteAddress || ''}
  631. placeholder="(unchanged) e.g. 1.1.1.1"
  632. onChange={(e) => { ob.settings.rewriteAddress = e.target.value; refresh(); }}
  633. />
  634. </Form.Item>
  635. <Form.Item label="Rewrite port">
  636. <InputNumber
  637. value={ob.settings.rewritePort || undefined}
  638. min={0}
  639. max={65535}
  640. style={{ width: '100%' }}
  641. placeholder="(unchanged)"
  642. onChange={(v) => { ob.settings.rewritePort = Number(v) || 0; refresh(); }}
  643. />
  644. </Form.Item>
  645. <Form.Item label="User level">
  646. <InputNumber
  647. value={ob.settings.userLevel || 0}
  648. min={0}
  649. style={{ width: '100%' }}
  650. onChange={(v) => { ob.settings.userLevel = Number(v) || 0; refresh(); }}
  651. />
  652. </Form.Item>
  653. <Form.Item label="Rules">
  654. <Button
  655. size="small"
  656. type="primary"
  657. icon={<PlusOutlined />}
  658. onClick={() => { (ob.settings.rules || (ob.settings.rules = [])).push(new Outbound.DNSRule()); refresh(); }}
  659. />
  660. </Form.Item>
  661. {rules.map((rule, index) => (
  662. <div key={index}>
  663. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  664. <div className="item-heading">
  665. <span>Rule {index + 1}</span>
  666. <DeleteOutlined
  667. className="danger-icon"
  668. onClick={() => { (ob.settings.rules as unknown[]).splice(index, 1); refresh(); }}
  669. />
  670. </div>
  671. </Form.Item>
  672. <Form.Item label="Action">
  673. <Select
  674. value={rule.action}
  675. onChange={(v) => { rule.action = v; refresh(); }}
  676. options={(DNSRuleActions as string[]).map((a) => ({ value: a, label: a }))}
  677. />
  678. </Form.Item>
  679. <Form.Item label="QType">
  680. <Input
  681. value={rule.qtype}
  682. placeholder="1,3,23-24"
  683. onChange={(e) => { rule.qtype = e.target.value; refresh(); }}
  684. />
  685. </Form.Item>
  686. <Form.Item label={t('domainName')}>
  687. <Input
  688. value={rule.domain}
  689. placeholder="domain:example.com"
  690. onChange={(e) => { rule.domain = e.target.value; refresh(); }}
  691. />
  692. </Form.Item>
  693. </div>
  694. ))}
  695. </>
  696. );
  697. }
  698. function WireguardFields({ ob, refresh, regenerate, t }: TFieldProps & { regenerate: () => void }) {
  699. const peers = (ob.settings.peers || []) as Array<{ endpoint?: string; publicKey?: string; psk?: string; allowedIPs?: string[]; keepAlive?: number }>;
  700. return (
  701. <>
  702. <Form.Item label={t('pages.inbounds.address')}>
  703. <Input
  704. value={ob.settings.address || ''}
  705. onChange={(e) => { ob.settings.address = e.target.value; refresh(); }}
  706. />
  707. </Form.Item>
  708. <Form.Item
  709. label={
  710. <>
  711. {t('pages.inbounds.privatekey')}
  712. <SyncOutlined className="random-icon" onClick={regenerate} />
  713. </>
  714. }
  715. >
  716. <Input
  717. value={ob.settings.secretKey || ''}
  718. onChange={(e) => { ob.settings.secretKey = e.target.value; refresh(); }}
  719. />
  720. </Form.Item>
  721. <Form.Item label={t('pages.inbounds.publicKey')}>
  722. <Input value={ob.settings.pubKey || ''} disabled />
  723. </Form.Item>
  724. <Form.Item label="Domain strategy">
  725. <Select
  726. value={ob.settings.domainStrategy || ''}
  727. onChange={(v) => { ob.settings.domainStrategy = v; refresh(); }}
  728. options={['', ...(WireguardDomainStrategy as string[])].map((x) => ({ value: x, label: x || `(${t('none')})` }))}
  729. />
  730. </Form.Item>
  731. <Form.Item label="MTU">
  732. <InputNumber value={ob.settings.mtu || 0} min={0} onChange={(v) => { ob.settings.mtu = Number(v) || 0; refresh(); }} />
  733. </Form.Item>
  734. <Form.Item label="Workers">
  735. <InputNumber value={ob.settings.workers || 0} min={0} onChange={(v) => { ob.settings.workers = Number(v) || 0; refresh(); }} />
  736. </Form.Item>
  737. <Form.Item label="No-kernel TUN">
  738. <Switch checked={!!ob.settings.noKernelTun} onChange={(v) => { ob.settings.noKernelTun = v; refresh(); }} />
  739. </Form.Item>
  740. <Form.Item label="Reserved">
  741. <Input value={ob.settings.reserved || ''} onChange={(e) => { ob.settings.reserved = e.target.value; refresh(); }} />
  742. </Form.Item>
  743. <Form.Item label="Peers">
  744. <Button
  745. size="small"
  746. type="primary"
  747. icon={<PlusOutlined />}
  748. onClick={() => { (ob.settings.peers || (ob.settings.peers = [])).push(new Outbound.WireguardSettings.Peer()); refresh(); }}
  749. />
  750. </Form.Item>
  751. {peers.map((peer, index) => (
  752. <div key={index}>
  753. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  754. <div className="item-heading">
  755. <span>Peer {index + 1}</span>
  756. {peers.length > 1 && (
  757. <DeleteOutlined
  758. className="danger-icon"
  759. onClick={() => { (ob.settings.peers as unknown[]).splice(index, 1); refresh(); }}
  760. />
  761. )}
  762. </div>
  763. </Form.Item>
  764. <Form.Item label="Endpoint">
  765. <Input value={peer.endpoint} onChange={(e) => { peer.endpoint = e.target.value; refresh(); }} />
  766. </Form.Item>
  767. <Form.Item label={t('pages.inbounds.publicKey')}>
  768. <Input value={peer.publicKey} onChange={(e) => { peer.publicKey = e.target.value; refresh(); }} />
  769. </Form.Item>
  770. <Form.Item label="PSK">
  771. <Input value={peer.psk} onChange={(e) => { peer.psk = e.target.value; refresh(); }} />
  772. </Form.Item>
  773. <Form.Item label="Allowed IPs">
  774. {(peer.allowedIPs || []).map((ip, idx) => (
  775. <Space.Compact key={idx} block style={{ marginBottom: 4 }}>
  776. <Input
  777. value={ip}
  778. onChange={(e) => { peer.allowedIPs![idx] = e.target.value; refresh(); }}
  779. />
  780. {(peer.allowedIPs || []).length > 1 && (
  781. <InputAddon onClick={() => { peer.allowedIPs!.splice(idx, 1); refresh(); }}>
  782. <MinusOutlined />
  783. </InputAddon>
  784. )}
  785. </Space.Compact>
  786. ))}
  787. <Button
  788. size="small"
  789. icon={<PlusOutlined />}
  790. onClick={() => { (peer.allowedIPs = peer.allowedIPs || []).push(''); refresh(); }}
  791. />
  792. </Form.Item>
  793. <Form.Item label="Keep alive">
  794. <InputNumber value={peer.keepAlive || 0} min={0} onChange={(v) => { peer.keepAlive = Number(v) || 0; refresh(); }} />
  795. </Form.Item>
  796. </div>
  797. ))}
  798. </>
  799. );
  800. }
  801. function VMessVLessFields({ ob, refresh, isVMess, isVLESS, t }: TFieldProps & { isVMess: boolean; isVLESS: boolean }) {
  802. const rev = ob.settings.reverseSniffing || {};
  803. return (
  804. <>
  805. <Form.Item label="ID">
  806. <Input value={ob.settings.id || ''} onChange={(e) => { ob.settings.id = e.target.value; refresh(); }} />
  807. </Form.Item>
  808. {isVMess && (
  809. <Form.Item label={t('security')}>
  810. <Select
  811. value={ob.settings.security}
  812. onChange={(v) => { ob.settings.security = v; refresh(); }}
  813. options={SECURITY_OPTIONS.map((s) => ({ value: s, label: s }))}
  814. />
  815. </Form.Item>
  816. )}
  817. {isVLESS && (
  818. <Form.Item label={t('encryption')}>
  819. <Input
  820. value={ob.settings.encryption || ''}
  821. onChange={(e) => { ob.settings.encryption = e.target.value; refresh(); }}
  822. />
  823. </Form.Item>
  824. )}
  825. {isVLESS && (
  826. <Form.Item label="Reverse tag">
  827. <Input
  828. value={ob.settings.reverseTag || ''}
  829. placeholder="optional"
  830. onChange={(e) => { ob.settings.reverseTag = e.target.value; refresh(); }}
  831. />
  832. </Form.Item>
  833. )}
  834. {isVLESS && ob.settings.reverseTag && (
  835. <>
  836. <Form.Item label="Reverse Sniffing">
  837. <Switch checked={!!rev.enabled} onChange={(v) => { rev.enabled = v; refresh(); }} />
  838. </Form.Item>
  839. {rev.enabled && (
  840. <>
  841. <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
  842. <Checkbox.Group
  843. className="sniffing-options"
  844. value={rev.destOverride || []}
  845. onChange={(v) => { rev.destOverride = v as string[]; refresh(); }}
  846. options={Object.entries(SNIFFING_OPTION).map(([label, value]) => ({ label, value: value as string }))}
  847. />
  848. </Form.Item>
  849. <Form.Item label="Metadata Only">
  850. <Switch checked={!!rev.metadataOnly} onChange={(v) => { rev.metadataOnly = v; refresh(); }} />
  851. </Form.Item>
  852. <Form.Item label="Route Only">
  853. <Switch checked={!!rev.routeOnly} onChange={(v) => { rev.routeOnly = v; refresh(); }} />
  854. </Form.Item>
  855. <Form.Item label="IPs Excluded">
  856. <Select
  857. mode="tags"
  858. value={rev.ipsExcluded || []}
  859. tokenSeparators={[',']}
  860. placeholder="IP/CIDR/geoip:*/ext:*"
  861. style={{ width: '100%' }}
  862. onChange={(v) => { rev.ipsExcluded = v as string[]; refresh(); }}
  863. />
  864. </Form.Item>
  865. <Form.Item label="Domains Excluded">
  866. <Select
  867. mode="tags"
  868. value={rev.domainsExcluded || []}
  869. tokenSeparators={[',']}
  870. placeholder="domain:*/ext:*"
  871. style={{ width: '100%' }}
  872. onChange={(v) => { rev.domainsExcluded = v as string[]; refresh(); }}
  873. />
  874. </Form.Item>
  875. </>
  876. )}
  877. </>
  878. )}
  879. {ob.canEnableTlsFlow() && (
  880. <Form.Item label="Flow">
  881. <Select
  882. value={ob.settings.flow || ''}
  883. onChange={(v) => { ob.settings.flow = v; refresh(); }}
  884. options={[{ value: '', label: t('none') }, ...FLOW_OPTIONS.map((k) => ({ value: k, label: k }))]}
  885. />
  886. </Form.Item>
  887. )}
  888. </>
  889. );
  890. }
  891. function StreamFields({ ob, refresh, streamNetworkChange, isHysteria, t }: TFieldProps & { streamNetworkChange: (next: string) => void; isHysteria: boolean }) {
  892. const networks = isHysteria ? [...NETWORKS, 'hysteria'] : NETWORKS;
  893. return (
  894. <>
  895. <Form.Item label={t('transmission')}>
  896. <Select
  897. value={ob.stream.network}
  898. onChange={streamNetworkChange}
  899. options={networks.map((net) => ({ value: net, label: NETWORK_LABELS[net] || net }))}
  900. />
  901. </Form.Item>
  902. {ob.stream.network === 'tcp' && (
  903. <>
  904. <Form.Item label={`HTTP ${t('camouflage')}`}>
  905. <Switch
  906. checked={ob.stream.tcp.type === 'http'}
  907. onChange={(checked) => { ob.stream.tcp.type = checked ? 'http' : 'none'; refresh(); }}
  908. />
  909. </Form.Item>
  910. {ob.stream.tcp.type === 'http' && (
  911. <>
  912. <Form.Item label={t('host')}>
  913. <Input value={ob.stream.tcp.host || ''} onChange={(e) => { ob.stream.tcp.host = e.target.value; refresh(); }} />
  914. </Form.Item>
  915. <Form.Item label={t('path')}>
  916. <Input value={ob.stream.tcp.path || ''} onChange={(e) => { ob.stream.tcp.path = e.target.value; refresh(); }} />
  917. </Form.Item>
  918. </>
  919. )}
  920. </>
  921. )}
  922. {ob.stream.network === 'kcp' && (
  923. <>
  924. {(
  925. [
  926. ['mtu', 'MTU', 0],
  927. ['tti', 'TTI (ms)', 0],
  928. ['upCap', 'Uplink (MB/s)', 0],
  929. ['downCap', 'Downlink (MB/s)', 0],
  930. ['cwndMultiplier', 'CWND multiplier', 1],
  931. ['maxSendingWindow', 'Max sending window', 0],
  932. ] as const
  933. ).map(([field, label, min]) => (
  934. <Form.Item key={field} label={label}>
  935. <InputNumber
  936. value={ob.stream.kcp[field] ?? 0}
  937. min={min}
  938. onChange={(v) => { ob.stream.kcp[field] = Number(v) || 0; refresh(); }}
  939. />
  940. </Form.Item>
  941. ))}
  942. </>
  943. )}
  944. {ob.stream.network === 'ws' && (
  945. <>
  946. <Form.Item label={t('host')}>
  947. <Input value={ob.stream.ws.host || ''} onChange={(e) => { ob.stream.ws.host = e.target.value; refresh(); }} />
  948. </Form.Item>
  949. <Form.Item label={t('path')}>
  950. <Input value={ob.stream.ws.path || ''} onChange={(e) => { ob.stream.ws.path = e.target.value; refresh(); }} />
  951. </Form.Item>
  952. <Form.Item label="Heartbeat (s)">
  953. <InputNumber
  954. value={ob.stream.ws.heartbeatPeriod || 0}
  955. min={0}
  956. onChange={(v) => { ob.stream.ws.heartbeatPeriod = Number(v) || 0; refresh(); }}
  957. />
  958. </Form.Item>
  959. </>
  960. )}
  961. {ob.stream.network === 'grpc' && (
  962. <>
  963. <Form.Item label="Service name">
  964. <Input value={ob.stream.grpc.serviceName || ''} onChange={(e) => { ob.stream.grpc.serviceName = e.target.value; refresh(); }} />
  965. </Form.Item>
  966. <Form.Item label="Authority">
  967. <Input value={ob.stream.grpc.authority || ''} onChange={(e) => { ob.stream.grpc.authority = e.target.value; refresh(); }} />
  968. </Form.Item>
  969. <Form.Item label="Multi mode">
  970. <Switch checked={!!ob.stream.grpc.multiMode} onChange={(v) => { ob.stream.grpc.multiMode = v; refresh(); }} />
  971. </Form.Item>
  972. </>
  973. )}
  974. {ob.stream.network === 'httpupgrade' && (
  975. <>
  976. <Form.Item label={t('host')}>
  977. <Input value={ob.stream.httpupgrade.host || ''} onChange={(e) => { ob.stream.httpupgrade.host = e.target.value; refresh(); }} />
  978. </Form.Item>
  979. <Form.Item label={t('path')}>
  980. <Input value={ob.stream.httpupgrade.path || ''} onChange={(e) => { ob.stream.httpupgrade.path = e.target.value; refresh(); }} />
  981. </Form.Item>
  982. </>
  983. )}
  984. {ob.stream.network === 'xhttp' && <XhttpFields ob={ob} refresh={refresh} t={t} />}
  985. {ob.stream.network === 'hysteria' && <HysteriaTransportFields ob={ob} refresh={refresh} />}
  986. </>
  987. );
  988. }
  989. function XhttpFields({ ob, refresh, t }: TFieldProps) {
  990. const xh = ob.stream.xhttp;
  991. return (
  992. <>
  993. <Form.Item label={t('host')}>
  994. <Input value={xh.host || ''} onChange={(e) => { xh.host = e.target.value; refresh(); }} />
  995. </Form.Item>
  996. <Form.Item label={t('path')}>
  997. <Input value={xh.path || ''} onChange={(e) => { xh.path = e.target.value; refresh(); }} />
  998. </Form.Item>
  999. <Form.Item label={t('pages.inbounds.stream.tcp.requestHeader')}>
  1000. <Button size="small" icon={<PlusOutlined />} onClick={() => { xh.addHeader('', ''); refresh(); }} />
  1001. </Form.Item>
  1002. <Form.Item wrapperCol={{ span: 24 }}>
  1003. {(xh.headers as Array<{ name: string; value: string }>).map((header, idx) => (
  1004. <Space.Compact key={idx} block className="mb-8">
  1005. <InputAddon>{`${idx + 1}`}</InputAddon>
  1006. <Input
  1007. value={header.name}
  1008. placeholder="Name"
  1009. onChange={(e) => { header.name = e.target.value; refresh(); }}
  1010. />
  1011. <Input
  1012. value={header.value}
  1013. placeholder="Value"
  1014. onChange={(e) => { header.value = e.target.value; refresh(); }}
  1015. />
  1016. <Button icon={<MinusOutlined />} onClick={() => { xh.removeHeader(idx); refresh(); }} />
  1017. </Space.Compact>
  1018. ))}
  1019. </Form.Item>
  1020. <Form.Item label="Mode">
  1021. <Select
  1022. value={xh.mode}
  1023. onChange={(v) => { xh.mode = v; refresh(); }}
  1024. options={Object.values(MODE_OPTION).map((m) => ({ value: m as string, label: m as string }))}
  1025. />
  1026. </Form.Item>
  1027. {xh.mode === 'packet-up' && (
  1028. <>
  1029. <Form.Item label="Max Upload Size (Byte)">
  1030. <Input value={xh.scMaxEachPostBytes} onChange={(e) => { xh.scMaxEachPostBytes = e.target.value; refresh(); }} />
  1031. </Form.Item>
  1032. <Form.Item label="Min Upload Interval (Ms)">
  1033. <Input value={xh.scMinPostsIntervalMs} onChange={(e) => { xh.scMinPostsIntervalMs = e.target.value; refresh(); }} />
  1034. </Form.Item>
  1035. </>
  1036. )}
  1037. <Form.Item label="Padding Bytes">
  1038. <Input value={xh.xPaddingBytes} onChange={(e) => { xh.xPaddingBytes = e.target.value; refresh(); }} />
  1039. </Form.Item>
  1040. <Form.Item label="Padding Obfs Mode">
  1041. <Switch checked={!!xh.xPaddingObfsMode} onChange={(v) => { xh.xPaddingObfsMode = v; refresh(); }} />
  1042. </Form.Item>
  1043. {xh.xPaddingObfsMode && (
  1044. <>
  1045. <Form.Item label="Padding Key">
  1046. <Input value={xh.xPaddingKey} placeholder="x_padding" onChange={(e) => { xh.xPaddingKey = e.target.value; refresh(); }} />
  1047. </Form.Item>
  1048. <Form.Item label="Padding Header">
  1049. <Input value={xh.xPaddingHeader} placeholder="X-Padding" onChange={(e) => { xh.xPaddingHeader = e.target.value; refresh(); }} />
  1050. </Form.Item>
  1051. <Form.Item label="Padding Placement">
  1052. <Select
  1053. value={xh.xPaddingPlacement || ''}
  1054. onChange={(v) => { xh.xPaddingPlacement = v; refresh(); }}
  1055. options={[
  1056. { value: '', label: 'Default (queryInHeader)' },
  1057. { value: 'queryInHeader', label: 'queryInHeader' },
  1058. { value: 'header', label: 'header' },
  1059. { value: 'cookie', label: 'cookie' },
  1060. { value: 'query', label: 'query' },
  1061. ]}
  1062. />
  1063. </Form.Item>
  1064. <Form.Item label="Padding Method">
  1065. <Select
  1066. value={xh.xPaddingMethod || ''}
  1067. onChange={(v) => { xh.xPaddingMethod = v; refresh(); }}
  1068. options={[
  1069. { value: '', label: 'Default (repeat-x)' },
  1070. { value: 'repeat-x', label: 'repeat-x' },
  1071. { value: 'tokenish', label: 'tokenish' },
  1072. ]}
  1073. />
  1074. </Form.Item>
  1075. </>
  1076. )}
  1077. <Form.Item label="Uplink HTTP Method">
  1078. <Select
  1079. value={xh.uplinkHTTPMethod || ''}
  1080. onChange={(v) => { xh.uplinkHTTPMethod = v; refresh(); }}
  1081. options={[
  1082. { value: '', label: 'Default (POST)' },
  1083. { value: 'POST', label: 'POST' },
  1084. { value: 'PUT', label: 'PUT' },
  1085. { value: 'GET', label: 'GET (packet-up only)', disabled: xh.mode !== 'packet-up' },
  1086. ]}
  1087. />
  1088. </Form.Item>
  1089. <Form.Item label="Session Placement">
  1090. <Select
  1091. value={xh.sessionPlacement || ''}
  1092. onChange={(v) => { xh.sessionPlacement = v; refresh(); }}
  1093. options={[
  1094. { value: '', label: 'Default (path)' },
  1095. { value: 'path', label: 'path' },
  1096. { value: 'header', label: 'header' },
  1097. { value: 'cookie', label: 'cookie' },
  1098. { value: 'query', label: 'query' },
  1099. ]}
  1100. />
  1101. </Form.Item>
  1102. {xh.sessionPlacement && xh.sessionPlacement !== 'path' && (
  1103. <Form.Item label="Session Key">
  1104. <Input value={xh.sessionKey} placeholder="x_session" onChange={(e) => { xh.sessionKey = e.target.value; refresh(); }} />
  1105. </Form.Item>
  1106. )}
  1107. <Form.Item label="Sequence Placement">
  1108. <Select
  1109. value={xh.seqPlacement || ''}
  1110. onChange={(v) => { xh.seqPlacement = v; refresh(); }}
  1111. options={[
  1112. { value: '', label: 'Default (path)' },
  1113. { value: 'path', label: 'path' },
  1114. { value: 'header', label: 'header' },
  1115. { value: 'cookie', label: 'cookie' },
  1116. { value: 'query', label: 'query' },
  1117. ]}
  1118. />
  1119. </Form.Item>
  1120. {xh.seqPlacement && xh.seqPlacement !== 'path' && (
  1121. <Form.Item label="Sequence Key">
  1122. <Input value={xh.seqKey} placeholder="x_seq" onChange={(e) => { xh.seqKey = e.target.value; refresh(); }} />
  1123. </Form.Item>
  1124. )}
  1125. {xh.mode === 'packet-up' && (
  1126. <Form.Item label="Uplink Data Placement">
  1127. <Select
  1128. value={xh.uplinkDataPlacement || ''}
  1129. onChange={(v) => { xh.uplinkDataPlacement = v; refresh(); }}
  1130. options={[
  1131. { value: '', label: 'Default (body)' },
  1132. { value: 'body', label: 'body' },
  1133. { value: 'header', label: 'header' },
  1134. { value: 'cookie', label: 'cookie' },
  1135. { value: 'query', label: 'query' },
  1136. ]}
  1137. />
  1138. </Form.Item>
  1139. )}
  1140. {xh.mode === 'packet-up' && xh.uplinkDataPlacement && xh.uplinkDataPlacement !== 'body' && (
  1141. <>
  1142. <Form.Item label="Uplink Data Key">
  1143. <Input value={xh.uplinkDataKey} placeholder="x_data" onChange={(e) => { xh.uplinkDataKey = e.target.value; refresh(); }} />
  1144. </Form.Item>
  1145. <Form.Item label="Uplink Chunk Size">
  1146. <InputNumber
  1147. value={xh.uplinkChunkSize}
  1148. min={0}
  1149. placeholder="0 (unlimited)"
  1150. onChange={(v) => { xh.uplinkChunkSize = Number(v) || 0; refresh(); }}
  1151. />
  1152. </Form.Item>
  1153. </>
  1154. )}
  1155. {(xh.mode === 'stream-up' || xh.mode === 'stream-one') && (
  1156. <Form.Item label="No gRPC Header">
  1157. <Switch checked={!!xh.noGRPCHeader} onChange={(v) => { xh.noGRPCHeader = v; refresh(); }} />
  1158. </Form.Item>
  1159. )}
  1160. <Form.Item label="XMUX">
  1161. <Switch checked={!!xh.enableXmux} onChange={(v) => { xh.enableXmux = v; refresh(); }} />
  1162. </Form.Item>
  1163. {xh.enableXmux && (
  1164. <>
  1165. {!xh.xmux.maxConnections && (
  1166. <Form.Item label="Max Concurrency">
  1167. <Input value={xh.xmux.maxConcurrency} onChange={(e) => { xh.xmux.maxConcurrency = e.target.value; refresh(); }} />
  1168. </Form.Item>
  1169. )}
  1170. {!xh.xmux.maxConcurrency && (
  1171. <Form.Item label="Max Connections">
  1172. <Input value={xh.xmux.maxConnections} onChange={(e) => { xh.xmux.maxConnections = e.target.value; refresh(); }} />
  1173. </Form.Item>
  1174. )}
  1175. <Form.Item label="Max Reuse Times">
  1176. <Input value={xh.xmux.cMaxReuseTimes} onChange={(e) => { xh.xmux.cMaxReuseTimes = e.target.value; refresh(); }} />
  1177. </Form.Item>
  1178. <Form.Item label="Max Request Times">
  1179. <Input value={xh.xmux.hMaxRequestTimes} onChange={(e) => { xh.xmux.hMaxRequestTimes = e.target.value; refresh(); }} />
  1180. </Form.Item>
  1181. <Form.Item label="Max Reusable Secs">
  1182. <Input value={xh.xmux.hMaxReusableSecs} onChange={(e) => { xh.xmux.hMaxReusableSecs = e.target.value; refresh(); }} />
  1183. </Form.Item>
  1184. <Form.Item label="Keep Alive Period">
  1185. <InputNumber
  1186. value={xh.xmux.hKeepAlivePeriod}
  1187. min={0}
  1188. onChange={(v) => { xh.xmux.hKeepAlivePeriod = Number(v) || 0; refresh(); }}
  1189. />
  1190. </Form.Item>
  1191. </>
  1192. )}
  1193. </>
  1194. );
  1195. }
  1196. function HysteriaTransportFields({ ob, refresh }: FieldProps) {
  1197. const h = ob.stream.hysteria;
  1198. return (
  1199. <>
  1200. <Form.Item label="Auth password">
  1201. <Input value={h.auth || ''} onChange={(e) => { h.auth = e.target.value; refresh(); }} />
  1202. </Form.Item>
  1203. <Form.Item label="Congestion">
  1204. <Select
  1205. value={h.congestion || ''}
  1206. onChange={(v) => { h.congestion = v; refresh(); }}
  1207. options={[
  1208. { value: '', label: 'BBR (auto)' },
  1209. { value: 'brutal', label: 'Brutal' },
  1210. ]}
  1211. />
  1212. </Form.Item>
  1213. <Form.Item label="Upload">
  1214. <Input value={h.up} placeholder="100 mbps" onChange={(e) => { h.up = e.target.value; refresh(); }} />
  1215. </Form.Item>
  1216. <Form.Item label="Download">
  1217. <Input value={h.down} placeholder="100 mbps" onChange={(e) => { h.down = e.target.value; refresh(); }} />
  1218. </Form.Item>
  1219. <Form.Item label="UDP hop port">
  1220. <Input value={h.udphopPort} placeholder="1145-1919" onChange={(e) => { h.udphopPort = e.target.value; refresh(); }} />
  1221. </Form.Item>
  1222. <Form.Item label="Max idle (s)">
  1223. <InputNumber value={h.maxIdleTimeout} min={4} max={120} onChange={(v) => { h.maxIdleTimeout = Number(v) || 0; refresh(); }} />
  1224. </Form.Item>
  1225. <Form.Item label="Keep alive (s)">
  1226. <InputNumber value={h.keepAlivePeriod} min={2} max={60} onChange={(v) => { h.keepAlivePeriod = Number(v) || 0; refresh(); }} />
  1227. </Form.Item>
  1228. <Form.Item label="Disable Path MTU">
  1229. <Switch checked={!!h.disablePathMTUDiscovery} onChange={(v) => { h.disablePathMTUDiscovery = v; refresh(); }} />
  1230. </Form.Item>
  1231. </>
  1232. );
  1233. }
  1234. function TlsFields({ ob, refresh, t }: TFieldProps) {
  1235. return (
  1236. <>
  1237. <Form.Item label={t('security')}>
  1238. <Radio.Group
  1239. value={ob.stream.security}
  1240. buttonStyle="solid"
  1241. onChange={(e) => { ob.stream.security = e.target.value; refresh(); }}
  1242. >
  1243. <Radio.Button value="none">{t('none')}</Radio.Button>
  1244. <Radio.Button value="tls">TLS</Radio.Button>
  1245. {ob.canEnableReality() && <Radio.Button value="reality">Reality</Radio.Button>}
  1246. </Radio.Group>
  1247. </Form.Item>
  1248. {ob.stream.isTls && (
  1249. <>
  1250. <Form.Item label="SNI">
  1251. <Input value={ob.stream.tls.serverName} placeholder="server name" onChange={(e) => { ob.stream.tls.serverName = e.target.value; refresh(); }} />
  1252. </Form.Item>
  1253. <Form.Item label="uTLS">
  1254. <Select
  1255. value={ob.stream.tls.fingerprint || ''}
  1256. onChange={(v) => { ob.stream.tls.fingerprint = v; refresh(); }}
  1257. options={[{ value: '', label: t('none') }, ...UTLS_OPTIONS.map((k) => ({ value: k, label: k }))]}
  1258. />
  1259. </Form.Item>
  1260. <Form.Item label="ALPN">
  1261. <Select
  1262. mode="multiple"
  1263. value={ob.stream.tls.alpn || []}
  1264. onChange={(v) => { ob.stream.tls.alpn = v; refresh(); }}
  1265. options={ALPN_OPTIONS.map((alpn) => ({ value: alpn, label: alpn }))}
  1266. />
  1267. </Form.Item>
  1268. <Form.Item label="ECH">
  1269. <Input value={ob.stream.tls.echConfigList} onChange={(e) => { ob.stream.tls.echConfigList = e.target.value; refresh(); }} />
  1270. </Form.Item>
  1271. <Form.Item label="Verify peer name">
  1272. <Input value={ob.stream.tls.verifyPeerCertByName} placeholder="cloudflare-dns.com" onChange={(e) => { ob.stream.tls.verifyPeerCertByName = e.target.value; refresh(); }} />
  1273. </Form.Item>
  1274. <Form.Item label="Pinned SHA256">
  1275. <Input value={ob.stream.tls.pinnedPeerCertSha256} placeholder="base64 SHA256" onChange={(e) => { ob.stream.tls.pinnedPeerCertSha256 = e.target.value; refresh(); }} />
  1276. </Form.Item>
  1277. </>
  1278. )}
  1279. {ob.stream.isReality && (
  1280. <>
  1281. <Form.Item label="SNI">
  1282. <Input value={ob.stream.reality.serverName} onChange={(e) => { ob.stream.reality.serverName = e.target.value; refresh(); }} />
  1283. </Form.Item>
  1284. <Form.Item label="uTLS">
  1285. <Select
  1286. value={ob.stream.reality.fingerprint}
  1287. onChange={(v) => { ob.stream.reality.fingerprint = v; refresh(); }}
  1288. options={UTLS_OPTIONS.map((k) => ({ value: k, label: k }))}
  1289. />
  1290. </Form.Item>
  1291. <Form.Item label="Short ID">
  1292. <Input value={ob.stream.reality.shortId} onChange={(e) => { ob.stream.reality.shortId = e.target.value; refresh(); }} />
  1293. </Form.Item>
  1294. <Form.Item label="SpiderX">
  1295. <Input value={ob.stream.reality.spiderX} onChange={(e) => { ob.stream.reality.spiderX = e.target.value; refresh(); }} />
  1296. </Form.Item>
  1297. <Form.Item label={t('pages.inbounds.publicKey')}>
  1298. <Input.TextArea
  1299. value={ob.stream.reality.publicKey}
  1300. autoSize={{ minRows: 2 }}
  1301. onChange={(e) => { ob.stream.reality.publicKey = e.target.value; refresh(); }}
  1302. />
  1303. </Form.Item>
  1304. <Form.Item label="mldsa65 verify">
  1305. <Input.TextArea
  1306. value={ob.stream.reality.mldsa65Verify}
  1307. autoSize={{ minRows: 2 }}
  1308. onChange={(e) => { ob.stream.reality.mldsa65Verify = e.target.value; refresh(); }}
  1309. />
  1310. </Form.Item>
  1311. </>
  1312. )}
  1313. </>
  1314. );
  1315. }
  1316. function SockoptFields({ ob, refresh }: FieldProps) {
  1317. return (
  1318. <>
  1319. <Form.Item label="Sockopts">
  1320. <Switch checked={!!ob.stream.sockoptSwitch} onChange={(v) => { ob.stream.sockoptSwitch = v; refresh(); }} />
  1321. </Form.Item>
  1322. {ob.stream.sockoptSwitch && (
  1323. <>
  1324. <Form.Item label="Dialer proxy">
  1325. <Input value={ob.stream.sockopt.dialerProxy || ''} onChange={(e) => { ob.stream.sockopt.dialerProxy = e.target.value; refresh(); }} />
  1326. </Form.Item>
  1327. <Form.Item label="Address+Port strategy">
  1328. <Select
  1329. value={ob.stream.sockopt.addressPortStrategy}
  1330. onChange={(v) => { ob.stream.sockopt.addressPortStrategy = v; refresh(); }}
  1331. options={Object.values(Address_Port_Strategy).map((k) => ({ value: k as string, label: k as string }))}
  1332. />
  1333. </Form.Item>
  1334. <Form.Item label="Keep alive interval">
  1335. <InputNumber
  1336. value={ob.stream.sockopt.tcpKeepAliveInterval}
  1337. min={0}
  1338. onChange={(v) => { ob.stream.sockopt.tcpKeepAliveInterval = Number(v) || 0; refresh(); }}
  1339. />
  1340. </Form.Item>
  1341. <Form.Item label="TCP Fast Open">
  1342. <Switch checked={!!ob.stream.sockopt.tcpFastOpen} onChange={(v) => { ob.stream.sockopt.tcpFastOpen = v; refresh(); }} />
  1343. </Form.Item>
  1344. <Form.Item label="Multipath TCP">
  1345. <Switch checked={!!ob.stream.sockopt.tcpMptcp} onChange={(v) => { ob.stream.sockopt.tcpMptcp = v; refresh(); }} />
  1346. </Form.Item>
  1347. <Form.Item label="Penetrate">
  1348. <Switch checked={!!ob.stream.sockopt.penetrate} onChange={(v) => { ob.stream.sockopt.penetrate = v; refresh(); }} />
  1349. </Form.Item>
  1350. <Form.Item label="Mark (fwmark)">
  1351. <InputNumber
  1352. value={ob.stream.sockopt.mark}
  1353. min={0}
  1354. onChange={(v) => { ob.stream.sockopt.mark = Number(v) || 0; refresh(); }}
  1355. />
  1356. </Form.Item>
  1357. <Form.Item label="Interface">
  1358. <Input value={ob.stream.sockopt.interfaceName} onChange={(e) => { ob.stream.sockopt.interfaceName = e.target.value; refresh(); }} />
  1359. </Form.Item>
  1360. </>
  1361. )}
  1362. </>
  1363. );
  1364. }
  1365. function MuxFields({ ob, refresh, t }: TFieldProps) {
  1366. return (
  1367. <>
  1368. <Form.Item label={t('pages.settings.mux')}>
  1369. <Switch checked={!!ob.mux.enabled} onChange={(v) => { ob.mux.enabled = v; refresh(); }} />
  1370. </Form.Item>
  1371. {ob.mux.enabled && (
  1372. <>
  1373. <Form.Item label="Concurrency">
  1374. <InputNumber
  1375. value={ob.mux.concurrency}
  1376. min={-1}
  1377. max={1024}
  1378. onChange={(v) => { ob.mux.concurrency = Number(v) || 0; refresh(); }}
  1379. />
  1380. </Form.Item>
  1381. <Form.Item label="xudp concurrency">
  1382. <InputNumber
  1383. value={ob.mux.xudpConcurrency}
  1384. min={-1}
  1385. max={1024}
  1386. onChange={(v) => { ob.mux.xudpConcurrency = Number(v) || 0; refresh(); }}
  1387. />
  1388. </Form.Item>
  1389. <Form.Item label="xudp UDP 443">
  1390. <Select
  1391. value={ob.mux.xudpProxyUDP443}
  1392. onChange={(v) => { ob.mux.xudpProxyUDP443 = v; refresh(); }}
  1393. options={['reject', 'allow', 'skip'].map((x) => ({ value: x, label: x }))}
  1394. />
  1395. </Form.Item>
  1396. </>
  1397. )}
  1398. </>
  1399. );
  1400. }