refactor: remove legacy advancedJson state

This commit is contained in:
MHSanaei
2026-05-14 20:32:38 +02:00
parent 05b68c3b13
commit b79abc8bc9

View File

@@ -70,8 +70,11 @@ const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
const inbound = ref(null);
const dbForm = ref(null);
const saving = ref(false);
const advancedJson = ref({ stream: '', sniffing: '', settings: '' });
const advancedStreamText = ref('');
const advancedSniffingText = ref('');
const advancedSettingsText = ref('');
const activeTabKey = ref('basic');
const advancedSectionKey = ref('all');
// Cached default cert/key paths from /panel/setting/defaultSettings —
// powers the "Set default cert" button on the TLS form.
const defaultCert = ref('');
@@ -224,13 +227,13 @@ function freshDbForm() {
function primeAdvancedJson() {
if (!inbound.value) return;
try {
advancedJson.value.stream = JSON.stringify(JSON.parse(inbound.value.stream.toString()), null, 2);
advancedStreamText.value = JSON.stringify(JSON.parse(inbound.value.stream.toString()), null, 2);
} catch (_e) { /* keep prior text */ }
try {
advancedJson.value.sniffing = JSON.stringify(JSON.parse(inbound.value.sniffing.toString()), null, 2);
advancedSniffingText.value = JSON.stringify(JSON.parse(inbound.value.sniffing.toString()), null, 2);
} catch (_e) { /* keep prior text */ }
try {
advancedJson.value.settings = JSON.stringify(JSON.parse(inbound.value.settings.toString()), null, 2);
advancedSettingsText.value = JSON.stringify(JSON.parse(inbound.value.settings.toString()), null, 2);
} catch (_e) { /* keep prior text */ }
}
@@ -244,6 +247,7 @@ watch(() => props.open, (next) => {
primeAdvancedJson();
}
activeTabKey.value = 'basic';
advancedSectionKey.value = 'all';
fetchDefaultCertSettings();
});
@@ -253,18 +257,18 @@ function applyAdvancedJsonToBasic() {
let parsedStream;
let parsedSniffing;
try {
parsedSettings = advancedJson.value.settings.trim()
? JSON.parse(advancedJson.value.settings)
parsedSettings = advancedSettingsText.value.trim()
? JSON.parse(advancedSettingsText.value)
: inbound.value.settings?.toJson?.();
} catch (e) { message.error(`Settings JSON invalid: ${e.message}`); return false; }
try {
parsedStream = advancedJson.value.stream.trim()
? JSON.parse(advancedJson.value.stream)
parsedStream = advancedStreamText.value.trim()
? JSON.parse(advancedStreamText.value)
: inbound.value.stream?.toJson?.();
} catch (e) { message.error(`Stream JSON invalid: ${e.message}`); return false; }
try {
parsedSniffing = advancedJson.value.sniffing.trim()
? JSON.parse(advancedJson.value.sniffing)
parsedSniffing = advancedSniffingText.value.trim()
? JSON.parse(advancedSniffingText.value)
: inbound.value.sniffing?.toJson?.();
} catch (e) { message.error(`Sniffing JSON invalid: ${e.message}`); return false; }
@@ -324,6 +328,203 @@ function onNetworkChange(next) {
}
}
function parseAdvancedSliceOrFallback(rawText, fallbackValue) {
if (!rawText?.trim()) return fallbackValue;
return JSON.parse(rawText);
}
function unwrapWrappedObject(parsed, key) {
if (
parsed
&& typeof parsed === 'object'
&& !Array.isArray(parsed)
&& parsed[key] !== undefined
) {
return parsed[key];
}
return parsed;
}
const advancedAllConfig = computed({
get: () => {
if (!inbound.value) return '';
try {
const settings = parseAdvancedSliceOrFallback(
advancedSettingsText.value,
inbound.value.settings?.toJson?.() || {},
);
const streamSettings = parseAdvancedSliceOrFallback(
advancedStreamText.value,
inbound.value.stream?.toJson?.() || {},
);
const sniffing = parseAdvancedSliceOrFallback(
advancedSniffingText.value,
inbound.value.sniffing?.toJson?.() || {},
);
return JSON.stringify({
listen: inbound.value.listen,
port: inbound.value.port,
protocol: inbound.value.protocol,
settings,
sniffing,
streamSettings,
tag: inbound.value.tag,
}, null, 2);
} catch (_e) {
return '';
}
},
set: (next) => {
let parsed;
try {
parsed = JSON.parse(next);
} catch (e) {
message.error(`All JSON invalid: ${e.message}`);
return;
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
message.error('All JSON must be an inbound object.');
return;
}
try {
if (typeof parsed.listen === 'string') {
inbound.value.listen = parsed.listen;
}
if (parsed.port !== undefined) {
const parsedPort = Number(parsed.port);
if (!Number.isNaN(parsedPort) && Number.isFinite(parsedPort)) {
inbound.value.port = parsedPort;
}
}
if (typeof parsed.protocol === 'string' && PROTOCOLS.includes(parsed.protocol)) {
inbound.value.protocol = parsed.protocol;
}
if (typeof parsed.tag === 'string') {
inbound.value.tag = parsed.tag;
}
const existingSettings = parseAdvancedSliceOrFallback(
advancedSettingsText.value,
inbound.value?.settings?.toJson?.() || {},
);
const settings = parsed.settings ?? existingSettings;
const streamSettings = parsed.streamSettings ?? (inbound.value?.stream?.toJson?.() || {});
const sniffing = parsed.sniffing ?? (inbound.value?.sniffing?.toJson?.() || {});
advancedSettingsText.value = JSON.stringify(settings, null, 2);
advancedStreamText.value = JSON.stringify(streamSettings, null, 2);
advancedSniffingText.value = JSON.stringify(sniffing, null, 2);
} catch (e) {
message.error(`All JSON invalid: ${e.message}`);
}
},
});
const advancedSettingsConfig = computed({
get: () => {
if (!inbound.value) return '';
try {
const settings = parseAdvancedSliceOrFallback(
advancedSettingsText.value,
inbound.value.settings?.toJson?.() || {},
);
return JSON.stringify({
settings,
}, null, 2);
} catch (_e) {
return '';
}
},
set: (next) => {
let parsed;
try {
parsed = JSON.parse(next);
} catch (e) {
message.error(`Settings JSON invalid: ${e.message}`);
return;
}
const unwrapped = unwrapWrappedObject(parsed, 'settings');
if (!unwrapped || typeof unwrapped !== 'object' || Array.isArray(unwrapped)) {
message.error('Settings JSON must be an object or { settings: { ... } }.');
return;
}
try {
advancedSettingsText.value = JSON.stringify(unwrapped, null, 2);
} catch (e) {
message.error(`Settings JSON invalid: ${e.message}`);
}
},
});
const advancedSniffingConfig = computed({
get: () => {
if (!inbound.value) return '';
try {
const sniffing = parseAdvancedSliceOrFallback(
advancedSniffingText.value,
inbound.value.sniffing?.toJson?.() || {},
);
return JSON.stringify({ sniffing }, null, 2);
} catch (_e) {
return '';
}
},
set: (next) => {
let parsed;
try {
parsed = JSON.parse(next);
} catch (e) {
message.error(`Sniffing JSON invalid: ${e.message}`);
return;
}
const unwrapped = unwrapWrappedObject(parsed, 'sniffing');
if (!unwrapped || typeof unwrapped !== 'object' || Array.isArray(unwrapped)) {
message.error('Sniffing JSON must be an object or { sniffing: { ... } }.');
return;
}
try {
advancedSniffingText.value = JSON.stringify(unwrapped, null, 2);
} catch (e) {
message.error(`Sniffing JSON invalid: ${e.message}`);
}
},
});
const advancedStreamConfig = computed({
get: () => {
if (!inbound.value) return '';
try {
const streamSettings = parseAdvancedSliceOrFallback(
advancedStreamText.value,
inbound.value.stream?.toJson?.() || {},
);
return JSON.stringify({ streamSettings }, null, 2);
} catch (_e) {
return '';
}
},
set: (next) => {
let parsed;
try {
parsed = JSON.parse(next);
} catch (e) {
message.error(`Stream JSON invalid: ${e.message}`);
return;
}
const unwrapped = unwrapWrappedObject(parsed, 'streamSettings');
if (!unwrapped || typeof unwrapped !== 'object' || Array.isArray(unwrapped)) {
message.error('Stream JSON must be an object or { streamSettings: { ... } }.');
return;
}
try {
advancedStreamText.value = JSON.stringify(unwrapped, null, 2);
} catch (e) {
message.error(`Stream JSON invalid: ${e.message}`);
}
},
});
// === Random helpers wired to the form's sync icons ==================
function randomEmail(target) {
if (target) target.email = RandomUtil.randomLowerAndNum(9);
@@ -525,16 +726,16 @@ async function submit() {
let settings;
try {
streamSettings = canEnableStream.value
? JSON.stringify(JSON.parse(advancedJson.value.stream))
? JSON.stringify(JSON.parse(advancedStreamText.value))
: (inbound.value.stream?.sockopt
? JSON.stringify({ sockopt: inbound.value.stream.sockopt.toJson() })
: '');
} catch (e) { message.error(`Stream JSON invalid: ${e.message}`); return; }
try {
sniffing = JSON.stringify(JSON.parse(advancedJson.value.sniffing || inbound.value.sniffing.toString()));
sniffing = JSON.stringify(JSON.parse(advancedSniffingText.value || inbound.value.sniffing.toString()));
} catch (e) { message.error(`Sniffing JSON invalid: ${e.message}`); return; }
try {
settings = JSON.stringify(JSON.parse(advancedJson.value.settings || inbound.value.settings.toString()));
settings = JSON.stringify(JSON.parse(advancedSettingsText.value || inbound.value.settings.toString()));
} catch (e) { message.error(`Settings JSON invalid: ${e.message}`); return; }
// The structured form mutates `inbound.stream` directly when the
@@ -598,7 +799,7 @@ watch(
() => {
if (!inbound.value?.stream) return;
try {
advancedJson.value.stream = JSON.stringify(JSON.parse(inbound.value.stream.toString()), null, 2);
advancedStreamText.value = JSON.stringify(JSON.parse(inbound.value.stream.toString()), null, 2);
} catch (_e) { /* leave as is */ }
},
);
@@ -607,7 +808,7 @@ watch(
() => {
if (!inbound.value?.sniffing) return;
try {
advancedJson.value.sniffing = JSON.stringify(JSON.parse(inbound.value.sniffing.toString()), null, 2);
advancedSniffingText.value = JSON.stringify(JSON.parse(inbound.value.sniffing.toString()), null, 2);
} catch (_e) { /* leave as is */ }
},
);
@@ -616,7 +817,7 @@ watch(
() => {
if (!inbound.value?.settings) return;
try {
advancedJson.value.settings = JSON.stringify(JSON.parse(inbound.value.settings.toString()), null, 2);
advancedSettingsText.value = JSON.stringify(JSON.parse(inbound.value.settings.toString()), null, 2);
} catch (_e) { /* leave as is */ }
},
);
@@ -1005,7 +1206,7 @@ watch(
<a-form-item>
<template #label>
<a-tooltip
title='Physical interface for outbound traffic. Use "auto" to detect; auto-enabled when Auto system routes is set.'>
title="Physical interface for outbound traffic. Use 'auto' to detect; auto-enabled when Auto system routes is set.">
Auto outbounds interface
</a-tooltip>
</template>
@@ -1944,20 +2145,48 @@ watch(
<!-- ============================== ADVANCED ============================== -->
<a-tab-pane key="advanced" :tab="t('pages.xray.advancedTemplate')">
<a-alert type="info" show-icon
message="Edit raw stream JSON to access advanced fields we don't yet expose through the form."
class="mb-12" />
<a-form layout="vertical">
<a-form-item label="settings (clients, encryption, fallbacks, )">
<JsonEditor v-model:value="advancedJson.settings" min-height="280px" max-height="520px" />
</a-form-item>
<a-form-item label="streamSettings">
<JsonEditor v-model:value="advancedJson.stream" min-height="280px" max-height="520px" />
</a-form-item>
<a-form-item label="sniffing (overrides the Sniffing tab when set)">
<JsonEditor v-model:value="advancedJson.sniffing" min-height="180px" max-height="360px" />
</a-form-item>
</a-form>
<div class="advanced-shell">
<div class="advanced-panel">
<div class="advanced-panel__header">
<div>
<div class="advanced-panel__title">Inbound JSON sections</div>
<div class="advanced-panel__subtitle">
Full inbound JSON and focused editors for settings, sniffing, and streamSettings.
</div>
</div>
</div>
<a-tabs v-model:active-key="advancedSectionKey" class="advanced-inner-tabs">
<a-tab-pane key="all" tab="All">
<div class="advanced-editor-meta">
Full inbound object with all fields in one editor.
</div>
<JsonEditor v-model:value="advancedAllConfig" min-height="340px" max-height="560px" />
</a-tab-pane>
<a-tab-pane key="settings" tab="Settings">
<div class="advanced-editor-meta">
Xray settings block wrapper:
<code>{ settings: { ... } }</code>.
</div>
<JsonEditor v-model:value="advancedSettingsConfig" min-height="320px" max-height="540px" />
</a-tab-pane>
<a-tab-pane key="sniffingSection" tab="Sniffing">
<div class="advanced-editor-meta">
Xray sniffing block wrapper:
<code>{ sniffing: { ... } }</code>.
</div>
<JsonEditor v-model:value="advancedSniffingConfig" min-height="240px" max-height="420px" />
</a-tab-pane>
<a-tab-pane key="streamSection" tab="Stream">
<div class="advanced-editor-meta">
Xray stream block wrapper:
<code>{ streamSettings: { ... } }</code>.
</div>
<JsonEditor v-model:value="advancedStreamConfig" min-height="320px" max-height="540px" />
</a-tab-pane>
</a-tabs>
</div>
</div>
</a-tab-pane>
</a-tabs>
</a-modal>
@@ -2033,6 +2262,73 @@ watch(
margin-top: 4px;
}
.advanced-shell {
display: flex;
flex-direction: column;
gap: 12px;
}
.advanced-panel {
padding: 14px;
border: 1px solid rgba(128, 128, 128, 0.18);
border-radius: 12px;
background: rgba(128, 128, 128, 0.04);
}
.advanced-panel__header {
margin-bottom: 12px;
}
.advanced-panel__title {
font-size: 14px;
font-weight: 600;
line-height: 1.4;
}
.advanced-panel__subtitle {
margin-top: 4px;
color: rgba(0, 0, 0, 0.6);
line-height: 1.5;
}
.advanced-inner-tabs :deep(.ant-tabs-nav) {
margin-bottom: 12px;
}
.advanced-inner-tabs :deep(.ant-tabs-tab) {
padding-inline: 14px;
}
.advanced-editor-meta {
margin-bottom: 10px;
color: rgba(0, 0, 0, 0.65);
line-height: 1.5;
}
@media (max-width: 768px) {
.advanced-panel {
padding: 12px;
border-radius: 10px;
}
.advanced-inner-tabs :deep(.ant-tabs-tab) {
padding-inline: 10px;
}
}
:global(.dark) .advanced-panel__subtitle,
:global(.dark) .advanced-editor-meta,
:global(.ultra) .advanced-panel__subtitle,
:global(.ultra) .advanced-editor-meta {
color: rgba(255, 255, 255, 0.65);
}
:global(.dark) .advanced-panel,
:global(.ultra) .advanced-panel {
border-color: rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.03);
}
.section-heading {
font-weight: 500;
margin: 12px 0 6px;