mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
refactor(frontend): split outbound protocol forms into per-protocol files
Replace the grouped outbound-core-fields / outbound-wireguard-fields with one file per protocol under outbounds/protocols/: vmess, vless, trojan, shadowsocks, http, socks, wireguard, freedom, blackhole, dns, loopback (+ shared server-target). Matches the prompt's 1-file-per-protocol structure (per-modal). Outbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
This commit is contained in:
parent
2be473aea3
commit
47a2eb7efe
11 changed files with 208 additions and 131 deletions
|
|
@ -53,6 +53,7 @@ import {
|
|||
MODE_OPTIONS,
|
||||
NETWORK_OPTIONS,
|
||||
PROTOCOL_OPTIONS,
|
||||
SERVER_PROTOCOLS,
|
||||
UTLS_OPTIONS,
|
||||
} from './outbound-form-constants';
|
||||
import {
|
||||
|
|
@ -61,9 +62,20 @@ import {
|
|||
isMuxAllowed,
|
||||
newStreamSlice,
|
||||
} from './outbound-form-helpers';
|
||||
import { OutboundCoreProtocolFields } from './outbound-core-fields';
|
||||
import { WireguardOutboundFields } from './outbound-wireguard-fields';
|
||||
import { BlackholeFields, DnsFields, FreedomFields, LoopbackFields } from './protocols';
|
||||
import {
|
||||
BlackholeFields,
|
||||
DnsFields,
|
||||
FreedomFields,
|
||||
HttpFields,
|
||||
LoopbackFields,
|
||||
ServerTarget,
|
||||
ShadowsocksFields,
|
||||
SocksFields,
|
||||
TrojanFields,
|
||||
VlessFields,
|
||||
VmessFields,
|
||||
WireguardFields,
|
||||
} from './protocols';
|
||||
import './OutboundFormModal.css';
|
||||
|
||||
// Pattern A rewrite of OutboundFormModal. Built as a sibling `.new.tsx`
|
||||
|
|
@ -388,7 +400,13 @@ export default function OutboundFormModal({
|
|||
<Input placeholder={t('pages.xray.outboundForm.localIpPlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<OutboundCoreProtocolFields protocol={protocol} />
|
||||
{SERVER_PROTOCOLS.has(protocol) && <ServerTarget />}
|
||||
{protocol === 'vmess' && <VmessFields />}
|
||||
{protocol === 'vless' && <VlessFields />}
|
||||
{protocol === 'trojan' && <TrojanFields />}
|
||||
{protocol === 'shadowsocks' && <ShadowsocksFields />}
|
||||
{protocol === 'http' && <HttpFields />}
|
||||
{protocol === 'socks' && <SocksFields />}
|
||||
|
||||
{protocol === 'loopback' && <LoopbackFields />}
|
||||
{protocol === 'blackhole' && <BlackholeFields />}
|
||||
|
|
@ -470,7 +488,7 @@ export default function OutboundFormModal({
|
|||
</Form.Item>
|
||||
)}
|
||||
|
||||
{protocol === 'wireguard' && <WireguardOutboundFields form={form} />}
|
||||
{protocol === 'wireguard' && <WireguardFields form={form} />}
|
||||
|
||||
{streamAllowed && network && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, InputNumber, Select, Switch } from 'antd';
|
||||
|
||||
import {
|
||||
ShadowsocksOutboundFormSettingsSchema,
|
||||
TrojanOutboundFormSettingsSchema,
|
||||
VlessOutboundFormSettingsSchema,
|
||||
VmessOutboundFormSettingsSchema,
|
||||
} from '@/schemas/forms/outbound-form';
|
||||
import { SSMethodSchema } from '@/schemas/protocols/shared/shadowsocks';
|
||||
import { antdRule } from '@/utils/zodForm';
|
||||
|
||||
import { SECURITY_OPTIONS, SERVER_PROTOCOLS, SS_METHOD_OPTIONS } from './outbound-form-constants';
|
||||
|
||||
export function OutboundCoreProtocolFields({ protocol }: { protocol: string }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
{/* Shared connect target (address + port) for protocols
|
||||
whose form schema carries them flat at settings root.
|
||||
Hidden for freedom/blackhole/dns/loopback/wireguard. */}
|
||||
{SERVER_PROTOCOLS.has(protocol) && (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.address')}
|
||||
name={['settings', 'address']}
|
||||
rules={[{ required: true, message: t('pages.xray.outboundForm.addressRequired') }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.port')}
|
||||
name={['settings', 'port']}
|
||||
rules={[{ required: true, message: t('pages.xray.outboundForm.portRequired') }]}
|
||||
>
|
||||
<InputNumber min={1} max={65535} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(protocol === 'vmess' || protocol === 'vless') && (
|
||||
<Form.Item
|
||||
label="ID"
|
||||
name={['settings', 'id']}
|
||||
rules={[antdRule(VmessOutboundFormSettingsSchema.shape.id, t)]}
|
||||
>
|
||||
<Input placeholder="UUID" />
|
||||
</Form.Item>
|
||||
)}
|
||||
{protocol === 'vmess' && (
|
||||
<Form.Item
|
||||
label={t('security')}
|
||||
name={['settings', 'security']}
|
||||
rules={[antdRule(VmessOutboundFormSettingsSchema.shape.security, t)]}
|
||||
>
|
||||
<Select options={SECURITY_OPTIONS} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{protocol === 'vless' && (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('encryption')}
|
||||
name={['settings', 'encryption']}
|
||||
rules={[antdRule(VlessOutboundFormSettingsSchema.shape.encryption, t)]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('pages.clients.reverseTag')} name={['settings', 'reverseTag']}>
|
||||
<Input placeholder={t('pages.xray.outboundForm.optional')} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(protocol === 'trojan' || protocol === 'shadowsocks') && (
|
||||
<Form.Item
|
||||
label={t('password')}
|
||||
name={['settings', 'password']}
|
||||
rules={[
|
||||
antdRule(
|
||||
protocol === 'trojan'
|
||||
? TrojanOutboundFormSettingsSchema.shape.password
|
||||
: ShadowsocksOutboundFormSettingsSchema.shape.password,
|
||||
t,
|
||||
),
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{protocol === 'shadowsocks' && (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('encryption')}
|
||||
name={['settings', 'method']}
|
||||
rules={[antdRule(SSMethodSchema, t)]}
|
||||
>
|
||||
<Select options={SS_METHOD_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.udpOverTcp')}
|
||||
name={['settings', 'uot']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('pages.xray.outboundForm.uotVersion')} name={['settings', 'UoTVersion']}>
|
||||
<InputNumber min={1} max={2} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(protocol === 'socks' || protocol === 'http') && (
|
||||
<>
|
||||
<Form.Item label={t('username')} name={['settings', 'user']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('password')} name={['settings', 'pass']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
16
frontend/src/pages/xray/outbounds/protocols/http.tsx
Normal file
16
frontend/src/pages/xray/outbounds/protocols/http.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
export default function HttpFields() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item label={t('username')} name={['settings', 'user']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('password')} name={['settings', 'pass']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
export { default as ServerTarget } from './server-target';
|
||||
export { default as VmessFields } from './vmess';
|
||||
export { default as VlessFields } from './vless';
|
||||
export { default as TrojanFields } from './trojan';
|
||||
export { default as ShadowsocksFields } from './shadowsocks';
|
||||
export { default as HttpFields } from './http';
|
||||
export { default as SocksFields } from './socks';
|
||||
export { default as WireguardFields } from './wireguard';
|
||||
export { default as FreedomFields } from './freedom';
|
||||
export { default as LoopbackFields } from './loopback';
|
||||
export { default as BlackholeFields } from './blackhole';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, InputNumber } from 'antd';
|
||||
|
||||
export default function ServerTarget() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.address')}
|
||||
name={['settings', 'address']}
|
||||
rules={[{ required: true, message: t('pages.xray.outboundForm.addressRequired') }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.port')}
|
||||
name={['settings', 'port']}
|
||||
rules={[{ required: true, message: t('pages.xray.outboundForm.portRequired') }]}
|
||||
>
|
||||
<InputNumber min={1} max={65535} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
40
frontend/src/pages/xray/outbounds/protocols/shadowsocks.tsx
Normal file
40
frontend/src/pages/xray/outbounds/protocols/shadowsocks.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, InputNumber, Select, Switch } from 'antd';
|
||||
|
||||
import { ShadowsocksOutboundFormSettingsSchema } from '@/schemas/forms/outbound-form';
|
||||
import { SSMethodSchema } from '@/schemas/protocols/shared/shadowsocks';
|
||||
import { antdRule } from '@/utils/zodForm';
|
||||
|
||||
import { SS_METHOD_OPTIONS } from '../outbound-form-constants';
|
||||
|
||||
export default function ShadowsocksFields() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('password')}
|
||||
name={['settings', 'password']}
|
||||
rules={[antdRule(ShadowsocksOutboundFormSettingsSchema.shape.password, t)]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('encryption')}
|
||||
name={['settings', 'method']}
|
||||
rules={[antdRule(SSMethodSchema, t)]}
|
||||
>
|
||||
<Select options={SS_METHOD_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.udpOverTcp')}
|
||||
name={['settings', 'uot']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('pages.xray.outboundForm.uotVersion')} name={['settings', 'UoTVersion']}>
|
||||
<InputNumber min={1} max={2} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
16
frontend/src/pages/xray/outbounds/protocols/socks.tsx
Normal file
16
frontend/src/pages/xray/outbounds/protocols/socks.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
export default function SocksFields() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item label={t('username')} name={['settings', 'user']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('password')} name={['settings', 'pass']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
18
frontend/src/pages/xray/outbounds/protocols/trojan.tsx
Normal file
18
frontend/src/pages/xray/outbounds/protocols/trojan.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
import { TrojanOutboundFormSettingsSchema } from '@/schemas/forms/outbound-form';
|
||||
import { antdRule } from '@/utils/zodForm';
|
||||
|
||||
export default function TrojanFields() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('password')}
|
||||
name={['settings', 'password']}
|
||||
rules={[antdRule(TrojanOutboundFormSettingsSchema.shape.password, t)]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
33
frontend/src/pages/xray/outbounds/protocols/vless.tsx
Normal file
33
frontend/src/pages/xray/outbounds/protocols/vless.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
import {
|
||||
VlessOutboundFormSettingsSchema,
|
||||
VmessOutboundFormSettingsSchema,
|
||||
} from '@/schemas/forms/outbound-form';
|
||||
import { antdRule } from '@/utils/zodForm';
|
||||
|
||||
export default function VlessFields() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="ID"
|
||||
name={['settings', 'id']}
|
||||
rules={[antdRule(VmessOutboundFormSettingsSchema.shape.id, t)]}
|
||||
>
|
||||
<Input placeholder="UUID" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('encryption')}
|
||||
name={['settings', 'encryption']}
|
||||
rules={[antdRule(VlessOutboundFormSettingsSchema.shape.encryption, t)]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('pages.clients.reverseTag')} name={['settings', 'reverseTag']}>
|
||||
<Input placeholder={t('pages.xray.outboundForm.optional')} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
29
frontend/src/pages/xray/outbounds/protocols/vmess.tsx
Normal file
29
frontend/src/pages/xray/outbounds/protocols/vmess.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, Select } from 'antd';
|
||||
|
||||
import { VmessOutboundFormSettingsSchema } from '@/schemas/forms/outbound-form';
|
||||
import { antdRule } from '@/utils/zodForm';
|
||||
|
||||
import { SECURITY_OPTIONS } from '../outbound-form-constants';
|
||||
|
||||
export default function VmessFields() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="ID"
|
||||
name={['settings', 'id']}
|
||||
rules={[antdRule(VmessOutboundFormSettingsSchema.shape.id, t)]}
|
||||
>
|
||||
<Input placeholder="UUID" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('security')}
|
||||
name={['settings', 'security']}
|
||||
rules={[antdRule(VmessOutboundFormSettingsSchema.shape.security, t)]}
|
||||
>
|
||||
<Select options={SECURITY_OPTIONS} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import { InputAddon } from '@/components/ui';
|
|||
import { WireguardDomainStrategy } from '@/schemas/primitives';
|
||||
import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
|
||||
|
||||
export function WireguardOutboundFields({ form }: { form: FormInstance<OutboundFormValues> }) {
|
||||
export default function WireguardFields({ form }: { form: FormInstance<OutboundFormValues> }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
Loading…
Reference in a new issue