diff options
| -rw-r--r-- | database/model/model.go | 1 | ||||
| -rw-r--r-- | web/assets/js/model/xray.js | 18 | ||||
| -rw-r--r-- | web/html/xui/client_bulk_modal.html | 15 | ||||
| -rw-r--r-- | web/html/xui/client_modal.html | 6 | ||||
| -rw-r--r-- | web/service/inbound.go | 148 | ||||
| -rw-r--r-- | xray/client_traffic.go | 1 |
6 files changed, 162 insertions, 27 deletions
diff --git a/database/model/model.go b/database/model/model.go index 087e8b07..c9f9c105 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -83,4 +83,5 @@ type Client struct { Enable bool `json:"enable" form:"enable"` TgID string `json:"tgId" form:"tgId"` SubID string `json:"subId" form:"subId"` + Reset int `json:"reset" form:"reset"` } diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index f7e13be2..d183b882 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -1783,7 +1783,7 @@ Inbound.VmessSettings = class extends Inbound.Settings { } }; Inbound.VmessSettings.Vmess = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) { + constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) { super(); this.id = id; this.email = email; @@ -1793,6 +1793,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass { this.enable = enable; this.tgId = tgId; this.subId = subId; + this.reset = reset; } static fromJson(json={}) { @@ -1805,6 +1806,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass { json.enable, json.tgId, json.subId, + json.reset, ); } get _expiryTime() { @@ -1873,7 +1875,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings { }; Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) { + constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) { super(); this.id = id; this.flow = flow; @@ -1884,6 +1886,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { this.enable = enable; this.tgId = tgId; this.subId = subId; + this.reset = reset; } static fromJson(json={}) { @@ -1897,6 +1900,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { json.enable, json.tgId, json.subId, + json.reset, ); } @@ -1996,7 +2000,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings { } }; Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { - constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) { + constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) { super(); this.password = password; this.flow = flow; @@ -2007,6 +2011,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { this.enable = enable; this.tgId = tgId; this.subId = subId; + this.reset = reset; } toJson() { @@ -2020,6 +2025,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { enable: this.enable, tgId: this.tgId, subId: this.subId, + reset: this.reset, }; } @@ -2034,6 +2040,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { json.enable, json.tgId, json.subId, + json.reset, ); } @@ -2138,7 +2145,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { }; Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { - constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) { + constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) { super(); this.method = method; this.password = password; @@ -2149,6 +2156,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { this.enable = enable; this.tgId = tgId; this.subId = subId; + this.reset = reset; } toJson() { @@ -2162,6 +2170,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { enable: this.enable, tgId: this.tgId, subId: this.subId, + reset: this.reset, }; } @@ -2176,6 +2185,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { json.enable, json.tgId, json.subId, + json.reset, ); } diff --git a/web/html/xui/client_bulk_modal.html b/web/html/xui/client_bulk_modal.html index f758f627..9d79bee1 100644 --- a/web/html/xui/client_bulk_modal.html +++ b/web/html/xui/client_bulk_modal.html @@ -117,6 +117,18 @@ :dropdown-class-name="themeSwitcher.darkCardClass" v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker> </a-form-item> + <a-form-item v-if="clientsBulkModal.expiryTime != 0"> + <span slot="label"> + <span>{{ i18n "pages.client.renew" }}</span> + <a-tooltip> + <template slot="title"> + <span>{{ i18n "pages.client.renewDesc" }}</span> + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + </span> + <a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number> + </a-form-item> </a-form> </a-modal> <script> @@ -142,6 +154,7 @@ tgId: "", flow: "", delayedStart: false, + reset: 0, ok() { clients = []; method = clientsBulkModal.emailMethod; @@ -170,6 +183,7 @@ if (clientsBulkModal.inbound.xtls) { newClient.flow = clientsBulkModal.flow; } + newClient.reset = clientsBulkModal.reset; clients.push(newClient); } ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id); @@ -199,6 +213,7 @@ this.dbInbound = new DBInbound(dbInbound); this.inbound = dbInbound.toInbound(); this.delayedStart = false; + this.reset = 0; }, newClient(protocol) { switch (protocol) { diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html index b2c03129..d9e9a0a2 100644 --- a/web/html/xui/client_modal.html +++ b/web/html/xui/client_modal.html @@ -115,6 +115,12 @@ get statsColor() { return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB); }, + get delayedStart() { + return this.clientModal.delayedStart; + }, + set delayedStart(value) { + this.clientModal.delayedStart = value; + }, get delayedExpireDays() { return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0; }, diff --git a/web/service/inbound.go b/web/service/inbound.go index 5972d9a5..4ce93f31 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -263,7 +263,18 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, tag := oldInbound.Tag - err = s.updateClientTraffics(oldInbound, inbound) + db := database.GetDB() + tx := db.Begin() + + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + + err = s.updateClientTraffics(tx, oldInbound, inbound) if err != nil { return inbound, false, err } @@ -304,11 +315,10 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, } s.xrayApi.Close() - db := database.GetDB() - return inbound, needRestart, db.Save(oldInbound).Error + return inbound, needRestart, tx.Save(oldInbound).Error } -func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) error { +func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inbound, newInbound *model.Inbound) error { oldClients, err := s.GetClients(oldInbound) if err != nil { return err @@ -318,17 +328,6 @@ func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbo return err } - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - var emailExists bool for _, oldClient := range oldClients { @@ -601,7 +600,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin if len(clients[0].Email) > 0 { if len(oldEmail) > 0 { - err = s.UpdateClientStat(oldEmail, &clients[0]) + err = s.UpdateClientStat(tx, oldEmail, &clients[0]) if err != nil { return false, err } @@ -676,6 +675,13 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff return err, false } + needRestart0, count, err := s.autoRenewClients(tx) + if err != nil { + logger.Warning("Error in renew clients:", err) + } else if count > 0 { + logger.Debugf("%v clients renewed", count) + } + needRestart1, count, err := s.disableInvalidClients(tx) if err != nil { logger.Warning("Error in disabling invalid clients:", err) @@ -689,7 +695,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff } else if count > 0 { logger.Debugf("%v inbounds disabled", count) } - return nil, (needRestart1 || needRestart2) + return nil, (needRestart0 || needRestart1 || needRestart2) } func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error { @@ -823,6 +829,102 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl return dbClientTraffics, nil } +func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) { + // check for time expired + var traffics []*xray.ClientTraffic + now := time.Now().Unix() * 1000 + var err, err1 error + + err = tx.Model(xray.ClientTraffic{}).Where("reset > 0 and expiry_time > 0 and expiry_time <= ?", now).Find(&traffics).Error + if err != nil { + return false, 0, err + } + // return if there is no client to renew + if len(traffics) == 0 { + return false, 0, nil + } + + var inbound_ids []int + var inbounds []*model.Inbound + needRestart := false + var clientsToAdd []struct { + protocol string + tag string + client map[string]interface{} + } + + for _, traffic := range traffics { + inbound_ids = append(inbound_ids, traffic.InboundId) + } + err = tx.Model(model.Inbound{}).Where("id IN ?", inbound_ids).Find(&inbounds).Error + if err != nil { + return false, 0, err + } + for inbound_index := range inbounds { + settings := map[string]interface{}{} + json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) + clients := settings["clients"].([]interface{}) + for client_index := range clients { + c := clients[client_index].(map[string]interface{}) + for traffic_index, traffic := range traffics { + if traffic.Email == c["email"].(string) { + newExpiryTime := traffic.ExpiryTime + for newExpiryTime < now { + newExpiryTime += (int64(traffic.Reset) * 86400000) + } + c["expiryTime"] = newExpiryTime + traffics[traffic_index].ExpiryTime = newExpiryTime + traffics[traffic_index].Down = 0 + traffics[traffic_index].Up = 0 + if !traffic.Enable { + traffics[traffic_index].Enable = true + clientsToAdd = append(clientsToAdd, + struct { + protocol string + tag string + client map[string]interface{} + }{ + protocol: string(inbounds[inbound_index].Protocol), + tag: inbounds[inbound_index].Tag, + client: c, + }) + } + clients[client_index] = interface{}(c) + break + } + } + } + settings["clients"] = clients + newSettings, err := json.MarshalIndent(settings, "", " ") + if err != nil { + return false, 0, err + } + inbounds[inbound_index].Settings = string(newSettings) + } + err = tx.Save(inbounds).Error + if err != nil { + return false, 0, err + } + err = tx.Save(traffics).Error + if err != nil { + return false, 0, err + } + if p != nil { + err1 = s.xrayApi.Init(p.GetAPIPort()) + if err1 != nil { + return true, int64(len(traffics)), nil + } + for _, clientToAdd := range clientsToAdd { + err1 = s.xrayApi.AddUser(clientToAdd.protocol, clientToAdd.tag, clientToAdd.client) + if err1 != nil { + needRestart = true + } + } + s.xrayApi.Close() + } + return needRestart, int64(len(traffics)), nil +} + func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) { now := time.Now().Unix() * 1000 needRestart := false @@ -916,6 +1018,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model clientTraffic.Enable = true clientTraffic.Up = 0 clientTraffic.Down = 0 + clientTraffic.Reset = client.Reset result := tx.Create(&clientTraffic) err := result.Error if err != nil { @@ -924,16 +1027,15 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model return nil } -func (s *InboundService) UpdateClientStat(email string, client *model.Client) error { - db := database.GetDB() - - result := db.Model(xray.ClientTraffic{}). +func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error { + result := tx.Model(xray.ClientTraffic{}). Where("email = ?", email). Updates(map[string]interface{}{ "enable": true, "email": client.Email, "total": client.TotalGB, - "expiry_time": client.ExpiryTime}) + "expiry_time": client.ExpiryTime, + "reset": client.Reset}) err := result.Error if err != nil { return err @@ -1429,7 +1531,7 @@ func (s *InboundService) DelDepletedClients(id int) (err error) { } }() - whereText := "inbound_id " + whereText := "reset = 0 and inbound_id " if id < 0 { whereText += "> ?" } else { diff --git a/xray/client_traffic.go b/xray/client_traffic.go index d1302da4..0f2389a0 100644 --- a/xray/client_traffic.go +++ b/xray/client_traffic.go @@ -9,4 +9,5 @@ type ClientTraffic struct { Down int64 `json:"down" form:"down"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` Total int64 `json:"total" form:"total"` + Reset int `json:"reset" form:"reset" gorm:"default:0"` } |
