From 41e0989f96a4d7ad66bd27f0efe740c8dff8524d Mon Sep 17 00:00:00 2001 From: jischeng <49861575+jischeng@users.noreply.github.com> Date: Thu, 16 Jul 2026 18:21:24 +0800 Subject: [PATCH] feat(starrocks): enable user and privilege management --- .../components/admin/DatabaseUserAdmin.vue | 71 ++++++---- .../src/components/sidebar/TreeItem.vue | 4 +- .../src/lib/database/databaseUserAdmin.ts | 126 ++++++++++++++---- apps/desktop/src/stores/connectionStore.ts | 4 +- .../assets/database-drivers.manifest.json | 2 +- .../dbx-core/tests/database_capabilities.rs | 3 + packages/app-tests/databaseUserAdmin.test.ts | 98 +++++++++++++- 7 files changed, 248 insertions(+), 60 deletions(-) diff --git a/apps/desktop/src/components/admin/DatabaseUserAdmin.vue b/apps/desktop/src/components/admin/DatabaseUserAdmin.vue index e23af1cb1..75567a9df 100644 --- a/apps/desktop/src/components/admin/DatabaseUserAdmin.vue +++ b/apps/desktop/src/components/admin/DatabaseUserAdmin.vue @@ -14,7 +14,7 @@ import { useSqlHighlighter } from "@/composables/useSqlHighlighter"; import type { ConnectionConfig } from "@/types/database"; import * as api from "@/lib/backend/api"; import { executeWithProductionSqlGuard } from "@/lib/database/productionExecutionGuard"; -import { grantsFromQueryResult, getDatabaseUserAdminProvider, supportsDatabaseUserAdmin, type DatabaseUserIdentity, type PrivilegeScope } from "@/lib/database/databaseUserAdmin"; +import { grantsFromQueryResult, resolveDatabaseUserAdminProviderForConnection, type DatabaseUserIdentity, type PrivilegeScope } from "@/lib/database/databaseUserAdmin"; const props = defineProps<{ connection: ConnectionConfig; @@ -54,9 +54,16 @@ const grantOption = ref(false); const selectedPrivileges = ref(["SELECT"]); const createCanLogin = ref(true); -const supported = computed(() => supportsDatabaseUserAdmin(props.connection.db_type)); -const provider = computed(() => getDatabaseUserAdminProvider(props.connection.db_type)); +const provider = computed(() => resolveDatabaseUserAdminProviderForConnection(props.connection)); +const supported = computed(() => provider.value !== null); const isPostgres = computed(() => provider.value?.dialect === "postgres"); +const canCreateUser = computed(() => !!provider.value?.createUserSql); +const canAlterPassword = computed(() => !!provider.value?.alterPasswordSql); +const canAlterLogin = computed(() => !!provider.value?.alterLoginSql); +const canDropUser = computed(() => !!provider.value?.dropUserSql); +const canGrantPrivileges = computed(() => !!provider.value?.grantPrivilegesSql); +const canRevokePrivileges = computed(() => !!provider.value?.revokePrivilegesSql); +const canEditPrivileges = computed(() => canGrantPrivileges.value || canRevokePrivileges.value); const selectedUser = computed(() => users.value.find((user) => userKey(user) === selectedUserKey.value)); const filteredUsers = computed(() => { const query = search.value.trim().toLowerCase(); @@ -64,7 +71,7 @@ const filteredUsers = computed(() => { return users.value.filter((user) => userLabel(user).toLowerCase().includes(query)); }); const selectedPrivilegeSet = computed(() => new Set(selectedPrivileges.value)); -const availablePrivileges = computed(() => provider.value?.privilegesForScope(privilegeScope.value) ?? []); +const availablePrivileges = computed(() => provider.value?.privilegesForScope?.(privilegeScope.value) ?? []); const hasPrivilegePicker = computed(() => privilegeScope.value !== "role"); const loginDisableLabel = computed(() => (isPostgres.value ? t("userAdmin.disableLogin") : t("userAdmin.lock"))); const loginEnableLabel = computed(() => (isPostgres.value ? t("userAdmin.enableLogin") : t("userAdmin.unlock"))); @@ -135,7 +142,7 @@ async function loadGrants() { const result = await api.executeQuery(props.connection.id, "", userProvider.showGrantsSql(user), undefined, undefined, { maxRows: 1000, }); - grants.value = grantsFromQueryResult(result); + grants.value = (userProvider.parseGrants ?? grantsFromQueryResult)(result); } catch (error: any) { grantError.value = error?.message || String(error); grants.value = []; @@ -188,10 +195,11 @@ async function applyPendingSql() { function previewCreateUser() { const userProvider = provider.value; - if (!userProvider) return; + const createUserSql = userProvider?.createUserSql; + if (!createUserSql) return; if (!createUser.value.trim() || !createPassword.value) return; previewSql( - userProvider.createUserSql({ + createUserSql({ user: createUser.value.trim(), host: createHost.value.trim() || "%", password: createPassword.value, @@ -209,8 +217,9 @@ function previewCreateUser() { function previewPasswordChange() { const user = selectedUser.value; const userProvider = provider.value; - if (!user || !userProvider || !newPassword.value) return; - previewSql(userProvider.alterPasswordSql(user, newPassword.value), { + const alterPasswordSql = userProvider?.alterPasswordSql; + if (!user || !alterPasswordSql || !newPassword.value) return; + previewSql(alterPasswordSql(user, newPassword.value), { danger: true, afterApply: async () => { passwordDialogOpen.value = false; @@ -222,23 +231,26 @@ function previewPasswordChange() { function previewDropUser() { const user = selectedUser.value; const userProvider = provider.value; - if (!user || !userProvider) return; - previewSql(userProvider.dropUserSql(user), { danger: true }); + const dropUserSql = userProvider?.dropUserSql; + if (!user || !dropUserSql) return; + previewSql(dropUserSql(user), { danger: true }); } function previewLoginChange(enabled: boolean) { const user = selectedUser.value; const userProvider = provider.value; - if (!user || !userProvider) return; - previewSql(userProvider.alterLoginSql(user, enabled), { danger: true }); + const alterLoginSql = userProvider?.alterLoginSql; + if (!user || !alterLoginSql) return; + previewSql(alterLoginSql(user, enabled), { danger: true }); } function previewGrant() { const user = selectedUser.value; const userProvider = provider.value; - if (!user || !userProvider || (privilegeScope.value === "role" && !privilegeRole.value.trim())) return; + const grantPrivilegesSql = userProvider?.grantPrivilegesSql; + if (!user || !grantPrivilegesSql || (privilegeScope.value === "role" && !privilegeRole.value.trim())) return; previewSql( - userProvider.grantPrivilegesSql({ + grantPrivilegesSql({ user, privileges: selectedPrivileges.value, database: privilegeDatabase.value, @@ -253,9 +265,10 @@ function previewGrant() { function previewRevoke() { const user = selectedUser.value; const userProvider = provider.value; - if (!user || !userProvider || (privilegeScope.value === "role" && !privilegeRole.value.trim())) return; + const revokePrivilegesSql = userProvider?.revokePrivilegesSql; + if (!user || !revokePrivilegesSql || (privilegeScope.value === "role" && !privilegeRole.value.trim())) return; previewSql( - userProvider.revokePrivilegesSql({ + revokePrivilegesSql({ user, privileges: selectedPrivileges.value, database: privilegeDatabase.value, @@ -270,7 +283,7 @@ function previewRevoke() { function resetPrivilegeDefaults(scope: PrivilegeScope) { const userProvider = provider.value; if (!userProvider) return; - selectedPrivileges.value = userProvider.defaultPrivilegesForScope(scope); + selectedPrivileges.value = userProvider.defaultPrivilegesForScope?.(scope) ?? []; if (userProvider.dialect === "postgres") { if (scope === "database") privilegeDatabase.value = props.connection.database || "postgres"; if (scope === "schema" || scope === "table") privilegeDatabase.value = "public"; @@ -296,7 +309,7 @@ watch( ); watch( - () => provider.value?.dialect, + () => provider.value, () => { privilegeScope.value = provider.value?.defaultScope ?? "mysql"; resetPrivilegeDefaults(privilegeScope.value); @@ -326,7 +339,7 @@ onMounted(loadUsers); {{ t("grid.refresh") }} - @@ -385,27 +398,27 @@ onMounted(loadUsers);
- - - -
-
-
+
+
{{ t("userAdmin.grants") }} @@ -420,7 +433,7 @@ onMounted(loadUsers);
-