|
|
@@ -2,15 +2,26 @@ import { useMemo } from 'react';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
import {
|
|
|
Button,
|
|
|
- Collapse,
|
|
|
+ Card,
|
|
|
Input,
|
|
|
InputNumber,
|
|
|
Select,
|
|
|
- Space,
|
|
|
Switch,
|
|
|
+ Tabs,
|
|
|
} from 'antd';
|
|
|
+import {
|
|
|
+ DeleteOutlined,
|
|
|
+ PartitionOutlined,
|
|
|
+ PlusOutlined,
|
|
|
+ ScissorOutlined,
|
|
|
+ SendOutlined,
|
|
|
+ SettingOutlined,
|
|
|
+ ThunderboltOutlined,
|
|
|
+} from '@ant-design/icons';
|
|
|
import type { AllSetting } from '@/models/setting';
|
|
|
import { SettingListItem } from '@/components/ui';
|
|
|
+import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
|
+import { catTabLabel } from './catTabLabel';
|
|
|
import { sanitizePath, normalizePath } from './uriPath';
|
|
|
import './SubscriptionFormatsTab.css';
|
|
|
|
|
|
@@ -72,6 +83,7 @@ function readJson<T>(raw: string, fallback: T): T {
|
|
|
|
|
|
export default function SubscriptionFormatsTab({ allSetting, updateSetting }: SubscriptionFormatsTabProps) {
|
|
|
const { t } = useTranslation();
|
|
|
+ const { isMobile } = useMediaQuery();
|
|
|
|
|
|
const fragment = allSetting.subJsonFragment !== '';
|
|
|
const noisesEnabled = allSetting.subJsonNoises !== '';
|
|
|
@@ -190,10 +202,10 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
- <Collapse defaultActiveKey="1" items={[
|
|
|
+ <Tabs defaultActiveKey="1" items={[
|
|
|
{
|
|
|
key: '1',
|
|
|
- label: t('pages.settings.panelSettings'),
|
|
|
+ label: catTabLabel(<SettingOutlined />, t('pages.settings.panelSettings'), isMobile),
|
|
|
children: (
|
|
|
<>
|
|
|
{allSetting.subJsonEnable && (
|
|
|
@@ -239,40 +251,30 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
|
|
},
|
|
|
{
|
|
|
key: '2',
|
|
|
- label: t('pages.settings.fragment'),
|
|
|
+ label: catTabLabel(<ScissorOutlined />, t('pages.settings.fragment'), isMobile),
|
|
|
children: (
|
|
|
<>
|
|
|
<SettingListItem paddings="small" title={t('pages.settings.fragment')} description={t('pages.settings.fragmentDesc')}>
|
|
|
<Switch checked={fragment} onChange={setFragmentEnabled} />
|
|
|
</SettingListItem>
|
|
|
{fragment && (
|
|
|
- <div className="nested-block">
|
|
|
- <Collapse items={[
|
|
|
- {
|
|
|
- key: 'sett',
|
|
|
- label: t('pages.settings.fragmentSett'),
|
|
|
- children: (
|
|
|
- <>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.packets')}>
|
|
|
- <Input value={fragmentObj.packets} placeholder="1-1 | 1-3 | tlshello | …"
|
|
|
- onChange={(e) => setFragmentField('packets', e.target.value)} />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.length')}>
|
|
|
- <Input value={fragmentObj.length} placeholder="100-200"
|
|
|
- onChange={(e) => setFragmentField('length', e.target.value)} />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.interval')}>
|
|
|
- <Input value={fragmentObj.interval} placeholder="10-20"
|
|
|
- onChange={(e) => setFragmentField('interval', e.target.value)} />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.maxSplit')}>
|
|
|
- <Input value={fragmentObj.maxSplit} placeholder="300-400"
|
|
|
- onChange={(e) => setFragmentField('maxSplit', e.target.value)} />
|
|
|
- </SettingListItem>
|
|
|
- </>
|
|
|
- ),
|
|
|
- },
|
|
|
- ]} />
|
|
|
+ <div className="format-settings">
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.packets')}>
|
|
|
+ <Input value={fragmentObj.packets} placeholder="1-1 | 1-3 | tlshello | …"
|
|
|
+ onChange={(e) => setFragmentField('packets', e.target.value)} />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.length')}>
|
|
|
+ <Input value={fragmentObj.length} placeholder="100-200"
|
|
|
+ onChange={(e) => setFragmentField('length', e.target.value)} />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.interval')}>
|
|
|
+ <Input value={fragmentObj.interval} placeholder="10-20"
|
|
|
+ onChange={(e) => setFragmentField('interval', e.target.value)} />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.maxSplit')}>
|
|
|
+ <Input value={fragmentObj.maxSplit} placeholder="300-400"
|
|
|
+ onChange={(e) => setFragmentField('maxSplit', e.target.value)} />
|
|
|
+ </SettingListItem>
|
|
|
</div>
|
|
|
)}
|
|
|
</>
|
|
|
@@ -280,54 +282,60 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
|
|
},
|
|
|
{
|
|
|
key: '3',
|
|
|
- label: t('pages.settings.subFormats.noises'),
|
|
|
+ label: catTabLabel(<ThunderboltOutlined />, t('pages.settings.subFormats.noises'), isMobile),
|
|
|
children: (
|
|
|
<>
|
|
|
<SettingListItem paddings="small" title={t('pages.settings.subFormats.noises')} description={t('pages.settings.noisesDesc')}>
|
|
|
<Switch checked={noisesEnabled} onChange={setNoisesEnabled} />
|
|
|
</SettingListItem>
|
|
|
{noisesEnabled && (
|
|
|
- <div className="nested-block">
|
|
|
- <Collapse items={noisesArray.map((noise, index) => ({
|
|
|
- key: String(index),
|
|
|
- label: t('pages.settings.subFormats.noiseItem', { n: index + 1 }),
|
|
|
- children: (
|
|
|
- <>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.type')}>
|
|
|
- <Select
|
|
|
- value={noise.type}
|
|
|
- style={{ width: '100%' }}
|
|
|
- onChange={(v) => updateNoiseField(index, 'type', v)}
|
|
|
- options={['rand', 'base64', 'str', 'hex'].map((p) => ({ value: p, label: p }))}
|
|
|
- />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.packet')}>
|
|
|
- <Input value={noise.packet} placeholder="5-10"
|
|
|
- onChange={(e) => updateNoiseField(index, 'packet', e.target.value)} />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.delayMs')}>
|
|
|
- <Input value={noise.delay} placeholder="10-20"
|
|
|
- onChange={(e) => updateNoiseField(index, 'delay', e.target.value)} />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.applyTo')}>
|
|
|
- <Select
|
|
|
- value={noise.applyTo}
|
|
|
- style={{ width: '100%' }}
|
|
|
- onChange={(v) => updateNoiseField(index, 'applyTo', v)}
|
|
|
- options={['ip', 'ipv4', 'ipv6'].map((p) => ({ value: p, label: p }))}
|
|
|
- />
|
|
|
- </SettingListItem>
|
|
|
- <Space style={{ padding: '10px 20px' }}>
|
|
|
- {noisesArray.length > 1 && (
|
|
|
- <Button type="primary" danger onClick={() => removeNoise(index)}>
|
|
|
- {t('delete')}
|
|
|
- </Button>
|
|
|
- )}
|
|
|
- </Space>
|
|
|
- </>
|
|
|
- ),
|
|
|
- }))} />
|
|
|
- <Button type="primary" style={{ marginTop: 10 }} onClick={addNoise}>{t('pages.settings.subFormats.addNoise')}</Button>
|
|
|
+ <div className="format-settings-list">
|
|
|
+ {noisesArray.map((noise, index) => (
|
|
|
+ <Card
|
|
|
+ key={index}
|
|
|
+ size="small"
|
|
|
+ className="noise-card"
|
|
|
+ title={t('pages.settings.subFormats.noiseItem', { n: index + 1 })}
|
|
|
+ extra={noisesArray.length > 1 ? (
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ danger
|
|
|
+ icon={<DeleteOutlined />}
|
|
|
+ aria-label={t('delete')}
|
|
|
+ onClick={() => removeNoise(index)}
|
|
|
+ />
|
|
|
+ ) : null}
|
|
|
+ styles={{ body: { padding: 0 } }}
|
|
|
+ >
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.type')}>
|
|
|
+ <Select
|
|
|
+ value={noise.type}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ onChange={(v) => updateNoiseField(index, 'type', v)}
|
|
|
+ options={['rand', 'base64', 'str', 'hex'].map((p) => ({ value: p, label: p }))}
|
|
|
+ />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.packet')}>
|
|
|
+ <Input value={noise.packet} placeholder="5-10"
|
|
|
+ onChange={(e) => updateNoiseField(index, 'packet', e.target.value)} />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.delayMs')}>
|
|
|
+ <Input value={noise.delay} placeholder="10-20"
|
|
|
+ onChange={(e) => updateNoiseField(index, 'delay', e.target.value)} />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.applyTo')}>
|
|
|
+ <Select
|
|
|
+ value={noise.applyTo}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ onChange={(v) => updateNoiseField(index, 'applyTo', v)}
|
|
|
+ options={['ip', 'ipv4', 'ipv6'].map((p) => ({ value: p, label: p }))}
|
|
|
+ />
|
|
|
+ </SettingListItem>
|
|
|
+ </Card>
|
|
|
+ ))}
|
|
|
+ <Button type="dashed" block icon={<PlusOutlined />} onClick={addNoise}>
|
|
|
+ {t('pages.settings.subFormats.addNoise')}
|
|
|
+ </Button>
|
|
|
</div>
|
|
|
)}
|
|
|
</>
|
|
|
@@ -335,40 +343,30 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
|
|
},
|
|
|
{
|
|
|
key: '4',
|
|
|
- label: t('pages.settings.mux'),
|
|
|
+ label: catTabLabel(<PartitionOutlined />, t('pages.settings.mux'), isMobile),
|
|
|
children: (
|
|
|
<>
|
|
|
<SettingListItem paddings="small" title={t('pages.settings.mux')} description={t('pages.settings.muxDesc')}>
|
|
|
<Switch checked={muxEnabled} onChange={setMuxEnabled} />
|
|
|
</SettingListItem>
|
|
|
{muxEnabled && (
|
|
|
- <div className="nested-block">
|
|
|
- <Collapse items={[
|
|
|
- {
|
|
|
- key: 'sett',
|
|
|
- label: t('pages.settings.muxSett'),
|
|
|
- children: (
|
|
|
- <>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.concurrency')}>
|
|
|
- <InputNumber value={muxObj.concurrency} min={-1} max={1024} style={{ width: '100%' }}
|
|
|
- onChange={(v) => setMuxField('concurrency', Number(v) || 0)} />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.xudpConcurrency')}>
|
|
|
- <InputNumber value={muxObj.xudpConcurrency} min={-1} max={1024} style={{ width: '100%' }}
|
|
|
- onChange={(v) => setMuxField('xudpConcurrency', Number(v) || 0)} />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={t('pages.settings.subFormats.xudpUdp443')}>
|
|
|
- <Select
|
|
|
- value={muxObj.xudpProxyUDP443}
|
|
|
- style={{ width: '100%' }}
|
|
|
- onChange={(v) => setMuxField('xudpProxyUDP443', v)}
|
|
|
- options={['reject', 'allow', 'skip'].map((p) => ({ value: p, label: p }))}
|
|
|
- />
|
|
|
- </SettingListItem>
|
|
|
- </>
|
|
|
- ),
|
|
|
- },
|
|
|
- ]} />
|
|
|
+ <div className="format-settings">
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.concurrency')}>
|
|
|
+ <InputNumber value={muxObj.concurrency} min={-1} max={1024} style={{ width: '100%' }}
|
|
|
+ onChange={(v) => setMuxField('concurrency', Number(v) || 0)} />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.xudpConcurrency')}>
|
|
|
+ <InputNumber value={muxObj.xudpConcurrency} min={-1} max={1024} style={{ width: '100%' }}
|
|
|
+ onChange={(v) => setMuxField('xudpConcurrency', Number(v) || 0)} />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={t('pages.settings.subFormats.xudpUdp443')}>
|
|
|
+ <Select
|
|
|
+ value={muxObj.xudpProxyUDP443}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ onChange={(v) => setMuxField('xudpProxyUDP443', v)}
|
|
|
+ options={['reject', 'allow', 'skip'].map((p) => ({ value: p, label: p }))}
|
|
|
+ />
|
|
|
+ </SettingListItem>
|
|
|
</div>
|
|
|
)}
|
|
|
</>
|
|
|
@@ -376,42 +374,32 @@ export default function SubscriptionFormatsTab({ allSetting, updateSetting }: Su
|
|
|
},
|
|
|
{
|
|
|
key: '5',
|
|
|
- label: t('pages.settings.direct'),
|
|
|
+ label: catTabLabel(<SendOutlined />, t('pages.settings.direct'), isMobile),
|
|
|
children: (
|
|
|
<>
|
|
|
<SettingListItem paddings="small" title={t('pages.settings.direct')} description={t('pages.settings.directDesc')}>
|
|
|
<Switch checked={directEnabled} onChange={setDirectEnabled} />
|
|
|
</SettingListItem>
|
|
|
{directEnabled && (
|
|
|
- <div className="nested-block">
|
|
|
- <Collapse items={[
|
|
|
- {
|
|
|
- key: 'rules',
|
|
|
- label: t('pages.settings.direct'),
|
|
|
- children: (
|
|
|
- <>
|
|
|
- <SettingListItem paddings="small" title={<>{t('pages.settings.direct')} IPs</>}>
|
|
|
- <Select
|
|
|
- mode="tags"
|
|
|
- value={directIPs}
|
|
|
- style={{ width: '100%' }}
|
|
|
- onChange={setDirectIPs}
|
|
|
- options={directIPsOptions}
|
|
|
- />
|
|
|
- </SettingListItem>
|
|
|
- <SettingListItem paddings="small" title={<>{t('pages.settings.direct')} {t('domainName')}</>}>
|
|
|
- <Select
|
|
|
- mode="tags"
|
|
|
- value={directDomains}
|
|
|
- style={{ width: '100%' }}
|
|
|
- onChange={setDirectDomains}
|
|
|
- options={directDomainsOptions}
|
|
|
- />
|
|
|
- </SettingListItem>
|
|
|
- </>
|
|
|
- ),
|
|
|
- },
|
|
|
- ]} />
|
|
|
+ <div className="format-settings">
|
|
|
+ <SettingListItem paddings="small" title={<>{t('pages.settings.direct')} IPs</>}>
|
|
|
+ <Select
|
|
|
+ mode="tags"
|
|
|
+ value={directIPs}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ onChange={setDirectIPs}
|
|
|
+ options={directIPsOptions}
|
|
|
+ />
|
|
|
+ </SettingListItem>
|
|
|
+ <SettingListItem paddings="small" title={<>{t('pages.settings.direct')} {t('domainName')}</>}>
|
|
|
+ <Select
|
|
|
+ mode="tags"
|
|
|
+ value={directDomains}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ onChange={setDirectDomains}
|
|
|
+ options={directDomainsOptions}
|
|
|
+ />
|
|
|
+ </SettingListItem>
|
|
|
</div>
|
|
|
)}
|
|
|
</>
|