From f793cf9fe5542795645f7eec1ef643f5e00ca825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piggy=20Park=20=28=EB=B0=95=EC=9A=A9=ED=83=9C=29?= Date: Mon, 7 Jul 2025 14:38:04 +0900 Subject: [PATCH 1/3] feat(trusted-account-auto-sync): apply new auto-sync mapping method for azure account (azure management group) feat(trusted-account-auto-sync): apply new auto-sync mapping method for azure account (azure management group) --- .../schema/api-verbs/create.ts | 1 + .../schema/api-verbs/update.ts | 1 + .../identity/trusted-account/schema/model.ts | 1 + .../components/info-tooltip/InfoTooltip.vue | 35 ++++ .../components/ServiceAccountAutoSync.vue | 1 + .../ServiceAccountAutoSyncMappingMethod.vue | 154 ++++++++---------- .../constants/auto-sync-options-contant.ts | 77 +++++++++ .../pages/ServiceAccountAddPage.vue | 1 + .../stores/service-account-page-store.ts | 10 +- 9 files changed, 193 insertions(+), 88 deletions(-) create mode 100644 apps/web/src/common/components/info-tooltip/InfoTooltip.vue create mode 100644 apps/web/src/services/service-account/constants/auto-sync-options-contant.ts diff --git a/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/create.ts b/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/create.ts index bedf702ac3..043f6a78cd 100644 --- a/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/create.ts +++ b/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/create.ts @@ -14,6 +14,7 @@ export interface TrustedAccountCreateParameters { sync_options?: { skip_project_group: boolean; single_workspace_id: string; + use_management_group_as_workspace?: boolean; // only for Azure }; plugin_options?: Record; tags?: Tags; diff --git a/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/update.ts b/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/update.ts index 3f6e252d51..0a72e9a2b5 100644 --- a/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/update.ts +++ b/apps/web/src/api-clients/identity/trusted-account/schema/api-verbs/update.ts @@ -11,6 +11,7 @@ export interface TrustedAccountUpdateParameters { sync_options?: { skip_project_group: boolean; single_workspace_id: string; + use_management_group_as_workspace?: boolean; // only for Azure }; plugin_options?: Record; tags?: Tags; diff --git a/apps/web/src/api-clients/identity/trusted-account/schema/model.ts b/apps/web/src/api-clients/identity/trusted-account/schema/model.ts index e01e0d73d5..e8cf6ae1e3 100644 --- a/apps/web/src/api-clients/identity/trusted-account/schema/model.ts +++ b/apps/web/src/api-clients/identity/trusted-account/schema/model.ts @@ -13,6 +13,7 @@ export interface TrustedAccountModel { sync_options?: { skip_project_group: boolean; single_workspace_id: string; + use_management_group_as_workspace?: boolean; // only for Azure }; plugin_options?: Record; tags: Tags; diff --git a/apps/web/src/common/components/info-tooltip/InfoTooltip.vue b/apps/web/src/common/components/info-tooltip/InfoTooltip.vue new file mode 100644 index 0000000000..5274de29c2 --- /dev/null +++ b/apps/web/src/common/components/info-tooltip/InfoTooltip.vue @@ -0,0 +1,35 @@ + + + diff --git a/apps/web/src/services/service-account/components/ServiceAccountAutoSync.vue b/apps/web/src/services/service-account/components/ServiceAccountAutoSync.vue index f0bfb2dfcf..01bbb791be 100644 --- a/apps/web/src/services/service-account/components/ServiceAccountAutoSync.vue +++ b/apps/web/src/services/service-account/components/ServiceAccountAutoSync.vue @@ -77,6 +77,7 @@ const handleClickSaveButton = async () => { sync_options: { skip_project_group: serviceAccountPageFormState.skipProjectGroup, single_workspace_id: serviceAccountPageFormState.selectedSingleWorkspace ?? undefined, + use_management_group_as_workspace: serviceAccountPageFormState.useManagementGroupAsWorkspace ?? undefined, }, plugin_options: serviceAccountPageFormState.additionalOptions, }); diff --git a/apps/web/src/services/service-account/components/ServiceAccountAutoSyncMappingMethod.vue b/apps/web/src/services/service-account/components/ServiceAccountAutoSyncMappingMethod.vue index 7586074292..1e331e959c 100644 --- a/apps/web/src/services/service-account/components/ServiceAccountAutoSyncMappingMethod.vue +++ b/apps/web/src/services/service-account/components/ServiceAccountAutoSyncMappingMethod.vue @@ -4,73 +4,26 @@ import { computed, reactive, watch } from 'vue'; import { PFieldTitle, PRadio } from '@cloudforet/mirinae'; + +import { i18n } from '@/translations'; + import { useAppContextStore } from '@/store/app-context/app-context-store'; import { useUserWorkspaceStore } from '@/store/app-context/workspace/user-workspace-store'; +import InfoTooltip from '@/common/components/info-tooltip/InfoTooltip.vue'; import MappingMethod from '@/common/components/mapping-method/MappingMethod.vue'; import WorkspaceLogoIcon from '@/common/modules/navigations/top-bar/modules/top-bar-header/WorkspaceLogoIcon.vue'; import WorkspaceDropdown from '@/services/service-account/components/WorkspaceDropdown.vue'; +import { CSP_AUTO_SYNC_OPTIONS_MAP, WORKSPACE_MAPPING_OPTIONS_MAP } from '@/services/service-account/constants/auto-sync-options-contant'; +import type { ServiceAccountStoreFormState } from '@/services/service-account/stores/service-account-page-store'; import { useServiceAccountPageStore } from '@/services/service-account/stores/service-account-page-store'; -const cspAdditionalOptionMap = { - aws: { - name: 'AWS Organization', - workspaceMappingOptions: [ - { - label: 'Top-level Organization Units', - value: 'multipleWorkspaces', - }, - { - label: 'AWS Organization', - value: 'singleWorkspace', - }, - ], - projectGroupMappingOptions: [ - { - label: 'Nested Organization Units', - value: 'projectGroups', - }, - ], - }, - azure: { - name: 'Azure Tenant', - workspaceMappingOptions: [ - { - label: 'Multitenant Organization', - value: 'multipleWorkspaces', - }, - { - label: 'Azure Tenant', - value: 'singleWorkspace', - }, - ], - projectGroupMappingOptions: [ - { - label: 'Nested Management Groups', - value: 'projectGroups', - }, - ], - }, - google_cloud: { - name: 'Google Cloud Organization', - workspaceMappingOptions: [ - { - label: 'Top-level Folders in Google Cloud Organization', - value: 'multipleWorkspaces', - }, - { - label: 'Google Cloud Organization', - value: 'singleWorkspace', - }, - ], - projectGroupMappingOptions: [ - { - label: 'Folders in Google Cloud Organization', - value: 'projectGroups', - }, - ], - }, +type FormData = Partial>; +type MappingMethodOptionType = { + label: string; + value: any; + info?: string; }; const props = withDefaults(defineProps<{mode:'UPDATE'|'READ'}>(), { @@ -83,11 +36,29 @@ const serviceAccountPageFormState = serviceAccountPageStore.formState; const appContextStore = useAppContextStore(); const userWorkspaceStore = useUserWorkspaceStore(); + const state = reactive({ - selectedWorkspace: computed(() => serviceAccountPageStore.formState.selectedSingleWorkspace ?? ''), - additionalOptionUiByProvider: computed(() => cspAdditionalOptionMap[serviceAccountPageState.selectedProvider] ?? {}), - workspaceMapping: 'multipleWorkspaces', - projectGroupMapping: 'projectGroups', + selectedWorkspace: computed(() => serviceAccountPageStore.formState.selectedSingleWorkspace ?? undefined), + mappingMethodProviderLabel: computed(() => CSP_AUTO_SYNC_OPTIONS_MAP[serviceAccountPageState.selectedProvider]?.name ?? ''), + workspaceMappingOptions: computed(() => CSP_AUTO_SYNC_OPTIONS_MAP[serviceAccountPageState.selectedProvider].workspaceMappingOptions), + projectGroupMappingOptions: computed(() => [ + ...CSP_AUTO_SYNC_OPTIONS_MAP[serviceAccountPageState.selectedProvider].projectGroupMappingOptions, + { + label: i18n.t('IDENTITY.SERVICE_ACCOUNT.AUTO_SYNC.SKIP_PROJECT_GROUP_MAPPING'), + value: false, + }, + ]), + workspaceMapping: 'multi' as (typeof WORKSPACE_MAPPING_OPTIONS_MAP)[keyof typeof WORKSPACE_MAPPING_OPTIONS_MAP], + projectGroupMapping: true as boolean, + + formData: computed(() => (state.isDomainForm ? { + selectedSingleWorkspace: state.workspaceMapping === WORKSPACE_MAPPING_OPTIONS_MAP.SINGLE ? state.selectedWorkspace : undefined, + skipProjectGroup: !state.projectGroupMapping, + useManagementGroupAsWorkspace: state.workspaceMapping === WORKSPACE_MAPPING_OPTIONS_MAP.MULTI_MANAGEMENT_GROUP_FOR_AZURE ? true : undefined, + } : { + skipProjectGroup: !state.projectGroupMapping, + })), + selectedWorkspaceItem: computed(() => userWorkspaceStore.getters.workspaceMap[state.selectedWorkspace] ?? {}), isAdminMode: computed(() => appContextStore.getters.isAdminMode), isResourceGroupDomain: computed(() => serviceAccountPageState.originServiceAccountItem.resource_group === 'DOMAIN'), @@ -112,15 +83,9 @@ const state = reactive({ name: 'project_group', }, ])), - formData: computed(() => (state.isDomainForm ? { - selectedSingleWorkspace: state.workspaceMapping === 'singleWorkspace' ? state.selectedWorkspace : '', - skipProjectGroup: state.projectGroupMapping === 'skip', - } : { - skipProjectGroup: state.projectGroupMapping === 'skip', - })), - selectedWorkspaceMappingOptionLabel: computed(() => cspAdditionalOptionMap[serviceAccountPageState.selectedProvider].workspaceMappingOptions - .find((option) => (option.value === (state.selectedWorkspace ? 'singleWorkspace' : 'multipleWorkspaces')))?.label), - selectedProjectGroupMappingOptionLabel: computed(() => cspAdditionalOptionMap[serviceAccountPageState.selectedProvider].projectGroupMappingOptions[0].label), + selectedWorkspaceMappingOptionLabel: computed(() => CSP_AUTO_SYNC_OPTIONS_MAP[serviceAccountPageState.selectedProvider].workspaceMappingOptions + .find((option) => (option.value === state.workspaceMapping))?.label), + selectedProjectGroupMappingOptionLabel: computed(() => CSP_AUTO_SYNC_OPTIONS_MAP[serviceAccountPageState.selectedProvider].projectGroupMappingOptions[0].label), }); const handleUpdateWorkspace = (workspaceId:string) => { @@ -129,6 +94,14 @@ const handleUpdateWorkspace = (workspaceId:string) => { }); }; +const handleWorkspaceMappingChange = (value: (typeof WORKSPACE_MAPPING_OPTIONS_MAP)[keyof typeof WORKSPACE_MAPPING_OPTIONS_MAP]) => { + state.workspaceMapping = value; +}; + +const handleProjectGroupMappingChange = (value: boolean) => { + state.projectGroupMapping = value; +}; + watch(() => state.formData, (formData) => { Object.entries(formData).forEach(([key, value]) => { serviceAccountPageStore.setFormState(key, value); @@ -137,8 +110,14 @@ watch(() => state.formData, (formData) => { watch(() => serviceAccountPageState.originServiceAccountItem, (item) => { if (item) { - state.workspaceMapping = item.sync_options?.single_workspace_id ? 'singleWorkspace' : 'multipleWorkspaces'; - state.projectGroupMapping = item.sync_options?.skip_project_group ? 'skip' : 'projectGroups'; + if (!item.sync_options?.single_workspace_id && !item.sync_options?.use_management_group_as_workspace) { // normal multi workspace case + state.workspaceMapping = WORKSPACE_MAPPING_OPTIONS_MAP.MULTI; + } else if (item.sync_options?.use_management_group_as_workspace) { // Azure multi management group workspace case + state.workspaceMapping = WORKSPACE_MAPPING_OPTIONS_MAP.MULTI_MANAGEMENT_GROUP_FOR_AZURE; + } else { // normal single workspace case + state.workspaceMapping = WORKSPACE_MAPPING_OPTIONS_MAP.SINGLE; + } + state.projectGroupMapping = !item.sync_options?.skip_project_group; } }, { immediate: true }); @@ -154,7 +133,7 @@ watch(() => serviceAccountPageState.originServiceAccountItem, (item) => { class="mb-6" >