fix(browser): ensure connection before mount loads

This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-06-29 12:05:46 +08:00
1 parent ded6e002b5
commit 8094f4901d
9 files changed
+89 -9

No files matched your search

@@ -13,6 +13,7 @@ import ErrorBanner from "@/components/ui/ErrorBanner.vue";
import DataGrid from "@/components/grid/DataGrid.vue";
import QueryLoadingState from "@/components/common/QueryLoadingState.vue";
import * as api from "@/lib/api";
import { useConnectionStore } from "@/stores/connectionStore";
import { clampSearchSplitWidth } from "@/lib/dataGridSearchSplit";
import { documentViewerFontStyle } from "@/lib/documentViewerFontStyle";
import { buildDocumentFilterCondition, combineDocumentFilterConditions, currentDocumentFilterJson, defaultDocumentFilterRule, documentFilterModeNeedsValue, documentFilterModeOptions, documentStoreProviderFor, type DocumentFilterMode, type DocumentFilterRule } from "@/lib/documentStoreProvider";
@@ -28,6 +29,7 @@ import "splitpanes/dist/splitpanes.css";
const { t } = useI18n();
const settingsStore = useSettingsStore();
const connectionStore = useConnectionStore();
const props = defineProps<{
connectionId: string;
@@ -714,7 +716,14 @@ function highlightedJson(json: string): string {
});
}
onMounted(load);
onMounted(async () => {
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
load();
});
onBeforeUnmount(() => {
if (documentLoadExecutionId.value) void api.cancelQuery(documentLoadExecutionId.value);
stopDocumentLoadingTimer();
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useConnectionStore } from "@/stores/connectionStore";
import { ChevronDown, ChevronRight, FolderClosed, FolderOpen, KeyRound, Loader2, Plus, RefreshCw, Search, Trash2 } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -84,6 +85,7 @@ const props = withDefaults(
const { t } = useI18n();
const { toast } = useToast();
const connectionStore = useConnectionStore();
const searchInputRef = ref<HTMLInputElement>();
const prefix = ref("");
const keys = ref<KvKeySummary[]>([]);
@@ -539,7 +541,14 @@ function refresh(): boolean {
watch(
() => props.connectionId,
() => void loadKeys(true),
async () => {
try {
await connectionStore.ensureConnected(props.connectionId);
} catch {
// Connection failed — loadKeys will show the error state
}
void loadKeys(true);
},
);
watch(
@@ -552,7 +561,14 @@ watch(
{ immediate: true },
);
onMounted(() => void loadKeys(true));
onMounted(async () => {
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
void loadKeys(true);
});
defineExpose({ focusSearch, refresh });
</script>
@@ -3,6 +3,7 @@ import { formatError } from "@/lib/errorUtils";
import { ref, computed, onMounted, watch } from "vue";
import type { MqClusterInfo, TopicInfo } from "@/types/mq";
import { mqTestConnection } from "@/lib/api";
import { useConnectionStore } from "@/stores/connectionStore";
import { mqClusterOptionsFromExtra } from "@/lib/mqTenantForm";
import TenantsPanel from "./TenantsPanel.vue";
import NamespacesPanel from "./NamespacesPanel.vue";
@@ -21,6 +22,7 @@ interface Props {
}
const props = defineProps<Props>();
const connectionStore = useConnectionStore();
type MqTab = "tenants" | "namespaces" | "topics" | "subscriptions" | "monitoring" | "clients" | "policies" | "permissions" | "raw";
@@ -168,7 +170,12 @@ watch(
);
// Lifecycle
onMounted(() => {
onMounted(async () => {
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
loadClusterInfo();
});
</script>
@@ -13,6 +13,7 @@ import EditorSearchPanel from "@/components/editor/EditorSearchPanel.vue";
import NacosConfigDiffDialog from "@/components/nacos/NacosConfigDiffDialog.vue";
import NacosConfigHistoryDialog from "@/components/nacos/NacosConfigHistoryDialog.vue";
import { useToast } from "@/composables/useToast";
import { useConnectionStore } from "@/stores/connectionStore";
import { useI18n } from "vue-i18n";
import * as api from "@/lib/api";
import { buildNacosConfigDeleteConfirm, buildNacosConfigExportFileName, buildNacosConfigHistoryRollbackConfirm, buildNacosInstanceConfirm, createNacosSaveAsCopy, resolveNacosConfigCopyText } from "@/lib/nacosAdmin";
@@ -39,6 +40,7 @@ type AdminTab = "configs" | "services";
const { toast } = useToast();
const { t } = useI18n();
const settingsStore = useSettingsStore();
const connectionStore = useConnectionStore();
const { isDark } = useTheme();
const activeTab = ref<AdminTab>("configs");
const connectionInfo = ref<NacosConnectionInfo | null>(null);
@@ -839,12 +841,22 @@ watch(
originalConfigContent.value = "";
destroyConfigEditor();
selectedService.value = null;
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
await loadInfo();
await Promise.all([loadConfigsWithRetry(1), loadServicesWithRetry(1)]);
},
);
onMounted(async () => {
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
await loadInfo();
await Promise.all([loadConfigsWithRetry(1), loadServicesWithRetry(1)]);
});
@@ -1236,11 +1236,16 @@ onBeforeUnmount(() => {
watch(
() => [props.connection.id, props.database, props.schema] as const,
() => {
async () => {
selectedSchema.value = props.schema;
userHasSelectedFilter.value = false;
objectFilter.value = "all";
clearTableSelection();
try {
await connectionStore.ensureConnected(props.connection.id);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connection.id, e);
}
void reload();
},
{ immediate: true },
@@ -255,6 +255,11 @@ watch(autoRefreshInterval, () => {
// Lifecycle
// ---------------------------------------------------------------------------
onMounted(async () => {
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
await fetchClusterNodes();
await fetchInfo();
});
@@ -921,12 +921,29 @@ function onCommandInputKeydown(event: KeyboardEvent) {
}
}
onMounted(() => {
onMounted(async () => {
resumeRedisBrowserBackgroundWork();
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
void loadKeys();
});
onActivated(resumeRedisBrowserBackgroundWork);
onActivated(async () => {
resumeRedisBrowserBackgroundWork();
// Ensure the connection is still alive after reactivation (e.g. tab switch).
// If keys failed to load previously (empty list), retry loading.
try {
await connectionStore.ensureConnected(props.connectionId);
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
if (flatKeys.value.length === 0 && !loading.value) {
void loadKeys();
}
});
onDeactivated(pauseRedisBrowserBackgroundWork);
@@ -59,9 +59,10 @@ const showClientColumns = computed(() => {
onMounted(async () => {
if (connectionMode.value === "cluster") {
try {
await connectionStore.ensureConnected(props.connectionId);
nodes.value = await api.redisClusterMasterNodes(props.connectionId);
} catch {
// Silently fail — nodes list is best-effort
} catch (e) {
console.warn("[DBX] ensureConnected failed for", props.connectionId, e);
}
}
});
@@ -494,30 +494,38 @@ async function toggle() {
await connectionStore.loadDatabases(node.connectionId);
}
} else if (node.type === "redis-db" && node.connectionId && node.database) {
await connectionStore.ensureConnected(node.connectionId);
const tabTitle = `${connectionStore.getConfig(node.connectionId)?.name || "Redis"}:db${node.database}`;
queryStore.createTab(node.connectionId, node.database, tabTitle, "redis");
} else if (node.type === "mq-tenant" && node.connectionId) {
await connectionStore.ensureConnected(node.connectionId);
queryStore.openMqAdmin(node.connectionId, { tenant: node.mqTenant || node.label });
} else if (node.type === "nacos-namespace" && node.connectionId) {
await connectionStore.ensureConnected(node.connectionId);
queryStore.openNacosAdmin(node.connectionId, { namespace: node.nacosNamespace || "", namespaceName: node.nacosNamespaceName || node.label });
} else if (node.type === "etcd-root" && node.connectionId) {
await connectionStore.ensureConnected(node.connectionId);
const tabTitle = `${connectionStore.getConfig(node.connectionId)?.name || "etcd"}:keys`;
queryStore.createTab(node.connectionId, "", tabTitle, "etcd");
refreshActiveKvBrowserAfterOpen("etcd", node.connectionId);
} else if (node.type === "zookeeper-root" && node.connectionId) {
await connectionStore.ensureConnected(node.connectionId);
const tabTitle = `${connectionStore.getConfig(node.connectionId)?.name || "ZooKeeper"}:keys`;
queryStore.createTab(node.connectionId, "", tabTitle, "zookeeper");
refreshActiveKvBrowserAfterOpen("zookeeper", node.connectionId);
} else if (node.type === "user-admin" && node.connectionId) {
await connectionStore.ensureConnected(node.connectionId);
queryStore.openUserAdmin(node.connectionId);
} else if (node.type === "mongo-db" && node.connectionId && node.database) {
await connectionStore.loadMongoCollections(node.connectionId, node.database);
} else if (node.type === "mongo-collection" && node.connectionId && node.database) {
await connectionStore.loadTableGroups(node.connectionId, node.database, node.label, node.schema, node.id);
} else if (node.type === "elasticsearch-index" && node.connectionId) {
await connectionStore.ensureConnected(node.connectionId);
const tab = queryStore.createTab(node.connectionId, node.database || "default", node.label, "mongo");
queryStore.updateSql(tab, node.label);
} else if (node.type === "vector-collection" && node.connectionId) {
await connectionStore.ensureConnected(node.connectionId);
const collectionRef = node.id.includes("__vector_collection:") ? node.id.split("__vector_collection:").pop() || node.label : node.label;
const tab = queryStore.createTab(node.connectionId, node.database || "default", node.label, "vector");
queryStore.updateSql(tab, collectionRef);