diff options
Diffstat (limited to 'web/html')
| -rw-r--r-- | web/html/common/page.html | 1 | ||||
| -rw-r--r-- | web/html/form/outbound.html | 31 | ||||
| -rw-r--r-- | web/html/form/protocol/vless.html | 30 | ||||
| -rw-r--r-- | web/html/form/stream/stream_sockopt.html | 9 | ||||
| -rw-r--r-- | web/html/form/tls_settings.html | 18 | ||||
| -rw-r--r-- | web/html/inbounds.html | 121 | ||||
| -rw-r--r-- | web/html/index.html | 72 | ||||
| -rw-r--r-- | web/html/modals/inbound_modal.html | 94 | ||||
| -rw-r--r-- | web/html/settings/xray/dns.html | 7 | ||||
| -rw-r--r-- | web/html/xray.html | 19 |
10 files changed, 369 insertions, 33 deletions
diff --git a/web/html/common/page.html b/web/html/common/page.html index c0a7ca63..0af63afb 100644 --- a/web/html/common/page.html +++ b/web/html/common/page.html @@ -49,6 +49,7 @@ const basePath = '{{ .base_path }}'; axios.defaults.baseURL = basePath; </script> +<script src="{{ .base_path }}assets/js/websocket.js?{{ .cur_ver }}"></script> {{ end }} {{ define "page/body_end" }} diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html index aa6aa323..1926c30e 100644 --- a/web/html/form/outbound.html +++ b/web/html/form/outbound.html @@ -239,6 +239,28 @@ </a-select> </a-form-item> </template> + <!-- XTLS Vision Advanced Settings --> + <template v-if="outbound.protocol === Protocols.VLESS && (outbound.settings.flow === 'xtls-rprx-vision' || outbound.settings.flow === 'xtls-rprx-vision-udp443')"> + <a-form-item label="Vision Pre-Connect"> + <a-input-number v-model.number="outbound.settings.testpre" :min="0" :max="10" :style="{ width: '100%' }" placeholder="0"></a-input-number> + </a-form-item> + <a-form-item label="Vision Seed"> + <a-row :gutter="8"> + <a-col :span="6"> + <a-input-number v-model.number="outbound.settings.testseed[0]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number> + </a-col> + <a-col :span="6"> + <a-input-number v-model.number="outbound.settings.testseed[1]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number> + </a-col> + <a-col :span="6"> + <a-input-number v-model.number="outbound.settings.testseed[2]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number> + </a-col> + <a-col :span="6"> + <a-input-number v-model.number="outbound.settings.testseed[3]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number> + </a-col> + </a-row> + </a-form-item> + </template> </template> <!-- Servers (trojan/shadowsocks/socks/http) settings --> @@ -501,6 +523,15 @@ <a-form-item label="Penetrate"> <a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch> </a-form-item> + <a-form-item label="Trusted X-Forwarded-For"> + <a-select mode="tags" v-model="outbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }" + :dropdown-class-name="themeSwitcher.currentTheme"> + <a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option> + <a-select-option value="X-Real-IP">X-Real-IP</a-select-option> + <a-select-option value="True-Client-IP">True-Client-IP</a-select-option> + <a-select-option value="X-Client-IP">X-Client-IP</a-select-option> + </a-select> + </a-form-item> </template> <!-- mux settings --> diff --git a/web/html/form/protocol/vless.html b/web/html/form/protocol/vless.html index 140b9c1a..ad5b4265 100644 --- a/web/html/form/protocol/vless.html +++ b/web/html/form/protocol/vless.html @@ -39,6 +39,7 @@ </a-space> </a-form-item> </a-form> + <a-divider v-if="inbound.settings.selectedAuth" :style="{ margin: '5px 0' }"></a-divider> </template> <template v-if="inbound.isTcp && !inbound.settings.selectedAuth"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> @@ -69,4 +70,33 @@ </a-form> <a-divider :style="{ margin: '5px 0' }"></a-divider> </template> +<template v-if="inbound.settings.vlesses.some(c => c.flow === 'xtls-rprx-vision' || c.flow === 'xtls-rprx-vision-udp443')"> + <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> + <a-form-item label="Vision Seed"> + <a-row :gutter="8"> + <a-col :span="6"> + <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 :span="6"> + <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 :span="6"> + <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 :span="6"> + <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-row> + <a-space :size="8" :style="{ marginTop: '8px' }"> + <a-button type="primary" @click="setRandomTestseed"> + Rand + </a-button> + <a-button @click="resetTestseed"> + Reset + </a-button> + </a-space> + </a-form-item> + </a-form> + <a-divider :style="{ margin: '5px 0' }"></a-divider> +</template> {{end}} diff --git a/web/html/form/stream/stream_sockopt.html b/web/html/form/stream/stream_sockopt.html index 4480594a..062b83df 100644 --- a/web/html/form/stream/stream_sockopt.html +++ b/web/html/form/stream/stream_sockopt.html @@ -61,6 +61,15 @@ <a-form-item label="Interface Name"> <a-input v-model="inbound.stream.sockopt.interfaceName"></a-input> </a-form-item> + <a-form-item label="Trusted X-Forwarded-For"> + <a-select mode="tags" v-model="inbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }" + :dropdown-class-name="themeSwitcher.currentTheme"> + <a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option> + <a-select-option value="X-Real-IP">X-Real-IP</a-select-option> + <a-select-option value="True-Client-IP">True-Client-IP</a-select-option> + <a-select-option value="X-Client-IP">X-Client-IP</a-select-option> + </a-select> + </a-form-item> </template> </a-form> {{end}} diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html index c3844a7f..3723130e 100644 --- a/web/html/form/tls_settings.html +++ b/web/html/form/tls_settings.html @@ -60,16 +60,20 @@ <a-form-item label="VerifyPeerCertInNames"> <a-input v-model.trim="inbound.stream.tls.verifyPeerCertInNames"></a-input> </a-form-item> + <a-divider :style="{ margin: '3px 0' }"></a-divider> <template v-for="cert,index in inbound.stream.tls.certs"> <a-form-item label='{{ i18n "certificate" }}'> - <a-radio-group v-model="cert.useFile" button-style="solid"> - <a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button> - <a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button> + <a-radio-group v-model="cert.useFile" button-style="solid" :style="{ display: 'inline-flex', whiteSpace: 'nowrap', maxWidth: '100%' }"> + <a-radio-button :value="true" :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button> + <a-radio-button :value="false" :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button> </a-radio-group> - <a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" - :style="{ marginLeft: '10px' }"></a-button> - <a-button icon="minus" v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" - @click="inbound.stream.tls.removeCert(index)" :style="{ marginLeft: '10px' }"></a-button> + </a-form-item> + <a-form-item label=" "> + <a-space> + <a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"></a-button> + <a-button icon="minus" v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" + @click="inbound.stream.tls.removeCert(index)"></a-button> + </a-space> </a-form-item> <template v-if="cert.useFile"> <a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'> diff --git a/web/html/inbounds.html b/web/html/inbounds.html index 86bde2c8..4e1149ae 100644 --- a/web/html/inbounds.html +++ b/web/html/inbounds.html @@ -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(); - } - else { - this.getDBInbounds(); + + // 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(); + } } - this.loading(false); }, computed: { total() { diff --git a/web/html/index.html b/web/html/index.html index 9cbb019d..bbbbb708 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -1102,6 +1102,20 @@ }); fileInput.click(); }, + startPolling() { + // Fallback polling mechanism + const pollInterval = setInterval(async () => { + if (window.wsClient && window.wsClient.isConnected) { + clearInterval(pollInterval); + return; + } + try { + await this.getStatus(); + } catch (e) { + console.error(e); + } + }, 2000); + }, }, async mounted() { if (window.location.protocol !== "https:") { @@ -1113,13 +1127,57 @@ this.ipLimitEnable = msg.obj.ipLimitEnable; } - while (true) { - try { - await this.getStatus(); - } catch (e) { - console.error(e); - } - await PromiseUtil.sleep(2000); + // Initial status fetch + await this.getStatus(); + + // Setup WebSocket for real-time updates + if (window.wsClient) { + window.wsClient.connect(); + + // Listen for status updates + window.wsClient.on('status', (payload) => { + this.setStatus(payload); + }); + + // Listen for Xray state changes + window.wsClient.on('xray_state', (payload) => { + if (this.status && this.status.xray) { + this.status.xray.state = payload.state; + this.status.xray.errorMsg = payload.errorMsg || ''; + switch (payload.state) { + case 'running': + this.status.xray.color = "green"; + this.status.xray.stateMsg = '{{ i18n "pages.index.xrayStatusRunning" }}'; + break; + case 'stop': + this.status.xray.color = "orange"; + this.status.xray.stateMsg = '{{ i18n "pages.index.xrayStatusStop" }}'; + break; + case 'error': + this.status.xray.color = "red"; + this.status.xray.stateMsg = '{{ i18n "pages.index.xrayStatusError" }}'; + break; + } + } + }); + + // 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'); + this.startPolling(); + }); + + window.wsClient.on('disconnected', () => { + if (window.wsClient.reconnectAttempts >= window.wsClient.maxReconnectAttempts) { + console.warn('WebSocket reconnection failed, falling back to polling'); + this.startPolling(); + } + }); + } else { + // Fallback to polling if WebSocket is not available + this.startPolling(); } }, }); diff --git a/web/html/modals/inbound_modal.html b/web/html/modals/inbound_modal.html index 3c844381..c3883285 100644 --- a/web/html/modals/inbound_modal.html +++ b/web/html/modals/inbound_modal.html @@ -6,7 +6,8 @@ </a-modal> <script> - const inModal = { + // Make inModal globally available to ensure it works with any base path + const inModal = window.inModal = { title: '', visible: false, confirmLoading: false, @@ -26,6 +27,14 @@ } else { 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) { this.dbInbound = new DBInbound(dbInbound); } else { @@ -42,9 +51,43 @@ loading(loading = true) { 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: ['[[', ']]'], el: '#inbound-modal', data: { @@ -60,7 +103,7 @@ return inModal.isEdit; }, 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() { return app.datepicker; @@ -87,6 +130,28 @@ } } }, + watch: { + 'inModal.inbound.stream.security'(newVal, oldVal) { + // Clear flow when security changes from reality/tls to none + if (inModal.inbound.protocol == Protocols.VLESS && !inModal.inbound.canEnableTlsFlow()) { + inModal.inbound.settings.vlesses.forEach(client => { + 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: { streamNetworkChange() { if (!inModal.inbound.canEnableTls()) { @@ -204,8 +269,29 @@ this.inbound.settings.decryption = 'none'; this.inbound.settings.encryption = 'none'; 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]); } - }, }); diff --git a/web/html/settings/xray/dns.html b/web/html/settings/xray/dns.html index ba768cb8..8a18bbb4 100644 --- a/web/html/settings/xray/dns.html +++ b/web/html/settings/xray/dns.html @@ -56,6 +56,13 @@ <a-switch v-model="dnsDisableFallbackIfMatch"></a-switch> </template> </a-setting-list-item> + <a-setting-list-item paddings="small"> + <template #title>{{ i18n "pages.xray.dns.enableParallelQuery" }}</template> + <template #description>{{ i18n "pages.xray.dns.enableParallelQueryDesc" }}</template> + <template #control> + <a-switch v-model="dnsEnableParallelQuery"></a-switch> + </template> + </a-setting-list-item> <a-setting-list-item paddings="small"> <template #title>{{ i18n "pages.xray.dns.useSystemHosts" }}</template> diff --git a/web/html/xray.html b/web/html/xray.html index f4d89c84..ce5e1ab7 100644 --- a/web/html/xray.html +++ b/web/html/xray.html @@ -269,7 +269,7 @@ tag: "direct", protocol: "freedom" }, - routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"], + routingDomainStrategies: ["AsIs", "IpIfNonMatch", "IpOnDemand"], log: { loglevel: ["none", "debug", "info", "warning", "error"], access: ["none", "./access.log"], @@ -1315,7 +1315,8 @@ newTemplateSettings.dns = { servers: [], queryStrategy: "UseIP", - tag: "dns_inbound" + tag: "dns_inbound", + enableParallelQuery: false }; newTemplateSettings.fakedns = null; } else { @@ -1391,6 +1392,20 @@ this.templateSettings = newTemplateSettings; } }, + dnsEnableParallelQuery: { + get: function () { + return this.enableDNS ? (this.templateSettings.dns.enableParallelQuery || false) : false; + }, + set: function (newValue) { + newTemplateSettings = this.templateSettings; + if (newValue) { + newTemplateSettings.dns.enableParallelQuery = newValue; + } else { + delete newTemplateSettings.dns.enableParallelQuery + } + this.templateSettings = newTemplateSettings; + } + }, dnsUseSystemHosts: { get: function () { return this.enableDNS ? this.templateSettings.dns.useSystemHosts : false; |
