From c754b852951bb364dfe2e5ab43b67760332d86b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Thu, 6 Aug 2026 12:16:52 +0800 Subject: [PATCH] feat(nacos): improve service and instance management --- .../connection/ConnectionDialog.vue | 651 +++--- .../components/nacos/NacosAdminConsole.vue | 1025 ++++++++- .../nacos/NacosConfigBatchDialog.vue | 3 +- .../__tests__/NacosAdminConsoleLayout.spec.ts | 51 + apps/desktop/src/i18n/locales/en.ts | 93 + apps/desktop/src/i18n/locales/es.ts | 93 + apps/desktop/src/i18n/locales/it.ts | 93 + apps/desktop/src/i18n/locales/ja.ts | 93 + apps/desktop/src/i18n/locales/ko.ts | 93 + apps/desktop/src/i18n/locales/pt-BR.ts | 93 + apps/desktop/src/i18n/locales/zh-CN.ts | 97 +- apps/desktop/src/i18n/locales/zh-TW.ts | 93 + .../connectionDialogScrolling.spec.ts | 10 +- .../connection/nacosConnectionDialog.spec.ts | 66 + .../lib/__tests__/nacos/nacosAdmin.spec.ts | 4 +- .../nacos/nacosNamespaceVisibility.spec.ts | 33 + apps/desktop/src/lib/backend/api.ts | 6 + apps/desktop/src/lib/backend/http.ts | 32 +- apps/desktop/src/lib/backend/nacos-tauri.ts | 32 +- .../__tests__/nacosServiceManagement.spec.ts | 34 + apps/desktop/src/lib/nacos/nacosAdmin.ts | 14 +- .../src/lib/nacos/nacosNamespaceVisibility.ts | 51 + .../src/lib/nacos/nacosServiceManagement.ts | 57 + apps/desktop/src/stores/connectionStore.ts | 4 +- apps/desktop/src/types/nacos.ts | 67 +- crates/dbx-core/src/nacos/batch.rs | 22 +- crates/dbx-core/src/nacos/config.rs | 7 +- crates/dbx-core/src/nacos/http.rs | 1942 ++++++++++++++--- crates/dbx-core/src/nacos/port.rs | 21 +- crates/dbx-core/src/nacos/search.rs | 20 +- crates/dbx-core/src/nacos/service.rs | 139 +- crates/dbx-core/src/nacos/types.rs | 242 +- crates/dbx-mcp/src/backend.rs | 7 +- crates/dbx-mcp/tests/local.rs | 9 +- crates/dbx-web/src/main.rs | 6 + crates/dbx-web/src/routes/nacos.rs | 117 +- src-tauri/src/commands/nacos_cmd.rs | 56 +- src-tauri/src/lib.rs | 6 + 38 files changed, 4787 insertions(+), 695 deletions(-) create mode 100644 apps/desktop/src/lib/__tests__/connection/nacosConnectionDialog.spec.ts create mode 100644 apps/desktop/src/lib/__tests__/nacos/nacosNamespaceVisibility.spec.ts create mode 100644 apps/desktop/src/lib/nacos/__tests__/nacosServiceManagement.spec.ts create mode 100644 apps/desktop/src/lib/nacos/nacosNamespaceVisibility.ts create mode 100644 apps/desktop/src/lib/nacos/nacosServiceManagement.ts diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index f86036607..dd7aca7a5 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -19,7 +19,7 @@ import type { InfluxDbExternalConfig, InfluxDbVersion } from "@/types/influxdb"; import type { VictoriaMetricsExternalConfig } from "@/types/victoriametrics"; import type { MqAdminConfig, MqAuth, MqSystemKind } from "@/types/mq"; import type { MqttConnectionConfig } from "@/types/mqtt"; -import type { NacosAdminConfig, NacosAuthConfig, NacosImplementation, NacosMetricsMode, NacosRNacosConsoleAuth, NacosVersionMode } from "@/types/nacos"; +import type { NacosAdminConfig, NacosAuthConfig, NacosImplementation, NacosMetricsMode, NacosNamespaceInfo, NacosRNacosConsoleAuth, NacosVersionMode } from "@/types/nacos"; import { CONNECTION_ATTEMPT_CANCELLED_MESSAGE, useConnectionStore } from "@/stores/connectionStore"; import { useTunnelProfileStore } from "@/stores/tunnelProfileStore"; import { detachTunnelProfileLayer, tunnelProfileReferenceLayer, tunnelProfileSummary } from "@/lib/connection/tunnelProfiles"; @@ -64,7 +64,8 @@ import { normalizeRabbitmqAddresses } from "@/lib/connection/rabbitmqAddresses"; import { detectMqUiAuthKind, isMqAuthKindAllowedForSystem, type MqUiAuthKind } from "@/lib/connection/mqAuth"; import { driverInstallProgressChannel, driverInstallProgressPercent, isDriverInstallProgressForOperation, type DriverInstallProgress } from "@/lib/connection/driverInstallProgressUi"; import { requiresSqlServerLegacyCompatibilityComponent, setSqlServerLegacyCompatibilityConfig, sqlServerUsesLegacyCompatibility, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility"; -import { nacosMetricsCandidates, normalizeNacosEndpoint, normalizeNacosMetricsUrl } from "@/lib/nacos/nacosAdmin"; +import { normalizeNacosEndpoint, normalizeNacosMetricsUrl } from "@/lib/nacos/nacosAdmin"; +import { normalizeNacosNamespaceSelection, normalizeNacosNamespacesForDisplay } from "@/lib/nacos/nacosNamespaceVisibility"; import { ArrowLeft, ArrowDown, @@ -115,6 +116,7 @@ export type ConfigTab = "connection" | "advanced" | "tls" | "transport"; type ProductionScope = "connection" | "databases"; type MqTokenSigningMode = "none" | "hs256" | "rs256"; type NacosAuthKind = NacosAuthConfig["kind"]; +type NacosConnectionProfile = "v2" | "v3" | "rnacos"; type DremioConnectionMode = "arrow-flight-sql" | "legacy"; type JdbcDriverSelectItem = { id: string; @@ -128,6 +130,11 @@ const DREMIO_ARROW_FLIGHT_SQL_JDBC_DRIVER_CLASS = "org.apache.arrow.driver.jdbc. const DREMIO_LEGACY_JDBC_URL = "jdbc:dremio:direct=127.0.0.1:31010"; const DREMIO_LEGACY_JDBC_DRIVER_CLASS = "com.dremio.jdbc.Driver"; const DEFAULT_SSH_USER = "root"; +const NACOS_CONNECTION_PROFILES: ReadonlyArray<{ value: NacosConnectionProfile; title: string }> = [ + { value: "v2", title: "Nacos 2.x" }, + { value: "v3", title: "Nacos 3.x" }, + { value: "rnacos", title: "r-nacos" }, +]; type LegacyTransportFields = { ssh_enabled?: boolean; @@ -150,7 +157,6 @@ type LegacyTransportFields = { type LegacyConnectionConfig = ConnectionConfig & LegacyTransportFields; type ConnectionForm = Omit; type ConnectionTestState = ConnectionTestResult & { ok: boolean }; -type SuccessfulConnectionTest = { result: ConnectionTestResult; config: ConnectionConfig }; const { t } = useI18n(); const { toast } = useToast(); @@ -201,6 +207,12 @@ const visibleDatabaseSelection = ref>(new Set()); const visibleDatabaseSearchText = ref(""); const visibleDatabaseError = ref(""); const visibleDatabaseShowSystem = ref(false); +const showVisibleNacosNamespacesDialog = ref(false); +const isLoadingVisibleNacosNamespaces = ref(false); +const visibleNacosNamespaces = ref([]); +const visibleNacosNamespaceSelection = ref>(new Set()); +const visibleNacosNamespaceSearchText = ref(""); +const visibleNacosNamespaceError = ref(""); const showProductionDatabasesDialog = ref(false); const isLoadingProductionDatabases = ref(false); const productionDatabaseNames = ref([]); @@ -673,11 +685,11 @@ const mqKafkaSaslMechanismOptions = [ { value: "SCRAM-SHA-512", label: "SCRAM-SHA-512" }, ]; const nacosImplementation = ref("nacos"); -const nacosVersionMode = ref("auto"); +// Nacos 2 and 3 expose different API planes (and Nacos 3 commonly needs a +// separate Console address). New connections must therefore choose an +// explicit version instead of relying on endpoint-shape guessing. +const nacosVersionMode = ref("v2"); const nacosServerAddr = ref(""); -const nacosNamespace = ref(""); -const nacosContextPath = ref(""); -const nacosContextPathCustomized = ref(false); const nacosRNacosConsoleAddr = ref(""); const nacosHistoryEnabled = ref(false); const nacosConsoleAuthKind = ref("inherit"); @@ -706,63 +718,20 @@ const mqttTlsSkipVerify = ref(false); const mqttKeepAliveSecs = ref(60); const mqttConnectTimeoutSecs = ref(30); const mqttMaxPacketSizeBytes = ref(16 * 1024 * 1024); - -const nacosPrimaryAddressLabel = computed(() => { - if (nacosImplementation.value === "rnacos") return t("connection.nacosPrimaryAddressRNacos"); - if (nacosVersionMode.value === "v2") return t("connection.nacosPrimaryAddressV2"); - if (nacosVersionMode.value === "v3") return t("connection.nacosPrimaryAddressV3"); - return t("connection.nacosPrimaryAddressAuto"); -}); const nacosPrimaryAddressPlaceholder = computed(() => { return "http://127.0.0.1:8848/nacos"; }); -const nacosNormalizedPreview = computed(() => { - if (!nacosServerAddr.value.trim()) return ""; +const nacosV3AdminEndpointWarning = computed(() => { + if (nacosImplementation.value !== "nacos" || nacosVersionMode.value !== "v3" || !nacosServerAddr.value.trim()) return ""; try { - const normalized = normalizeNacosEndpoint(nacosServerAddr.value, { - implementation: nacosImplementation.value, - versionMode: nacosVersionMode.value, - contextPath: nacosContextPathCustomized.value ? nacosContextPath.value : undefined, - }); - return `${normalized.serverAddr}${normalized.contextPath || ""}`; + const url = new URL(nacosServerAddr.value.trim()); + if (url.port === "8080") { + return t("nacos.nacosV3ConsolePortWarning"); + } } catch { - return ""; - } -}); -const nacosEffectiveContextPath = computed(() => { - if (!nacosServerAddr.value.trim()) { - return nacosContextPathCustomized.value ? nacosContextPath.value.trim() || "/" : "/nacos"; - } - try { - const normalized = normalizeNacosEndpoint(nacosServerAddr.value, { - implementation: nacosImplementation.value, - versionMode: nacosVersionMode.value, - contextPath: nacosContextPathCustomized.value ? nacosContextPath.value : undefined, - }); - return normalized.contextPath || "/"; - } catch { - return nacosContextPathCustomized.value ? nacosContextPath.value.trim() || "/" : "/"; - } -}); -const nacosContextPathInput = computed({ - get: () => (nacosContextPathCustomized.value ? nacosContextPath.value : nacosEffectiveContextPath.value), - set: (value: string) => { - nacosContextPathCustomized.value = true; - nacosContextPath.value = value; - }, -}); -const nacosMetricsAutoPreview = computed(() => { - if (!nacosNormalizedPreview.value) return ""; - try { - const normalized = normalizeNacosEndpoint(nacosServerAddr.value, { - implementation: nacosImplementation.value, - versionMode: nacosVersionMode.value, - contextPath: nacosContextPathCustomized.value ? nacosContextPath.value : undefined, - }); - return nacosMetricsCandidates(normalized.serverAddr, normalized.contextPath, nacosImplementation.value).join(" ยท "); - } catch { - return ""; + // The normal form validation will report an invalid URL on save. } + return ""; }); const nacosMetricsUrlError = computed(() => { if (nacosMetricsMode.value !== "custom") return ""; @@ -774,13 +743,22 @@ const nacosMetricsUrlError = computed(() => { } }); -function resetNacosContextPathCustomization() { - nacosContextPathCustomized.value = false; - nacosContextPath.value = ""; +const nacosConnectionProfile = computed(() => { + if (nacosImplementation.value === "rnacos") return "rnacos"; + return nacosVersionMode.value === "v3" ? "v3" : "v2"; +}); + +function selectNacosConnectionProfile(profile: NacosConnectionProfile) { + if (profile === "rnacos") { + nacosImplementation.value = "rnacos"; + return; + } + nacosImplementation.value = "nacos"; + nacosVersionMode.value = profile; } watch(nacosImplementation, (implementation) => { - if (implementation === "rnacos") nacosVersionMode.value = "auto"; + if (implementation === "rnacos") nacosVersionMode.value = "v2"; if (implementation !== "rnacos") nacosHistoryEnabled.value = false; }); @@ -1211,11 +1189,12 @@ watch(mqAuthKind, (kind) => { function resetNacosFields(config?: Partial) { nacosImplementation.value = config?.implementation || (config?.rnacosConsoleAddr ? "rnacos" : "nacos"); - nacosVersionMode.value = config?.versionMode || "auto"; - nacosServerAddr.value = config?.serverAddr?.trim() || ""; - nacosNamespace.value = config?.namespace || ""; - nacosContextPath.value = config?.contextPath || ""; - nacosContextPathCustomized.value = !!config?.contextPath; + // Saved `auto` profiles are legacy records. The form always saves an + // explicit selection and no longer relies on a separate Console address. + nacosVersionMode.value = config?.versionMode === "v3" ? "v3" : "v2"; + const serverAddr = config?.serverAddr?.trim() || ""; + const contextPath = config?.contextPath?.trim() || ""; + nacosServerAddr.value = serverAddr && contextPath && contextPath !== "/" && !serverAddr.endsWith(contextPath) ? `${serverAddr.replace(/\/+$/, "")}/${contextPath.replace(/^\/+/, "")}` : serverAddr; nacosRNacosConsoleAddr.value = config?.rnacosConsoleAddr?.trim() || ""; nacosHistoryEnabled.value = config?.rnacosHistoryEnabled ?? !!config?.rnacosConsoleAddr; const consoleAuth = config?.rnacosConsoleAuth || { kind: "inherit" }; @@ -1507,13 +1486,14 @@ function buildNacosAdminConfig(): NacosAdminConfig { const normalized = normalizeNacosEndpoint(primaryAddress, { implementation: nacosImplementation.value, versionMode: nacosVersionMode.value, - contextPath: nacosContextPathCustomized.value ? nacosContextPath.value : undefined, + contextPath: undefined, }); if (nacosImplementation.value === "rnacos" && normalized.warnings.length) { throw new Error(t("connection.nacosRNacosOpenApiRequired")); } - const rnacosConsoleConfigured = nacosImplementation.value === "rnacos" && !!nacosRNacosConsoleAddr.value.trim(); - if (nacosImplementation.value === "rnacos" && nacosHistoryEnabled.value && !rnacosConsoleConfigured) { + const rnacosExtensionsEnabled = nacosImplementation.value === "rnacos" && nacosHistoryEnabled.value; + const rnacosConsoleConfigured = rnacosExtensionsEnabled && !!nacosRNacosConsoleAddr.value.trim(); + if (rnacosExtensionsEnabled && !rnacosConsoleConfigured) { throw new Error(t("connection.nacosRNacosConsoleUrlRequired")); } let rnacosConsoleAuth: NacosRNacosConsoleAuth | undefined; @@ -1541,9 +1521,8 @@ function buildNacosAdminConfig(): NacosAdminConfig { implementation: nacosImplementation.value, versionMode: nacosImplementation.value === "nacos" ? nacosVersionMode.value : undefined, serverAddr: normalized.serverAddr, - namespace: nacosNamespace.value.trim() || undefined, contextPath: normalized.contextPath || undefined, - rnacosConsoleAddr: nacosImplementation.value === "rnacos" ? nacosRNacosConsoleAddr.value.trim() || undefined : undefined, + rnacosConsoleAddr: rnacosExtensionsEnabled ? nacosRNacosConsoleAddr.value.trim() || undefined : undefined, rnacosHistoryEnabled: nacosImplementation.value === "rnacos" ? nacosHistoryEnabled.value : undefined, rnacosConsoleAuth, auth: buildNacosAuth(), @@ -1554,49 +1533,6 @@ function buildNacosAdminConfig(): NacosAdminConfig { }; } -function dockerNacosConsoleFallbackUrl(serverAddr: string): string | null { - let parsed: URL; - try { - parsed = new URL(serverAddr); - } catch { - return null; - } - const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80"); - const host = parsed.hostname.toLowerCase(); - if (port !== "8848" || !["127.0.0.1", "localhost", "::1"].includes(host)) return null; - - parsed.port = "8085"; - return parsed.toString().replace(/\/$/, ""); -} - -function isNacosAdminEndpointNotFound(message: string): boolean { - return /Nacos admin endpoint was not found/i.test(message); -} - -async function tryNacosDockerConsoleFallback(config: ConnectionConfig, originalError: string, runId: number): Promise { - if (config.db_type !== "nacos" || !isNacosAdminEndpointNotFound(originalError)) return null; - - const fallbackUrl = dockerNacosConsoleFallbackUrl(nacosServerAddr.value); - if (!fallbackUrl || fallbackUrl === nacosServerAddr.value.trim()) return null; - - const previousUrl = nacosServerAddr.value; - nacosServerAddr.value = fallbackUrl; - try { - const fallbackConfig = connectionConfigForSubmit(config.id, config.name); - const result = await testConnectionWithTimeout(fallbackConfig, runId); - return { - config: fallbackConfig, - result: { - ...result, - message: `${result.message} ${t("connection.nacosConsoleUrlAutoAdjusted", { from: previousUrl.trim(), to: fallbackUrl })}`, - }, - }; - } catch { - nacosServerAddr.value = previousUrl; - return null; - } -} - function errorMessage(error: unknown): string { if (error instanceof Error) return error.message; return String(error); @@ -2858,7 +2794,8 @@ const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" && const shouldShowAgentDriverInstallHint = computed(() => showAgentDriverInstallHint(form.value.db_type, agentDrivers.value, form.value.driver_profile)); const h2DriverMissing = computed(() => form.value.db_type === "h2" && isH2FileMode.value && agentDrivers.value.find((d) => d.db_type === "h2")?.installed !== true); const agentDriverFocus = computed(() => ({ target: "driver", driver: agentDriverInstallKey(form.value.db_type, form.value.driver_profile) })); -const canChooseVisibleDatabases = computed(() => connectionCanChooseVisibleDatabases(form.value)); +const canChooseVisibleNacosNamespaces = computed(() => form.value.db_type === "nacos"); +const canChooseVisibleDatabases = computed(() => !canChooseVisibleNacosNamespaces.value && connectionCanChooseVisibleDatabases(form.value)); const visibleFilterUsesSchemas = computed(() => connectionUsesVisibleSchemaFilter(form.value)); const hasVisibleDatabaseFilter = computed(() => Array.isArray(form.value.visible_databases)); const visibleDatabaseSummary = computed(() => { @@ -2882,6 +2819,16 @@ const visibleDatabaseTotalCount = computed(() => listedVisibleDatabaseNames.valu const visibleDatabaseCanSave = computed(() => canSaveVisibleDatabaseSelection([...visibleDatabaseSelection.value])); const visibleDatabaseHasSystemObjects = computed(() => defaultListedVisibleDatabaseNames.value.length < visibleDatabaseNames.value.length); const visibleSystemObjectsLabelKey = computed(() => (visibleFilterUsesSchemas.value ? "visibleSchemas.showSystemSchemas" : "visibleDatabases.showSystemDatabases")); +const filteredVisibleNacosNamespaces = computed(() => { + const query = visibleNacosNamespaceSearchText.value.trim().toLowerCase(); + if (!query) return visibleNacosNamespaces.value; + return visibleNacosNamespaces.value.filter((namespace) => { + const label = namespace.namespaceShowName || namespace.namespace || "public"; + return `${label} ${namespace.namespace}`.toLowerCase().includes(query); + }); +}); +const visibleNacosNamespaceSelectedCount = computed(() => visibleNacosNamespaceSelection.value.size); +const visibleNacosNamespaceCanSave = computed(() => visibleNacosNamespaceSelection.value.size > 0); const filteredProductionDatabaseNames = computed(() => { const query = productionDatabaseSearchText.value.trim().toLowerCase(); if (!query) return productionDatabaseNames.value; @@ -3028,7 +2975,7 @@ const agentInstallProgressLabel = computed(() => { }); const canCloseAgentInstallDialog = computed(() => !agentInstallRunning.value || !!agentInstallError.value); const sqlServerDriverMode = computed<"auto" | "legacy">(() => (sqlServerUsesLegacyCompatibility(form.value) ? "legacy" : "auto")); -const shouldUseWideConnectionDialog = computed(() => dialogStep.value === "config" && (canChooseVisibleDatabases.value || (canChooseVisibleSchemas.value && !visibleFilterUsesSchemas.value))); +const shouldUseWideConnectionDialog = computed(() => dialogStep.value === "config" && (canChooseVisibleDatabases.value || canChooseVisibleNacosNamespaces.value || (canChooseVisibleSchemas.value && !visibleFilterUsesSchemas.value))); const connectionDialogContentClass = computed(() => { if (dialogStep.value === "select") return "connection-dialog-content--picker sm:h-[720px] sm:max-w-[880px]"; const widthClass = shouldUseWideConnectionDialog.value ? "connection-dialog-content--wide sm:max-w-[660px]" : "connection-dialog-content--standard sm:max-w-[560px]"; @@ -3147,21 +3094,13 @@ async function testConnection() { if (runId !== testRunId) return; const rawMessage = mongodbAuthFailureHint(errorMessage(e)); const message = config ? connectionErrorWithDriverUpdateHint(config, rawMessage) : rawMessage; - const fallback = config ? await tryNacosDockerConsoleFallback(config, message, runId) : null; - if (runId !== testRunId) return; - if (fallback) { - applySuccessfulConnectionTest(fallback.result, fallback.config, submittedSourceName); - void persistSuccessfulConnectionTest(fallback.result, fallback.config, submittedSourceName, runId); - clearEditedConnectionErrorAfterSuccessfulTest(); - } else { - const shouldShowSqlServerLegacyMode = config?.db_type === "sqlserver" && !sqlServerUsesLegacyCompatibility(config) && isSqlServerTlsHandshakeFailure(message); - if (shouldShowSqlServerLegacyMode) { - configTab.value = "advanced"; - } - clearTestedConnectionInfo(); - testResult.value = { ok: false, message }; - showConnectionError(message); + const shouldShowSqlServerLegacyMode = config?.db_type === "sqlserver" && !sqlServerUsesLegacyCompatibility(config) && isSqlServerTlsHandshakeFailure(message); + if (shouldShowSqlServerLegacyMode) { + configTab.value = "advanced"; } + clearTestedConnectionInfo(); + testResult.value = { ok: false, message }; + showConnectionError(message); } finally { if (runId === testRunId) { isTesting.value = false; @@ -3452,7 +3391,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo applyNacosServerAddr(config, nacosConfig.serverAddr); config.username = nacosAuthKind.value === "usernamePassword" ? nacosUsername.value.trim() : ""; config.password = nacosAuthKind.value === "usernamePassword" ? nacosPassword.value : ""; - config.database = nacosConfig.namespace || undefined; + config.database = undefined; config.connection_string = undefined; config.url_params = ""; } else if (config.db_type === "mqtt") { @@ -3898,6 +3837,15 @@ function resetVisibleDatabaseDraftState() { visibleDatabaseShowSystem.value = false; } +function resetVisibleNacosNamespaceDraftState() { + showVisibleNacosNamespacesDialog.value = false; + isLoadingVisibleNacosNamespaces.value = false; + visibleNacosNamespaces.value = []; + visibleNacosNamespaceSelection.value = new Set(); + visibleNacosNamespaceSearchText.value = ""; + visibleNacosNamespaceError.value = ""; +} + function resetProductionDatabaseDraftState() { showProductionDatabasesDialog.value = false; isLoadingProductionDatabases.value = false; @@ -3967,6 +3915,78 @@ async function openVisibleDatabasesPicker() { } } +function nacosNamespaceValue(namespace: NacosNamespaceInfo): string { + return namespace.namespace || ""; +} + +function nacosNamespaceLabel(namespace: NacosNamespaceInfo): string { + return namespace.namespaceShowName || namespace.namespace || "public"; +} + +function normalizeVisibleNacosNamespaceSelection(selected: Iterable, namespaces: NacosNamespaceInfo[]): string[] { + return normalizeNacosNamespaceSelection(selected, namespaces); +} + +async function openVisibleNacosNamespacesPicker() { + if (!ensureConnectionHostResolvedFromUrl()) return; + if (!canChooseVisibleNacosNamespaces.value || isLoadingVisibleNacosNamespaces.value) return; + + isLoadingVisibleNacosNamespaces.value = true; + visibleNacosNamespaceError.value = ""; + visibleNacosNamespaceSearchText.value = ""; + const draftId = buildDraftVisibleDatabasesConnectionId(uuid()); + + try { + const draftConfig = { + ...connectionConfigForSubmit(draftId), + id: draftId, + one_time: true, + }; + await api.connectDb(draftConfig); + const namespaces = normalizeNacosNamespacesForDisplay(await api.nacosListNamespaces(draftId)); + visibleNacosNamespaces.value = [...namespaces].sort((left, right) => nacosNamespaceLabel(left).localeCompare(nacosNamespaceLabel(right))); + const configured = form.value.visible_databases; + const initialSelection = Array.isArray(configured) ? normalizeVisibleNacosNamespaceSelection(configured, visibleNacosNamespaces.value) : visibleNacosNamespaces.value.map(nacosNamespaceValue); + visibleNacosNamespaceSelection.value = new Set(initialSelection); + showVisibleNacosNamespacesDialog.value = true; + } catch (e: any) { + visibleNacosNamespaces.value = []; + visibleNacosNamespaceSelection.value = new Set(); + visibleNacosNamespaceError.value = errorMessage(e); + testResult.value = { ok: false, message: visibleNacosNamespaceError.value }; + showVisibleNacosNamespacesDialog.value = true; + } finally { + await api.disconnectDb(draftId).catch(() => undefined); + isLoadingVisibleNacosNamespaces.value = false; + } +} + +function toggleVisibleNacosNamespace(namespace: string) { + const next = new Set(visibleNacosNamespaceSelection.value); + if (next.has(namespace)) next.delete(namespace); + else next.add(namespace); + visibleNacosNamespaceSelection.value = next; +} + +function selectAllVisibleNacosNamespaces() { + visibleNacosNamespaceSelection.value = new Set(visibleNacosNamespaces.value.map(nacosNamespaceValue)); +} + +function clearVisibleNacosNamespaceSelection() { + visibleNacosNamespaceSelection.value = new Set(); +} + +function showAllVisibleNacosNamespaces() { + form.value.visible_databases = undefined; + resetVisibleNacosNamespaceDraftState(); +} + +function saveVisibleNacosNamespaceSelection() { + if (!visibleNacosNamespaceCanSave.value) return; + form.value.visible_databases = normalizeVisibleNacosNamespaceSelection(visibleNacosNamespaceSelection.value, visibleNacosNamespaces.value); + showVisibleNacosNamespacesDialog.value = false; +} + async function loadVisibleDatabaseNames(connectionId: string, config: ConnectionConfig): Promise { if (connectionUsesVisibleSchemaFilter(config)) { return api.listSchemas(connectionId, config.database || ""); @@ -4233,6 +4253,7 @@ function resetForm() { selectedDbCategory.value = "sql"; configTab.value = "connection"; resetVisibleDatabaseDraftState(); + resetVisibleNacosNamespaceDraftState(); resetProductionDatabaseDraftState(); resetVisibleSchemasState(); resetTestState(); @@ -5085,7 +5106,7 @@ function openExternalUrl(url: string) { -
+
@@ -5654,146 +5675,74 @@ function openExternalUrl(url: string) { @@ -6674,7 +6623,7 @@ function openExternalUrl(url: string) { -
+