Просмотр исходного кода

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.
MHSanaei 1 день назад
Родитель
Сommit
b10a9f1de7

+ 3 - 1
frontend/src/composables/useNodeList.js

@@ -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 };
 }

+ 1 - 1
frontend/src/pages/inbounds/InboundFormModal.vue

@@ -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
frontend/src/pages/inbounds/InboundList.vue

@@ -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
frontend/src/pages/inbounds/InboundsPage.vue

@@ -66,7 +66,7 @@ useWebSocket({
 const { isMobile } = useMediaQuery();
 // Node list lives on the central panel; the Inbounds page consumes
 // the id→node 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"