diff --git a/frontend/src/components/FinalMaskForm.vue b/frontend/src/components/FinalMaskForm.vue
index 23856b90..9613ae06 100644
--- a/frontend/src/components/FinalMaskForm.vue
+++ b/frontend/src/components/FinalMaskForm.vue
@@ -72,8 +72,6 @@ function newNoiseItem() {
:label-col="{ md: { span: 8 } }"
:wrapper-col="{ md: { span: 14 } }"
>
-
-
diff --git a/frontend/src/pages/xray/OutboundFormModal.vue b/frontend/src/pages/xray/OutboundFormModal.vue
index 2e338d4f..2b5f0834 100644
--- a/frontend/src/pages/xray/OutboundFormModal.vue
+++ b/frontend/src/pages/xray/OutboundFormModal.vue
@@ -930,7 +930,14 @@ function regenerateWgKeys() {
-
+
+
diff --git a/frontend/src/pages/xray/XrayPage.vue b/frontend/src/pages/xray/XrayPage.vue
index 90afab4b..bde8a769 100644
--- a/frontend/src/pages/xray/XrayPage.vue
+++ b/frontend/src/pages/xray/XrayPage.vue
@@ -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() {
{{ t('pages.xray.advancedTemplate') }}
-
-
-
-
-
+
+
+ {{ t('pages.xray.completeTemplate') }}
+ {{ t('pages.xray.Inbounds') }}
+ {{ t('pages.xray.Outbounds') }}
+ {{ t('pages.xray.Routings') }}
+
+