{{ i18n "pages.inbounds.IPLimit" }} |
diff --git a/web/job/periodic_client_traffic_reset_job.go b/web/job/periodic_client_traffic_reset_job.go
new file mode 100644
index 00000000..8f02f0e2
--- /dev/null
+++ b/web/job/periodic_client_traffic_reset_job.go
@@ -0,0 +1,42 @@
+package job
+
+import (
+ "x-ui/logger"
+ "x-ui/web/service"
+)
+
+type PeriodicClientTrafficResetJob struct {
+ inboundService service.InboundService
+ period Period
+}
+
+func NewPeriodicClientTrafficResetJob(period Period) *PeriodicClientTrafficResetJob {
+ return &PeriodicClientTrafficResetJob{
+ period: period,
+ }
+}
+
+func (j *PeriodicClientTrafficResetJob) Run() {
+ clients, err := j.inboundService.GetClientsByTrafficReset(string(j.period))
+ logger.Infof("Running periodic client traffic reset job for period: %s", j.period)
+ if err != nil {
+ logger.Warning("Failed to get clients for traffic reset:", err)
+ return
+ }
+
+ resetCount := 0
+
+ for _, client := range clients {
+ if err := j.inboundService.ResetClientTrafficByEmail(client.Email); err != nil {
+ logger.Warning("Failed to reset traffic for client", client.Email, ":", err)
+ continue
+ }
+
+ resetCount++
+ logger.Infof("Reset traffic for client %s", client.Email)
+ }
+
+ if resetCount > 0 {
+ logger.Infof("Periodic client traffic reset completed: %d clients reseted", resetCount)
+ }
+}
diff --git a/web/job/periodic_traffic_reset_job.go b/web/job/periodic_traffic_reset_job.go
new file mode 100644
index 00000000..5d3cd178
--- /dev/null
+++ b/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 reseted", resetCount)
+ }
+}
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 2646b1e7..13ac4cc0 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -41,6 +41,46 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
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) GetClientsByTrafficReset(period string) ([]model.Client, error) {
+ db := database.GetDB()
+ var inbounds []*model.Inbound
+
+ // Get all inbounds first
+ err := db.Model(model.Inbound{}).Find(&inbounds).Error
+ if err != nil {
+ return nil, err
+ }
+
+ var clientsWithReset []model.Client
+
+ // Parse each inbound's settings to find clients with matching traffic reset period
+ for _, inbound := range inbounds {
+ clients, err := s.GetClients(inbound)
+ if err != nil {
+ logger.Warning("Failed to get clients for inbound", inbound.Id, ":", err)
+ continue
+ }
+
+ for _, client := range clients {
+ if client.TrafficReset == period {
+ clientsWithReset = append(clientsWithReset, client)
+ }
+ }
+ }
+
+ return clientsWithReset, nil
+}
+
func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
db := database.GetDB()
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
@@ -409,6 +449,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Remark = inbound.Remark
oldInbound.Enable = inbound.Enable
oldInbound.ExpiryTime = inbound.ExpiryTime
+ oldInbound.TrafficReset = inbound.TrafficReset
oldInbound.Listen = inbound.Listen
oldInbound.Port = inbound.Port
oldInbound.Protocol = inbound.Protocol
@@ -698,6 +739,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
}
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) {
+ // TODO: check if TrafficReset field are updating
clients, err := s.GetClients(data)
if err != nil {
return false, err
@@ -1684,6 +1726,7 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
db := database.GetDB()
+ // Reset traffic stats in ClientTraffic table
result := db.Model(xray.ClientTraffic{}).
Where("email = ?", clientEmail).
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
@@ -1692,6 +1735,48 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
if err != nil {
return err
}
+
+ // Update lastTrafficResetTime in client settings
+ _, inbound, err := s.GetClientInboundByEmail(clientEmail)
+ if err != nil {
+ logger.Warning("Failed to get inbound for client", clientEmail, ":", err)
+ return nil // Don't fail the reset if we can't update the timestamp
+ }
+
+ if inbound != nil {
+ var settings map[string]any
+ err = json.Unmarshal([]byte(inbound.Settings), &settings)
+ if err != nil {
+ logger.Warning("Failed to parse inbound settings:", err)
+ return nil
+ }
+
+ clientsSettings := settings["clients"].([]any)
+ now := time.Now().Unix() * 1000
+
+ for client_index := range clientsSettings {
+ c := clientsSettings[client_index].(map[string]any)
+ if c["email"] == clientEmail {
+ c["lastTrafficResetTime"] = now
+ c["updated_at"] = now
+ break
+ }
+ }
+
+ settings["clients"] = clientsSettings
+ modifiedSettings, err := json.MarshalIndent(settings, "", " ")
+ if err != nil {
+ logger.Warning("Failed to marshal inbound settings:", err)
+ return nil
+ }
+
+ inbound.Settings = string(modifiedSettings)
+ err = db.Save(inbound).Error
+ if err != nil {
+ logger.Warning("Failed to save inbound with updated client settings:", err)
+ }
+ }
+
return nil
}
diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml
index 9f11a30c..488da92c 100644
--- a/web/translation/translate.ar_EG.toml
+++ b/web/translation/translate.ar_EG.toml
@@ -248,6 +248,14 @@
"days" = "يوم/أيام"
"renew" = "تجديد تلقائي"
"renewDesc" = "تجديد تلقائي بعد انتهاء الصلاحية. (0 = تعطيل)(الوحدة: يوم)"
+"periodicTrafficResetTitle" = "إعادة تعيين حركة المرور"
+"periodicTrafficResetDesc" = "إعادة تعيين عداد حركة المرور تلقائيًا في فترات محددة"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "أبداً"
+"daily" = "يومياً"
+"weekly" = "أسبوعياً"
+"monthly" = "شهرياً"
[pages.inbounds.toasts]
"obtain" = "تم الحصول عليه"
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index f802dd6d..f7791860 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -230,6 +230,8 @@
"exportInbound" = "Export Inbound"
"import" = "Import"
"importInbound" = "Import an Inbound"
+"periodicTrafficResetTitle" = "Traffic Reset"
+"periodicTrafficResetDesc" = "Automatically reset traffic counter at specified intervals"
[pages.client]
"add" = "Add Client"
@@ -249,6 +251,12 @@
"renew" = "Auto Renew"
"renewDesc" = "Auto-renewal after expiration. (0 = disable)(unit: day)"
+[pages.inbounds.periodicTrafficReset]
+"never" = "Never"
+"daily" = "Daily"
+"weekly" = "Weekly"
+"monthly" = "Monthly"
+
[pages.inbounds.toasts]
"obtain" = "Obtain"
"updateSuccess" = "The update was successful."
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index 80ddd98a..813597e4 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -248,6 +248,14 @@
"days" = "Día(s)"
"renew" = "Renovación automática"
"renewDesc" = "Renovación automática después de la expiración. (0 = desactivar) (unidad: día)"
+"periodicTrafficResetTitle" = "Reset de Tráfico"
+"periodicTrafficResetDesc" = "Reiniciar automáticamente el contador de tráfico en intervalos especificados"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "Nunca"
+"daily" = "Diariamente"
+"weekly" = "Semanalmente"
+"monthly" = "Mensualmente"
[pages.inbounds.toasts]
"obtain" = "Recibir"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 778dd528..b77e753c 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -247,7 +247,15 @@
"expireDays" = "مدت زمان"
"days" = "(روز)"
"renew" = "تمدید خودکار"
-"renewDesc" = "(تمدید خودکار پساز انقضا. (0 = غیرفعال)(واحد: روز"
+"renewDesc" = "تمدید خودکار پساز انقضا. (0 = غیرفعال)(واحد: روز)"
+"periodicTrafficResetTitle" = "بازنشانی ترافیک"
+"periodicTrafficResetDesc" = "بازنشانی خودکار شمارنده ترافیک در فواصل زمانی مشخص"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "هرگز"
+"daily" = "روزانه"
+"weekly" = "هفتگی"
+"monthly" = "ماهانه"
[pages.inbounds.toasts]
"obtain" = "فراهمسازی"
diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml
index 2a5b568b..441debd5 100644
--- a/web/translation/translate.id_ID.toml
+++ b/web/translation/translate.id_ID.toml
@@ -248,6 +248,14 @@
"days" = "Hari"
"renew" = "Perpanjang Otomatis"
"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)"
+"periodicTrafficResetTitle" = "Reset Trafik Berkala"
+"periodicTrafficResetDesc" = "Reset otomatis penghitung trafik pada interval tertentu"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "Tidak Pernah"
+"daily" = "Harian"
+"weekly" = "Mingguan"
+"monthly" = "Bulanan"
[pages.inbounds.toasts]
"obtain" = "Dapatkan"
diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml
index 7af0a339..e9d75bde 100644
--- a/web/translation/translate.ja_JP.toml
+++ b/web/translation/translate.ja_JP.toml
@@ -248,6 +248,14 @@
"days" = "日"
"renew" = "自動更新"
"renewDesc" = "期限が切れた後に自動更新。(0 = 無効)(単位:日)"
+"periodicTrafficResetTitle" = "トラフィックリセット"
+"periodicTrafficResetDesc" = "指定された間隔でトラフィックカウンタを自動的にリセット"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "なし"
+"daily" = "毎日"
+"weekly" = "毎週"
+"monthly" = "毎月"
[pages.inbounds.toasts]
"obtain" = "取得"
diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml
index f61f8995..9cc16826 100644
--- a/web/translation/translate.pt_BR.toml
+++ b/web/translation/translate.pt_BR.toml
@@ -248,6 +248,14 @@
"days" = "Dia(s)"
"renew" = "Renovação Automática"
"renewDesc" = "Renovação automática após expiração. (0 = desativado)(unidade: dia)"
+"periodicTrafficResetTitle" = "Reset de Tráfego"
+"periodicTrafficResetDesc" = "Reinicia automaticamente o contador de tráfego em intervalos especificados"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "Nunca"
+"daily" = "Diariamente"
+"weekly" = "Semanalmente"
+"monthly" = "Mensalmente"
[pages.inbounds.toasts]
"obtain" = "Obter"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index 060b7334..bbaaaf3a 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -248,6 +248,14 @@
"days" = "дней"
"renew" = "Автопродление"
"renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)"
+"periodicTrafficResetTitle" = "Сброс трафика"
+"periodicTrafficResetDesc" = "Автоматический сброс счетчика трафика через указанные интервалы"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "Никогда"
+"daily" = "Ежедневно"
+"weekly" = "Еженедельно"
+"monthly" = "Ежемесячно"
[pages.inbounds.toasts]
"obtain" = "Получить"
diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml
index ac10bf65..286ea1c2 100644
--- a/web/translation/translate.tr_TR.toml
+++ b/web/translation/translate.tr_TR.toml
@@ -248,6 +248,14 @@
"days" = "Gün"
"renew" = "Otomatik Yenile"
"renewDesc" = "Süresi dolduktan sonra otomatik yenileme. (0 = devre dışı)(birim: gün)"
+"periodicTrafficResetTitle" = "Trafik Sıfırlama"
+"periodicTrafficResetDesc" = "Belirtilen aralıklarla trafik sayacını otomatik olarak sıfırla"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "Asla"
+"daily" = "Günlük"
+"weekly" = "Haftalık"
+"monthly" = "Aylık"
[pages.inbounds.toasts]
"obtain" = "Elde Et"
diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml
index 4005f14f..06634ef4 100644
--- a/web/translation/translate.uk_UA.toml
+++ b/web/translation/translate.uk_UA.toml
@@ -248,6 +248,14 @@
"days" = "Дні(в)"
"renew" = "Автоматичне оновлення"
"renewDesc" = "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)"
+"periodicTrafficResetTitle" = "Скидання трафіку"
+"periodicTrafficResetDesc" = "Автоматично скидати лічильник трафіку через певні проміжки часу"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "Ніколи"
+"daily" = "Щодня"
+"weekly" = "Щотижня"
+"monthly" = "Щомісяця"
[pages.inbounds.toasts]
"obtain" = "Отримати"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index 66ed38ce..b601b263 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -248,6 +248,14 @@
"days" = "ngày"
"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)"
+"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"
+
+[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]
"obtain" = "Nhận"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml
index 5875add9..e0f2c222 100644
--- a/web/translation/translate.zh_CN.toml
+++ b/web/translation/translate.zh_CN.toml
@@ -248,6 +248,14 @@
"days" = "天"
"renew" = "自动续订"
"renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)"
+"periodicTrafficResetTitle" = "流量重置"
+"periodicTrafficResetDesc" = "按指定间隔自动重置流量计数器"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "从不"
+"daily" = "每日"
+"weekly" = "每周"
+"monthly" = "每月"
[pages.inbounds.toasts]
"obtain" = "获取"
diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml
index 6843e9ee..66b2bd33 100644
--- a/web/translation/translate.zh_TW.toml
+++ b/web/translation/translate.zh_TW.toml
@@ -248,6 +248,14 @@
"days" = "天"
"renew" = "自動續訂"
"renewDesc" = "到期後自動續訂。(0 = 禁用)(單位: 天)"
+"periodicTrafficResetTitle" = "流量重置"
+"periodicTrafficResetDesc" = "按指定間隔自動重置流量計數器"
+
+[pages.inbounds.periodicTrafficReset]
+"never" = "從不"
+"daily" = "每日"
+"weekly" = "每週"
+"monthly" = "每月"
[pages.inbounds.toasts]
"obtain" = "獲取"
diff --git a/web/web.go b/web/web.go
index 38eb24d6..81b62952 100644
--- a/web/web.go
+++ b/web/web.go
@@ -266,6 +266,31 @@ func (s *Server) startTask() {
// check client ips from log file every day
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
+ // TODO: for testing, run every minute, change back to daily later
+ // s.cron.AddJob("@daily", job.NewPeriodicTrafficResetJob("daily"))
+ s.cron.AddJob("* * * * *", 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"))
+
+ // Client traffic reset jobs
+ logger.Info("Scheduling periodic client traffic reset jobs")
+ // Run once a day, midnight
+ // TODO: for testing, run every minute, change back to daily later
+ // s.cron.AddJob("@daily", job.NewPeriodicClientTrafficResetJob("daily"))
+ s.cron.AddJob("* * * * *", job.NewPeriodicClientTrafficResetJob("daily"))
+ // Run once a week, midnight between Sat/Sun
+ s.cron.AddJob("@weekly", job.NewPeriodicClientTrafficResetJob("weekly"))
+ // Run once a month, midnight, first of month
+ s.cron.AddJob("@monthly", job.NewPeriodicClientTrafficResetJob("monthly"))
+ }
+
// Make a traffic condition every day, 8:30
var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
|