From c63c517b4fed7c01e60e8174a673cf24d890e786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Sun, 16 Aug 2026 11:23:53 +0800 Subject: [PATCH] feat(nacos): add access control support --- .../connection/ConnectionDialog.vue | 102 +- .../src/components/layout/ContentArea.vue | 7 + .../components/nacos/NacosAccessControl.vue | 686 ++++++ .../nacos/NacosAccessControlConsole.vue | 95 + .../components/nacos/NacosAdminConsole.vue | 10 +- .../nacos/NacosNamespaceMultiSelect.vue | 128 ++ .../nacos/NacosRoleAccessControl.vue | 967 ++++++++ .../NacosAccessControlConsole.spec.ts | 210 ++ .../__tests__/NacosAdminConsoleLayout.spec.ts | 5 + .../NacosNamespaceMultiSelect.spec.ts | 15 + .../__tests__/NacosRoleAccessControl.spec.ts | 50 + .../src/components/sidebar/ConnectionTree.vue | 5 +- .../sidebar/SidebarTreeRuntimeHost.vue | 51 +- .../src/components/sidebar/TreeItem.vue | 5 + .../sidebar/VisibleNacosNamespacesDialog.vue | 4 +- .../SidebarTreeRuntimeHost.expansion.spec.ts | 78 +- .../VisibleNacosNamespacesDialog.spec.ts | 11 + .../src/i18n/__tests__/backendErrors.spec.ts | 16 + apps/desktop/src/i18n/backend-errors.ts | 5 + apps/desktop/src/i18n/locales/en.ts | 173 +- apps/desktop/src/i18n/locales/es.ts | 12 + apps/desktop/src/i18n/locales/it.ts | 12 + apps/desktop/src/i18n/locales/ja.ts | 12 + apps/desktop/src/i18n/locales/ko.ts | 9 + .../src/i18n/locales/nacosAccessControl.ts | 162 ++ apps/desktop/src/i18n/locales/pt-BR.ts | 12 + apps/desktop/src/i18n/locales/zh-CN.ts | 173 +- apps/desktop/src/i18n/locales/zh-TW.ts | 13 + .../connection/nacosConnectionDialog.spec.ts | 17 +- .../lib/__tests__/nacos/nacosAdmin.spec.ts | 21 + .../nacos/nacosNamespaceVisibility.spec.ts | 13 +- .../sidebarConnectionStateFilter.spec.ts | 16 +- .../sidebar/sidebarTreeItemLayout.spec.ts | 4 +- .../__tests__/sidebar/treeNodeClick.spec.ts | 26 +- apps/desktop/src/lib/backend/api.ts | 13 + apps/desktop/src/lib/backend/http.ts | 64 + apps/desktop/src/lib/backend/nacos-tauri.ts | 64 + apps/desktop/src/lib/nacos/nacosAdmin.ts | 39 +- .../src/lib/nacos/nacosNamespaceVisibility.ts | 9 + .../src/lib/sidebar/sidebarActiveTabTarget.ts | 12 + .../src/lib/sidebar/sidebarSearchTree.ts | 26 +- .../src/lib/sidebar/sidebarTreeItemLayout.ts | 2 + apps/desktop/src/lib/sidebar/treeNodeClick.ts | 9 +- apps/desktop/src/lib/sidebar/treeNodeIcon.ts | 1 + apps/desktop/src/lib/tabs/tabPresentation.ts | 5 + ...nnectionStore.nacosNamespaceAccess.spec.ts | 102 + .../__tests__/connectionStore.timeout.spec.ts | 26 + apps/desktop/src/stores/connectionStore.ts | 28 +- apps/desktop/src/types/database.ts | 2 + apps/desktop/src/types/nacos.ts | 172 +- crates/dbx-core/src/connection.rs | 9 +- crates/dbx-core/src/nacos/access_control.rs | 1464 ++++++++++++ crates/dbx-core/src/nacos/config.rs | 36 + crates/dbx-core/src/nacos/http.rs | 2028 ++++++++++++++++- crates/dbx-core/src/nacos/mod.rs | 5 + crates/dbx-core/src/nacos/namespace_access.rs | 835 +++++++ crates/dbx-core/src/nacos/port.rs | 82 + crates/dbx-core/src/nacos/prometheus.rs | 1 + crates/dbx-core/src/nacos/service.rs | 254 ++- crates/dbx-core/src/nacos/types.rs | 428 ++++ crates/dbx-web/src/main.rs | 13 + crates/dbx-web/src/routes/connection.rs | 15 +- crates/dbx-web/src/routes/nacos.rs | 195 ++ .../app-tests/sidebarTreeAffordances.test.ts | 2 +- src-tauri/src/commands/connection.rs | 3 +- src-tauri/src/commands/nacos_cmd.rs | 115 + src-tauri/src/lib.rs | 13 + 67 files changed, 9062 insertions(+), 135 deletions(-) create mode 100644 apps/desktop/src/components/nacos/NacosAccessControl.vue create mode 100644 apps/desktop/src/components/nacos/NacosAccessControlConsole.vue create mode 100644 apps/desktop/src/components/nacos/NacosNamespaceMultiSelect.vue create mode 100644 apps/desktop/src/components/nacos/NacosRoleAccessControl.vue create mode 100644 apps/desktop/src/components/nacos/__tests__/NacosAccessControlConsole.spec.ts create mode 100644 apps/desktop/src/components/nacos/__tests__/NacosNamespaceMultiSelect.spec.ts create mode 100644 apps/desktop/src/components/nacos/__tests__/NacosRoleAccessControl.spec.ts create mode 100644 apps/desktop/src/components/sidebar/__tests__/VisibleNacosNamespacesDialog.spec.ts create mode 100644 apps/desktop/src/i18n/locales/nacosAccessControl.ts create mode 100644 apps/desktop/src/stores/__tests__/connectionStore.nacosNamespaceAccess.spec.ts create mode 100644 crates/dbx-core/src/nacos/access_control.rs create mode 100644 crates/dbx-core/src/nacos/namespace_access.rs diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 1ff7ba37a3..42a43547d3 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -69,8 +69,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 { normalizeNacosEndpoint, normalizeNacosMetricsUrl } from "@/lib/nacos/nacosAdmin"; -import { nacosNamespaceIdentity, normalizeNacosNamespaceSelection, normalizeNacosNamespacesForDisplay } from "@/lib/nacos/nacosNamespaceVisibility"; +import { normalizeNacosEndpoint, normalizeNacosMetricsUrl, parseNacosManagedNamespaces } from "@/lib/nacos/nacosAdmin"; +import { loadReadableNacosNamespaces, nacosNamespaceIdentity, normalizeNacosNamespaceSelection } from "@/lib/nacos/nacosNamespaceVisibility"; import { ArrowLeft, ArrowDown, @@ -821,11 +821,12 @@ const mqKafkaSaslMechanismOptions = [ { value: "SCRAM-SHA-512", label: "SCRAM-SHA-512" }, ]; const nacosImplementation = ref("nacos"); -// 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. +// Nacos 2 and 3 expose different API planes. New connections must therefore +// choose an explicit version instead of relying on endpoint-shape guessing. const nacosVersionMode = ref("v2"); const nacosServerAddr = ref(""); +const nacosOrdinaryAccount = ref(false); +const nacosManagedNamespacesText = ref(""); const nacosRNacosConsoleAddr = ref(""); const nacosHistoryEnabled = ref(false); const nacosConsoleAuthKind = ref("inherit"); @@ -1390,6 +1391,8 @@ function resetNacosFields(config?: Partial) { const serverAddr = config?.serverAddr?.trim() || ""; const contextPath = config?.contextPath?.trim() || ""; nacosServerAddr.value = serverAddr && contextPath && contextPath !== "/" && !serverAddr.endsWith(contextPath) ? `${serverAddr.replace(/\/+$/, "")}/${contextPath.replace(/^\/+/, "")}` : serverAddr; + nacosOrdinaryAccount.value = !!config?.managedNamespaces?.length; + nacosManagedNamespacesText.value = (config?.managedNamespaces || []).join("\n"); nacosRNacosConsoleAddr.value = config?.rnacosConsoleAddr?.trim() || ""; nacosHistoryEnabled.value = config?.rnacosHistoryEnabled ?? !!config?.rnacosConsoleAddr; const consoleAuth = config?.rnacosConsoleAuth || { kind: "inherit" }; @@ -1731,12 +1734,16 @@ function buildNacosAdminConfig(): NacosAdminConfig { if (nacosImplementation.value === "rnacos" && normalized.warnings.length) { throw new Error(t("connection.nacosRNacosOpenApiRequired")); } - const rnacosExtensionsEnabled = nacosImplementation.value === "rnacos" && nacosHistoryEnabled.value; - const rnacosConsoleConfigured = rnacosExtensionsEnabled && !!nacosRNacosConsoleAddr.value.trim(); - if (rnacosExtensionsEnabled && !rnacosConsoleConfigured) { + const rnacosConsoleConfigured = nacosImplementation.value === "rnacos" && !!nacosRNacosConsoleAddr.value.trim(); + if (nacosImplementation.value === "rnacos" && nacosHistoryEnabled.value && !rnacosConsoleConfigured) { throw new Error(t("connection.nacosRNacosConsoleUrlRequired")); } let rnacosConsoleAuth: NacosRNacosConsoleAuth | undefined; + const usesManagedNamespaces = nacosImplementation.value === "nacos" && nacosAuthKind.value === "usernamePassword" && nacosOrdinaryAccount.value; + const managedNamespaces = usesManagedNamespaces ? parseNacosManagedNamespaces(nacosManagedNamespacesText.value) : []; + if (usesManagedNamespaces && managedNamespaces.length === 0) { + throw new Error(t("nacos.nacosOrdinaryNamespacesRequired")); + } let metricsUrl: string | undefined; if (nacosMetricsMode.value === "custom") { try { @@ -1762,7 +1769,8 @@ function buildNacosAdminConfig(): NacosAdminConfig { versionMode: nacosImplementation.value === "nacos" ? nacosVersionMode.value : undefined, serverAddr: normalized.serverAddr, contextPath: normalized.contextPath || undefined, - rnacosConsoleAddr: rnacosExtensionsEnabled ? nacosRNacosConsoleAddr.value.trim() || undefined : undefined, + managedNamespaces: managedNamespaces.length ? managedNamespaces : undefined, + rnacosConsoleAddr: rnacosConsoleConfigured ? nacosRNacosConsoleAddr.value.trim() : undefined, rnacosHistoryEnabled: nacosImplementation.value === "rnacos" ? nacosHistoryEnabled.value : undefined, rnacosConsoleAuth, auth: buildNacosAuth(), @@ -4426,7 +4434,7 @@ async function openVisibleNacosNamespacesPicker() { one_time: true, }; await api.connectDb(draftConfig); - const namespaces = normalizeNacosNamespacesForDisplay(await api.nacosListNamespaces(draftId)); + const namespaces = await loadReadableNacosNamespaces(draftId, api); 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); @@ -4510,7 +4518,7 @@ function initialProductionDatabaseSelection(databaseNames: string[]): string[] { async function loadProductionDatabaseNames(connectionId: string, config: ConnectionConfig): Promise { if (config.db_type === "nacos") { - return normalizeNacosNamespacesForDisplay(await api.nacosListNamespaces(connectionId)).map((namespace) => namespace.namespace); + return (await loadReadableNacosNamespaces(connectionId, api)).map((namespace) => namespace.namespace); } if (config.db_type === "redis") { return (await api.redisListDatabases(connectionId)).map((database) => String(database.db)); @@ -6316,6 +6324,29 @@ function openExternalUrl(url: string) { +
+
+ + {{ t("nacos.nacosOrdinaryAccount") }} + {{ t("nacos.nacosOrdinaryAccountHint") }} + + +
+
+
+ + {{ t("nacos.nacosManagedNamespacesSeparator") }} +
+