mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-07 05:35:00 +03:00
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.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { useState } from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { expect, within, waitFor } from 'storybook/test';
|
||||
import { Button } from 'antd';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import { AntdRootConfig } from '@/i18n/AntdRootConfig';
|
||||
import { DBInbound } from '@/models/dbinbound';
|
||||
import { ThemeProvider } from '@/hooks/useTheme';
|
||||
import { setDatepicker } from '@/hooks/useDatepicker';
|
||||
|
||||
import InboundFormModal from './InboundFormModal';
|
||||
|
||||
const meta = {
|
||||
title: 'Inbounds/InboundFormModal',
|
||||
component: InboundFormModal,
|
||||
tags: ['autodocs'],
|
||||
beforeEach: () => {
|
||||
setDatepicker('gregorian');
|
||||
},
|
||||
args: {
|
||||
open: false,
|
||||
mode: 'add',
|
||||
dbInbound: null,
|
||||
dbInbounds: [],
|
||||
onClose: () => {},
|
||||
onSaved: () => {},
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
'The real inbound form. Exercise named options, keyboard navigation, validation and modal focus here; verify TalkBack/Chrome and NVDA separately before claiming screen-reader compatibility.',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof InboundFormModal>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
function InboundDemo({ edit = false }: { edit?: boolean }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [queryClient] = useState(
|
||||
() => new QueryClient({ defaultOptions: { queries: { retry: false } } }),
|
||||
);
|
||||
const [inbound] = useState(
|
||||
() =>
|
||||
new DBInbound({
|
||||
id: 41,
|
||||
protocol: 'shadowsocks',
|
||||
enable: true,
|
||||
port: 14443,
|
||||
settings: {
|
||||
method: 'chacha20-ietf-poly1305',
|
||||
password: 'example-password',
|
||||
network: 'tcp,udp',
|
||||
clients: [],
|
||||
},
|
||||
streamSettings: { network: 'tcp', security: 'none', tcpSettings: {} },
|
||||
}),
|
||||
);
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AntdRootConfig>
|
||||
<ThemeProvider>
|
||||
<Button onClick={() => setOpen(true)}>{edit ? 'Edit inbound' : 'Add inbound'}</Button>
|
||||
<InboundFormModal
|
||||
open={open}
|
||||
mode={edit ? 'edit' : 'add'}
|
||||
dbInbound={edit ? inbound : null}
|
||||
dbInbounds={[]}
|
||||
availableNodes={[]}
|
||||
onClose={() => setOpen(false)}
|
||||
onSaved={() => {}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</AntdRootConfig>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export const Create: Story = {
|
||||
render: () => <InboundDemo />,
|
||||
};
|
||||
|
||||
export const SelectableOptions: Story = {
|
||||
...Create,
|
||||
play: async ({ canvas, canvasElement, userEvent }) => {
|
||||
const page = within(canvasElement.ownerDocument.body);
|
||||
const opener = canvas.getByRole('button', { name: 'Add inbound' });
|
||||
await userEvent.click(opener);
|
||||
const dialog = await page.findByRole('dialog', { name: 'Add Inbound' });
|
||||
const protocol = within(dialog).getByRole('combobox', { name: 'Protocol' });
|
||||
await userEvent.selectOptions(
|
||||
protocol,
|
||||
within(protocol).getByRole('option', { name: 'shadowsocks' }),
|
||||
);
|
||||
await expect(protocol).toHaveDisplayValue('shadowsocks');
|
||||
await expect(protocol).toHaveFocus();
|
||||
await userEvent.click(within(dialog).getByRole('tab', { name: 'Protocol' }));
|
||||
const cipher = within(dialog).getByRole('combobox', { name: 'Encryption method' });
|
||||
await userEvent.selectOptions(
|
||||
cipher,
|
||||
within(cipher).getByRole('option', { name: 'chacha20-ietf-poly1305' }),
|
||||
);
|
||||
await userEvent.click(within(dialog).getByRole('tab', { name: 'Stream' }));
|
||||
const transport = within(dialog).getByRole('combobox', { name: 'Transmission' });
|
||||
await userEvent.selectOptions(
|
||||
transport,
|
||||
within(transport).getByRole('option', { name: 'WebSocket' }),
|
||||
);
|
||||
await expect(within(dialog).getByRole('textbox', { name: 'Path' })).toHaveValue('/');
|
||||
await userEvent.click(within(dialog).getAllByRole('button', { name: 'Close' })[0]);
|
||||
await waitFor(() => expect(opener).toHaveFocus());
|
||||
},
|
||||
};
|
||||
|
||||
export const Edit: Story = {
|
||||
render: () => <InboundDemo edit />,
|
||||
};
|
||||
@@ -1,8 +1,22 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useEffect, useRef, useState, useId } from 'react';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { Alert, Form, Input, InputNumber, Modal, Radio, Select, Switch, Tabs, message } from 'antd';
|
||||
import {
|
||||
Alert,
|
||||
ConfigProvider,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Radio,
|
||||
Select,
|
||||
Switch,
|
||||
Tabs,
|
||||
message,
|
||||
} from 'antd';
|
||||
import { Controller, FormProvider, useForm, useWatch } from 'react-hook-form';
|
||||
|
||||
import { HttpUtil, NumberFormatter, RandomUtil, SizeFormatter, Wireguard } from '@/utils';
|
||||
@@ -92,6 +106,28 @@ const SHARE_ADDR_STRATEGIES = ['node', 'listen', 'custom'] as const;
|
||||
const SHARE_ADDR_HOSTNAME_RE =
|
||||
/^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$/;
|
||||
|
||||
function inboundPopupContainer(trigger?: HTMLElement): HTMLElement {
|
||||
return (
|
||||
trigger?.closest<HTMLElement>('.ant-modal-body') ?? trigger?.parentElement ?? document.body
|
||||
);
|
||||
}
|
||||
|
||||
function loopInboundFocus(event: KeyboardEvent<HTMLDivElement>) {
|
||||
if (event.key !== 'Tab') return;
|
||||
const first = event.currentTarget.querySelector<HTMLButtonElement>(
|
||||
'.ant-modal-close:not(:disabled)',
|
||||
);
|
||||
const last = Array.from(
|
||||
event.currentTarget.querySelectorAll<HTMLButtonElement>(
|
||||
'.ant-modal-footer button:not(:disabled)',
|
||||
),
|
||||
).at(-1);
|
||||
if (first && last && event.target === (event.shiftKey ? first : last)) {
|
||||
event.preventDefault();
|
||||
(event.shiftKey ? last : first).focus();
|
||||
}
|
||||
}
|
||||
|
||||
function isValidShareAddrInput(value: string): boolean {
|
||||
const v = value.trim();
|
||||
if (v.length === 0) return true;
|
||||
@@ -677,7 +713,7 @@ export default function InboundFormModal({
|
||||
)}
|
||||
|
||||
<FormField name="protocol" label={t('pages.inbounds.protocol')}>
|
||||
<Select id="protocol" disabled={mode === 'edit'} options={PROTOCOL_OPTIONS} />
|
||||
<NativeSelect id="protocol" disabled={mode === 'edit'} options={PROTOCOL_OPTIONS} />
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
@@ -695,7 +731,7 @@ export default function InboundFormModal({
|
||||
label={t('pages.inbounds.form.shareAddrStrategy')}
|
||||
tooltip={t('pages.inbounds.form.shareAddrStrategyHelp')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={SHARE_ADDR_STRATEGIES.filter(
|
||||
(strategy) => strategy !== 'node' || nodeShareOptionAvailable,
|
||||
).map((strategy) => ({
|
||||
@@ -780,7 +816,7 @@ export default function InboundFormModal({
|
||||
</Form.Item>
|
||||
|
||||
<FormField name="trafficReset" label={t('pages.inbounds.periodicTrafficResetTitle')}>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={TRAFFIC_RESETS.map((r) => ({
|
||||
value: r,
|
||||
label: t(`pages.inbounds.periodicTrafficReset.${r}`),
|
||||
@@ -958,7 +994,7 @@ export default function InboundFormModal({
|
||||
<>
|
||||
{hasSelectableTransport && (
|
||||
<Form.Item htmlFor={`${uid}-network`} label={t('transmission')}>
|
||||
<Select
|
||||
<NativeSelect
|
||||
id={`${uid}-network`}
|
||||
style={{ width: '75%' }}
|
||||
value={network}
|
||||
@@ -1034,8 +1070,11 @@ export default function InboundFormModal({
|
||||
|
||||
const securityTab = (
|
||||
<>
|
||||
<Form.Item label={t('pages.inbounds.securityTab')}>
|
||||
<Form.Item
|
||||
label={<span id={`${uid}-security-label`}>{t('pages.inbounds.securityTab')}</span>}
|
||||
>
|
||||
<Radio.Group
|
||||
aria-labelledby={`${uid}-security-label`}
|
||||
value={security}
|
||||
buttonStyle="solid"
|
||||
disabled={!tlsOk}
|
||||
@@ -1174,7 +1213,7 @@ export default function InboundFormModal({
|
||||
const sniffingTab = <SniffingTab />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigProvider virtual={false} getPopupContainer={inboundPopupContainer}>
|
||||
{messageContextHolder}
|
||||
{modalContextHolder}
|
||||
<Modal
|
||||
@@ -1188,6 +1227,11 @@ export default function InboundFormModal({
|
||||
onOk={submit}
|
||||
onCancel={onClose}
|
||||
destroyOnHidden
|
||||
modalRender={(content) => (
|
||||
<div role="presentation" onKeyDown={loopInboundFocus}>
|
||||
{content}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<Form
|
||||
@@ -1273,6 +1317,6 @@ export default function InboundFormModal({
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</Modal>
|
||||
</>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, InputNumber, Select, Switch } from 'antd';
|
||||
import { Form, Input, InputNumber, Switch } from 'antd';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
@@ -60,7 +61,7 @@ export default function HysteriaFields() {
|
||||
{masq && (
|
||||
<>
|
||||
<FormField label={t('pages.inbounds.form.type')} name={[...MASQ_PATH, 'type']}>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'default (404 page)' },
|
||||
{ value: 'proxy', label: 'proxy (reverse proxy)' },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input, Select, Switch } from 'antd';
|
||||
import { Input, Switch } from 'antd';
|
||||
|
||||
import { FormField } from '@/components/form/rhf';
|
||||
import AccountsList from './accounts-list';
|
||||
@@ -10,7 +11,7 @@ export default function MixedFields({ mixedUdpOn }: { mixedUdpOn: boolean }) {
|
||||
<>
|
||||
<AccountsList />
|
||||
<FormField name={['settings', 'auth']} label={t('pages.inbounds.info.auth')}>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: 'noauth', label: 'noauth' },
|
||||
{ value: 'password', label: 'password' },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Input, Select, Space, Switch } from 'antd';
|
||||
import { Button, Form, Input, Space, Switch } from 'antd';
|
||||
import { ReloadOutlined } from '@ant-design/icons';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
@@ -25,7 +26,9 @@ export default function ShadowsocksFields({ isSSWith2022 }: ShadowsocksFieldsPro
|
||||
setValue('settings.password', RandomUtil.randomShadowsocksPassword(v as string));
|
||||
}}
|
||||
>
|
||||
<Select options={SSInboundMethodSchema.options.map((m) => ({ value: m, label: m }))} />
|
||||
<NativeSelect
|
||||
options={SSInboundMethodSchema.options.map((m) => ({ value: m, label: m }))}
|
||||
/>
|
||||
</FormField>
|
||||
{isSSWith2022 && (
|
||||
<Form.Item htmlFor={`${uid}-password`} label={t('password')}>
|
||||
@@ -48,7 +51,7 @@ export default function ShadowsocksFields({ isSSWith2022 }: ShadowsocksFieldsPro
|
||||
</Form.Item>
|
||||
)}
|
||||
<FormField name={['settings', 'network']} label={t('pages.inbounds.network')}>
|
||||
<Select
|
||||
<NativeSelect
|
||||
style={{ width: 120 }}
|
||||
options={[
|
||||
{ value: 'tcp,udp', label: 'TCP, UDP' },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useState, useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -111,7 +112,7 @@ export default function TuicFields() {
|
||||
label={t('pages.xray.tuic.logLevel')}
|
||||
tooltip={t('pages.xray.tuic.logLevelHint')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Warn', value: 'warn' },
|
||||
@@ -199,7 +200,7 @@ export default function TuicFields() {
|
||||
label={t('pages.xray.tuic.congestionControl')}
|
||||
tooltip={t('pages.xray.tuic.congestionControlHint')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ label: 'BBR', value: 'bbr' },
|
||||
{ label: 'CUBIC', value: 'cubic' },
|
||||
@@ -244,7 +245,7 @@ export default function TuicFields() {
|
||||
label={t('pages.xray.tuic.udpRelayMode')}
|
||||
tooltip={t('pages.xray.tuic.udpRelayModeHint')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ label: 'Native (Recommended)', value: 'native' },
|
||||
{ label: 'QUIC', value: 'quic' },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input, InputNumber, Select, Switch } from 'antd';
|
||||
import { Input, InputNumber, Switch } from 'antd';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
import { FormField } from '@/components/form/rhf';
|
||||
@@ -21,7 +22,7 @@ export default function TunnelFields() {
|
||||
name={['settings', 'allowedNetwork']}
|
||||
label={t('pages.inbounds.form.allowedNetwork')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: 'tcp,udp', label: 'TCP, UDP' },
|
||||
{ value: 'tcp', label: 'TCP' },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useState, useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Input, InputNumber, Select, Space, Typography } from 'antd';
|
||||
import { Button, Form, Input, InputNumber, Space, Typography } from 'antd';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
import { FormField } from '@/components/form/rhf';
|
||||
@@ -50,7 +51,7 @@ export default function VlessFields({
|
||||
</FormField>
|
||||
<Form.Item htmlFor={`${uid}-authKind`} label={t('pages.inbounds.vlessAuthGenerate')}>
|
||||
<Space size={8} wrap>
|
||||
<Select
|
||||
<NativeSelect
|
||||
id={`${uid}-authKind`}
|
||||
value={authKind}
|
||||
onChange={(v) => setAuthKind(v)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useState, useId } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -112,7 +113,9 @@ export default function RealityForm({
|
||||
name={['streamSettings', 'realitySettings', 'settings', 'fingerprint']}
|
||||
label="uTLS"
|
||||
>
|
||||
<Select options={Object.values(UTLS_FINGERPRINT).map((fp) => ({ value: fp, label: fp }))} />
|
||||
<NativeSelect
|
||||
options={Object.values(UTLS_FINGERPRINT).map((fp) => ({ value: fp, label: fp }))}
|
||||
/>
|
||||
</FormField>
|
||||
<Form.Item
|
||||
htmlFor={`${uid}-target`}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -154,7 +155,7 @@ function CertRow({
|
||||
name={['streamSettings', 'tlsSettings', 'certificates', index, 'usage']}
|
||||
label={t('pages.inbounds.form.usageOption')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
style={{ width: '50%' }}
|
||||
options={Object.values(USAGE_OPTION).map((u) => ({ value: u, label: u }))}
|
||||
/>
|
||||
@@ -212,7 +213,7 @@ function EchSockoptSection() {
|
||||
name={['streamSettings', 'tlsSettings', 'echSockopt', 'domainStrategy']}
|
||||
label={t('pages.xray.wireguard.domainStrategy')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={Object.values(DOMAIN_STRATEGY_OPTION).map((v) => ({ value: v, label: v }))}
|
||||
/>
|
||||
</FormField>
|
||||
@@ -354,14 +355,14 @@ export default function TlsForm({
|
||||
<Form.Item htmlFor={`${uid}-minVersion`} label={t('pages.inbounds.form.minMaxVersion')}>
|
||||
<Space.Compact block>
|
||||
<FormField name={['streamSettings', 'tlsSettings', 'minVersion']} noStyle>
|
||||
<Select
|
||||
<NativeSelect
|
||||
id={`${uid}-minVersion`}
|
||||
style={{ width: '50%' }}
|
||||
options={Object.values(TLS_VERSION_OPTION).map((v) => ({ value: v, label: v }))}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField name={['streamSettings', 'tlsSettings', 'maxVersion']} noStyle>
|
||||
<Select
|
||||
<NativeSelect
|
||||
aria-label={t('pages.inbounds.form.maxVersion')}
|
||||
style={{ width: '50%' }}
|
||||
options={Object.values(TLS_VERSION_OPTION).map((v) => ({ value: v, label: v }))}
|
||||
@@ -370,7 +371,7 @@ export default function TlsForm({
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
<FormField name={['streamSettings', 'tlsSettings', 'settings', 'fingerprint']} label="uTLS">
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'None' },
|
||||
...Object.values(UTLS_FINGERPRINT).map((fp) => ({ value: fp, label: fp })),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Alert, Form, InputNumber, Segmented, Select, Switch } from 'antd';
|
||||
@@ -207,13 +208,13 @@ export default function SockoptForm({
|
||||
name={['streamSettings', 'sockopt', 'tcpcongestion']}
|
||||
label={t('pages.inbounds.form.tcpCongestion')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
style={{ width: '50%' }}
|
||||
options={Object.values(TCP_CONGESTION_OPTION).map((c) => ({ value: c, label: c }))}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField name={['streamSettings', 'sockopt', 'tproxy']} label="TProxy">
|
||||
<Select
|
||||
<NativeSelect
|
||||
style={{ width: '50%' }}
|
||||
options={[
|
||||
{ value: 'off', label: 'Off' },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NativeSelect } from '@/components/form/NativeSelect';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AutoComplete, Input, InputNumber, Select, Switch } from 'antd';
|
||||
import { AutoComplete, Input, InputNumber, Switch } from 'antd';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
@@ -81,7 +82,7 @@ export default function XhttpForm() {
|
||||
name={['streamSettings', 'xhttpSettings', 'mode']}
|
||||
label={t('pages.inbounds.info.mode')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
style={{ width: '50%' }}
|
||||
options={(['auto', 'packet-up', 'stream-up', 'stream-one'] as const).map((m) => ({
|
||||
value: m,
|
||||
@@ -149,7 +150,7 @@ export default function XhttpForm() {
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
|
||||
label={t('pages.inbounds.form.uplinkHttpMethod')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'Default (POST)' },
|
||||
{ value: 'POST', label: 'POST' },
|
||||
@@ -187,7 +188,7 @@ export default function XhttpForm() {
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
|
||||
label={t('pages.inbounds.form.paddingPlacement')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'Default (queryInHeader)' },
|
||||
{ value: 'queryInHeader', label: 'queryInHeader' },
|
||||
@@ -201,7 +202,7 @@ export default function XhttpForm() {
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
|
||||
label={t('pages.inbounds.form.paddingMethod')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'Default (repeat-x)' },
|
||||
{ value: 'repeat-x', label: 'repeat-x' },
|
||||
@@ -215,7 +216,7 @@ export default function XhttpForm() {
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionIDPlacement']}
|
||||
label={t('pages.inbounds.form.sessionPlacement')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
@@ -259,7 +260,7 @@ export default function XhttpForm() {
|
||||
name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
|
||||
label={t('pages.inbounds.form.sequencePlacement')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
@@ -283,7 +284,7 @@ export default function XhttpForm() {
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
|
||||
label={t('pages.inbounds.form.uplinkDataPlacement')}
|
||||
>
|
||||
<Select
|
||||
<NativeSelect
|
||||
options={[
|
||||
{ value: '', label: 'Default (auto)' },
|
||||
{ value: 'auto', label: 'auto' },
|
||||
|
||||
Reference in New Issue
Block a user