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 @@
+
+
+
+
+ {{ i18n "pages.inbounds.isGroupEditDesc" }}
+
+ {{ i18n "pages.inbounds.isGroupEdit" }}
+
+
+
+
+
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index 53693ab3..e5d4b821 100644
--- a/web/html/xui/inbounds.html
+++ b/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);
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index b2d74c3b..4fcb50c6 100644
--- a/web/translation/translate.en_US.toml
+++ b/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"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index 83c9460b..a61689f9 100644
--- a/web/translation/translate.es_ES.toml
+++ b/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"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 9613c12e..38c28579 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -181,6 +181,8 @@
"exportInbound" = "استخراج ورودی"
"import" = "افزودن"
"importInbound" = "افزودن یک ورودی"
+"isGroupEdit" = "ویرایش گروهی"
+"isGroupEditDesc" = "تمامی کاربران با سابسکریپشن یکسان ویرایش میشوند"
[pages.client]
"add" = "کاربر جدید"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index 7cf2be0a..4bfda2b9 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -181,6 +181,8 @@
"exportInbound" = "Экспорт входящих"
"import" = "Импортировать"
"importInbound" = "Импортировать входящее сообщение"
+"isGroupEdit" = "Редактирование группы"
+"isGroupEditDesc" = "Редактируются все пользователи с одной подпиской"
[pages.client]
"add" = "Добавить пользователя"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index 9cd7bc46..cd19328d 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/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"
diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml
index c229dc29..a7399642 100644
--- a/web/translation/translate.zh_Hans.toml
+++ b/web/translation/translate.zh_Hans.toml
@@ -181,6 +181,8 @@
"exportInbound" = "出口 入境"
"import"="导入"
"importInbound" = "导入入站"
+"isGroupEdit" = "分组编辑"
+"isGroupEditDesc" = "编辑具有相同订阅的所有用户"
[pages.client]
"add" = "添加客户端"