From d18a1a37ceb7c005a5a829ba8b9ef2d103c181b5 Mon Sep 17 00:00:00 2001 From: Sanaei <ho3ein.sanaei@gmail.com> Date: Tue, 4 Feb 2025 11:27:58 +0100 Subject: [PATCH] revert group management (#2656) * Revert "json post base path bug fixed (#2647)" This reverts commit 04cf250a547bb64265d256e7d15af7cea5ecfa67. * Revert "Group Management of Subscription Clients" * Revert "fix getSubGroupClients for enable/disable and edit clients." * Revert "Enhance database initialization in db.go (#2645)" This reverts commit 66fe84181b9c4e2f6c6be943a7f486b4308c32ff. * Revert "Add checkpoint handling in CloseDB function (#2646)" This reverts commit 4dd40f6f192e3f94f2ea4fe9e942e6663b5a1527. * Revert "Improved database model migration and added indexing (#2655)" This reverts commit b922d986d6783ce28d00ca948024dee44a11f29e. --- database/db.go | 54 +----- database/model/model.go | 4 +- install.sh | 2 +- web/assets/js/model/setting.js | 1 - web/assets/js/util/utils.js | 35 ---- web/controller/inbound.go | 154 +--------------- web/entity/entity.go | 1 - web/html/common/qrcode_modal.html | 24 +-- web/html/xui/client_modal.html | 197 ++------------------- web/html/xui/form/client.html | 14 +- web/html/xui/inbound_client_table.html | 2 +- web/html/xui/inbound_modal.html | 4 - web/html/xui/inbounds.html | 234 ++++++------------------- web/html/xui/settings.html | 1 - web/job/client_traffic_sync_job.go | 178 ------------------- web/service/setting.go | 10 -- web/translation/translate.en_US.toml | 5 - web/translation/translate.es_ES.toml | 3 - web/translation/translate.fa_IR.toml | 5 - web/translation/translate.id_ID.toml | 5 - web/translation/translate.ja_JP.toml | 5 - web/translation/translate.pt_BR.toml | 5 - web/translation/translate.ru_RU.toml | 5 - web/translation/translate.tr_TR.toml | 5 - web/translation/translate.uk_UA.toml | 5 - web/translation/translate.vi_VN.toml | 5 - web/translation/translate.zh_CN.toml | 5 - web/translation/translate.zh_TW.toml | 5 - web/web.go | 7 - x-ui.sh | 2 +- xray/client_traffic.go | 4 +- 31 files changed, 96 insertions(+), 890 deletions(-) delete mode 100644 web/job/client_traffic_sync_job.go diff --git a/database/db.go b/database/db.go index d252cfe0..300a73c0 100644 --- a/database/db.go +++ b/database/db.go @@ -26,35 +26,20 @@ const ( ) func initModels() error { - // Order matters: first create tables without dependencies - baseModels := []interface{}{ + models := []interface{}{ &model.User{}, - &model.Setting{}, - } - - // Migrate base models - for _, model := range baseModels { - if err := db.AutoMigrate(model); err != nil { - log.Printf("Error auto migrating base model: %v", err) - return err - } - } - - // Then migrate models with dependencies - dependentModels := []interface{}{ &model.Inbound{}, &model.OutboundTraffics{}, + &model.Setting{}, &model.InboundClientIps{}, &xray.ClientTraffic{}, } - - for _, model := range dependentModels { + for _, model := range models { if err := db.AutoMigrate(model); err != nil { - log.Printf("Error auto migrating dependent model: %v", err) + log.Printf("Error auto migrating model: %v", err) return err } } - return nil } @@ -97,31 +82,9 @@ func InitDB(dbPath string) error { } c := &gorm.Config{ - Logger: gormLogger, - SkipDefaultTransaction: true, - PrepareStmt: true, + Logger: gormLogger, } - - dsn := dbPath + "?cache=shared&_journal_mode=WAL&_synchronous=NORMAL" - db, err = gorm.Open(sqlite.Open(dsn), c) - if err != nil { - return err - } - - sqlDB, err := db.DB() - if err != nil { - return err - } - - _, err = sqlDB.Exec("PRAGMA cache_size = -64000;") - if err != nil { - return err - } - _, err = sqlDB.Exec("PRAGMA temp_store = MEMORY;") - if err != nil { - return err - } - _, err = sqlDB.Exec("PRAGMA foreign_keys = ON;") + db, err = gorm.Open(sqlite.Open(dbPath), c) if err != nil { return err } @@ -138,11 +101,6 @@ func InitDB(dbPath string) error { func CloseDB() error { if db != nil { - - if err := Checkpoint(); err != nil { - log.Printf("error executing checkpoint: %v", err) - } - sqlDB, err := db.DB() if err != nil { return err diff --git a/database/model/model.go b/database/model/model.go index 915cae0b..e9d1836f 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -29,14 +29,14 @@ type User struct { type Inbound struct { Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - UserId int `json:"-" gorm:"index"` + UserId int `json:"-"` Up int64 `json:"up" form:"up"` Down int64 `json:"down" form:"down"` Total int64 `json:"total" form:"total"` Remark string `json:"remark" form:"remark"` Enable bool `json:"enable" form:"enable"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` - ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id;constraint:OnDelete:CASCADE" json:"clientStats"` + ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // config part Listen string `json:"listen" form:"listen"` diff --git a/install.sh b/install.sh index f5ce9b96..150ae86c 100644 --- a/install.sh +++ b/install.sh @@ -283,4 +283,4 @@ install_x-ui() { echo -e "${green}Running...${plain}" install_base -install_x-ui $1 \ No newline at end of file +install_x-ui $1 diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js index 72fe77ef..8e010598 100644 --- a/web/assets/js/model/setting.js +++ b/web/assets/js/model/setting.js @@ -26,7 +26,6 @@ class AllSetting { this.xrayTemplateConfig = ""; this.secretEnable = false; this.subEnable = false; - this.subSyncEnable = true; this.subListen = ""; this.subPort = 2096; this.subPath = "/sub/"; diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js index 10825490..30f1f6a2 100644 --- a/web/assets/js/util/utils.js +++ b/web/assets/js/util/utils.js @@ -70,41 +70,6 @@ class HttpUtil { } return msg; } - - static async jsonPost(url, data) { - let msg; - try { - const requestOptions = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }; - const resp = await fetch(basePath + url.replace(/^\/+|\/+$/g, ''), requestOptions); - const response = await resp.json(); - - msg = this._respToMsg({data : response}); - } catch (e) { - msg = new Msg(false, e.toString()); - } - this._handleMsg(msg); - return msg; - } - - static async postWithModalJson(url, data, modal) { - if (modal) { - modal.loading(true); - } - const msg = await this.jsonPost(url, data); - if (modal) { - modal.loading(false); - if (msg instanceof Msg && msg.success) { - modal.close(); - } - } - return msg; - } } class PromiseUtil { diff --git a/web/controller/inbound.go b/web/controller/inbound.go index a8003484..c22ce192 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -1,10 +1,10 @@ package controller import ( - "errors" "encoding/json" "fmt" "strconv" + "x-ui/database/model" "x-ui/web/service" "x-ui/web/session" @@ -33,13 +33,9 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/addClient", a.addInboundClient) - g.POST("/addGroupClient", a.addGroupInboundClient) g.POST("/:id/delClient/:clientId", a.delInboundClient) - g.POST("/delGroupClients", a.delGroupClients) g.POST("/updateClient/:clientId", a.updateInboundClient) - g.POST("/updateClients", a.updateGroupInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) - g.POST("/resetGroupClientTraffic", a.resetGroupClientTraffic) g.POST("/resetAllTraffics", a.resetAllTraffics) g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) g.POST("/delDepletedClients/:id", a.delDepletedClients) @@ -194,34 +190,6 @@ func (a *InboundController) addInboundClient(c *gin.Context) { } } -func (a *InboundController) addGroupInboundClient(c *gin.Context) { - var requestData []model.Inbound - - err := c.ShouldBindJSON(&requestData) - - if err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - - needRestart := true - - for _, data := range requestData { - - needRestart, err = a.inboundService.AddInboundClient(&data) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - } - - jsonMsg(c, "Client(s) added", nil) - if err == nil && needRestart { - a.xrayService.SetToNeedRestart() - } - -} - func (a *InboundController) delInboundClient(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -243,38 +211,6 @@ func (a *InboundController) delInboundClient(c *gin.Context) { } } -func (a *InboundController) delGroupClients(c *gin.Context) { - var requestData []struct { - InboundID int `json:"inboundId"` - ClientID string `json:"clientId"` - } - - if err := c.ShouldBindJSON(&requestData); err != nil { - jsonMsg(c, "Invalid request data", err) - return - } - - needRestart := false - - for _, req := range requestData { - needRestartTmp, err := a.inboundService.DelInboundClient(req.InboundID, req.ClientID) - if err != nil { - jsonMsg(c, "Failed to delete client", err) - return - } - - if needRestartTmp { - needRestart = true - } - } - - jsonMsg(c, "Clients deleted successfully", nil) - - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - func (a *InboundController) updateInboundClient(c *gin.Context) { clientId := c.Param("clientId") @@ -298,56 +234,6 @@ func (a *InboundController) updateInboundClient(c *gin.Context) { } } -func (a *InboundController) updateGroupInboundClient(c *gin.Context) { - var requestData []map[string]interface{} - - if err := c.ShouldBindJSON(&requestData); err != nil { - jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err) - return - } - - needRestart := false - - for _, item := range requestData { - - inboundMap, ok := item["inbound"].(map[string]interface{}) - if !ok { - jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'inbound' to map")) - return - } - - clientId, ok := item["clientId"].(string) - if !ok { - jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'clientId' to string")) - return - } - - inboundJSON, err := json.Marshal(inboundMap) - if err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - - var inboundModel model.Inbound - if err := json.Unmarshal(inboundJSON, &inboundModel); err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } - - if restart, err := a.inboundService.UpdateInboundClient(&inboundModel, clientId); err != nil { - jsonMsg(c, "Something went wrong!", err) - return - } else { - needRestart = needRestart || restart - } - } - - jsonMsg(c, "Client updated", nil) - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - func (a *InboundController) resetClientTraffic(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -367,44 +253,6 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) { } } -func (a *InboundController) resetGroupClientTraffic(c *gin.Context) { - var requestData []struct { - InboundID int `json:"inboundId"` // Map JSON "inboundId" to struct field "InboundID" - Email string `json:"email"` // Map JSON "email" to struct field "Email" - } - - // Parse JSON body directly using ShouldBindJSON - if err := c.ShouldBindJSON(&requestData); err != nil { - jsonMsg(c, "Invalid request data", err) - return - } - - needRestart := false - - // Process each request data - for _, req := range requestData { - needRestartTmp, err := a.inboundService.ResetClientTraffic(req.InboundID, req.Email) - if err != nil { - jsonMsg(c, "Failed to reset client traffic", err) - return - } - - // If any request requires a restart, set needRestart to true - if needRestartTmp { - needRestart = true - } - } - - // Send response back to the client - jsonMsg(c, "Traffic reset for all clients", nil) - - // Restart the service if required - if needRestart { - a.xrayService.SetToNeedRestart() - } -} - - func (a *InboundController) resetAllTraffics(c *gin.Context) { err := a.inboundService.ResetAllTraffics() if err != nil { diff --git a/web/entity/entity.go b/web/entity/entity.go index f5f375ea..12206340 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -40,7 +40,6 @@ type AllSetting struct { TimeLocation string `json:"timeLocation" form:"timeLocation"` SecretEnable bool `json:"secretEnable" form:"secretEnable"` SubEnable bool `json:"subEnable" form:"subEnable"` - SubSyncEnable bool `json:"subSyncEnable" form:"subSyncEnable"` SubListen string `json:"subListen" form:"subListen"` SubPort int `json:"subPort" form:"subPort"` SubPath string `json:"subPath" form:"subPath"` diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html index 6558c347..94e750c7 100644 --- a/web/html/common/qrcode_modal.html +++ b/web/html/common/qrcode_modal.html @@ -23,15 +23,13 @@ </tr-qr-bg> </tr-qr-box> </template> - <template v-if="!isJustSub"> - <template v-for="(row, index) in qrModal.qrcodes"> - <tr-qr-box class="qr-box"> - <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag> - <tr-qr-bg class="qr-bg"> - <canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas> - </tr-qr-bg> - </tr-qr-box> - </template> + <template v-for="(row, index) in qrModal.qrcodes"> + <tr-qr-box class="qr-box"> + <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag> + <tr-qr-bg class="qr-bg"> + <canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas> + </tr-qr-bg> + </tr-qr-box> </template> </tr-qr-modal> </a-modal> @@ -45,14 +43,12 @@ qrcodes: [], clipboard: null, visible: false, - isJustSub: false, subId: '', - show: function(title = '', dbInbound, client, isJustSub = false) { + show: function(title = '', dbInbound, client) { this.title = title; this.dbInbound = dbInbound; this.inbound = dbInbound.toInbound(); this.client = client; - this.isJustSub = isJustSub; this.subId = ''; this.qrcodes = []; if (this.inbound.protocol == Protocols.WIREGUARD) { @@ -80,9 +76,7 @@ delimiters: ['[[', ']]'], el: '#qrcode-modal', data: { - qrModal: qrModal,get isJustSub(){ - return qrModal.isJustSub - } + qrModal: qrModal, }, methods: { copyToClipboard(elementId, content) { diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html index f2ea30d0..aa62e02a 100644 --- a/web/html/xui/client_modal.html +++ b/web/html/xui/client_modal.html @@ -16,16 +16,7 @@ title: '', okText: '', isEdit: false, - group: { - canGroup: true, - isGroup: false, - currentClient: null, - inbounds: [], - clients: [], - editIds: [] - }, dbInbound: new DBInbound(), - dbInbounds: null, inbound: new Inbound(), clients: [], clientStats: [], @@ -34,126 +25,33 @@ clientIps: null, delayedStart: false, ok() { - if (app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup) { - const currentClient = clientModal.group.currentClient; - const { limitIp, comment, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient; - const uniqueEmails = clientModalApp.makeGroupEmailsUnique(clientModal.dbInbounds, currentClient.email, clientModal.group.clients); - - clientModal.group.clients.forEach((client, index) => { - client.email = uniqueEmails[index]; - client.limitIp = limitIp; - client.comment = comment; - client.totalGB = totalGB; - client.expiryTime = expiryTime; - client.reset = reset; - client.enable = enable; - - if (subId) { - client.subId = subId; - } - if (tgId) { - client.tgId = tgId; - } - if (flow) { - client.flow = flow; - } - }); - - if (clientModal.isEdit) { - ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds, clientModal.group.editIds); - } else { - ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds); - } + if (clientModal.isEdit) { + ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId); } else { - if (clientModal.isEdit) { - ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId); - } else { - ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id); - } + ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id); } }, - show({ - title = '', - okText = '{{ i18n "sure" }}', - index = null, - dbInbound = null, - dbInbounds = null, - confirm = () => { - }, - isEdit = false - }) { - this.group = { - canGroup: true, - isGroup: false, - currentClient: null, - inbounds: [], - clients: [], - editIds: [] - } - this.dbInbounds = dbInbounds; + show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) { this.visible = true; this.title = title; this.okText = okText; this.isEdit = isEdit; - if (app.subSettings.enable && dbInbounds !== null && Array.isArray(dbInbounds)) { - if (isEdit) { - this.showProcess(dbInbound, index); - let processSingleEdit = true - if (this.group.canGroup) { - this.group.currentClient = this.clients[this.index] - const response = app.getSubGroupClients(dbInbounds, this.group.currentClient) - if (response.clients.length > 1) { - this.group.isGroup = true; - this.group.inbounds = response.inbounds - this.group.clients = response.clients - this.group.editIds = response.editIds - if (this.clients[index].expiryTime < 0) { - this.delayedStart = true; - } - processSingleEdit = false - } - } - if (processSingleEdit) { - this.singleEditClientProcess(index) - } - } else { - this.group.isGroup = true; - dbInbounds.forEach((dbInboundItem) => { - this.showProcess(dbInboundItem); - if (this.dbInbound.isMultiUser()) { - this.addClient(this.inbound.protocol, this.clients); - this.group.inbounds.push(dbInboundItem.id) - this.group.clients.push(this.clients[this.index]) - } - }) - this.group.currentClient = this.clients[this.index] + this.dbInbound = new DBInbound(dbInbound); + this.inbound = dbInbound.toInbound(); + this.clients = this.inbound.clients; + this.index = index === null ? this.clients.length : index; + this.delayedStart = false; + if (isEdit) { + if (this.clients[index].expiryTime < 0) { + this.delayedStart = true; } + this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]); } else { - this.showProcess(dbInbound, index); - if (isEdit) { - this.singleEditClientProcess(index) - } else { - this.addClient(this.inbound.protocol, this.clients); - } + this.addClient(this.inbound.protocol, this.clients); } this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email); this.confirm = confirm; - }, - showProcess(dbInbound, index = null) { - this.dbInbound = new DBInbound(dbInbound); - this.inbound = dbInbound.toInbound(); - if (this.dbInbound.isMultiUser()) { - this.clients = this.inbound.clients; - this.index = index === null ? this.clients.length : index; - this.delayedStart = false; - } - }, - singleEditClientProcess(index) { - if (this.clients[index].expiryTime < 0) { - this.delayedStart = true; - } - this.oldClientId = this.getClientId(this.dbInbound.protocol, this.clients[index]); - }, + }, getClientId(protocol, client) { switch (protocol) { case Protocols.TROJAN: return client.password; @@ -174,7 +72,7 @@ clientModal.visible = false; clientModal.loading(false); }, - loading(loading = true) { + loading(loading=true) { clientModal.confirmLoading = loading; }, }; @@ -196,18 +94,6 @@ get isEdit() { return this.clientModal.isEdit; }, - get isGroup() { - return this.clientModal.group.isGroup; - }, - get isGroupEdit() { - return this.clientModal.group.canGroup; - }, - set isGroupEdit(value) { - this.clientModal.group.canGroup = value; - if (!value) { - this.clientModal.singleEditClientProcess(this.clientModal.index) - } - }, get datepicker() { return app.datepicker; }, @@ -234,36 +120,6 @@ }, }, methods: { - makeGroupEmailsUnique(dbInbounds, baseEmail, groupClients) { - // Extract the base part of the email (before the "__" if present) - const match = baseEmail.match(/^(.*?)__/); - const base = match ? match[1] : baseEmail; - - // Generate initial emails for each client in the group - const generatedEmails = groupClients.map((_, index) => `${base}__${index + 1}`); - - // Function to check if an email already exists in dbInbounds but belongs to a different subId - const isDuplicate = (emailToCheck, clientSubId) => { - return dbInbounds.some((dbInbound) => { - const settings = JSON.parse(dbInbound.settings); - const clients = settings && settings.clients ? settings.clients : []; - return clients.some(client => client.email === emailToCheck && client.subId !== clientSubId); - }); - }; - - // Check if any of the generated emails are duplicates - const hasDuplicates = generatedEmails.some((email, index) => { - return isDuplicate(email, groupClients[index].subId); - }); - - // If duplicates exist, add a random string to the base email to ensure uniqueness - if (hasDuplicates) { - const randomString = `-${RandomUtil.randomLowerAndNum(4)}`; - return groupClients.map((_, index) => `${base}${randomString}__${index + 1}`); - } - - return generatedEmails; - }, async getDBClientIps(email) { const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`); if (!msg.success) { @@ -291,22 +147,7 @@ } catch (error) { } }, - async resetClientTrafficHandler(client, dbInboundId, clients = []) { - if (clients.length > 0) { - const resetRequests = clients - .filter(client => { - const inbound = clientModal.dbInbounds.find(inbound => inbound.id === client.inboundId); - return inbound && app.hasClientStats(inbound, client.email); - }).map(client => ({ inboundId: client.inboundId, email: client.email})); - - return HttpUtil.postWithModalJson('/panel/inbound/resetGroupClientTraffic', resetRequests, null) - } else { - return HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email) - } - }, - resetClientTraffic(client, dbInboundId, iconElement) { - const subGroup = app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup && clientModal.dbInbounds && clientModal.dbInbounds.length > 0 ? app.getSubGroupClients(clientModal.dbInbounds, client) : []; - const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : []; + resetClientTraffic(email, dbInboundId, iconElement) { this.$confirm({ title: '{{ i18n "pages.inbounds.resetTraffic"}}', content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', @@ -315,8 +156,8 @@ cancelText: '{{ i18n "cancel"}}', onOk: async () => { iconElement.disabled = true; - const msg = await this.resetClientTrafficHandler(client, dbInboundId, clients); - if (msg && msg.success) { + const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email); + if (msg.success) { this.clientModal.clientStats.up = 0; this.clientModal.clientStats.down = 0; } diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index a0a1ced8..0b894f01 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -3,18 +3,6 @@ <a-form-item label='{{ i18n "pages.inbounds.enable" }}'> <a-switch v-model="client.enable"></a-switch> </a-form-item> - <a-form-item v-if="isEdit && app.subSettings.enable && isGroup"> - <template slot="label"> - <a-tooltip> - <template slot="title"> - <span>{{ i18n "pages.client.isGroupEditDesc" }}</span> - </template> - {{ i18n "pages.client.isGroupEdit" }} - <a-icon type="question-circle"></a-icon> - </a-tooltip> - </template> - <a-switch v-model="isGroupEdit"></a-switch> - </a-form-item> <a-form-item> <template slot="label"> <a-tooltip> @@ -146,7 +134,7 @@ <a-tooltip> <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> <a-icon type="retweet" - @click="resetClientTraffic(client,clientStats.inboundId,$event.target)" + @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon> </a-tooltip> </a-form-item> diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html index 74d77eab..13593cea 100644 --- a/web/html/xui/inbound_client_table.html +++ b/web/html/xui/inbound_client_table.html @@ -12,7 +12,7 @@ <template slot="title">{{ i18n "info" }}</template> <a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon> </a-tooltip> - <a-tooltip v-if="hasClientStats(record, client.email)"> + <a-tooltip> <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> <a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'> <a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon> diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index 66e4b7b7..4de3518c 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -14,7 +14,6 @@ confirmLoading: false, okText: '{{ i18n "sure" }}', isEdit: false, - isGroup: false, confirm: null, inbound: new Inbound(), dbInbound: new DBInbound(), @@ -62,9 +61,6 @@ get isEdit() { return inModal.isEdit; }, - get isGroup() { - return inModal.isGroup; - }, get client() { return inModal.inbound.clients[0]; }, diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index 1cd1be1a..89a37a29 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -224,10 +224,6 @@ <a-icon type="rest"></a-icon> {{ i18n "pages.inbounds.delDepletedClients" }} </a-menu-item> - <a-menu-item v-if="subSettings.enable && dbInbounds.length > 0" key="addGroupClient"> - <a-icon type="usergroup-add"></a-icon> - {{ i18n "pages.client.groupAdd"}} - </a-menu-item> </a-menu> </a-dropdown> </a-col> @@ -863,9 +859,6 @@ case "delDepletedClients": this.delDepletedClients(-1) break; - case "addGroupClient": - this.openGroupAddClient() - break; } }, clickAction(action, dbInbound) { @@ -1011,21 +1004,6 @@ await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal); }, - openGroupAddClient() { - clientModal.show({ - title: '{{ i18n "pages.client.groupAdd"}}', - okText: '{{ i18n "pages.client.submitAdd"}}', - dbInbounds: this.dbInbounds, - confirm: async (clients, dbInboundIds) => { - await this.addGroupClient(clients, dbInboundIds, clientModal).then((res) => { - if(res){ - this.showQrcode(dbInboundIds[0],clients[0], true) - } - }); - }, - isEdit: false - }); - }, openAddClient(dbInboundId) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); clientModal.show({ @@ -1033,11 +1011,7 @@ okText: '{{ i18n "pages.client.submitAdd"}}', dbInbound: dbInbound, confirm: async (clients, dbInboundId) => { - await this.addClient(clients, dbInboundId, clientModal).then((res) => { - if(res){ - this.showQrcode(dbInboundId,clients) - } - }); + await this.addClient(clients, dbInboundId, clientModal); }, isEdit: false }); @@ -1060,7 +1034,6 @@ clientModal.show({ title: '{{ i18n "pages.client.edit"}}', okText: '{{ i18n "pages.client.submitEdit"}}', - dbInbounds: this.dbInbounds, dbInbound: dbInbound, index: index, confirm: async (client, dbInboundId, clientId) => { @@ -1086,36 +1059,12 @@ }; await this.submit(`/panel/inbound/addClient`, data, modal); }, - async addGroupClient(clients, dbInboundIds, modal) { - const data = [] - dbInboundIds.forEach((dbInboundId, index) => { - data.push({ - id: dbInboundId, - settings: '{"clients": [' + clients[index].toString() + ']}', - }) - }) - return await this.submit(`/panel/inbound/addGroupClient`, data, modal, true) - }, async updateClient(client, dbInboundId, clientId) { - if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){ - const data = [] - client.forEach((client, index) => { - data.push({ - clientId: clientId[index], - inbound: { - id: dbInboundId[index], - settings: '{"clients": [' + client.toString() + ']}', - } - }) - }) - await this.submit(`/panel/inbound/updateClients`, data, clientModal, true); - }else{ - const data = { - id: dbInboundId, - settings: '{"clients": [' + client.toString() + ']}', - }; - await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal); - } + const data = { + id: dbInboundId, + settings: '{"clients": [' + client.toString() + ']}', + }; + await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal); }, resetTraffic(dbInboundId) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); @@ -1143,100 +1092,52 @@ onOk: () => this.submit('/panel/inbound/del/' + dbInboundId), }); }, - async delClientHandler(dbInboundId, currentClient, clients = []) { - if (clients.length > 0) { - const deleteRequestData = []; - - for (const client of clients) { - const dbInbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId); - if (dbInbound) { - const inbound = dbInbound.toInbound(); - if (inbound && inbound.clients && inbound.clients.length === 1) { - let newClient = Inbound.Settings.getSettings(inbound.protocol).toString(); - newClient = JSON.parse(newClient); - newClient = newClient && newClient.clients && newClient.clients.length > 0 ? JSON.stringify(newClient.clients[0], null, 2) : null; - if (newClient) { - const data = { - id: client.inboundId, - settings: '{"clients": [' + newClient + ']}', - }; - await this.submit(`/panel/inbound/addClient`, data, null); - } - } - - deleteRequestData.push({ - inboundId: client.inboundId, - clientId: client.clientId, - }); - } - } - await this.submit('/panel/inbound/delGroupClients', deleteRequestData, null, true); - } else { - const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); - const clientId = this.getClientId(dbInbound.protocol, currentClient); - await this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`); - } - }, - delClient(dbInboundId, currentClient, confirmation = true) { - const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, currentClient) : []; - const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : []; + delClient(dbInboundId, client,confirmation = true) { + dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); + clientId = this.getClientId(dbInbound.protocol, client); if (confirmation){ - const clientEmails = clients.length > 0 ? clients.map(item => item.email) : currentClient.email this.$confirm({ - title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + clientEmails, + title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email, content: '{{ i18n "pages.inbounds.deleteClientContent"}}', class: themeSwitcher.currentTheme, okText: '{{ i18n "delete"}}', cancelText: '{{ i18n "cancel"}}', - onOk: () => this.delClientHandler(dbInboundId, currentClient, clients), + onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`), }); } else { - this.delClientHandler(dbInboundId, currentClient, clients) + this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`); } }, getSubGroupClients(dbInbounds, currentClient) { - const response = { - inbounds: [], - clients: [], - editIds: [], - }; - - if (!Array.isArray(dbInbounds) || dbInbounds.length === 0) { - return response; - } - if (!currentClient || !currentClient.subId) { - return response; - } - - dbInbounds.forEach((dbInboundItem) => { - try { - const dbInbound = new DBInbound(dbInboundItem); - if (!dbInbound) { - return; + const response = { + inbounds: [], + clients: [], + editIds: [] } - - const inbound = dbInbound.toInbound(); - if (!inbound || !Array.isArray(inbound.clients)) { - return; + if (dbInbounds && dbInbounds.length > 0 && currentClient) { + dbInbounds.forEach((dbInboundItem) => { + const dbInbound = new DBInbound(dbInboundItem); + if (dbInbound) { + const inbound = dbInbound.toInbound(); + if (inbound) { + const clients = inbound.clients; + if (clients.length > 0) { + clients.forEach((client) => { + if (client['subId'] === currentClient['subId']) { + client['inboundId'] = dbInboundItem.id + client['clientId'] = this.getClientId(dbInbound.protocol, client) + response.inbounds.push(dbInboundItem.id) + response.clients.push(client) + response.editIds.push(client['clientId']) + } + }) + } + } + } + }) } - - inbound.clients.forEach((client) => { - if (client.subId === currentClient.subId) { - client.inboundId = dbInboundItem.id; - client.clientId = this.getClientId(dbInbound.protocol, client); - - response.inbounds.push(dbInboundItem.id); - response.clients.push(client); - response.editIds.push(client.clientId); - } - }); - } catch (error) { - console.error("Error processing dbInboundItem:", dbInboundItem, error); - } - }); - - return response; - }, + return response; + }, getClientId(protocol, client) { switch (protocol) { case Protocols.TROJAN: return client.password; @@ -1264,10 +1165,10 @@ } return newDbInbound; }, - showQrcode(dbInboundId, client, isJustSub = false) { + showQrcode(dbInboundId, client) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); newDbInbound = this.checkFallback(dbInbound); - qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub); + qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client); }, showInfo(dbInboundId, client) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); @@ -1287,61 +1188,36 @@ }, async switchEnableClient(dbInboundId, client) { this.loading() - const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : []; - if (subGroup && subGroup.clients && subGroup.clients.length > 0){ - await this.updateClient(subGroup.clients.map(item => { - item.enable = !item.enable - return item - }), subGroup.inbounds, subGroup.editIds); - }else{ - dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); - inbound = dbInbound.toInbound(); - clients = inbound.clients; - index = this.findIndexOfClient(dbInbound.protocol, clients, client); - clients[index].enable = !clients[index].enable; - clientId = this.getClientId(dbInbound.protocol, clients[index]); - await this.updateClient(clients[index], dbInboundId, clientId); - } + dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); + inbound = dbInbound.toInbound(); + clients = inbound.clients; + index = this.findIndexOfClient(dbInbound.protocol, clients, client); + clients[index].enable = !clients[index].enable; + clientId = this.getClientId(dbInbound.protocol, clients[index]); + await this.updateClient(clients[index], dbInboundId, clientId); this.loading(false); }, - async submit(url, data, model, isJson = false) { - const msg = isJson ? await HttpUtil.postWithModalJson(url, data, model) : await HttpUtil.postWithModal(url, data, model); + async submit(url, data, modal) { + const msg = await HttpUtil.postWithModal(url, data, modal); if (msg.success) { await this.getDBInbounds(); } - return msg }, getInboundClients(dbInbound) { return dbInbound.toInbound().clients; }, - resetClientTrafficHandler(client, dbInboundId, clients = []) { - if (clients.length > 0){ - const resetRequests = clients - .filter(client => { - const inbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId); - return inbound && this.hasClientStats(inbound, client.email); - }).map(client => ({ inboundId: client.inboundId, email: client.email})); - - this.submit('/panel/inbound/resetGroupClientTraffic', resetRequests, null, true) - }else { - this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email); - } - }, resetClientTraffic(client, dbInboundId, confirmation = true) { - const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : []; - const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : []; if (confirmation){ - const clientEmails = clients.length > 0 ? clients.map(item => item.email) : client.email this.$confirm({ - title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + clientEmails, + title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + client.email, content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', class: themeSwitcher.currentTheme, okText: '{{ i18n "reset"}}', cancelText: '{{ i18n "cancel"}}', - onOk: () => this.resetClientTrafficHandler(client, dbInboundId, clients), + onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email), }) } else { - this.resetClientTrafficHandler(client, dbInboundId, clients); + this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email); } }, resetAllTraffic() { @@ -1377,10 +1253,6 @@ isExpiry(dbInbound, index) { return dbInbound.toInbound().isExpiry(index); }, - hasClientStats(dbInbound, email) { - if (email.length == 0) return 0; - return !!dbInbound.clientStats.find(stats => stats.email === email); - }, getUpStats(dbInbound, email) { if (email.length == 0) return 0; clientStats = dbInbound.clientStats.find(stats => stats.email === email); diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html index ee4c7fe7..9eff8bec 100644 --- a/web/html/xui/settings.html +++ b/web/html/xui/settings.html @@ -268,7 +268,6 @@ <a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'> <a-list item-layout="horizontal"> <setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item> - <setting-list-item type="switch" title='{{ i18n "pages.settings.subSyncEnable"}}' desc='{{ i18n "pages.settings.subSyncEnableDesc"}}' v-model="allSetting.subSyncEnable"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item> diff --git a/web/job/client_traffic_sync_job.go b/web/job/client_traffic_sync_job.go deleted file mode 100644 index 587c76e1..00000000 --- a/web/job/client_traffic_sync_job.go +++ /dev/null @@ -1,178 +0,0 @@ -package job - -import ( - "encoding/json" - "fmt" - "time" - - "x-ui/database" - "x-ui/database/model" - "x-ui/logger" - "x-ui/xray" - - "gorm.io/gorm" -) - -type SyncClientTrafficJob struct { - subClientsCollection map[string][]string -} - -func NewClientTrafficSyncJob() *SyncClientTrafficJob { - return new(SyncClientTrafficJob) -} -func (j *SyncClientTrafficJob) Run() { - // Step 1: Group clients by SubID - subClientsCollection := j.collectClientsGroupedBySubId() - - // Step 2: Sync client traffics for each SubID group - for subId, emails := range subClientsCollection { - err := j.syncClientTraffics(map[string][]string{subId: emails}) - if err != nil { - logger.Error("Failed to sync traffics for SubID ", subId, ": ", err) - } - } -} - -// collectClientsGroupedBySubId groups clients by their SubIDs -func (j *SyncClientTrafficJob) collectClientsGroupedBySubId() map[string][]string { - db := database.GetDB() - result := make(map[string][]string) - - // Fetch all inbounds - var inbounds []*model.Inbound - if err := db.Model(&model.Inbound{}).Find(&inbounds).Error; err != nil { - logger.Error("Error fetching inbounds: ", err) - return result // Return empty map on error - } - - // Process each inbound - for _, inbound := range inbounds { - if inbound.Settings == "" { - continue - } - - settingsMap, err := parseSettings(inbound.Settings, uint(inbound.Id)) - if err != nil { - logger.Error(err) - continue - } - - clients, ok := settingsMap["clients"].([]interface{}) - if !ok { - continue - } - - processClients(clients, result) - } - - // Remove SubIDs with one or fewer emails - filterSingleEmailSubIDs(result) - - return result -} - -// parseSettings unmarshals the JSON settings and returns it as a map -func parseSettings(settings string, inboundID uint) (map[string]interface{}, error) { - if !json.Valid([]byte(settings)) { - return nil, fmt.Errorf("Invalid JSON format in Settings for inbound ID %d", inboundID) - } - - var tempData map[string]interface{} - if err := json.Unmarshal([]byte(settings), &tempData); err != nil { - return nil, fmt.Errorf("Error unmarshalling settings for inbound ID %d: %v", inboundID, err) - } - - return tempData, nil -} - -// processClients extracts SubID and email from the clients and populates the result map -func processClients(clients []interface{}, result map[string][]string) { - for _, client := range clients { - clientMap, ok := client.(map[string]interface{}) - if !ok { - continue - } - - subId, ok := clientMap["subId"].(string) - if !ok || subId == "" { - continue - } - - email, ok := clientMap["email"].(string) - if !ok || email == "" { - continue - } - - result[subId] = append(result[subId], email) - } -} - -// filterSingleEmailSubIDs removes SubIDs with one or fewer emails from the result map -func filterSingleEmailSubIDs(result map[string][]string) { - for subId, emails := range result { - if len(emails) <= 1 { - delete(result, subId) - } - } -} - -// syncClientTraffics synchronizes traffic data for each SubID group -func (j *SyncClientTrafficJob) syncClientTraffics(result map[string][]string) error { - for subId, emails := range result { - db := database.GetDB() - - // Step 1: Calculate maxUp and maxDown (outside transaction) - var maxUp, maxDown int64 - err := calculateMaxTraffic(db, emails, &maxUp, &maxDown) - if err != nil { - logger.Error("Failed to calculate max traffic for SubID ", subId, ": ", err) - continue - } - - // Step 2: Update traffic data with retry mechanism - err = retryOperation(func() error { - return updateTraffic(db, emails, maxUp, maxDown) - }, 5, 100*time.Millisecond) - - if err != nil { - logger.Error("Failed to update client traffics for SubID ", subId, ": ", err) - } - } - return nil -} - -// calculateMaxTraffic calculates max up and down traffic for a group of emails -func calculateMaxTraffic(db *gorm.DB, emails []string, maxUp, maxDown *int64) error { - return db.Model(&xray.ClientTraffic{}). - Where("email IN ?", emails). - Select("MAX(up) AS max_up, MAX(down) AS max_down"). - Row(). - Scan(maxUp, maxDown) -} - -// updateTraffic updates the traffic data in the database within a transaction -func updateTraffic(db *gorm.DB, emails []string, maxUp, maxDown int64) error { - return db.Transaction(func(tx *gorm.DB) error { - return tx.Model(&xray.ClientTraffic{}). - Where("email IN ?", emails). - Updates(map[string]interface{}{ - "up": maxUp, - "down": maxDown, - }).Error - }) -} - -// retryOperation retries an operation multiple times with a delay -func retryOperation(operation func() error, maxRetries int, delay time.Duration) error { - var err error - for i := 0; i < maxRetries; i++ { - err = operation() - if err == nil { - return nil - } - logger.Info(fmt.Sprintf("Retry %d/%d failed: %v", i+1, maxRetries, err)) - time.Sleep(delay) - } - return err -} - diff --git a/web/service/setting.go b/web/service/setting.go index 0e9a009e..d238c33d 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -50,7 +50,6 @@ var defaultValueMap = map[string]string{ "tgLang": "en-US", "secretEnable": "false", "subEnable": "false", - "subSyncEnable": "true", "subListen": "", "subPort": "2096", "subPath": "/sub/", @@ -417,14 +416,6 @@ func (s *SettingService) GetSubEnable() (bool, error) { return s.getBool("subEnable") } -func (s *SettingService) GetSubSyncEnable() (bool, error) { - return s.getBool("subSyncEnable") -} - -func (s *SettingService) SetSubSyncEnable(value bool) error { - return s.setBool("subSyncEnable", value) -} - func (s *SettingService) GetSubListen() (string, error) { return s.getString("subListen") } @@ -553,7 +544,6 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) { "defaultKey": func() (interface{}, error) { return s.GetKeyFile() }, "tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() }, "subEnable": func() (interface{}, error) { return s.GetSubEnable() }, - "subSyncEnable": func() (interface{}, error) { return s.GetSubSyncEnable() }, "subURI": func() (interface{}, error) { return s.GetSubURI() }, "subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() }, "remarkModel": func() (interface{}, error) { return s.GetRemarkModel() }, diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 0e62db38..638d619e 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "Add Client" -"groupAdd" = "Add subscription user" -"isGroupEdit" = "Group editing" -"isGroupEditDesc" = "All clients with the same subscription are edited" "edit" = "Edit Client" "submitAdd" = "Add Client" "submitEdit" = "Save Changes" @@ -290,8 +287,6 @@ "subSettings" = "Subscription" "subEnable" = "Enable Subscription Service" "subEnableDesc" = "Enables the subscription service." -"subSyncEnable" = "Enable Subscription Sync" -"subSyncEnableDesc" = "Traffic from clients with the same subscription will be synchronized every 10 seconds." "subListen" = "Listen IP" "subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)" "subPort" = "Listen Port" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index e529267c..62ae984e 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -190,7 +190,6 @@ [pages.client] "add" = "Agregar Cliente" -"groupAdd" = "Agregar usuario de suscripción" "edit" = "Editar Cliente" "submitAdd" = "Agregar Cliente" "submitEdit" = "Guardar Cambios" @@ -288,8 +287,6 @@ "subSettings" = "Suscripción" "subEnable" = "Habilitar Servicio" "subEnableDesc" = "Función de suscripción con configuración separada." -"subSyncEnable" = "Habilitar sincronización de suscripciones" -"subSyncEnableDesc" = "El tráfico de los clientes con la misma suscripción se sincronizará cada 10 segundos." "subListen" = "Listening IP" "subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs." "subPort" = "Puerto de Suscripción" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 18ed3dfc..6219996d 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "کاربر جدید" -"groupAdd" = "افزودن کاربر سابسکریپشن" -"isGroupEdit" = "ویرایش گروهی" -"isGroupEditDesc" = "همه کاربران با سابسکریپشن یکسان ویرایش می شوند" "edit" = "ویرایش کاربر" "submitAdd" = "اضافه کردن" "submitEdit" = "ذخیره تغییرات" @@ -290,8 +287,6 @@ "subSettings" = "سابسکریپشن" "subEnable" = "فعالسازی سرویس سابسکریپشن" "subEnableDesc" = "سرویس سابسکریپشن را فعالمیکند" -"subSyncEnable" = "فعالسازی همگام سازی سابسکریپشن" -"subSyncEnableDesc" = "ترافیک کلاینت هایی که سابسکریپشن یکسان دارند هر ۱۰ ثانیه همگام میشوند." "subListen" = "آدرس آیپی" "subListenDesc" = "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید" "subPort" = "پورت" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml index 0d768add..6eb6d9b5 100644 --- a/web/translation/translate.id_ID.toml +++ b/web/translation/translate.id_ID.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "Tambah Klien" -"groupAdd" = "Tambahkan pengguna langganan" -"isGroupEdit" = "Pengeditan grup" -"isGroupEditDesc" = "Semua klien dengan langganan yang sama akan diedit" "edit" = "Edit Klien" "submitAdd" = "Tambah Klien" "submitEdit" = "Simpan Perubahan" @@ -290,8 +287,6 @@ "subSettings" = "Langganan" "subEnable" = "Aktifkan Layanan Langganan" "subEnableDesc" = "Mengaktifkan layanan langganan." -"subSyncEnable" = "Aktifkan Sinkronisasi Langganan" -"subSyncEnableDesc" = "Lalu lintas dari klien dengan langganan yang sama akan disinkronkan setiap 10 detik." "subListen" = "IP Pendengar" "subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)" "subPort" = "Port Pendengar" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml index e4f46387..db024b3e 100644 --- a/web/translation/translate.ja_JP.toml +++ b/web/translation/translate.ja_JP.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "クライアント追加" -"groupAdd" = "サブスクリプション ユーザーの追加" -"isGroupEdit" = "グループの編集" -"isGroupEditDesc" = "同じサブスクリプションを持つすべてのクライアントが編集されます" "edit" = "クライアント編集" "submitAdd" = "クライアント追加" "submitEdit" = "変更を保存" @@ -290,8 +287,6 @@ "subSettings" = "サブスクリプション設定" "subEnable" = "サブスクリプションサービスを有効にする" "subEnableDesc" = "サブスクリプションサービス機能を有効にする" -"subSyncEnable" = "サブスクリプション同期を有効にする" -"subSyncEnableDesc" = "同じサブスクリプションを持つクライアントからのトラフィックは 10 秒ごとに同期されます。" "subListen" = "監視IP" "subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)" "subPort" = "監視ポート" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml index f83f14de..bc0c617c 100644 --- a/web/translation/translate.pt_BR.toml +++ b/web/translation/translate.pt_BR.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "Adicionar Cliente" -"groupAdd" = "Adicionar usuário de assinatura" -"isGroupEdit" = "Edição de grupo" -"isGroupEditDesc" = "Todos os clientes com a mesma assinatura são editados" "edit" = "Editar Cliente" "submitAdd" = "Adicionar Cliente" "submitEdit" = "Salvar Alterações" @@ -290,8 +287,6 @@ "subSettings" = "Assinatura" "subEnable" = "Ativar Serviço de Assinatura" "subEnableDesc" = "Ativa o serviço de assinatura." -"subSyncEnable" = "Habilitar sincronização de assinatura" -"subSyncEnableDesc" = "O tráfego de clientes com a mesma assinatura será sincronizado a cada 10 segundos." "subListen" = "IP de Escuta" "subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)" "subPort" = "Porta de Escuta" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 1c47f5c2..e0875226 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "Добавить пользователя" -"groupAdd" = "Добавить пользователя подписки" -"isGroupEdit" = "Групповое редактирование" -"isGroupEditDesc" = "Все клиенты с одинаковой подпиской редактируются" "edit" = "Редактировать пользователя" "submitAdd" = "Добавить пользователя" "submitEdit" = "Сохранить изменения" @@ -290,8 +287,6 @@ "subSettings" = "Подписка" "subEnable" = "Включить службу" "subEnableDesc" = "Функция подписки с отдельной конфигурацией" -"subSyncEnable" = "Включить синхронизацию подписки" -"subSyncEnableDesc" = "Трафик от клиентов с одинаковой подпиской будет синхронизироваться каждые 10 секунд." "subListen" = "Прослушивание IP" "subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса" "subPort" = "Порт подписки" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml index 68c947aa..662c87c3 100644 --- a/web/translation/translate.tr_TR.toml +++ b/web/translation/translate.tr_TR.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "Müşteri Ekle" -"groupAdd" = "Abonelik kullanıcısı ekle" -"isGroupEdit" = "Grup düzenleme" -"isGroupEditDesc" = "Aynı aboneliğe sahip tüm istemciler düzenlendi" "edit" = "Müşteriyi Düzenle" "submitAdd" = "Müşteri Ekle" "submitEdit" = "Değişiklikleri Kaydet" @@ -290,8 +287,6 @@ "subSettings" = "Abonelik" "subEnable" = "Abonelik Hizmetini Etkinleştir" "subEnableDesc" = "Abonelik hizmetini etkinleştirir." -"subSyncEnable" = "Abonelik Senkronizasyonunu Etkinleştir" -"subSyncEnableDesc" = "Aynı aboneliğe sahip istemcilerden gelen trafik her 10 saniyede bir senkronize edilecektir." "subListen" = "Dinleme IP" "subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)" "subPort" = "Dinleme Portu" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml index afb0bc06..1619dda1 100644 --- a/web/translation/translate.uk_UA.toml +++ b/web/translation/translate.uk_UA.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "Додати клієнта" -"groupAdd" = "Додати підписаного користувача" -"isGroupEdit" = "Редагування групи" -"isGroupEditDesc" = "Всі клієнти з однаковою підпискою редагуються" "edit" = "Редагувати клієнта" "submitAdd" = "Додати клієнта" "submitEdit" = "Зберегти зміни" @@ -290,8 +287,6 @@ "subSettings" = "Підписка" "subEnable" = "Увімкнути службу підписки" "subEnableDesc" = "Вмикає службу підписки." -"subSyncEnable" = "Увімкнути синхронізацію підписки" -"subSyncEnableDesc" = "Трафік від клієнтів з однаковою підпискою буде синхронізовано кожні 10 секунд." "subListen" = "Слухати IP" "subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)" "subPort" = "Слухати порт" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 46af08d7..8f6edb32 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "Thêm người dùng" -"groupAdd" = "Thêm người dùng đăng ký" -"isGroupEdit" = "Chỉnh sửa nhóm" -"isGroupEditDesc" = "Tất cả người dùng có cùng đăng ký đều có thể được chỉnh sửa" "edit" = "Chỉnh sửa người dùng" "submitAdd" = "Thêm" "submitEdit" = "Lưu thay đổi" @@ -290,8 +287,6 @@ "subSettings" = "Gói đăng ký" "subEnable" = "Bật dịch vụ" "subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng" -"subSyncEnable" = "Bật đồng bộ đăng ký" -"subSyncEnableDesc" = "Lưu lượng truy cập từ các máy khách có cùng đăng ký sẽ được đồng bộ sau mỗi 10 giây." "subListen" = "Listening IP" "subListenDesc" = "Mặc định để trống để nghe tất cả các IP" "subPort" = "Cổng gói đăng ký" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index 39cc4276..843fee1b 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "添加客户端" -"groupAdd" = "添加订阅用户" -"isGroupEdit" = "群组编辑" -"isGroupEditDesc" = "所有具有相同订阅的客户端均被编辑" "edit" = "编辑客户端" "submitAdd" = "添加客户端" "submitEdit" = "保存修改" @@ -290,8 +287,6 @@ "subSettings" = "订阅设置" "subEnable" = "启用订阅服务" "subEnableDesc" = "启用订阅服务功能" -"subSyncEnable" = "启用订阅同步" -"subSyncEnableDesc" = "具有相同订阅的客户端的流量将每 10 秒同步一次。" "subListen" = "监听 IP" "subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)" "subPort" = "监听端口" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml index 6726282d..3e4dda7f 100644 --- a/web/translation/translate.zh_TW.toml +++ b/web/translation/translate.zh_TW.toml @@ -190,9 +190,6 @@ [pages.client] "add" = "新增客戶端" -"groupAdd" = "新增訂閱使用者" -"isGroupEdit" = "群組編輯" -"isGroupEditDesc" = "所有具有相同訂閱的用戶端都被編輯" "edit" = "編輯客戶端" "submitAdd" = "新增客戶端" "submitEdit" = "儲存修改" @@ -290,8 +287,6 @@ "subSettings" = "訂閱設定" "subEnable" = "啟用訂閱服務" "subEnableDesc" = "啟用訂閱服務功能" -"subSyncEnable" = "啟用訂閱同步" -"subSyncEnableDesc" = "來自具有相同訂閱的客戶端的流量將每 10 秒同步一次。" "subListen" = "監聽 IP" "subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)" "subPort" = "監聽埠" diff --git a/web/web.go b/web/web.go index 4bd1e1db..35ccec70 100644 --- a/web/web.go +++ b/web/web.go @@ -260,13 +260,6 @@ func (s *Server) startTask() { s.cron.AddJob("@every 10s", job.NewXrayTrafficJob()) }() - isSubEnable, err1 := s.settingService.GetSubEnable() - isSubSyncEnable, err2 := s.settingService.GetSubSyncEnable() - if err1 == nil && err2 == nil && isSubEnable && isSubSyncEnable { - // Sync the client traffic with the same SubId every 10 seconds - s.cron.AddJob("@every 10s", job.NewClientTrafficSyncJob()) - } - // check client ips from log file every 10 sec s.cron.AddJob("@every 10s", job.NewCheckClientIpJob()) diff --git a/x-ui.sh b/x-ui.sh index 7e8cbfda..4f2e57d8 100644 --- a/x-ui.sh +++ b/x-ui.sh @@ -1912,4 +1912,4 @@ if [[ $# > 0 ]]; then esac else show_menu -fi \ No newline at end of file +fi diff --git a/xray/client_traffic.go b/xray/client_traffic.go index 6eb2d270..0f2389a0 100644 --- a/xray/client_traffic.go +++ b/xray/client_traffic.go @@ -2,9 +2,9 @@ package xray type ClientTraffic struct { Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - InboundId int `json:"inboundId" form:"inboundId" gorm:"index;not null"` + InboundId int `json:"inboundId" form:"inboundId"` Enable bool `json:"enable" form:"enable"` - Email string `json:"email" form:"email" gorm:"uniqueIndex"` + Email string `json:"email" form:"email" gorm:"unique"` Up int64 `json:"up" form:"up"` Down int64 `json:"down" form:"down"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`