mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
feat(frontend): security tab Reality + ECH + mldsa65 controls (Pattern A)
Adds the Reality sub-form and the four API-call buttons that drive
the server-generated material:
- genRealityKeypair calls /panel/api/server/getNewX25519Cert and writes
the result into ['streamSettings', 'realitySettings', 'privateKey']
and the nested settings.publicKey path.
- genMldsa65 calls /panel/api/server/getNewmldsa65 for the
post-quantum seed/verify pair.
- getNewEchCert calls /panel/api/server/getNewEchCert with the current
serverName and writes echServerKeys + settings.echConfigList.
- randomizeRealityTarget seeds target + serverNames from the random
reality-targets pool.
- randomizeShortIds calls RandomUtil.randomShortIds (comma-joined
string) and splits into the schema's string[] form.
Reality fields are bound directly to schema paths — show/xver/target,
maxTimediff, min/max ClientVer, the settings.{publicKey, fingerprint,
spiderX, mldsa65Verify} nested subtree, plus the array fields
(serverNames, shortIds) rendered as Select mode="tags" since both ship
as string[] on the wire.
TLS certificates list (Form.List with the useFile DU) still pending —
that's a chunky sub-form on its own.
This commit is contained in:
parent
534e954954
commit
8db1be8592
1 changed files with 215 additions and 0 deletions
|
|
@ -31,6 +31,7 @@ import {
|
||||||
isSS2022,
|
isSS2022,
|
||||||
} from '@/lib/xray/protocol-capabilities';
|
} from '@/lib/xray/protocol-capabilities';
|
||||||
import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
|
import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
|
||||||
|
import { getRandomRealityTarget } from '@/models/reality-targets';
|
||||||
import {
|
import {
|
||||||
InboundFormBaseSchema,
|
InboundFormBaseSchema,
|
||||||
InboundFormSchema,
|
InboundFormSchema,
|
||||||
|
|
@ -128,6 +129,80 @@ export default function InboundFormModalNew({
|
||||||
const tlsAllowed = canEnableTls({ protocol, streamSettings: { network, security } });
|
const tlsAllowed = canEnableTls({ protocol, streamSettings: { network, security } });
|
||||||
const realityAllowed = canEnableReality({ protocol, streamSettings: { network, security } });
|
const realityAllowed = canEnableReality({ protocol, streamSettings: { network, security } });
|
||||||
|
|
||||||
|
const genRealityKeypair = async () => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const msg = await HttpUtil.get('/panel/api/server/getNewX25519Cert');
|
||||||
|
if (msg?.success) {
|
||||||
|
const obj = msg.obj as { privateKey: string; publicKey: string };
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'privateKey'], obj.privateKey);
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'publicKey'], obj.publicKey);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearRealityKeypair = () => {
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'privateKey'], '');
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'publicKey'], '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const genMldsa65 = async () => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const msg = await HttpUtil.get('/panel/api/server/getNewmldsa65');
|
||||||
|
if (msg?.success) {
|
||||||
|
const obj = msg.obj as { seed: string; verify: string };
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'mldsa65Seed'], obj.seed);
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify'], obj.verify);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearMldsa65 = () => {
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'mldsa65Seed'], '');
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify'], '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const randomizeRealityTarget = () => {
|
||||||
|
const tgt = getRandomRealityTarget() as { target: string; sni: string };
|
||||||
|
form.setFieldValue(['streamSettings', 'realitySettings', 'target'], tgt.target);
|
||||||
|
form.setFieldValue(
|
||||||
|
['streamSettings', 'realitySettings', 'serverNames'],
|
||||||
|
tgt.sni.split(',').map((s) => s.trim()).filter(Boolean),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const randomizeShortIds = () => {
|
||||||
|
form.setFieldValue(
|
||||||
|
['streamSettings', 'realitySettings', 'shortIds'],
|
||||||
|
RandomUtil.randomShortIds().split(',').map((s) => s.trim()).filter(Boolean),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNewEchCert = async () => {
|
||||||
|
const sni = form.getFieldValue(['streamSettings', 'tlsSettings', 'serverName']);
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const msg = await HttpUtil.post('/panel/api/server/getNewEchCert', { sni });
|
||||||
|
if (msg?.success) {
|
||||||
|
const obj = msg.obj as { echServerKeys: string; echConfigList: string };
|
||||||
|
form.setFieldValue(['streamSettings', 'tlsSettings', 'echServerKeys'], obj.echServerKeys);
|
||||||
|
form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], obj.echConfigList);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearEchCert = () => {
|
||||||
|
form.setFieldValue(['streamSettings', 'tlsSettings', 'echServerKeys'], '');
|
||||||
|
form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], '');
|
||||||
|
};
|
||||||
|
|
||||||
const onSecurityChange = (next: string) => {
|
const onSecurityChange = (next: string) => {
|
||||||
const current = (form.getFieldValue('streamSettings') as Record<string, unknown>) ?? {};
|
const current = (form.getFieldValue('streamSettings') as Record<string, unknown>) ?? {};
|
||||||
const cleaned: Record<string, unknown> = { ...current, security: next };
|
const cleaned: Record<string, unknown> = { ...current, security: next };
|
||||||
|
|
@ -1480,6 +1555,146 @@ export default function InboundFormModalNew({
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name={['streamSettings', 'tlsSettings', 'echServerKeys']} label="ECH key">
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'tlsSettings', 'settings', 'echConfigList']}
|
||||||
|
label="ECH config"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label=" ">
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" loading={saving} onClick={getNewEchCert}>
|
||||||
|
Get New ECH Cert
|
||||||
|
</Button>
|
||||||
|
<Button danger onClick={clearEchCert}>Clear</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{security === 'reality' && (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'show']}
|
||||||
|
label="Show"
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={['streamSettings', 'realitySettings', 'xver']} label="Xver">
|
||||||
|
<InputNumber min={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'settings', 'fingerprint']}
|
||||||
|
label="uTLS"
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{Object.values(UTLS_FINGERPRINT).map((fp) => (
|
||||||
|
<Select.Option key={fp} value={fp}>{fp}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'target']}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
Target{' '}
|
||||||
|
<SyncOutlined className="random-icon" onClick={randomizeRealityTarget} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'serverNames']}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
SNI{' '}
|
||||||
|
<SyncOutlined className="random-icon" onClick={randomizeRealityTarget} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Select mode="tags" tokenSeparators={[',']} style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'maxTimediff']}
|
||||||
|
label="Max Time Diff (ms)"
|
||||||
|
>
|
||||||
|
<InputNumber min={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'minClientVer']}
|
||||||
|
label="Min Client Ver"
|
||||||
|
>
|
||||||
|
<Input placeholder="25.9.11" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'maxClientVer']}
|
||||||
|
label="Max Client Ver"
|
||||||
|
>
|
||||||
|
<Input placeholder="25.9.11" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'shortIds']}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
Short IDs{' '}
|
||||||
|
<SyncOutlined className="random-icon" onClick={randomizeShortIds} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Select mode="tags" tokenSeparators={[',']} style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'settings', 'spiderX']}
|
||||||
|
label="SpiderX"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'settings', 'publicKey']}
|
||||||
|
label={t('pages.inbounds.publicKey')}
|
||||||
|
>
|
||||||
|
<Input.TextArea autoSize={{ minRows: 1, maxRows: 4 }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'privateKey']}
|
||||||
|
label={t('pages.inbounds.privatekey')}
|
||||||
|
>
|
||||||
|
<Input.TextArea autoSize={{ minRows: 1, maxRows: 4 }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label=" ">
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" loading={saving} onClick={genRealityKeypair}>
|
||||||
|
Get New Cert
|
||||||
|
</Button>
|
||||||
|
<Button danger onClick={clearRealityKeypair}>Clear</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'mldsa65Seed']}
|
||||||
|
label="mldsa65 Seed"
|
||||||
|
>
|
||||||
|
<Input.TextArea autoSize={{ minRows: 2, maxRows: 6 }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify']}
|
||||||
|
label="mldsa65 Verify"
|
||||||
|
>
|
||||||
|
<Input.TextArea autoSize={{ minRows: 2, maxRows: 6 }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label=" ">
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" loading={saving} onClick={genMldsa65}>
|
||||||
|
Get New Seed
|
||||||
|
</Button>
|
||||||
|
<Button danger onClick={clearMldsa65}>Clear</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue