Jelajahi Sumber

feat: Simple periodic traffic reset (for Inbounds) – daily | weekly | monthly (#3407)

* Add periodic traffic reset feature model and ui with localization support

* Remove periodic traffic reset fields from client

* fix: add periodicTrafficReset field to inbound data structure

* feat: implement periodic traffic reset job and integrate with cron scheduler

* feat: enhance periodic traffic reset functionality with scheduling and inbound filtering

* refactor: rename periodicTrafficReset to trafficReset and add lastTrafficResetTime field

* feat: add periodic client traffic reset job and schedule tasks

* Update web/job/periodic_traffic_reset_job.go

Co-authored-by: Copilot <[email protected]>

* Update web/job/periodic_client_traffic_reset_job.go

Co-authored-by: Copilot <[email protected]>

* Update web/service/inbound.go

Co-authored-by: Copilot <[email protected]>

* refactor: rename periodicTrafficReset to trafficReset and add lastTrafficResetTime

* feat: add last traffic reset time display and update logic in inbound service

* fix: correct log message for completed periodic traffic reset

* refactor: update traffic reset fields in Inbound model and remove unused client traffic reset job

* refactor: remove unused traffic reset logic and clean up client model fields

* cleanup comments

* fix
Vadim Iskuchekov 16 jam lalu
induk
melakukan
9623e87511

+ 12 - 10
database/model/model.go

@@ -27,16 +27,18 @@ type User struct {
 }
 }
 
 
 type Inbound struct {
 type Inbound struct {
-	Id          int                  `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
-	UserId      int                  `json:"-"`
-	Up          int64                `json:"up" form:"up"`
-	Down        int64                `json:"down" form:"down"`
-	Total       int64                `json:"total" form:"total"`
-	AllTime     int64                `json:"allTime" form:"allTime" gorm:"default:0"`
-	Remark      string               `json:"remark" form:"remark"`
-	Enable      bool                 `json:"enable" form:"enable"`
-	ExpiryTime  int64                `json:"expiryTime" form:"expiryTime"`
-	ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
+	Id                   int                  `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
+	UserId               int                  `json:"-"`
+	Up                   int64                `json:"up" form:"up"`
+	Down                 int64                `json:"down" form:"down"`
+	Total                int64                `json:"total" form:"total"`
+	AllTime              int64                `json:"allTime" form:"allTime" gorm:"default:0"`
+	Remark               string               `json:"remark" form:"remark"`
+	Enable               bool                 `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"`
+	ExpiryTime           int64                `json:"expiryTime" form:"expiryTime"`
+	TrafficReset         string               `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2"`
+	LastTrafficResetTime int64                `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"`
+	ClientStats          []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
 
 
 	// config part
 	// config part
 	Listen         string   `json:"listen" form:"listen"`
 	Listen         string   `json:"listen" form:"listen"`

+ 2 - 0
web/assets/js/model/dbinbound.js

@@ -10,6 +10,8 @@ class DBInbound {
         this.remark = "";
         this.remark = "";
         this.enable = true;
         this.enable = true;
         this.expiryTime = 0;
         this.expiryTime = 0;
+        this.trafficReset = "never";
+        this.lastTrafficResetTime = 0;
 
 
         this.listen = "";
         this.listen = "";
         this.port = 0;
         this.port = 0;

+ 24 - 0
web/html/form/inbound.html

@@ -44,6 +44,30 @@
         <a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
         <a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
     </a-form-item>
     </a-form-item>
 
 
+    <a-form-item>
+        <template slot="label">
+            <a-tooltip>
+                <template slot="title">
+                    <span>{{ i18n "pages.inbounds.periodicTrafficResetDesc" }}</span>
+                    <br v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
+                    <span v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
+                        <strong>{{ i18n "pages.inbounds.lastReset" }}:</strong> 
+                        <span v-if="datepicker == 'gregorian'">[[ moment(dbInbound.lastTrafficResetTime).format('YYYY-MM-DD HH:mm:ss') ]]</span>
+                        <span v-else>[[ DateUtil.convertToJalalian(moment(dbInbound.lastTrafficResetTime)) ]]</span>
+                    </span>
+                </template>
+                {{ i18n "pages.inbounds.periodicTrafficResetTitle" }}
+                <a-icon type="question-circle"></a-icon>
+            </a-tooltip>
+        </template>
+        <a-select v-model="dbInbound.trafficReset" :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value="never">{{ i18n "pages.inbounds.periodicTrafficReset.never" }}</a-select-option>
+            <a-select-option value="daily">{{ i18n "pages.inbounds.periodicTrafficReset.daily" }}</a-select-option>
+            <a-select-option value="weekly">{{ i18n "pages.inbounds.periodicTrafficReset.weekly" }}</a-select-option>
+            <a-select-option value="monthly">{{ i18n "pages.inbounds.periodicTrafficReset.monthly" }}</a-select-option>
+        </a-select>
+    </a-form-item>
+
     <a-form-item>
     <a-form-item>
         <template slot="label">
         <template slot="label">
             <a-tooltip>
             <a-tooltip>

+ 12 - 0
web/html/inbounds.html

@@ -503,6 +503,12 @@
                                 </a-tag>
                                 </a-tag>
                               </td>
                               </td>
                             </tr>
                             </tr>
+                            <tr>
+                              <td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td>
+                              <td>
+                                <a-tag color="blue">[[ i18n("pages.inbounds.periodicTrafficReset." + dbInbound.trafficReset) ]]</a-tag>
+                              </td>
+                            </tr>
                           </table>
                           </table>
                         </template>
                         </template>
                         <a-badge>
                         <a-badge>
@@ -951,6 +957,8 @@
                     remark: dbInbound.remark + " - Cloned",
                     remark: dbInbound.remark + " - Cloned",
                     enable: dbInbound.enable,
                     enable: dbInbound.enable,
                     expiryTime: dbInbound.expiryTime,
                     expiryTime: dbInbound.expiryTime,
+                    trafficReset: dbInbound.trafficReset,
+                    lastTrafficResetTime: dbInbound.lastTrafficResetTime,
 
 
                     listen: '',
                     listen: '',
                     port: RandomUtil.randomInteger(10000, 60000),
                     port: RandomUtil.randomInteger(10000, 60000),
@@ -995,6 +1003,8 @@
                     remark: dbInbound.remark,
                     remark: dbInbound.remark,
                     enable: dbInbound.enable,
                     enable: dbInbound.enable,
                     expiryTime: dbInbound.expiryTime,
                     expiryTime: dbInbound.expiryTime,
+                    trafficReset: dbInbound.trafficReset,
+                    lastTrafficResetTime: dbInbound.lastTrafficResetTime,
 
 
                     listen: inbound.listen,
                     listen: inbound.listen,
                     port: inbound.port,
                     port: inbound.port,
@@ -1018,6 +1028,8 @@
                     remark: dbInbound.remark,
                     remark: dbInbound.remark,
                     enable: dbInbound.enable,
                     enable: dbInbound.enable,
                     expiryTime: dbInbound.expiryTime,
                     expiryTime: dbInbound.expiryTime,
+                    trafficReset: dbInbound.trafficReset,
+                    lastTrafficResetTime: dbInbound.lastTrafficResetTime,
 
 
                     listen: inbound.listen,
                     listen: inbound.listen,
                     port: inbound.port,
                     port: inbound.port,

+ 44 - 0
web/job/periodic_traffic_reset_job.go

@@ -0,0 +1,44 @@
+package job
+
+import (
+	"x-ui/logger"
+	"x-ui/web/service"
+)
+
+type Period string
+
+type PeriodicTrafficResetJob struct {
+	inboundService service.InboundService
+	period         Period
+}
+
+func NewPeriodicTrafficResetJob(period Period) *PeriodicTrafficResetJob {
+	return &PeriodicTrafficResetJob{
+		period: period,
+	}
+}
+
+func (j *PeriodicTrafficResetJob) Run() {
+	inbounds, err := j.inboundService.GetInboundsByTrafficReset(string(j.period))
+	logger.Infof("Running periodic traffic reset job for period: %s", j.period)
+	if err != nil {
+		logger.Warning("Failed to get inbounds for traffic reset:", err)
+		return
+	}
+
+	resetCount := 0
+
+	for _, inbound := range inbounds {
+		if err := j.inboundService.ResetAllClientTraffics(inbound.Id); err != nil {
+			logger.Warning("Failed to reset traffic for inbound", inbound.Id, ":", err)
+			continue
+		}
+
+		resetCount++
+		logger.Infof("Reset traffic for inbound %d (%s)", inbound.Id, inbound.Remark)
+	}
+
+	if resetCount > 0 {
+		logger.Infof("Periodic traffic reset completed: %d inbounds reset", resetCount)
+	}
+}

+ 44 - 11
web/service/inbound.go

@@ -41,6 +41,16 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
 	return inbounds, nil
 	return inbounds, nil
 }
 }
 
 
+func (s *InboundService) GetInboundsByTrafficReset(period string) ([]*model.Inbound, error) {
+	db := database.GetDB()
+	var inbounds []*model.Inbound
+	err := db.Model(model.Inbound{}).Where("traffic_reset = ?", period).Find(&inbounds).Error
+	if err != nil && err != gorm.ErrRecordNotFound {
+		return nil, err
+	}
+	return inbounds, nil
+}
+
 func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
 func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
 	db := database.GetDB()
 	db := database.GetDB()
 	if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
 	if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
@@ -409,6 +419,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
 	oldInbound.Remark = inbound.Remark
 	oldInbound.Remark = inbound.Remark
 	oldInbound.Enable = inbound.Enable
 	oldInbound.Enable = inbound.Enable
 	oldInbound.ExpiryTime = inbound.ExpiryTime
 	oldInbound.ExpiryTime = inbound.ExpiryTime
+	oldInbound.TrafficReset = inbound.TrafficReset
 	oldInbound.Listen = inbound.Listen
 	oldInbound.Listen = inbound.Listen
 	oldInbound.Port = inbound.Port
 	oldInbound.Port = inbound.Port
 	oldInbound.Protocol = inbound.Protocol
 	oldInbound.Protocol = inbound.Protocol
@@ -698,6 +709,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
 }
 }
 
 
 func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) {
 func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) {
+	// TODO: check if TrafficReset field is updating
 	clients, err := s.GetClients(data)
 	clients, err := s.GetClients(data)
 	if err != nil {
 	if err != nil {
 		return false, err
 		return false, err
@@ -1684,6 +1696,7 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
 func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
 func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
 	db := database.GetDB()
 	db := database.GetDB()
 
 
+	// Reset traffic stats in ClientTraffic table
 	result := db.Model(xray.ClientTraffic{}).
 	result := db.Model(xray.ClientTraffic{}).
 		Where("email = ?", clientEmail).
 		Where("email = ?", clientEmail).
 		Updates(map[string]any{"enable": true, "up": 0, "down": 0})
 		Updates(map[string]any{"enable": true, "up": 0, "down": 0})
@@ -1692,6 +1705,7 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+
 	return nil
 	return nil
 }
 }
 
 
@@ -1759,20 +1773,39 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
 
 
 func (s *InboundService) ResetAllClientTraffics(id int) error {
 func (s *InboundService) ResetAllClientTraffics(id int) error {
 	db := database.GetDB()
 	db := database.GetDB()
+	now := time.Now().Unix() * 1000
 
 
-	whereText := "inbound_id "
-	if id == -1 {
-		whereText += " > ?"
-	} else {
-		whereText += " = ?"
-	}
+	return db.Transaction(func(tx *gorm.DB) error {
+		whereText := "inbound_id "
+		if id == -1 {
+			whereText += " > ?"
+		} else {
+			whereText += " = ?"
+		}
 
 
-	result := db.Model(xray.ClientTraffic{}).
-		Where(whereText, id).
-		Updates(map[string]any{"enable": true, "up": 0, "down": 0})
+		// Reset client traffics
+		result := tx.Model(xray.ClientTraffic{}).
+			Where(whereText, id).
+			Updates(map[string]any{"enable": true, "up": 0, "down": 0})
 
 
-	err := result.Error
-	return err
+		if result.Error != nil {
+			return result.Error
+		}
+
+		// Update lastTrafficResetTime for the inbound(s)
+		inboundWhereText := "id "
+		if id == -1 {
+			inboundWhereText += " > ?"
+		} else {
+			inboundWhereText += " = ?"
+		}
+
+		result = tx.Model(model.Inbound{}).
+			Where(inboundWhereText, id).
+			Update("last_traffic_reset_time", now)
+
+		return result.Error
+	})
 }
 }
 
 
 func (s *InboundService) ResetAllTraffics() error {
 func (s *InboundService) ResetAllTraffics() error {

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

@@ -244,6 +244,9 @@
 "exportInbound" = "تصدير الإدخال"
 "exportInbound" = "تصدير الإدخال"
 "import" = "استيراد"
 "import" = "استيراد"
 "importInbound" = "استيراد إدخال"
 "importInbound" = "استيراد إدخال"
+"periodicTrafficResetTitle" = "إعادة تعيين حركة المرور"
+"periodicTrafficResetDesc" = "إعادة تعيين عداد حركة المرور تلقائيًا في فترات محددة"
+"lastReset" = "آخر إعادة تعيين"
 
 
 [pages.client]
 [pages.client]
 "add" = "أضف عميل"
 "add" = "أضف عميل"
@@ -263,6 +266,12 @@
 "renew" = "تجديد تلقائي"
 "renew" = "تجديد تلقائي"
 "renewDesc" = "تجديد تلقائي بعد انتهاء الصلاحية. (0 = تعطيل)(الوحدة: يوم)"
 "renewDesc" = "تجديد تلقائي بعد انتهاء الصلاحية. (0 = تعطيل)(الوحدة: يوم)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "أبداً"
+"daily" = "يومياً"
+"weekly" = "أسبوعياً"
+"monthly" = "شهرياً"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "تم الحصول عليه"
 "obtain" = "تم الحصول عليه"
 "updateSuccess" = "تم التحديث بنجاح"
 "updateSuccess" = "تم التحديث بنجاح"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Export Inbound"
 "exportInbound" = "Export Inbound"
 "import" = "Import"
 "import" = "Import"
 "importInbound" = "Import an Inbound"
 "importInbound" = "Import an Inbound"
+"periodicTrafficResetTitle" = "Traffic Reset"
+"periodicTrafficResetDesc" = "Automatically reset traffic counter at specified intervals"
+"lastReset" = "Last Reset"
 
 
 [pages.client]
 [pages.client]
 "add" = "Add Client"
 "add" = "Add Client"
@@ -263,6 +266,12 @@
 "renew" = "Auto Renew"
 "renew" = "Auto Renew"
 "renewDesc" = "Auto-renewal after expiration. (0 = disable)(unit: day)"
 "renewDesc" = "Auto-renewal after expiration. (0 = disable)(unit: day)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Never"
+"daily" = "Daily"
+"weekly" = "Weekly"
+"monthly" = "Monthly"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Obtain"
 "obtain" = "Obtain"
 "updateSuccess" = "The update was successful."
 "updateSuccess" = "The update was successful."

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Exportación entrante"
 "exportInbound" = "Exportación entrante"
 "import" = "Importar"
 "import" = "Importar"
 "importInbound" = "Importar un entrante"
 "importInbound" = "Importar un entrante"
+"periodicTrafficResetTitle" = "Reset de Tráfico"
+"periodicTrafficResetDesc" = "Reiniciar automáticamente el contador de tráfico en intervalos especificados"
+"lastReset" = "Último reinicio"
 
 
 [pages.client]
 [pages.client]
 "add" = "Agregar Cliente"
 "add" = "Agregar Cliente"
@@ -263,6 +266,12 @@
 "renew" = "Renovación automática"
 "renew" = "Renovación automática"
 "renewDesc" = "Renovación automática después de la expiración. (0 = desactivar) (unidad: día)"
 "renewDesc" = "Renovación automática después de la expiración. (0 = desactivar) (unidad: día)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Nunca"
+"daily" = "Diariamente"
+"weekly" = "Semanalmente"
+"monthly" = "Mensualmente"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Recibir"
 "obtain" = "Recibir"
 "updateSuccess" = "La actualización fue exitosa"
 "updateSuccess" = "La actualización fue exitosa"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "استخراج ورودی"
 "exportInbound" = "استخراج ورودی"
 "import" = "افزودن"
 "import" = "افزودن"
 "importInbound" = "افزودن یک ورودی"
 "importInbound" = "افزودن یک ورودی"
+"periodicTrafficResetTitle" = "بازنشانی ترافیک"
+"periodicTrafficResetDesc" = "بازنشانی خودکار شمارنده ترافیک در فواصل زمانی مشخص"
+"lastReset" = "آخرین بازنشانی"
 
 
 [pages.client]
 [pages.client]
 "add" = "کاربر جدید"
 "add" = "کاربر جدید"
@@ -261,7 +264,13 @@
 "expireDays" = "مدت زمان"
 "expireDays" = "مدت زمان"
 "days" = "(روز)"
 "days" = "(روز)"
 "renew" = "تمدید خودکار"
 "renew" = "تمدید خودکار"
-"renewDesc" = "(تمدید خودکار پس‌از ‌انقضا. (0 = غیرفعال)(واحد: روز"
+"renewDesc" = "تمدید خودکار پس‌از ‌انقضا. (0 = غیرفعال)(واحد: روز)"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "هرگز"
+"daily" = "روزانه"
+"weekly" = "هفتگی"
+"monthly" = "ماهانه"
 
 
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "فراهم‌سازی"
 "obtain" = "فراهم‌سازی"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Ekspor Masuk"
 "exportInbound" = "Ekspor Masuk"
 "import" = "Impor"
 "import" = "Impor"
 "importInbound" = "Impor Masuk"
 "importInbound" = "Impor Masuk"
+"periodicTrafficResetTitle" = "Reset Trafik Berkala"
+"periodicTrafficResetDesc" = "Reset otomatis penghitung trafik pada interval tertentu"
+"lastReset" = "Reset Terakhir"
 
 
 [pages.client]
 [pages.client]
 "add" = "Tambah Klien"
 "add" = "Tambah Klien"
@@ -263,6 +266,12 @@
 "renew" = "Perpanjang Otomatis"
 "renew" = "Perpanjang Otomatis"
 "renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)"
 "renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Tidak Pernah"
+"daily" = "Harian"
+"weekly" = "Mingguan"
+"monthly" = "Bulanan"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Dapatkan"
 "obtain" = "Dapatkan"
 "updateSuccess" = "Pembaruan berhasil"
 "updateSuccess" = "Pembaruan berhasil"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "インバウンドルールをエクスポート"
 "exportInbound" = "インバウンドルールをエクスポート"
 "import" = "インポート"
 "import" = "インポート"
 "importInbound" = "インバウンドルールをインポート"
 "importInbound" = "インバウンドルールをインポート"
+"periodicTrafficResetTitle" = "トラフィックリセット"
+"periodicTrafficResetDesc" = "指定された間隔でトラフィックカウンタを自動的にリセット"
+"lastReset" = "最後のリセット"
 
 
 [pages.client]
 [pages.client]
 "add" = "クライアント追加"
 "add" = "クライアント追加"
@@ -263,6 +266,12 @@
 "renew" = "自動更新"
 "renew" = "自動更新"
 "renewDesc" = "期限が切れた後に自動更新。(0 = 無効)(単位:日)"
 "renewDesc" = "期限が切れた後に自動更新。(0 = 無効)(単位:日)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "なし"
+"daily" = "毎日"
+"weekly" = "毎週"
+"monthly" = "毎月"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "取得"
 "obtain" = "取得"
 "updateSuccess" = "更新が成功しました"
 "updateSuccess" = "更新が成功しました"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Exportar Inbound"
 "exportInbound" = "Exportar Inbound"
 "import" = "Importar"
 "import" = "Importar"
 "importInbound" = "Importar um Inbound"
 "importInbound" = "Importar um Inbound"
+"periodicTrafficResetTitle" = "Reset de Tráfego"
+"periodicTrafficResetDesc" = "Reinicia automaticamente o contador de tráfego em intervalos especificados"
+"lastReset" = "Último Reset"
 
 
 [pages.client]
 [pages.client]
 "add" = "Adicionar Cliente"
 "add" = "Adicionar Cliente"
@@ -263,6 +266,12 @@
 "renew" = "Renovação Automática"
 "renew" = "Renovação Automática"
 "renewDesc" = "Renovação automática após expiração. (0 = desativado)(unidade: dia)"
 "renewDesc" = "Renovação automática após expiração. (0 = desativado)(unidade: dia)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Nunca"
+"daily" = "Diariamente"
+"weekly" = "Semanalmente"
+"monthly" = "Mensalmente"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Obter"
 "obtain" = "Obter"
 "updateSuccess" = "A atualização foi bem-sucedida"
 "updateSuccess" = "A atualização foi bem-sucedida"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Экспорт инбаундов"
 "exportInbound" = "Экспорт инбаундов"
 "import" = "Импортировать"
 "import" = "Импортировать"
 "importInbound" = "Импорт инбаундов"
 "importInbound" = "Импорт инбаундов"
+"periodicTrafficResetTitle" = "Сброс трафика"
+"periodicTrafficResetDesc" = "Автоматический сброс счетчика трафика через указанные интервалы"
+"lastReset" = "Последний сброс"
 
 
 [pages.client]
 [pages.client]
 "add" = "Создать клиента"
 "add" = "Создать клиента"
@@ -263,6 +266,12 @@
 "renew" = "Автопродление"
 "renew" = "Автопродление"
 "renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)"
 "renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Никогда"
+"daily" = "Ежедневно"
+"weekly" = "Еженедельно"
+"monthly" = "Ежемесячно"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Получить"
 "obtain" = "Получить"
 "updateSuccess" = "Обновление прошло успешно"
 "updateSuccess" = "Обновление прошло успешно"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Geleni Dışa Aktar"
 "exportInbound" = "Geleni Dışa Aktar"
 "import" = "İçe Aktar"
 "import" = "İçe Aktar"
 "importInbound" = "Bir Gelen İçe Aktar"
 "importInbound" = "Bir Gelen İçe Aktar"
+"periodicTrafficResetTitle" = "Trafik Sıfırlama"
+"periodicTrafficResetDesc" = "Belirtilen aralıklarla trafik sayacını otomatik olarak sıfırla"
+"lastReset" = "Son Sıfırlama"
 
 
 [pages.client]
 [pages.client]
 "add" = "Müşteri Ekle"
 "add" = "Müşteri Ekle"
@@ -263,6 +266,12 @@
 "renew" = "Otomatik Yenile"
 "renew" = "Otomatik Yenile"
 "renewDesc" = "Süresi dolduktan sonra otomatik yenileme. (0 = devre dışı)(birim: gün)"
 "renewDesc" = "Süresi dolduktan sonra otomatik yenileme. (0 = devre dışı)(birim: gün)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Asla"
+"daily" = "Günlük"
+"weekly" = "Haftalık"
+"monthly" = "Aylık"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Elde Et"
 "obtain" = "Elde Et"
 "updateSuccess" = "Güncelleme başarılı oldu"
 "updateSuccess" = "Güncelleme başarılı oldu"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Експортувати вхідні"
 "exportInbound" = "Експортувати вхідні"
 "import" = "Імпорт"
 "import" = "Імпорт"
 "importInbound" = "Імпортувати вхідний"
 "importInbound" = "Імпортувати вхідний"
+"periodicTrafficResetTitle" = "Скидання трафіку"
+"periodicTrafficResetDesc" = "Автоматично скидати лічильник трафіку через певні проміжки часу"
+"lastReset" = "Останнє скидання"
 
 
 [pages.client]
 [pages.client]
 "add" = "Додати клієнта"
 "add" = "Додати клієнта"
@@ -263,6 +266,12 @@
 "renew" = "Автоматичне оновлення"
 "renew" = "Автоматичне оновлення"
 "renewDesc" = "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)"
 "renewDesc" = "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Ніколи"
+"daily" = "Щодня"
+"weekly" = "Щотижня"
+"monthly" = "Щомісяця"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Отримати"
 "obtain" = "Отримати"
 "updateSuccess" = "Оновлення пройшло успішно"
 "updateSuccess" = "Оновлення пройшло успішно"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "Xuất nhập khẩu"
 "exportInbound" = "Xuất nhập khẩu"
 "import" = "Nhập"
 "import" = "Nhập"
 "importInbound" = "Nhập inbound"
 "importInbound" = "Nhập inbound"
+"periodicTrafficResetTitle" = "Đặt lại lưu lượng"
+"periodicTrafficResetDesc" = "Tự động đặt lại bộ đếm lưu lượng theo khoảng thời gian xác định"
+"lastReset" = "Đặt lại lần cuối"
 
 
 [pages.client]
 [pages.client]
 "add" = "Thêm người dùng"
 "add" = "Thêm người dùng"
@@ -263,6 +266,12 @@
 "renew" = "Tự động gia hạn"
 "renew" = "Tự động gia hạn"
 "renewDesc" = "Tự động gia hạn sau khi hết hạn. (0 = tắt)(đơn vị: ngày)"
 "renewDesc" = "Tự động gia hạn sau khi hết hạn. (0 = tắt)(đơn vị: ngày)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "Không bao giờ"
+"daily" = "Hàng ngày"
+"weekly" = "Hàng tuần"
+"monthly" = "Hàng tháng"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "Nhận"
 "obtain" = "Nhận"
 "updateSuccess" = "Cập nhật thành công"
 "updateSuccess" = "Cập nhật thành công"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "导出入站规则"
 "exportInbound" = "导出入站规则"
 "import"="导入"
 "import"="导入"
 "importInbound" = "导入入站规则"
 "importInbound" = "导入入站规则"
+"periodicTrafficResetTitle" = "流量重置"
+"periodicTrafficResetDesc" = "按指定间隔自动重置流量计数器"
+"lastReset" = "上次重置"
 
 
 [pages.client]
 [pages.client]
 "add" = "添加客户端"
 "add" = "添加客户端"
@@ -263,6 +266,12 @@
 "renew" = "自动续订"
 "renew" = "自动续订"
 "renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)"
 "renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "从不"
+"daily" = "每日"
+"weekly" = "每周"
+"monthly" = "每月"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "获取"
 "obtain" = "获取"
 "updateSuccess" = "更新成功"
 "updateSuccess" = "更新成功"

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

@@ -244,6 +244,9 @@
 "exportInbound" = "匯出入站規則"
 "exportInbound" = "匯出入站規則"
 "import"="匯入"
 "import"="匯入"
 "importInbound" = "匯入入站規則"
 "importInbound" = "匯入入站規則"
+"periodicTrafficResetTitle" = "流量重置"
+"periodicTrafficResetDesc" = "按指定間隔自動重置流量計數器"
+"lastReset" = "上次重置"
 
 
 [pages.client]
 [pages.client]
 "add" = "新增客戶端"
 "add" = "新增客戶端"
@@ -263,6 +266,12 @@
 "renew" = "自動續訂"
 "renew" = "自動續訂"
 "renewDesc" = "到期後自動續訂。(0 = 禁用)(單位: 天)"
 "renewDesc" = "到期後自動續訂。(0 = 禁用)(單位: 天)"
 
 
+[pages.inbounds.periodicTrafficReset]
+"never" = "從不"
+"daily" = "每日"
+"weekly" = "每週"
+"monthly" = "每月"
+
 [pages.inbounds.toasts]
 [pages.inbounds.toasts]
 "obtain" = "獲取"
 "obtain" = "獲取"
 "updateSuccess" = "更新成功"
 "updateSuccess" = "更新成功"

+ 13 - 0
web/web.go

@@ -289,6 +289,19 @@ func (s *Server) startTask() {
 	// check client ips from log file every day
 	// check client ips from log file every day
 	s.cron.AddJob("@daily", job.NewClearLogsJob())
 	s.cron.AddJob("@daily", job.NewClearLogsJob())
 
 
+	// Periodic traffic resets
+	logger.Info("Scheduling periodic traffic reset jobs")
+	{
+		// Inbound traffic reset jobs
+		// Run once a day, midnight
+		s.cron.AddJob("@daily", job.NewPeriodicTrafficResetJob("daily"))
+		// Run once a week, midnight between Sat/Sun
+		s.cron.AddJob("@weekly", job.NewPeriodicTrafficResetJob("weekly"))
+		// Run once a month, midnight, first of month
+		s.cron.AddJob("@monthly", job.NewPeriodicTrafficResetJob("monthly"))
+
+	}
+
 	// Make a traffic condition every day, 8:30
 	// Make a traffic condition every day, 8:30
 	var entry cron.EntryID
 	var entry cron.EntryID
 	isTgbotenabled, err := s.settingService.GetTgbotEnabled()
 	isTgbotenabled, err := s.settingService.GetTgbotEnabled()