diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index 93693e3339..a701c88ec5 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -919,16 +919,16 @@ class Inbound extends XrayCommonClass { isExpiry(index) { switch (this.protocol) { case Protocols.VMESS: - if(this.settings.vmesses[index]._expiryTime != null) - return this.settings.vmesses[index]._expiryTime < new Date().getTime(); + if(this.settings.vmesses[index].expiryTime > 0) + return this.settings.vmesses[index].expiryTime < new Date().getTime(); return false case Protocols.VLESS: - if(this.settings.vlesses[index]._expiryTime != null) - return this.settings.vlesses[index]._expiryTime < new Date().getTime(); + if(this.settings.vlesses[index].expiryTime > 0) + return this.settings.vlesses[index].expiryTime < new Date().getTime(); return false case Protocols.TROJAN: - if(this.settings.trojans[index]._expiryTime != null) - return this.settings.trojans[index]._expiryTime < new Date().getTime(); + if(this.settings.trojans[index].expiryTime > 0) + return this.settings.trojans[index].expiryTime < new Date().getTime(); return false default: return false; @@ -1459,6 +1459,9 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass { if (this.expiryTime === 0 || this.expiryTime === "") { return null; } + if (this.expiryTime < 0){ + return this.expiryTime / -84600000; + } return moment(this.expiryTime); } @@ -1547,6 +1550,9 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { if (this.expiryTime === 0 || this.expiryTime === "") { return null; } + if (this.expiryTime < 0){ + return this.expiryTime / -84600000; + } return moment(this.expiryTime); } @@ -1678,6 +1684,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { if (this.expiryTime === 0 || this.expiryTime === "") { return null; } + if (this.expiryTime < 0){ + return this.expiryTime / -84600000; + } return moment(this.expiryTime); } diff --git a/web/html/xui/client_bulk_modal.html b/web/html/xui/client_bulk_modal.html index 6ac1ec70cb..e6f5094bb7 100644 --- a/web/html/xui/client_bulk_modal.html +++ b/web/html/xui/client_bulk_modal.html @@ -51,7 +51,13 @@ - + + + + + + + {{ i18n "pages.inbounds.expireDate" }} @@ -88,6 +94,7 @@ emailPostfix: "", subId: "", tgId: "", + delayedStart: false, ok() { method=clientsBulkModal.emailMethod; if(method>1){ @@ -119,7 +126,7 @@ this.confirm = confirm; this.quantity = 1; this.totalGB = 0; - this.expiryTime = ''; + this.expiryTime = 0; this.emailMethod= 0; this.firstNum= 1; this.lastNum= 1; @@ -130,6 +137,7 @@ this.dbInbound = new DBInbound(dbInbound); this.inbound = dbInbound.toInbound(); this.clients = this.getClients(this.inbound.protocol, this.inbound.settings); + this.delayedStart = false; }, getClients(protocol, clientSettings) { switch(protocol){ @@ -164,6 +172,12 @@ get inbound() { return this.clientsBulkModal.inbound; }, + get delayedExpireDays() { + return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -84600000 : 0; + }, + set delayedExpireDays(days){ + this.clientsBulkModal.expiryTime = -84600000 * days; + }, }, }); diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html index 8afccbb38f..6b6cc5f7e4 100644 --- a/web/html/xui/client_modal.html +++ b/web/html/xui/client_modal.html @@ -18,6 +18,7 @@ clientStats: [], index: null, isExpired: false, + delayedStart: false, ok() { ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index); }, @@ -33,6 +34,10 @@ this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false; if (!isEdit){ this.addClient(this.inbound.protocol, this.clients); + } else { + if (this.clients[index].expiryTime < 0){ + this.delayedStart = true; + } } this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email); this.confirm = confirm; @@ -81,7 +86,7 @@ }, get isTrafficExhausted() { if(!clientStats) return false - if(clientStats.total == 0) return false + if(clientStats.total <= 0) return false if(clientStats.up + clientStats.down < clientStats.total) return false return true }, @@ -90,10 +95,16 @@ }, get statsColor() { if(!clientStats) return 'blue' - if(clientStats.total === 0) return 'blue' + if(clientStats.total <= 0) return 'blue' else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan' else return 'red' - } + }, + get delayedExpireDays() { + return clientModal.isEdit && this.client.expiryTime < 0 ? this.client.expiryTime / -84600000 : 0; + }, + set delayedExpireDays(days){ + this.client.expiryTime = -84600000 * days; + }, }, methods: { getNewEmail(client) { diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index 1dac816568..b217cae1dc 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -65,7 +65,13 @@ - + + + + + + + {{ i18n "pages.inbounds.expireDate" }} diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html index 024ded7cb2..6100cd9b71 100644 --- a/web/html/xui/inbound_client_table.html +++ b/web/html/xui/inbound_client_table.html @@ -37,11 +37,12 @@ {{ i18n "indefinite" }} + [[ infoModal.clientSettings.expiryTime / -84600000 ]] {{ i18n "pages.client.days" }} {{ i18n "indefinite" }} @@ -183,7 +184,7 @@ inbound: new Inbound(), dbInbound: new DBInbound(), settings: null, - clientSettings: new Inbound.Settings(), + clientSettings: null, clientStats: [], upStats: 0, downStats: 0, diff --git a/web/service/inbound.go b/web/service/inbound.go index e78a10902e..c48900f753 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -366,11 +366,16 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e if len(traffics) == 0 { return nil } - db := database.GetDB() - dbInbound := db.Model(model.Inbound{}) + traffics, err = s.adjustTraffics(traffics) + if err != nil { + return err + } + + db := database.GetDB() db = db.Model(xray.ClientTraffic{}) tx := db.Begin() + defer func() { if err != nil { tx.Rollback() @@ -378,7 +383,22 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e tx.Commit() } }() + + err = tx.Save(traffics).Error + if err != nil { + logger.Warning("AddClientTraffic update data ", err) + } + + return nil +} + +func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_traffics []*xray.ClientTraffic, err error) { + db := database.GetDB() + dbInbound := db.Model(model.Inbound{}) txInbound := dbInbound.Begin() + + tx := db.Model(xray.ClientTraffic{}) + defer func() { if err != nil { txInbound.Rollback() @@ -387,18 +407,20 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e } }() - for _, traffic := range traffics { + for traffic_index, traffic := range traffics { inbound := &model.Inbound{} - client := &xray.ClientTraffic{} - err := tx.Where("email = ?", traffic.Email).First(client).Error + client_traffic := &xray.ClientTraffic{} + err := tx.Where("email = ?", traffic.Email).First(client_traffic).Error if err != nil { if err == gorm.ErrRecordNotFound { logger.Warning(err, traffic.Email) } continue } + client_traffic.Up += traffic.Up + client_traffic.Down += traffic.Down - err = txInbound.Where("id=?", client.InboundId).First(inbound).Error + err = txInbound.Where("id=?", client_traffic.InboundId).First(inbound).Error if err != nil { if err == gorm.ErrRecordNotFound { logger.Warning(err, traffic.Email) @@ -409,28 +431,35 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e settings := map[string][]model.Client{} json.Unmarshal([]byte(inbound.Settings), &settings) clients := settings["clients"] - for _, client := range clients { + needUpdate := false + for client_index, client := range clients { if traffic.Email == client.Email { - traffic.ExpiryTime = client.ExpiryTime - traffic.Total = client.TotalGB + if client.ExpiryTime < 0 { + clients[client_index].ExpiryTime = (time.Now().Unix() * 1000) - client.ExpiryTime + needUpdate = true + } + client_traffic.ExpiryTime = client.ExpiryTime + client_traffic.Total = client.TotalGB + break } } - if tx.Where("inbound_id = ? and email = ?", inbound.Id, traffic.Email). - UpdateColumns(map[string]interface{}{ - "enable": true, - "expiry_time": traffic.ExpiryTime, - "total": traffic.Total, - "up": gorm.Expr("up + ?", traffic.Up), - "down": gorm.Expr("down + ?", traffic.Down)}).RowsAffected == 0 { - err = tx.Create(traffic).Error - } - if err != nil { - logger.Warning("AddClientTraffic update data ", err) - continue + if needUpdate { + settings["clients"] = clients + modifiedSettings, err := json.MarshalIndent(settings, "", " ") + if err != nil { + return nil, err + } + + err = txInbound.Where("id=?", inbound.Id).Update("settings", string(modifiedSettings)).Error + if err != nil { + return nil, err + } } + + traffics[traffic_index] = client_traffic } - return + return traffics, nil } func (s *InboundService) DisableInvalidInbounds() (int64, error) { diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 87d3556f0f..6204ed1412 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -378,6 +378,8 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) { expiryTime := "" if traffic.ExpiryTime == 0 { expiryTime = "♾Unlimited" + } else if traffic.ExpiryTime < 0 { + expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-84600000) } else { expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") } @@ -412,6 +414,8 @@ func (t *Tgbot) searchClient(chatId int64, email string) { expiryTime := "" if traffic.ExpiryTime == 0 { expiryTime = "♾Unlimited" + } else if traffic.ExpiryTime < 0 { + expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-84600000) } else { expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") } @@ -450,6 +454,8 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) { expiryTime := "" if traffic.ExpiryTime == 0 { expiryTime = "♾Unlimited" + } else if traffic.ExpiryTime < 0 { + expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-84600000) } else { expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") } @@ -483,6 +489,8 @@ func (t *Tgbot) searchForClient(chatId int64, query string) { expiryTime := "" if traffic.ExpiryTime == 0 { expiryTime = "♾Unlimited" + } else if traffic.ExpiryTime < 0 { + expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-84600000) } else { expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") } @@ -560,6 +568,8 @@ func (t *Tgbot) getExhausted() string { expiryTime := "" if traffic.ExpiryTime == 0 { expiryTime = "♾Unlimited" + } else if traffic.ExpiryTime < 0 { + expiryTime += fmt.Sprintf("%d days", traffic.ExpiryTime/-84600000) } else { expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") } diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 4670a61252..1c829e752a 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -145,6 +145,9 @@ "last" = "Last" "prefix" = "Prefix" "postfix" = "postfix" +"delayedStart" = "Start after first use" +"expireDays" = "Expire days" +"days" = "day(s)" [pages.inbounds.toasts] "obtain" = "Obtain" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index defc93b9ca..0499b980cb 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -145,6 +145,9 @@ "last" = "تا" "prefix" = "پیشوند" "postfix" = "پسوند" +"delayedStart" = "شروع بعد از اولین استفاده" +"expireDays" = "روزهای اعتبار" +"days" = "(روز)" [pages.inbounds.toasts] "obtain" = "Obtain" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index caa3b0b442..3a771181ee 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -145,6 +145,9 @@ "last" = "最后" "prefix" = "前缀" "postfix" = "后缀" +"delayedStart" = "首次使用后开始" +"expireDays" = "过期天数" +"days" = "天" [pages.inbounds.toasts] "obtain" = "获取"