Jelajahi Sumber

feat: add configurable auto-restart on client auto-disable

Add a configurable option to restart Xray when clients are auto-disabled and persist disable actions.

Changes include:
- New setting restartXrayOnClientDisable (default true), getters/setters in SettingService, UI toggle in general settings, and translations for multiple locales.
- AddTraffic signature updated to return a third bool (clientsDisabled). disableInvalidClients now calls Xray API to remove users, marks client_traffics.enable=false, updates inbound.Settings JSON so clients appear disabled in stored settings, and returns appropriate counts/errors.
- XrayTrafficJob now checks the clientsDisabled flag and restarts Xray when the setting is enabled (with fallback to mark Xray as needing restart on failure).
- XrayService.GetXrayConfig call adjusted to ignore AddTraffic returns.
- Subscription generation (subService/subJson/subClash) no longer filters clients by their enable flag when matching subId.
- Minor fixes: check_client_ip_job now checks scanner.Err and improved API error handling/logging.

These changes ensure auto-disabled clients are propagated to Xray and the stored inbound settings, and provide an option to restart Xray automatically after auto-disable events.
MHSanaei 1 hari lalu
induk
melakukan
6099a07ff0

+ 1 - 1
sub/subClashService.go

@@ -55,7 +55,7 @@ func (s *SubClashService) GetClash(subId string, host string) (string, string, e
 			}
 		}
 		for _, client := range clients {
-			if client.Enable && client.SubID == subId {
+			if client.SubID == subId {
 				clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
 				proxies = append(proxies, s.getProxies(inbound, client, host)...)
 			}

+ 1 - 1
sub/subJsonService.go

@@ -116,7 +116,7 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
 		}
 
 		for _, client := range clients {
-			if client.Enable && client.SubID == subId {
+			if client.SubID == subId {
 				clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
 				newConfigs := s.getConfig(inbound, client, host)
 				configArray = append(configArray, newConfigs...)

+ 1 - 1
sub/subService.go

@@ -77,7 +77,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
 			}
 		}
 		for _, client := range clients {
-			if client.Enable && client.SubID == subId {
+			if client.SubID == subId {
 				link := s.getLink(inbound, client.Email)
 				result = append(result, link)
 				ct := s.getClientTraffics(inbound.ClientStats, client.Email)

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

@@ -43,6 +43,7 @@ class AllSetting {
         this.subDomain = "";
         this.externalTrafficInformEnable = false;
         this.externalTrafficInformURI = "";
+        this.restartXrayOnClientDisable = true;
         this.subCertFile = "";
         this.subKeyFile = "";
         this.subUpdates = 12;

+ 1 - 0
web/entity/entity.go

@@ -71,6 +71,7 @@ type AllSetting struct {
 	SubUpdates                  int    `json:"subUpdates" form:"subUpdates"`                                   // Subscription update interval in minutes
 	ExternalTrafficInformEnable bool   `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"` // Enable external traffic reporting
 	ExternalTrafficInformURI    string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"`       // URI for external traffic reporting
+	RestartXrayOnClientDisable  bool   `json:"restartXrayOnClientDisable" form:"restartXrayOnClientDisable"`   // Restart Xray when clients are auto-disabled by expiry/traffic limit
 	SubEncrypt                  bool   `json:"subEncrypt" form:"subEncrypt"`                                   // Encrypt subscription responses
 	SubShowInfo                 bool   `json:"subShowInfo" form:"subShowInfo"`                                 // Show client information in subscriptions
 	SubURI                      string `json:"subURI" form:"subURI"`                                           // Subscription server URI

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

@@ -124,6 +124,13 @@
                     v-model="allSetting.externalTrafficInformURI"></a-input>
             </template>
         </a-setting-list-item>
+        <a-setting-list-item paddings="small">
+            <template #title>{{ i18n "pages.settings.restartXrayOnClientDisable"}}</template>
+            <template #description>{{ i18n "pages.settings.restartXrayOnClientDisableDesc"}}</template>
+            <template #control>
+                <a-switch v-model="allSetting.restartXrayOnClientDisable"></a-switch>
+            </template>
+        </a-setting-list-item>
     </a-collapse-panel>
     <a-collapse-panel key="5" header='{{ i18n "pages.settings.dateAndTime" }}'>
         <a-setting-list-item paddings="small">

+ 3 - 0
web/job/check_client_ip_job.go

@@ -199,6 +199,9 @@ func (j *CheckClientIpJob) processLogFile() bool {
 			inboundClientIps[email][ip] = timestamp
 		}
 	}
+	if err := scanner.Err(); err != nil {
+		j.checkError(err)
+	}
 
 	shouldCleanLog := false
 	for email, ipTimestamps := range inboundClientIps {

+ 13 - 1
web/job/xray_traffic_job.go

@@ -33,7 +33,7 @@ func (j *XrayTrafficJob) Run() {
 	if err != nil {
 		return
 	}
-	err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
+	err, needRestart0, clientsDisabled := j.inboundService.AddTraffic(traffics, clientTraffics)
 	if err != nil {
 		logger.Warning("add inbound traffic failed:", err)
 	}
@@ -41,6 +41,18 @@ func (j *XrayTrafficJob) Run() {
 	if err != nil {
 		logger.Warning("add outbound traffic failed:", err)
 	}
+	if clientsDisabled {
+		restartOnDisable, settingErr := j.settingService.GetRestartXrayOnClientDisable()
+		if settingErr != nil {
+			logger.Warning("get RestartXrayOnClientDisable failed:", settingErr)
+		}
+		if restartOnDisable {
+			if err := j.xrayService.RestartXray(true); err != nil {
+				logger.Warning("restart xray after disabling clients failed:", err)
+				j.xrayService.SetToNeedRestart()
+			}
+		}
+	}
 	if ExternalTrafficInformEnable, err := j.settingService.GetExternalTrafficInformEnable(); ExternalTrafficInformEnable {
 		j.informTrafficToExternalAPI(traffics, clientTraffics)
 	} else if err != nil {

+ 90 - 29
web/service/inbound.go

@@ -1228,7 +1228,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
 	return needRestart, tx.Save(oldInbound).Error
 }
 
-func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
+func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool, bool) {
 	var err error
 	db := database.GetDB()
 	tx := db.Begin()
@@ -1242,11 +1242,11 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
 	}()
 	err = s.addInboundTraffic(tx, inboundTraffics)
 	if err != nil {
-		return err, false
+		return err, false, false
 	}
 	err = s.addClientTraffic(tx, clientTraffics)
 	if err != nil {
-		return err, false
+		return err, false, false
 	}
 
 	needRestart0, count, err := s.autoRenewClients(tx)
@@ -1256,11 +1256,13 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
 		logger.Debugf("%v clients renewed", count)
 	}
 
+	disabledClientsCount := int64(0)
 	needRestart1, count, err := s.disableInvalidClients(tx)
 	if err != nil {
 		logger.Warning("Error in disabling invalid clients:", err)
 	} else if count > 0 {
 		logger.Debugf("%v clients disabled", count)
+		disabledClientsCount = count
 	}
 
 	needRestart2, count, err := s.disableInvalidInbounds(tx)
@@ -1269,7 +1271,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
 	} else if count > 0 {
 		logger.Debugf("%v inbounds disabled", count)
 	}
-	return nil, (needRestart0 || needRestart1 || needRestart2)
+	return nil, (needRestart0 || needRestart1 || needRestart2), disabledClientsCount > 0
 }
 
 func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
@@ -1546,46 +1548,105 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
 	now := time.Now().Unix() * 1000
 	needRestart := false
 
-	if p != nil {
-		var results []struct {
-			Tag   string
-			Email string
-		}
+	var clientsToDisable []struct {
+		InboundId int
+		Tag       string
+		Email     string
+	}
 
-		err := tx.Table("inbounds").
-			Select("inbounds.tag, client_traffics.email").
-			Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id").
-			Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true).
-			Scan(&results).Error
-		if err != nil {
-			return false, 0, err
-		}
+	err := tx.Table("inbounds").
+		Select("inbounds.id as inbound_id, inbounds.tag, client_traffics.email").
+		Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id").
+		Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true).
+		Scan(&clientsToDisable).Error
+	if err != nil {
+		return false, 0, err
+	}
+
+	if p != nil {
 		s.xrayApi.Init(p.GetAPIPort())
-		for _, result := range results {
-			err1 := s.xrayApi.RemoveUser(result.Tag, result.Email)
+		for _, client := range clientsToDisable {
+			err1 := s.xrayApi.RemoveUser(client.Tag, client.Email)
 			if err1 == nil {
-				logger.Debug("Client disabled by api:", result.Email)
+				logger.Debug("Client disabled by api:", client.Email)
 			} else {
-				if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) {
+				if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", client.Email)) {
 					logger.Debug("User is already disabled. Nothing to do more...")
 				} else {
-					if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) {
-						logger.Debug("User is already disabled. Nothing to do more...")
-					} else {
-						logger.Debug("Error in disabling client by api:", err1)
-						needRestart = true
-					}
+					logger.Debug("Error in disabling client by api:", err1)
+					needRestart = true
 				}
 			}
 		}
 		s.xrayApi.Close()
 	}
+
 	result := tx.Model(xray.ClientTraffic{}).
 		Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
 		Update("enable", false)
-	err := result.Error
+	err = result.Error
 	count := result.RowsAffected
-	return needRestart, count, err
+	if err != nil {
+		return needRestart, count, err
+	}
+
+	// Also set enable=false in inbounds.settings JSON so clients are visibly disabled
+	if len(clientsToDisable) > 0 {
+		inboundEmailMap := make(map[int]map[string]struct{})
+		for _, c := range clientsToDisable {
+			if inboundEmailMap[c.InboundId] == nil {
+				inboundEmailMap[c.InboundId] = make(map[string]struct{})
+			}
+			inboundEmailMap[c.InboundId][c.Email] = struct{}{}
+		}
+		inboundIds := make([]int, 0, len(inboundEmailMap))
+		for id := range inboundEmailMap {
+			inboundIds = append(inboundIds, id)
+		}
+		var inbounds []*model.Inbound
+		if err = tx.Model(model.Inbound{}).Where("id IN ?", inboundIds).Find(&inbounds).Error; err != nil {
+			logger.Warning("disableInvalidClients fetch inbounds:", err)
+			return needRestart, count, nil
+		}
+		for _, inbound := range inbounds {
+			settings := map[string]any{}
+			if jsonErr := json.Unmarshal([]byte(inbound.Settings), &settings); jsonErr != nil {
+				continue
+			}
+			clients, ok := settings["clients"].([]any)
+			if !ok {
+				continue
+			}
+			emailSet := inboundEmailMap[inbound.Id]
+			changed := false
+			for i := range clients {
+				c, ok := clients[i].(map[string]any)
+				if !ok {
+					continue
+				}
+				email, _ := c["email"].(string)
+				if _, shouldDisable := emailSet[email]; shouldDisable {
+					c["enable"] = false
+					c["updated_at"] = time.Now().Unix() * 1000
+					clients[i] = c
+					changed = true
+				}
+			}
+			if changed {
+				settings["clients"] = clients
+				modifiedSettings, jsonErr := json.MarshalIndent(settings, "", "  ")
+				if jsonErr != nil {
+					continue
+				}
+				inbound.Settings = string(modifiedSettings)
+			}
+		}
+		if err = tx.Save(inbounds).Error; err != nil {
+			logger.Warning("disableInvalidClients update inbound settings:", err)
+		}
+	}
+
+	return needRestart, count, nil
 }
 
 func (s *InboundService) GetInboundTags() (string, error) {

+ 9 - 0
web/service/setting.go

@@ -83,6 +83,7 @@ var defaultValueMap = map[string]string{
 	"nord":                        "",
 	"externalTrafficInformEnable": "false",
 	"externalTrafficInformURI":    "",
+	"restartXrayOnClientDisable":  "true",
 	"xrayOutboundTestUrl":         "https://www.google.com/generate_204",
 
 	// LDAP defaults
@@ -628,6 +629,14 @@ func (s *SettingService) SetExternalTrafficInformURI(InformURI string) error {
 	return s.setString("externalTrafficInformURI", InformURI)
 }
 
+func (s *SettingService) GetRestartXrayOnClientDisable() (bool, error) {
+	return s.getBool("restartXrayOnClientDisable")
+}
+
+func (s *SettingService) SetRestartXrayOnClientDisable(value bool) error {
+	return s.setBool("restartXrayOnClientDisable", value)
+}
+
 func (s *SettingService) GetIpLimitEnable() (bool, error) {
 	accessLogPath, err := xray.GetAccessLogPath()
 	if err != nil {

+ 1 - 1
web/service/xray.go

@@ -103,7 +103,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
 		return nil, err
 	}
 
-	s.inboundService.AddTraffic(nil, nil)
+	_, _, _ = s.inboundService.AddTraffic(nil, nil)
 
 	inbounds, err := s.inboundService.GetAllInbounds()
 	if err != nil {

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "يبعت تنبيه لـ API خارجي مع كل تحديث للترافيك."
 "externalTrafficInformURI" = "مسار تنبيه الترافيك الخارجي"
 "externalTrafficInformURIDesc" = "تحديثات الترافيك هتتبعت للمسار ده."
+"restartXrayOnClientDisable" = "إعادة تشغيل Xray بعد التعطيل التلقائي"
+"restartXrayOnClientDisableDesc" = "عند تعطيل العميل تلقائيا بسبب انتهاء الصلاحية أو حد حركة المرور، أعد تشغيل Xray."
 "fragment" = "تجزئة"
 "fragmentDesc" = "يفعل تجزئة لحزمة TLS hello."
 "fragmentSett" = "إعدادات التجزئة"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
 "externalTrafficInformURI" = "External Traffic Inform URI"
 "externalTrafficInformURIDesc" = "Traffic updates are sent to this URI."
+"restartXrayOnClientDisable" = "Restart Xray After Auto Disable"
+"restartXrayOnClientDisableDesc" = "When a client is automatically disabled due to expiration or traffic limit, restart Xray."
 "fragment" = "Fragmentation"
 "fragmentDesc" = "Enable fragmentation for TLS hello packet."
 "fragmentSett" = "Fragmentation Settings"

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

@@ -476,6 +476,8 @@
 "externalTrafficInformEnableDesc" = "Informar a la API externa sobre cada actualización de tráfico."
 "externalTrafficInformURI" = "URI de información de tráfico externo"
 "externalTrafficInformURIDesc" = "Las actualizaciones de tráfico se envían a este URI."
+"restartXrayOnClientDisable" = "Reiniciar Xray tras desactivación automática"
+"restartXrayOnClientDisableDesc" = "Cuando un cliente se desactive automáticamente por vencimiento o límite de tráfico, reiniciar Xray."
 "subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
 "fragment" = "Fragmentación"
 "fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "مصرف ترافیک به سرویس خارجی ارسال می شود"
 "externalTrafficInformURI" = "لینک اطلاع رسانی خارجی مصرف ترافیک"
 "externalTrafficInformURIDesc" = "ترافیک های مصرفی به این لینک هم ارسال می شود"
+"restartXrayOnClientDisable" = "ری‌استارت Xray بعد از غیرفعال‌سازی خودکار"
+"restartXrayOnClientDisableDesc" = "وقتی کاربر به‌صورت خودکار به‌دلیل اتمام زمان یا ترافیک غیرفعال می‌شود، Xray ری‌استارت شود."
 "fragment" = "فرگمنت"
 "fragmentDesc" = "فعال کردن فرگمنت برای بسته‌ی نخست تی‌ال‌اس"
 "fragmentSett" = "تنظیمات فرگمنت"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
 "externalTrafficInformURI" = "Lalu Lintas Eksternal Menginformasikan URI"
 "externalTrafficInformURIDesc" = "Pembaruan lalu lintas dikirim ke URI ini."
+"restartXrayOnClientDisable" = "Nyalakan Ulang Xray Setelah Nonaktif Otomatis"
+"restartXrayOnClientDisableDesc" = "Saat klien otomatis dinonaktifkan karena kedaluwarsa atau batas trafik, mulai ulang Xray."
 "fragment" = "Fragmentasi"
 "fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
 "fragmentSett" = "Pengaturan Fragmentasi"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "トラフィックの更新ごとに外部 API に通知します。"
 "externalTrafficInformURI" = "外部トラフィック通知 URI"
 "externalTrafficInformURIDesc" = "トラフィックの更新ごとに外部 API に通知します。"
+"restartXrayOnClientDisable" = "自動無効化後に Xray を再起動"
+"restartXrayOnClientDisableDesc" = "有効期限切れまたはトラフィック上限でクライアントが自動的に無効化されたとき、Xray を再起動します。"
 "fragment" = "フラグメント"
 "fragmentDesc" = "TLS helloパケットのフラグメントを有効にする"
 "fragmentSett" = "設定"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "Informar a API externa sobre cada atualização de tráfego."
 "externalTrafficInformURI" = "URI de informação de tráfego externo"
 "externalTrafficInformURIDesc" = "As atualizações de tráfego são enviadas para este URI."
+"restartXrayOnClientDisable" = "Reiniciar Xray Após Desativação Automática"
+"restartXrayOnClientDisableDesc" = "Quando um cliente for desativado automaticamente por expiração ou limite de tráfego, reinicie o Xray."
 "fragment" = "Fragmentação"
 "fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello."
 "fragmentSett" = "Configurações de Fragmentação"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика"
 "externalTrafficInformURI" = "URI информации о внешнем трафике"
 "externalTrafficInformURIDesc" = "Обновления трафика отправляются на этот URI"
+"restartXrayOnClientDisable" = "Перезапускать Xray после автоотключения"
+"restartXrayOnClientDisableDesc" = "Когда клиент автоматически отключается из-за окончания срока действия или лимита трафика, перезапускать Xray."
 "fragment" = "Фрагментация"
 "fragmentDesc" = "Включить фрагментацию TLS-хэндшейка"
 "fragmentSett" = "Настройки фрагментации"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "Her trafik güncellemesinde harici API'yi bilgilendirin."
 "externalTrafficInformURI" = "Harici Trafik Bilgisi URI'si"
 "externalTrafficInformURIDesc" = "Trafik güncellemeleri bu URI'ye gönderildi."
+"restartXrayOnClientDisable" = "Otomatik Devre Dışı Sonrası Xray'i Yeniden Başlat"
+"restartXrayOnClientDisableDesc" = "Bir istemci süre dolumu veya trafik limiti nedeniyle otomatik devre dışı bırakıldığında Xray'i yeniden başlat."
 "fragment" = "Parçalama"
 "fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir."
 "fragmentSett" = "Parçalama Ayarları"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "Інформувати зовнішній API про кожне оновлення трафіку."
 "externalTrafficInformURI" = "Інформаційний URI зовнішнього трафіку"
 "externalTrafficInformURIDesc" = "Оновлення трафіку надсилаються на цей URI."
+"restartXrayOnClientDisable" = "Перезапускати Xray після авто-вимкнення"
+"restartXrayOnClientDisableDesc" = "Коли клієнт автоматично вимикається через закінчення терміну дії або ліміт трафіку, перезапускати Xray."
 "fragment" = "Фрагментація"
 "fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS"
 "fragmentSett" = "Параметри фрагментації"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "Thông báo cho API bên ngoài về mọi cập nhật lưu lượng truy cập."
 "externalTrafficInformURI" = "URI thông báo lưu lượng truy cập bên ngoài"
 "externalTrafficInformURIDesc" = "Cập nhật lưu lượng truy cập được gửi tới URI này."
+"restartXrayOnClientDisable" = "Khởi Động Lại Xray Sau Khi Tự Động Vô Hiệu Hóa"
+"restartXrayOnClientDisableDesc" = "Khi người dùng bị vô hiệu hóa tự động do hết hạn hoặc chạm giới hạn lưu lượng, hãy khởi động lại Xray."
 "fragment" = "Sự phân mảnh"
 "fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
 "fragmentSett" = "Cài đặt phân mảnh"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "每次流量更新时通知外部 API"
 "externalTrafficInformURI" = "外部流量通知 URI"
 "externalTrafficInformURIDesc" = "流量更新将发送到此 URI"
+"restartXrayOnClientDisable" = "客户端自动禁用后重启 Xray"
+"restartXrayOnClientDisableDesc" = "当客户端因到期或流量超限被自动禁用时,重启 Xray。"
 "fragment" = "分片"
 "fragmentDesc" = "启用 TLS hello 数据包分片"
 "fragmentSett" = "设置"

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

@@ -477,6 +477,8 @@
 "externalTrafficInformEnableDesc" = "每次流量更新時通知外部 API"
 "externalTrafficInformURI" = "外部流量通知 URI"
 "externalTrafficInformURIDesc" = "流量更新將會傳送到此 URI"
+"restartXrayOnClientDisable" = "用戶自動停用後重新啟動 Xray"
+"restartXrayOnClientDisableDesc" = "當用戶因到期或流量上限而被自動停用時,重新啟動 Xray。"
 "fragment" = "分片"
 "fragmentDesc" = "啟用 TLS hello 資料包分片"
 "fragmentSett" = "設定"