InboundFormModal.tsx 86 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417
  1. import { useEffect, useRef, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import dayjs from 'dayjs';
  4. import {
  5. Button,
  6. Card,
  7. Checkbox,
  8. Empty,
  9. Form,
  10. Input,
  11. InputNumber,
  12. Modal,
  13. Radio,
  14. Select,
  15. Space,
  16. Switch,
  17. Tabs,
  18. Tooltip,
  19. Typography,
  20. message,
  21. } from 'antd';
  22. import {
  23. ArrowDownOutlined,
  24. ArrowUpOutlined,
  25. DeleteOutlined,
  26. MinusOutlined,
  27. PlusOutlined,
  28. SyncOutlined,
  29. } from '@ant-design/icons';
  30. import { HttpUtil, NumberFormatter, RandomUtil, SizeFormatter, Wireguard } from '@/utils';
  31. import {
  32. rawInboundToFormValues,
  33. formValuesToWirePayload,
  34. } from '@/lib/xray/inbound-form-adapter';
  35. import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults';
  36. import {
  37. canEnableReality,
  38. canEnableStream,
  39. canEnableTls,
  40. isSS2022,
  41. } from '@/lib/xray/protocol-capabilities';
  42. import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
  43. import { getRandomRealityTarget } from '@/models/reality-targets';
  44. import {
  45. InboundFormBaseSchema,
  46. InboundFormSchema,
  47. type FallbackRow,
  48. type InboundFormValues,
  49. } from '@/schemas/forms/inbound-form';
  50. import { antdRule } from '@/utils/zodForm';
  51. import {
  52. ALPN_OPTION,
  53. DOMAIN_STRATEGY_OPTION,
  54. Protocols,
  55. SNIFFING_OPTION,
  56. TCP_CONGESTION_OPTION,
  57. TLS_CIPHER_OPTION,
  58. TLS_VERSION_OPTION,
  59. USAGE_OPTION,
  60. UTLS_FINGERPRINT,
  61. } from '@/schemas/primitives';
  62. import { SockoptStreamSettingsSchema } from '@/schemas/protocols/stream/sockopt';
  63. import { TlsStreamSettingsSchema } from '@/schemas/protocols/security/tls';
  64. import { RealityStreamSettingsSchema } from '@/schemas/protocols/security/reality';
  65. import DateTimePicker from '@/components/DateTimePicker';
  66. import HeaderMapEditor from '@/components/HeaderMapEditor';
  67. import InputAddon from '@/components/InputAddon';
  68. import JsonEditor from '@/components/JsonEditor';
  69. import type { FormInstance } from 'antd';
  70. import type { NamePath } from 'antd/es/form/interface';
  71. const { TextArea } = Input;
  72. import type { DBInbound } from '@/models/dbinbound';
  73. import type { NodeRecord } from '@/api/queries/useNodesQuery';
  74. // Pattern A rewrite of InboundFormModal. Built as a sibling file so the
  75. // build stays green while the rewrite progresses section by section.
  76. // InboundsPage continues to render the old InboundFormModal.tsx until the
  77. // atomic swap at the end (Core Decision 7).
  78. const { Text } = Typography;
  79. // Sub-editor for one slice of the form (settings, streamSettings, sniffing).
  80. // Holds a local text buffer so the user can type freely; on every keystroke
  81. // we try to JSON.parse and forward the result to form state. Invalid JSON
  82. // is held in the buffer until the next valid moment — no panic on partial
  83. // input. The buffer seeds once on mount; the modal's destroyOnHidden makes
  84. // each open a fresh editor instance, so we don't need to re-sync on outer
  85. // form changes.
  86. function AdvancedSliceEditor({
  87. form,
  88. path,
  89. minHeight,
  90. maxHeight,
  91. }: {
  92. form: FormInstance<InboundFormValues>;
  93. path: NamePath;
  94. minHeight?: string;
  95. maxHeight?: string;
  96. }) {
  97. const [text, setText] = useState(() =>
  98. JSON.stringify(form.getFieldValue(path) ?? {}, null, 2),
  99. );
  100. return (
  101. <JsonEditor
  102. value={text}
  103. minHeight={minHeight}
  104. maxHeight={maxHeight}
  105. onChange={(next) => {
  106. setText(next);
  107. try {
  108. form.setFieldValue(path, JSON.parse(next));
  109. } catch {
  110. }
  111. }}
  112. />
  113. );
  114. }
  115. const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p }));
  116. const TRAFFIC_RESETS = ['never', 'hourly', 'daily', 'weekly', 'monthly'] as const;
  117. const NODE_ELIGIBLE_PROTOCOLS = new Set<string>([
  118. Protocols.VLESS,
  119. Protocols.VMESS,
  120. Protocols.TROJAN,
  121. Protocols.SHADOWSOCKS,
  122. Protocols.HYSTERIA,
  123. Protocols.WIREGUARD,
  124. ]);
  125. interface InboundFormModalProps {
  126. open: boolean;
  127. onClose: () => void;
  128. onSaved: () => void;
  129. mode: 'add' | 'edit';
  130. dbInbound: DBInbound | null;
  131. dbInbounds: DBInbound[];
  132. availableNodes?: NodeRecord[];
  133. }
  134. function buildAddModeValues(): InboundFormValues {
  135. const settings = createDefaultInboundSettings('vless') ?? undefined;
  136. return rawInboundToFormValues({
  137. protocol: 'vless',
  138. settings,
  139. streamSettings: { network: 'tcp', security: 'none' },
  140. sniffing: {},
  141. port: RandomUtil.randomInteger(10000, 60000),
  142. listen: '',
  143. tag: '',
  144. enable: true,
  145. trafficReset: 'never',
  146. });
  147. }
  148. export default function InboundFormModal({
  149. open,
  150. onClose,
  151. onSaved,
  152. mode,
  153. dbInbound,
  154. dbInbounds,
  155. availableNodes,
  156. }: InboundFormModalProps) {
  157. const { t } = useTranslation();
  158. const [messageApi, messageContextHolder] = message.useMessage();
  159. const [form] = Form.useForm<InboundFormValues>();
  160. const [saving, setSaving] = useState(false);
  161. const fallbackKeyRef = useRef(0);
  162. const [fallbacks, setFallbacks] = useState<FallbackRow[]>([]);
  163. const selectableNodes = (availableNodes || []).filter((n) => n.enable);
  164. const protocol = (Form.useWatch('protocol', form) ?? '') as string;
  165. const isNodeEligible = NODE_ELIGIBLE_PROTOCOLS.has(protocol);
  166. const sniffingEnabled = Form.useWatch(['sniffing', 'enabled'], form) ?? false;
  167. const vlessEncryption = Form.useWatch(['settings', 'encryption'], form) ?? '';
  168. const ssMethod = Form.useWatch(['settings', 'method'], form);
  169. const isSSWith2022 = isSS2022({
  170. protocol,
  171. settings: typeof ssMethod === 'string' ? { method: ssMethod } : {},
  172. });
  173. const mixedUdpOn = Form.useWatch(['settings', 'udp'], form) ?? false;
  174. const network = Form.useWatch(['streamSettings', 'network'], form) ?? '';
  175. const security = Form.useWatch(['streamSettings', 'security'], form) ?? 'none';
  176. const streamEnabled = canEnableStream({ protocol });
  177. const tlsAllowed = canEnableTls({ protocol, streamSettings: { network, security } });
  178. const realityAllowed = canEnableReality({ protocol, streamSettings: { network, security } });
  179. const isFallbackHost =
  180. (protocol === Protocols.VLESS || protocol === Protocols.TROJAN)
  181. && network === 'tcp'
  182. && (security === 'tls' || security === 'reality');
  183. const fallbackChildOptions = (dbInbounds || [])
  184. .filter((ib) => ib.id !== dbInbound?.id)
  185. .map((ib) => ({
  186. label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`,
  187. value: ib.id,
  188. }));
  189. const loadFallbacks = async (masterId: number | null) => {
  190. if (!masterId) {
  191. setFallbacks([]);
  192. return;
  193. }
  194. const msg = await HttpUtil.get(`/panel/api/inbounds/${masterId}/fallbacks`);
  195. if (!msg?.success || !Array.isArray(msg.obj)) {
  196. setFallbacks([]);
  197. return;
  198. }
  199. setFallbacks(
  200. (msg.obj as { childId: number; name?: string; alpn?: string; path?: string; xver?: number }[])
  201. .map((r) => ({
  202. rowKey: `fb-${++fallbackKeyRef.current}`,
  203. childId: r.childId,
  204. name: r.name || '',
  205. alpn: r.alpn || '',
  206. path: r.path || '',
  207. xver: r.xver || 0,
  208. })),
  209. );
  210. };
  211. const saveFallbacks = async (masterId: number) => {
  212. if (!masterId) return true;
  213. const payload = {
  214. fallbacks: fallbacks.filter((c) => c.childId).map((c, i) => ({
  215. childId: c.childId,
  216. name: c.name,
  217. alpn: c.alpn,
  218. path: c.path,
  219. xver: Number(c.xver) || 0,
  220. sortOrder: i,
  221. })),
  222. };
  223. const msg = await HttpUtil.post(
  224. `/panel/api/inbounds/${masterId}/fallbacks`,
  225. payload,
  226. { headers: { 'Content-Type': 'application/json' } },
  227. );
  228. return !!msg?.success;
  229. };
  230. const addFallback = () => {
  231. setFallbacks((prev) => [...prev, {
  232. rowKey: `fb-${++fallbackKeyRef.current}`,
  233. childId: null,
  234. name: '',
  235. alpn: '',
  236. path: '',
  237. xver: 0,
  238. }]);
  239. };
  240. const updateFallback = (rowKey: string, patch: Partial<FallbackRow>) => {
  241. setFallbacks((prev) => prev.map((r) => r.rowKey === rowKey ? { ...r, ...patch } : r));
  242. };
  243. const removeFallback = (idx: number) => {
  244. setFallbacks((prev) => prev.filter((_, i) => i !== idx));
  245. };
  246. // Move a fallback row up/down by swapping adjacent indices. The order
  247. // is persisted via the fallback row's sortOrder (rebuilt by index on
  248. // save), so reordering survives reloads.
  249. const moveFallback = (idx: number, direction: -1 | 1) => {
  250. setFallbacks((prev) => {
  251. const target = idx + direction;
  252. if (target < 0 || target >= prev.length) return prev;
  253. const next = prev.slice();
  254. [next[idx], next[target]] = [next[target], next[idx]];
  255. return next;
  256. });
  257. };
  258. // One-shot: add a fresh fallback row for every eligible inbound (i.e.
  259. // every option in fallbackChildOptions) that is not already wired up.
  260. // Convenient for operators who want catch-all routing to every host
  261. // they manage on the panel.
  262. const addAllFallbacks = () => {
  263. setFallbacks((prev) => {
  264. const alreadyHave = new Set(prev.map((r) => r.childId));
  265. const additions = fallbackChildOptions
  266. .filter((opt) => !alreadyHave.has(opt.value))
  267. .map<FallbackRow>((opt) => ({
  268. rowKey: `fb-${++fallbackKeyRef.current}`,
  269. childId: opt.value,
  270. name: '',
  271. alpn: '',
  272. path: '',
  273. xver: 0,
  274. }));
  275. if (additions.length === 0) return prev;
  276. return [...prev, ...additions];
  277. });
  278. };
  279. const genRealityKeypair = async () => {
  280. setSaving(true);
  281. try {
  282. const msg = await HttpUtil.get('/panel/api/server/getNewX25519Cert');
  283. if (msg?.success) {
  284. const obj = msg.obj as { privateKey: string; publicKey: string };
  285. form.setFieldValue(['streamSettings', 'realitySettings', 'privateKey'], obj.privateKey);
  286. form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'publicKey'], obj.publicKey);
  287. }
  288. } finally {
  289. setSaving(false);
  290. }
  291. };
  292. const clearRealityKeypair = () => {
  293. form.setFieldValue(['streamSettings', 'realitySettings', 'privateKey'], '');
  294. form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'publicKey'], '');
  295. };
  296. const genMldsa65 = async () => {
  297. setSaving(true);
  298. try {
  299. const msg = await HttpUtil.get('/panel/api/server/getNewmldsa65');
  300. if (msg?.success) {
  301. const obj = msg.obj as { seed: string; verify: string };
  302. form.setFieldValue(['streamSettings', 'realitySettings', 'mldsa65Seed'], obj.seed);
  303. form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify'], obj.verify);
  304. }
  305. } finally {
  306. setSaving(false);
  307. }
  308. };
  309. const clearMldsa65 = () => {
  310. form.setFieldValue(['streamSettings', 'realitySettings', 'mldsa65Seed'], '');
  311. form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify'], '');
  312. };
  313. const randomizeRealityTarget = () => {
  314. const tgt = getRandomRealityTarget() as { target: string; sni: string };
  315. form.setFieldValue(['streamSettings', 'realitySettings', 'target'], tgt.target);
  316. form.setFieldValue(
  317. ['streamSettings', 'realitySettings', 'serverNames'],
  318. tgt.sni.split(',').map((s) => s.trim()).filter(Boolean),
  319. );
  320. };
  321. const randomizeShortIds = () => {
  322. form.setFieldValue(
  323. ['streamSettings', 'realitySettings', 'shortIds'],
  324. RandomUtil.randomShortIds().split(',').map((s) => s.trim()).filter(Boolean),
  325. );
  326. };
  327. const getNewEchCert = async () => {
  328. const sni = form.getFieldValue(['streamSettings', 'tlsSettings', 'serverName']);
  329. setSaving(true);
  330. try {
  331. const msg = await HttpUtil.post('/panel/api/server/getNewEchCert', { sni });
  332. if (msg?.success) {
  333. const obj = msg.obj as { echServerKeys: string; echConfigList: string };
  334. form.setFieldValue(['streamSettings', 'tlsSettings', 'echServerKeys'], obj.echServerKeys);
  335. form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], obj.echConfigList);
  336. }
  337. } finally {
  338. setSaving(false);
  339. }
  340. };
  341. const clearEchCert = () => {
  342. form.setFieldValue(['streamSettings', 'tlsSettings', 'echServerKeys'], '');
  343. form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], '');
  344. };
  345. const onSecurityChange = (next: string) => {
  346. const current = (form.getFieldValue('streamSettings') as Record<string, unknown>) ?? {};
  347. const cleaned: Record<string, unknown> = { ...current, security: next };
  348. delete cleaned.tlsSettings;
  349. delete cleaned.realitySettings;
  350. if (next === 'tls') cleaned.tlsSettings = TlsStreamSettingsSchema.parse({});
  351. if (next === 'reality') cleaned.realitySettings = RealityStreamSettingsSchema.parse({});
  352. form.setFieldValue('streamSettings', cleaned);
  353. };
  354. const xhttpMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'mode'], form);
  355. const xhttpObfsMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'xPaddingObfsMode'], form) ?? false;
  356. const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form);
  357. const xhttpSeqPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'seqPlacement'], form);
  358. const xhttpUplinkPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'uplinkDataPlacement'], form);
  359. const externalProxyArr = Form.useWatch(['streamSettings', 'externalProxy'], form);
  360. const externalProxyOn = Array.isArray(externalProxyArr) && externalProxyArr.length > 0;
  361. const sockoptValue = Form.useWatch(['streamSettings', 'sockopt'], form);
  362. const sockoptOn = !!sockoptValue && typeof sockoptValue === 'object' && Object.keys(sockoptValue as object).length > 0;
  363. const toggleExternalProxy = (on: boolean) => {
  364. if (on) {
  365. const port = (form.getFieldValue('port') as number) ?? 443;
  366. form.setFieldValue(['streamSettings', 'externalProxy'], [{
  367. forceTls: 'same',
  368. dest: typeof window !== 'undefined' ? window.location.hostname : '',
  369. port,
  370. remark: '',
  371. sni: '',
  372. fingerprint: '',
  373. alpn: [],
  374. }]);
  375. } else {
  376. form.setFieldValue(['streamSettings', 'externalProxy'], []);
  377. }
  378. };
  379. const toggleSockopt = (on: boolean) => {
  380. if (on) {
  381. form.setFieldValue(
  382. ['streamSettings', 'sockopt'],
  383. SockoptStreamSettingsSchema.parse({}),
  384. );
  385. } else {
  386. form.setFieldValue(['streamSettings', 'sockopt'], undefined);
  387. }
  388. };
  389. const wgSecretKey = Form.useWatch(['settings', 'secretKey'], form);
  390. const wgPubKey = typeof wgSecretKey === 'string' && wgSecretKey.length > 0
  391. ? Wireguard.generateKeypair(wgSecretKey).publicKey
  392. : '';
  393. const regenInboundWg = () => {
  394. const kp = Wireguard.generateKeypair();
  395. form.setFieldValue(['settings', 'secretKey'], kp.privateKey);
  396. };
  397. const regenWgPeerKeypair = (peerName: number) => {
  398. const kp = Wireguard.generateKeypair();
  399. form.setFieldValue(['settings', 'peers', peerName, 'privateKey'], kp.privateKey);
  400. form.setFieldValue(['settings', 'peers', peerName, 'publicKey'], kp.publicKey);
  401. };
  402. const matchesVlessAuth = (
  403. block: { id?: string; label?: string } | undefined | null,
  404. authId: string,
  405. ) => {
  406. if (block?.id === authId) return true;
  407. const label = (block?.label || '').toLowerCase().replace(/[-_\s]/g, '');
  408. if (authId === 'mlkem768') return label.includes('mlkem768');
  409. if (authId === 'x25519') return label.includes('x25519');
  410. return false;
  411. };
  412. const getNewVlessEnc = async (authId: string) => {
  413. if (!authId) return;
  414. setSaving(true);
  415. try {
  416. const msg = await HttpUtil.get('/panel/api/server/getNewVlessEnc');
  417. if (!msg?.success) return;
  418. const obj = msg.obj as {
  419. auths?: { decryption: string; encryption: string; label?: string; id?: string }[];
  420. };
  421. const block = (obj.auths || []).find((a) => matchesVlessAuth(a, authId));
  422. if (!block) return;
  423. form.setFieldValue(['settings', 'decryption'], block.decryption);
  424. form.setFieldValue(['settings', 'encryption'], block.encryption);
  425. } finally {
  426. setSaving(false);
  427. }
  428. };
  429. const clearVlessEnc = () => {
  430. form.setFieldValue(['settings', 'decryption'], 'none');
  431. form.setFieldValue(['settings', 'encryption'], 'none');
  432. };
  433. const selectedVlessAuth = (() => {
  434. const enc = typeof vlessEncryption === 'string' ? vlessEncryption : '';
  435. if (!enc || enc === 'none') return 'None';
  436. const parts = enc.split('.').filter(Boolean);
  437. const authKey = parts[parts.length - 1] || '';
  438. if (!authKey) return t('pages.inbounds.vlessAuthCustom');
  439. return authKey.length > 300
  440. ? t('pages.inbounds.vlessAuthMlkem768')
  441. : t('pages.inbounds.vlessAuthX25519');
  442. })();
  443. useEffect(() => {
  444. if (!open) return;
  445. const initial = mode === 'edit' && dbInbound
  446. ? rawInboundToFormValues(dbInbound)
  447. : buildAddModeValues();
  448. form.resetFields();
  449. form.setFieldsValue(initial);
  450. if (
  451. mode === 'edit'
  452. && dbInbound
  453. && (dbInbound.protocol === Protocols.VLESS || dbInbound.protocol === Protocols.TROJAN)
  454. ) {
  455. loadFallbacks(dbInbound.id);
  456. } else {
  457. setFallbacks([]);
  458. }
  459. }, [open, mode, dbInbound, form]);
  460. // Why: protocol picker reset cascades through the form — clearing the
  461. // settings DU branch and dropping a nodeId that no longer applies. The
  462. // legacy modal did this imperatively in onProtocolChange; here we hook
  463. // into AntD's onValuesChange and let setFieldValue keep the rest of
  464. // the form state intact.
  465. const onValuesChange = (changed: Partial<InboundFormValues>) => {
  466. if (mode === 'edit') return;
  467. if ('protocol' in changed && typeof changed.protocol === 'string') {
  468. const next = changed.protocol;
  469. const settings = createDefaultInboundSettings(next) ?? undefined;
  470. form.setFieldValue('settings', settings);
  471. if (!NODE_ELIGIBLE_PROTOCOLS.has(next)) {
  472. form.setFieldValue('nodeId', null);
  473. }
  474. }
  475. };
  476. const submit = async () => {
  477. let values: InboundFormValues;
  478. try {
  479. values = await form.validateFields();
  480. } catch {
  481. return;
  482. }
  483. const parsed = InboundFormSchema.safeParse(values);
  484. if (!parsed.success) {
  485. const issue = parsed.error.issues[0];
  486. messageApi.error(
  487. t(issue?.message ?? 'somethingWentWrong', {
  488. defaultValue: issue?.message ?? 'invalid',
  489. }),
  490. );
  491. return;
  492. }
  493. setSaving(true);
  494. try {
  495. const payload = formValuesToWirePayload(parsed.data);
  496. const url = mode === 'edit' && dbInbound
  497. ? `/panel/api/inbounds/update/${dbInbound.id}`
  498. : '/panel/api/inbounds/add';
  499. const msg = await HttpUtil.post(url, payload);
  500. if (msg?.success) {
  501. if (isFallbackHost) {
  502. const obj = msg.obj as { id?: number; Id?: number } | null;
  503. const masterId = mode === 'edit'
  504. ? dbInbound!.id
  505. : (obj?.id ?? obj?.Id ?? 0);
  506. if (masterId) await saveFallbacks(masterId);
  507. }
  508. onSaved();
  509. onClose();
  510. }
  511. } finally {
  512. setSaving(false);
  513. }
  514. };
  515. const title = mode === 'edit'
  516. ? t('pages.inbounds.modifyInbound')
  517. : t('pages.inbounds.addInbound');
  518. const okText = mode === 'edit'
  519. ? t('pages.clients.submitEdit')
  520. : t('create');
  521. const basicTab = (
  522. <>
  523. <Form.Item name="enable" label={t('enable')} valuePropName="checked">
  524. <Switch />
  525. </Form.Item>
  526. <Form.Item name="remark" label={t('pages.inbounds.remark')}>
  527. <Input />
  528. </Form.Item>
  529. {selectableNodes.length > 0 && isNodeEligible && (
  530. <Form.Item name="nodeId" label={t('pages.inbounds.deployTo')}>
  531. <Select
  532. disabled={mode === 'edit'}
  533. placeholder={t('pages.inbounds.localPanel')}
  534. allowClear
  535. >
  536. <Select.Option value={null}>{t('pages.inbounds.localPanel')}</Select.Option>
  537. {selectableNodes.map((n) => (
  538. <Select.Option
  539. key={n.id}
  540. value={n.id}
  541. disabled={n.status === 'offline'}
  542. >
  543. {n.name}{n.status === 'offline' ? ' (offline)' : ''}
  544. </Select.Option>
  545. ))}
  546. </Select>
  547. </Form.Item>
  548. )}
  549. <Form.Item name="protocol" label={t('pages.inbounds.protocol')}>
  550. <Select disabled={mode === 'edit'} options={PROTOCOL_OPTIONS} />
  551. </Form.Item>
  552. <Form.Item name="listen" label={t('pages.inbounds.address')}>
  553. <Input placeholder={t('pages.inbounds.monitorDesc')} />
  554. </Form.Item>
  555. <Form.Item
  556. name="port"
  557. label={t('pages.inbounds.port')}
  558. rules={[antdRule(InboundFormBaseSchema.shape.port, t)]}
  559. >
  560. <InputNumber min={1} max={65535} />
  561. </Form.Item>
  562. <Form.Item
  563. label={
  564. <Tooltip title={t('pages.inbounds.meansNoLimit')}>
  565. {t('pages.inbounds.totalFlow')}
  566. </Tooltip>
  567. }
  568. >
  569. <Form.Item
  570. noStyle
  571. shouldUpdate={(prev, curr) => prev.total !== curr.total}
  572. >
  573. {({ getFieldValue, setFieldValue }) => {
  574. const totalBytes = (getFieldValue('total') as number) ?? 0;
  575. const totalGB = totalBytes
  576. ? Math.round((totalBytes / SizeFormatter.ONE_GB) * 100) / 100
  577. : 0;
  578. return (
  579. <InputNumber
  580. value={totalGB}
  581. min={0}
  582. step={1}
  583. onChange={(v) => {
  584. const bytes = NumberFormatter.toFixed(
  585. (Number(v) || 0) * SizeFormatter.ONE_GB,
  586. 0,
  587. );
  588. setFieldValue('total', bytes);
  589. }}
  590. />
  591. );
  592. }}
  593. </Form.Item>
  594. </Form.Item>
  595. <Form.Item name="trafficReset" label={t('pages.inbounds.periodicTrafficResetTitle')}>
  596. <Select>
  597. {TRAFFIC_RESETS.map((r) => (
  598. <Select.Option key={r} value={r}>
  599. {t(`pages.inbounds.periodicTrafficReset.${r}`)}
  600. </Select.Option>
  601. ))}
  602. </Select>
  603. </Form.Item>
  604. <Form.Item
  605. label={
  606. <Tooltip title={t('pages.inbounds.leaveBlankToNeverExpire')}>
  607. {t('pages.inbounds.expireDate')}
  608. </Tooltip>
  609. }
  610. >
  611. <Form.Item
  612. noStyle
  613. shouldUpdate={(prev, curr) => prev.expiryTime !== curr.expiryTime}
  614. >
  615. {({ getFieldValue, setFieldValue }) => {
  616. const expiry = (getFieldValue('expiryTime') as number) ?? 0;
  617. return (
  618. <DateTimePicker
  619. value={expiry > 0 ? dayjs(expiry) : null}
  620. onChange={(d) => setFieldValue('expiryTime', d ? d.valueOf() : 0)}
  621. />
  622. );
  623. }}
  624. </Form.Item>
  625. </Form.Item>
  626. </>
  627. );
  628. const fallbacksCard = (
  629. <Card size="small" className="mt-12" title={t('pages.inbounds.fallbacks.title') || 'Fallbacks'}>
  630. {fallbacks.length === 0 && (
  631. <Empty
  632. description={t('pages.inbounds.fallbacks.empty') || 'No fallbacks yet'}
  633. styles={{ image: { height: 40 } }}
  634. style={{ margin: '8px 0 12px' }}
  635. />
  636. )}
  637. {fallbacks.map((record, idx) => (
  638. <div
  639. key={record.rowKey}
  640. style={{ border: '1px solid var(--app-border-tertiary)', borderRadius: 6, padding: '10px 12px', marginBottom: 8 }}
  641. >
  642. <Space.Compact block style={{ marginBottom: 6 }}>
  643. <Select
  644. value={record.childId}
  645. options={fallbackChildOptions}
  646. showSearch
  647. placeholder={t('pages.inbounds.fallbacks.pickInbound') || 'Pick an inbound'}
  648. filterOption={(input, option) =>
  649. ((option?.label as string) || '').toLowerCase().includes(input.toLowerCase())
  650. }
  651. style={{ width: '100%' }}
  652. onChange={(v) => updateFallback(record.rowKey, { childId: v })}
  653. />
  654. <Button
  655. disabled={idx === 0}
  656. onClick={() => moveFallback(idx, -1)}
  657. title="Move up"
  658. >
  659. <ArrowUpOutlined />
  660. </Button>
  661. <Button
  662. disabled={idx === fallbacks.length - 1}
  663. onClick={() => moveFallback(idx, 1)}
  664. title="Move down"
  665. >
  666. <ArrowDownOutlined />
  667. </Button>
  668. <Button danger onClick={() => removeFallback(idx)}>
  669. <DeleteOutlined />
  670. </Button>
  671. </Space.Compact>
  672. <Space.Compact block>
  673. <InputAddon>SNI</InputAddon>
  674. <Input
  675. placeholder={t('pages.inbounds.fallbacks.matchAny') || 'any'}
  676. value={record.name}
  677. onChange={(e) => updateFallback(record.rowKey, { name: e.target.value })}
  678. />
  679. <InputAddon>ALPN</InputAddon>
  680. <Input
  681. placeholder={t('pages.inbounds.fallbacks.matchAny') || 'any'}
  682. value={record.alpn}
  683. onChange={(e) => updateFallback(record.rowKey, { alpn: e.target.value })}
  684. />
  685. <InputAddon>Path</InputAddon>
  686. <Input
  687. placeholder="/"
  688. value={record.path}
  689. onChange={(e) => updateFallback(record.rowKey, { path: e.target.value })}
  690. />
  691. <InputAddon>xver</InputAddon>
  692. <InputNumber
  693. min={0}
  694. max={2}
  695. value={record.xver}
  696. onChange={(v) => updateFallback(record.rowKey, { xver: Number(v) || 0 })}
  697. />
  698. </Space.Compact>
  699. </div>
  700. ))}
  701. <Space>
  702. <Button size="small" onClick={addFallback}>
  703. <PlusOutlined /> {t('pages.inbounds.fallbacks.add') || 'Add fallback'}
  704. </Button>
  705. <Button
  706. size="small"
  707. onClick={addAllFallbacks}
  708. disabled={fallbackChildOptions.length === 0
  709. || fallbacks.length >= fallbackChildOptions.length}
  710. title="Add a fallback row for every eligible inbound not yet wired up"
  711. >
  712. Add all
  713. </Button>
  714. </Space>
  715. </Card>
  716. );
  717. const protocolTab = (
  718. <>
  719. {protocol === Protocols.WIREGUARD && (
  720. <>
  721. <Form.Item
  722. name={['settings', 'secretKey']}
  723. label={
  724. <>
  725. Secret key{' '}
  726. <SyncOutlined className="random-icon" onClick={regenInboundWg} />
  727. </>
  728. }
  729. >
  730. <Input />
  731. </Form.Item>
  732. <Form.Item label="Public key">
  733. <Input value={wgPubKey} disabled />
  734. </Form.Item>
  735. <Form.Item name={['settings', 'mtu']} label="MTU">
  736. <InputNumber />
  737. </Form.Item>
  738. <Form.Item
  739. name={['settings', 'noKernelTun']}
  740. label="No-kernel TUN"
  741. valuePropName="checked"
  742. >
  743. <Switch />
  744. </Form.Item>
  745. <Form.List name={['settings', 'peers']}>
  746. {(fields, { add, remove }) => (
  747. <>
  748. <Form.Item label="Peers">
  749. <Button
  750. size="small"
  751. onClick={() => add({
  752. publicKey: '',
  753. allowedIPs: [],
  754. })}
  755. >
  756. <PlusOutlined /> Add peer
  757. </Button>
  758. </Form.Item>
  759. {fields.map((field, idx) => (
  760. <div key={field.key} className="wg-peer">
  761. <Form.Item label={`Peer ${idx + 1}`}>
  762. {fields.length > 1 && (
  763. <Button
  764. size="small"
  765. danger
  766. onClick={() => remove(field.name)}
  767. >
  768. <MinusOutlined />
  769. </Button>
  770. )}
  771. </Form.Item>
  772. <Form.Item
  773. name={[field.name, 'privateKey']}
  774. label={
  775. <>
  776. Secret key{' '}
  777. <SyncOutlined
  778. className="random-icon"
  779. onClick={() => regenWgPeerKeypair(field.name)}
  780. />
  781. </>
  782. }
  783. >
  784. <Input />
  785. </Form.Item>
  786. <Form.Item name={[field.name, 'publicKey']} label="Public key">
  787. <Input />
  788. </Form.Item>
  789. <Form.Item name={[field.name, 'preSharedKey']} label="PSK">
  790. <Input />
  791. </Form.Item>
  792. <Form.List name={[field.name, 'allowedIPs']}>
  793. {(ipFields, { add: addIp, remove: removeIp }) => (
  794. <Form.Item label="Allowed IPs">
  795. <Button size="small" onClick={() => addIp('')}>
  796. <PlusOutlined />
  797. </Button>
  798. {ipFields.map((ipField) => (
  799. <Space.Compact key={ipField.key} block className="mt-4">
  800. <Form.Item name={ipField.name} noStyle>
  801. <Input />
  802. </Form.Item>
  803. {ipFields.length > 1 && (
  804. <Button size="small" onClick={() => removeIp(ipField.name)}>
  805. <MinusOutlined />
  806. </Button>
  807. )}
  808. </Space.Compact>
  809. ))}
  810. </Form.Item>
  811. )}
  812. </Form.List>
  813. <Form.Item name={[field.name, 'keepAlive']} label="Keep-alive">
  814. <InputNumber min={0} />
  815. </Form.Item>
  816. </div>
  817. ))}
  818. </>
  819. )}
  820. </Form.List>
  821. </>
  822. )}
  823. {protocol === Protocols.TUN && (
  824. <>
  825. <Form.Item name={['settings', 'name']} label="Interface name">
  826. <Input placeholder="xray0" />
  827. </Form.Item>
  828. <Form.Item name={['settings', 'mtu']} label="MTU">
  829. <InputNumber min={0} />
  830. </Form.Item>
  831. <Form.List name={['settings', 'gateway']}>
  832. {(fields, { add, remove }) => (
  833. <Form.Item label="Gateway">
  834. <Button size="small" onClick={() => add('')}>
  835. <PlusOutlined />
  836. </Button>
  837. {fields.map((field, j) => (
  838. <Space.Compact key={field.key} block className="mt-4">
  839. <Form.Item name={field.name} noStyle>
  840. <Input placeholder={j === 0 ? '10.0.0.1/16' : 'fc00::1/64'} />
  841. </Form.Item>
  842. <Button size="small" onClick={() => remove(field.name)}>
  843. <MinusOutlined />
  844. </Button>
  845. </Space.Compact>
  846. ))}
  847. </Form.Item>
  848. )}
  849. </Form.List>
  850. <Form.List name={['settings', 'dns']}>
  851. {(fields, { add, remove }) => (
  852. <Form.Item label="DNS">
  853. <Button size="small" onClick={() => add('')}>
  854. <PlusOutlined />
  855. </Button>
  856. {fields.map((field, j) => (
  857. <Space.Compact key={field.key} block className="mt-4">
  858. <Form.Item name={field.name} noStyle>
  859. <Input placeholder={j === 0 ? '1.1.1.1' : '8.8.8.8'} />
  860. </Form.Item>
  861. <Button size="small" onClick={() => remove(field.name)}>
  862. <MinusOutlined />
  863. </Button>
  864. </Space.Compact>
  865. ))}
  866. </Form.Item>
  867. )}
  868. </Form.List>
  869. <Form.Item name={['settings', 'userLevel']} label="User level">
  870. <InputNumber min={0} />
  871. </Form.Item>
  872. <Form.List name={['settings', 'autoSystemRoutingTable']}>
  873. {(fields, { add, remove }) => (
  874. <Form.Item
  875. label={
  876. <Tooltip title="Windows-only. CIDRs added to the system routing table automatically so matching traffic goes through TUN.">
  877. Auto system routes
  878. </Tooltip>
  879. }
  880. >
  881. <Button size="small" onClick={() => add('')}>
  882. <PlusOutlined />
  883. </Button>
  884. {fields.map((field, j) => (
  885. <Space.Compact key={field.key} block className="mt-4">
  886. <Form.Item name={field.name} noStyle>
  887. <Input placeholder={j === 0 ? '0.0.0.0/0' : '::/0'} />
  888. </Form.Item>
  889. <Button size="small" onClick={() => remove(field.name)}>
  890. <MinusOutlined />
  891. </Button>
  892. </Space.Compact>
  893. ))}
  894. </Form.Item>
  895. )}
  896. </Form.List>
  897. <Form.Item
  898. name={['settings', 'autoOutboundsInterface']}
  899. label={
  900. <Tooltip title="Physical interface for outbound traffic. Use 'auto' to detect; auto-enabled when Auto system routes is set.">
  901. Auto outbounds interface
  902. </Tooltip>
  903. }
  904. >
  905. <Input placeholder="auto" />
  906. </Form.Item>
  907. </>
  908. )}
  909. {protocol === Protocols.TUNNEL && (
  910. <>
  911. <Form.Item name={['settings', 'rewriteAddress']} label="Rewrite address">
  912. <Input />
  913. </Form.Item>
  914. <Form.Item name={['settings', 'rewritePort']} label="Rewrite port">
  915. <InputNumber min={0} max={65535} />
  916. </Form.Item>
  917. <Form.Item name={['settings', 'allowedNetwork']} label="Allowed network">
  918. <Select>
  919. <Select.Option value="tcp,udp">TCP, UDP</Select.Option>
  920. <Select.Option value="tcp">TCP</Select.Option>
  921. <Select.Option value="udp">UDP</Select.Option>
  922. </Select>
  923. </Form.Item>
  924. <Form.List name={['settings', 'portMap']}>
  925. {(fields, { add, remove }) => (
  926. <>
  927. <Form.Item label="Port map">
  928. <Button size="small" onClick={() => add({ name: '', value: '' })}>
  929. <PlusOutlined />
  930. </Button>
  931. </Form.Item>
  932. {fields.length > 0 && (
  933. <Form.Item wrapperCol={{ span: 24 }}>
  934. {fields.map((field, idx) => (
  935. <Space.Compact key={field.key} className="mb-8" block>
  936. <InputAddon>{String(idx + 1)}</InputAddon>
  937. <Form.Item name={[field.name, 'name']} noStyle>
  938. <Input placeholder="5555" />
  939. </Form.Item>
  940. <Form.Item name={[field.name, 'value']} noStyle>
  941. <Input placeholder="1.1.1.1:7777" />
  942. </Form.Item>
  943. <Button onClick={() => remove(field.name)}>
  944. <MinusOutlined />
  945. </Button>
  946. </Space.Compact>
  947. ))}
  948. </Form.Item>
  949. )}
  950. </>
  951. )}
  952. </Form.List>
  953. <Form.Item
  954. name={['settings', 'followRedirect']}
  955. label="Follow redirect"
  956. valuePropName="checked"
  957. >
  958. <Switch />
  959. </Form.Item>
  960. </>
  961. )}
  962. {(protocol === Protocols.HTTP || protocol === Protocols.MIXED) && (
  963. <>
  964. <Form.List name={['settings', 'accounts']}>
  965. {(fields, { add, remove }) => (
  966. <>
  967. <Form.Item label="Accounts">
  968. <Button size="small" onClick={() => add({ user: '', pass: '' })}>
  969. <PlusOutlined /> Add
  970. </Button>
  971. </Form.Item>
  972. {fields.length > 0 && (
  973. <Form.Item wrapperCol={{ span: 24 }}>
  974. {fields.map((field, idx) => (
  975. <Space.Compact key={field.key} className="mb-8" block>
  976. <InputAddon>{String(idx + 1)}</InputAddon>
  977. <Form.Item name={[field.name, 'user']} noStyle>
  978. <Input placeholder="Username" />
  979. </Form.Item>
  980. <Form.Item name={[field.name, 'pass']} noStyle>
  981. <Input placeholder="Password" />
  982. </Form.Item>
  983. <Button onClick={() => remove(field.name)}>
  984. <MinusOutlined />
  985. </Button>
  986. </Space.Compact>
  987. ))}
  988. </Form.Item>
  989. )}
  990. </>
  991. )}
  992. </Form.List>
  993. {protocol === Protocols.HTTP && (
  994. <Form.Item
  995. name={['settings', 'allowTransparent']}
  996. label="Allow transparent"
  997. valuePropName="checked"
  998. >
  999. <Switch />
  1000. </Form.Item>
  1001. )}
  1002. {protocol === Protocols.MIXED && (
  1003. <>
  1004. <Form.Item name={['settings', 'auth']} label="Auth">
  1005. <Select>
  1006. <Select.Option value="noauth">noauth</Select.Option>
  1007. <Select.Option value="password">password</Select.Option>
  1008. </Select>
  1009. </Form.Item>
  1010. <Form.Item
  1011. name={['settings', 'udp']}
  1012. label="UDP"
  1013. valuePropName="checked"
  1014. >
  1015. <Switch />
  1016. </Form.Item>
  1017. {mixedUdpOn && (
  1018. <Form.Item name={['settings', 'ip']} label="UDP IP">
  1019. <Input />
  1020. </Form.Item>
  1021. )}
  1022. </>
  1023. )}
  1024. </>
  1025. )}
  1026. {protocol === Protocols.SHADOWSOCKS && (
  1027. <>
  1028. <Form.Item name={['settings', 'method']} label="Encryption method">
  1029. <Select
  1030. onChange={(v) => {
  1031. form.setFieldValue(
  1032. ['settings', 'password'],
  1033. RandomUtil.randomShadowsocksPassword(v as string),
  1034. );
  1035. }}
  1036. >
  1037. {SSMethodSchema.options.map((m) => (
  1038. <Select.Option key={m} value={m}>{m}</Select.Option>
  1039. ))}
  1040. </Select>
  1041. </Form.Item>
  1042. {isSSWith2022 && (
  1043. <Form.Item
  1044. name={['settings', 'password']}
  1045. label={
  1046. <>
  1047. Password{' '}
  1048. <SyncOutlined
  1049. className="random-icon"
  1050. onClick={() => {
  1051. const method = form.getFieldValue(['settings', 'method']);
  1052. form.setFieldValue(
  1053. ['settings', 'password'],
  1054. RandomUtil.randomShadowsocksPassword(method as string),
  1055. );
  1056. }}
  1057. />
  1058. </>
  1059. }
  1060. >
  1061. <Input />
  1062. </Form.Item>
  1063. )}
  1064. <Form.Item name={['settings', 'network']} label="Network">
  1065. <Select style={{ width: 120 }}>
  1066. <Select.Option value="tcp,udp">TCP, UDP</Select.Option>
  1067. <Select.Option value="tcp">TCP</Select.Option>
  1068. <Select.Option value="udp">UDP</Select.Option>
  1069. </Select>
  1070. </Form.Item>
  1071. <Form.Item
  1072. name={['settings', 'ivCheck']}
  1073. label="ivCheck"
  1074. valuePropName="checked"
  1075. >
  1076. <Switch />
  1077. </Form.Item>
  1078. </>
  1079. )}
  1080. {protocol === Protocols.VLESS && (
  1081. <>
  1082. <Form.Item name={['settings', 'decryption']} label={t('pages.inbounds.decryption')}>
  1083. <Input />
  1084. </Form.Item>
  1085. <Form.Item name={['settings', 'encryption']} label={t('pages.inbounds.encryption')}>
  1086. <Input />
  1087. </Form.Item>
  1088. <Form.Item label=" ">
  1089. <Space size={8} wrap>
  1090. <Button type="primary" loading={saving} onClick={() => getNewVlessEnc('x25519')}>
  1091. {t('pages.inbounds.vlessAuthX25519')}
  1092. </Button>
  1093. <Button type="primary" loading={saving} onClick={() => getNewVlessEnc('mlkem768')}>
  1094. {t('pages.inbounds.vlessAuthMlkem768')}
  1095. </Button>
  1096. <Button danger onClick={clearVlessEnc}>{t('clear')}</Button>
  1097. </Space>
  1098. <Text type="secondary" className="vless-auth-state">
  1099. {t('pages.inbounds.vlessAuthSelected', { auth: selectedVlessAuth })}
  1100. </Text>
  1101. </Form.Item>
  1102. </>
  1103. )}
  1104. {isFallbackHost && fallbacksCard}
  1105. </>
  1106. );
  1107. // Switching `network` swaps which per-network key (tcpSettings, wsSettings,
  1108. // grpcSettings, ...) appears on the wire. We clear the previously selected
  1109. // network's settings blob and seed a default empty object for the new one
  1110. // so AntD's Form.Items aren't pointed at undefined nested paths.
  1111. const onNetworkChange = (next: string) => {
  1112. const ALL = ['tcpSettings', 'kcpSettings', 'wsSettings', 'grpcSettings', 'httpupgradeSettings', 'xhttpSettings'];
  1113. const current = (form.getFieldValue('streamSettings') as Record<string, unknown>) ?? {};
  1114. const cleaned: Record<string, unknown> = { ...current, network: next };
  1115. for (const k of ALL) {
  1116. if (k !== `${next}Settings`) delete cleaned[k];
  1117. }
  1118. cleaned[`${next}Settings`] = {};
  1119. form.setFieldValue('streamSettings', cleaned);
  1120. };
  1121. const streamTab = (
  1122. <>
  1123. {protocol !== Protocols.HYSTERIA && (
  1124. <Form.Item label="Transmission">
  1125. <Select
  1126. value={network}
  1127. style={{ width: '75%' }}
  1128. onChange={onNetworkChange}
  1129. >
  1130. <Select.Option value="tcp">TCP (RAW)</Select.Option>
  1131. <Select.Option value="kcp">mKCP</Select.Option>
  1132. <Select.Option value="ws">WebSocket</Select.Option>
  1133. <Select.Option value="grpc">gRPC</Select.Option>
  1134. <Select.Option value="httpupgrade">HTTPUpgrade</Select.Option>
  1135. <Select.Option value="xhttp">XHTTP</Select.Option>
  1136. </Select>
  1137. </Form.Item>
  1138. )}
  1139. {network === 'tcp' && (
  1140. <>
  1141. <Form.Item
  1142. name={['streamSettings', 'tcpSettings', 'acceptProxyProtocol']}
  1143. label="Proxy Protocol"
  1144. valuePropName="checked"
  1145. >
  1146. <Switch />
  1147. </Form.Item>
  1148. <Form.Item label={`HTTP ${t('camouflage')}`}>
  1149. <Form.Item
  1150. noStyle
  1151. shouldUpdate={(prev, curr) =>
  1152. prev.streamSettings?.tcpSettings?.header?.type
  1153. !== curr.streamSettings?.tcpSettings?.header?.type
  1154. }
  1155. >
  1156. {({ getFieldValue, setFieldValue }) => {
  1157. const headerType = getFieldValue(
  1158. ['streamSettings', 'tcpSettings', 'header', 'type'],
  1159. ) as string | undefined;
  1160. return (
  1161. <Switch
  1162. checked={headerType === 'http'}
  1163. onChange={(v) => {
  1164. setFieldValue(
  1165. ['streamSettings', 'tcpSettings', 'header'],
  1166. v
  1167. ? {
  1168. type: 'http',
  1169. request: {
  1170. version: '1.1',
  1171. method: 'GET',
  1172. path: ['/'],
  1173. headers: {},
  1174. },
  1175. response: {
  1176. version: '1.1',
  1177. status: '200',
  1178. reason: 'OK',
  1179. headers: {},
  1180. },
  1181. }
  1182. : { type: 'none' },
  1183. );
  1184. }}
  1185. />
  1186. );
  1187. }}
  1188. </Form.Item>
  1189. </Form.Item>
  1190. {/* Host + path camouflage inputs only render when the Switch
  1191. above is on. Both are string[] on the wire; normalize +
  1192. getValueProps translate to/from comma-joined input. Mirrors
  1193. the symmetric outbound side. */}
  1194. <Form.Item
  1195. noStyle
  1196. shouldUpdate={(prev, curr) =>
  1197. prev.streamSettings?.tcpSettings?.header?.type
  1198. !== curr.streamSettings?.tcpSettings?.header?.type
  1199. }
  1200. >
  1201. {({ getFieldValue }) => {
  1202. const headerType = getFieldValue(
  1203. ['streamSettings', 'tcpSettings', 'header', 'type'],
  1204. ) as string | undefined;
  1205. if (headerType !== 'http') return null;
  1206. return (
  1207. <>
  1208. <Form.Item
  1209. label={t('host')}
  1210. name={[
  1211. 'streamSettings', 'tcpSettings', 'header',
  1212. 'request', 'headers', 'Host',
  1213. ]}
  1214. normalize={(v: unknown) =>
  1215. typeof v === 'string'
  1216. ? v.split(',').map((s) => s.trim()).filter(Boolean)
  1217. : Array.isArray(v) ? v : []
  1218. }
  1219. getValueProps={(v: unknown) => ({
  1220. value: Array.isArray(v) ? v.join(',') : '',
  1221. })}
  1222. >
  1223. <Input placeholder="example.com,cdn.example.com" />
  1224. </Form.Item>
  1225. <Form.Item
  1226. label={t('path')}
  1227. name={[
  1228. 'streamSettings', 'tcpSettings', 'header',
  1229. 'request', 'path',
  1230. ]}
  1231. normalize={(v: unknown) =>
  1232. typeof v === 'string'
  1233. ? v.split(',').map((s) => s.trim()).filter(Boolean)
  1234. : Array.isArray(v) ? v : ['/']
  1235. }
  1236. getValueProps={(v: unknown) => ({
  1237. value: Array.isArray(v) ? v.join(',') : '/',
  1238. })}
  1239. >
  1240. <Input placeholder="/,/api,/static" />
  1241. </Form.Item>
  1242. <Form.Item
  1243. label="Request headers"
  1244. name={[
  1245. 'streamSettings', 'tcpSettings', 'header',
  1246. 'request', 'headers',
  1247. ]}
  1248. >
  1249. <HeaderMapEditor mode="v2" />
  1250. </Form.Item>
  1251. {/* Response side: shaped as a separate sub-object on the
  1252. wire ({version, status, reason, headers}). Inbound is
  1253. the server, so the response side is the one the panel
  1254. sends back to clients during HTTP camouflage. */}
  1255. <Form.Item
  1256. label="Response version"
  1257. name={[
  1258. 'streamSettings', 'tcpSettings', 'header',
  1259. 'response', 'version',
  1260. ]}
  1261. >
  1262. <Input placeholder="1.1" />
  1263. </Form.Item>
  1264. <Form.Item
  1265. label="Response status"
  1266. name={[
  1267. 'streamSettings', 'tcpSettings', 'header',
  1268. 'response', 'status',
  1269. ]}
  1270. >
  1271. <Input placeholder="200" />
  1272. </Form.Item>
  1273. <Form.Item
  1274. label="Response reason"
  1275. name={[
  1276. 'streamSettings', 'tcpSettings', 'header',
  1277. 'response', 'reason',
  1278. ]}
  1279. >
  1280. <Input placeholder="OK" />
  1281. </Form.Item>
  1282. <Form.Item
  1283. label="Response headers"
  1284. name={[
  1285. 'streamSettings', 'tcpSettings', 'header',
  1286. 'response', 'headers',
  1287. ]}
  1288. >
  1289. <HeaderMapEditor mode="v2" />
  1290. </Form.Item>
  1291. </>
  1292. );
  1293. }}
  1294. </Form.Item>
  1295. </>
  1296. )}
  1297. {network === 'ws' && (
  1298. <>
  1299. <Form.Item
  1300. name={['streamSettings', 'wsSettings', 'acceptProxyProtocol']}
  1301. label="Proxy Protocol"
  1302. valuePropName="checked"
  1303. >
  1304. <Switch />
  1305. </Form.Item>
  1306. <Form.Item name={['streamSettings', 'wsSettings', 'host']} label={t('host')}>
  1307. <Input />
  1308. </Form.Item>
  1309. <Form.Item name={['streamSettings', 'wsSettings', 'path']} label={t('path')}>
  1310. <Input />
  1311. </Form.Item>
  1312. <Form.Item
  1313. name={['streamSettings', 'wsSettings', 'heartbeatPeriod']}
  1314. label="Heartbeat Period"
  1315. >
  1316. <InputNumber min={0} />
  1317. </Form.Item>
  1318. <Form.Item
  1319. label="Headers"
  1320. name={['streamSettings', 'wsSettings', 'headers']}
  1321. >
  1322. <HeaderMapEditor mode="v1" />
  1323. </Form.Item>
  1324. </>
  1325. )}
  1326. {network === 'grpc' && (
  1327. <>
  1328. <Form.Item
  1329. name={['streamSettings', 'grpcSettings', 'serviceName']}
  1330. label="Service Name"
  1331. >
  1332. <Input />
  1333. </Form.Item>
  1334. <Form.Item
  1335. name={['streamSettings', 'grpcSettings', 'authority']}
  1336. label="Authority"
  1337. >
  1338. <Input />
  1339. </Form.Item>
  1340. <Form.Item
  1341. name={['streamSettings', 'grpcSettings', 'multiMode']}
  1342. label="Multi Mode"
  1343. valuePropName="checked"
  1344. >
  1345. <Switch />
  1346. </Form.Item>
  1347. </>
  1348. )}
  1349. {network === 'xhttp' && (
  1350. <>
  1351. <Form.Item name={['streamSettings', 'xhttpSettings', 'host']} label={t('host')}>
  1352. <Input />
  1353. </Form.Item>
  1354. <Form.Item name={['streamSettings', 'xhttpSettings', 'path']} label={t('path')}>
  1355. <Input />
  1356. </Form.Item>
  1357. <Form.Item name={['streamSettings', 'xhttpSettings', 'mode']} label="Mode">
  1358. <Select style={{ width: '50%' }}>
  1359. {(['auto', 'packet-up', 'stream-up', 'stream-one'] as const).map((m) => (
  1360. <Select.Option key={m} value={m}>{m}</Select.Option>
  1361. ))}
  1362. </Select>
  1363. </Form.Item>
  1364. {xhttpMode === 'packet-up' && (
  1365. <>
  1366. <Form.Item
  1367. name={['streamSettings', 'xhttpSettings', 'scMaxBufferedPosts']}
  1368. label="Max Buffered Upload"
  1369. >
  1370. <InputNumber />
  1371. </Form.Item>
  1372. <Form.Item
  1373. name={['streamSettings', 'xhttpSettings', 'scMaxEachPostBytes']}
  1374. label="Max Upload Size (Byte)"
  1375. >
  1376. <Input />
  1377. </Form.Item>
  1378. </>
  1379. )}
  1380. {xhttpMode === 'stream-up' && (
  1381. <Form.Item
  1382. name={['streamSettings', 'xhttpSettings', 'scStreamUpServerSecs']}
  1383. label="Stream-Up Server"
  1384. >
  1385. <Input />
  1386. </Form.Item>
  1387. )}
  1388. <Form.Item
  1389. name={['streamSettings', 'xhttpSettings', 'serverMaxHeaderBytes']}
  1390. label="Server Max Header Bytes"
  1391. >
  1392. <InputNumber min={0} placeholder="0 (default)" />
  1393. </Form.Item>
  1394. <Form.Item
  1395. name={['streamSettings', 'xhttpSettings', 'xPaddingBytes']}
  1396. label="Padding Bytes"
  1397. >
  1398. <Input />
  1399. </Form.Item>
  1400. <Form.Item
  1401. name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
  1402. label="Uplink HTTP Method"
  1403. >
  1404. <Select>
  1405. <Select.Option value="">Default (POST)</Select.Option>
  1406. <Select.Option value="POST">POST</Select.Option>
  1407. <Select.Option value="PUT">PUT</Select.Option>
  1408. <Select.Option value="GET" disabled={xhttpMode !== 'packet-up'}>
  1409. GET (packet-up only)
  1410. </Select.Option>
  1411. </Select>
  1412. </Form.Item>
  1413. <Form.Item
  1414. name={['streamSettings', 'xhttpSettings', 'xPaddingObfsMode']}
  1415. label="Padding Obfs Mode"
  1416. valuePropName="checked"
  1417. >
  1418. <Switch />
  1419. </Form.Item>
  1420. {xhttpObfsMode && (
  1421. <>
  1422. <Form.Item
  1423. name={['streamSettings', 'xhttpSettings', 'xPaddingKey']}
  1424. label="Padding Key"
  1425. >
  1426. <Input placeholder="x_padding" />
  1427. </Form.Item>
  1428. <Form.Item
  1429. name={['streamSettings', 'xhttpSettings', 'xPaddingHeader']}
  1430. label="Padding Header"
  1431. >
  1432. <Input placeholder="X-Padding" />
  1433. </Form.Item>
  1434. <Form.Item
  1435. name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
  1436. label="Padding Placement"
  1437. >
  1438. <Select>
  1439. <Select.Option value="">Default (queryInHeader)</Select.Option>
  1440. <Select.Option value="queryInHeader">queryInHeader</Select.Option>
  1441. <Select.Option value="header">header</Select.Option>
  1442. <Select.Option value="cookie">cookie</Select.Option>
  1443. <Select.Option value="query">query</Select.Option>
  1444. </Select>
  1445. </Form.Item>
  1446. <Form.Item
  1447. name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
  1448. label="Padding Method"
  1449. >
  1450. <Select>
  1451. <Select.Option value="">Default (repeat-x)</Select.Option>
  1452. <Select.Option value="repeat-x">repeat-x</Select.Option>
  1453. <Select.Option value="tokenish">tokenish</Select.Option>
  1454. </Select>
  1455. </Form.Item>
  1456. </>
  1457. )}
  1458. <Form.Item
  1459. name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
  1460. label="Session Placement"
  1461. >
  1462. <Select>
  1463. <Select.Option value="">Default (path)</Select.Option>
  1464. <Select.Option value="path">path</Select.Option>
  1465. <Select.Option value="header">header</Select.Option>
  1466. <Select.Option value="cookie">cookie</Select.Option>
  1467. <Select.Option value="query">query</Select.Option>
  1468. </Select>
  1469. </Form.Item>
  1470. {xhttpSessionPlacement && xhttpSessionPlacement !== 'path' && (
  1471. <Form.Item
  1472. name={['streamSettings', 'xhttpSettings', 'sessionKey']}
  1473. label="Session Key"
  1474. >
  1475. <Input placeholder="x_session" />
  1476. </Form.Item>
  1477. )}
  1478. <Form.Item
  1479. name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
  1480. label="Sequence Placement"
  1481. >
  1482. <Select>
  1483. <Select.Option value="">Default (path)</Select.Option>
  1484. <Select.Option value="path">path</Select.Option>
  1485. <Select.Option value="header">header</Select.Option>
  1486. <Select.Option value="cookie">cookie</Select.Option>
  1487. <Select.Option value="query">query</Select.Option>
  1488. </Select>
  1489. </Form.Item>
  1490. {xhttpSeqPlacement && xhttpSeqPlacement !== 'path' && (
  1491. <Form.Item
  1492. name={['streamSettings', 'xhttpSettings', 'seqKey']}
  1493. label="Sequence Key"
  1494. >
  1495. <Input placeholder="x_seq" />
  1496. </Form.Item>
  1497. )}
  1498. {xhttpMode === 'packet-up' && (
  1499. <>
  1500. <Form.Item
  1501. name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
  1502. label="Uplink Data Placement"
  1503. >
  1504. <Select>
  1505. <Select.Option value="">Default (body)</Select.Option>
  1506. <Select.Option value="body">body</Select.Option>
  1507. <Select.Option value="header">header</Select.Option>
  1508. <Select.Option value="cookie">cookie</Select.Option>
  1509. <Select.Option value="query">query</Select.Option>
  1510. </Select>
  1511. </Form.Item>
  1512. {xhttpUplinkPlacement && xhttpUplinkPlacement !== 'body' && (
  1513. <Form.Item
  1514. name={['streamSettings', 'xhttpSettings', 'uplinkDataKey']}
  1515. label="Uplink Data Key"
  1516. >
  1517. <Input placeholder="x_data" />
  1518. </Form.Item>
  1519. )}
  1520. </>
  1521. )}
  1522. <Form.Item
  1523. name={['streamSettings', 'xhttpSettings', 'noSSEHeader']}
  1524. label="No SSE Header"
  1525. valuePropName="checked"
  1526. >
  1527. <Switch />
  1528. </Form.Item>
  1529. </>
  1530. )}
  1531. {network === 'httpupgrade' && (
  1532. <>
  1533. <Form.Item
  1534. name={['streamSettings', 'httpupgradeSettings', 'acceptProxyProtocol']}
  1535. label="Proxy Protocol"
  1536. valuePropName="checked"
  1537. >
  1538. <Switch />
  1539. </Form.Item>
  1540. <Form.Item
  1541. name={['streamSettings', 'httpupgradeSettings', 'host']}
  1542. label={t('host')}
  1543. >
  1544. <Input />
  1545. </Form.Item>
  1546. <Form.Item
  1547. name={['streamSettings', 'httpupgradeSettings', 'path']}
  1548. label={t('path')}
  1549. >
  1550. <Input />
  1551. </Form.Item>
  1552. <Form.Item
  1553. label="Headers"
  1554. name={['streamSettings', 'httpupgradeSettings', 'headers']}
  1555. >
  1556. <HeaderMapEditor mode="v1" />
  1557. </Form.Item>
  1558. </>
  1559. )}
  1560. {network === 'kcp' && (
  1561. <>
  1562. <Form.Item name={['streamSettings', 'kcpSettings', 'mtu']} label="MTU">
  1563. <InputNumber min={576} max={1460} />
  1564. </Form.Item>
  1565. <Form.Item name={['streamSettings', 'kcpSettings', 'tti']} label="TTI (ms)">
  1566. <InputNumber min={10} max={100} />
  1567. </Form.Item>
  1568. <Form.Item name={['streamSettings', 'kcpSettings', 'upCap']} label="Uplink (MB/s)">
  1569. <InputNumber min={0} />
  1570. </Form.Item>
  1571. <Form.Item name={['streamSettings', 'kcpSettings', 'downCap']} label="Downlink (MB/s)">
  1572. <InputNumber min={0} />
  1573. </Form.Item>
  1574. <Form.Item
  1575. name={['streamSettings', 'kcpSettings', 'cwndMultiplier']}
  1576. label="CWND Multiplier"
  1577. >
  1578. <InputNumber min={1} />
  1579. </Form.Item>
  1580. <Form.Item
  1581. name={['streamSettings', 'kcpSettings', 'maxSendingWindow']}
  1582. label="Max Sending Window"
  1583. >
  1584. <InputNumber min={0} />
  1585. </Form.Item>
  1586. </>
  1587. )}
  1588. <Form.Item label="External Proxy">
  1589. <Switch checked={externalProxyOn} onChange={toggleExternalProxy} />
  1590. </Form.Item>
  1591. {externalProxyOn && (
  1592. <Form.List name={['streamSettings', 'externalProxy']}>
  1593. {(fields, { add, remove }) => (
  1594. <>
  1595. <Form.Item label=" " colon={false}>
  1596. <Button
  1597. size="small"
  1598. type="primary"
  1599. onClick={() => add({
  1600. forceTls: 'same',
  1601. dest: '',
  1602. port: 443,
  1603. remark: '',
  1604. sni: '',
  1605. fingerprint: '',
  1606. alpn: [],
  1607. })}
  1608. >
  1609. <PlusOutlined />
  1610. </Button>
  1611. </Form.Item>
  1612. <Form.Item wrapperCol={{ span: 24 }}>
  1613. {fields.map((field) => (
  1614. <div key={field.key} style={{ margin: '8px 0' }}>
  1615. <Space.Compact block>
  1616. <Form.Item name={[field.name, 'forceTls']} noStyle>
  1617. <Select style={{ width: '20%' }}>
  1618. <Select.Option value="same">{t('pages.inbounds.same')}</Select.Option>
  1619. <Select.Option value="none">{t('none')}</Select.Option>
  1620. <Select.Option value="tls">TLS</Select.Option>
  1621. </Select>
  1622. </Form.Item>
  1623. <Form.Item name={[field.name, 'dest']} noStyle>
  1624. <Input style={{ width: '30%' }} placeholder={t('host')} />
  1625. </Form.Item>
  1626. <Form.Item name={[field.name, 'port']} noStyle>
  1627. <InputNumber style={{ width: '15%' }} min={1} max={65535} />
  1628. </Form.Item>
  1629. <Form.Item name={[field.name, 'remark']} noStyle>
  1630. <Input style={{ width: '25%' }} placeholder={t('pages.inbounds.remark')} />
  1631. </Form.Item>
  1632. <InputAddon onClick={() => remove(field.name)}>
  1633. <MinusOutlined />
  1634. </InputAddon>
  1635. </Space.Compact>
  1636. <Form.Item
  1637. noStyle
  1638. shouldUpdate={(prev, curr) =>
  1639. prev.streamSettings?.externalProxy?.[field.name]?.forceTls
  1640. !== curr.streamSettings?.externalProxy?.[field.name]?.forceTls
  1641. }
  1642. >
  1643. {({ getFieldValue }) => {
  1644. const ft = getFieldValue([
  1645. 'streamSettings', 'externalProxy', field.name, 'forceTls',
  1646. ]);
  1647. if (ft !== 'tls') return null;
  1648. return (
  1649. <Space.Compact style={{ marginTop: 6 }} block>
  1650. <Form.Item name={[field.name, 'sni']} noStyle>
  1651. <Input style={{ width: '30%' }} placeholder="SNI (defaults to host)" />
  1652. </Form.Item>
  1653. <Form.Item name={[field.name, 'fingerprint']} noStyle>
  1654. <Select style={{ width: '30%' }} placeholder="Fingerprint">
  1655. <Select.Option value="">Default</Select.Option>
  1656. {Object.values(UTLS_FINGERPRINT).map((fp) => (
  1657. <Select.Option key={fp} value={fp}>{fp}</Select.Option>
  1658. ))}
  1659. </Select>
  1660. </Form.Item>
  1661. <Form.Item name={[field.name, 'alpn']} noStyle>
  1662. <Select mode="multiple" style={{ width: '40%' }} placeholder="ALPN">
  1663. {Object.values(ALPN_OPTION).map((a) => (
  1664. <Select.Option key={a} value={a}>{a}</Select.Option>
  1665. ))}
  1666. </Select>
  1667. </Form.Item>
  1668. </Space.Compact>
  1669. );
  1670. }}
  1671. </Form.Item>
  1672. </div>
  1673. ))}
  1674. </Form.Item>
  1675. </>
  1676. )}
  1677. </Form.List>
  1678. )}
  1679. <Form.Item label="Sockopt">
  1680. <Switch checked={sockoptOn} onChange={toggleSockopt} />
  1681. </Form.Item>
  1682. {sockoptOn && (
  1683. <>
  1684. <Form.Item name={['streamSettings', 'sockopt', 'mark']} label="Route Mark">
  1685. <InputNumber min={0} />
  1686. </Form.Item>
  1687. <Form.Item
  1688. name={['streamSettings', 'sockopt', 'tcpKeepAliveInterval']}
  1689. label="TCP Keep Alive Interval"
  1690. >
  1691. <InputNumber min={0} />
  1692. </Form.Item>
  1693. <Form.Item
  1694. name={['streamSettings', 'sockopt', 'tcpKeepAliveIdle']}
  1695. label="TCP Keep Alive Idle"
  1696. >
  1697. <InputNumber min={0} />
  1698. </Form.Item>
  1699. <Form.Item name={['streamSettings', 'sockopt', 'tcpMaxSeg']} label="TCP Max Seg">
  1700. <InputNumber min={0} />
  1701. </Form.Item>
  1702. <Form.Item
  1703. name={['streamSettings', 'sockopt', 'tcpUserTimeout']}
  1704. label="TCP User Timeout"
  1705. >
  1706. <InputNumber min={0} />
  1707. </Form.Item>
  1708. <Form.Item
  1709. name={['streamSettings', 'sockopt', 'tcpWindowClamp']}
  1710. label="TCP Window Clamp"
  1711. >
  1712. <InputNumber min={0} />
  1713. </Form.Item>
  1714. <Form.Item
  1715. name={['streamSettings', 'sockopt', 'acceptProxyProtocol']}
  1716. label="Proxy Protocol"
  1717. valuePropName="checked"
  1718. >
  1719. <Switch />
  1720. </Form.Item>
  1721. <Form.Item
  1722. name={['streamSettings', 'sockopt', 'tcpFastOpen']}
  1723. label="TCP Fast Open"
  1724. valuePropName="checked"
  1725. >
  1726. <Switch />
  1727. </Form.Item>
  1728. <Form.Item
  1729. name={['streamSettings', 'sockopt', 'tcpMptcp']}
  1730. label="Multipath TCP"
  1731. valuePropName="checked"
  1732. >
  1733. <Switch />
  1734. </Form.Item>
  1735. <Form.Item
  1736. name={['streamSettings', 'sockopt', 'penetrate']}
  1737. label="Penetrate"
  1738. valuePropName="checked"
  1739. >
  1740. <Switch />
  1741. </Form.Item>
  1742. <Form.Item
  1743. name={['streamSettings', 'sockopt', 'V6Only']}
  1744. label="V6 Only"
  1745. valuePropName="checked"
  1746. >
  1747. <Switch />
  1748. </Form.Item>
  1749. <Form.Item
  1750. name={['streamSettings', 'sockopt', 'domainStrategy']}
  1751. label="Domain Strategy"
  1752. >
  1753. <Select style={{ width: '50%' }}>
  1754. {Object.values(DOMAIN_STRATEGY_OPTION).map((d) => (
  1755. <Select.Option key={d} value={d}>{d}</Select.Option>
  1756. ))}
  1757. </Select>
  1758. </Form.Item>
  1759. <Form.Item
  1760. name={['streamSettings', 'sockopt', 'tcpcongestion']}
  1761. label="TCP Congestion"
  1762. >
  1763. <Select style={{ width: '50%' }}>
  1764. {Object.values(TCP_CONGESTION_OPTION).map((c) => (
  1765. <Select.Option key={c} value={c}>{c}</Select.Option>
  1766. ))}
  1767. </Select>
  1768. </Form.Item>
  1769. <Form.Item name={['streamSettings', 'sockopt', 'tproxy']} label="TProxy">
  1770. <Select style={{ width: '50%' }}>
  1771. <Select.Option value="off">Off</Select.Option>
  1772. <Select.Option value="redirect">Redirect</Select.Option>
  1773. <Select.Option value="tproxy">TProxy</Select.Option>
  1774. </Select>
  1775. </Form.Item>
  1776. <Form.Item name={['streamSettings', 'sockopt', 'dialerProxy']} label="Dialer Proxy">
  1777. <Input />
  1778. </Form.Item>
  1779. <Form.Item
  1780. name={['streamSettings', 'sockopt', 'interfaceName']}
  1781. label="Interface Name"
  1782. >
  1783. <Input />
  1784. </Form.Item>
  1785. <Form.Item
  1786. name={['streamSettings', 'sockopt', 'trustedXForwardedFor']}
  1787. label="Trusted X-Forwarded-For"
  1788. >
  1789. <Select mode="tags" style={{ width: '100%' }} tokenSeparators={[',']}>
  1790. <Select.Option value="CF-Connecting-IP">CF-Connecting-IP</Select.Option>
  1791. <Select.Option value="X-Real-IP">X-Real-IP</Select.Option>
  1792. <Select.Option value="True-Client-IP">True-Client-IP</Select.Option>
  1793. <Select.Option value="X-Client-IP">X-Client-IP</Select.Option>
  1794. </Select>
  1795. </Form.Item>
  1796. </>
  1797. )}
  1798. </>
  1799. );
  1800. const securityTab = (
  1801. <>
  1802. <Form.Item label={t('pages.inbounds.securityTab')}>
  1803. <Form.Item
  1804. noStyle
  1805. shouldUpdate={(prev, curr) =>
  1806. prev.streamSettings?.security !== curr.streamSettings?.security
  1807. }
  1808. >
  1809. {({ getFieldValue }) => {
  1810. const sec = getFieldValue(['streamSettings', 'security']) ?? 'none';
  1811. return (
  1812. <Select
  1813. value={sec}
  1814. disabled={!tlsAllowed}
  1815. onChange={onSecurityChange}
  1816. style={{ width: 180 }}
  1817. >
  1818. <Select.Option value="none">none</Select.Option>
  1819. <Select.Option value="tls">tls</Select.Option>
  1820. {realityAllowed && <Select.Option value="reality">reality</Select.Option>}
  1821. </Select>
  1822. );
  1823. }}
  1824. </Form.Item>
  1825. </Form.Item>
  1826. {security === 'tls' && (
  1827. <>
  1828. <Form.Item name={['streamSettings', 'tlsSettings', 'serverName']} label="SNI">
  1829. <Input placeholder="Server Name Indication" />
  1830. </Form.Item>
  1831. <Form.Item name={['streamSettings', 'tlsSettings', 'cipherSuites']} label="Cipher Suites">
  1832. <Select>
  1833. <Select.Option value="">Auto</Select.Option>
  1834. {Object.entries(TLS_CIPHER_OPTION).map(([k, v]) => (
  1835. <Select.Option key={v} value={v}>{k}</Select.Option>
  1836. ))}
  1837. </Select>
  1838. </Form.Item>
  1839. <Form.Item label="Min/Max Version">
  1840. <Space.Compact block>
  1841. <Form.Item name={['streamSettings', 'tlsSettings', 'minVersion']} noStyle>
  1842. <Select style={{ width: '50%' }}>
  1843. {Object.values(TLS_VERSION_OPTION).map((v) => (
  1844. <Select.Option key={v} value={v}>{v}</Select.Option>
  1845. ))}
  1846. </Select>
  1847. </Form.Item>
  1848. <Form.Item name={['streamSettings', 'tlsSettings', 'maxVersion']} noStyle>
  1849. <Select style={{ width: '50%' }}>
  1850. {Object.values(TLS_VERSION_OPTION).map((v) => (
  1851. <Select.Option key={v} value={v}>{v}</Select.Option>
  1852. ))}
  1853. </Select>
  1854. </Form.Item>
  1855. </Space.Compact>
  1856. </Form.Item>
  1857. <Form.Item
  1858. name={['streamSettings', 'tlsSettings', 'settings', 'fingerprint']}
  1859. label="uTLS"
  1860. >
  1861. <Select>
  1862. <Select.Option value="">None</Select.Option>
  1863. {Object.values(UTLS_FINGERPRINT).map((fp) => (
  1864. <Select.Option key={fp} value={fp}>{fp}</Select.Option>
  1865. ))}
  1866. </Select>
  1867. </Form.Item>
  1868. <Form.Item name={['streamSettings', 'tlsSettings', 'alpn']} label="ALPN">
  1869. <Select mode="multiple" tokenSeparators={[',']} style={{ width: '100%' }}>
  1870. {Object.values(ALPN_OPTION).map((a) => (
  1871. <Select.Option key={a} value={a}>{a}</Select.Option>
  1872. ))}
  1873. </Select>
  1874. </Form.Item>
  1875. <Form.Item
  1876. name={['streamSettings', 'tlsSettings', 'rejectUnknownSni']}
  1877. label="Reject Unknown SNI"
  1878. valuePropName="checked"
  1879. >
  1880. <Switch />
  1881. </Form.Item>
  1882. <Form.Item
  1883. name={['streamSettings', 'tlsSettings', 'disableSystemRoot']}
  1884. label="Disable System Root"
  1885. valuePropName="checked"
  1886. >
  1887. <Switch />
  1888. </Form.Item>
  1889. <Form.Item
  1890. name={['streamSettings', 'tlsSettings', 'enableSessionResumption']}
  1891. label="Session Resumption"
  1892. valuePropName="checked"
  1893. >
  1894. <Switch />
  1895. </Form.Item>
  1896. <Form.List name={['streamSettings', 'tlsSettings', 'certificates']}>
  1897. {(certFields, { add, remove }) => (
  1898. <>
  1899. <Form.Item label={t('certificate')}>
  1900. <Button
  1901. type="primary"
  1902. size="small"
  1903. onClick={() => add({
  1904. useFile: true,
  1905. certificateFile: '',
  1906. keyFile: '',
  1907. certificate: [],
  1908. key: [],
  1909. oneTimeLoading: false,
  1910. usage: 'encipherment',
  1911. buildChain: false,
  1912. })}
  1913. >
  1914. <PlusOutlined />
  1915. </Button>
  1916. </Form.Item>
  1917. {certFields.map((certField, idx) => (
  1918. <div key={certField.key}>
  1919. <Form.Item
  1920. name={[certField.name, 'useFile']}
  1921. label={`${t('certificate')} ${idx + 1}`}
  1922. >
  1923. <Radio.Group buttonStyle="solid">
  1924. <Radio.Button value={true}>
  1925. {t('pages.inbounds.certificatePath')}
  1926. </Radio.Button>
  1927. <Radio.Button value={false}>
  1928. {t('pages.inbounds.certificateContent')}
  1929. </Radio.Button>
  1930. </Radio.Group>
  1931. </Form.Item>
  1932. {certFields.length > 1 && (
  1933. <Form.Item label=" ">
  1934. <Button
  1935. size="small"
  1936. danger
  1937. onClick={() => remove(certField.name)}
  1938. >
  1939. <MinusOutlined /> Remove
  1940. </Button>
  1941. </Form.Item>
  1942. )}
  1943. <Form.Item
  1944. noStyle
  1945. shouldUpdate={(prev, curr) =>
  1946. prev.streamSettings?.tlsSettings?.certificates?.[certField.name]?.useFile
  1947. !== curr.streamSettings?.tlsSettings?.certificates?.[certField.name]?.useFile
  1948. }
  1949. >
  1950. {({ getFieldValue }) => {
  1951. const useFile = getFieldValue([
  1952. 'streamSettings', 'tlsSettings', 'certificates',
  1953. certField.name, 'useFile',
  1954. ]);
  1955. return useFile ? (
  1956. <>
  1957. <Form.Item
  1958. name={[certField.name, 'certificateFile']}
  1959. label={t('pages.inbounds.publicKey')}
  1960. >
  1961. <Input />
  1962. </Form.Item>
  1963. <Form.Item
  1964. name={[certField.name, 'keyFile']}
  1965. label={t('pages.inbounds.privatekey')}
  1966. >
  1967. <Input />
  1968. </Form.Item>
  1969. </>
  1970. ) : (
  1971. <>
  1972. <Form.Item
  1973. name={[certField.name, 'certificate']}
  1974. label={t('pages.inbounds.publicKey')}
  1975. normalize={(v) => typeof v === 'string'
  1976. ? v.split('\n')
  1977. : v}
  1978. getValueProps={(v) => ({
  1979. value: Array.isArray(v) ? v.join('\n') : v,
  1980. })}
  1981. >
  1982. <TextArea autoSize={{ minRows: 3, maxRows: 8 }} />
  1983. </Form.Item>
  1984. <Form.Item
  1985. name={[certField.name, 'key']}
  1986. label={t('pages.inbounds.privatekey')}
  1987. normalize={(v) => typeof v === 'string'
  1988. ? v.split('\n')
  1989. : v}
  1990. getValueProps={(v) => ({
  1991. value: Array.isArray(v) ? v.join('\n') : v,
  1992. })}
  1993. >
  1994. <TextArea autoSize={{ minRows: 3, maxRows: 8 }} />
  1995. </Form.Item>
  1996. </>
  1997. );
  1998. }}
  1999. </Form.Item>
  2000. <Form.Item
  2001. name={[certField.name, 'oneTimeLoading']}
  2002. label="One Time Loading"
  2003. valuePropName="checked"
  2004. >
  2005. <Switch />
  2006. </Form.Item>
  2007. <Form.Item
  2008. name={[certField.name, 'usage']}
  2009. label="Usage Option"
  2010. >
  2011. <Select style={{ width: '50%' }}>
  2012. {Object.values(USAGE_OPTION).map((u) => (
  2013. <Select.Option key={u} value={u}>{u}</Select.Option>
  2014. ))}
  2015. </Select>
  2016. </Form.Item>
  2017. <Form.Item
  2018. noStyle
  2019. shouldUpdate={(prev, curr) =>
  2020. prev.streamSettings?.tlsSettings?.certificates?.[certField.name]?.usage
  2021. !== curr.streamSettings?.tlsSettings?.certificates?.[certField.name]?.usage
  2022. }
  2023. >
  2024. {({ getFieldValue }) => {
  2025. const usage = getFieldValue([
  2026. 'streamSettings', 'tlsSettings', 'certificates',
  2027. certField.name, 'usage',
  2028. ]);
  2029. if (usage !== 'issue') return null;
  2030. return (
  2031. <Form.Item
  2032. name={[certField.name, 'buildChain']}
  2033. label="Build Chain"
  2034. valuePropName="checked"
  2035. >
  2036. <Switch />
  2037. </Form.Item>
  2038. );
  2039. }}
  2040. </Form.Item>
  2041. </div>
  2042. ))}
  2043. </>
  2044. )}
  2045. </Form.List>
  2046. <Form.Item name={['streamSettings', 'tlsSettings', 'echServerKeys']} label="ECH key">
  2047. <Input />
  2048. </Form.Item>
  2049. <Form.Item
  2050. name={['streamSettings', 'tlsSettings', 'settings', 'echConfigList']}
  2051. label="ECH config"
  2052. >
  2053. <Input />
  2054. </Form.Item>
  2055. <Form.Item label=" ">
  2056. <Space>
  2057. <Button type="primary" loading={saving} onClick={getNewEchCert}>
  2058. Get New ECH Cert
  2059. </Button>
  2060. <Button danger onClick={clearEchCert}>Clear</Button>
  2061. </Space>
  2062. </Form.Item>
  2063. </>
  2064. )}
  2065. {security === 'reality' && (
  2066. <>
  2067. <Form.Item
  2068. name={['streamSettings', 'realitySettings', 'show']}
  2069. label="Show"
  2070. valuePropName="checked"
  2071. >
  2072. <Switch />
  2073. </Form.Item>
  2074. <Form.Item name={['streamSettings', 'realitySettings', 'xver']} label="Xver">
  2075. <InputNumber min={0} />
  2076. </Form.Item>
  2077. <Form.Item
  2078. name={['streamSettings', 'realitySettings', 'settings', 'fingerprint']}
  2079. label="uTLS"
  2080. >
  2081. <Select>
  2082. {Object.values(UTLS_FINGERPRINT).map((fp) => (
  2083. <Select.Option key={fp} value={fp}>{fp}</Select.Option>
  2084. ))}
  2085. </Select>
  2086. </Form.Item>
  2087. <Form.Item
  2088. name={['streamSettings', 'realitySettings', 'target']}
  2089. label={
  2090. <>
  2091. Target{' '}
  2092. <SyncOutlined className="random-icon" onClick={randomizeRealityTarget} />
  2093. </>
  2094. }
  2095. >
  2096. <Input />
  2097. </Form.Item>
  2098. <Form.Item
  2099. name={['streamSettings', 'realitySettings', 'serverNames']}
  2100. label={
  2101. <>
  2102. SNI{' '}
  2103. <SyncOutlined className="random-icon" onClick={randomizeRealityTarget} />
  2104. </>
  2105. }
  2106. >
  2107. <Select mode="tags" tokenSeparators={[',']} style={{ width: '100%' }} />
  2108. </Form.Item>
  2109. <Form.Item
  2110. name={['streamSettings', 'realitySettings', 'maxTimediff']}
  2111. label="Max Time Diff (ms)"
  2112. >
  2113. <InputNumber min={0} />
  2114. </Form.Item>
  2115. <Form.Item
  2116. name={['streamSettings', 'realitySettings', 'minClientVer']}
  2117. label="Min Client Ver"
  2118. >
  2119. <Input placeholder="25.9.11" />
  2120. </Form.Item>
  2121. <Form.Item
  2122. name={['streamSettings', 'realitySettings', 'maxClientVer']}
  2123. label="Max Client Ver"
  2124. >
  2125. <Input placeholder="25.9.11" />
  2126. </Form.Item>
  2127. <Form.Item
  2128. name={['streamSettings', 'realitySettings', 'shortIds']}
  2129. label={
  2130. <>
  2131. Short IDs{' '}
  2132. <SyncOutlined className="random-icon" onClick={randomizeShortIds} />
  2133. </>
  2134. }
  2135. >
  2136. <Select mode="tags" tokenSeparators={[',']} style={{ width: '100%' }} />
  2137. </Form.Item>
  2138. <Form.Item
  2139. name={['streamSettings', 'realitySettings', 'settings', 'spiderX']}
  2140. label="SpiderX"
  2141. >
  2142. <Input />
  2143. </Form.Item>
  2144. <Form.Item
  2145. name={['streamSettings', 'realitySettings', 'settings', 'publicKey']}
  2146. label={t('pages.inbounds.publicKey')}
  2147. >
  2148. <Input.TextArea autoSize={{ minRows: 1, maxRows: 4 }} />
  2149. </Form.Item>
  2150. <Form.Item
  2151. name={['streamSettings', 'realitySettings', 'privateKey']}
  2152. label={t('pages.inbounds.privatekey')}
  2153. >
  2154. <Input.TextArea autoSize={{ minRows: 1, maxRows: 4 }} />
  2155. </Form.Item>
  2156. <Form.Item label=" ">
  2157. <Space>
  2158. <Button type="primary" loading={saving} onClick={genRealityKeypair}>
  2159. Get New Cert
  2160. </Button>
  2161. <Button danger onClick={clearRealityKeypair}>Clear</Button>
  2162. </Space>
  2163. </Form.Item>
  2164. <Form.Item
  2165. name={['streamSettings', 'realitySettings', 'mldsa65Seed']}
  2166. label="mldsa65 Seed"
  2167. >
  2168. <Input.TextArea autoSize={{ minRows: 2, maxRows: 6 }} />
  2169. </Form.Item>
  2170. <Form.Item
  2171. name={['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify']}
  2172. label="mldsa65 Verify"
  2173. >
  2174. <Input.TextArea autoSize={{ minRows: 2, maxRows: 6 }} />
  2175. </Form.Item>
  2176. <Form.Item label=" ">
  2177. <Space>
  2178. <Button type="primary" loading={saving} onClick={genMldsa65}>
  2179. Get New Seed
  2180. </Button>
  2181. <Button danger onClick={clearMldsa65}>Clear</Button>
  2182. </Space>
  2183. </Form.Item>
  2184. </>
  2185. )}
  2186. </>
  2187. );
  2188. const advancedTab = (
  2189. <Tabs
  2190. items={[
  2191. {
  2192. key: 'settings',
  2193. label: t('pages.inbounds.advanced.settings'),
  2194. children: (
  2195. <AdvancedSliceEditor
  2196. form={form}
  2197. path="settings"
  2198. minHeight="320px"
  2199. maxHeight="540px"
  2200. />
  2201. ),
  2202. },
  2203. ...(streamEnabled
  2204. ? [{
  2205. key: 'stream',
  2206. label: t('pages.inbounds.advanced.stream'),
  2207. children: (
  2208. <AdvancedSliceEditor
  2209. form={form}
  2210. path="streamSettings"
  2211. minHeight="320px"
  2212. maxHeight="540px"
  2213. />
  2214. ),
  2215. }]
  2216. : []),
  2217. {
  2218. key: 'sniffing',
  2219. label: t('pages.inbounds.advanced.sniffing'),
  2220. children: (
  2221. <AdvancedSliceEditor
  2222. form={form}
  2223. path="sniffing"
  2224. minHeight="240px"
  2225. maxHeight="420px"
  2226. />
  2227. ),
  2228. },
  2229. ]}
  2230. />
  2231. );
  2232. const sniffingTab = (
  2233. <>
  2234. <Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
  2235. <Switch />
  2236. </Form.Item>
  2237. {sniffingEnabled && (
  2238. <>
  2239. <Form.Item name={['sniffing', 'destOverride']} wrapperCol={{ span: 24 }}>
  2240. <Checkbox.Group>
  2241. {Object.entries(SNIFFING_OPTION).map(([key, value]) => (
  2242. <Checkbox key={key} value={value}>{key}</Checkbox>
  2243. ))}
  2244. </Checkbox.Group>
  2245. </Form.Item>
  2246. <Form.Item
  2247. name={['sniffing', 'metadataOnly']}
  2248. label={t('pages.inbounds.sniffingMetadataOnly')}
  2249. valuePropName="checked"
  2250. >
  2251. <Switch />
  2252. </Form.Item>
  2253. <Form.Item
  2254. name={['sniffing', 'routeOnly']}
  2255. label={t('pages.inbounds.sniffingRouteOnly')}
  2256. valuePropName="checked"
  2257. >
  2258. <Switch />
  2259. </Form.Item>
  2260. <Form.Item
  2261. name={['sniffing', 'ipsExcluded']}
  2262. label={t('pages.inbounds.sniffingIpsExcluded')}
  2263. >
  2264. <Select
  2265. mode="tags"
  2266. tokenSeparators={[',']}
  2267. placeholder="IP/CIDR/geoip:*/ext:*"
  2268. style={{ width: '100%' }}
  2269. />
  2270. </Form.Item>
  2271. <Form.Item
  2272. name={['sniffing', 'domainsExcluded']}
  2273. label={t('pages.inbounds.sniffingDomainsExcluded')}
  2274. >
  2275. <Select
  2276. mode="tags"
  2277. tokenSeparators={[',']}
  2278. placeholder="domain:*/ext:*"
  2279. style={{ width: '100%' }}
  2280. />
  2281. </Form.Item>
  2282. </>
  2283. )}
  2284. </>
  2285. );
  2286. return (
  2287. <>
  2288. {messageContextHolder}
  2289. <Modal
  2290. open={open}
  2291. title={title}
  2292. okText={okText}
  2293. cancelText={t('close')}
  2294. confirmLoading={saving}
  2295. mask={{ closable: false }}
  2296. width={780}
  2297. onOk={submit}
  2298. onCancel={onClose}
  2299. destroyOnHidden
  2300. >
  2301. <Form
  2302. form={form}
  2303. colon={false}
  2304. labelCol={{ sm: { span: 8 } }}
  2305. wrapperCol={{ sm: { span: 14 } }}
  2306. onValuesChange={onValuesChange}
  2307. >
  2308. <Tabs items={[
  2309. { key: 'basic', label: t('pages.xray.basicTemplate'), children: basicTab },
  2310. ...(([
  2311. Protocols.VLESS,
  2312. Protocols.SHADOWSOCKS,
  2313. Protocols.HTTP,
  2314. Protocols.MIXED,
  2315. Protocols.TUNNEL,
  2316. Protocols.TUN,
  2317. Protocols.WIREGUARD,
  2318. ] as string[]).includes(protocol) || isFallbackHost
  2319. ? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }]
  2320. : []),
  2321. ...(streamEnabled
  2322. ? [
  2323. { key: 'stream', label: t('pages.inbounds.streamTab'), children: streamTab },
  2324. { key: 'security', label: t('pages.inbounds.securityTab'), children: securityTab },
  2325. ]
  2326. : []),
  2327. { key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab },
  2328. { key: 'advanced', label: t('pages.xray.advancedTemplate'), children: advancedTab },
  2329. ]} />
  2330. </Form>
  2331. </Modal>
  2332. </>
  2333. );
  2334. }