From cedc46a14dc1afa3e79dd1f3271a901829745f3a Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 8 May 2026 19:45:14 +0200 Subject: [PATCH] feat(frontend): inbound modal QR + tabs + restored TLS fallbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-client QR action: the qr icon on the expand-row table opened the big info modal instead of the QR modal. Route it to QrCodeModal and extend that modal with a `client` prop so genAllLinks() produces the per-client share URLs (and per-peer remarks for WireGuard). Inbound's Data redesign: split the dense single-page view into three tabs — Inbound, Client, Subscription. Drop every QR rendering from this modal (QrCodeModal is the QR home now). Each row in the Inbound tab is one label/value pair instead of the legacy 2-column grid, and long values like the VLESS encryption blob render as a wrapping code block with a copy button so they can't blow out the dialog. The Subscription tab renders sub URL + JSON URL as clickable anchors that open in a new tab. Restored TLS fallbacks UI: the model already exposed VLESSSettings.Fallback / TrojanSettings.Fallback with addFallback / delFallback / fallbackToJson, but the form modal never surfaced them during the Vue 3 migration. Re-add the legacy form (SNI, ALPN, Path, Destination, PROXY) on the protocol tab, gated on TCP transport plus (for VLESS) encryption=none — same conditions as main. Column widths: Protocol 70→130 and All-time Traffic 60→95 in the inbound list; All-time Traffic 90→130 in the client expand-row, so the header text fits and tags don't get squeezed. Co-Authored-By: Claude Opus 4.7 --- .../src/pages/inbounds/ClientRowTable.vue | 2 +- .../src/pages/inbounds/InboundFormModal.vue | 131 +++ .../src/pages/inbounds/InboundInfoModal.vue | 1019 ++++++++++------- frontend/src/pages/inbounds/InboundList.vue | 4 +- frontend/src/pages/inbounds/InboundsPage.vue | 11 +- frontend/src/pages/inbounds/QrCodeModal.vue | 14 +- 6 files changed, 748 insertions(+), 433 deletions(-) diff --git a/frontend/src/pages/inbounds/ClientRowTable.vue b/frontend/src/pages/inbounds/ClientRowTable.vue index eaaa4255..345e0b35 100644 --- a/frontend/src/pages/inbounds/ClientRowTable.vue +++ b/frontend/src/pages/inbounds/ClientRowTable.vue @@ -407,7 +407,7 @@ function rowKey(client) { 80px /* online */ minmax(160px, 2fr) /* client identity */ minmax(160px, 2fr) /* traffic */ - 90px /* all-time */ + 130px /* all-time */ 140px; /* expiry */ gap: 12px; align-items: center; diff --git a/frontend/src/pages/inbounds/InboundFormModal.vue b/frontend/src/pages/inbounds/InboundFormModal.vue index fb38f197..133e0767 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.vue +++ b/frontend/src/pages/inbounds/InboundFormModal.vue @@ -140,6 +140,25 @@ const canEnableTls = computed(() => inbound.value?.canEnableTls?.() === true); const canEnableReality = computed(() => inbound.value?.canEnableReality?.() === true); const canEnableTlsFlow = computed(() => inbound.value?.canEnableTlsFlow?.() === true); +// VLESS/Trojan TLS fallbacks — surfaced in the protocol tab when the +// inbound is on TCP and (for VLESS) using no Xray-side encryption. +const showFallbacks = computed(() => { + if (!inbound.value) return false; + if (inbound.value.stream?.network !== 'tcp') return false; + if (inbound.value.protocol === Protocols.VLESS) { + const enc = inbound.value.settings?.encryption; + return !enc || enc === 'none'; + } + return inbound.value.protocol === Protocols.TROJAN; +}); + +function addFallback() { + inbound.value?.settings?.addFallback?.(); +} +function delFallback(idx) { + inbound.value?.settings?.delFallback?.(idx); +} + // Date / GB bridges (legacy used moment via _expiryTime; we go direct). const expiryDate = computed({ get: () => (dbForm.value?.expiryTime > 0 ? dayjs(dbForm.value.expiryTime) : null), @@ -867,6 +886,107 @@ watch( + + + @@ -1623,6 +1743,17 @@ watch( border-bottom: 1px solid rgba(128, 128, 128, 0.15); } +.fallbacks-header { + display: flex; + align-items: center; + gap: 8px; + margin: 8px 0; +} +.fallbacks-title { + font-weight: 500; + flex: 1; +} + .wg-peer { margin-top: 4px; } diff --git a/frontend/src/pages/inbounds/InboundInfoModal.vue b/frontend/src/pages/inbounds/InboundInfoModal.vue index 2abe1e01..66015582 100644 --- a/frontend/src/pages/inbounds/InboundInfoModal.vue +++ b/frontend/src/pages/inbounds/InboundInfoModal.vue @@ -1,7 +1,7 @@