| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471 |
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
- import { useTranslation } from 'react-i18next';
- import {
- Button,
- Form,
- Input,
- InputNumber,
- message,
- Modal,
- Radio,
- Select,
- Space,
- Switch,
- Tabs,
- Checkbox,
- } from 'antd';
- import { SyncOutlined, PlusOutlined, MinusOutlined, DeleteOutlined } from '@ant-design/icons';
- import { Wireguard } from '@/utils';
- import InputAddon from '@/components/InputAddon';
- import {
- Outbound,
- Protocols,
- SSMethods,
- TLS_FLOW_CONTROL,
- UTLS_FINGERPRINT,
- ALPN_OPTION,
- SNIFFING_OPTION,
- USERS_SECURITY,
- OutboundDomainStrategies,
- WireguardDomainStrategy,
- Address_Port_Strategy,
- MODE_OPTION,
- DNSRuleActions,
- } from '@/models/outbound';
- import FinalMaskForm from '@/components/FinalMaskForm';
- import JsonEditor from '@/components/JsonEditor';
- import './OutboundFormModal.css';
- interface OutboundFormModalProps {
- open: boolean;
- outbound: Record<string, unknown> | null;
- existingTags: string[];
- onClose: () => void;
- onConfirm: (outbound: Record<string, unknown>) => void;
- }
- const PROTOCOL_OPTIONS = Object.values(Protocols) as string[];
- const SECURITY_OPTIONS = Object.values(USERS_SECURITY) as string[];
- const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL) as string[];
- const UTLS_OPTIONS = Object.values(UTLS_FINGERPRINT) as string[];
- const ALPN_OPTIONS = Object.values(ALPN_OPTION) as string[];
- const NETWORKS = ['tcp', 'kcp', 'ws', 'grpc', 'httpupgrade', 'xhttp'];
- const NETWORK_LABELS: Record<string, string> = {
- tcp: 'TCP (RAW)',
- kcp: 'mKCP',
- ws: 'WebSocket',
- grpc: 'gRPC',
- httpupgrade: 'HTTPUpgrade',
- xhttp: 'XHTTP',
- };
- export default function OutboundFormModal({
- open,
- outbound: outboundProp,
- existingTags,
- onClose,
- onConfirm,
- }: OutboundFormModalProps) {
- const { t } = useTranslation();
- const [messageApi, messageContextHolder] = message.useMessage();
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const outboundRef = useRef<any>(null);
- const [, setTick] = useState(0);
- const [activeKey, setActiveKey] = useState('1');
- const [linkInput, setLinkInput] = useState('');
- const [advancedJson, setAdvancedJson] = useState('');
- const revertingTabRef = useRef(false);
- const isEdit = outboundProp != null;
- const refresh = useCallback(() => setTick((n) => n + 1), []);
- const primeAdvancedJson = useCallback(() => {
- const ob = outboundRef.current;
- if (!ob) {
- setAdvancedJson('');
- return;
- }
- try {
- setAdvancedJson(JSON.stringify(ob.toJson(), null, 2));
- } catch {
- setAdvancedJson('');
- }
- }, []);
- useEffect(() => {
- if (!open) return;
- outboundRef.current = outboundProp
- ? Outbound.fromJson(outboundProp)
- : new Outbound();
- setActiveKey('1');
- setLinkInput('');
- primeAdvancedJson();
- refresh();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [open, outboundProp]);
- function applyAdvancedJsonToForm(): boolean {
- const raw = advancedJson.trim();
- if (!raw) return true;
- const ob = outboundRef.current;
- let currentJson = '';
- try {
- currentJson = JSON.stringify(ob?.toJson() ?? {}, null, 2);
- } catch {
- /* ignore */
- }
- if (raw === currentJson.trim()) return true;
- let parsed;
- try {
- parsed = JSON.parse(raw);
- } catch (e) {
- messageApi.error(`JSON: ${(e as Error).message}`);
- return false;
- }
- try {
- const fallbackTag = ob?.tag;
- const next = Outbound.fromJson(parsed);
- if (!next.tag && fallbackTag) next.tag = fallbackTag;
- outboundRef.current = next;
- refresh();
- return true;
- } catch (e) {
- messageApi.error(`JSON: ${(e as Error).message}`);
- return false;
- }
- }
- function onTabChange(key: string) {
- if (document.activeElement instanceof HTMLElement) {
- document.activeElement.blur();
- }
- if (revertingTabRef.current) {
- revertingTabRef.current = false;
- setActiveKey(key);
- return;
- }
- const prev = activeKey;
- if (key === '2') {
- primeAdvancedJson();
- setActiveKey(key);
- } else if (key === '1' && prev === '2') {
- if (!applyAdvancedJsonToForm()) {
- revertingTabRef.current = true;
- setActiveKey('2');
- } else {
- setActiveKey(key);
- }
- } else {
- setActiveKey(key);
- }
- }
- const ob = outboundRef.current;
- const proto = ob?.protocol;
- const isVMess = proto === Protocols.VMess;
- const isVLESS = proto === Protocols.VLESS;
- const isVMessOrVLess = isVMess || isVLESS;
- const isTrojan = proto === Protocols.Trojan;
- const isShadowsocks = proto === Protocols.Shadowsocks;
- const isFreedom = proto === Protocols.Freedom;
- const isBlackhole = proto === Protocols.Blackhole;
- const isDNS = proto === Protocols.DNS;
- const isWireguard = proto === Protocols.Wireguard;
- const isHysteria = proto === Protocols.Hysteria;
- const isLoopback = proto === Protocols.Loopback;
- function onProtocolChange(next: string) {
- if (!ob) return;
- ob.protocol = next;
- refresh();
- }
- function streamNetworkChange(next: string) {
- if (!ob?.stream) return;
- ob.stream.network = next;
- if (!ob.canEnableTls()) ob.stream.security = 'none';
- refresh();
- }
- function regenerateWgKeys() {
- if (!ob?.settings) return;
- const pair = Wireguard.generateKeypair();
- ob.settings.secretKey = pair.privateKey;
- ob.settings.pubKey = pair.publicKey;
- refresh();
- }
- const duplicateTag = useMemo(() => {
- if (!ob?.tag) return false;
- const myTag = ob.tag.trim();
- if (!myTag) return false;
- if (isEdit && (outboundProp?.tag as string | undefined) === myTag) return false;
- return (existingTags || []).includes(myTag);
- }, [ob?.tag, existingTags, isEdit, outboundProp]);
- const tagEmpty = !ob?.tag?.trim();
- const tagValidateStatus: 'error' | 'warning' | 'success' = tagEmpty
- ? 'error'
- : duplicateTag
- ? 'warning'
- : 'success';
- const tagHelp = tagEmpty
- ? 'Tag is required'
- : duplicateTag
- ? 'Tag already used by another outbound'
- : '';
- function onOk() {
- if (!ob) return;
- if (activeKey === '2' && !applyAdvancedJsonToForm()) return;
- if (!ob.tag?.trim()) {
- messageApi.error('Tag is required');
- return;
- }
- if (duplicateTag) {
- messageApi.error('Tag already used by another outbound');
- return;
- }
- onConfirm(ob.toJson());
- }
- function convertLink() {
- const link = linkInput.trim();
- if (!link) return;
- try {
- const next = Outbound.fromLink(link);
- if (!next) {
- messageApi.error('Wrong Link!');
- return;
- }
- outboundRef.current = next;
- primeAdvancedJson();
- setLinkInput('');
- messageApi.success('Link imported successfully...');
- setActiveKey('1');
- refresh();
- } catch (e) {
- messageApi.error(`Link parse: ${(e as Error).message}`);
- }
- }
- const title = isEdit
- ? `${t('edit')} ${t('pages.xray.Outbounds')}`
- : `+ ${t('pages.xray.Outbounds')}`;
- const okText = isEdit ? t('pages.clients.submitEdit') : t('create');
- if (!ob) {
- return (
- <>
- {messageContextHolder}
- <Modal open={open} title={title} footer={null} onCancel={onClose} />
- </>
- );
- }
- return (
- <>
- {messageContextHolder}
- <Modal
- open={open}
- title={title}
- okText={okText}
- cancelText={t('close')}
- mask={{ closable: false }}
- width={780}
- onOk={onOk}
- onCancel={onClose}
- >
- <Tabs
- activeKey={activeKey}
- onChange={onTabChange}
- items={[
- {
- key: '1',
- label: t('pages.xray.basicTemplate'),
- children: (
- <>
- <Form colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 14 } }}>
- <Form.Item label={t('protocol')}>
- <Select
- value={proto}
- onChange={onProtocolChange}
- options={PROTOCOL_OPTIONS.map((p) => ({ value: p, label: p }))}
- />
- </Form.Item>
- <Form.Item label="Tag" validateStatus={tagValidateStatus} help={tagHelp} hasFeedback>
- <Input
- value={ob.tag}
- placeholder="unique-tag"
- onChange={(e) => {
- ob.tag = e.target.value;
- refresh();
- }}
- />
- </Form.Item>
- <Form.Item label="Send through">
- <Input
- value={ob.sendThrough || ''}
- placeholder="local IP"
- onChange={(e) => {
- ob.sendThrough = e.target.value;
- refresh();
- }}
- />
- </Form.Item>
- {isFreedom && <FreedomFields ob={ob} refresh={refresh} />}
- {isBlackhole && (
- <Form.Item label="Response Type">
- <Select
- value={ob.settings.type || ''}
- onChange={(v) => { ob.settings.type = v; refresh(); }}
- options={[
- { value: '', label: '(empty)' },
- { value: 'none', label: 'none' },
- { value: 'http', label: 'http' },
- ]}
- />
- </Form.Item>
- )}
- {isLoopback && (
- <Form.Item label="Inbound tag">
- <Input
- value={ob.settings.inboundTag || ''}
- placeholder="inbound tag using in routing rules"
- onChange={(e) => { ob.settings.inboundTag = e.target.value; refresh(); }}
- />
- </Form.Item>
- )}
- {isDNS && <DnsFields ob={ob} refresh={refresh} t={t} />}
- {isWireguard && <WireguardFields ob={ob} refresh={refresh} regenerate={regenerateWgKeys} t={t} />}
- {ob.hasAddressPort() && (
- <>
- <Form.Item label={t('pages.inbounds.address')}>
- <Input
- value={ob.settings.address || ''}
- onChange={(e) => { ob.settings.address = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label={t('pages.inbounds.port')}>
- <InputNumber
- value={ob.settings.port || 0}
- min={1}
- max={65535}
- onChange={(v) => { ob.settings.port = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- {isVMessOrVLess && (
- <VMessVLessFields ob={ob} refresh={refresh} isVMess={isVMess} isVLESS={isVLESS} t={t} />
- )}
- {(isTrojan || isShadowsocks) && (
- <Form.Item label={t('password')}>
- <Input
- value={ob.settings.password || ''}
- onChange={(e) => { ob.settings.password = e.target.value; refresh(); }}
- />
- </Form.Item>
- )}
- {isShadowsocks && (
- <>
- <Form.Item label={t('encryption')}>
- <Select
- value={ob.settings.method}
- onChange={(v) => { ob.settings.method = v; refresh(); }}
- options={Object.entries(SSMethods).map(([k, v]) => ({ value: v as string, label: k }))}
- />
- </Form.Item>
- <Form.Item label="UDP over TCP">
- <Switch checked={!!ob.settings.uot} onChange={(v) => { ob.settings.uot = v; refresh(); }} />
- </Form.Item>
- <Form.Item label="UoT version">
- <InputNumber
- value={ob.settings.UoTVersion ?? 1}
- min={1}
- max={2}
- onChange={(v) => { ob.settings.UoTVersion = Number(v) || 1; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- {ob.hasUsername() && (
- <>
- <Form.Item label={t('username')}>
- <Input
- value={ob.settings.user || ''}
- onChange={(e) => { ob.settings.user = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label={t('password')}>
- <Input
- value={ob.settings.pass || ''}
- onChange={(e) => { ob.settings.pass = e.target.value; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- {isHysteria && (
- <Form.Item label="Version">
- <InputNumber value={ob.settings.version || 2} min={2} max={2} disabled />
- </Form.Item>
- )}
- {ob.canEnableStream() && (
- <StreamFields ob={ob} refresh={refresh} streamNetworkChange={streamNetworkChange} isHysteria={isHysteria} t={t} />
- )}
- {ob.canEnableTls() && <TlsFields ob={ob} refresh={refresh} t={t} />}
- {ob.stream && <SockoptFields ob={ob} refresh={refresh} />}
- {ob.canEnableMux() && <MuxFields ob={ob} refresh={refresh} t={t} />}
- </Form>
- {ob.stream && ob.canEnableStream() && (
- <FinalMaskForm stream={ob.stream} protocol={proto} onChange={refresh} />
- )}
- </>
- ),
- },
- {
- key: '2',
- label: 'JSON',
- children: (
- <Space orientation="vertical" size={10} style={{ width: '100%', marginTop: 10 }}>
- <Input.Search
- value={linkInput}
- placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"
- enterButton="Convert"
- onChange={(e) => setLinkInput(e.target.value)}
- onSearch={convertLink}
- />
- <JsonEditor
- value={advancedJson}
- onChange={setAdvancedJson}
- minHeight="360px"
- maxHeight="600px"
- />
- </Space>
- ),
- },
- ]}
- />
- </Modal>
- </>
- );
- }
- type OB = Outbound;
- interface FieldProps {
- ob: OB;
- refresh: () => void;
- }
- interface TFieldProps extends FieldProps {
- t: (k: string) => string;
- }
- function FreedomFields({ ob, refresh }: FieldProps) {
- const fragment = (ob.settings.fragment || {}) as Record<string, string>;
- const noises = (ob.settings.noises || []) as Array<{ type: string; packet: string; delay: string; applyTo: string }>;
- const finalRules = (ob.settings.finalRules || []) as Array<{ action: string; network?: string; port?: string; ip?: string[]; blockDelay?: string }>;
- return (
- <>
- <Form.Item label="Strategy">
- <Select
- value={ob.settings.domainStrategy}
- onChange={(v) => { ob.settings.domainStrategy = v; refresh(); }}
- options={(OutboundDomainStrategies as string[]).map((s) => ({ value: s, label: s }))}
- />
- </Form.Item>
- <Form.Item label="Redirect">
- <Input
- value={ob.settings.redirect || ''}
- onChange={(e) => { ob.settings.redirect = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="Fragment">
- <Switch
- checked={!!ob.settings.fragment && Object.keys(ob.settings.fragment).length > 0}
- onChange={(checked) => {
- ob.settings.fragment = checked
- ? { packets: 'tlshello', length: '100-200', interval: '10-20', maxSplit: '300-400' }
- : {};
- refresh();
- }}
- />
- </Form.Item>
- {ob.settings.fragment && Object.keys(ob.settings.fragment).length > 0 && (
- <>
- <Form.Item label="Packets">
- <Select
- value={fragment.packets}
- onChange={(v) => { (ob.settings.fragment as Record<string, string>).packets = v; refresh(); }}
- options={[
- { value: '1-3', label: '1-3' },
- { value: 'tlshello', label: 'tlshello' },
- ]}
- />
- </Form.Item>
- {(['length', 'interval', 'maxSplit'] as const).map((field) => (
- <Form.Item key={field} label={field === 'maxSplit' ? 'Max Split' : field[0].toUpperCase() + field.slice(1)}>
- <Input
- value={fragment[field] || ''}
- onChange={(e) => { (ob.settings.fragment as Record<string, string>)[field] = e.target.value; refresh(); }}
- />
- </Form.Item>
- ))}
- </>
- )}
- <Form.Item label="Noises">
- <Switch
- checked={noises.length > 0}
- onChange={(checked) => {
- ob.settings.noises = checked ? [new Outbound.FreedomSettings.Noise()] : [];
- refresh();
- }}
- />
- {noises.length > 0 && (
- <Button
- size="small"
- type="primary"
- className="ml-8"
- icon={<PlusOutlined />}
- onClick={() => { (ob.settings.noises as unknown[]).push(new Outbound.FreedomSettings.Noise()); refresh(); }}
- />
- )}
- </Form.Item>
- {noises.map((noise, index) => (
- <div key={index}>
- <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
- <div className="item-heading">
- <span>Noise {index + 1}</span>
- {noises.length > 1 && (
- <DeleteOutlined
- className="danger-icon"
- onClick={() => { (ob.settings.noises as unknown[]).splice(index, 1); refresh(); }}
- />
- )}
- </div>
- </Form.Item>
- <Form.Item label="Type">
- <Select
- value={noise.type}
- onChange={(v) => { noise.type = v; refresh(); }}
- options={['rand', 'base64', 'str', 'hex'].map((x) => ({ value: x, label: x }))}
- />
- </Form.Item>
- <Form.Item label="Packet">
- <Input value={noise.packet} onChange={(e) => { noise.packet = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Delay (ms)">
- <Input value={noise.delay} onChange={(e) => { noise.delay = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Apply to">
- <Select
- value={noise.applyTo}
- onChange={(v) => { noise.applyTo = v; refresh(); }}
- options={['ip', 'ipv4', 'ipv6'].map((x) => ({ value: x, label: x }))}
- />
- </Form.Item>
- </div>
- ))}
- <Form.Item label="Final Rules">
- <Button
- size="small"
- type="primary"
- icon={<PlusOutlined />}
- onClick={() => { ob.settings.addFinalRule('allow'); refresh(); }}
- />
- <span className="ml-8" style={{ opacity: 0.6 }}>
- Override Xray's default private-IP block (needed for LAN access through proxy)
- </span>
- </Form.Item>
- {finalRules.map((rule, index) => (
- <div key={`fr-${index}`}>
- <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
- <div className="item-heading">
- <span>Rule {index + 1}</span>
- <DeleteOutlined
- className="danger-icon"
- onClick={() => { ob.settings.delFinalRule(index); refresh(); }}
- />
- </div>
- </Form.Item>
- <Form.Item label="Action">
- <Select
- value={rule.action}
- onChange={(v) => { rule.action = v; refresh(); }}
- options={['allow', 'block'].map((x) => ({ value: x, label: x }))}
- />
- </Form.Item>
- <Form.Item label="Network">
- <Select
- value={rule.network}
- allowClear
- placeholder="(any)"
- onChange={(v) => { rule.network = v; refresh(); }}
- options={['tcp', 'udp', 'tcp,udp'].map((x) => ({ value: x, label: x }))}
- />
- </Form.Item>
- <Form.Item label="Port">
- <Input
- value={rule.port}
- placeholder="e.g. 80,443 or 1000-2000"
- onChange={(e) => { rule.port = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="IP / CIDR / geoip">
- <Select
- mode="tags"
- value={rule.ip || []}
- tokenSeparators={[',', ' ']}
- placeholder="e.g. 10.0.0.0/8, geoip:private, ext:cn.dat:cn"
- onChange={(v) => { rule.ip = v as string[]; refresh(); }}
- />
- </Form.Item>
- {rule.action === 'block' && (
- <Form.Item label="Block delay (ms)">
- <Input
- value={rule.blockDelay}
- placeholder="optional: 5000-10000"
- onChange={(e) => { rule.blockDelay = e.target.value; refresh(); }}
- />
- </Form.Item>
- )}
- </div>
- ))}
- </>
- );
- }
- function DnsFields({ ob, refresh, t }: TFieldProps) {
- const rules = (ob.settings.rules || []) as Array<{ action: string; qtype?: string; domain?: string }>;
- return (
- <>
- <Form.Item label="Rewrite network">
- <Select
- value={ob.settings.rewriteNetwork}
- allowClear
- placeholder="(unchanged)"
- onChange={(v) => { ob.settings.rewriteNetwork = v; refresh(); }}
- options={['udp', 'tcp'].map((x) => ({ value: x, label: x }))}
- />
- </Form.Item>
- <Form.Item label="Rewrite address">
- <Input
- value={ob.settings.rewriteAddress || ''}
- placeholder="(unchanged) e.g. 1.1.1.1"
- onChange={(e) => { ob.settings.rewriteAddress = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="Rewrite port">
- <InputNumber
- value={ob.settings.rewritePort || undefined}
- min={0}
- max={65535}
- style={{ width: '100%' }}
- placeholder="(unchanged)"
- onChange={(v) => { ob.settings.rewritePort = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="User level">
- <InputNumber
- value={ob.settings.userLevel || 0}
- min={0}
- style={{ width: '100%' }}
- onChange={(v) => { ob.settings.userLevel = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="Rules">
- <Button
- size="small"
- type="primary"
- icon={<PlusOutlined />}
- onClick={() => { (ob.settings.rules || (ob.settings.rules = [])).push(new Outbound.DNSRule()); refresh(); }}
- />
- </Form.Item>
- {rules.map((rule, index) => (
- <div key={index}>
- <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
- <div className="item-heading">
- <span>Rule {index + 1}</span>
- <DeleteOutlined
- className="danger-icon"
- onClick={() => { (ob.settings.rules as unknown[]).splice(index, 1); refresh(); }}
- />
- </div>
- </Form.Item>
- <Form.Item label="Action">
- <Select
- value={rule.action}
- onChange={(v) => { rule.action = v; refresh(); }}
- options={(DNSRuleActions as string[]).map((a) => ({ value: a, label: a }))}
- />
- </Form.Item>
- <Form.Item label="QType">
- <Input
- value={rule.qtype}
- placeholder="1,3,23-24"
- onChange={(e) => { rule.qtype = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label={t('domainName')}>
- <Input
- value={rule.domain}
- placeholder="domain:example.com"
- onChange={(e) => { rule.domain = e.target.value; refresh(); }}
- />
- </Form.Item>
- </div>
- ))}
- </>
- );
- }
- function WireguardFields({ ob, refresh, regenerate, t }: TFieldProps & { regenerate: () => void }) {
- const peers = (ob.settings.peers || []) as Array<{ endpoint?: string; publicKey?: string; psk?: string; allowedIPs?: string[]; keepAlive?: number }>;
- return (
- <>
- <Form.Item label={t('pages.inbounds.address')}>
- <Input
- value={ob.settings.address || ''}
- onChange={(e) => { ob.settings.address = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item
- label={
- <>
- {t('pages.inbounds.privatekey')}
- <SyncOutlined className="random-icon" onClick={regenerate} />
- </>
- }
- >
- <Input
- value={ob.settings.secretKey || ''}
- onChange={(e) => { ob.settings.secretKey = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label={t('pages.inbounds.publicKey')}>
- <Input value={ob.settings.pubKey || ''} disabled />
- </Form.Item>
- <Form.Item label="Domain strategy">
- <Select
- value={ob.settings.domainStrategy || ''}
- onChange={(v) => { ob.settings.domainStrategy = v; refresh(); }}
- options={['', ...(WireguardDomainStrategy as string[])].map((x) => ({ value: x, label: x || `(${t('none')})` }))}
- />
- </Form.Item>
- <Form.Item label="MTU">
- <InputNumber value={ob.settings.mtu || 0} min={0} onChange={(v) => { ob.settings.mtu = Number(v) || 0; refresh(); }} />
- </Form.Item>
- <Form.Item label="Workers">
- <InputNumber value={ob.settings.workers || 0} min={0} onChange={(v) => { ob.settings.workers = Number(v) || 0; refresh(); }} />
- </Form.Item>
- <Form.Item label="No-kernel TUN">
- <Switch checked={!!ob.settings.noKernelTun} onChange={(v) => { ob.settings.noKernelTun = v; refresh(); }} />
- </Form.Item>
- <Form.Item label="Reserved">
- <Input value={ob.settings.reserved || ''} onChange={(e) => { ob.settings.reserved = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Peers">
- <Button
- size="small"
- type="primary"
- icon={<PlusOutlined />}
- onClick={() => { (ob.settings.peers || (ob.settings.peers = [])).push(new Outbound.WireguardSettings.Peer()); refresh(); }}
- />
- </Form.Item>
- {peers.map((peer, index) => (
- <div key={index}>
- <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
- <div className="item-heading">
- <span>Peer {index + 1}</span>
- {peers.length > 1 && (
- <DeleteOutlined
- className="danger-icon"
- onClick={() => { (ob.settings.peers as unknown[]).splice(index, 1); refresh(); }}
- />
- )}
- </div>
- </Form.Item>
- <Form.Item label="Endpoint">
- <Input value={peer.endpoint} onChange={(e) => { peer.endpoint = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label={t('pages.inbounds.publicKey')}>
- <Input value={peer.publicKey} onChange={(e) => { peer.publicKey = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="PSK">
- <Input value={peer.psk} onChange={(e) => { peer.psk = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Allowed IPs">
- {(peer.allowedIPs || []).map((ip, idx) => (
- <Space.Compact key={idx} block style={{ marginBottom: 4 }}>
- <Input
- value={ip}
- onChange={(e) => { peer.allowedIPs![idx] = e.target.value; refresh(); }}
- />
- {(peer.allowedIPs || []).length > 1 && (
- <InputAddon onClick={() => { peer.allowedIPs!.splice(idx, 1); refresh(); }}>
- <MinusOutlined />
- </InputAddon>
- )}
- </Space.Compact>
- ))}
- <Button
- size="small"
- icon={<PlusOutlined />}
- onClick={() => { (peer.allowedIPs = peer.allowedIPs || []).push(''); refresh(); }}
- />
- </Form.Item>
- <Form.Item label="Keep alive">
- <InputNumber value={peer.keepAlive || 0} min={0} onChange={(v) => { peer.keepAlive = Number(v) || 0; refresh(); }} />
- </Form.Item>
- </div>
- ))}
- </>
- );
- }
- function VMessVLessFields({ ob, refresh, isVMess, isVLESS, t }: TFieldProps & { isVMess: boolean; isVLESS: boolean }) {
- const rev = ob.settings.reverseSniffing || {};
- return (
- <>
- <Form.Item label="ID">
- <Input value={ob.settings.id || ''} onChange={(e) => { ob.settings.id = e.target.value; refresh(); }} />
- </Form.Item>
- {isVMess && (
- <Form.Item label={t('security')}>
- <Select
- value={ob.settings.security}
- onChange={(v) => { ob.settings.security = v; refresh(); }}
- options={SECURITY_OPTIONS.map((s) => ({ value: s, label: s }))}
- />
- </Form.Item>
- )}
- {isVLESS && (
- <Form.Item label={t('encryption')}>
- <Input
- value={ob.settings.encryption || ''}
- onChange={(e) => { ob.settings.encryption = e.target.value; refresh(); }}
- />
- </Form.Item>
- )}
- {isVLESS && (
- <Form.Item label="Reverse tag">
- <Input
- value={ob.settings.reverseTag || ''}
- placeholder="optional"
- onChange={(e) => { ob.settings.reverseTag = e.target.value; refresh(); }}
- />
- </Form.Item>
- )}
- {isVLESS && ob.settings.reverseTag && (
- <>
- <Form.Item label="Reverse Sniffing">
- <Switch checked={!!rev.enabled} onChange={(v) => { rev.enabled = v; refresh(); }} />
- </Form.Item>
- {rev.enabled && (
- <>
- <Form.Item wrapperCol={{ md: { span: 14, offset: 8 } }}>
- <Checkbox.Group
- className="sniffing-options"
- value={rev.destOverride || []}
- onChange={(v) => { rev.destOverride = v as string[]; refresh(); }}
- options={Object.entries(SNIFFING_OPTION).map(([label, value]) => ({ label, value: value as string }))}
- />
- </Form.Item>
- <Form.Item label="Metadata Only">
- <Switch checked={!!rev.metadataOnly} onChange={(v) => { rev.metadataOnly = v; refresh(); }} />
- </Form.Item>
- <Form.Item label="Route Only">
- <Switch checked={!!rev.routeOnly} onChange={(v) => { rev.routeOnly = v; refresh(); }} />
- </Form.Item>
- <Form.Item label="IPs Excluded">
- <Select
- mode="tags"
- value={rev.ipsExcluded || []}
- tokenSeparators={[',']}
- placeholder="IP/CIDR/geoip:*/ext:*"
- style={{ width: '100%' }}
- onChange={(v) => { rev.ipsExcluded = v as string[]; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="Domains Excluded">
- <Select
- mode="tags"
- value={rev.domainsExcluded || []}
- tokenSeparators={[',']}
- placeholder="domain:*/ext:*"
- style={{ width: '100%' }}
- onChange={(v) => { rev.domainsExcluded = v as string[]; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- </>
- )}
- {ob.canEnableTlsFlow() && (
- <Form.Item label="Flow">
- <Select
- value={ob.settings.flow || ''}
- onChange={(v) => { ob.settings.flow = v; refresh(); }}
- options={[{ value: '', label: t('none') }, ...FLOW_OPTIONS.map((k) => ({ value: k, label: k }))]}
- />
- </Form.Item>
- )}
- </>
- );
- }
- function StreamFields({ ob, refresh, streamNetworkChange, isHysteria, t }: TFieldProps & { streamNetworkChange: (next: string) => void; isHysteria: boolean }) {
- const networks = isHysteria ? [...NETWORKS, 'hysteria'] : NETWORKS;
- return (
- <>
- <Form.Item label={t('transmission')}>
- <Select
- value={ob.stream.network}
- onChange={streamNetworkChange}
- options={networks.map((net) => ({ value: net, label: NETWORK_LABELS[net] || net }))}
- />
- </Form.Item>
- {ob.stream.network === 'tcp' && (
- <>
- <Form.Item label={`HTTP ${t('camouflage')}`}>
- <Switch
- checked={ob.stream.tcp.type === 'http'}
- onChange={(checked) => { ob.stream.tcp.type = checked ? 'http' : 'none'; refresh(); }}
- />
- </Form.Item>
- {ob.stream.tcp.type === 'http' && (
- <>
- <Form.Item label={t('host')}>
- <Input value={ob.stream.tcp.host || ''} onChange={(e) => { ob.stream.tcp.host = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label={t('path')}>
- <Input value={ob.stream.tcp.path || ''} onChange={(e) => { ob.stream.tcp.path = e.target.value; refresh(); }} />
- </Form.Item>
- </>
- )}
- </>
- )}
- {ob.stream.network === 'kcp' && (
- <>
- {(
- [
- ['mtu', 'MTU', 0],
- ['tti', 'TTI (ms)', 0],
- ['upCap', 'Uplink (MB/s)', 0],
- ['downCap', 'Downlink (MB/s)', 0],
- ['cwndMultiplier', 'CWND multiplier', 1],
- ['maxSendingWindow', 'Max sending window', 0],
- ] as const
- ).map(([field, label, min]) => (
- <Form.Item key={field} label={label}>
- <InputNumber
- value={ob.stream.kcp[field] ?? 0}
- min={min}
- onChange={(v) => { ob.stream.kcp[field] = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- ))}
- </>
- )}
- {ob.stream.network === 'ws' && (
- <>
- <Form.Item label={t('host')}>
- <Input value={ob.stream.ws.host || ''} onChange={(e) => { ob.stream.ws.host = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label={t('path')}>
- <Input value={ob.stream.ws.path || ''} onChange={(e) => { ob.stream.ws.path = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Heartbeat (s)">
- <InputNumber
- value={ob.stream.ws.heartbeatPeriod || 0}
- min={0}
- onChange={(v) => { ob.stream.ws.heartbeatPeriod = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- {ob.stream.network === 'grpc' && (
- <>
- <Form.Item label="Service name">
- <Input value={ob.stream.grpc.serviceName || ''} onChange={(e) => { ob.stream.grpc.serviceName = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Authority">
- <Input value={ob.stream.grpc.authority || ''} onChange={(e) => { ob.stream.grpc.authority = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Multi mode">
- <Switch checked={!!ob.stream.grpc.multiMode} onChange={(v) => { ob.stream.grpc.multiMode = v; refresh(); }} />
- </Form.Item>
- </>
- )}
- {ob.stream.network === 'httpupgrade' && (
- <>
- <Form.Item label={t('host')}>
- <Input value={ob.stream.httpupgrade.host || ''} onChange={(e) => { ob.stream.httpupgrade.host = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label={t('path')}>
- <Input value={ob.stream.httpupgrade.path || ''} onChange={(e) => { ob.stream.httpupgrade.path = e.target.value; refresh(); }} />
- </Form.Item>
- </>
- )}
- {ob.stream.network === 'xhttp' && <XhttpFields ob={ob} refresh={refresh} t={t} />}
- {ob.stream.network === 'hysteria' && <HysteriaTransportFields ob={ob} refresh={refresh} />}
- </>
- );
- }
- function XhttpFields({ ob, refresh, t }: TFieldProps) {
- const xh = ob.stream.xhttp;
- return (
- <>
- <Form.Item label={t('host')}>
- <Input value={xh.host || ''} onChange={(e) => { xh.host = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label={t('path')}>
- <Input value={xh.path || ''} onChange={(e) => { xh.path = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label={t('pages.inbounds.stream.tcp.requestHeader')}>
- <Button size="small" icon={<PlusOutlined />} onClick={() => { xh.addHeader('', ''); refresh(); }} />
- </Form.Item>
- <Form.Item wrapperCol={{ span: 24 }}>
- {(xh.headers as Array<{ name: string; value: string }>).map((header, idx) => (
- <Space.Compact key={idx} block className="mb-8">
- <InputAddon>{`${idx + 1}`}</InputAddon>
- <Input
- value={header.name}
- placeholder="Name"
- onChange={(e) => { header.name = e.target.value; refresh(); }}
- />
- <Input
- value={header.value}
- placeholder="Value"
- onChange={(e) => { header.value = e.target.value; refresh(); }}
- />
- <Button icon={<MinusOutlined />} onClick={() => { xh.removeHeader(idx); refresh(); }} />
- </Space.Compact>
- ))}
- </Form.Item>
- <Form.Item label="Mode">
- <Select
- value={xh.mode}
- onChange={(v) => { xh.mode = v; refresh(); }}
- options={Object.values(MODE_OPTION).map((m) => ({ value: m as string, label: m as string }))}
- />
- </Form.Item>
- {xh.mode === 'packet-up' && (
- <>
- <Form.Item label="Max Upload Size (Byte)">
- <Input value={xh.scMaxEachPostBytes} onChange={(e) => { xh.scMaxEachPostBytes = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Min Upload Interval (Ms)">
- <Input value={xh.scMinPostsIntervalMs} onChange={(e) => { xh.scMinPostsIntervalMs = e.target.value; refresh(); }} />
- </Form.Item>
- </>
- )}
- <Form.Item label="Padding Bytes">
- <Input value={xh.xPaddingBytes} onChange={(e) => { xh.xPaddingBytes = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Padding Obfs Mode">
- <Switch checked={!!xh.xPaddingObfsMode} onChange={(v) => { xh.xPaddingObfsMode = v; refresh(); }} />
- </Form.Item>
- {xh.xPaddingObfsMode && (
- <>
- <Form.Item label="Padding Key">
- <Input value={xh.xPaddingKey} placeholder="x_padding" onChange={(e) => { xh.xPaddingKey = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Padding Header">
- <Input value={xh.xPaddingHeader} placeholder="X-Padding" onChange={(e) => { xh.xPaddingHeader = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Padding Placement">
- <Select
- value={xh.xPaddingPlacement || ''}
- onChange={(v) => { xh.xPaddingPlacement = v; refresh(); }}
- options={[
- { value: '', label: 'Default (queryInHeader)' },
- { value: 'queryInHeader', label: 'queryInHeader' },
- { value: 'header', label: 'header' },
- { value: 'cookie', label: 'cookie' },
- { value: 'query', label: 'query' },
- ]}
- />
- </Form.Item>
- <Form.Item label="Padding Method">
- <Select
- value={xh.xPaddingMethod || ''}
- onChange={(v) => { xh.xPaddingMethod = v; refresh(); }}
- options={[
- { value: '', label: 'Default (repeat-x)' },
- { value: 'repeat-x', label: 'repeat-x' },
- { value: 'tokenish', label: 'tokenish' },
- ]}
- />
- </Form.Item>
- </>
- )}
- <Form.Item label="Uplink HTTP Method">
- <Select
- value={xh.uplinkHTTPMethod || ''}
- onChange={(v) => { xh.uplinkHTTPMethod = v; refresh(); }}
- options={[
- { value: '', label: 'Default (POST)' },
- { value: 'POST', label: 'POST' },
- { value: 'PUT', label: 'PUT' },
- { value: 'GET', label: 'GET (packet-up only)', disabled: xh.mode !== 'packet-up' },
- ]}
- />
- </Form.Item>
- <Form.Item label="Session Placement">
- <Select
- value={xh.sessionPlacement || ''}
- onChange={(v) => { xh.sessionPlacement = v; refresh(); }}
- options={[
- { value: '', label: 'Default (path)' },
- { value: 'path', label: 'path' },
- { value: 'header', label: 'header' },
- { value: 'cookie', label: 'cookie' },
- { value: 'query', label: 'query' },
- ]}
- />
- </Form.Item>
- {xh.sessionPlacement && xh.sessionPlacement !== 'path' && (
- <Form.Item label="Session Key">
- <Input value={xh.sessionKey} placeholder="x_session" onChange={(e) => { xh.sessionKey = e.target.value; refresh(); }} />
- </Form.Item>
- )}
- <Form.Item label="Sequence Placement">
- <Select
- value={xh.seqPlacement || ''}
- onChange={(v) => { xh.seqPlacement = v; refresh(); }}
- options={[
- { value: '', label: 'Default (path)' },
- { value: 'path', label: 'path' },
- { value: 'header', label: 'header' },
- { value: 'cookie', label: 'cookie' },
- { value: 'query', label: 'query' },
- ]}
- />
- </Form.Item>
- {xh.seqPlacement && xh.seqPlacement !== 'path' && (
- <Form.Item label="Sequence Key">
- <Input value={xh.seqKey} placeholder="x_seq" onChange={(e) => { xh.seqKey = e.target.value; refresh(); }} />
- </Form.Item>
- )}
- {xh.mode === 'packet-up' && (
- <Form.Item label="Uplink Data Placement">
- <Select
- value={xh.uplinkDataPlacement || ''}
- onChange={(v) => { xh.uplinkDataPlacement = v; refresh(); }}
- options={[
- { value: '', label: 'Default (body)' },
- { value: 'body', label: 'body' },
- { value: 'header', label: 'header' },
- { value: 'cookie', label: 'cookie' },
- { value: 'query', label: 'query' },
- ]}
- />
- </Form.Item>
- )}
- {xh.mode === 'packet-up' && xh.uplinkDataPlacement && xh.uplinkDataPlacement !== 'body' && (
- <>
- <Form.Item label="Uplink Data Key">
- <Input value={xh.uplinkDataKey} placeholder="x_data" onChange={(e) => { xh.uplinkDataKey = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Uplink Chunk Size">
- <InputNumber
- value={xh.uplinkChunkSize}
- min={0}
- placeholder="0 (unlimited)"
- onChange={(v) => { xh.uplinkChunkSize = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- {(xh.mode === 'stream-up' || xh.mode === 'stream-one') && (
- <Form.Item label="No gRPC Header">
- <Switch checked={!!xh.noGRPCHeader} onChange={(v) => { xh.noGRPCHeader = v; refresh(); }} />
- </Form.Item>
- )}
- <Form.Item label="XMUX">
- <Switch checked={!!xh.enableXmux} onChange={(v) => { xh.enableXmux = v; refresh(); }} />
- </Form.Item>
- {xh.enableXmux && (
- <>
- {!xh.xmux.maxConnections && (
- <Form.Item label="Max Concurrency">
- <Input value={xh.xmux.maxConcurrency} onChange={(e) => { xh.xmux.maxConcurrency = e.target.value; refresh(); }} />
- </Form.Item>
- )}
- {!xh.xmux.maxConcurrency && (
- <Form.Item label="Max Connections">
- <Input value={xh.xmux.maxConnections} onChange={(e) => { xh.xmux.maxConnections = e.target.value; refresh(); }} />
- </Form.Item>
- )}
- <Form.Item label="Max Reuse Times">
- <Input value={xh.xmux.cMaxReuseTimes} onChange={(e) => { xh.xmux.cMaxReuseTimes = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Max Request Times">
- <Input value={xh.xmux.hMaxRequestTimes} onChange={(e) => { xh.xmux.hMaxRequestTimes = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Max Reusable Secs">
- <Input value={xh.xmux.hMaxReusableSecs} onChange={(e) => { xh.xmux.hMaxReusableSecs = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Keep Alive Period">
- <InputNumber
- value={xh.xmux.hKeepAlivePeriod}
- min={0}
- onChange={(v) => { xh.xmux.hKeepAlivePeriod = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- </>
- );
- }
- function HysteriaTransportFields({ ob, refresh }: FieldProps) {
- const h = ob.stream.hysteria;
- return (
- <>
- <Form.Item label="Auth password">
- <Input value={h.auth || ''} onChange={(e) => { h.auth = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Congestion">
- <Select
- value={h.congestion || ''}
- onChange={(v) => { h.congestion = v; refresh(); }}
- options={[
- { value: '', label: 'BBR (auto)' },
- { value: 'brutal', label: 'Brutal' },
- ]}
- />
- </Form.Item>
- <Form.Item label="Upload">
- <Input value={h.up} placeholder="100 mbps" onChange={(e) => { h.up = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Download">
- <Input value={h.down} placeholder="100 mbps" onChange={(e) => { h.down = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="UDP hop port">
- <Input value={h.udphopPort} placeholder="1145-1919" onChange={(e) => { h.udphopPort = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Max idle (s)">
- <InputNumber value={h.maxIdleTimeout} min={4} max={120} onChange={(v) => { h.maxIdleTimeout = Number(v) || 0; refresh(); }} />
- </Form.Item>
- <Form.Item label="Keep alive (s)">
- <InputNumber value={h.keepAlivePeriod} min={2} max={60} onChange={(v) => { h.keepAlivePeriod = Number(v) || 0; refresh(); }} />
- </Form.Item>
- <Form.Item label="Disable Path MTU">
- <Switch checked={!!h.disablePathMTUDiscovery} onChange={(v) => { h.disablePathMTUDiscovery = v; refresh(); }} />
- </Form.Item>
- </>
- );
- }
- function TlsFields({ ob, refresh, t }: TFieldProps) {
- return (
- <>
- <Form.Item label={t('security')}>
- <Radio.Group
- value={ob.stream.security}
- buttonStyle="solid"
- onChange={(e) => { ob.stream.security = e.target.value; refresh(); }}
- >
- <Radio.Button value="none">{t('none')}</Radio.Button>
- <Radio.Button value="tls">TLS</Radio.Button>
- {ob.canEnableReality() && <Radio.Button value="reality">Reality</Radio.Button>}
- </Radio.Group>
- </Form.Item>
- {ob.stream.isTls && (
- <>
- <Form.Item label="SNI">
- <Input value={ob.stream.tls.serverName} placeholder="server name" onChange={(e) => { ob.stream.tls.serverName = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="uTLS">
- <Select
- value={ob.stream.tls.fingerprint || ''}
- onChange={(v) => { ob.stream.tls.fingerprint = v; refresh(); }}
- options={[{ value: '', label: t('none') }, ...UTLS_OPTIONS.map((k) => ({ value: k, label: k }))]}
- />
- </Form.Item>
- <Form.Item label="ALPN">
- <Select
- mode="multiple"
- value={ob.stream.tls.alpn || []}
- onChange={(v) => { ob.stream.tls.alpn = v; refresh(); }}
- options={ALPN_OPTIONS.map((alpn) => ({ value: alpn, label: alpn }))}
- />
- </Form.Item>
- <Form.Item label="ECH">
- <Input value={ob.stream.tls.echConfigList} onChange={(e) => { ob.stream.tls.echConfigList = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Verify peer name">
- <Input value={ob.stream.tls.verifyPeerCertByName} placeholder="cloudflare-dns.com" onChange={(e) => { ob.stream.tls.verifyPeerCertByName = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Pinned SHA256">
- <Input value={ob.stream.tls.pinnedPeerCertSha256} placeholder="base64 SHA256" onChange={(e) => { ob.stream.tls.pinnedPeerCertSha256 = e.target.value; refresh(); }} />
- </Form.Item>
- </>
- )}
- {ob.stream.isReality && (
- <>
- <Form.Item label="SNI">
- <Input value={ob.stream.reality.serverName} onChange={(e) => { ob.stream.reality.serverName = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="uTLS">
- <Select
- value={ob.stream.reality.fingerprint}
- onChange={(v) => { ob.stream.reality.fingerprint = v; refresh(); }}
- options={UTLS_OPTIONS.map((k) => ({ value: k, label: k }))}
- />
- </Form.Item>
- <Form.Item label="Short ID">
- <Input value={ob.stream.reality.shortId} onChange={(e) => { ob.stream.reality.shortId = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="SpiderX">
- <Input value={ob.stream.reality.spiderX} onChange={(e) => { ob.stream.reality.spiderX = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label={t('pages.inbounds.publicKey')}>
- <Input.TextArea
- value={ob.stream.reality.publicKey}
- autoSize={{ minRows: 2 }}
- onChange={(e) => { ob.stream.reality.publicKey = e.target.value; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="mldsa65 verify">
- <Input.TextArea
- value={ob.stream.reality.mldsa65Verify}
- autoSize={{ minRows: 2 }}
- onChange={(e) => { ob.stream.reality.mldsa65Verify = e.target.value; refresh(); }}
- />
- </Form.Item>
- </>
- )}
- </>
- );
- }
- function SockoptFields({ ob, refresh }: FieldProps) {
- return (
- <>
- <Form.Item label="Sockopts">
- <Switch checked={!!ob.stream.sockoptSwitch} onChange={(v) => { ob.stream.sockoptSwitch = v; refresh(); }} />
- </Form.Item>
- {ob.stream.sockoptSwitch && (
- <>
- <Form.Item label="Dialer proxy">
- <Input value={ob.stream.sockopt.dialerProxy || ''} onChange={(e) => { ob.stream.sockopt.dialerProxy = e.target.value; refresh(); }} />
- </Form.Item>
- <Form.Item label="Address+Port strategy">
- <Select
- value={ob.stream.sockopt.addressPortStrategy}
- onChange={(v) => { ob.stream.sockopt.addressPortStrategy = v; refresh(); }}
- options={Object.values(Address_Port_Strategy).map((k) => ({ value: k as string, label: k as string }))}
- />
- </Form.Item>
- <Form.Item label="Keep alive interval">
- <InputNumber
- value={ob.stream.sockopt.tcpKeepAliveInterval}
- min={0}
- onChange={(v) => { ob.stream.sockopt.tcpKeepAliveInterval = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="TCP Fast Open">
- <Switch checked={!!ob.stream.sockopt.tcpFastOpen} onChange={(v) => { ob.stream.sockopt.tcpFastOpen = v; refresh(); }} />
- </Form.Item>
- <Form.Item label="Multipath TCP">
- <Switch checked={!!ob.stream.sockopt.tcpMptcp} onChange={(v) => { ob.stream.sockopt.tcpMptcp = v; refresh(); }} />
- </Form.Item>
- <Form.Item label="Penetrate">
- <Switch checked={!!ob.stream.sockopt.penetrate} onChange={(v) => { ob.stream.sockopt.penetrate = v; refresh(); }} />
- </Form.Item>
- <Form.Item label="Mark (fwmark)">
- <InputNumber
- value={ob.stream.sockopt.mark}
- min={0}
- onChange={(v) => { ob.stream.sockopt.mark = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="Interface">
- <Input value={ob.stream.sockopt.interfaceName} onChange={(e) => { ob.stream.sockopt.interfaceName = e.target.value; refresh(); }} />
- </Form.Item>
- </>
- )}
- </>
- );
- }
- function MuxFields({ ob, refresh, t }: TFieldProps) {
- return (
- <>
- <Form.Item label={t('pages.settings.mux')}>
- <Switch checked={!!ob.mux.enabled} onChange={(v) => { ob.mux.enabled = v; refresh(); }} />
- </Form.Item>
- {ob.mux.enabled && (
- <>
- <Form.Item label="Concurrency">
- <InputNumber
- value={ob.mux.concurrency}
- min={-1}
- max={1024}
- onChange={(v) => { ob.mux.concurrency = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="xudp concurrency">
- <InputNumber
- value={ob.mux.xudpConcurrency}
- min={-1}
- max={1024}
- onChange={(v) => { ob.mux.xudpConcurrency = Number(v) || 0; refresh(); }}
- />
- </Form.Item>
- <Form.Item label="xudp UDP 443">
- <Select
- value={ob.mux.xudpProxyUDP443}
- onChange={(v) => { ob.mux.xudpProxyUDP443 = v; refresh(); }}
- options={['reject', 'allow', 'skip'].map((x) => ({ value: x, label: x }))}
- />
- </Form.Item>
- </>
- )}
- </>
- );
- }
|