Bläddra i källkod

new: subJsonEnable

after this subEnable by default is true
and subJsonEnable is false
mhsanaei 10 timmar sedan
förälder
incheckning
59ea2645db

+ 7 - 1
sub/sub.go

@@ -85,6 +85,12 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	// Determine if JSON subscription endpoint is enabled
+	subJsonEnable, err := s.settingService.GetSubJsonEnable()
+	if err != nil {
+		return nil, err
+	}
+
 	// Set base_path based on LinksPath for template rendering
 	// Set base_path based on LinksPath for template rendering
 	engine.Use(func(c *gin.Context) {
 	engine.Use(func(c *gin.Context) {
 		c.Set("base_path", LinksPath)
 		c.Set("base_path", LinksPath)
@@ -186,7 +192,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 	g := engine.Group("/")
 	g := engine.Group("/")
 
 
 	s.sub = NewSUBController(
 	s.sub = NewSUBController(
-		g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
+		g, LinksPath, JsonPath, subJsonEnable, Encrypt, ShowInfo, RemarkModel, SubUpdates,
 		SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
 		SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
 
 
 	return engine, nil
 	return engine, nil

+ 10 - 3
sub/subController.go

@@ -13,6 +13,7 @@ type SUBController struct {
 	subTitle       string
 	subTitle       string
 	subPath        string
 	subPath        string
 	subJsonPath    string
 	subJsonPath    string
+	jsonEnabled    bool
 	subEncrypt     bool
 	subEncrypt     bool
 	updateInterval string
 	updateInterval string
 
 
@@ -24,6 +25,7 @@ func NewSUBController(
 	g *gin.RouterGroup,
 	g *gin.RouterGroup,
 	subPath string,
 	subPath string,
 	jsonPath string,
 	jsonPath string,
+	jsonEnabled bool,
 	encrypt bool,
 	encrypt bool,
 	showInfo bool,
 	showInfo bool,
 	rModel string,
 	rModel string,
@@ -39,6 +41,7 @@ func NewSUBController(
 		subTitle:       subTitle,
 		subTitle:       subTitle,
 		subPath:        subPath,
 		subPath:        subPath,
 		subJsonPath:    jsonPath,
 		subJsonPath:    jsonPath,
+		jsonEnabled:    jsonEnabled,
 		subEncrypt:     encrypt,
 		subEncrypt:     encrypt,
 		updateInterval: update,
 		updateInterval: update,
 
 
@@ -51,10 +54,11 @@ func NewSUBController(
 
 
 func (a *SUBController) initRouter(g *gin.RouterGroup) {
 func (a *SUBController) initRouter(g *gin.RouterGroup) {
 	gLink := g.Group(a.subPath)
 	gLink := g.Group(a.subPath)
-	gJson := g.Group(a.subJsonPath)
-
 	gLink.GET(":subid", a.subs)
 	gLink.GET(":subid", a.subs)
-	gJson.GET(":subid", a.subJsons)
+	if a.jsonEnabled {
+		gJson := g.Group(a.subJsonPath)
+		gJson.GET(":subid", a.subJsons)
+	}
 }
 }
 
 
 func (a *SUBController) subs(c *gin.Context) {
 func (a *SUBController) subs(c *gin.Context) {
@@ -74,6 +78,9 @@ func (a *SUBController) subs(c *gin.Context) {
 		if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") {
 		if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") {
 			// Build page data in service
 			// Build page data in service
 			subURL, subJsonURL := a.subService.BuildURLs(scheme, hostWithPort, a.subPath, a.subJsonPath, subId)
 			subURL, subJsonURL := a.subService.BuildURLs(scheme, hostWithPort, a.subPath, a.subJsonPath, subId)
+			if !a.jsonEnabled {
+				subJsonURL = ""
+			}
 			page := a.subService.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, subURL, subJsonURL)
 			page := a.subService.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, subURL, subJsonURL)
 			c.HTML(200, "subpage.html", gin.H{
 			c.HTML(200, "subpage.html", gin.H{
 				"title":        "subscription.title",
 				"title":        "subscription.title",

+ 2 - 1
web/assets/js/model/setting.js

@@ -26,7 +26,8 @@ class AllSetting {
         this.twoFactorEnable = false;
         this.twoFactorEnable = false;
         this.twoFactorToken = "";
         this.twoFactorToken = "";
         this.xrayTemplateConfig = "";
         this.xrayTemplateConfig = "";
-        this.subEnable = false;
+        this.subEnable = true;
+        this.subJsonEnable = false;
         this.subTitle = "";
         this.subTitle = "";
         this.subListen = "";
         this.subListen = "";
         this.subPort = 2096;
         this.subPort = 2096;

+ 4 - 1
web/assets/js/subscription.js

@@ -101,7 +101,10 @@
       if (sj) this.app.subJsonUrl = sj;
       if (sj) this.app.subJsonUrl = sj;
       drawQR(this.app.subUrl);
       drawQR(this.app.subUrl);
       try {
       try {
-        new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 });
+        const elJson = document.getElementById('qrcode-subjson');
+        if (elJson && this.app.subJsonUrl) {
+          new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 });
+        }
       } catch (e) { /* ignore */ }
       } catch (e) { /* ignore */ }
       this._onResize = () => { this.viewportWidth = window.innerWidth; };
       this._onResize = () => { this.viewportWidth = window.innerWidth; };
       window.addEventListener('resize', this._onResize);
       window.addEventListener('resize', this._onResize);

+ 1 - 0
web/entity/entity.go

@@ -42,6 +42,7 @@ type AllSetting struct {
 	TwoFactorEnable             bool   `json:"twoFactorEnable" form:"twoFactorEnable"`
 	TwoFactorEnable             bool   `json:"twoFactorEnable" form:"twoFactorEnable"`
 	TwoFactorToken              string `json:"twoFactorToken" form:"twoFactorToken"`
 	TwoFactorToken              string `json:"twoFactorToken" form:"twoFactorToken"`
 	SubEnable                   bool   `json:"subEnable" form:"subEnable"`
 	SubEnable                   bool   `json:"subEnable" form:"subEnable"`
+	SubJsonEnable               bool   `json:"subJsonEnable" form:"subJsonEnable"`
 	SubTitle                    string `json:"subTitle" form:"subTitle"`
 	SubTitle                    string `json:"subTitle" form:"subTitle"`
 	SubListen                   string `json:"subListen" form:"subListen"`
 	SubListen                   string `json:"subListen" form:"subListen"`
 	SubPort                     int    `json:"subPort" form:"subPort"`
 	SubPort                     int    `json:"subPort" form:"subPort"`

+ 4 - 2
web/html/inbounds.html

@@ -736,10 +736,11 @@
       refreshing: false,
       refreshing: false,
       refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
       refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
       subSettings: {
       subSettings: {
-        enable: false,
+        enable: true,
         subTitle: '',
         subTitle: '',
         subURI: '',
         subURI: '',
         subJsonURI: '',
         subJsonURI: '',
+        subJsonEnable: false,
       },
       },
       remarkModel: '-ieo',
       remarkModel: '-ieo',
       datepicker: 'gregorian',
       datepicker: 'gregorian',
@@ -795,7 +796,8 @@
             enable: subEnable,
             enable: subEnable,
             subTitle: subTitle,
             subTitle: subTitle,
             subURI: subURI,
             subURI: subURI,
-            subJsonURI: subJsonURI
+            subJsonURI: subJsonURI,
+            subJsonEnable: subJsonEnable,
           };
           };
           this.pageSize = pageSize;
           this.pageSize = pageSize;
           this.remarkModel = remarkModel;
           this.remarkModel = remarkModel;

+ 2 - 2
web/html/modals/inbound_info_modal.html

@@ -308,7 +308,7 @@
           </tr-info-title>
           </tr-info-title>
           <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
           <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
         </tr-info-row>
         </tr-info-row>
-        <tr-info-row class="tr-info-row">
+        <tr-info-row class="tr-info-row" v-if="app.subSettings.subJsonEnable">
           <tr-info-title class="tr-info-title">
           <tr-info-title class="tr-info-title">
             <a-tag color="purple">Json Link</a-tag>
             <a-tag color="purple">Json Link</a-tag>
             <a-tooltip title='{{ i18n "copy" }}'>
             <a-tooltip title='{{ i18n "copy" }}'>
@@ -548,7 +548,7 @@
       if (this.clientSettings) {
       if (this.clientSettings) {
         if (this.clientSettings.subId) {
         if (this.clientSettings.subId) {
           this.subLink = this.genSubLink(this.clientSettings.subId);
           this.subLink = this.genSubLink(this.clientSettings.subId);
-          this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
+          this.subJsonLink = app.subSettings.subJsonEnable ? this.genSubJsonLink(this.clientSettings.subId) : '';
         }
         }
       }
       }
       this.visible = true;
       this.visible = true;

+ 4 - 2
web/html/modals/qrcode_modal.html

@@ -30,7 +30,7 @@
           </tr-qr-bg-inner>
           </tr-qr-bg-inner>
         </tr-qr-bg>
         </tr-qr-bg>
       </tr-qr-box>
       </tr-qr-box>
-      <tr-qr-box class="qr-box">
+      <tr-qr-box class="qr-box" v-if="app.subSettings.subJsonEnable">
         <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag>
         <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag>
         <tr-qr-bg class="qr-bg-sub">
         <tr-qr-bg class="qr-bg-sub">
           <tr-qr-bg-inner class="qr-bg-sub-inner">
           <tr-qr-bg-inner class="qr-bg-sub-inner">
@@ -262,7 +262,9 @@
       if (qrModal.client && qrModal.client.subId) {
       if (qrModal.client && qrModal.client.subId) {
         qrModal.subId = qrModal.client.subId;
         qrModal.subId = qrModal.client.subId;
         this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
         this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
-        this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
+        if (app.subSettings.subJsonEnable) {
+          this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
+        }
       }
       }
       qrModal.qrcodes.forEach((element, index) => {
       qrModal.qrcodes.forEach((element, index) => {
         this.setQrCode("qrCode-" + index, element.link);
         this.setQrCode("qrCode-" + index, element.link);

+ 3 - 1
web/html/settings.html

@@ -79,7 +79,7 @@
                     </template>
                     </template>
                     {{ template "settings/panel/subscription/general" . }}
                     {{ template "settings/panel/subscription/general" . }}
                   </a-tab-pane>
                   </a-tab-pane>
-                  <a-tab-pane key="5" v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }">
+                  <a-tab-pane key="5" v-if="allSetting.subJsonEnable" :style="{ paddingTop: '20px' }">
                     <template #tab>
                     <template #tab>
                       <a-icon type="code"></a-icon>
                       <a-icon type="code"></a-icon>
                       <span>{{ i18n "pages.settings.subSettings" }} (JSON)</span>
                       <span>{{ i18n "pages.settings.subSettings" }} (JSON)</span>
@@ -523,6 +523,8 @@
           if (this.allSetting.subEnable) {
           if (this.allSetting.subEnable) {
             subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
             subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
             if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
             if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
+          }
+          if (this.allSetting.subJsonEnable) {
             subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
             subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
             if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
             if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
           }
           }

+ 7 - 0
web/html/settings/panel/subscription/general.html

@@ -8,6 +8,13 @@
                 <a-switch v-model="allSetting.subEnable"></a-switch>
                 <a-switch v-model="allSetting.subEnable"></a-switch>
             </template>
             </template>
         </a-setting-list-item>
         </a-setting-list-item>
+        <a-setting-list-item paddings="small">
+            <template #title>JSON Subscription</template>
+            <template #description>{{ i18n "pages.settings.subJsonEnable"}}</template>
+            <template #control>
+                <a-switch v-model="allSetting.subJsonEnable"></a-switch>
+            </template>
+        </a-setting-list-item>
         <a-setting-list-item paddings="small">
         <a-setting-list-item paddings="small">
             <template #title>{{ i18n "pages.settings.subTitle"}}</template>
             <template #title>{{ i18n "pages.settings.subTitle"}}</template>
             <template #description>{{ i18n "pages.settings.subTitleDesc"}}</template>
             <template #description>{{ i18n "pages.settings.subTitleDesc"}}</template>

+ 2 - 2
web/html/settings/panel/subscription/subpage.html

@@ -56,7 +56,7 @@
                             <a-space direction="vertical" align="center">
                             <a-space direction="vertical" align="center">
                                 <a-row type="flex" :gutter="[8,8]"
                                 <a-row type="flex" :gutter="[8,8]"
                                     justify="center" style="width:100%">
                                     justify="center" style="width:100%">
-                                    <a-col :xs="24" :sm="12"
+                                    <a-col :xs="24" :sm="app.subJsonUrl ? 12 : 24"
                                         style="text-align:center;">
                                         style="text-align:center;">
                                         <tr-qr-box class="qr-box">
                                         <tr-qr-box class="qr-box">
                                             <a-tag color="purple"
                                             <a-tag color="purple"
@@ -75,7 +75,7 @@
                                             </tr-qr-bg>
                                             </tr-qr-bg>
                                         </tr-qr-box>
                                         </tr-qr-box>
                                     </a-col>
                                     </a-col>
-                                    <a-col :xs="24" :sm="12"
+                                    <a-col v-if="app.subJsonUrl" :xs="24" :sm="12"
                                         style="text-align:center;">
                                         style="text-align:center;">
                                         <tr-qr-box class="qr-box">
                                         <tr-qr-box class="qr-box">
                                             <a-tag color="purple"
                                             <a-tag color="purple"

+ 17 - 4
web/service/setting.go

@@ -50,7 +50,8 @@ var defaultValueMap = map[string]string{
 	"tgLang":                      "en-US",
 	"tgLang":                      "en-US",
 	"twoFactorEnable":             "false",
 	"twoFactorEnable":             "false",
 	"twoFactorToken":              "",
 	"twoFactorToken":              "",
-	"subEnable":                   "false",
+	"subEnable":                   "true",
+	"subJsonEnable":               "false",
 	"subTitle":                    "",
 	"subTitle":                    "",
 	"subListen":                   "",
 	"subListen":                   "",
 	"subPort":                     "2096",
 	"subPort":                     "2096",
@@ -427,6 +428,10 @@ func (s *SettingService) GetSubEnable() (bool, error) {
 	return s.getBool("subEnable")
 	return s.getBool("subEnable")
 }
 }
 
 
+func (s *SettingService) GetSubJsonEnable() (bool, error) {
+	return s.getBool("subJsonEnable")
+}
+
 func (s *SettingService) GetSubTitle() (string, error) {
 func (s *SettingService) GetSubTitle() (string, error) {
 	return s.getString("subTitle")
 	return s.getString("subTitle")
 }
 }
@@ -575,6 +580,7 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
 		"defaultKey":    func() (any, error) { return s.GetKeyFile() },
 		"defaultKey":    func() (any, error) { return s.GetKeyFile() },
 		"tgBotEnable":   func() (any, error) { return s.GetTgbotEnabled() },
 		"tgBotEnable":   func() (any, error) { return s.GetTgbotEnabled() },
 		"subEnable":     func() (any, error) { return s.GetSubEnable() },
 		"subEnable":     func() (any, error) { return s.GetSubEnable() },
+		"subJsonEnable": func() (any, error) { return s.GetSubJsonEnable() },
 		"subTitle":      func() (any, error) { return s.GetSubTitle() },
 		"subTitle":      func() (any, error) { return s.GetSubTitle() },
 		"subURI":        func() (any, error) { return s.GetSubURI() },
 		"subURI":        func() (any, error) { return s.GetSubURI() },
 		"subJsonURI":    func() (any, error) { return s.GetSubJsonURI() },
 		"subJsonURI":    func() (any, error) { return s.GetSubJsonURI() },
@@ -593,7 +599,14 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
 		result[key] = value
 		result[key] = value
 	}
 	}
 
 
-	if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
+	subEnable := result["subEnable"].(bool)
+	subJsonEnable := false
+	if v, ok := result["subJsonEnable"]; ok {
+		if b, ok2 := v.(bool); ok2 {
+			subJsonEnable = b
+		}
+	}
+	if (subEnable && result["subURI"].(string) == "") || (subJsonEnable && result["subJsonURI"].(string) == "") {
 		subURI := ""
 		subURI := ""
 		subTitle, _ := s.GetSubTitle()
 		subTitle, _ := s.GetSubTitle()
 		subPort, _ := s.GetSubPort()
 		subPort, _ := s.GetSubPort()
@@ -619,13 +632,13 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
 		} else {
 		} else {
 			subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
 			subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
 		}
 		}
-		if result["subURI"].(string) == "" {
+		if subEnable && result["subURI"].(string) == "" {
 			result["subURI"] = subURI + subPath
 			result["subURI"] = subURI + subPath
 		}
 		}
 		if result["subTitle"].(string) == "" {
 		if result["subTitle"].(string) == "" {
 			result["subTitle"] = subTitle
 			result["subTitle"] = subTitle
 		}
 		}
-		if result["subJsonURI"].(string) == "" {
+		if subJsonEnable && result["subJsonURI"].(string) == "" {
 			result["subJsonURI"] = subURI + subJsonPath
 			result["subJsonURI"] = subURI + subJsonPath
 		}
 		}
 	}
 	}

+ 19 - 11
web/service/tgbot.go

@@ -2093,6 +2093,7 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
 	subPort, _ := t.settingService.GetSubPort()
 	subPort, _ := t.settingService.GetSubPort()
 	subPath, _ := t.settingService.GetSubPath()
 	subPath, _ := t.settingService.GetSubPath()
 	subJsonPath, _ := t.settingService.GetSubJsonPath()
 	subJsonPath, _ := t.settingService.GetSubJsonPath()
+	subJsonEnable, _ := t.settingService.GetSubJsonEnable()
 	subKeyFile, _ := t.settingService.GetSubKeyFile()
 	subKeyFile, _ := t.settingService.GetSubKeyFile()
 	subCertFile, _ := t.settingService.GetSubCertFile()
 	subCertFile, _ := t.settingService.GetSubCertFile()
 
 
@@ -2137,6 +2138,9 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
 
 
 	subURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subPath, client.SubID)
 	subURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subPath, client.SubID)
 	subJsonURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID)
 	subJsonURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID)
+	if !subJsonEnable {
+		subJsonURL = ""
+	}
 	return subURL, subJsonURL, nil
 	return subURL, subJsonURL, nil
 }
 }
 
 
@@ -2146,8 +2150,10 @@ func (t *Tgbot) sendClientSubLinks(chatId int64, email string) {
 		t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
 		t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
 		return
 		return
 	}
 	}
-	msg := "Subscription URL:\r\n<code>" + subURL + "</code>\r\n\r\n" +
-		"JSON URL:\r\n<code>" + subJsonURL + "</code>"
+	msg := "Subscription URL:\r\n<code>" + subURL + "</code>"
+	if subJsonURL != "" {
+		msg += "\r\n\r\nJSON URL:\r\n<code>" + subJsonURL + "</code>"
+	}
 	inlineKeyboard := tu.InlineKeyboard(
 	inlineKeyboard := tu.InlineKeyboard(
 		tu.InlineKeyboardRow(
 		tu.InlineKeyboardRow(
 			tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links "+email)),
 			tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links "+email)),
@@ -2271,15 +2277,17 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
 		t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
 		t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
 	}
 	}
 
 
-	// Send JSON URL QR (filename: subjson.png)
-	if png, err := createQR(subJsonURL, 320); err == nil {
-		document := tu.Document(
-			tu.ID(chatId),
-			tu.FileFromBytes(png, "subjson.png"),
-		)
-		_, _ = bot.SendDocument(context.Background(), document)
-	} else {
-		t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
+	// Send JSON URL QR (filename: subjson.png) when available
+	if subJsonURL != "" {
+		if png, err := createQR(subJsonURL, 320); err == nil {
+			document := tu.Document(
+				tu.ID(chatId),
+				tu.FileFromBytes(png, "subjson.png"),
+			)
+			_, _ = bot.SendDocument(context.Background(), document)
+		} else {
+			t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
+		}
 	}
 	}
 
 
 	// Also generate a few individual links' QRs (first up to 5)
 	// Also generate a few individual links' QRs (first up to 5)

+ 1 - 0
web/translation/translate.ar_EG.toml

@@ -371,6 +371,7 @@
 "subSettings" = "الاشتراك"
 "subSettings" = "الاشتراك"
 "subEnable" = "تفعيل خدمة الاشتراك"
 "subEnable" = "تفعيل خدمة الاشتراك"
 "subEnableDesc" = "يفعل خدمة الاشتراك."
 "subEnableDesc" = "يفعل خدمة الاشتراك."
+"subJsonEnable" = "تمكين/تعطيل نقطة نهاية اشتراك JSON بشكل مستقل."
 "subTitle" = "عنوان الاشتراك"
 "subTitle" = "عنوان الاشتراك"
 "subTitleDesc" = "العنوان اللي هيظهر في عميل VPN"
 "subTitleDesc" = "العنوان اللي هيظهر في عميل VPN"
 "subListen" = "IP الاستماع"
 "subListen" = "IP الاستماع"

+ 3 - 2
web/translation/translate.en_US.toml

@@ -369,8 +369,9 @@
 "timeZone" = "Time Zone"
 "timeZone" = "Time Zone"
 "timeZoneDesc" = "Scheduled tasks will run based on this time zone."
 "timeZoneDesc" = "Scheduled tasks will run based on this time zone."
 "subSettings" = "Subscription"
 "subSettings" = "Subscription"
-"subEnable" = "Enable Subscription Service"
-"subEnableDesc" = "Enables the subscription service."
+"subEnable" = "Subscription Service"
+"subEnableDesc" = "Enable/Disable the subscription service."
+"subJsonEnable" = "Enable/Disable the JSON subscription endpoint independently."
 "subTitle" = "Subscription Title"
 "subTitle" = "Subscription Title"
 "subTitleDesc" = "Title shown in VPN client"
 "subTitleDesc" = "Title shown in VPN client"
 "subListen" = "Listen IP"
 "subListen" = "Listen IP"

+ 1 - 0
web/translation/translate.es_ES.toml

@@ -371,6 +371,7 @@
 "subSettings" = "Suscripción"
 "subSettings" = "Suscripción"
 "subEnable" = "Habilitar Servicio"
 "subEnable" = "Habilitar Servicio"
 "subEnableDesc" = "Función de suscripción con configuración separada."
 "subEnableDesc" = "Función de suscripción con configuración separada."
+"subJsonEnable" = "Habilitar/Deshabilitar el endpoint de suscripción JSON de forma independiente."
 "subTitle" = "Título de la Suscripción"
 "subTitle" = "Título de la Suscripción"
 "subTitleDesc" = "Título mostrado en el cliente de VPN"
 "subTitleDesc" = "Título mostrado en el cliente de VPN"
 "subListen" = "Listening IP"
 "subListen" = "Listening IP"

+ 1 - 0
web/translation/translate.fa_IR.toml

@@ -371,6 +371,7 @@
 "subSettings" = "سابسکریپشن"
 "subSettings" = "سابسکریپشن"
 "subEnable" = "فعال‌سازی سرویس سابسکریپشن"
 "subEnable" = "فعال‌سازی سرویس سابسکریپشن"
 "subEnableDesc" = "سرویس سابسکریپشن‌ را فعال‌می‌کند"
 "subEnableDesc" = "سرویس سابسکریپشن‌ را فعال‌می‌کند"
+"subJsonEnable" = "فعال/غیرفعال‌سازی مستقل نقطه دسترسی سابسکریپشن JSON."
 "subTitle" = "عنوان اشتراک"
 "subTitle" = "عنوان اشتراک"
 "subTitleDesc" = "عنوان نمایش داده شده در کلاینت VPN"
 "subTitleDesc" = "عنوان نمایش داده شده در کلاینت VPN"
 "subListen" = "آدرس آی‌پی"
 "subListen" = "آدرس آی‌پی"

+ 1 - 0
web/translation/translate.id_ID.toml

@@ -371,6 +371,7 @@
 "subSettings" = "Langganan"
 "subSettings" = "Langganan"
 "subEnable" = "Aktifkan Layanan Langganan"
 "subEnable" = "Aktifkan Layanan Langganan"
 "subEnableDesc" = "Mengaktifkan layanan langganan."
 "subEnableDesc" = "Mengaktifkan layanan langganan."
+"subJsonEnable" = "Aktifkan/Nonaktifkan endpoint langganan JSON secara mandiri."
 "subTitle" = "Judul Langganan"
 "subTitle" = "Judul Langganan"
 "subTitleDesc" = "Judul yang ditampilkan di klien VPN"
 "subTitleDesc" = "Judul yang ditampilkan di klien VPN"
 "subListen" = "IP Pendengar"
 "subListen" = "IP Pendengar"

+ 1 - 0
web/translation/translate.ja_JP.toml

@@ -371,6 +371,7 @@
 "subSettings" = "サブスクリプション設定"
 "subSettings" = "サブスクリプション設定"
 "subEnable" = "サブスクリプションサービスを有効にする"
 "subEnable" = "サブスクリプションサービスを有効にする"
 "subEnableDesc" = "サブスクリプションサービス機能を有効にする"
 "subEnableDesc" = "サブスクリプションサービス機能を有効にする"
+"subJsonEnable" = "JSON サブスクリプションのエンドポイントを個別に有効/無効にする。"
 "subTitle" = "サブスクリプションタイトル"
 "subTitle" = "サブスクリプションタイトル"
 "subTitleDesc" = "VPNクライアントに表示されるタイトル"
 "subTitleDesc" = "VPNクライアントに表示されるタイトル"
 "subListen" = "監視IP"
 "subListen" = "監視IP"

+ 1 - 0
web/translation/translate.pt_BR.toml

@@ -371,6 +371,7 @@
 "subSettings" = "Assinatura"
 "subSettings" = "Assinatura"
 "subEnable" = "Ativar Serviço de Assinatura"
 "subEnable" = "Ativar Serviço de Assinatura"
 "subEnableDesc" = "Ativa o serviço de assinatura."
 "subEnableDesc" = "Ativa o serviço de assinatura."
+"subJsonEnable" = "Ativar/Desativar o endpoint de assinatura JSON de forma independente."
 "subTitle" = "Título da Assinatura"
 "subTitle" = "Título da Assinatura"
 "subTitleDesc" = "Título exibido no cliente VPN"
 "subTitleDesc" = "Título exibido no cliente VPN"
 "subListen" = "IP de Escuta"
 "subListen" = "IP de Escuta"

+ 1 - 0
web/translation/translate.ru_RU.toml

@@ -371,6 +371,7 @@
 "subSettings" = "Подписка"
 "subSettings" = "Подписка"
 "subEnable" = "Включить подписку"
 "subEnable" = "Включить подписку"
 "subEnableDesc" = "Функция подписки с отдельной конфигурацией"
 "subEnableDesc" = "Функция подписки с отдельной конфигурацией"
+"subJsonEnable" = "Включить/отключить JSON-эндпоинт подписки независимо."
 "subTitle" = "Заголовок подписки"
 "subTitle" = "Заголовок подписки"
 "subTitleDesc" = "Название подписки, которое видит клиент в VPN клиенте"
 "subTitleDesc" = "Название подписки, которое видит клиент в VPN клиенте"
 "subListen" = "Прослушивание IP"
 "subListen" = "Прослушивание IP"

+ 1 - 0
web/translation/translate.tr_TR.toml

@@ -371,6 +371,7 @@
 "subSettings" = "Abonelik"
 "subSettings" = "Abonelik"
 "subEnable" = "Abonelik Hizmetini Etkinleştir"
 "subEnable" = "Abonelik Hizmetini Etkinleştir"
 "subEnableDesc" = "Abonelik hizmetini etkinleştirir."
 "subEnableDesc" = "Abonelik hizmetini etkinleştirir."
+"subJsonEnable" = "JSON abonelik uç noktasını bağımsız olarak Etkinleştir/Devre Dışı bırak."
 "subTitle" = "Abonelik Başlığı"
 "subTitle" = "Abonelik Başlığı"
 "subTitleDesc" = "VPN istemcisinde gösterilen başlık"
 "subTitleDesc" = "VPN istemcisinde gösterilen başlık"
 "subListen" = "Dinleme IP"
 "subListen" = "Dinleme IP"

+ 1 - 0
web/translation/translate.uk_UA.toml

@@ -371,6 +371,7 @@
 "subSettings" = "Підписка"
 "subSettings" = "Підписка"
 "subEnable" = "Увімкнути службу підписки"
 "subEnable" = "Увімкнути службу підписки"
 "subEnableDesc" = "Вмикає службу підписки."
 "subEnableDesc" = "Вмикає службу підписки."
+"subJsonEnable" = "Увімкнути/вимкнути JSON-кінець підписки незалежно."
 "subTitle" = "Назва Підписки"
 "subTitle" = "Назва Підписки"
 "subTitleDesc" = "Назва, яка відображається у VPN-клієнті"
 "subTitleDesc" = "Назва, яка відображається у VPN-клієнті"
 "subListen" = "Слухати IP"
 "subListen" = "Слухати IP"

+ 1 - 0
web/translation/translate.vi_VN.toml

@@ -371,6 +371,7 @@
 "subSettings" = "Gói đăng ký"
 "subSettings" = "Gói đăng ký"
 "subEnable" = "Bật dịch vụ"
 "subEnable" = "Bật dịch vụ"
 "subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
 "subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
+"subJsonEnable" = "Bật/Tắt điểm cuối đăng ký JSON độc lập."
 "subTitle" = "Tiêu đề Đăng ký"
 "subTitle" = "Tiêu đề Đăng ký"
 "subTitleDesc" = "Tiêu đề hiển thị trong ứng dụng VPN"
 "subTitleDesc" = "Tiêu đề hiển thị trong ứng dụng VPN"
 "subListen" = "Listening IP"
 "subListen" = "Listening IP"

+ 1 - 0
web/translation/translate.zh_CN.toml

@@ -371,6 +371,7 @@
 "subSettings" = "订阅设置"
 "subSettings" = "订阅设置"
 "subEnable" = "启用订阅服务"
 "subEnable" = "启用订阅服务"
 "subEnableDesc" = "启用订阅服务功能"
 "subEnableDesc" = "启用订阅服务功能"
+"subJsonEnable" = "单独启用/禁用 JSON 订阅端点。"
 "subTitle" = "订阅标题"
 "subTitle" = "订阅标题"
 "subTitleDesc" = "在VPN客户端中显示的标题"
 "subTitleDesc" = "在VPN客户端中显示的标题"
 "subListen" = "监听 IP"
 "subListen" = "监听 IP"

+ 1 - 0
web/translation/translate.zh_TW.toml

@@ -371,6 +371,7 @@
 "subSettings" = "訂閱設定"
 "subSettings" = "訂閱設定"
 "subEnable" = "啟用訂閱服務"
 "subEnable" = "啟用訂閱服務"
 "subEnableDesc" = "啟用訂閱服務功能"
 "subEnableDesc" = "啟用訂閱服務功能"
+"subJsonEnable" = "獨立啟用/停用 JSON 訂閱端點。"
 "subTitle" = "訂閱標題"
 "subTitle" = "訂閱標題"
 "subTitleDesc" = "在VPN客戶端中顯示的標題"
 "subTitleDesc" = "在VPN客戶端中顯示的標題"
 "subListen" = "監聽 IP"
 "subListen" = "監聽 IP"