feat(tls): add a dynamic DoH helper for echConfigList, with CDN guidance (#6869)

echConfigList already accepted Xray's dynamic form, target domain + DoH URL,
but nothing in the inbound or host forms explained or built it. A helper now
builds that value from presets or a custom domain and resolver, accepts only the
schemes Xray dials (https, h2c, udp; scheme case normalised), warns that a key
generated locally does not work behind a CDN, and asks before overwriting local
ECH keys. The helper's controls carry accessible labels, and the resolver list
holds only identified public resolvers.
This commit is contained in:
55575455
2026-10-24 12:07:11 +08:00
committed by GitHub
parent 41bafb3fd5
commit f43645656c
21 changed files with 1380 additions and 53 deletions
+41 -3
View File
@@ -1,6 +1,17 @@
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Form, Input, InputNumber, Modal, Select, Switch, Tabs, message } from 'antd';
import {
Alert,
Form,
Input,
InputNumber,
Modal,
Select,
Switch,
Tabs,
Typography,
message,
} from 'antd';
import {
ProfileOutlined,
SafetyCertificateOutlined,
@@ -18,6 +29,7 @@ import { BulkAddHostSchema, type BulkAddHostValues } from '@/schemas/api/host';
import type { InboundOption } from '@/schemas/client';
import { ALPN_OPTION, UTLS_FINGERPRINT } from '@/schemas/primitives';
import { CipherSuitesSelect } from '@/components/form';
import DynamicEchHelper from '@/components/form/DynamicEchHelper';
import { FormField, rhfZodValidate } from '@/components/form/rhf';
import { useNodesQuery } from '@/api/queries/useNodesQuery';
import { useMediaQuery } from '@/hooks/useMediaQuery';
@@ -94,6 +106,8 @@ export default function HostFormModal({
const [loading, setLoading] = useState(false);
const security = (useWatch({ control: methods.control, name: 'security' }) ?? 'same') as string;
const hostEchConfig = useWatch({ control: methods.control, name: 'echConfigList' }) ?? '';
const hostSni = useWatch({ control: methods.control, name: 'sni' }) ?? '';
const showTls = security === 'tls' || security === 'reality' || security === 'same';
const showTlsExtras = security === 'tls' || security === 'same';
const watchedHosts = useWatch({ control: methods.control, name: 'hosts' });
@@ -372,10 +386,34 @@ export default function HostFormModal({
</FormField>
<FormField
name="echConfigList"
label={t('pages.hosts.fields.echConfigList')}
label={t('pages.inbounds.form.echConfigList')}
extra={
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{t('pages.inbounds.form.echConfigListHint')}
</Typography.Text>
}
>
<Input.TextArea rows={2} />
<Input.TextArea
rows={2}
placeholder={t('pages.inbounds.form.echDynamicPlaceholder')}
/>
</FormField>
<Form.Item label=" " style={{ marginBottom: 8 }}>
<DynamicEchHelper
value={hostEchConfig}
sni={hostSni}
onApply={(next) =>
methods.setValue('echConfigList', next, { shouldDirty: true })
}
/>
</Form.Item>
<Alert
type="info"
showIcon
data-testid="ech-cdn-warning"
title={t('pages.inbounds.form.echCdnWarningTitle')}
description={t('pages.inbounds.form.echCdnWarningBody')}
/>
</>
)}
</>