|
@@ -1,10 +1,12 @@
|
|
|
import { useEffect, useRef } from 'react';
|
|
import { useEffect, useRef } from 'react';
|
|
|
import { AutoComplete, Button, Divider, Form, Input, InputNumber, Select, Space, Switch } from 'antd';
|
|
import { AutoComplete, Button, Divider, Form, Input, InputNumber, Select, Space, Switch } from 'antd';
|
|
|
import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
|
|
|
+import { useTranslation } from 'react-i18next';
|
|
|
import type { FormInstance } from 'antd/es/form';
|
|
import type { FormInstance } from 'antd/es/form';
|
|
|
import type { NamePath } from 'antd/es/form/interface';
|
|
import type { NamePath } from 'antd/es/form/interface';
|
|
|
|
|
|
|
|
import { RandomUtil } from '@/utils';
|
|
import { RandomUtil } from '@/utils';
|
|
|
|
|
+import { activateOnKey } from '@/utils/a11y';
|
|
|
import { OutboundProtocols, UTLS_FINGERPRINT } from '@/schemas/primitives';
|
|
import { OutboundProtocols, UTLS_FINGERPRINT } from '@/schemas/primitives';
|
|
|
|
|
|
|
|
const UTLS_FINGERPRINT_OPTIONS = Object.values(UTLS_FINGERPRINT).map((value) => ({ value, label: value }));
|
|
const UTLS_FINGERPRINT_OPTIONS = Object.values(UTLS_FINGERPRINT).map((value) => ({ value, label: value }));
|
|
@@ -224,6 +226,7 @@ export default function FinalMaskForm({ name, network, protocol, form, showAll =
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function TcpMasksList({ base, form }: { base: (string | number)[]; form: FormInstance }) {
|
|
function TcpMasksList({ base, form }: { base: (string | number)[]; form: FormInstance }) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
return (
|
|
return (
|
|
|
<Form.List name={[...base, 'tcp']}>
|
|
<Form.List name={[...base, 'tcp']}>
|
|
|
{(fields, { add, remove }) => (
|
|
{(fields, { add, remove }) => (
|
|
@@ -233,6 +236,7 @@ function TcpMasksList({ base, form }: { base: (string | number)[]; form: FormIns
|
|
|
type="primary"
|
|
type="primary"
|
|
|
size="small"
|
|
size="small"
|
|
|
icon={<PlusOutlined />}
|
|
icon={<PlusOutlined />}
|
|
|
|
|
+ aria-label={t('add')}
|
|
|
onClick={() => add({ type: 'fragment', settings: defaultTcpMaskSettings('fragment') })}
|
|
onClick={() => add({ type: 'fragment', settings: defaultTcpMaskSettings('fragment') })}
|
|
|
/>
|
|
/>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
@@ -265,12 +269,20 @@ function TcpMaskItem({
|
|
|
// type change). All Form.Item `name=` use RELATIVE paths within the
|
|
// type change). All Form.Item `name=` use RELATIVE paths within the
|
|
|
// outer Form.List context.
|
|
// outer Form.List context.
|
|
|
const absolutePath = [...listPath, fieldName];
|
|
const absolutePath = [...listPath, fieldName];
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div>
|
|
<div>
|
|
|
<Divider style={{ margin: 0 }}>
|
|
<Divider style={{ margin: 0 }}>
|
|
|
TCP Mask {displayIndex}
|
|
TCP Mask {displayIndex}
|
|
|
- <DeleteOutlined className="danger-icon" onClick={onRemove} />
|
|
|
|
|
|
|
+ <DeleteOutlined
|
|
|
|
|
+ className="danger-icon"
|
|
|
|
|
+ role="button"
|
|
|
|
|
+ tabIndex={0}
|
|
|
|
|
+ aria-label={t('remove')}
|
|
|
|
|
+ onClick={onRemove}
|
|
|
|
|
+ onKeyDown={activateOnKey(onRemove)}
|
|
|
|
|
+ />
|
|
|
</Divider>
|
|
</Divider>
|
|
|
|
|
|
|
|
<Form.Item label="Type" name={[fieldName, 'type']}>
|
|
<Form.Item label="Type" name={[fieldName, 'type']}>
|
|
@@ -415,12 +427,13 @@ function FragmentRangeList({
|
|
|
validator?: (rule: unknown, value: unknown) => Promise<void>;
|
|
validator?: (rule: unknown, value: unknown) => Promise<void>;
|
|
|
minItems?: number;
|
|
minItems?: number;
|
|
|
}) {
|
|
}) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
return (
|
|
return (
|
|
|
<Form.List name={listName}>
|
|
<Form.List name={listName}>
|
|
|
{(fields, { add, remove }) => (
|
|
{(fields, { add, remove }) => (
|
|
|
<>
|
|
<>
|
|
|
<Form.Item label={label}>
|
|
<Form.Item label={label}>
|
|
|
- <Button type="primary" size="small" icon={<PlusOutlined />} onClick={() => add('')} />
|
|
|
|
|
|
|
+ <Button type="primary" size="small" icon={<PlusOutlined />} aria-label={t('add')} onClick={() => add('')} />
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
{fields.map((field, idx) => (
|
|
{fields.map((field, idx) => (
|
|
|
<Form.Item
|
|
<Form.Item
|
|
@@ -431,8 +444,17 @@ function FragmentRangeList({
|
|
|
>
|
|
>
|
|
|
<Input
|
|
<Input
|
|
|
placeholder={placeholder}
|
|
placeholder={placeholder}
|
|
|
- addonAfter={fields.length > minItems
|
|
|
|
|
- ? <DeleteOutlined className="danger-icon" onClick={() => remove(field.name)} />
|
|
|
|
|
|
|
+ suffix={fields.length > minItems
|
|
|
|
|
+ ? (
|
|
|
|
|
+ <DeleteOutlined
|
|
|
|
|
+ className="danger-icon"
|
|
|
|
|
+ role="button"
|
|
|
|
|
+ tabIndex={0}
|
|
|
|
|
+ aria-label={t('remove')}
|
|
|
|
|
+ onClick={() => remove(field.name)}
|
|
|
|
|
+ onKeyDown={activateOnKey(() => remove(field.name))}
|
|
|
|
|
+ />
|
|
|
|
|
+ )
|
|
|
: null}
|
|
: null}
|
|
|
/>
|
|
/>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
@@ -475,6 +497,7 @@ function HeaderCustomGroups({
|
|
|
form: FormInstance;
|
|
form: FormInstance;
|
|
|
absoluteSettingsPath: (string | number)[];
|
|
absoluteSettingsPath: (string | number)[];
|
|
|
}) {
|
|
}) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
{(['clients', 'servers'] as const).map((groupKey) => (
|
|
{(['clients', 'servers'] as const).map((groupKey) => (
|
|
@@ -486,6 +509,7 @@ function HeaderCustomGroups({
|
|
|
type="primary"
|
|
type="primary"
|
|
|
size="small"
|
|
size="small"
|
|
|
icon={<PlusOutlined />}
|
|
icon={<PlusOutlined />}
|
|
|
|
|
+ aria-label={t('add')}
|
|
|
onClick={() => addGroup([defaultClientServerItem()])}
|
|
onClick={() => addGroup([defaultClientServerItem()])}
|
|
|
/>
|
|
/>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
@@ -493,7 +517,14 @@ function HeaderCustomGroups({
|
|
|
<div key={group.key}>
|
|
<div key={group.key}>
|
|
|
<Divider style={{ margin: 0 }}>
|
|
<Divider style={{ margin: 0 }}>
|
|
|
{groupKey === 'clients' ? 'Clients' : 'Servers'} Group {gi + 1}
|
|
{groupKey === 'clients' ? 'Clients' : 'Servers'} Group {gi + 1}
|
|
|
- <DeleteOutlined className="danger-icon" onClick={() => removeGroup(group.name)} />
|
|
|
|
|
|
|
+ <DeleteOutlined
|
|
|
|
|
+ className="danger-icon"
|
|
|
|
|
+ role="button"
|
|
|
|
|
+ tabIndex={0}
|
|
|
|
|
+ aria-label={t('remove')}
|
|
|
|
|
+ onClick={() => removeGroup(group.name)}
|
|
|
|
|
+ onKeyDown={activateOnKey(() => removeGroup(group.name))}
|
|
|
|
|
+ />
|
|
|
</Divider>
|
|
</Divider>
|
|
|
<Form.List name={[group.name]}>
|
|
<Form.List name={[group.name]}>
|
|
|
{(items, { add: addItem, remove: removeItem }) => (
|
|
{(items, { add: addItem, remove: removeItem }) => (
|
|
@@ -502,6 +533,7 @@ function HeaderCustomGroups({
|
|
|
<Button
|
|
<Button
|
|
|
size="small"
|
|
size="small"
|
|
|
icon={<PlusOutlined />}
|
|
icon={<PlusOutlined />}
|
|
|
|
|
+ aria-label={t('add')}
|
|
|
onClick={() => addItem(defaultClientServerItem())}
|
|
onClick={() => addItem(defaultClientServerItem())}
|
|
|
/>
|
|
/>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
@@ -531,6 +563,7 @@ function HeaderCustomGroups({
|
|
|
function UdpMasksList({
|
|
function UdpMasksList({
|
|
|
base, form, isHysteria, isWireguard, network,
|
|
base, form, isHysteria, isWireguard, network,
|
|
|
}: { base: (string | number)[]; form: FormInstance; isHysteria: boolean; isWireguard: boolean; network: string }) {
|
|
}: { base: (string | number)[]; form: FormInstance; isHysteria: boolean; isWireguard: boolean; network: string }) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
return (
|
|
return (
|
|
|
<Form.List name={[...base, 'udp']}>
|
|
<Form.List name={[...base, 'udp']}>
|
|
|
{(fields, { add, remove }) => (
|
|
{(fields, { add, remove }) => (
|
|
@@ -540,6 +573,7 @@ function UdpMasksList({
|
|
|
type="primary"
|
|
type="primary"
|
|
|
size="small"
|
|
size="small"
|
|
|
icon={<PlusOutlined />}
|
|
icon={<PlusOutlined />}
|
|
|
|
|
+ aria-label={t('add')}
|
|
|
onClick={() => {
|
|
onClick={() => {
|
|
|
const def = isHysteria || isWireguard ? 'salamander' : 'mkcp-legacy';
|
|
const def = isHysteria || isWireguard ? 'salamander' : 'mkcp-legacy';
|
|
|
add({ type: def, settings: defaultUdpMaskSettings(def) });
|
|
add({ type: def, settings: defaultUdpMaskSettings(def) });
|
|
@@ -578,6 +612,7 @@ function UdpMaskItem({
|
|
|
onRemove: () => void;
|
|
onRemove: () => void;
|
|
|
}) {
|
|
}) {
|
|
|
const absolutePath = [...listPath, fieldName];
|
|
const absolutePath = [...listPath, fieldName];
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
|
|
|
|
|
const onTypeChange = (v: string) => {
|
|
const onTypeChange = (v: string) => {
|
|
|
form.setFieldValue([...absolutePath, 'settings'], defaultUdpMaskSettings(v));
|
|
form.setFieldValue([...absolutePath, 'settings'], defaultUdpMaskSettings(v));
|
|
@@ -605,7 +640,14 @@ function UdpMaskItem({
|
|
|
<div>
|
|
<div>
|
|
|
<Divider style={{ margin: 0 }}>
|
|
<Divider style={{ margin: 0 }}>
|
|
|
UDP Mask {displayIndex}
|
|
UDP Mask {displayIndex}
|
|
|
- <DeleteOutlined className="danger-icon" onClick={onRemove} />
|
|
|
|
|
|
|
+ <DeleteOutlined
|
|
|
|
|
+ className="danger-icon"
|
|
|
|
|
+ role="button"
|
|
|
|
|
+ tabIndex={0}
|
|
|
|
|
+ aria-label={t('remove')}
|
|
|
|
|
+ onClick={onRemove}
|
|
|
|
|
+ onKeyDown={activateOnKey(onRemove)}
|
|
|
|
|
+ />
|
|
|
</Divider>
|
|
</Divider>
|
|
|
|
|
|
|
|
<Form.Item label="Type" name={[fieldName, 'type']}>
|
|
<Form.Item label="Type" name={[fieldName, 'type']}>
|
|
@@ -735,6 +777,7 @@ function SalamanderUdpMaskSettings({
|
|
|
form: FormInstance;
|
|
form: FormInstance;
|
|
|
absolutePath: (string | number)[];
|
|
absolutePath: (string | number)[];
|
|
|
}) {
|
|
}) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
const packetSizePath = [...absolutePath, 'settings', 'packetSize'];
|
|
const packetSizePath = [...absolutePath, 'settings', 'packetSize'];
|
|
|
const packetSize = Form.useWatch(packetSizePath, { form, preserve: true });
|
|
const packetSize = Form.useWatch(packetSizePath, { form, preserve: true });
|
|
|
const mode = typeof packetSize === 'string' && packetSize.trim() !== '' ? 'gecko' : 'salamander';
|
|
const mode = typeof packetSize === 'string' && packetSize.trim() !== '' ? 'gecko' : 'salamander';
|
|
@@ -776,6 +819,7 @@ function SalamanderUdpMaskSettings({
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
<Button
|
|
<Button
|
|
|
icon={<ReloadOutlined />}
|
|
icon={<ReloadOutlined />}
|
|
|
|
|
+ aria-label={t('regenerate')}
|
|
|
onClick={() => form.setFieldValue(
|
|
onClick={() => form.setFieldValue(
|
|
|
[...absolutePath, 'settings', 'password'],
|
|
[...absolutePath, 'settings', 'password'],
|
|
|
RandomUtil.randomLowerAndNum(16),
|
|
RandomUtil.randomLowerAndNum(16),
|
|
@@ -810,7 +854,7 @@ function GeckoPacketSizeInput({
|
|
|
return (
|
|
return (
|
|
|
<Space.Compact block>
|
|
<Space.Compact block>
|
|
|
<InputNumber
|
|
<InputNumber
|
|
|
- addonBefore="Min"
|
|
|
|
|
|
|
+ prefix="Min"
|
|
|
min={GECKO_MIN_PACKET_SIZE}
|
|
min={GECKO_MIN_PACKET_SIZE}
|
|
|
max={GECKO_MAX_PACKET_SIZE}
|
|
max={GECKO_MAX_PACKET_SIZE}
|
|
|
precision={0}
|
|
precision={0}
|
|
@@ -820,7 +864,7 @@ function GeckoPacketSizeInput({
|
|
|
style={{ width: '50%' }}
|
|
style={{ width: '50%' }}
|
|
|
/>
|
|
/>
|
|
|
<InputNumber
|
|
<InputNumber
|
|
|
- addonBefore="Max"
|
|
|
|
|
|
|
+ prefix="Max"
|
|
|
min={GECKO_MIN_PACKET_SIZE}
|
|
min={GECKO_MIN_PACKET_SIZE}
|
|
|
max={GECKO_MAX_PACKET_SIZE}
|
|
max={GECKO_MAX_PACKET_SIZE}
|
|
|
precision={0}
|
|
precision={0}
|
|
@@ -840,6 +884,7 @@ function UdpHeaderCustom({
|
|
|
form: FormInstance;
|
|
form: FormInstance;
|
|
|
absoluteSettingsPath: (string | number)[];
|
|
absoluteSettingsPath: (string | number)[];
|
|
|
}) {
|
|
}) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
{(['client', 'server'] as const).map((groupKey) => (
|
|
{(['client', 'server'] as const).map((groupKey) => (
|
|
@@ -851,6 +896,7 @@ function UdpHeaderCustom({
|
|
|
type="primary"
|
|
type="primary"
|
|
|
size="small"
|
|
size="small"
|
|
|
icon={<PlusOutlined />}
|
|
icon={<PlusOutlined />}
|
|
|
|
|
+ aria-label={t('add')}
|
|
|
onClick={() => add(defaultUdpClientServerItem())}
|
|
onClick={() => add(defaultUdpClientServerItem())}
|
|
|
/>
|
|
/>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
@@ -858,7 +904,14 @@ function UdpHeaderCustom({
|
|
|
<div key={item.key}>
|
|
<div key={item.key}>
|
|
|
<Divider style={{ margin: 0 }}>
|
|
<Divider style={{ margin: 0 }}>
|
|
|
{groupKey === 'client' ? 'Client' : 'Server'} {ci + 1}
|
|
{groupKey === 'client' ? 'Client' : 'Server'} {ci + 1}
|
|
|
- <DeleteOutlined className="danger-icon" onClick={() => remove(item.name)} />
|
|
|
|
|
|
|
+ <DeleteOutlined
|
|
|
|
|
+ className="danger-icon"
|
|
|
|
|
+ role="button"
|
|
|
|
|
+ tabIndex={0}
|
|
|
|
|
+ aria-label={t('remove')}
|
|
|
|
|
+ onClick={() => remove(item.name)}
|
|
|
|
|
+ onKeyDown={activateOnKey(() => remove(item.name))}
|
|
|
|
|
+ />
|
|
|
</Divider>
|
|
</Divider>
|
|
|
<ItemEditor
|
|
<ItemEditor
|
|
|
fieldName={item.name}
|
|
fieldName={item.name}
|
|
@@ -883,6 +936,7 @@ function NoiseItems({
|
|
|
form: FormInstance;
|
|
form: FormInstance;
|
|
|
absoluteSettingsPath: (string | number)[];
|
|
absoluteSettingsPath: (string | number)[];
|
|
|
}) {
|
|
}) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
<Form.Item label="Reset" name={[udpFieldName, 'settings', 'reset']}>
|
|
<Form.Item label="Reset" name={[udpFieldName, 'settings', 'reset']}>
|
|
@@ -896,6 +950,7 @@ function NoiseItems({
|
|
|
type="primary"
|
|
type="primary"
|
|
|
size="small"
|
|
size="small"
|
|
|
icon={<PlusOutlined />}
|
|
icon={<PlusOutlined />}
|
|
|
|
|
+ aria-label={t('add')}
|
|
|
onClick={() => add(defaultNoiseItem())}
|
|
onClick={() => add(defaultNoiseItem())}
|
|
|
/>
|
|
/>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
@@ -903,7 +958,14 @@ function NoiseItems({
|
|
|
<div key={item.key}>
|
|
<div key={item.key}>
|
|
|
<Divider style={{ margin: 0 }}>
|
|
<Divider style={{ margin: 0 }}>
|
|
|
Noise {ni + 1}
|
|
Noise {ni + 1}
|
|
|
- <DeleteOutlined className="danger-icon" onClick={() => remove(item.name)} />
|
|
|
|
|
|
|
+ <DeleteOutlined
|
|
|
|
|
+ className="danger-icon"
|
|
|
|
|
+ role="button"
|
|
|
|
|
+ tabIndex={0}
|
|
|
|
|
+ aria-label={t('remove')}
|
|
|
|
|
+ onClick={() => remove(item.name)}
|
|
|
|
|
+ onKeyDown={activateOnKey(() => remove(item.name))}
|
|
|
|
|
+ />
|
|
|
</Divider>
|
|
</Divider>
|
|
|
<ItemEditor
|
|
<ItemEditor
|
|
|
fieldName={item.name}
|
|
fieldName={item.name}
|
|
@@ -930,6 +992,7 @@ function ItemEditor({
|
|
|
delayMode?: 'number' | 'string';
|
|
delayMode?: 'number' | 'string';
|
|
|
onRemove?: () => void;
|
|
onRemove?: () => void;
|
|
|
}) {
|
|
}) {
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
const onTypeChange = (v: string) => {
|
|
const onTypeChange = (v: string) => {
|
|
|
if (v === 'base64') {
|
|
if (v === 'base64') {
|
|
|
form.setFieldValue([...absoluteItemPath, 'packet'], RandomUtil.randomBase64());
|
|
form.setFieldValue([...absoluteItemPath, 'packet'], RandomUtil.randomBase64());
|
|
@@ -1005,6 +1068,7 @@ function ItemEditor({
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
<Button
|
|
<Button
|
|
|
icon={<ReloadOutlined />}
|
|
icon={<ReloadOutlined />}
|
|
|
|
|
+ aria-label={t('regenerate')}
|
|
|
onClick={() => form.setFieldValue([...absoluteItemPath, 'packet'], RandomUtil.randomBase64())}
|
|
onClick={() => form.setFieldValue([...absoluteItemPath, 'packet'], RandomUtil.randomBase64())}
|
|
|
/>
|
|
/>
|
|
|
</Space.Compact>
|
|
</Space.Compact>
|