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:
MHSanaei 2026-05-26 02:09:48 +02:00
parent 74a2813fb4
commit 102465f9d1
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A

View file

@ -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>