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

This commit is contained in:
Ali Rahimi 2024-01-22 12:36:01 +01:00 committed by MHSanaei
parent 5c695ca652
commit b172d450e3
10 changed files with 237 additions and 47 deletions

View File

@ -1,6 +1,7 @@
package controller package controller
import ( import (
"errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
@ -32,7 +33,9 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/clientIps/:email", a.getClientIps) g.POST("/clientIps/:email", a.getClientIps)
g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/clearClientIps/:email", a.clearClientIps)
g.POST("/addClient", a.addInboundClient) g.POST("/addClient", a.addInboundClient)
g.POST("/addGroupClient", a.addGroupInboundClient)
g.POST("/:id/delClient/:clientId", a.delInboundClient) g.POST("/:id/delClient/:clientId", a.delInboundClient)
g.POST("/updateClients", a.updateGroupInboundClient)
g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics) 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) { func (a *InboundController) delInboundClient(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { 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) { func (a *InboundController) resetClientTraffic(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { if err != nil {

View File

@ -16,12 +16,15 @@
title: '', title: '',
okText: '', okText: '',
group: { group: {
canGroup: true,
isGroup: false, isGroup: false,
currentClient: null, currentClient: null,
inbounds: [], inbounds: [],
clients: [], clients: [],
editIds: []
}, },
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
dbInbounds: null,
inbound: new Inbound(), inbound: new Inbound(),
clients: [], clients: [],
clientStats: [], clientStats: [],
@ -30,16 +33,16 @@
clientIps: null, clientIps: null,
delayedStart: false, delayedStart: false,
ok() { ok() {
if (clientModal.isEdit) { if (clientModal.group.isGroup && clientModal.group.canGroup) {
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
} else {
if (clientModal.group.isGroup) {
const currentClient = clientModal.group.currentClient; const currentClient = clientModal.group.currentClient;
clientModal.group.clients.forEach((client, index) => { clientModal.group.clients.forEach((client, index) => {
const { email, limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient; const { email, limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient;
client.email = `${email}-${index + 1}`; const match = email.match(/^(.*?)__/);
const new_email = match ? match[1] : email;
client.email = `${new_email}__${index + 1}`;
client.limitIp = limitIp; client.limitIp = limitIp;
client.totalGB = totalGB; client.totalGB = totalGB;
client.expiryTime = expiryTime; client.expiryTime = expiryTime;
@ -56,38 +59,69 @@
client.flow = 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); ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds);
}
} else {
if (clientModal.isEdit){
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
}else{ }else{
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id); 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 = { this.group = {
canGroup: true,
isGroup: false, isGroup: false,
currentClient: null, currentClient: null,
inbounds: [], inbounds: [],
clients: [], clients: [],
editIds: []
} }
this.dbInbounds = dbInbounds;
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)) { 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.isGroup = true;
dbInbound.forEach((dbInboundItem) => { 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.showProcess(dbInboundItem);
this.addClient(this.inbound.protocol, this.clients);
this.group.inbounds.push(dbInboundItem.id) this.group.inbounds.push(dbInboundItem.id)
this.group.clients.push(this.clients[this.index]) this.group.clients.push(this.clients[this.index])
}) })
this.group.currentClient = this.clients[this.index] this.group.currentClient = this.clients[this.index]
}
} else { } else {
this.showProcess(dbInbound, index); this.showProcess(dbInbound, index);
if (isEdit) { if (isEdit) {
if (this.clients[index].expiryTime < 0) { this.singleEditClientProcess(index)
this.delayedStart = true;
}
this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
} else { } else {
this.addClient(this.inbound.protocol, this.clients); this.addClient(this.inbound.protocol, this.clients);
} }
@ -101,7 +135,34 @@
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;
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) { getClientId(protocol, client) {
switch (protocol) { switch (protocol) {
@ -148,6 +209,15 @@
get isGroup() { get isGroup() {
return this.clientModal.group.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() { get datepicker() {
return app.datepicker; return app.datepicker;
}, },

View File

@ -3,6 +3,18 @@
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'> <a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
<a-switch v-model="client.enable"></a-switch> <a-switch v-model="client.enable"></a-switch>
</a-form-item> </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> <a-form-item>
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -181,6 +181,8 @@
"exportInbound" = "Xuất nhập khẩu" "exportInbound" = "Xuất nhập khẩu"
"import" = "Nhập" "import" = "Nhập"
"importInbound" = "Nhập inbound" "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] [pages.client]
"add" = "Thêm người dùng" "add" = "Thêm người dùng"

View File

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