Files
3x-ui-agent/frontend/src/pages/settings/SubscriptionGeneralTab.tsx
T
Muse 1bd63e6625 settings trim: remove dead settings, upstream remnants, JSON/Clash subscriptions
Per SETTINGS-AUDIT (all three tiers approved 2026-10-23):

Dead settings:
- panelOutbound, ipLimitAllowlist (+ orphan parser job), outboundDownThreshold
- tg fake events outbound.down/outbound.up/xray.crash (no publisher in lite)
- phantom routing sub-tabs (Happ/Clash/Incy) and subJsonRules direct-rules tab
  (no backend keys); subHideSettings moved to the subscription info tab first

Upstream remnants:
- ldap* / smtp* model+schema keys, EmailNotifications orphan component
- twoFactorEnable/twoFactorToken + login-page probe (no backend handler)
- externalTrafficInform*

Newly approved removals:
- JSON subscription family (producer, routes, UA auto-detect, mux/finalmask/
  observatory) and Clash subscription family (producer, routes, UA detect)
- SubBalancer (table, service, controller, settings tab)
- subThemeDir (subscription page falls back to the built-in template)
- datepicker jalalian option (gregorian only; Persian picker removed)

JSON/Clash endpoints now 404; link-format output pinned by regression tests.
Guarded keys untouched: webListen/webPort/webBasePath/certs, tg* chain,
expireDiff/trafficDiff/tgBotProxy, subHideSettings, pageSize/tgLang.
2026-10-22 19:21:07 +00:00

261 lines
9.5 KiB
TypeScript

import { Input, InputNumber, Switch, Tabs } from 'antd';
import {
IdcardOutlined,
InfoCircleOutlined,
SafetyCertificateOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import type { AllSetting } from '@/models/setting';
import { onNumber } from '@/utils/onNumber';
import { DefaultSettingTag, SettingListItem } from '@/components/ui';
import { RemarkTemplateField } from '@/components/form';
import { useMediaQuery } from '@/hooks/useMediaQuery';
import { catTabLabel } from './catTabLabel';
import { sanitizePath, normalizePath } from './uriPath';
interface SubscriptionGeneralTabProps {
allSetting: AllSetting;
updateSetting: (patch: Partial<AllSetting>) => void;
}
export default function SubscriptionGeneralTab({
allSetting,
updateSetting,
}: SubscriptionGeneralTabProps) {
const { t } = useTranslation();
const { isMobile } = useMediaQuery();
return (
<Tabs
defaultActiveKey="1"
items={[
{
key: '1',
label: catTabLabel(<SettingOutlined />, t('pages.settings.panelSettings'), isMobile),
children: (
<>
<SettingListItem
paddings="small"
title={t('pages.settings.subEnable')}
description={t('pages.settings.subEnableDesc')}
>
<Switch
checked={allSetting.subEnable}
onChange={(v) => updateSetting({ subEnable: v })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subListen')}
description={t('pages.settings.subListenDesc')}
>
<Input
value={allSetting.subListen}
onChange={(e) => updateSetting({ subListen: e.target.value })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subDomain')}
description={t('pages.settings.subDomainDesc')}
>
<Input
value={allSetting.subDomain}
onChange={(e) => updateSetting({ subDomain: e.target.value })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subPort')}
badge={<DefaultSettingTag settingKey="subPort" value={allSetting.subPort} />}
description={t('pages.settings.subPortDesc')}
>
<InputNumber
value={allSetting.subPort || null}
min={0}
max={65535}
placeholder="2096"
style={{ width: '100%' }}
onChange={onNumber((v) => updateSetting({ subPort: v }), { onClear: 0 })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subPath')}
description={t('pages.settings.subPathDesc')}
>
<Input
value={allSetting.subPath}
placeholder="/sub/"
onChange={(e) => updateSetting({ subPath: sanitizePath(e.target.value) })}
onBlur={() => updateSetting({ subPath: normalizePath(allSetting.subPath) })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subURI')}
description={t('pages.settings.subURIDesc')}
>
<Input
value={allSetting.subURI}
placeholder="(http|https)://domain[:port]/path/"
onChange={(e) => updateSetting({ subURI: e.target.value })}
/>
</SettingListItem>
</>
),
},
{
key: '2',
label: catTabLabel(<InfoCircleOutlined />, t('pages.settings.information'), isMobile),
children: (
<>
<SettingListItem
paddings="small"
title={t('pages.settings.subEncrypt')}
description={t('pages.settings.subEncryptDesc')}
>
<Switch
checked={allSetting.subEncrypt}
onChange={(v) => updateSetting({ subEncrypt: v })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.remarkTemplate')}
description={t('pages.settings.remarkTemplateDesc')}
>
<RemarkTemplateField
value={allSetting.remarkTemplate}
onChange={(v) => updateSetting({ remarkTemplate: v })}
maxLength={256}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subShowIdentityOnAllLinks')}
description={t('pages.settings.subShowIdentityOnAllLinksDesc')}
>
<Switch
checked={allSetting.subShowIdentityOnAllLinks}
onChange={(v) => updateSetting({ subShowIdentityOnAllLinks: v })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subUpdates')}
badge={<DefaultSettingTag settingKey="subUpdates" value={allSetting.subUpdates} />}
description={t('pages.settings.subUpdatesDesc')}
>
<InputNumber
value={allSetting.subUpdates}
min={0}
max={525600}
style={{ width: '100%' }}
onChange={onNumber((v) => updateSetting({ subUpdates: v }))}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subHideSettings')}
description={t('pages.settings.subHideSettingsDesc')}
>
<Switch
checked={allSetting.subHideSettings}
onChange={(v) => updateSetting({ subHideSettings: v })}
/>
</SettingListItem>
</>
),
},
{
key: '3',
label: catTabLabel(<IdcardOutlined />, t('pages.settings.profile'), isMobile),
children: (
<>
<SettingListItem
paddings="small"
title={t('pages.settings.subTitle')}
description={t('pages.settings.subTitleDesc')}
>
<RemarkTemplateField
value={allSetting.subTitle}
onChange={(v) => updateSetting({ subTitle: v })}
metadataOnly
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subSupportUrl')}
description={t('pages.settings.subSupportUrlDesc')}
>
<RemarkTemplateField
value={allSetting.subSupportUrl}
placeholder="https://example.com"
onChange={(v) => updateSetting({ subSupportUrl: v })}
metadataOnly
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subProfileUrl')}
description={t('pages.settings.subProfileUrlDesc')}
>
<RemarkTemplateField
value={allSetting.subProfileUrl}
placeholder="https://example.com"
onChange={(v) => updateSetting({ subProfileUrl: v })}
metadataOnly
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subAnnounce')}
description={t('pages.settings.subAnnounceDesc')}
>
<RemarkTemplateField
value={allSetting.subAnnounce}
onChange={(v) => updateSetting({ subAnnounce: v })}
multiline
rows={3}
metadataOnly
/>
</SettingListItem>
</>
),
},
{
key: '4',
label: catTabLabel(<SafetyCertificateOutlined />, t('pages.settings.certs'), isMobile),
children: (
<>
<SettingListItem
paddings="small"
title={t('pages.settings.subCertPath')}
description={t('pages.settings.subCertPathDesc')}
>
<Input
value={allSetting.subCertFile}
onChange={(e) => updateSetting({ subCertFile: e.target.value })}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subKeyPath')}
description={t('pages.settings.subKeyPathDesc')}
>
<Input
value={allSetting.subKeyFile}
onChange={(e) => updateSetting({ subKeyFile: e.target.value })}
/>
</SettingListItem>
</>
),
},
]}
/>
);
}