Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--database/model/model.go1
-rw-r--r--web/assets/js/model/xray.js18
-rw-r--r--web/html/xui/client_bulk_modal.html15
-rw-r--r--web/html/xui/client_modal.html6
-rw-r--r--web/service/inbound.go148
-rw-r--r--xray/client_traffic.go1
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"`
}