Browse Source

add group user with the same subscription id to all inbounds (#1650)

Ali Rahimi 1 year ago
parent
commit
5c695ca652

+ 35 - 0
web/assets/js/util/utils.js

@@ -83,6 +83,41 @@ 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(url, 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 {

+ 23 - 16
web/controller/inbound.go

@@ -159,24 +159,31 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
 }
 
 func (a *InboundController) addInboundClient(c *gin.Context) {
-	data := &model.Inbound{}
-	err := c.ShouldBind(data)
-	if err != nil {
-		jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
-		return
-	}
+	var requestData []model.Inbound
 
-	needRestart := true
+    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()
+    }
 
-	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) {

+ 12 - 5
web/html/common/qrcode_modal.html

@@ -11,10 +11,12 @@
         <a-divider>Subscription</a-divider>
         <div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div>
     </template>
-    <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
-    <template v-for="(row, index) in qrModal.qrcodes">
-        <a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
-        <div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div>
+    <a-divider v-if="!isJustSub">{{ i18n "pages.inbounds.client" }}</a-divider>
+    <template v-if="!isJustSub">
+        <template v-for="(row, index) in qrModal.qrcodes">
+            <a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
+            <div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div>
+        </template>
     </template>
 </a-modal>
 
@@ -27,12 +29,14 @@
         qrcodes: [],
         clipboard: null,
         visible: false,
+        isJustSub: false,
         subId: '',
-        show: function (title = '', dbInbound, client) {
+        show: function (title = '', dbInbound, client, isJustSub = false) {
             this.title = title;
             this.dbInbound = dbInbound;
             this.inbound = dbInbound.toInbound();
             this.client = client;
+            this.isJustSub = isJustSub;
             this.subId = '';
             this.qrcodes = [];
             this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
@@ -53,6 +57,9 @@
         el: '#qrcode-modal',
         data: {
             qrModal: qrModal,
+            get isJustSub(){
+               return qrModal.isJustSub
+            }
         },
         methods: {
             copyToClipboard(elmentId, content) {

+ 67 - 13
web/html/xui/client_modal.html

@@ -15,7 +15,12 @@
         confirmLoading: false,
         title: '',
         okText: '',
-        isEdit: false,
+        group: {
+            isGroup: false,
+            currentClient: null,
+            inbounds: [],
+            clients: [],
+        },
         dbInbound: new DBInbound(),
         inbound: new Inbound(),
         clients: [],
@@ -28,30 +33,76 @@
             if (clientModal.isEdit) {
                 ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
             } else {
-                ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
+                if (clientModal.group.isGroup) {
+                    const currentClient = clientModal.group.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;
+
+                        if (subId) {
+                            client.subId = subId;
+                        }
+                        if (tgId) {
+                            client.tgId = tgId;
+                        }
+                        if (flow) {
+                            client.flow = flow;
+                        }
+                    });
+                    ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds);
+                } else {
+                    ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
+                }
             }
         },
         show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
+            this.group = {
+                isGroup: false,
+                currentClient: null,
+                inbounds: [],
+                clients: [],
+            }
             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]
+            } else {
+                this.showProcess(dbInbound, index);
+                if (isEdit) {
+                    if (this.clients[index].expiryTime < 0) {
+                        this.delayedStart = true;
+                    }
+                    this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
+                } else {
+                    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();
             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.addClient(this.inbound.protocol, this.clients);
-            }
-            this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
-            this.confirm = confirm;
-        },  
+            this.addClient(this.inbound.protocol, this.clients);
+        },
         getClientId(protocol, client) {
             switch (protocol) {
                 case Protocols.TROJAN: return client.password;
@@ -94,6 +145,9 @@
             get isEdit() {
                 return this.clientModal.isEdit;
             },
+            get isGroup() {
+                return this.clientModal.group.isGroup;
+            },
             get datepicker() {
                 return app.datepicker;
             },

+ 2 - 2
web/html/xui/form/client.html

@@ -15,7 +15,7 @@
         </template>
         <a-input v-model.trim="client.email"></a-input>
     </a-form-item>
-    <a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
+    <a-form-item v-if="(inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS) && !isGroup">
         <template slot="label">
             <a-tooltip>
                 <template slot="title">
@@ -28,7 +28,7 @@
         </template>
         <a-input v-model.trim="client.password"></a-input>
     </a-form-item>
-    <a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
+    <a-form-item v-if="(inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS) && !isGroup">
         <template slot="label">
             <a-tooltip>
                 <template slot="title">

+ 4 - 0
web/html/xui/inbound_modal.html

@@ -13,6 +13,7 @@
         confirmLoading: false,
         okText: '{{ i18n "sure" }}',
         isEdit: false,
+        isGroup: false,
         confirm: null,
         inbound: new Inbound(),
         dbInbound: new DBInbound(),
@@ -60,6 +61,9 @@
             get isEdit() {
                 return inModal.isEdit;
             },
+            get isGroup() {
+                return inModal.isGroup;
+            },
             get client() {
                 return inModal.inbound.clients[0];
             },

+ 47 - 12
web/html/xui/inbounds.html

@@ -145,6 +145,10 @@
                                                 <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>
@@ -285,7 +289,7 @@
                                             <p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
                                         </template>
                                         <a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
-                                    </a-popover>  
+                                    </a-popover>
                                 </template>
                             </template>
                             <template slot="traffic" slot-scope="text, dbInbound">
@@ -339,7 +343,7 @@
                                                         <a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
                                                         <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
                                                         <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
-                                                    </template>                    
+                                                    </template>
                                                 </td>
                                             </tr>
                                             <tr>
@@ -373,7 +377,7 @@
                                                             <p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
                                                         </template>
                                                         <a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
-                                                    </a-popover>                
+                                                    </a-popover>
                                                 </td>
                                             </tr>
                                             <tr>
@@ -740,6 +744,9 @@
                     case "delDepletedClients":
                         this.delDepletedClients(-1)
                         break;
+                    case "addGroupClient":
+                        this.openGroupAddClient()
+                        break;
                 }
             },
             clickAction(action, dbInbound) {
@@ -883,6 +890,20 @@
 
                 await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
             },
+            openGroupAddClient() {
+                clientModal.show({
+                    title: '{{ i18n "pages.client.groupAdd"}}',
+                    okText: '{{ i18n "pages.client.submitAdd"}}',
+                    dbInbound: this.dbInbounds,
+                    confirm: async (clients, dbInboundIds) => {
+                        clientModal.loading();
+                        await this.addGroupClient(clients, dbInboundIds);
+                        clientModal.close();
+                        await this.showQrcode(dbInboundIds[0],clients[0], true)
+                    },
+                    isEdit: false
+                });
+            },
             openAddClient(dbInboundId) {
                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
                 clientModal.show({
@@ -893,6 +914,7 @@
                         clientModal.loading();
                         await this.addClient(clients, dbInboundId);
                         clientModal.close();
+                        await this.showQrcode(dbInboundId,clients)
                     },
                     isEdit: false
                 });
@@ -936,11 +958,24 @@
                 }
             },
             async addClient(clients, dbInboundId) {
-                const data = {
+                const data = [{
                     id: dbInboundId,
                     settings: '{"clients": [' + clients.toString() + ']}',
-                };
-                await this.submit(`/panel/inbound/addClient`, data);
+                }];
+
+                await this.submit(`/panel/inbound/addClient`, data, true)
+            },
+
+            async addGroupClient(clients, dbInboundIds) {
+                const data = []
+                dbInboundIds.forEach((dbInboundId, index) => {
+                    data.push({
+                        id: dbInboundId,
+                        settings: '{"clients": [' + clients[index].toString() + ']}',
+                    })
+                })
+
+                await this.submit(`/panel/inbound/addClient`, data, true)
             },
             async updateClient(client, dbInboundId, clientId) {
                 const data = {
@@ -1001,8 +1036,8 @@
             checkFallback(dbInbound) {
                 newDbInbound = new DBInbound(dbInbound);
                 if (dbInbound.listen.startsWith("@")){
-                    rootInbound = this.inbounds.find((i) => 
-                        i.isTcp && 
+                    rootInbound = this.inbounds.find((i) =>
+                        i.isTcp &&
                         ['trojan','vless'].includes(i.protocol) &&
                         i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
                     );
@@ -1018,10 +1053,10 @@
                 }
                 return newDbInbound;
             },
-            showQrcode(dbInboundId, client) {
+            showQrcode(dbInboundId, client, isJustSub = false) {
                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
                 newDbInbound = this.checkFallback(dbInbound);
-                qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
+                qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub);
             },
             showInfo(dbInboundId, client) {
                 dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -1050,8 +1085,8 @@
                 await this.updateClient(clients[index], dbInboundId, clientId);
                 this.loading(false);
             },
-            async submit(url, data) {
-                const msg = await HttpUtil.postWithModal(url, data);
+            async submit(url, data, isJson = false) {
+                const msg = isJson ? await HttpUtil.postWithModalJson(url, data) : await HttpUtil.postWithModal(url, data);
                 if (msg.success) {
                     await this.getDBInbounds();
                 }

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

@@ -184,6 +184,7 @@
 
 [pages.client]
 "add" = "Add Client"
+"groupAdd" = "Add subscription user"
 "edit" = "Edit Client"
 "submitAdd" = "Add Client"
 "submitEdit" = "Save Changes"

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

@@ -184,6 +184,7 @@
 
 [pages.client]
 "add" = "Agregar Cliente"
+"groupAdd" = "Agregar usuario de suscripción"
 "edit" = "Editar Cliente"
 "submitAdd" = "Agregar Cliente"
 "submitEdit" = "Guardar Cambios"

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

@@ -184,6 +184,7 @@
 
 [pages.client]
 "add" = "کاربر جدید"
+"groupAdd" = "کاربر جدید سابسکریپشن"
 "edit" = "ویرایش کاربر"
 "submitAdd" = "اضافه کردن"
 "submitEdit" = "ذخیره تغییرات"

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

@@ -184,6 +184,7 @@
 
 [pages.client]
 "add" = "Добавить пользователя"
+"groupAdd" = "Добавить пользователя подписки"
 "edit" = "Редактировать пользователя"
 "submitAdd" = "Добавить пользователя"
 "submitEdit" = "Сохранить изменения"

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

@@ -184,6 +184,7 @@
 
 [pages.client]
 "add" = "Thêm người dùng"
+"groupAdd" = "Thêm người dùng đăng ký"
 "edit" = "Chỉnh sửa người dùng"
 "submitAdd" = "Thêm"
 "submitEdit" = "Lưu thay đổi"

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

@@ -184,6 +184,7 @@
 
 [pages.client]
 "add" = "添加客户端"
+"groupAdd" = "添加订阅用户"
 "edit" = "编辑客户端"
 "submitAdd" = "添加客户端"
 "submitEdit" = "保存修改"