mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-12-23 06:42:41 +00:00
Compare commits
12 commits
6247e79ac3
...
d3670a8a6c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3670a8a6c | ||
|
|
d2b8593f5b | ||
|
|
1bf2e3cd6b | ||
|
|
7c9204d183 | ||
|
|
ca4d2e1e90 | ||
|
|
cf1b39c714 | ||
|
|
c4e9f3d9f5 | ||
|
|
d2201c8fd4 | ||
|
|
28aa2d0a0c | ||
|
|
1d732606f3 | ||
|
|
527e1b2c75 | ||
|
|
a4909f83f0 |
7 changed files with 139 additions and 92 deletions
|
|
@ -1897,6 +1897,12 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
// Ensure testseed is always initialized as an array
|
||||||
|
let testseed = [900, 500, 900, 256];
|
||||||
|
if (json.testseed && Array.isArray(json.testseed) && json.testseed.length >= 4) {
|
||||||
|
testseed = json.testseed;
|
||||||
|
}
|
||||||
|
|
||||||
const obj = new Inbound.VLESSSettings(
|
const obj = new Inbound.VLESSSettings(
|
||||||
Protocols.VLESS,
|
Protocols.VLESS,
|
||||||
(json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
|
(json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
|
||||||
|
|
@ -1904,7 +1910,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||||
json.encryption,
|
json.encryption,
|
||||||
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []),
|
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []),
|
||||||
json.selectedAuth,
|
json.selectedAuth,
|
||||||
json.testseed && json.testseed.length >= 4 ? json.testseed : [900, 500, 900, 256]
|
testseed
|
||||||
);
|
);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,14 @@ class WebSocketClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const wsUrl = `${protocol}//${window.location.host}${this.basePath}ws`;
|
// Ensure basePath ends with '/' for proper URL construction
|
||||||
|
let basePath = this.basePath || '';
|
||||||
|
if (basePath && !basePath.endsWith('/')) {
|
||||||
|
basePath += '/';
|
||||||
|
}
|
||||||
|
const wsUrl = `${protocol}//${window.location.host}${basePath}ws`;
|
||||||
|
|
||||||
|
console.log('WebSocket connecting to:', wsUrl, 'basePath:', this.basePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.ws = new WebSocket(wsUrl);
|
this.ws = new WebSocket(wsUrl);
|
||||||
|
|
|
||||||
|
|
@ -171,55 +171,12 @@ func (w *WebSocketController) writePump(client *websocket.Client, conn *ws.Conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, err := conn.NextWriter(ws.TextMessage)
|
// Send each message individually (no batching)
|
||||||
if err != nil {
|
// This ensures each JSON message is sent separately and can be parsed correctly
|
||||||
|
if err := conn.WriteMessage(ws.TextMessage, message); err != nil {
|
||||||
logger.Debugf("WebSocket write error for client %s: %v", client.ID, err)
|
logger.Debugf("WebSocket write error for client %s: %v", client.ID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writer.Write(message)
|
|
||||||
|
|
||||||
// Optimization: message batching with smart limit
|
|
||||||
// Process accumulated messages but limit to prevent delays
|
|
||||||
n := len(client.Send)
|
|
||||||
maxQueued := 20 // Increased from 10 to 20 for better throughput
|
|
||||||
if n > maxQueued {
|
|
||||||
// Skip old messages, keep only the latest for relevance
|
|
||||||
skipped := n - maxQueued
|
|
||||||
for i := 0; i < skipped; i++ {
|
|
||||||
select {
|
|
||||||
case <-client.Send:
|
|
||||||
// Skip old message
|
|
||||||
default:
|
|
||||||
// Channel closed or empty, stop skipping
|
|
||||||
goto skipDone
|
|
||||||
}
|
|
||||||
}
|
|
||||||
skipDone:
|
|
||||||
n = len(client.Send) // Update count after skipping
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batching: send multiple messages in one frame
|
|
||||||
// Safe reading with channel close check
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
select {
|
|
||||||
case msg, ok := <-client.Send:
|
|
||||||
if !ok {
|
|
||||||
// Channel closed, exit
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writer.Write([]byte{'\n'})
|
|
||||||
writer.Write(msg)
|
|
||||||
default:
|
|
||||||
// No more messages in queue, stop batching
|
|
||||||
goto batchDone
|
|
||||||
}
|
|
||||||
}
|
|
||||||
batchDone:
|
|
||||||
|
|
||||||
if err := writer.Close(); err != nil {
|
|
||||||
logger.Debugf("WebSocket writer close error for client %s: %v", client.ID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
conn.SetWriteDeadline(time.Now().Add(writeWait))
|
conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider v-if="inbound.settings.selectedAuth && inbound.settings.vlesses.some(c => c.flow === 'xtls-rprx-vision' || c.flow === 'xtls-rprx-vision-udp443')" :style="{ margin: '5px 0' }"></a-divider>
|
<a-divider v-if="inbound.settings.selectedAuth" :style="{ margin: '5px 0' }"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
|
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
|
@ -75,23 +75,23 @@
|
||||||
<a-form-item label="Vision Seed">
|
<a-form-item label="Vision Seed">
|
||||||
<a-row :gutter="8">
|
<a-row :gutter="8">
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number v-model.number="inbound.settings.testseed[0]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[0] !== undefined) ? inbound.settings.testseed[0] : 900" @change="(val) => updateTestseed(0, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number v-model.number="inbound.settings.testseed[1]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[1] !== undefined) ? inbound.settings.testseed[1] : 500" @change="(val) => updateTestseed(1, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number v-model.number="inbound.settings.testseed[2]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[2] !== undefined) ? inbound.settings.testseed[2] : 900" @change="(val) => updateTestseed(2, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<a-input-number v-model.number="inbound.settings.testseed[3]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[3] !== undefined) ? inbound.settings.testseed[3] : 256" @change="(val) => updateTestseed(3, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-space :size="8" :style="{ marginTop: '8px' }">
|
<a-space :size="8" :style="{ marginTop: '8px' }">
|
||||||
<a-button type="primary" @click="inbound.settings.testseed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)]">
|
<a-button type="primary" @click="setRandomTestseed">
|
||||||
Rand
|
Rand
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button @click="inbound.settings.testseed = [900, 500, 900, 256]">
|
<a-button @click="resetTestseed">
|
||||||
Reset
|
Reset
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
|
|
||||||
|
|
@ -1128,8 +1128,11 @@
|
||||||
},
|
},
|
||||||
openEditClient(dbInboundId, client) {
|
openEditClient(dbInboundId, client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
if (!dbInbound) return;
|
||||||
clients = this.getInboundClients(dbInbound);
|
clients = this.getInboundClients(dbInbound);
|
||||||
|
if (!clients || !Array.isArray(clients)) return;
|
||||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
|
if (index < 0) return;
|
||||||
clientModal.show({
|
clientModal.show({
|
||||||
title: '{{ i18n "pages.client.edit"}}',
|
title: '{{ i18n "pages.client.edit"}}',
|
||||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||||
|
|
@ -1144,11 +1147,14 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
findIndexOfClient(protocol, clients, client) {
|
findIndexOfClient(protocol, clients, client) {
|
||||||
|
if (!clients || !Array.isArray(clients) || !client) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
case Protocols.SHADOWSOCKS:
|
||||||
return clients.findIndex(item => item.password === client.password && item.email === client.email);
|
return clients.findIndex(item => item && item.password === client.password && item.email === client.email);
|
||||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
default: return clients.findIndex(item => item && item.id === client.id && item.email === client.email);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId, modal) {
|
async addClient(clients, dbInboundId, modal) {
|
||||||
|
|
@ -1271,11 +1277,15 @@
|
||||||
},
|
},
|
||||||
showInfo(dbInboundId, client) {
|
showInfo(dbInboundId, client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
if (!dbInbound) return;
|
||||||
index = 0;
|
index = 0;
|
||||||
if (dbInbound.isMultiUser()) {
|
if (dbInbound.isMultiUser()) {
|
||||||
inbound = dbInbound.toInbound();
|
inbound = dbInbound.toInbound();
|
||||||
clients = inbound.clients;
|
clients = inbound && inbound.clients ? inbound.clients : null;
|
||||||
|
if (clients && Array.isArray(clients)) {
|
||||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
|
if (index < 0) index = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
infoModal.show(newDbInbound, index);
|
infoModal.show(newDbInbound, index);
|
||||||
|
|
@ -1288,9 +1298,12 @@
|
||||||
async switchEnableClient(dbInboundId, client) {
|
async switchEnableClient(dbInboundId, client) {
|
||||||
this.loading()
|
this.loading()
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
if (!dbInbound) return;
|
||||||
inbound = dbInbound.toInbound();
|
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);
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
|
if (index < 0 || !clients[index]) return;
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
|
|
@ -1303,7 +1316,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getInboundClients(dbInbound) {
|
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) {
|
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||||
if (confirmation) {
|
if (confirmation) {
|
||||||
|
|
@ -1443,7 +1458,12 @@
|
||||||
formatLastOnline(email) {
|
formatLastOnline(email) {
|
||||||
const ts = this.getLastOnline(email)
|
const ts = this.getLastOnline(email)
|
||||||
if (!ts) return '-'
|
if (!ts) return '-'
|
||||||
|
// Check if IntlUtil is available (may not be loaded yet)
|
||||||
|
if (typeof IntlUtil !== 'undefined' && IntlUtil.formatDate) {
|
||||||
return IntlUtil.formatDate(ts)
|
return IntlUtil.formatDate(ts)
|
||||||
|
}
|
||||||
|
// Fallback to simple date formatting if IntlUtil is not available
|
||||||
|
return new Date(ts).toLocaleString()
|
||||||
},
|
},
|
||||||
isRemovable(dbInboundId) {
|
isRemovable(dbInboundId) {
|
||||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1;
|
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1;
|
||||||
|
|
@ -1588,15 +1608,16 @@
|
||||||
|
|
||||||
// Listen for traffic updates
|
// Listen for traffic updates
|
||||||
window.wsClient.on('traffic', (payload) => {
|
window.wsClient.on('traffic', (payload) => {
|
||||||
if (payload && payload.clientTraffics) {
|
if (payload && payload.clientTraffics && Array.isArray(payload.clientTraffics)) {
|
||||||
// Update client traffic statistics
|
// Update client traffic statistics
|
||||||
payload.clientTraffics.forEach(clientTraffic => {
|
payload.clientTraffics.forEach(clientTraffic => {
|
||||||
const dbInbound = this.dbInbounds.find(ib => {
|
const dbInbound = this.dbInbounds.find(ib => {
|
||||||
|
if (!ib) return false;
|
||||||
const clients = this.getInboundClients(ib);
|
const clients = this.getInboundClients(ib);
|
||||||
return clients && clients.some(c => c.email === clientTraffic.email);
|
return clients && Array.isArray(clients) && clients.some(c => c && c.email === clientTraffic.email);
|
||||||
});
|
});
|
||||||
if (dbInbound && dbInbound.clientStats) {
|
if (dbInbound && dbInbound.clientStats && Array.isArray(dbInbound.clientStats)) {
|
||||||
const stats = dbInbound.clientStats.find(s => s.email === clientTraffic.email);
|
const stats = dbInbound.clientStats.find(s => s && s.email === clientTraffic.email);
|
||||||
if (stats) {
|
if (stats) {
|
||||||
stats.up = clientTraffic.up || stats.up;
|
stats.up = clientTraffic.up || stats.up;
|
||||||
stats.down = clientTraffic.down || stats.down;
|
stats.down = clientTraffic.down || stats.down;
|
||||||
|
|
@ -1624,17 +1645,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for notifications
|
// Notifications disabled - white notifications are not needed
|
||||||
window.wsClient.on('notification', (payload) => {
|
|
||||||
if (payload && payload.title) {
|
|
||||||
const type = payload.level || 'info';
|
|
||||||
this.$notification[type]({
|
|
||||||
message: payload.title,
|
|
||||||
description: payload.message || '',
|
|
||||||
duration: 4.5,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fallback to polling if WebSocket fails
|
// Fallback to polling if WebSocket fails
|
||||||
window.wsClient.on('error', () => {
|
window.wsClient.on('error', () => {
|
||||||
|
|
|
||||||
|
|
@ -1161,17 +1161,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for notifications
|
// Notifications disabled - white notifications are not needed
|
||||||
window.wsClient.on('notification', (payload) => {
|
|
||||||
if (payload && payload.title) {
|
|
||||||
const type = payload.level || 'info';
|
|
||||||
this.$notification[type]({
|
|
||||||
message: payload.title,
|
|
||||||
description: payload.message || '',
|
|
||||||
duration: 4.5,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fallback to polling if WebSocket fails
|
// Fallback to polling if WebSocket fails
|
||||||
window.wsClient.on('error', () => {
|
window.wsClient.on('error', () => {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const inModal = {
|
// Make inModal globally available to ensure it works with any base path
|
||||||
|
const inModal = window.inModal = {
|
||||||
title: '',
|
title: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
|
|
@ -26,6 +27,14 @@
|
||||||
} else {
|
} else {
|
||||||
this.inbound = new Inbound();
|
this.inbound = new Inbound();
|
||||||
}
|
}
|
||||||
|
// Always ensure testseed is initialized for VLESS protocol (even if vision flow is not set yet)
|
||||||
|
// This ensures Vue reactivity works properly
|
||||||
|
if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
|
||||||
|
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed) || this.inbound.settings.testseed.length < 4) {
|
||||||
|
// Create a new array to ensure Vue reactivity
|
||||||
|
this.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (dbInbound) {
|
if (dbInbound) {
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -42,9 +51,43 @@
|
||||||
loading(loading = true) {
|
loading(loading = true) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
|
// Vision Seed methods - always available regardless of Vue context
|
||||||
|
updateTestseed(index, value) {
|
||||||
|
// Use inModal.inbound explicitly to ensure correct context
|
||||||
|
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||||
|
// Ensure testseed is initialized
|
||||||
|
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed)) {
|
||||||
|
inModal.inbound.settings.testseed = [900, 500, 900, 256];
|
||||||
|
}
|
||||||
|
// Ensure array has enough elements
|
||||||
|
while (inModal.inbound.settings.testseed.length <= index) {
|
||||||
|
inModal.inbound.settings.testseed.push(0);
|
||||||
|
}
|
||||||
|
// Update value
|
||||||
|
inModal.inbound.settings.testseed[index] = value;
|
||||||
|
},
|
||||||
|
setRandomTestseed() {
|
||||||
|
// Use inModal.inbound explicitly to ensure correct context
|
||||||
|
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||||
|
// Ensure testseed is initialized
|
||||||
|
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed) || inModal.inbound.settings.testseed.length < 4) {
|
||||||
|
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||||
|
}
|
||||||
|
// Create new array with random values
|
||||||
|
inModal.inbound.settings.testseed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
|
||||||
|
},
|
||||||
|
resetTestseed() {
|
||||||
|
// Use inModal.inbound explicitly to ensure correct context
|
||||||
|
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||||
|
// Reset testseed to default values
|
||||||
|
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
new Vue({
|
// Store Vue instance globally to ensure methods are always accessible
|
||||||
|
let inboundModalVueInstance = null;
|
||||||
|
|
||||||
|
inboundModalVueInstance = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#inbound-modal',
|
el: '#inbound-modal',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -60,7 +103,7 @@
|
||||||
return inModal.isEdit;
|
return inModal.isEdit;
|
||||||
},
|
},
|
||||||
get client() {
|
get client() {
|
||||||
return inModal.inbound.clients[0];
|
return inModal.inbound && inModal.inbound.clients && inModal.inbound.clients.length > 0 ? inModal.inbound.clients[0] : null;
|
||||||
},
|
},
|
||||||
get datepicker() {
|
get datepicker() {
|
||||||
return app.datepicker;
|
return app.datepicker;
|
||||||
|
|
@ -95,6 +138,18 @@
|
||||||
client.flow = "";
|
client.flow = "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Ensure testseed is always initialized when vision flow is enabled
|
||||||
|
'inModal.inbound.settings.vlesses': {
|
||||||
|
handler() {
|
||||||
|
if (inModal.inbound.protocol === Protocols.VLESS && inModal.inbound.settings && inModal.inbound.settings.vlesses) {
|
||||||
|
const hasVisionFlow = inModal.inbound.settings.vlesses.some(c => c.flow === 'xtls-rprx-vision' || c.flow === 'xtls-rprx-vision-udp443');
|
||||||
|
if (hasVisionFlow && (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed) || inModal.inbound.settings.testseed.length < 4)) {
|
||||||
|
inModal.inbound.settings.testseed = [900, 500, 900, 256];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -214,8 +269,29 @@
|
||||||
this.inbound.settings.decryption = 'none';
|
this.inbound.settings.decryption = 'none';
|
||||||
this.inbound.settings.encryption = 'none';
|
this.inbound.settings.encryption = 'none';
|
||||||
this.inbound.settings.selectedAuth = undefined;
|
this.inbound.settings.selectedAuth = undefined;
|
||||||
|
},
|
||||||
|
// Vision Seed methods - must be in Vue methods for proper binding
|
||||||
|
updateTestseed(index, value) {
|
||||||
|
// Ensure testseed is initialized
|
||||||
|
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed)) {
|
||||||
|
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
|
||||||
|
}
|
||||||
|
// Ensure array has enough elements
|
||||||
|
while (this.inbound.settings.testseed.length <= index) {
|
||||||
|
this.inbound.settings.testseed.push(0);
|
||||||
|
}
|
||||||
|
// Update value using Vue.set for reactivity
|
||||||
|
this.$set(this.inbound.settings.testseed, index, value);
|
||||||
|
},
|
||||||
|
setRandomTestseed() {
|
||||||
|
// Create new array with random values and use Vue.set for reactivity
|
||||||
|
const newSeed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
|
||||||
|
this.$set(this.inbound.settings, 'testseed', newSeed);
|
||||||
|
},
|
||||||
|
resetTestseed() {
|
||||||
|
// Reset testseed to default values using Vue.set for reactivity
|
||||||
|
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue