Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/web/html
diff options
context:
space:
mode:
Diffstat (limited to 'web/html')
-rw-r--r--web/html/common/qrcode_modal.html24
-rw-r--r--web/html/xui/client_modal.html203
-rw-r--r--web/html/xui/form/client.html14
-rw-r--r--web/html/xui/inbound_client_table.html2
-rw-r--r--web/html/xui/inbound_modal.html4
-rw-r--r--web/html/xui/inbounds.html197
-rw-r--r--web/html/xui/settings.html1
7 files changed, 375 insertions, 70 deletions
diff --git a/web/html/common/qrcode_modal.html b/web/html/common/qrcode_modal.html
index 94e750c7..6558c347 100644
--- a/web/html/common/qrcode_modal.html
+++ b/web/html/common/qrcode_modal.html
@@ -23,13 +23,15 @@
</tr-qr-bg>
</tr-qr-box>
</template>
- <template v-for="(row, index) in qrModal.qrcodes">
- <tr-qr-box class="qr-box">
- <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
- <tr-qr-bg class="qr-bg">
- <canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
- </tr-qr-bg>
- </tr-qr-box>
+ <template v-if="!isJustSub">
+ <template v-for="(row, index) in qrModal.qrcodes">
+ <tr-qr-box class="qr-box">
+ <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
+ <tr-qr-bg class="qr-bg">
+ <canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
+ </tr-qr-bg>
+ </tr-qr-box>
+ </template>
</template>
</tr-qr-modal>
</a-modal>
@@ -43,12 +45,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 = [];
if (this.inbound.protocol == Protocols.WIREGUARD) {
@@ -76,7 +80,9 @@
delimiters: ['[[', ']]'],
el: '#qrcode-modal',
data: {
- qrModal: qrModal,
+ qrModal: qrModal,get isJustSub(){
+ return qrModal.isJustSub
+ }
},
methods: {
copyToClipboard(elementId, content) {
diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html
index aa62e02a..de7915a5 100644
--- a/web/html/xui/client_modal.html
+++ b/web/html/xui/client_modal.html
@@ -16,7 +16,16 @@
title: '',
okText: '',
isEdit: false,
+ group: {
+ canGroup: true,
+ isGroup: false,
+ currentClient: null,
+ inbounds: [],
+ clients: [],
+ editIds: []
+ },
dbInbound: new DBInbound(),
+ dbInbounds: null,
inbound: new Inbound(),
clients: [],
clientStats: [],
@@ -25,33 +34,121 @@
clientIps: null,
delayedStart: false,
ok() {
- if (clientModal.isEdit) {
- ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
+ if (app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup) {
+ const currentClient = clientModal.group.currentClient;
+ const { limitIp, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient;
+ const uniqueEmails = clientModalApp.makeGroupEmailsUnique(clientModal.dbInbounds, currentClient.email, clientModal.group.clients);
+
+ clientModal.group.clients.forEach((client, index) => {
+ client.email = uniqueEmails[index];
+ 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 {
- ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
+ 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 (app.subSettings.enable && 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 = app.getSubGroupClients(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) {
+ this.singleEditClientProcess(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);
+ },
+ singleEditClientProcess(index) {
+ if (this.clients[index].expiryTime < 0) {
+ this.delayedStart = true;
}
- this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
- this.confirm = confirm;
- },
+ this.oldClientId = this.getClientId(this.dbInbound.protocol, this.clients[index]);
+ },
getClientId(protocol, client) {
switch (protocol) {
case Protocols.TROJAN: return client.password;
@@ -72,7 +169,7 @@
clientModal.visible = false;
clientModal.loading(false);
},
- loading(loading=true) {
+ loading(loading = true) {
clientModal.confirmLoading = loading;
},
};
@@ -94,6 +191,18 @@
get isEdit() {
return this.clientModal.isEdit;
},
+ 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;
},
@@ -120,22 +229,35 @@
},
},
methods: {
- async getDBClientIps(email) {
- const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
- if (!msg.success) {
- document.getElementById("clientIPs").value = msg.obj;
- return;
- }
- let ips = msg.obj;
- if (typeof ips === 'string' && ips.startsWith('[') && ips.endsWith(']')) {
- try {
- ips = JSON.parse(ips);
- ips = Array.isArray(ips) ? ips.join("\n") : ips;
- } catch (e) {
- console.error('Error parsing JSON:', e);
- }
+ makeGroupEmailsUnique(dbInbounds, baseEmail, groupClients) {
+ // Extract the base part of the email (before the "__" if present)
+ const match = baseEmail.match(/^(.*?)__/);
+ const base = match ? match[1] : baseEmail;
+
+ // Generate initial emails for each client in the group
+ const generatedEmails = groupClients.map((_, index) => `${base}__${index + 1}`);
+
+ // Function to check if an email already exists in dbInbounds but belongs to a different subId
+ const isDuplicate = (emailToCheck, clientSubId) => {
+ return dbInbounds.some((dbInbound) => {
+ const settings = JSON.parse(dbInbound.settings);
+ const clients = settings && settings.clients ? settings.clients : [];
+ return clients.some(client => client.email === emailToCheck && client.subId !== clientSubId);
+ });
+ };
+
+ // Check if any of the generated emails are duplicates
+ const hasDuplicates = generatedEmails.some((email, index) => {
+ return isDuplicate(email, groupClients[index].subId);
+ });
+
+ // If duplicates exist, add a random string to the base email to ensure uniqueness
+ if (hasDuplicates) {
+ const randomString = `-${RandomUtil.randomLowerAndNum(4)}`;
+ return groupClients.map((_, index) => `${base}${randomString}__${index + 1}`);
}
- document.getElementById("clientIPs").value = ips;
+
+ return generatedEmails;
},
async clearDBClientIps(email) {
try {
@@ -147,7 +269,22 @@
} catch (error) {
}
},
- resetClientTraffic(email, dbInboundId, iconElement) {
+ async resetClientTrafficHandler(client, dbInboundId, clients = []) {
+ if (clients.length > 0) {
+ const resetRequests = clients
+ .filter(client => {
+ const inbound = clientModal.dbInbounds.find(inbound => inbound.id === client.inboundId);
+ return inbound && app.hasClientStats(inbound, client.email);
+ }).map(client => ({ inboundId: client.inboundId, email: client.email}));
+
+ return HttpUtil.postWithModalJson('/panel/inbound/resetGroupClientTraffic', resetRequests, null)
+ } else {
+ return HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email)
+ }
+ },
+ resetClientTraffic(client, dbInboundId, iconElement) {
+ const subGroup = app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup && clientModal.dbInbounds && clientModal.dbInbounds.length > 0 ? app.getSubGroupClients(clientModal.dbInbounds, client) : [];
+ const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
this.$confirm({
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
@@ -156,8 +293,8 @@
cancelText: '{{ i18n "cancel"}}',
onOk: async () => {
iconElement.disabled = true;
- const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
- if (msg.success) {
+ const msg = await this.resetClientTrafficHandler(client, dbInboundId, clients);
+ if (msg && msg.success) {
this.clientModal.clientStats.up = 0;
this.clientModal.clientStats.down = 0;
}
diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html
index 0b894f01..a0a1ced8 100644
--- a/web/html/xui/form/client.html
+++ b/web/html/xui/form/client.html
@@ -3,6 +3,18 @@
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
<a-switch v-model="client.enable"></a-switch>
</a-form-item>
+ <a-form-item v-if="isEdit && app.subSettings.enable && isGroup">
+ <template slot="label">
+ <a-tooltip>
+ <template slot="title">
+ <span>{{ i18n "pages.client.isGroupEditDesc" }}</span>
+ </template>
+ {{ i18n "pages.client.isGroupEdit" }}
+ <a-icon type="question-circle"></a-icon>
+ </a-tooltip>
+ </template>
+ <a-switch v-model="isGroupEdit"></a-switch>
+ </a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
@@ -134,7 +146,7 @@
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-icon type="retweet"
- @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
+ @click="resetClientTraffic(client,clientStats.inboundId,$event.target)"
v-if="client.email.length > 0"></a-icon>
</a-tooltip>
</a-form-item>
diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html
index 13593cea..74d77eab 100644
--- a/web/html/xui/inbound_client_table.html
+++ b/web/html/xui/inbound_client_table.html
@@ -12,7 +12,7 @@
<template slot="title">{{ i18n "info" }}</template>
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
</a-tooltip>
- <a-tooltip>
+ <a-tooltip v-if="hasClientStats(record, client.email)">
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon>
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index 4de3518c..66e4b7b7 100644
--- a/web/html/xui/inbound_modal.html
+++ b/web/html/xui/inbound_modal.html
@@ -14,6 +14,7 @@
confirmLoading: false,
okText: '{{ i18n "sure" }}',
isEdit: false,
+ isGroup: false,
confirm: null,
inbound: new Inbound(),
dbInbound: new DBInbound(),
@@ -61,6 +62,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 478f29de..5d4d918b 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -224,6 +224,10 @@
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</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-dropdown>
</a-col>
@@ -859,6 +863,9 @@
case "delDepletedClients":
this.delDepletedClients(-1)
break;
+ case "addGroupClient":
+ this.openGroupAddClient()
+ break;
}
},
clickAction(action, dbInbound) {
@@ -1004,6 +1011,21 @@
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
},
+ openGroupAddClient() {
+ clientModal.show({
+ title: '{{ i18n "pages.client.groupAdd"}}',
+ okText: '{{ i18n "pages.client.submitAdd"}}',
+ dbInbounds: this.dbInbounds,
+ confirm: async (clients, dbInboundIds) => {
+ await this.addGroupClient(clients, dbInboundIds, clientModal).then((res) => {
+ if(res){
+ this.showQrcode(dbInboundIds[0],clients[0], true)
+ }
+ });
+ },
+ isEdit: false
+ });
+ },
openAddClient(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
clientModal.show({
@@ -1011,7 +1033,11 @@
okText: '{{ i18n "pages.client.submitAdd"}}',
dbInbound: dbInbound,
confirm: async (clients, dbInboundId) => {
- await this.addClient(clients, dbInboundId, clientModal);
+ await this.addClient(clients, dbInboundId, clientModal).then((res) => {
+ if(res){
+ this.showQrcode(dbInboundId,clients)
+ }
+ });
},
isEdit: false
});
@@ -1034,6 +1060,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) => {
@@ -1059,12 +1086,36 @@
};
await this.submit(`/panel/inbound/addClient`, data, modal);
},
+ async addGroupClient(clients, dbInboundIds, modal) {
+ const data = []
+ dbInboundIds.forEach((dbInboundId, index) => {
+ data.push({
+ id: dbInboundId,
+ settings: '{"clients": [' + clients[index].toString() + ']}',
+ })
+ })
+ return await this.submit(`/panel/inbound/addGroupClient`, data, modal, true)
+ },
async updateClient(client, dbInboundId, clientId) {
- const data = {
- id: dbInboundId,
- settings: '{"clients": [' + client.toString() + ']}',
- };
- await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
+ 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, clientModal, true);
+ }else{
+ const data = {
+ id: dbInboundId,
+ settings: '{"clients": [' + client.toString() + ']}',
+ };
+ await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
+ }
},
resetTraffic(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -1092,22 +1143,87 @@
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
});
},
- delClient(dbInboundId, client,confirmation = true) {
- dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
- clientId = this.getClientId(dbInbound.protocol, client);
+ async delClientHandler(dbInboundId, currentClient, clients = []) {
+ if (clients.length > 0) {
+ const deleteRequestData = [];
+
+ for (const client of clients) {
+ const dbInbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId);
+ if (dbInbound) {
+ const inbound = dbInbound.toInbound();
+ if (inbound && inbound.clients && inbound.clients.length === 1) {
+ let newClient = Inbound.Settings.getSettings(inbound.protocol).toString();
+ newClient = JSON.parse(newClient);
+ newClient = newClient && newClient.clients && newClient.clients.length > 0 ? JSON.stringify(newClient.clients[0], null, 2) : null;
+ if (newClient) {
+ const data = {
+ id: client.inboundId,
+ settings: '{"clients": [' + newClient + ']}',
+ };
+ await this.submit(`/panel/inbound/addClient`, data, null);
+ }
+ }
+
+ deleteRequestData.push({
+ inboundId: client.inboundId,
+ clientId: client.clientId,
+ });
+ }
+ }
+ await this.submit('/panel/inbound/delGroupClients', deleteRequestData, null, true);
+ } else {
+ const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
+ const clientId = this.getClientId(dbInbound.protocol, currentClient);
+ await this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
+ }
+ },
+ delClient(dbInboundId, currentClient, confirmation = true) {
+ const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, currentClient) : [];
+ const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
if (confirmation){
+ const clientEmails = clients.length > 0 ? clients.map(item => item.email) : currentClient.email
this.$confirm({
- title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
+ title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + clientEmails,
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}',
cancelText: '{{ i18n "cancel"}}',
- onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
+ onOk: () => this.delClientHandler(dbInboundId, currentClient, clients),
});
} else {
- this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
+ this.delClientHandler(dbInboundId, currentClient, clients)
}
},
+ getSubGroupClients(dbInbounds, currentClient) {
+ const response = {
+ inbounds: [],
+ clients: [],
+ editIds: []
+ }
+ if (dbInbounds && dbInbounds.length > 0 && currentClient) {
+ dbInbounds.forEach((dbInboundItem) => {
+ const dbInbound = new DBInbound(dbInboundItem);
+ if (dbInbound) {
+ const inbound = dbInbound.toInbound();
+ if (inbound) {
+ const clients = inbound.clients;
+ if (clients.length > 0) {
+ clients.forEach((client) => {
+ if (client['subId'] === currentClient['subId']) {
+ client['inboundId'] = dbInboundItem.id
+ client['clientId'] = this.getClientId(dbInbound.protocol, client)
+ response.inbounds.push(dbInboundItem.id)
+ response.clients.push(client)
+ response.editIds.push(client['clientId'])
+ }
+ })
+ }
+ }
+ }
+ })
+ }
+ return response;
+ },
getClientId(protocol, client) {
switch (protocol) {
case Protocols.TROJAN: return client.password;
@@ -1135,10 +1251,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);
@@ -1158,36 +1274,61 @@
},
async switchEnableClient(dbInboundId, client) {
this.loading()
- dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
- inbound = dbInbound.toInbound();
- clients = inbound.clients;
- index = this.findIndexOfClient(dbInbound.protocol, clients, client);
- clients[index].enable = !clients[index].enable;
- clientId = this.getClientId(dbInbound.protocol, clients[index]);
- await this.updateClient(clients[index], dbInboundId, clientId);
+ const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : [];
+ if (subGroup && subGroup.clients && subGroup.clients.length > 0){
+ await this.updateClient(subGroup.clients.map(item => {
+ item.enable = !item.enable
+ return item
+ }), subGroup.inbounds, subGroup.editIds);
+ }else{
+ dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
+ inbound = dbInbound.toInbound();
+ clients = inbound.clients;
+ index = this.findIndexOfClient(dbInbound.protocol, clients, client);
+ clients[index].enable = !clients[index].enable;
+ clientId = this.getClientId(dbInbound.protocol, clients[index]);
+ await this.updateClient(clients[index], dbInboundId, clientId);
+ }
this.loading(false);
},
- async submit(url, data, modal) {
- const msg = await HttpUtil.postWithModal(url, data, modal);
+ async submit(url, data, model, isJson = false) {
+ const msg = isJson ? await HttpUtil.postWithModalJson(url, data, model) : await HttpUtil.postWithModal(url, data, model);
if (msg.success) {
await this.getDBInbounds();
}
+ return msg
},
getInboundClients(dbInbound) {
return dbInbound.toInbound().clients;
},
+ resetClientTrafficHandler(client, dbInboundId, clients = []) {
+ if (clients.length > 0){
+ const resetRequests = clients
+ .filter(client => {
+ const inbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId);
+ return inbound && this.hasClientStats(inbound, client.email);
+ }).map(client => ({ inboundId: client.inboundId, email: client.email}));
+
+ this.submit('/panel/inbound/resetGroupClientTraffic', resetRequests, null, true)
+ }else {
+ this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
+ }
+ },
resetClientTraffic(client, dbInboundId, confirmation = true) {
+ const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : [];
+ const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
if (confirmation){
+ const clientEmails = clients.length > 0 ? clients.map(item => item.email) : client.email
this.$confirm({
- title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + client.email,
+ title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + clientEmails,
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
class: themeSwitcher.currentTheme,
okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}',
- onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
+ onOk: () => this.resetClientTrafficHandler(client, dbInboundId, clients),
})
} else {
- this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
+ this.resetClientTrafficHandler(client, dbInboundId, clients);
}
},
resetAllTraffic() {
@@ -1223,6 +1364,10 @@
isExpiry(dbInbound, index) {
return dbInbound.toInbound().isExpiry(index);
},
+ hasClientStats(dbInbound, email) {
+ if (email.length == 0) return 0;
+ return !!dbInbound.clientStats.find(stats => stats.email === email);
+ },
getUpStats(dbInbound, email) {
if (email.length == 0) return 0;
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html
index 2c8515e2..fa56baf7 100644
--- a/web/html/xui/settings.html
+++ b/web/html/xui/settings.html
@@ -268,6 +268,7 @@
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
<a-list item-layout="horizontal">
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
+ <setting-list-item type="switch" title='{{ i18n "pages.settings.subSyncEnable"}}' desc='{{ i18n "pages.settings.subSyncEnableDesc"}}' v-model="allSetting.subSyncEnable"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>