Files
3x-ui/frontend/src/pages/inbounds/form/protocols/hysteria.tsx
T
Hamed f4030c889c fix(frontend): make inbound forms accessible to screen readers (#7025)
Expose selectable options and selected values, associate validation guidance with controls, and contain keyboard focus in inbound dialogs. Preserve create and edit serialization and add Chromium keyboard and accessibility checks.
2026-11-05 18:08:49 +01:00

127 lines
4.4 KiB
TypeScript

import { NativeSelect } from '@/components/form/NativeSelect';
import { useId } from 'react';
import { useTranslation } from 'react-i18next';
import { Form, Input, InputNumber, Switch } from 'antd';
import { useFormContext, useWatch } from 'react-hook-form';
import { HeaderMapEditor } from '@/components/form';
import { FormField } from '@/components/form/rhf';
const MASQ_PATH = ['streamSettings', 'hysteriaSettings', 'masquerade'];
export default function HysteriaFields() {
const { t } = useTranslation();
const uid = useId();
const { control, setValue } = useFormContext();
const masq = useWatch({ control, name: 'streamSettings.hysteriaSettings.masquerade' }) as
| { type?: string }
| undefined;
const masqType = useWatch({
control,
name: 'streamSettings.hysteriaSettings.masquerade.type',
}) as string | undefined;
return (
<>
<FormField
label={t('pages.inbounds.form.version')}
name={['streamSettings', 'hysteriaSettings', 'version']}
>
<InputNumber min={2} max={2} disabled />
</FormField>
<FormField
label={t('pages.inbounds.form.udpIdleTimeout')}
name={['streamSettings', 'hysteriaSettings', 'udpIdleTimeout']}
>
<InputNumber min={2} max={600} style={{ width: '100%' }} />
</FormField>
<Form.Item htmlFor={`${uid}-masquerade`} label={t('pages.inbounds.form.masquerade')}>
<Switch
id={`${uid}-masquerade`}
checked={!!masq}
onChange={(checked) =>
setValue(
'streamSettings.hysteriaSettings.masquerade',
checked
? {
type: '',
dir: '',
url: '',
rewriteHost: false,
insecure: false,
content: '',
headers: {},
statusCode: 0,
}
: undefined,
)
}
/>
</Form.Item>
{masq && (
<>
<FormField label={t('pages.inbounds.form.type')} name={[...MASQ_PATH, 'type']}>
<NativeSelect
options={[
{ value: '', label: 'default (404 page)' },
{ value: 'proxy', label: 'proxy (reverse proxy)' },
{ value: 'file', label: 'file (serve directory)' },
{ value: 'string', label: 'string (fixed body)' },
]}
/>
</FormField>
{masqType === 'proxy' && (
<>
<FormField label={t('pages.inbounds.form.upstreamUrl')} name={[...MASQ_PATH, 'url']}>
<Input placeholder="https://www.example.com" />
</FormField>
<FormField
label={t('pages.inbounds.form.rewriteHost')}
name={[...MASQ_PATH, 'rewriteHost']}
valueProp="checked"
>
<Switch />
</FormField>
<FormField
label={t('pages.inbounds.form.xForwarded')}
name={[...MASQ_PATH, 'xForwarded']}
valueProp="checked"
>
<Switch />
</FormField>
<FormField
label={t('pages.inbounds.form.skipTlsVerify')}
name={[...MASQ_PATH, 'insecure']}
valueProp="checked"
>
<Switch />
</FormField>
</>
)}
{masqType === 'file' && (
<FormField label={t('pages.inbounds.form.directory')} name={[...MASQ_PATH, 'dir']}>
<Input placeholder="/var/www/html" />
</FormField>
)}
{masqType === 'string' && (
<>
<FormField
label={t('pages.inbounds.form.statusCode')}
name={[...MASQ_PATH, 'statusCode']}
>
<InputNumber min={0} max={599} style={{ width: '100%' }} />
</FormField>
<FormField label={t('pages.inbounds.form.body')} name={[...MASQ_PATH, 'content']}>
<Input.TextArea autoSize={{ minRows: 3 }} />
</FormField>
<FormField label={t('pages.inbounds.form.headers')} name={[...MASQ_PATH, 'headers']}>
<HeaderMapEditor mode="v1" />
</FormField>
</>
)}
</>
)}
</>
);
}