| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- import { Alert, Button, Input, InputNumber, Select, Switch, Tabs } from 'antd';
- import {
- BranchesOutlined,
- CompassOutlined,
- IdcardOutlined,
- InfoCircleOutlined,
- NodeIndexOutlined,
- SafetyCertificateOutlined,
- SettingOutlined,
- } from '@ant-design/icons';
- import { useTranslation } from 'react-i18next';
- import { useNavigate, useSearchParams } from 'react-router';
- import type { AllSetting } from '@/models/setting';
- import type { SubProfileMode } from '@/schemas/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';
- import HappSettingsContent from './HappSettingsContent';
- import IncySettingsContent from './IncySettingsContent';
- import { remoteSourceBadge } from './subscriptionShared';
- interface SubscriptionGeneralTabProps {
- allSetting: AllSetting;
- updateSetting: (patch: Partial<AllSetting>) => void;
- }
- const PANEL_SETTINGS_TAB = '1';
- const HAPP_SETTINGS_TAB = '5';
- export default function SubscriptionGeneralTab({
- allSetting,
- updateSetting,
- }: SubscriptionGeneralTabProps) {
- const { t } = useTranslation();
- const navigate = useNavigate();
- const [searchParams] = useSearchParams();
- const { isMobile } = useMediaQuery();
- // Keep the URL semantic while mapping to the legacy numeric key used by these inner tabs.
- const initialTab =
- searchParams.get('subscriptionTab') === 'happ' ? HAPP_SETTINGS_TAB : PANEL_SETTINGS_TAB;
- return (
- <Tabs
- defaultActiveKey={initialTab}
- 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.subJsonEnableTitle')}
- description={t('pages.settings.subJsonEnable')}
- >
- <Switch
- checked={allSetting.subJsonEnable}
- onChange={(v) => updateSetting({ subJsonEnable: v })}
- />
- </SettingListItem>
- <SettingListItem paddings="small" title={t('pages.settings.subClashEnableTitle')}>
- <Switch
- checked={allSetting.subClashEnable}
- onChange={(v) => updateSetting({ subClashEnable: v })}
- />
- </SettingListItem>
- {(allSetting.subJsonEnable || allSetting.subClashEnable) && (
- <Alert
- type="info"
- showIcon
- style={{ margin: '12px 20px' }}
- title={t('pages.settings.subFormatsTipTitle')}
- description={t('pages.settings.subFormatsTipDesc')}
- action={
- <Button size="small" onClick={() => navigate('/settings#subscription-formats')}>
- {t('pages.settings.subFormatsTipAction')}
- </Button>
- }
- />
- )}
- <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}
- min={1}
- max={65535}
- style={{ width: '100%' }}
- onChange={onNumber((v) => updateSetting({ subPort: v }))}
- />
- </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.subInfoNodeEnable')}
- description={t('pages.settings.subInfoNodeEnableDesc')}
- >
- <Switch
- checked={allSetting.subInfoNodeEnable}
- onChange={(v) => updateSetting({ subInfoNodeEnable: v })}
- />
- </SettingListItem>
- <SettingListItem
- paddings="small"
- title={t('pages.settings.subCalendarExpireInclusive')}
- description={t('pages.settings.subCalendarExpireInclusiveDesc')}
- >
- <Switch
- checked={allSetting.subCalendarExpireInclusive}
- onChange={(v) => updateSetting({ subCalendarExpireInclusive: v })}
- />
- </SettingListItem>
- <SettingListItem
- paddings="small"
- title={t('pages.settings.subExpiredTemplate')}
- description={t('pages.settings.subExpiredTemplateDesc')}
- >
- <RemarkTemplateField
- value={allSetting.subExpiredTemplate}
- onChange={(v) => updateSetting({ subExpiredTemplate: v })}
- maxLength={256}
- />
- </SettingListItem>
- <SettingListItem
- paddings="small"
- title={t('pages.settings.subTrafficDepletedTemplate')}
- description={t('pages.settings.subTrafficDepletedTemplateDesc')}
- >
- <RemarkTemplateField
- value={allSetting.subTrafficDepletedTemplate}
- onChange={(v) => updateSetting({ subTrafficDepletedTemplate: v })}
- maxLength={256}
- />
- </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.externalSubUserAgent')}
- badge={
- <DefaultSettingTag
- settingKey="externalSubUserAgent"
- value={allSetting.externalSubUserAgent}
- />
- }
- description={t('pages.settings.externalSubUserAgentDesc')}
- >
- <Input
- value={allSetting.externalSubUserAgent}
- placeholder="v2rayNG/1.8.5"
- maxLength={512}
- allowClear
- onChange={(e) => updateSetting({ externalSubUserAgent: e.target.value })}
- />
- </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.subProfileMode')}
- description={t('pages.settings.subProfileModeDesc')}
- >
- <Select<SubProfileMode>
- id="sub-profile-mode"
- aria-label={t('pages.settings.subProfileMode')}
- value={allSetting.subProfileMode}
- style={{ width: '100%' }}
- onChange={(value) => updateSetting({ subProfileMode: value })}
- options={[
- { value: 'none', label: t('pages.settings.subProfileModeNone') },
- { value: 'builtin', label: t('pages.settings.subProfileModeBuiltin') },
- { value: 'custom', label: t('pages.settings.subProfileModeCustom') },
- ]}
- />
- </SettingListItem>
- {allSetting.subProfileMode === 'builtin' ? (
- <Alert
- type="warning"
- showIcon
- style={{ margin: '12px 20px' }}
- title={t('pages.settings.subProfileBuiltinWarning')}
- />
- ) : null}
- {allSetting.subProfileMode === 'custom' ? (
- <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>
- ) : null}
- <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>
- <SettingListItem
- paddings="small"
- title={t('pages.settings.subThemeDir')}
- description={
- <>
- {t('pages.settings.subThemeDirDesc')}{' '}
- <a
- href="https://github.com/MHSanaei/3x-ui/blob/main/docs/custom-subscription-templates.md"
- target="_blank"
- rel="noopener noreferrer"
- >
- {t('pages.settings.subThemeDirDocs')}
- </a>
- </>
- }
- >
- <Input
- value={allSetting.subThemeDir}
- placeholder="/etc/3x-ui/sub_templates/my-theme/"
- onChange={(e) => updateSetting({ subThemeDir: e.target.value })}
- />
- </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>
- </>
- ),
- },
- {
- key: '5',
- label: catTabLabel(<BranchesOutlined />, 'Happ', isMobile),
- children: (
- <HappSettingsContent
- allSetting={allSetting}
- updateSetting={updateSetting}
- isMobile={isMobile}
- remoteSourceBadge={remoteSourceBadge}
- />
- ),
- },
- {
- key: '6',
- label: catTabLabel(<NodeIndexOutlined />, 'Clash / Mihomo', isMobile),
- children: (
- <>
- <SettingListItem
- paddings="small"
- title={t('pages.settings.subClashEnableRouting')}
- description={t('pages.settings.subClashEnableRoutingDesc')}
- >
- <Switch
- checked={allSetting.subClashEnableRouting}
- onChange={(v) => updateSetting({ subClashEnableRouting: v })}
- />
- </SettingListItem>
- <SettingListItem
- paddings="small"
- title={t('pages.settings.subClashRoutingRules')}
- badge={remoteSourceBadge(allSetting.subClashRules)}
- description={t('pages.settings.subClashRoutingRulesDesc')}
- >
- <Input.TextArea
- value={allSetting.subClashRules}
- rows={8}
- placeholder={
- 'https://.../routing.yaml\n\nor inline rules:\nGEOSITE,category-ir,DIRECT'
- }
- onChange={(e) => updateSetting({ subClashRules: e.target.value })}
- />
- </SettingListItem>
- </>
- ),
- },
- {
- key: '7',
- label: catTabLabel(<CompassOutlined />, 'Incy', isMobile),
- children: (
- <IncySettingsContent
- allSetting={allSetting}
- updateSetting={updateSetting}
- isMobile={isMobile}
- remoteSourceBadge={remoteSourceBadge}
- />
- ),
- },
- ]}
- />
- );
- }
|