fix(inbounds): hide node UI when no enabled node exists

The Deploy-to selector, node column, node stat row, and node filter all
appeared whenever a node row existed in the DB. Local-only deployments
with no nodes (or only disabled nodes) saw a dropdown that only had
"Local Panel" and a filter that did nothing.

useNodeList now exposes hasActive (any node with enable === true).
Inbounds form and list gate node UI on hasActive instead of map size.
This commit is contained in:
MHSanaei
2026-05-13 17:42:40 +02:00
parent 4399fe2a85
commit b10a9f1de7
4 changed files with 11 additions and 7 deletions
+3 -1
View File
@@ -36,7 +36,9 @@ export function useNodeList() {
return n != null && n.enable && n.status === 'online';
}
const hasActive = computed(() => nodes.value.some((n) => n.enable));
onMounted(refresh);
return { nodes, fetched, refresh, byId, nameFor, isOnline };
return { nodes, fetched, refresh, byId, nameFor, isOnline, hasActive };
}
@@ -582,7 +582,7 @@ watch(
<a-form-item :label="t('pages.inbounds.remark')">
<a-input v-model:value="dbForm.remark" />
</a-form-item>
<a-form-item :label="t('pages.inbounds.deployTo')">
<a-form-item v-if="selectableNodes.length > 0" :label="t('pages.inbounds.deployTo')">
<a-select v-model:value="dbForm.nodeId" :disabled="mode === 'edit'"
:placeholder="t('pages.inbounds.localPanel')" allow-clear>
<a-select-option :value="null">{{ t('pages.inbounds.localPanel') }}</a-select-option>
+4 -3
View File
@@ -49,6 +49,7 @@ const props = defineProps({
// Map node id -> node row, supplied by the parent page so each
// inbound row can render its node name without an extra fetch.
nodesById: { type: Map, default: () => new Map() },
hasActiveNode: { type: Boolean, default: false },
});
const emit = defineEmits([
@@ -234,7 +235,7 @@ const desktopColumns = computed(() => {
if (hasAnyRemark.value) {
cols.push(sortableCol({ title: t('pages.inbounds.remark'), dataIndex: 'remark', key: 'remark', align: 'center', width: 60 }, 'remark'));
}
if (props.nodesById.size > 0) {
if (props.hasActiveNode) {
cols.push(sortableCol({ title: t('pages.inbounds.node'), key: 'node', align: 'center', width: 60 }, 'node'));
}
cols.push(
@@ -374,7 +375,7 @@ function showQrCodeMenu(dbInbound) {
{{ protocol }}
</a-select-option>
</a-select>
<a-select v-if="nodeOptions.length > 0" v-model:value="nodeFilter" allow-clear
<a-select v-if="hasActiveNode && nodeOptions.length > 0" v-model:value="nodeFilter" allow-clear
:placeholder="t('pages.inbounds.node')" :size="isMobile ? 'small' : 'middle'" :style="{ width: '170px' }">
<a-select-option v-for="node in nodeOptions" :key="node.value" :value="node.value">
{{ node.label }}
@@ -466,7 +467,7 @@ function showQrCodeMenu(dbInbound) {
<span class="stat-label">{{ t('pages.inbounds.port') }}</span>
<a-tag>{{ record.port }}</a-tag>
</div>
<div v-if="nodesById.size > 0" class="stat-row">
<div v-if="hasActiveNode" class="stat-row">
<span class="stat-label">{{ t('pages.inbounds.node') }}</span>
<a-tag v-if="record.nodeId == null" color="default">
{{ t('pages.inbounds.localPanel') }}
+3 -2
View File
@@ -66,7 +66,7 @@ useWebSocket({
const { isMobile } = useMediaQuery();
// Node list lives on the central panel; the Inbounds page consumes
// the idnode map for the new "Node" column. Fetched once on mount.
const { byId: nodesById } = useNodeList();
const { byId: nodesById, hasActive: hasActiveNode } = useNodeList();
const basePath = window.X_UI_BASE_PATH || '';
const requestUri = window.location.pathname;
@@ -647,7 +647,8 @@ function onRowAction({ key, dbInbound }) {
<InboundList :db-inbounds="dbInbounds" :client-count="clientCount" :online-clients="onlineClients"
:last-online-map="lastOnlineMap" :is-dark-theme="themeState.isDark" :expire-diff="expireDiff"
:traffic-diff="trafficDiff" :page-size="pageSize" :is-mobile="isMobile"
:sub-enable="subSettings.enable" :nodes-by-id="nodesById" @refresh="refresh"
:sub-enable="subSettings.enable" :nodes-by-id="nodesById" :has-active-node="hasActiveNode"
@refresh="refresh"
@add-inbound="onAddInbound" @general-action="onGeneralAction" @row-action="onRowAction"
@edit-client="onEditClient" @qrcode-client="onQrcodeClient" @info-client="onInfoClient"
@reset-traffic-client="onResetTrafficClient" @delete-client="onDeleteClient"