From 6c6b40e0631656d52b579b6c6e995ec23545be2f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 13 May 2026 16:48:16 +0200 Subject: [PATCH] fix(outbound): accept JSON-only configs and sync JSON to basic form on tab switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pasting a JSON config and clicking OK failed with "Something went wrong" because validation read the empty form-side tag input instead of the JSON's tag. Switching from the JSON tab to Basic also discarded any JSON the user had pasted. - onOk now validates and submits from the JSON tab using the parsed JSON - Tab switch JSON→Basic deserializes the JSON back into the structured form - Invalid JSON keeps the user on the JSON tab with a clear parse error - Empty form-tag / duplicate-tag errors are now specific, not generic --- frontend/src/pages/xray/OutboundFormModal.vue | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/frontend/src/pages/xray/OutboundFormModal.vue b/frontend/src/pages/xray/OutboundFormModal.vue index 4310b067..54c97939 100644 --- a/frontend/src/pages/xray/OutboundFormModal.vue +++ b/frontend/src/pages/xray/OutboundFormModal.vue @@ -80,8 +80,17 @@ watch(() => props.open, (next) => { primeAdvancedJson(); }); -watch(activeKey, (key) => { - if (key === '2') primeAdvancedJson(); +let isRevertingTab = false; +watch(activeKey, (key, prev) => { + if (isRevertingTab) { isRevertingTab = false; return; } + if (key === '2') { + primeAdvancedJson(); + } else if (key === '1' && prev === '2') { + if (!applyAdvancedJsonToForm()) { + isRevertingTab = true; + activeKey.value = '2'; + } + } }); function primeAdvancedJson() { @@ -93,6 +102,33 @@ function primeAdvancedJson() { } } +function applyAdvancedJsonToForm() { + const raw = advancedJson.value.trim(); + if (!raw) return true; + let currentJson = ''; + try { + currentJson = JSON.stringify(outbound.value?.toJson() ?? {}, null, 2); + } catch (_e) { /* fall through */ } + if (raw === currentJson.trim()) return true; + let parsed; + try { + parsed = JSON.parse(raw); + } catch (e) { + message.error(`JSON: ${e.message}`); + return false; + } + try { + const fallbackTag = outbound.value?.tag; + const next = Outbound.fromJson(parsed); + if (!next.tag && fallbackTag) next.tag = fallbackTag; + outbound.value = next; + return true; + } catch (e) { + message.error(`JSON: ${e.message}`); + return false; + } +} + function close() { emit('update:open', false); } function onProtocolChange(next) { @@ -131,27 +167,15 @@ const tagHelp = computed(() => { // ============== Submit ============== function onOk() { if (!outbound.value) return; + if (activeKey.value === '2' && !applyAdvancedJsonToForm()) return; if (!outbound.value.tag?.trim()) { - message.error(t('somethingWentWrong')); + message.error('Tag is required'); return; } if (duplicateTag.value) { - message.error(t('somethingWentWrong')); + message.error('Tag already used by another outbound'); return; } - // If user spent time in the JSON tab, prefer that body — round-trip - // it through Outbound.fromJson so the wire shape stays consistent. - if (activeKey.value === '2' && advancedJson.value.trim()) { - try { - const parsed = JSON.parse(advancedJson.value); - const built = Outbound.fromJson(parsed); - emit('confirm', built.toJson()); - return; - } catch (e) { - message.error(`JSON: ${e.message}`); - return; - } - } emit('confirm', outbound.value.toJson()); }