From 5543466fccdd227494ea7bb54c245a550449481c Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 13 May 2026 19:01:12 +0200 Subject: [PATCH] fix(forms): validate JSON tabs before applying or saving InboundFormModal: switching out of the Advanced tab now parses the three JSON textareas and rebuilds the structured Inbound via Inbound.fromJson, so the Basic tab reflects what was pasted. Invalid JSON keeps the user on Advanced with a specific parse error. XrayPage: Save now parses xraySetting upfront and snaps the user back to the Advanced tab on invalid JSON instead of letting the backend reject a generic blob. --- .../src/pages/inbounds/InboundFormModal.vue | 54 ++++++++++++- frontend/src/pages/xray/XrayPage.vue | 16 +++- web/service/config.json | 78 +++++++++---------- 3 files changed, 101 insertions(+), 47 deletions(-) diff --git a/frontend/src/pages/inbounds/InboundFormModal.vue b/frontend/src/pages/inbounds/InboundFormModal.vue index 45c30a9e..2c814dc2 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.vue +++ b/frontend/src/pages/inbounds/InboundFormModal.vue @@ -70,6 +70,7 @@ const inbound = ref(null); const dbForm = ref(null); const saving = ref(false); const advancedJson = ref({ stream: '', sniffing: '', settings: '' }); +const activeTabKey = ref('basic'); // Cached default cert/key paths from /panel/setting/defaultSettings — // powers the "Set default cert" button on the TLS form. const defaultCert = ref(''); @@ -241,9 +242,60 @@ watch(() => props.open, (next) => { dbForm.value = freshDbForm(); primeAdvancedJson(); } + activeTabKey.value = 'basic'; fetchDefaultCertSettings(); }); +function applyAdvancedJsonToBasic() { + if (!inbound.value) return true; + let parsedSettings; + let parsedStream; + let parsedSniffing; + try { + parsedSettings = advancedJson.value.settings.trim() + ? JSON.parse(advancedJson.value.settings) + : 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) + : 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) + : inbound.value.sniffing?.toJson?.(); + } catch (e) { message.error(`Sniffing JSON invalid: ${e.message}`); return false; } + + try { + inbound.value = Inbound.fromJson({ + port: inbound.value.port, + listen: inbound.value.listen, + protocol: inbound.value.protocol, + settings: parsedSettings, + streamSettings: parsedStream, + tag: inbound.value.tag, + sniffing: parsedSniffing, + clientStats: inbound.value.clientStats, + }); + } catch (e) { + message.error(`Advanced JSON: ${e.message}`); + return false; + } + return true; +} + +let isRevertingTab = false; +watch(activeTabKey, (next, prev) => { + if (isRevertingTab) { isRevertingTab = false; return; } + if (prev === 'advanced' && next !== 'advanced') { + if (!applyAdvancedJsonToBasic()) { + isRevertingTab = true; + activeTabKey.value = 'advanced'; + } + } +}); + // In add mode, switching protocol restamps settings + re-syncs port. function onProtocolChange(next) { if (props.mode === 'edit' || !inbound.value) return; @@ -572,7 +624,7 @@ watch(