diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js
index 61b322bd..48ff237d 100644
--- a/web/assets/js/util/utils.js
+++ b/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 {
diff --git a/web/controller/inbound.go b/web/controller/inbound.go
index 86da9813..b274be64 100644
--- a/web/controller/inbound.go
+++ b/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) {
diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html
index 3c4fd929..31b3450c 100644
--- a/web/html/common/qrcode_modal.html
+++ b/web/html/common/qrcode_modal.html
@@ -11,10 +11,12 @@
Subscription
- {{ i18n "pages.inbounds.client" }}
-
- [[ row.remark ]]
-
+ {{ i18n "pages.inbounds.client" }}
+
+
+ [[ row.remark ]]
+
+
@@ -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) {
diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html
index 4b270607..02c548e3 100644
--- a/web/html/xui/client_modal.html
+++ b/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;
},
diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html
index 204aca72..f4ac25f3 100644
--- a/web/html/xui/form/client.html
+++ b/web/html/xui/form/client.html
@@ -15,7 +15,7 @@
-
+
@@ -28,7 +28,7 @@
-
+
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index ab42e09c..7f9ed740 100644
--- a/web/html/xui/inbound_modal.html
+++ b/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];
},
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index c986e3fd..53693ab3 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -145,6 +145,10 @@
{{ i18n "pages.inbounds.delDepletedClients" }}
+
+
+ {{ i18n "pages.client.groupAdd"}}
+
@@ -285,7 +289,7 @@
[[ clientEmail ]]
[[ clientCount[dbInbound.id].online.length ]]
-
+
@@ -339,7 +343,7 @@
[[ dbInbound.toInbound().stream.network ]]
tls
reality
-
+
@@ -373,7 +377,7 @@
[[ clientEmail ]]
[[ clientCount[dbInbound.id].online.length ]]
-
+
@@ -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();
}
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index 006378ab..b2d74c3b 100644
--- a/web/translation/translate.en_US.toml
+++ b/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"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index bbf50b6c..83c9460b 100644
--- a/web/translation/translate.es_ES.toml
+++ b/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"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 1de8538c..9613c12e 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -184,6 +184,7 @@
[pages.client]
"add" = "کاربر جدید"
+"groupAdd" = "کاربر جدید سابسکریپشن"
"edit" = "ویرایش کاربر"
"submitAdd" = "اضافه کردن"
"submitEdit" = "ذخیره تغییرات"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index 2f33311e..7cf2be0a 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -184,6 +184,7 @@
[pages.client]
"add" = "Добавить пользователя"
+"groupAdd" = "Добавить пользователя подписки"
"edit" = "Редактировать пользователя"
"submitAdd" = "Добавить пользователя"
"submitEdit" = "Сохранить изменения"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index 1a2cfaa0..9cd7bc46 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/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"
diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml
index 966ef3f7..c229dc29 100644
--- a/web/translation/translate.zh_Hans.toml
+++ b/web/translation/translate.zh_Hans.toml
@@ -184,6 +184,7 @@
[pages.client]
"add" = "添加客户端"
+"groupAdd" = "添加订阅用户"
"edit" = "编辑客户端"
"submitAdd" = "添加客户端"
"submitEdit" = "保存修改"