mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-03-01 01:20:49 +03:00
Group editing feature of users with the same subscription (#1661)
This commit is contained in:
parent
5c695ca652
commit
b172d450e3
@ -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 {
|
||||||
|
@ -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,64 +33,95 @@
|
|||||||
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);
|
const currentClient = clientModal.group.currentClient;
|
||||||
} else {
|
|
||||||
if (clientModal.group.isGroup) {
|
|
||||||
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(/^(.*?)__/);
|
||||||
client.limitIp = limitIp;
|
const new_email = match ? match[1] : email;
|
||||||
client.totalGB = totalGB;
|
|
||||||
client.expiryTime = expiryTime;
|
|
||||||
client.reset = reset;
|
|
||||||
client.enable = enable;
|
|
||||||
|
|
||||||
if (subId) {
|
client.email = `${new_email}__${index + 1}`;
|
||||||
client.subId = subId;
|
client.limitIp = limitIp;
|
||||||
}
|
client.totalGB = totalGB;
|
||||||
if (tgId) {
|
client.expiryTime = expiryTime;
|
||||||
client.tgId = tgId;
|
client.reset = reset;
|
||||||
}
|
client.enable = enable;
|
||||||
if (flow) {
|
|
||||||
client.flow = flow;
|
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);
|
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);
|
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)) {
|
||||||
this.group.isGroup = true;
|
if (isEdit) {
|
||||||
dbInbound.forEach((dbInboundItem) => {
|
this.showProcess(dbInbound, index);
|
||||||
this.showProcess(dbInboundItem);
|
let processSingleEdit = true
|
||||||
this.group.inbounds.push(dbInboundItem.id)
|
if (this.group.canGroup){
|
||||||
this.group.clients.push(this.clients[this.index])
|
this.group.currentClient = this.clients[this.index]
|
||||||
})
|
const response = this.getGroupInboundsClients(dbInbounds,this.group.currentClient)
|
||||||
this.group.currentClient = this.clients[this.index]
|
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 {
|
} 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;
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
|
@ -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) {
|
||||||
const data = {
|
if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){
|
||||||
id: dbInboundId,
|
const data = []
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
client.forEach((client, index) => {
|
||||||
};
|
data.push({
|
||||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
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) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -181,6 +181,8 @@
|
|||||||
"exportInbound" = "استخراج ورودی"
|
"exportInbound" = "استخراج ورودی"
|
||||||
"import" = "افزودن"
|
"import" = "افزودن"
|
||||||
"importInbound" = "افزودن یک ورودی"
|
"importInbound" = "افزودن یک ورودی"
|
||||||
|
"isGroupEdit" = "ویرایش گروهی"
|
||||||
|
"isGroupEditDesc" = "تمامی کاربران با سابسکریپشن یکسان ویرایش میشوند"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "کاربر جدید"
|
"add" = "کاربر جدید"
|
||||||
|
@ -181,6 +181,8 @@
|
|||||||
"exportInbound" = "Экспорт входящих"
|
"exportInbound" = "Экспорт входящих"
|
||||||
"import" = "Импортировать"
|
"import" = "Импортировать"
|
||||||
"importInbound" = "Импортировать входящее сообщение"
|
"importInbound" = "Импортировать входящее сообщение"
|
||||||
|
"isGroupEdit" = "Редактирование группы"
|
||||||
|
"isGroupEditDesc" = "Редактируются все пользователи с одной подпиской"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Добавить пользователя"
|
"add" = "Добавить пользователя"
|
||||||
|
@ -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"
|
||||||
|
@ -181,6 +181,8 @@
|
|||||||
"exportInbound" = "出口 入境"
|
"exportInbound" = "出口 入境"
|
||||||
"import"="导入"
|
"import"="导入"
|
||||||
"importInbound" = "导入入站"
|
"importInbound" = "导入入站"
|
||||||
|
"isGroupEdit" = "分组编辑"
|
||||||
|
"isGroupEditDesc" = "编辑具有相同订阅的所有用户"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "添加客户端"
|
"add" = "添加客户端"
|
||||||
|
Loading…
Reference in New Issue
Block a user