mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-10-27 18:32:52 +00:00
Compare commits
16 commits
302dd01b04
...
e0664b29d4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0664b29d4 | ||
|
|
311d11a3c1 | ||
|
|
09933f845d | ||
|
|
7f4efbd92e | ||
|
|
6e263fff8a | ||
|
|
a1c0231abc | ||
|
|
10d57d9502 | ||
|
|
7d75cfc947 | ||
|
|
c418e992ca | ||
|
|
a7f460213d | ||
|
|
4ff0d56e8a | ||
|
|
8484945bb2 | ||
|
|
283aa528eb | ||
|
|
3407c67a68 | ||
|
|
d132ff8839 | ||
|
|
cad0074557 |
23 changed files with 384 additions and 29 deletions
|
|
@ -36,6 +36,8 @@ type Inbound struct {
|
|||
Remark string `json:"remark" form:"remark"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never"`
|
||||
LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||
|
||||
// config part
|
||||
|
|
@ -98,6 +100,8 @@ type Client struct {
|
|||
LimitIP int `json:"limitIp"`
|
||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never"`
|
||||
LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
TgID int64 `json:"tgId" form:"tgId"`
|
||||
SubID string `json:"subId" form:"subId"`
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class DBInbound {
|
|||
this.remark = "";
|
||||
this.enable = true;
|
||||
this.expiryTime = 0;
|
||||
this.periodicTrafficReset = "never";
|
||||
|
||||
this.listen = "";
|
||||
this.port = 0;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,24 @@
|
|||
<a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.periodicTrafficResetDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-select v-model="dbInbound.periodicTrafficReset" :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>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
|
|
|
|||
|
|
@ -645,6 +645,12 @@
|
|||
</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td>
|
||||
<td>
|
||||
<a-tag color="blue">[[ i18n("pages.inbounds.periodicTrafficReset." + dbInbound.periodicTrafficReset) ]]</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-badge>
|
||||
|
|
@ -1093,6 +1099,7 @@
|
|||
remark: dbInbound.remark + " - Cloned",
|
||||
enable: dbInbound.enable,
|
||||
expiryTime: dbInbound.expiryTime,
|
||||
periodicTrafficReset: dbInbound.periodicTrafficReset,
|
||||
|
||||
listen: '',
|
||||
port: RandomUtil.randomInteger(10000, 60000),
|
||||
|
|
@ -1137,6 +1144,7 @@
|
|||
remark: dbInbound.remark,
|
||||
enable: dbInbound.enable,
|
||||
expiryTime: dbInbound.expiryTime,
|
||||
periodicTrafficReset: dbInbound.periodicTrafficReset,
|
||||
|
||||
listen: inbound.listen,
|
||||
port: inbound.port,
|
||||
|
|
@ -1160,6 +1168,7 @@
|
|||
remark: dbInbound.remark,
|
||||
enable: dbInbound.enable,
|
||||
expiryTime: dbInbound.expiryTime,
|
||||
periodicTrafficReset: dbInbound.periodicTrafficReset,
|
||||
|
||||
listen: inbound.listen,
|
||||
port: inbound.port,
|
||||
|
|
|
|||
|
|
@ -237,6 +237,12 @@
|
|||
</a-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="infoModal.clientSettings.periodicTrafficReset">
|
||||
<td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td>
|
||||
<td>
|
||||
<a-tag color="blue">[[ i18n("pages.inbounds.periodicTrafficReset." + infoModal.clientSettings.periodicTrafficReset) ]]</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="app.ipLimitEnable">
|
||||
<td>{{ i18n "pages.inbounds.IPLimit" }}</td>
|
||||
<td>
|
||||
|
|
|
|||
42
web/job/periodic_client_traffic_reset_job.go
Normal file
42
web/job/periodic_client_traffic_reset_job.go
Normal file
|
|
@ -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 reset", resetCount)
|
||||
}
|
||||
}
|
||||
44
web/job/periodic_traffic_reset_job.go
Normal file
44
web/job/periodic_traffic_reset_job.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 is 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package session
|
|||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"net/http"
|
||||
|
||||
"x-ui/database/model"
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ func SetMaxAge(c *gin.Context, maxAge int) {
|
|||
Path: defaultPath,
|
||||
MaxAge: maxAge,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -61,5 +63,6 @@ func ClearSession(c *gin.Context) {
|
|||
Path: defaultPath,
|
||||
MaxAge: -1,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,6 +248,14 @@
|
|||
"days" = "يوم/أيام"
|
||||
"renew" = "تجديد تلقائي"
|
||||
"renewDesc" = "تجديد تلقائي بعد انتهاء الصلاحية. (0 = تعطيل)(الوحدة: يوم)"
|
||||
"periodicTrafficResetTitle" = "إعادة تعيين حركة المرور"
|
||||
"periodicTrafficResetDesc" = "إعادة تعيين عداد حركة المرور تلقائيًا في فترات محددة"
|
||||
|
||||
[pages.inbounds.periodicTrafficReset]
|
||||
"never" = "أبداً"
|
||||
"daily" = "يومياً"
|
||||
"weekly" = "أسبوعياً"
|
||||
"monthly" = "شهرياً"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "تم الحصول عليه"
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -247,7 +247,15 @@
|
|||
"expireDays" = "مدت زمان"
|
||||
"days" = "(روز)"
|
||||
"renew" = "تمدید خودکار"
|
||||
"renewDesc" = "(تمدید خودکار پساز انقضا. (0 = غیرفعال)(واحد: روز"
|
||||
"renewDesc" = "تمدید خودکار پساز انقضا. (0 = غیرفعال)(واحد: روز)"
|
||||
"periodicTrafficResetTitle" = "بازنشانی ترافیک"
|
||||
"periodicTrafficResetDesc" = "بازنشانی خودکار شمارنده ترافیک در فواصل زمانی مشخص"
|
||||
|
||||
[pages.inbounds.periodicTrafficReset]
|
||||
"never" = "هرگز"
|
||||
"daily" = "روزانه"
|
||||
"weekly" = "هفتگی"
|
||||
"monthly" = "ماهانه"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "فراهمسازی"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -248,6 +248,14 @@
|
|||
"days" = "日"
|
||||
"renew" = "自動更新"
|
||||
"renewDesc" = "期限が切れた後に自動更新。(0 = 無効)(単位:日)"
|
||||
"periodicTrafficResetTitle" = "トラフィックリセット"
|
||||
"periodicTrafficResetDesc" = "指定された間隔でトラフィックカウンタを自動的にリセット"
|
||||
|
||||
[pages.inbounds.periodicTrafficReset]
|
||||
"never" = "なし"
|
||||
"daily" = "毎日"
|
||||
"weekly" = "毎週"
|
||||
"monthly" = "毎月"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "取得"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -248,6 +248,14 @@
|
|||
"days" = "дней"
|
||||
"renew" = "Автопродление"
|
||||
"renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)"
|
||||
"periodicTrafficResetTitle" = "Сброс трафика"
|
||||
"periodicTrafficResetDesc" = "Автоматический сброс счетчика трафика через указанные интервалы"
|
||||
|
||||
[pages.inbounds.periodicTrafficReset]
|
||||
"never" = "Никогда"
|
||||
"daily" = "Ежедневно"
|
||||
"weekly" = "Еженедельно"
|
||||
"monthly" = "Ежемесячно"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "Получить"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -248,6 +248,14 @@
|
|||
"days" = "Дні(в)"
|
||||
"renew" = "Автоматичне оновлення"
|
||||
"renewDesc" = "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)"
|
||||
"periodicTrafficResetTitle" = "Скидання трафіку"
|
||||
"periodicTrafficResetDesc" = "Автоматично скидати лічильник трафіку через певні проміжки часу"
|
||||
|
||||
[pages.inbounds.periodicTrafficReset]
|
||||
"never" = "Ніколи"
|
||||
"daily" = "Щодня"
|
||||
"weekly" = "Щотижня"
|
||||
"monthly" = "Щомісяця"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "Отримати"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -248,6 +248,14 @@
|
|||
"days" = "天"
|
||||
"renew" = "自动续订"
|
||||
"renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)"
|
||||
"periodicTrafficResetTitle" = "流量重置"
|
||||
"periodicTrafficResetDesc" = "按指定间隔自动重置流量计数器"
|
||||
|
||||
[pages.inbounds.periodicTrafficReset]
|
||||
"never" = "从不"
|
||||
"daily" = "每日"
|
||||
"weekly" = "每周"
|
||||
"monthly" = "每月"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "获取"
|
||||
|
|
|
|||
|
|
@ -248,6 +248,14 @@
|
|||
"days" = "天"
|
||||
"renew" = "自動續訂"
|
||||
"renewDesc" = "到期後自動續訂。(0 = 禁用)(單位: 天)"
|
||||
"periodicTrafficResetTitle" = "流量重置"
|
||||
"periodicTrafficResetDesc" = "按指定間隔自動重置流量計數器"
|
||||
|
||||
[pages.inbounds.periodicTrafficReset]
|
||||
"never" = "從不"
|
||||
"daily" = "每日"
|
||||
"weekly" = "每週"
|
||||
"monthly" = "每月"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "獲取"
|
||||
|
|
|
|||
45
web/web.go
45
web/web.go
|
|
@ -31,7 +31,7 @@ import (
|
|||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
//go:embed assets/*
|
||||
//go:embed assets
|
||||
var assetsFS embed.FS
|
||||
|
||||
//go:embed html/*
|
||||
|
|
@ -180,6 +180,15 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||
assetsBasePath := basePath + "assets/"
|
||||
|
||||
store := cookie.NewStore(secret)
|
||||
// Configure default session cookie options, including expiration (MaxAge)
|
||||
if sessionMaxAge, err := s.settingService.GetSessionMaxAge(); err == nil {
|
||||
store.Options(sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: sessionMaxAge * 60, // minutes -> seconds
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
engine.Use(sessions.Sessions("3x-ui", store))
|
||||
engine.Use(func(c *gin.Context) {
|
||||
c.Set("base_path", basePath)
|
||||
|
|
@ -201,7 +210,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||
i18nWebFunc := func(key string, params ...string) string {
|
||||
return locale.I18n(locale.Web, key, params...)
|
||||
}
|
||||
engine.FuncMap["i18n"] = i18nWebFunc
|
||||
// Register template functions before loading templates
|
||||
funcMap := template.FuncMap{
|
||||
"i18n": i18nWebFunc,
|
||||
}
|
||||
engine.SetFuncMap(funcMap)
|
||||
engine.Use(locale.LocalizerMiddleware())
|
||||
|
||||
// set static files and template
|
||||
|
|
@ -211,11 +224,12 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use the registered func map with the loaded templates
|
||||
engine.LoadHTMLFiles(files...)
|
||||
engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets")))
|
||||
} else {
|
||||
// for production
|
||||
template, err := s.getHtmlTemplate(engine.FuncMap)
|
||||
template, err := s.getHtmlTemplate(funcMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -266,6 +280,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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue