refactor(inbounds): drop manual Fallbacks UI from inbound modal

The PortFallback protocol type now covers the common
VLESS-master-plus-children case with auto-wired dests, so the manual
Fallbacks editor (showFallbacks block in the Protocol tab) is mostly
redundant. Removed:

- the v-if="showFallbacks" template block (SNI/ALPN/Path/dest/PROXY rows)
- the showFallbacks computed
- the addFallback / delFallback helpers
- the .fallbacks-header / .fallbacks-title styles
- the showFallbacks gate from hasProtocolTabContent (so Trojan-over-TCP
  no longer shows an empty Protocol tab)

Power users who need a non-inbound fallback dest (nginx, static site)
can still author settings.fallbacks via the Advanced JSON tab.
This commit is contained in:
MHSanaei 2026-05-17 12:07:00 +02:00
parent fc8765917e
commit 418acf8cfa
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
2 changed files with 1 additions and 117 deletions

View file

@ -64,6 +64,7 @@ watch(
form.inboundIds = Array.isArray(props.attachedIds) ? [...props.attachedIds] : [];
void loadIps();
} else {
form.email = RandomUtil.randomLowerAndNum(9);
form.uuid = RandomUtil.randomUUID();
form.subId = RandomUtil.randomLowerAndNum(16);
form.password = RandomUtil.randomLowerAndNum(16);

View file

@ -204,22 +204,9 @@ const canEnableStream = computed(() => inbound.value?.canEnableStream?.() === tr
const canEnableTls = computed(() => inbound.value?.canEnableTls?.() === true);
const canEnableReality = computed(() => inbound.value?.canEnableReality?.() === 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;
});
const hasProtocolTabContent = computed(() => {
if (!inbound.value) return false;
if (isVlessLike.value) return true;
if (showFallbacks.value) return true;
switch (inbound.value.protocol) {
case Protocols.SHADOWSOCKS:
case Protocols.HTTP:
@ -233,13 +220,6 @@ const hasProtocolTabContent = computed(() => {
}
});
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),
@ -1179,91 +1159,6 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream'));
</div>
</a-form>
<!-- ============== Fallbacks (VLESS/Trojan over TCP) ============== -->
<template v-if="showFallbacks">
<a-divider style="margin: 12px 0" />
<div class="fallbacks-header">
<a-tooltip
title="Route incoming TLS traffic to a backend when it doesn't match a valid VLESS/Trojan handshake. Match by SNI, ALPN, and HTTP path; the most precise rule wins. Fallbacks require TCP+TLS transport.">
<span class="fallbacks-title">
Fallbacks ({{ inbound.settings.fallbacks.length }})
</span>
</a-tooltip>
<a-button type="primary" size="small" @click="addFallback">
<template #icon>
<PlusOutlined />
</template>
Add
</a-button>
</div>
<a-form v-for="(fallback, idx) in inbound.settings.fallbacks" :key="idx" :colon="false"
:label-col="{ sm: { span: 8 } }" :wrapper-col="{ sm: { span: 14 } }">
<a-divider style="margin: 0">
Fallback {{ idx + 1 }}
<DeleteOutlined class="danger-icon" @click="delFallback(idx)" />
</a-divider>
<a-form-item>
<template #label>
<a-tooltip title="Match TLS SNI (server name). Leave empty to match any SNI.">
SNI
</a-tooltip>
</template>
<a-input v-model:value.trim="fallback.name" placeholder="any (leave empty)" />
</a-form-item>
<a-form-item>
<template #label>
<a-tooltip
title="Match TLS ALPN. 'any' = no ALPN constraint. Use h2/http/1.1 split when the inbound advertises both.">
ALPN
</a-tooltip>
</template>
<a-select v-model:value="fallback.alpn">
<a-select-option value="">any</a-select-option>
<a-select-option value="h2">h2</a-select-option>
<a-select-option value="http/1.1">http/1.1</a-select-option>
</a-select>
</a-form-item>
<a-form-item :validate-status="fallback.path && !fallback.path.startsWith('/') ? 'error' : ''"
:help="fallback.path && !fallback.path.startsWith('/') ? 'Path must start with /' : ''">
<template #label>
<a-tooltip
title="Match the HTTP request path of the first packet. Must start with '/'. Leave empty to match any.">
Path
</a-tooltip>
</template>
<a-input v-model:value.trim="fallback.path" placeholder="any (leave empty) or /ws" />
</a-form-item>
<a-form-item :validate-status="!fallback.dest ? 'error' : ''"
:help="!fallback.dest ? 'Destination is required' : ''">
<template #label>
<a-tooltip
title="Where matching traffic is forwarded. Accepts a port number (80), an addr:port (127.0.0.1:8080), or a Unix socket path (/dev/shm/x.sock or @abstract).">
Destination
</a-tooltip>
</template>
<a-input v-model:value.trim="fallback.dest" placeholder="80 | 127.0.0.1:8080 | /dev/shm/x.sock" />
</a-form-item>
<a-form-item>
<template #label>
<a-tooltip
title="PROXY protocol version sent to the destination. Off (0) for plain TCP; v1/v2 to preserve client IP if the backend supports it.">
PROXY
</a-tooltip>
</template>
<a-select v-model:value="fallback.xver">
<a-select-option :value="0">Off</a-select-option>
<a-select-option :value="1">v1</a-select-option>
<a-select-option :value="2">v2</a-select-option>
</a-select>
</a-form-item>
</a-form>
</template>
</a-tab-pane>
<!-- ============================== STREAM ============================== -->
@ -2131,18 +2026,6 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream'));
margin-top: 6px;
}
.fallbacks-header {
display: flex;
align-items: center;
gap: 8px;
margin: 8px 0;
}
.fallbacks-title {
font-weight: 500;
flex: 1;
}
.wg-peer {
margin-top: 4px;
}