From b172d450e3a9dba8a56b2f6cd9e059559d232033 Mon Sep 17 00:00:00 2001 From: Ali Rahimi Date: Mon, 22 Jan 2024 12:36:01 +0100 Subject: [PATCH] Group editing feature of users with the same subscription (#1661) --- web/controller/inbound.go | 81 ++++++++++++++ web/html/xui/client_modal.html | 146 ++++++++++++++++++------- web/html/xui/form/client.html | 12 ++ web/html/xui/inbounds.html | 33 ++++-- web/translation/translate.en_US.toml | 2 + web/translation/translate.es_ES.toml | 2 + web/translation/translate.fa_IR.toml | 2 + web/translation/translate.ru_RU.toml | 2 + web/translation/translate.vi_VN.toml | 2 + web/translation/translate.zh_Hans.toml | 2 + 10 files changed, 237 insertions(+), 47 deletions(-) diff --git a/web/controller/inbound.go b/web/controller/inbound.go index b274be64..4d6e0af0 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "encoding/json" "fmt" "strconv" @@ -32,7 +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("/updateClients", a.updateGroupInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/resetAllTraffics", a.resetAllTraffics) @@ -186,6 +189,34 @@ 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 { @@ -230,6 +261,56 @@ 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 { diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html index 02c548e3..cb15e1e7 100644 --- a/web/html/xui/client_modal.html +++ b/web/html/xui/client_modal.html @@ -16,12 +16,15 @@ title: '', okText: '', group: { + canGroup: true, isGroup: false, currentClient: null, inbounds: [], clients: [], + editIds: [] }, dbInbound: new DBInbound(), + dbInbounds: null, inbound: new Inbound(), clients: [], clientStats: [], @@ -30,64 +33,95 @@ clientIps: null, delayedStart: false, ok() { - if (clientModal.isEdit) { - ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId); - } else { - if (clientModal.group.isGroup) { - const currentClient = clientModal.group.currentClient; + if (clientModal.group.isGroup && clientModal.group.canGroup) { + const currentClient = clientModal.group.currentClient; - clientModal.group.clients.forEach((client, index) => { - const { email, limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient; + clientModal.group.clients.forEach((client, index) => { + const { email, limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient; - client.email = `${email}-${index + 1}`; - client.limitIp = limitIp; - client.totalGB = totalGB; - client.expiryTime = expiryTime; - client.reset = reset; - client.enable = enable; + const match = email.match(/^(.*?)__/); + const new_email = match ? match[1] : email; - if (subId) { - client.subId = subId; - } - if (tgId) { - client.tgId = tgId; - } - if (flow) { - client.flow = flow; - } - }); + client.email = `${new_email}__${index + 1}`; + client.limitIp = limitIp; + 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); - } else { + } + } 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); } } }, - show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) { + 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; this.visible = true; this.title = title; this.okText = okText; this.isEdit = isEdit; - if (Array.isArray(dbInbound)) { - this.group.isGroup = true; - dbInbound.forEach((dbInboundItem) => { - this.showProcess(dbInboundItem); - this.group.inbounds.push(dbInboundItem.id) - this.group.clients.push(this.clients[this.index]) - }) - this.group.currentClient = this.clients[this.index] + if (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 = this.getGroupInboundsClients(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); + 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] + } } else { this.showProcess(dbInbound, index); if (isEdit) { - if (this.clients[index].expiryTime < 0) { - this.delayedStart = true; - } - this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]); + this.singleEditClientProcess(index) } else { this.addClient(this.inbound.protocol, this.clients); } @@ -101,7 +135,34 @@ this.clients = this.inbound.clients; this.index = index === null ? this.clients.length : index; this.delayedStart = false; - this.addClient(this.inbound.protocol, this.clients); + }, + singleEditClientProcess(index) { + if (this.clients[index].expiryTime < 0) { + this.delayedStart = true; + } + this.oldClientId = this.getClientId(this.dbInbound.protocol, this.clients[index]); + }, + getGroupInboundsClients(dbInbounds, currentClient) { + const response = { + inbounds: [], + clients: [], + editIds: [] + } + dbInbounds.forEach((dbInboundItem) => { + const dbInbound = new DBInbound(dbInboundItem); + const inbound = dbInbound.toInbound(); + const clients = inbound.clients; + if (clients.length > 0){ + clients.forEach((client) => { + if (client['subId'] === currentClient['subId']){ + response.inbounds.push(dbInboundItem.id) + response.clients.push(client) + response.editIds.push(this.getClientId(dbInbound.protocol, client)) + } + }) + } + }) + return response; }, getClientId(protocol, client) { switch (protocol) { @@ -148,6 +209,15 @@ 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; }, diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index f4ac25f3..434a806e 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -3,6 +3,18 @@ + + + +