mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-20 09:35:48 +00:00
feat: Add WebSocket support for real-time updates and enhance VLESS settings (#3605)
* feat: add support for trusted X-Forwarded-For and testseed parameters in VLESS settings * chore: update Xray Core version to 25.12.8 in release workflow * chore: update Xray Core version to 25.12.8 in Docker initialization script * chore: bump version to 2.8.6 and add watcher for security changes in inbound modal * refactor: remove default and random seed buttons from outbound form * refactor: update VLESS form to rename 'Test Seed' to 'Vision Seed' and change button functionality for seed generation * refactor: enhance TLS settings form layout with improved button styling and spacing * feat: integrate WebSocket support for real-time updates on inbounds and Xray service status * chore: downgrade version to 2.8.5 * refactor: translate comments to English * fix: ensure testseed is initialized correctly for VLESS protocol and improve client handling in inbound modal * refactor: simplify VLESS divider condition by removing unnecessary flow checks * fix: add fallback date formatting for cases when IntlUtil is not available * refactor: simplify WebSocket message handling by removing batching and ensuring individual message delivery * refactor: disable WebSocket notifications in inbound and index HTML files * refactor: enhance VLESS testseed initialization and button functionality in inbound modal * fix: * refactor: ensure proper WebSocket URL construction by normalizing basePath * fix: * fix: * fix: * refactor: update testseed methods for improved reactivity and binding in VLESS form * logger info to debug --------- Co-authored-by: lolka1333 <test123@gmail.com>
This commit is contained in:
@@ -1128,8 +1128,11 @@
|
||||
},
|
||||
openEditClient(dbInboundId, client) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
if (!dbInbound) return;
|
||||
clients = this.getInboundClients(dbInbound);
|
||||
if (!clients || !Array.isArray(clients)) return;
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
if (index < 0) return;
|
||||
clientModal.show({
|
||||
title: '{{ i18n "pages.client.edit"}}',
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
@@ -1144,11 +1147,14 @@
|
||||
});
|
||||
},
|
||||
findIndexOfClient(protocol, clients, client) {
|
||||
if (!clients || !Array.isArray(clients) || !client) {
|
||||
return -1;
|
||||
}
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN:
|
||||
case Protocols.SHADOWSOCKS:
|
||||
return clients.findIndex(item => item.password === client.password && item.email === client.email);
|
||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||
return clients.findIndex(item => item && item.password === client.password && item.email === client.email);
|
||||
default: return clients.findIndex(item => item && item.id === client.id && item.email === client.email);
|
||||
}
|
||||
},
|
||||
async addClient(clients, dbInboundId, modal) {
|
||||
@@ -1271,11 +1277,15 @@
|
||||
},
|
||||
showInfo(dbInboundId, client) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
if (!dbInbound) return;
|
||||
index = 0;
|
||||
if (dbInbound.isMultiUser()) {
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = inbound.clients;
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
clients = inbound && inbound.clients ? inbound.clients : null;
|
||||
if (clients && Array.isArray(clients)) {
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
if (index < 0) index = 0;
|
||||
}
|
||||
}
|
||||
newDbInbound = this.checkFallback(dbInbound);
|
||||
infoModal.show(newDbInbound, index);
|
||||
@@ -1288,9 +1298,12 @@
|
||||
async switchEnableClient(dbInboundId, client) {
|
||||
this.loading()
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
if (!dbInbound) return;
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = inbound.clients;
|
||||
clients = inbound && inbound.clients ? inbound.clients : null;
|
||||
if (!clients || !Array.isArray(clients)) return;
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
if (index < 0 || !clients[index]) return;
|
||||
clients[index].enable = !clients[index].enable;
|
||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||
@@ -1303,7 +1316,9 @@
|
||||
}
|
||||
},
|
||||
getInboundClients(dbInbound) {
|
||||
return dbInbound.toInbound().clients;
|
||||
if (!dbInbound) return null;
|
||||
const inbound = dbInbound.toInbound();
|
||||
return inbound && inbound.clients ? inbound.clients : null;
|
||||
},
|
||||
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||
if (confirmation) {
|
||||
@@ -1443,7 +1458,12 @@
|
||||
formatLastOnline(email) {
|
||||
const ts = this.getLastOnline(email)
|
||||
if (!ts) return '-'
|
||||
return IntlUtil.formatDate(ts)
|
||||
// Check if IntlUtil is available (may not be loaded yet)
|
||||
if (typeof IntlUtil !== 'undefined' && IntlUtil.formatDate) {
|
||||
return IntlUtil.formatDate(ts)
|
||||
}
|
||||
// Fallback to simple date formatting if IntlUtil is not available
|
||||
return new Date(ts).toLocaleString()
|
||||
},
|
||||
isRemovable(dbInboundId) {
|
||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1;
|
||||
@@ -1567,13 +1587,88 @@
|
||||
}
|
||||
this.loading();
|
||||
this.getDefaultSettings();
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
|
||||
// Initial data fetch
|
||||
this.getDBInbounds().then(() => {
|
||||
this.loading(false);
|
||||
});
|
||||
|
||||
// Setup WebSocket for real-time updates
|
||||
if (window.wsClient) {
|
||||
window.wsClient.connect();
|
||||
|
||||
// Listen for inbounds updates
|
||||
window.wsClient.on('inbounds', (payload) => {
|
||||
if (payload && Array.isArray(payload)) {
|
||||
// Use setInbounds to properly convert to DBInbound objects with methods
|
||||
this.setInbounds(payload);
|
||||
this.searchInbounds(this.searchKey);
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for traffic updates
|
||||
window.wsClient.on('traffic', (payload) => {
|
||||
if (payload && payload.clientTraffics && Array.isArray(payload.clientTraffics)) {
|
||||
// Update client traffic statistics
|
||||
payload.clientTraffics.forEach(clientTraffic => {
|
||||
const dbInbound = this.dbInbounds.find(ib => {
|
||||
if (!ib) return false;
|
||||
const clients = this.getInboundClients(ib);
|
||||
return clients && Array.isArray(clients) && clients.some(c => c && c.email === clientTraffic.email);
|
||||
});
|
||||
if (dbInbound && dbInbound.clientStats && Array.isArray(dbInbound.clientStats)) {
|
||||
const stats = dbInbound.clientStats.find(s => s && s.email === clientTraffic.email);
|
||||
if (stats) {
|
||||
stats.up = clientTraffic.up || stats.up;
|
||||
stats.down = clientTraffic.down || stats.down;
|
||||
stats.total = clientTraffic.total || stats.total;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update online clients list in real-time
|
||||
if (payload && Array.isArray(payload.onlineClients)) {
|
||||
this.onlineClients = payload.onlineClients;
|
||||
// Recalculate client counts to update online status
|
||||
this.dbInbounds.forEach(dbInbound => {
|
||||
const inbound = this.inbounds.find(ib => ib.id === dbInbound.id);
|
||||
if (inbound && this.clientCount[dbInbound.id]) {
|
||||
this.clientCount[dbInbound.id] = this.getClientCounts(dbInbound, inbound);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update last online map in real-time
|
||||
if (payload && payload.lastOnlineMap && typeof payload.lastOnlineMap === 'object') {
|
||||
this.lastOnlineMap = { ...this.lastOnlineMap, ...payload.lastOnlineMap };
|
||||
}
|
||||
});
|
||||
|
||||
// Notifications disabled - white notifications are not needed
|
||||
|
||||
// Fallback to polling if WebSocket fails
|
||||
window.wsClient.on('error', () => {
|
||||
console.warn('WebSocket connection failed, falling back to polling');
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
});
|
||||
|
||||
window.wsClient.on('disconnected', () => {
|
||||
if (window.wsClient.reconnectAttempts >= window.wsClient.maxReconnectAttempts) {
|
||||
console.warn('WebSocket reconnection failed, falling back to polling');
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback to polling if WebSocket is not available
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.getDBInbounds();
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
computed: {
|
||||
total() {
|
||||
|
||||
Reference in New Issue
Block a user