mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
fix(frontend): xray Advanced tab parity + finalmask gating
Advanced tab was a single textarea bound to the full xraySetting blob.
Restore the legacy 4-way view: a radio group toggles between All /
Inbounds / Outbounds / Routing Rules, and the textarea reads/writes
the matching slice through templateSettings. Added the legacy header
("Advanced Xray Configuration Template" + description) so the page
introduces itself like main.
Outbound finalmask leaked into protocols that don't have a stream
(Freedom / Blackhole / DNS / Socks / HTTP / Wireguard) because the
v-if only checked outbound.stream. Gate the whole FinalMaskForm on
outbound.canEnableStream() to match main.
Drop the leading divider inside FinalMaskForm — its parent already
provides separation, so the rule above "TCP Masks" was redundant.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
18434bdbbd
commit
2e3b7e29a9
3 changed files with 83 additions and 13 deletions
|
|
@ -72,8 +72,6 @@ function newNoiseItem() {
|
||||||
:label-col="{ md: { span: 8 } }"
|
:label-col="{ md: { span: 8 } }"
|
||||||
:wrapper-col="{ md: { span: 14 } }"
|
:wrapper-col="{ md: { span: 14 } }"
|
||||||
>
|
>
|
||||||
<a-divider :style="{ margin: '5px 0 0' }" />
|
|
||||||
|
|
||||||
<!-- ============================== TCP MASKS ============================== -->
|
<!-- ============================== TCP MASKS ============================== -->
|
||||||
<template v-if="showTcp">
|
<template v-if="showTcp">
|
||||||
<a-form-item label="TCP Masks">
|
<a-form-item label="TCP Masks">
|
||||||
|
|
|
||||||
|
|
@ -930,7 +930,14 @@ function regenerateWgKeys() {
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- ============== FinalMask (TCP/UDP masks + QUIC params) ============== -->
|
<!-- ============== FinalMask (TCP/UDP masks + QUIC params) ============== -->
|
||||||
<FinalMaskForm v-if="outbound.stream" :stream="outbound.stream" :protocol="proto" />
|
<!-- Gated by canEnableStream() so TCP masks don't leak into
|
||||||
|
Freedom / Blackhole / DNS / Socks / HTTP / Wireguard outbounds
|
||||||
|
(they don't have a stream config at all). Matches legacy. -->
|
||||||
|
<FinalMaskForm
|
||||||
|
v-if="outbound.stream && outbound.canEnableStream()"
|
||||||
|
:stream="outbound.stream"
|
||||||
|
:protocol="proto"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<!-- ============================== JSON ============================== -->
|
<!-- ============================== JSON ============================== -->
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,60 @@ async function onTestOutbound(idx) {
|
||||||
if (outbound) await testOutbound(idx, outbound);
|
if (outbound) await testOutbound(idx, outbound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Advanced tab — radio-driven view ==============================
|
||||||
|
// Mirrors the legacy advanced page: a 4-way radio toggles which slice
|
||||||
|
// of the xray config the textarea edits — the full config, just the
|
||||||
|
// inbounds, just the outbounds, or just the routing rules. Each slice
|
||||||
|
// reads/writes through templateSettings so edits propagate to the
|
||||||
|
// dirty-poll and structured tabs.
|
||||||
|
const advSettings = ref('xraySetting');
|
||||||
|
|
||||||
|
const advancedText = computed({
|
||||||
|
get: () => {
|
||||||
|
if (advSettings.value === 'xraySetting') return xraySetting.value;
|
||||||
|
const t = templateSettings.value;
|
||||||
|
if (!t) return '';
|
||||||
|
try {
|
||||||
|
switch (advSettings.value) {
|
||||||
|
case 'inboundSettings':
|
||||||
|
return JSON.stringify(t.inbounds || [], null, 2);
|
||||||
|
case 'outboundSettings':
|
||||||
|
return JSON.stringify(t.outbounds || [], null, 2);
|
||||||
|
case 'routingRuleSettings':
|
||||||
|
return JSON.stringify(t.routing?.rules || [], null, 2);
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
} catch (_e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (next) => {
|
||||||
|
if (advSettings.value === 'xraySetting') {
|
||||||
|
xraySetting.value = next;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Slice edits: parse-then-merge into templateSettings so the
|
||||||
|
// structured tabs and the dirty-poll re-stringify it cleanly.
|
||||||
|
let parsed;
|
||||||
|
try { parsed = JSON.parse(next); } catch (_e) { return; }
|
||||||
|
const t = templateSettings.value;
|
||||||
|
if (!t) return;
|
||||||
|
switch (advSettings.value) {
|
||||||
|
case 'inboundSettings':
|
||||||
|
t.inbounds = parsed;
|
||||||
|
break;
|
||||||
|
case 'outboundSettings':
|
||||||
|
t.outbounds = parsed;
|
||||||
|
break;
|
||||||
|
case 'routingRuleSettings':
|
||||||
|
if (!t.routing) t.routing = {};
|
||||||
|
t.routing.rules = parsed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// `WarpExist` / `NordExist` derive from the parsed templateSettings —
|
// `WarpExist` / `NordExist` derive from the parsed templateSettings —
|
||||||
// the Basics tab gates its WARP / NordVPN domain selectors on whether
|
// the Basics tab gates its WARP / NordVPN domain selectors on whether
|
||||||
// the matching outbound is provisioned, falling back to a "configure"
|
// the matching outbound is provisioned, falling back to a "configure"
|
||||||
|
|
@ -263,16 +317,27 @@ function confirmRestart() {
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<CodeOutlined /> <span>{{ t('pages.xray.advancedTemplate') }}</span>
|
<CodeOutlined /> <span>{{ t('pages.xray.advancedTemplate') }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-form layout="vertical">
|
<a-list-item-meta
|
||||||
<a-form-item label="xraySetting (full JSON)">
|
:title="t('pages.xray.Template')"
|
||||||
<a-textarea
|
:description="t('pages.xray.TemplateDesc')"
|
||||||
v-model:value="xraySetting"
|
/>
|
||||||
:auto-size="{ minRows: 18, maxRows: 40 }"
|
<a-radio-group
|
||||||
spellcheck="false"
|
v-model:value="advSettings"
|
||||||
class="json-editor"
|
button-style="solid"
|
||||||
/>
|
:size="isMobile ? 'small' : 'middle'"
|
||||||
</a-form-item>
|
:style="{ margin: '12px 0' }"
|
||||||
</a-form>
|
>
|
||||||
|
<a-radio-button value="xraySetting">{{ t('pages.xray.completeTemplate') }}</a-radio-button>
|
||||||
|
<a-radio-button value="inboundSettings">{{ t('pages.xray.Inbounds') }}</a-radio-button>
|
||||||
|
<a-radio-button value="outboundSettings">{{ t('pages.xray.Outbounds') }}</a-radio-button>
|
||||||
|
<a-radio-button value="routingRuleSettings">{{ t('pages.xray.Routings') }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="advancedText"
|
||||||
|
:auto-size="{ minRows: 18, maxRows: 40 }"
|
||||||
|
spellcheck="false"
|
||||||
|
class="json-editor"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue