Browse Source

Group editing feature of users with the same subscription (#1661)

Ali Rahimi 1 year ago
parent
commit
b172d450e3

+ 81 - 0
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 {

+ 108 - 38
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;
             },

+ 12 - 0
web/html/xui/form/client.html

@@ -3,6 +3,18 @@
     <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.inbounds.isGroupEditDesc" }}</span>
+                </template>
+                {{ i18n "pages.inbounds.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>

+ 24 - 9
web/html/xui/inbounds.html

@@ -894,7 +894,7 @@
                 clientModal.show({
                     title: '{{ i18n "pages.client.groupAdd"}}',
                     okText: '{{ i18n "pages.client.submitAdd"}}',
-                    dbInbound: this.dbInbounds,
+                    dbInbounds: this.dbInbounds,
                     confirm: async (clients, dbInboundIds) => {
                         clientModal.loading();
                         await this.addGroupClient(clients, dbInboundIds);
@@ -939,6 +939,7 @@
                 clientModal.show({
                     title: '{{ i18n "pages.client.edit"}}',
                     okText: '{{ i18n "pages.client.submitEdit"}}',
+                    dbInbounds: this.dbInbounds,
                     dbInbound: dbInbound,
                     index: index,
                     confirm: async (client, dbInboundId, clientId) => {
@@ -958,10 +959,10 @@
                 }
             },
             async addClient(clients, dbInboundId) {
-                const data = [{
+                const data = {
                     id: dbInboundId,
                     settings: '{"clients": [' + clients.toString() + ']}',
-                }];
+                };
 
                 await this.submit(`/panel/inbound/addClient`, data, true)
             },
@@ -975,14 +976,28 @@
                     })
                 })
 
-                await this.submit(`/panel/inbound/addClient`, data, true)
+                await this.submit(`/panel/inbound/addGroupClient`, data, true)
             },
             async updateClient(client, dbInboundId, clientId) {
-                const data = {
-                    id: dbInboundId,
-                    settings: '{"clients": [' + client.toString() + ']}',
-                };
-                await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
+                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, true);
+                }else{
+                    const data = {
+                        id: dbInboundId,
+                        settings: '{"clients": [' + client.toString() + ']}',
+                    };
+                    await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
+                }
             },
             resetTraffic(dbInboundId) {
                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);

+ 2 - 0
web/translation/translate.en_US.toml

@@ -181,6 +181,8 @@
 "exportInbound" = "Export Inbound"
 "import" = "Import"
 "importInbound" = "Import an Inbound"
+"isGroupEdit" = "Group editing"
+"isGroupEditDesc" = "All clients with the same subscription are edited"
 
 [pages.client]
 "add" = "Add Client"

+ 2 - 0
web/translation/translate.es_ES.toml

@@ -181,6 +181,8 @@
 "exportInbound" = "Exportación entrante"
 "import" = "Importar"
 "importInbound" = "Importar un entrante"
+"isGroupEdit" = "Edición de grupo"
+"isGroupEditDesc" = "Se editan todos los usuarios con la misma suscripción"
 
 [pages.client]
 "add" = "Agregar Cliente"

+ 2 - 0
web/translation/translate.fa_IR.toml

@@ -181,6 +181,8 @@
 "exportInbound" = "استخراج ورودی"
 "import" = "افزودن"
 "importInbound" = "افزودن یک ورودی"
+"isGroupEdit" = "ویرایش گروهی"
+"isGroupEditDesc" = "تمامی کاربران با سابسکریپشن یکسان ویرایش می‌شوند"
 
 [pages.client]
 "add" = "کاربر جدید"

+ 2 - 0
web/translation/translate.ru_RU.toml

@@ -181,6 +181,8 @@
 "exportInbound" = "Экспорт входящих"
 "import" = "Импортировать"
 "importInbound" = "Импортировать входящее сообщение"
+"isGroupEdit" = "Редактирование группы"
+"isGroupEditDesc" = "Редактируются все пользователи с одной подпиской"
 
 [pages.client]
 "add" = "Добавить пользователя"

+ 2 - 0
web/translation/translate.vi_VN.toml

@@ -181,6 +181,8 @@
 "exportInbound" = "Xuất nhập khẩu"
 "import" = "Nhập"
 "importInbound" = "Nhập inbound"
+"isGroupEdit" = "Chỉnh sửa nhóm"
+"isGroupEditDesc" = "Tất cả người dùng có cùng đăng ký đều được chỉnh sửa"
 
 [pages.client]
 "add" = "Thêm người dùng"

+ 2 - 0
web/translation/translate.zh_Hans.toml

@@ -181,6 +181,8 @@
 "exportInbound" = "出口 入境"
 "import"="导入"
 "importInbound" = "导入入站"
+"isGroupEdit" = "分组编辑"
+"isGroupEditDesc" = "编辑具有相同订阅的所有用户"
 
 [pages.client]
 "add" = "添加客户端"