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 } }"
|
||||
:wrapper-col="{ md: { span: 14 } }"
|
||||
>
|
||||
<a-divider :style="{ margin: '5px 0 0' }" />
|
||||
|
||||
<!-- ============================== TCP MASKS ============================== -->
|
||||
<template v-if="showTcp">
|
||||
<a-form-item label="TCP Masks">
|
||||
|
|
|
|||
|
|
@ -930,7 +930,14 @@ function regenerateWgKeys() {
|
|||
</a-form>
|
||||
|
||||
<!-- ============== 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>
|
||||
|
||||
<!-- ============================== JSON ============================== -->
|
||||
|
|
|
|||
|
|
@ -53,6 +53,60 @@ async function onTestOutbound(idx) {
|
|||
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 —
|
||||
// the Basics tab gates its WARP / NordVPN domain selectors on whether
|
||||
// the matching outbound is provisioned, falling back to a "configure"
|
||||
|
|
@ -263,16 +317,27 @@ function confirmRestart() {
|
|||
<template #tab>
|
||||
<CodeOutlined /> <span>{{ t('pages.xray.advancedTemplate') }}</span>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="xraySetting (full JSON)">
|
||||
<a-textarea
|
||||
v-model:value="xraySetting"
|
||||
:auto-size="{ minRows: 18, maxRows: 40 }"
|
||||
spellcheck="false"
|
||||
class="json-editor"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-list-item-meta
|
||||
:title="t('pages.xray.Template')"
|
||||
:description="t('pages.xray.TemplateDesc')"
|
||||
/>
|
||||
<a-radio-group
|
||||
v-model:value="advSettings"
|
||||
button-style="solid"
|
||||
:size="isMobile ? 'small' : 'middle'"
|
||||
:style="{ margin: '12px 0' }"
|
||||
>
|
||||
<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-tabs>
|
||||
</a-col>
|
||||
|
|
|
|||
Loading…
Reference in a new issue