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

This commit is contained in:
Ali Rahimi 2024-01-21 15:26:19 +01:00 committed by GitHub
parent e7ce8c8ddb
commit 5c695ca652
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 196 additions and 48 deletions

View file

@ -83,6 +83,41 @@ class HttpUtil {
} }
return msg; 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 { class PromiseUtil {

View file

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

View file

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

View file

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

View file

@ -15,7 +15,7 @@
</template> </template>
<a-input v-model.trim="client.email"></a-input> <a-input v-model.trim="client.email"></a-input>
</a-form-item> </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"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@ -28,7 +28,7 @@
</template> </template>
<a-input v-model.trim="client.password"></a-input> <a-input v-model.trim="client.password"></a-input>
</a-form-item> </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"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -184,6 +184,7 @@
[pages.client] [pages.client]
"add" = "Thêm người dùng" "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" "edit" = "Chỉnh sửa người dùng"
"submitAdd" = "Thêm" "submitAdd" = "Thêm"
"submitEdit" = "Lưu thay đổi" "submitEdit" = "Lưu thay đổi"

View file

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