mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-17 08:15:56 +03:00
refactor: remove legacy advancedJson state
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user