Browse Source

feat(settings): move the remark model control to the subscription tab

Relocate Remark Model & Separation Character from the General/Panel tab to the Subscription tab's Information section, beside Show Info and Email in Remark, since it only governs how share-link remarks are composed. The sample preview uses concrete example values and renders the separator literally.

Also drop the port from the subscription page link rows so each row shows just the inbound remark; the port still appears in the client QR modal and the client info modal.
MHSanaei 19 giờ trước cách đây
mục cha
commit
e63cde8fcb

+ 1 - 1
frontend/src/models/setting.ts

@@ -34,7 +34,7 @@ export class AllSetting {
   subSupportUrl = '';
   subProfileUrl = '';
   subAnnounce = '';
-  subEnableRouting = true;
+  subEnableRouting = false;
   subRoutingRules = '';
   subListen = '';
   subPort = 2096;

+ 0 - 49
frontend/src/pages/settings/GeneralTab.tsx

@@ -5,7 +5,6 @@ import {
   Input,
   InputNumber,
   Select,
-  Space,
   Switch,
 } from 'antd';
 import type { AllSetting } from '@/models/setting';
@@ -23,8 +22,6 @@ interface GeneralTabProps {
   updateSetting: (patch: Partial<AllSetting>) => void;
 }
 
-const REMARK_MODELS: Record<string, string> = { i: 'Inbound', e: 'Email', o: 'Other' };
-const REMARK_SEPARATORS = [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'];
 const DATEPICKER_LIST: { name: string; value: 'gregorian' | 'jalalian' }[] = [
   { name: 'Gregorian (Standard)', value: 'gregorian' },
   { name: 'Jalalian (شمسی)', value: 'jalalian' },
@@ -57,30 +54,6 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
     return () => { cancelled = true; };
   }, []);
 
-  const remarkModel = useMemo(() => {
-    const rm = allSetting.remarkModel || '';
-    return rm.length > 1 ? rm.substring(1).split('') : [];
-  }, [allSetting.remarkModel]);
-
-  const remarkSeparator = useMemo(() => {
-    const rm = allSetting.remarkModel || '-';
-    return rm.length > 1 ? rm.charAt(0) : '-';
-  }, [allSetting.remarkModel]);
-
-  const remarkSample = useMemo(() => {
-    const parts = remarkModel.map((k) => REMARK_MODELS[k]);
-    return parts.length === 0 ? '' : parts.join(remarkSeparator);
-  }, [remarkModel, remarkSeparator]);
-
-  function setRemarkModel(parts: string[]) {
-    updateSetting({ remarkModel: remarkSeparator + parts.join('') });
-  }
-
-  function setRemarkSeparator(sep: string) {
-    const tail = (allSetting.remarkModel || '-').substring(1);
-    updateSetting({ remarkModel: sep + tail });
-  }
-
   const ldapInboundTagList = useMemo(() => {
     const csv = allSetting.ldapInboundTags || '';
     return csv.length ? csv.split(',').map((s) => s.trim()).filter(Boolean) : [];
@@ -115,28 +88,6 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
         label: t('pages.settings.panelSettings'),
         children: (
           <>
-            <SettingListItem
-              paddings="small"
-              title={t('pages.settings.remarkModel')}
-              description={<>{t('pages.settings.sampleRemark')}: <i>#{remarkSample}</i></>}
-            >
-              <Space.Compact style={{ width: '100%' }}>
-                <Select
-                  mode="multiple"
-                  value={remarkModel}
-                  onChange={setRemarkModel}
-                  style={{ paddingRight: '.5rem', minWidth: '80%', width: 'auto' }}
-                  options={Object.entries(REMARK_MODELS).map(([k, l]) => ({ value: k, label: l }))}
-                />
-                <Select
-                  value={remarkSeparator}
-                  onChange={setRemarkSeparator}
-                  style={{ width: '20%' }}
-                  options={REMARK_SEPARATORS.map((s) => ({ value: s, label: s }))}
-                />
-              </Space.Compact>
-            </SettingListItem>
-
             <SettingListItem paddings="small" title={t('pages.settings.panelListeningIP')} description={t('pages.settings.panelListeningIPDesc')}>
               <Input value={allSetting.webListen} onChange={(e) => updateSetting({ webListen: e.target.value })} />
             </SettingListItem>

+ 68 - 1
frontend/src/pages/settings/SubscriptionGeneralTab.tsx

@@ -1,9 +1,14 @@
-import { Collapse, Divider, Input, InputNumber, Switch } from 'antd';
+import { useMemo } from 'react';
+import { Collapse, Divider, Input, InputNumber, Select, Space, Switch } from 'antd';
 import { useTranslation } from 'react-i18next';
 import type { AllSetting } from '@/models/setting';
 import { SettingListItem } from '@/components/ui';
 import { sanitizePath, normalizePath } from './uriPath';
 
+const REMARK_MODELS: Record<string, string> = { i: 'Inbound', e: 'Email', o: 'Other' };
+const REMARK_SAMPLES: Record<string, string> = { i: 'Germany', e: 'john', o: 'Relay' };
+const REMARK_SEPARATORS = [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'];
+
 interface SubscriptionGeneralTabProps {
   allSetting: AllSetting;
   updateSetting: (patch: Partial<AllSetting>) => void;
@@ -12,6 +17,30 @@ interface SubscriptionGeneralTabProps {
 export default function SubscriptionGeneralTab({ allSetting, updateSetting }: SubscriptionGeneralTabProps) {
   const { t } = useTranslation();
 
+  const remarkModel = useMemo(() => {
+    const rm = allSetting.remarkModel || '';
+    return rm.length > 1 ? rm.substring(1).split('') : [];
+  }, [allSetting.remarkModel]);
+
+  const remarkSeparator = useMemo(() => {
+    const rm = allSetting.remarkModel || '-';
+    return rm.length > 1 ? rm.charAt(0) : '-';
+  }, [allSetting.remarkModel]);
+
+  const remarkSample = useMemo(() => {
+    const parts = remarkModel.map((k) => REMARK_SAMPLES[k]);
+    return parts.length === 0 ? '' : parts.join(remarkSeparator);
+  }, [remarkModel, remarkSeparator]);
+
+  function setRemarkModel(parts: string[]) {
+    updateSetting({ remarkModel: remarkSeparator + parts.join('') });
+  }
+
+  function setRemarkSeparator(sep: string) {
+    const tail = (allSetting.remarkModel || '-').substring(1);
+    updateSetting({ remarkModel: sep + tail });
+  }
+
   return (
     <Collapse defaultActiveKey="1" items={[
       {
@@ -68,6 +97,44 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
               <Switch checked={allSetting.subEmailInRemark} onChange={(v) => updateSetting({ subEmailInRemark: v })} />
             </SettingListItem>
 
+            <SettingListItem
+              paddings="small"
+              title={t('pages.settings.remarkModel')}
+              description={
+                <>
+                  {t('pages.settings.sampleRemark')}:{' '}
+                  <span
+                    style={{
+                      fontFamily: 'monospace',
+                      padding: '1px 6px',
+                      borderRadius: 4,
+                      border: '1px solid var(--ant-color-border)',
+                      background: 'var(--ant-color-fill-tertiary)',
+                      whiteSpace: 'pre',
+                    }}
+                  >
+                    {remarkSample ? `#${remarkSample}` : '—'}
+                  </span>
+                </>
+              }
+            >
+              <Space.Compact style={{ width: '100%' }}>
+                <Select
+                  mode="multiple"
+                  value={remarkModel}
+                  onChange={setRemarkModel}
+                  style={{ paddingRight: '.5rem', minWidth: '80%', width: 'auto' }}
+                  options={Object.entries(REMARK_MODELS).map(([k, l]) => ({ value: k, label: l }))}
+                />
+                <Select
+                  value={remarkSeparator}
+                  onChange={setRemarkSeparator}
+                  style={{ width: '20%' }}
+                  options={REMARK_SEPARATORS.map((s) => ({ value: s, label: s === ' ' ? '␣' : s }))}
+                />
+              </Space.Compact>
+            </SettingListItem>
+
             <Divider>{t('pages.settings.subTitle')}</Divider>
 
             <SettingListItem paddings="small" title={t('pages.settings.subTitle')} description={t('pages.settings.subTitleDesc')}>

+ 2 - 2
frontend/src/pages/sub/SubPage.tsx

@@ -32,7 +32,7 @@ import {
 
 import { ClipboardManager, IntlUtil, LanguageManager } from '@/utils';
 import { isPostQuantumLink } from '@/lib/xray/inbound-link';
-import { LinkTags, linkMetaText, parseLinkParts } from '@/lib/xray/link-label';
+import { LinkTags, parseLinkParts } from '@/lib/xray/link-label';
 import { setMessageInstance } from '@/utils/messageBus';
 import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme';
 import SubUsageSummary from './SubUsageSummary';
@@ -396,7 +396,7 @@ export default function SubPage() {
                       {links.map((link, idx) => {
                         const parts = parseLinkParts(link, linkEmails[idx] || '');
                         const fallback = `Link ${idx + 1}`;
-                        const rowTitle = (parts && linkMetaText(parts)) || fallback;
+                        const rowTitle = parts?.remark || fallback;
                         const qrLabel = [parts?.remark, linkEmails[idx]].filter(Boolean).join('-') || rowTitle;
                         const canQr = !isPostQuantumLink(link);
                         return (

+ 2 - 2
web/service/setting.go

@@ -61,7 +61,7 @@ var defaultValueMap = map[string]string{
 	"subSupportUrl":               "",
 	"subProfileUrl":               "",
 	"subAnnounce":                 "",
-	"subEnableRouting":            "true",
+	"subEnableRouting":            "false",
 	"subRoutingRules":             "",
 	"subListen":                   "",
 	"subPort":                     "2096",
@@ -76,7 +76,7 @@ var defaultValueMap = map[string]string{
 	"subURI":                      "",
 	"subJsonPath":                 "/json/",
 	"subJsonURI":                  "",
-	"subClashEnable":              "true",
+	"subClashEnable":              "false",
 	"subClashPath":                "/clash/",
 	"subClashURI":                 "",
 	"subJsonFragment":             "",