mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-12 16:15:02 +03:00
1bdb517b88
Ant Design's Spin wraps its content in aria-live="polite", so every WebSocket tick on the dashboard, inbounds, clients and nodes pages was announced, and only the changed number was read, with no label. Pass aria-live="off" on every Spin that wraps content; aria-busy still signals loading. Each vital tile is now a group named by its metric, so "47.2 %" is read as CPU. rc-select renders each tag's remove "x" as an aria-hidden span with no tabIndex, so screen-reader users could only Backspace the last value away, never remove one SNI or inbound from the middle. selectTagRender swaps in a real button named "Remove <value>" and moves focus to the next tag after removal. It is applied to every tags/multiple Select. maxTagCount="responsive" is dropped because it hid selected inbounds behind "+N", where no screen reader could reach them. Two oxlint rules (antd-a11y/spin-content-not-live and antd-a11y/select-tag-remove) keep new code from regressing either fix.
89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import { Form, Select, Switch } from 'antd';
|
|
import type { FormInstance } from 'antd/es/form';
|
|
|
|
import { selectTagRender } from '@/components/form/SelectTag';
|
|
import { SNIFFING_OPTION } from '@/schemas/primitives';
|
|
|
|
const DEST_OPTIONS = Object.entries(SNIFFING_OPTION).map(([label, value]) => ({ value, label }));
|
|
|
|
export interface SniffingFieldsProps {
|
|
// Base path to the sniffing object in the form, e.g. ['sniffing'] (inbound),
|
|
// ['settings', 'reverseSniffing'] (VLESS reverse), ['settings', 'sniffing']
|
|
// (loopback). All sub-fields hang off this path.
|
|
name: (string | number)[];
|
|
form: FormInstance;
|
|
// Label for the enable toggle — Enable / Reverse Sniffing / Sniffing differ
|
|
// per host.
|
|
enableLabel: string;
|
|
}
|
|
|
|
// Shared sniffing form fragment used everywhere the panel edits an xray
|
|
// SniffingConfig: the inbound Sniffing tab, VLESS reverse sniffing, and the
|
|
// loopback outbound. Renders the enable toggle plus the destOverride /
|
|
// metadataOnly / routeOnly / excluded fields when enabled.
|
|
export default function SniffingFields({ name, form, enableLabel }: SniffingFieldsProps) {
|
|
const { t } = useTranslation();
|
|
const enabled = Form.useWatch([...name, 'enabled'], form) ?? false;
|
|
|
|
return (
|
|
<>
|
|
<Form.Item label={enableLabel} name={[...name, 'enabled']} valuePropName="checked">
|
|
<Switch />
|
|
</Form.Item>
|
|
|
|
{enabled && (
|
|
<>
|
|
<Form.Item name={[...name, 'destOverride']} wrapperCol={{ md: { span: 14, offset: 8 } }}>
|
|
<Select
|
|
mode="multiple"
|
|
tagRender={selectTagRender}
|
|
className="sniffing-options"
|
|
aria-label={t('pages.inbounds.sniffingDestOverride')}
|
|
options={DEST_OPTIONS}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t('pages.inbounds.sniffingMetadataOnly')}
|
|
name={[...name, 'metadataOnly']}
|
|
valuePropName="checked"
|
|
>
|
|
<Switch />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t('pages.inbounds.sniffingRouteOnly')}
|
|
name={[...name, 'routeOnly']}
|
|
valuePropName="checked"
|
|
>
|
|
<Switch />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t('pages.inbounds.sniffingIpsExcluded')}
|
|
name={[...name, 'ipsExcluded']}
|
|
>
|
|
<Select
|
|
mode="tags"
|
|
tagRender={selectTagRender}
|
|
tokenSeparators={[',']}
|
|
placeholder="IP/CIDR/geoip:*/ext:*"
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t('pages.inbounds.sniffingDomainsExcluded')}
|
|
name={[...name, 'domainsExcluded']}
|
|
>
|
|
<Select
|
|
mode="tags"
|
|
tagRender={selectTagRender}
|
|
tokenSeparators={[',']}
|
|
placeholder="domain:*/ext:*"
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</Form.Item>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
}
|