mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
feat(frontend): protocol tab VLESS auth on InboundFormModal.new.tsx
Adds the protocol tab to the sibling-file rewrite — currently only the VLESS section, which lays out decryption/encryption inputs and the three buttons that drive them: Get New x25519, Get New mlkem768, Clear. getNewVlessEnc + clearVlessEnc are ported from the legacy modal as pure setFieldValue paths into ['settings', 'decryption'] / ['settings', 'encryption'] — no class methods, no inboundRef. The matchesVlessAuth helper mirrors the legacy fuzzy label-matching so the backend response shape stays the only source of truth. selectedVlessAuth derives the displayed auth label from the encryption string via Form.useWatch — same heuristic as the legacy modal (.length > 300 → mlkem768, otherwise x25519). Tab spread is conditional: the protocol tab only appears when protocol === 'vless' right now. As more protocol sections land (shadowsocks, http/mixed, tunnel, tun, wireguard) the condition will widen to cover each one.
This commit is contained in:
parent
74a2813fb4
commit
102465f9d1
1 changed files with 83 additions and 0 deletions
|
|
@ -2,15 +2,18 @@ import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Typography,
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
|
|
||||||
|
|
@ -36,6 +39,8 @@ import type { NodeRecord } from '@/api/queries/useNodesQuery';
|
||||||
// InboundsPage continues to render the old InboundFormModal.tsx until the
|
// InboundsPage continues to render the old InboundFormModal.tsx until the
|
||||||
// atomic swap at the end (Core Decision 7).
|
// atomic swap at the end (Core Decision 7).
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p }));
|
const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p }));
|
||||||
const TRAFFIC_RESETS = ['never', 'hourly', 'daily', 'weekly', 'monthly'] as const;
|
const TRAFFIC_RESETS = ['never', 'hourly', 'daily', 'weekly', 'monthly'] as const;
|
||||||
const NODE_ELIGIBLE_PROTOCOLS = new Set<string>([
|
const NODE_ELIGIBLE_PROTOCOLS = new Set<string>([
|
||||||
|
|
@ -89,6 +94,52 @@ export default function InboundFormModalNew({
|
||||||
const protocol = Form.useWatch('protocol', form) ?? '';
|
const protocol = Form.useWatch('protocol', form) ?? '';
|
||||||
const isNodeEligible = NODE_ELIGIBLE_PROTOCOLS.has(protocol);
|
const isNodeEligible = NODE_ELIGIBLE_PROTOCOLS.has(protocol);
|
||||||
const sniffingEnabled = Form.useWatch(['sniffing', 'enabled'], form) ?? false;
|
const sniffingEnabled = Form.useWatch(['sniffing', 'enabled'], form) ?? false;
|
||||||
|
const vlessEncryption = Form.useWatch(['settings', 'encryption'], form) ?? '';
|
||||||
|
|
||||||
|
const matchesVlessAuth = (
|
||||||
|
block: { id?: string; label?: string } | undefined | null,
|
||||||
|
authId: string,
|
||||||
|
) => {
|
||||||
|
if (block?.id === authId) return true;
|
||||||
|
const label = (block?.label || '').toLowerCase().replace(/[-_\s]/g, '');
|
||||||
|
if (authId === 'mlkem768') return label.includes('mlkem768');
|
||||||
|
if (authId === 'x25519') return label.includes('x25519');
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNewVlessEnc = async (authId: string) => {
|
||||||
|
if (!authId) return;
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const msg = await HttpUtil.get('/panel/api/server/getNewVlessEnc');
|
||||||
|
if (!msg?.success) return;
|
||||||
|
const obj = msg.obj as {
|
||||||
|
auths?: { decryption: string; encryption: string; label?: string; id?: string }[];
|
||||||
|
};
|
||||||
|
const block = (obj.auths || []).find((a) => matchesVlessAuth(a, authId));
|
||||||
|
if (!block) return;
|
||||||
|
form.setFieldValue(['settings', 'decryption'], block.decryption);
|
||||||
|
form.setFieldValue(['settings', 'encryption'], block.encryption);
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearVlessEnc = () => {
|
||||||
|
form.setFieldValue(['settings', 'decryption'], 'none');
|
||||||
|
form.setFieldValue(['settings', 'encryption'], 'none');
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedVlessAuth = (() => {
|
||||||
|
const enc = typeof vlessEncryption === 'string' ? vlessEncryption : '';
|
||||||
|
if (!enc || enc === 'none') return 'None';
|
||||||
|
const parts = enc.split('.').filter(Boolean);
|
||||||
|
const authKey = parts[parts.length - 1] || '';
|
||||||
|
if (!authKey) return t('pages.inbounds.vlessAuthCustom');
|
||||||
|
return authKey.length > 300
|
||||||
|
? t('pages.inbounds.vlessAuthMlkem768')
|
||||||
|
: t('pages.inbounds.vlessAuthX25519');
|
||||||
|
})();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
|
|
@ -273,6 +324,35 @@ export default function InboundFormModalNew({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const protocolTab = (
|
||||||
|
<>
|
||||||
|
{protocol === Protocols.VLESS && (
|
||||||
|
<>
|
||||||
|
<Form.Item name={['settings', 'decryption']} label={t('pages.inbounds.decryption')}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={['settings', 'encryption']} label={t('pages.inbounds.encryption')}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label=" ">
|
||||||
|
<Space size={8} wrap>
|
||||||
|
<Button type="primary" loading={saving} onClick={() => getNewVlessEnc('x25519')}>
|
||||||
|
{t('pages.inbounds.vlessAuthX25519')}
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" loading={saving} onClick={() => getNewVlessEnc('mlkem768')}>
|
||||||
|
{t('pages.inbounds.vlessAuthMlkem768')}
|
||||||
|
</Button>
|
||||||
|
<Button danger onClick={clearVlessEnc}>{t('clear')}</Button>
|
||||||
|
</Space>
|
||||||
|
<Text type="secondary" className="vless-auth-state">
|
||||||
|
{t('pages.inbounds.vlessAuthSelected', { auth: selectedVlessAuth })}
|
||||||
|
</Text>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const sniffingTab = (
|
const sniffingTab = (
|
||||||
<>
|
<>
|
||||||
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
|
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
|
||||||
|
|
@ -357,6 +437,9 @@ export default function InboundFormModalNew({
|
||||||
>
|
>
|
||||||
<Tabs items={[
|
<Tabs items={[
|
||||||
{ key: 'basic', label: t('pages.xray.basicTemplate'), children: basicTab },
|
{ key: 'basic', label: t('pages.xray.basicTemplate'), children: basicTab },
|
||||||
|
...(protocol === Protocols.VLESS
|
||||||
|
? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }]
|
||||||
|
: []),
|
||||||
{ key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab },
|
{ key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab },
|
||||||
]} />
|
]} />
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue