diff --git a/apps/web/package.json b/apps/web/package.json index 71304a00dc..f0650526d7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "2.0.0-dev346", + "version": "2.0.0-dev347", "private": true, "description": "Cloudforet Console Web Application", "author": "Cloudforet", diff --git a/apps/web/src/api-clients/api-client-manager.ts b/apps/web/src/api-clients/api-client-manager.ts index d6caaf0605..4ea744e4c0 100644 --- a/apps/web/src/api-clients/api-client-manager.ts +++ b/apps/web/src/api-clients/api-client-manager.ts @@ -1,8 +1,7 @@ import { SpaceConnector } from '@cloudforet/core-lib/space-connector'; -import type { ApiClientsSchemaType } from '@/lib/config/global-config/api-client-schema'; -import { ApiClientEndpoint } from '@/lib/config/global-config/api-client-schema'; -import type { GlobalServiceConfig } from '@/lib/config/global-config/type'; +import { ApiClientEndpoint } from '@/lib/config/global-config/schema/api-client-schema'; +import type { ApiClientsSchemaType, GlobalServiceConfig } from '@/lib/config/global-config/types/type'; class APIClientManager { diff --git a/apps/web/src/common/composables/contents-accessibility/index.ts b/apps/web/src/common/composables/contents-accessibility/index.ts index 4ddb74a9ae..a4b9bef2ad 100644 --- a/apps/web/src/common/composables/contents-accessibility/index.ts +++ b/apps/web/src/common/composables/contents-accessibility/index.ts @@ -1,7 +1,7 @@ import type { Ref } from 'vue'; import { computed, reactive, toRef } from 'vue'; -import { useMenuStore } from '@/store/menu/menu-store'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import type { MenuId } from '@/lib/menu/config'; @@ -10,11 +10,10 @@ interface UseContentsAccessibilityReturnType { } export const useContentsAccessibility = (menuId: MenuId): UseContentsAccessibilityReturnType => { - const menuStore = useMenuStore(); - const menuState = menuStore.state; + const globalConfigStore = useGlobalConfigStore(); const state = reactive({ - visibleContents: computed(() => menuState.menuList.findIndex((menu) => menu.id === menuId) !== -1), + visibleContents: computed(() => globalConfigStore.getters.menuList.findIndex((menu) => menu.id === menuId) !== -1), }); return { diff --git a/apps/web/src/common/modules/navigations/stores/gnb-store.ts b/apps/web/src/common/modules/navigations/stores/gnb-store.ts index 4277a98f3e..a3e319cedf 100644 --- a/apps/web/src/common/modules/navigations/stores/gnb-store.ts +++ b/apps/web/src/common/modules/navigations/stores/gnb-store.ts @@ -22,7 +22,6 @@ import type { FavoriteOptions } from '@/common/modules/favorites/favorite-button import type { Breadcrumb } from '@/common/modules/page-layouts/type'; interface GnbStoreState { - visibleAlertIcon?: boolean; breadcrumbs: Breadcrumb[]; selectedItem: Breadcrumb; id?: string; @@ -45,7 +44,6 @@ export const useGnbStore = defineStore('gnb', () => { }); const state = reactive({ - visibleAlertIcon: false, breadcrumbs: [], selectedItem: {} as Breadcrumb, id: '', @@ -68,9 +66,6 @@ export const useGnbStore = defineStore('gnb', () => { }); const mutations = { - setVisibleAlertIcon: (val?: boolean) => { - state.visibleAlertIcon = val; - }, setBreadcrumbs: (breadcrumbs: Breadcrumb[]) => { state.breadcrumbs = breadcrumbs; }, diff --git a/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/TopBarToolset.vue b/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/TopBarToolset.vue index e95fc2df05..8100a02134 100644 --- a/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/TopBarToolset.vue +++ b/apps/web/src/common/modules/navigations/top-bar/modules/top-bar-toolset/TopBarToolset.vue @@ -7,9 +7,9 @@ import { i18n } from '@/translations'; import { useAppContextStore } from '@/store/app-context/app-context-store'; import { useDomainStore } from '@/store/domain/domain-store'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import { useUserStore } from '@/store/user/user-store'; -import { useGnbStore } from '@/common/modules/navigations/stores/gnb-store'; import TopBarAdminToggleButton from '@/common/modules/navigations/top-bar/modules/top-bar-toolset/modules/top-bar-admin-toggle-button/TopBarAdminToggleButton.vue'; import TopBarFavorite from '@/common/modules/navigations/top-bar/modules/top-bar-toolset/modules/top-bar-favorite/TopBarFavorite.vue'; @@ -32,13 +32,13 @@ const emit = defineEmits<{(event: 'hide-menu'): void; const appContextStore = useAppContextStore(); const domainStore = useDomainStore(); const userStore = useUserStore(); -const gnbStore = useGnbStore(); +const globalConfigStore = useGlobalConfigStore(); const state = reactive({ isDomainAdmin: computed(() => userStore.getters.isDomainAdmin), isAdminMode: computed(() => appContextStore.getters.isAdminMode), isGrantLoading: computed(() => appContextStore.getters.globalGrantLoading), - visibleAlertIcon: computed(() => gnbStore.state.visibleAlertIcon), + visibleAlertIcon: computed(() => globalConfigStore.state.schema.ALERT_MANAGER.uiAffects?.visibleAlertIcon), tooltipTexts: computed>(() => ({ adminToggle: (state.isAdminMode ? i18n.t('COMMON.GNB.TOOLTIP.EXIT_ADMIN_MODE') : i18n.t('COMMON.GNB.TOOLTIP.ENABLE_ADMIN_MODE')) as string, })), diff --git a/apps/web/src/components/GlobalMenu.vue b/apps/web/src/components/GlobalMenu.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/web/src/lib/access-control/page-access-helper.ts b/apps/web/src/lib/access-control/page-access-helper.ts index 8d3b56691c..fe5fb60ccf 100644 --- a/apps/web/src/lib/access-control/page-access-helper.ts +++ b/apps/web/src/lib/access-control/page-access-helper.ts @@ -1,6 +1,6 @@ import type { RoleType } from '@/api-clients/identity/role/type'; -import { useMenuStore } from '@/store/menu/menu-store'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import type { PageAccessMap, @@ -14,7 +14,7 @@ import { WORKSPACE_USER_MINIMAL_PERMISSIONS, } from '@/lib/access-control/config'; import config from '@/lib/config'; -import type { GlobalServiceConfig } from '@/lib/config/global-config/type'; +import type { GlobalServiceConfig } from '@/lib/config/global-config/types/type'; import type { Menu, MenuId } from '@/lib/menu/config'; import type { LSBItem, LSBMenu } from '@/common/modules/navigations/lsb/type'; @@ -48,9 +48,9 @@ export const flattenMenu = (menuList: Menu[]): Menu[] => menuList.flatMap((menu) export const getPageAccessMapFromRawData = (pageAccessPermissions?: string[], isRolePage?: boolean): PageAccessMap => { const globalConfig = config.get('SERVICES') || {}; - const menuStore = useMenuStore(); + const globalConfigStore = useGlobalConfigStore(); const result: PageAccessMap = {}; - const menuListByVersion = !isRolePage ? menuStore.state.menuList : getEnabledMenus(globalConfig); + const menuListByVersion = !isRolePage ? globalConfigStore.getters.menuList : getEnabledMenus(globalConfig); const flattenedMenuList = flattenMenu(menuListByVersion); const setPermissions = (id: string, read = true, write = true, access = true) => { result[id] = { read, write, access }; diff --git a/apps/web/src/lib/access-control/redirect-route-helper.ts b/apps/web/src/lib/access-control/redirect-route-helper.ts index 7836ae454b..b2b38aa98f 100644 --- a/apps/web/src/lib/access-control/redirect-route-helper.ts +++ b/apps/web/src/lib/access-control/redirect-route-helper.ts @@ -3,8 +3,7 @@ import type { Location } from 'vue-router/types/router'; import { ERROR_ROUTE } from '@/router/constant'; -import type { FlattenedMenuMap } from '@/store/menu/menu-store'; -import { useMenuStore } from '@/store/menu/menu-store'; +import { useGlobalConfigStore, type FlattenedMenuMap } from '@/store/global-config/global-config-store'; import type { PageAccessMap } from '@/lib/access-control/config'; import type { MenuId } from '@/lib/menu/config'; @@ -19,8 +18,8 @@ export const getRedirectRouteByPagePermission = (route: Route, pagePermissionsMa const menuId = route.meta?.menuId; if (!menuId) return { name: ERROR_ROUTE._NAME, params: { statusCode: '404' } }; - const menuStore = useMenuStore(); - const generateFlattenedMenuMap = menuStore.getters.generateFlattenedMenuMap; + const globalConfigStore = useGlobalConfigStore(); + const generateFlattenedMenuMap = globalConfigStore.getters.generateFlattenedMenuMap; const subMenuIdList = getSubMenuListByMenuId(menuId, generateFlattenedMenuMap); let redirectMenuId: MenuId|undefined; subMenuIdList.some((subMenuId) => { diff --git a/apps/web/src/lib/config/global-config/constants.ts b/apps/web/src/lib/config/global-config/constants/constants.ts similarity index 100% rename from apps/web/src/lib/config/global-config/constants.ts rename to apps/web/src/lib/config/global-config/constants/constants.ts diff --git a/apps/web/src/lib/config/global-config/feature-schema-manager.ts b/apps/web/src/lib/config/global-config/feature-schema-manager.ts index 6af3dc9904..134521d85f 100644 --- a/apps/web/src/lib/config/global-config/feature-schema-manager.ts +++ b/apps/web/src/lib/config/global-config/feature-schema-manager.ts @@ -1,54 +1,70 @@ -import { FEATURES } from '@/lib/config/global-config/constants'; -import { initialFeatureSchema } from '@/lib/config/global-config/feature-schema'; -import type { FeatureSchemaType, GlobalServiceConfig } from '@/lib/config/global-config/type'; -import { MENU_ID } from '@/lib/menu/config'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; -export class FeatureSchemaManager { - private serviceConfig: GlobalServiceConfig; +import { getFeatureConfigurator } from '@/lib/config/global-config/helpers/get-feature-configurator'; +import type { FeatureSchemaType, GlobalServiceConfig } from '@/lib/config/global-config/types/type'; - private readonly schema: FeatureSchemaType; +export class FeatureSchemaManager { + private config: GlobalServiceConfig = { + IAM: { + ENABLED: true, + VERSION: 'V1', + }, + }; - constructor(serviceConfig: GlobalServiceConfig) { - this.serviceConfig = serviceConfig; - this.schema = JSON.parse(JSON.stringify(initialFeatureSchema)); + initialize(config: GlobalServiceConfig) { + this.config = { + ...this.config, + ...config, + }; + this.createSchema(); } - applyGlobalConfig(): FeatureSchemaType { - Object.entries(this.serviceConfig).forEach(([serviceName, config]) => { - if (config.ENABLED) { - this.updateSchema(serviceName, config.VERSION); - } else { - delete this.schema[serviceName]; - } - }); + createSchema() { + const globalConfigStore = useGlobalConfigStore(); + const schema = {} as FeatureSchemaType; - return this.schema; - } + const featureMethodMap: Record> = {}; - private updateSchema(serviceName: string, version: string): void { - if (!this.schema || !this.schema[serviceName]) return; + Object.keys(this.config).forEach((feature) => { + if (this.config[feature]?.ENABLED) { + const configurator = getFeatureConfigurator(feature); + if (configurator) { + const currentVersion = this.config[feature]?.VERSION || 'V1'; + configurator.initialize(currentVersion); + const menuConfig = configurator.getMenu(); - this.schema[serviceName].currentVersion = version; + if (configurator.uiAffect) { + configurator.uiAffect.forEach((uiAffect) => { + const targetFeature = uiAffect.feature; + const targetVersion = targetFeature === feature + ? currentVersion + : (this.config[targetFeature]?.VERSION || 'V1'); - if (serviceName === FEATURES.ALERT_MANAGER) { - if (version === 'V2') { - const iamFeatureSettings = this.schema[FEATURES.IAM].V1; - iamFeatureSettings.menu[MENU_ID.USER_GROUP] = true; - if (iamFeatureSettings.adminMenu) { - iamFeatureSettings.adminMenu[MENU_ID.USER_GROUP] = true; + if (!featureMethodMap[targetFeature]) { + featureMethodMap[targetFeature] = {}; + } + + uiAffect.affects.forEach((affect) => { + if (affect.version === targetVersion) { + featureMethodMap[targetFeature][affect.method] = true; + } + }); + }); + } + + schema[feature] = { + version: currentVersion, + menu: menuConfig.menu, + adminMenu: menuConfig?.adminMenu === null ? null : (menuConfig?.adminMenu || menuConfig?.menu), + uiAffects: feature in featureMethodMap ? featureMethodMap[feature] : {}, + }; } - this.updateUiAffects(FEATURES.PROJECT, 'V1', 'visibleAlertTabAtDetail', false); - this.updateUiAffects(FEATURES.ASSET_INVENTORY, 'V1', 'visibleAlertTabAtDetail', false); - this.updateUiAffects(FEATURES.COST_EXPLORER, 'V1', 'visibleBudgetNotification', true); } - } - } + }); - private updateUiAffects(featureKey: string, version: string, affectKey: string, value: boolean): void { - const feature = this.schema[featureKey]?.[version]; - if (feature?.uiAffects?.[affectKey] !== undefined) { - feature.uiAffects[affectKey] = value; - } + globalConfigStore.setSchema(schema); } } + +export default new FeatureSchemaManager(); diff --git a/apps/web/src/lib/config/global-config/feature-schema.ts b/apps/web/src/lib/config/global-config/feature-schema.ts deleted file mode 100644 index 2ddd0251b7..0000000000 --- a/apps/web/src/lib/config/global-config/feature-schema.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { FEATURES } from '@/lib/config/global-config/constants'; -import type { FeatureSchemaType } from '@/lib/config/global-config/type'; -import { MENU_ID } from '@/lib/menu/config'; - -export const initialFeatureSchema: FeatureSchemaType = { - [FEATURES.DASHBOARDS]: { - currentVersion: 'V1', - V1: { - menu: { [MENU_ID.DASHBOARDS]: true }, - }, - }, - [FEATURES.PROJECT]: { - currentVersion: 'V1', - V1: { - menu: { [MENU_ID.PROJECT]: true }, - uiAffects: { - visibleAlertTabAtDetail: true, - }, - }, - }, - [FEATURES.SERVICE_ACCOUNT]: { - currentVersion: 'V1', - V1: { - menu: { [MENU_ID.SERVICE_ACCOUNT]: true }, - }, - }, - [FEATURES.ASSET_INVENTORY]: { - currentVersion: 'V1', - V1: { - menu: { - [MENU_ID.CLOUD_SERVICE]: true, - [MENU_ID.SERVER]: true, - [MENU_ID.SECURITY]: true, - [MENU_ID.METRIC_EXPLORER]: true, - [MENU_ID.COLLECTOR]: true, - }, - uiAffects: { - visibleAlertTabAtDetail: true, - }, - }, - }, - [FEATURES.COST_EXPLORER]: { - currentVersion: 'V1', - V1: { - menu: { - [MENU_ID.COST_ANALYSIS]: true, - [MENU_ID.BUDGET]: true, - [MENU_ID.COST_REPORT]: true, - }, - adminMenu: { - [MENU_ID.COST_ANALYSIS]: true, - [MENU_ID.BUDGET]: true, - [MENU_ID.COST_REPORT]: true, - [MENU_ID.DATA_SOURCES]: true, - [MENU_ID.COST_ADVANCED_SETTINGS]: true, - }, - uiAffects: { - visibleBudgetNotification: false, - }, - }, - }, - [FEATURES.ALERT_MANAGER]: { - currentVersion: 'V1', - V1: { - menu: { - [MENU_ID.ALERT_MANAGER_DASHBOARD]: true, - [MENU_ID.ALERTS]: true, - [MENU_ID.ESCALATION_POLICY]: true, - }, - uiAffects: { - visibleAlertIcon: true, - visibleUserNotification: false, - }, - }, - V2: { - menu: { - [MENU_ID.SERVICE]: true, - [MENU_ID.ALERTS]: true, - }, - uiAffects: { - visibleAlertIcon: false, - visibleUserNotification: true, - }, - }, - }, - [FEATURES.OPS_FLOW]: { - currentVersion: 'V1', - V1: { - menu: { - [MENU_ID.OPS_FLOW_LANDING]: true, - [MENU_ID.TASK_BOARD]: true, - }, - adminMenu: { - [MENU_ID.TASK_MANAGEMENT]: true, - }, - }, - }, - [FEATURES.IAM]: { - currentVersion: 'V1', - V1: { - menu: { - [MENU_ID.USER]: true, - [MENU_ID.USER_GROUP]: false, - [MENU_ID.APP]: true, - }, - adminMenu: { - [MENU_ID.USER]: true, - [MENU_ID.USER_GROUP]: false, - [MENU_ID.APP]: true, - [MENU_ID.ROLE]: true, - }, - }, - }, -}; diff --git a/apps/web/src/lib/config/global-config/generate-routes.ts b/apps/web/src/lib/config/global-config/generate-routes.ts new file mode 100644 index 0000000000..125c9d5f35 --- /dev/null +++ b/apps/web/src/lib/config/global-config/generate-routes.ts @@ -0,0 +1,31 @@ +import type { RouteConfig } from 'vue-router'; + +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; + +import { getFeatureConfigurator } from '@/lib/config/global-config/helpers/get-feature-configurator'; + +import adminAdvancedRoutes from '@/services/advanced/routes/admin/routes'; +import adminInfoRoutes from '@/services/info/routes/admin/routes'; +import infoRoutes from '@/services/info/routes/routes'; +import adminWorkspaceHomeRoutes from '@/services/workspace-home/routes/admin/routes'; +import workspaceHomeRoute from '@/services/workspace-home/routes/routes'; + +export const generateRoutes = (mode: string): RouteConfig[] => { + const globalConfigStore = useGlobalConfigStore(); + const schema = globalConfigStore.state.schema; + const baseRoutes = mode === 'admin' + ? [adminWorkspaceHomeRoutes, adminAdvancedRoutes, adminInfoRoutes] + : [workspaceHomeRoute, infoRoutes]; + + Object.keys(schema).forEach((serviceName) => { + const configurator = getFeatureConfigurator(serviceName); + if (configurator) { + const route = configurator.getRoutes(mode === 'admin'); + if (route && !baseRoutes.some((existingRoute) => existingRoute.path === route.path)) { + baseRoutes.push(route); + } + } + }); + + return baseRoutes; +}; diff --git a/apps/web/src/lib/config/global-config/helpers/get-feature-configurator.ts b/apps/web/src/lib/config/global-config/helpers/get-feature-configurator.ts new file mode 100644 index 0000000000..43e8a155ef --- /dev/null +++ b/apps/web/src/lib/config/global-config/helpers/get-feature-configurator.ts @@ -0,0 +1,23 @@ +import type { FeatureConfiguratorType } from '@/lib/config/global-config/types/type'; + +import AlertManagerConfigurator from '@/services/alert-manager/configurator'; +import AssetInventoryConfigurator from '@/services/asset-inventory/configurator'; +import CostExplorerConfigurator from '@/services/cost-explorer/configurator'; +import DashboardConfigurator from '@/services/dashboards/configurator'; +import IamConfigurator from '@/services/iam/configurator'; +import OpsFlowConfigurator from '@/services/ops-flow/configurator'; +import ProjectConfigurator from '@/services/project/configurator'; +import ServiceAccountConfigurator from '@/services/service-account/configurator'; + +const configurators = { + DASHBOARDS: DashboardConfigurator, + PROJECT: ProjectConfigurator, + SERVICE_ACCOUNT: ServiceAccountConfigurator, + ASSET_INVENTORY: AssetInventoryConfigurator, + COST_ANALYSIS: CostExplorerConfigurator, + ALERT_MANAGER: AlertManagerConfigurator, + OPS_FLOW: OpsFlowConfigurator, + IAM: IamConfigurator, +} as const; + +export const getFeatureConfigurator = (featureName: string): FeatureConfiguratorType | null => configurators[featureName] || null; diff --git a/apps/web/src/lib/config/global-config/api-client-schema.ts b/apps/web/src/lib/config/global-config/schema/api-client-schema.ts similarity index 57% rename from apps/web/src/lib/config/global-config/api-client-schema.ts rename to apps/web/src/lib/config/global-config/schema/api-client-schema.ts index d32b7f325e..069bd075f5 100644 --- a/apps/web/src/lib/config/global-config/api-client-schema.ts +++ b/apps/web/src/lib/config/global-config/schema/api-client-schema.ts @@ -1,15 +1,5 @@ -type apiClientType = { - V1: string; - V2?: string; -}; -export type ApiClientsSchemaType = { - DASHBOARDS: apiClientType, - PROJECT: apiClientType, - ASSET_INVENTORY: apiClientType, - COST_ANALYSIS: apiClientType, - OPS_FLOW: apiClientType, - ALERT_MANAGER: apiClientType, -}; +import type { ApiClientsSchemaType } from '@/lib/config/global-config/types/type'; + export const ApiClientEndpoint: ApiClientsSchemaType = { DASHBOARDS: { diff --git a/apps/web/src/lib/config/global-config/type.ts b/apps/web/src/lib/config/global-config/type.ts deleted file mode 100644 index a3a2f769be..0000000000 --- a/apps/web/src/lib/config/global-config/type.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { FEATURES } from '@/lib/config/global-config/constants'; -import type { MenuId } from '@/lib/menu/config'; - -export type GlobalServiceConfig = Record; - -export type FeatureKeyType = typeof FEATURES[keyof typeof FEATURES]; - -type MenuConfig = Partial>; -export type FeatureVersionSettingsType = { - menu: MenuConfig; - adminMenu?: MenuConfig; - uiAffects?: Record; -}; -type FeatureSettingType = { - currentVersion: string; - V1: FeatureVersionSettingsType; - V2?: FeatureVersionSettingsType; -}; -export type FeatureSchemaType = Record; - diff --git a/apps/web/src/lib/config/global-config/types/type.ts b/apps/web/src/lib/config/global-config/types/type.ts new file mode 100644 index 0000000000..f00dfecfe1 --- /dev/null +++ b/apps/web/src/lib/config/global-config/types/type.ts @@ -0,0 +1,48 @@ +import type { RouteConfig } from 'vue-router'; + +import type { FEATURES } from '@/lib/config/global-config/constants/constants'; +import type { Menu } from '@/lib/menu/config'; + +export type FeatureVersion = 'V1' | 'V2'; +export type GlobalServiceConfig = Record; + +export type FeatureKeyType = typeof FEATURES[keyof typeof FEATURES]; + +export interface FeatureMenuConfig { + menu: Menu; + adminMenu?: Menu|null; + uiAffects?: Record; + version?: FeatureVersion; +} + +export type FeatureSchemaType = Record; + +type apiClientType = { + V1: string; + V2?: string; +}; +export type ApiClientsSchemaType = { + DASHBOARDS: apiClientType, + PROJECT: apiClientType, + ASSET_INVENTORY: apiClientType, + COST_ANALYSIS: apiClientType, + OPS_FLOW: apiClientType, + ALERT_MANAGER: apiClientType, +}; + +export interface FeatureConfiguratorType { + getRoutes: (isAdmin?: boolean) => RouteConfig|null; + getMenu: (isAdmin?: boolean) => FeatureMenuConfig; + initialize: (version: FeatureVersion) => void; + uiAffect: FeatureUiAffect[]; +} + +interface UiAffectConfig { + method: string; + version: string; +} + +export interface FeatureUiAffect { + feature: string; + affects: UiAffectConfig[]; +} diff --git a/apps/web/src/lib/menu/config.ts b/apps/web/src/lib/menu/config.ts index ba6711164e..f419e34380 100644 --- a/apps/web/src/lib/menu/config.ts +++ b/apps/web/src/lib/menu/config.ts @@ -1,6 +1,7 @@ // Menu Ids' Rule: All menu ids are dot-delimited in depth, up to two depths. import type { HighlightTagType } from '@/store/display/type'; + export const MENU_ID = Object.freeze({ WORKSPACE_HOME: 'workspace_home', DASHBOARDS: 'dashboards', @@ -57,6 +58,7 @@ export interface Menu { subMenuList?: Menu[]; hideOnGNB?: boolean; hideOnSiteMap?: boolean; + order?: number; } export interface MenuInfo { diff --git a/apps/web/src/lib/menu/menu-architecture.ts b/apps/web/src/lib/menu/menu-architecture.ts index d2919df90e..da3c6abfa7 100644 --- a/apps/web/src/lib/menu/menu-architecture.ts +++ b/apps/web/src/lib/menu/menu-architecture.ts @@ -5,6 +5,7 @@ export const DEFAULT_MENU_LIST: Menu[] = [ { id: MENU_ID.WORKSPACE_HOME, needPermissionByRole: true, + order: 0, }, { id: MENU_ID.INFO, diff --git a/apps/web/src/lib/site-initializer/index.ts b/apps/web/src/lib/site-initializer/index.ts index 083286d469..b7a1d0f195 100644 --- a/apps/web/src/lib/site-initializer/index.ts +++ b/apps/web/src/lib/site-initializer/index.ts @@ -16,6 +16,8 @@ import { useAllReferenceStore } from '@/store/reference/all-reference-store'; import { useUserStore } from '@/store/user/user-store'; import config from '@/lib/config'; +import featureSchemaManager from '@/lib/config/global-config/feature-schema-manager'; +import { generateRoutes } from '@/lib/config/global-config/generate-routes'; import { initRequestIdleCallback } from '@/lib/request-idle-callback-polyfill'; import { initAmcharts5 } from '@/lib/site-initializer/amcharts5'; import { initGtag, initGtm } from '@/lib/site-initializer/analysis'; @@ -32,8 +34,6 @@ import { checkSsoAccessToken } from '@/lib/site-initializer/sso'; import { initUserAndAuth } from '@/lib/site-initializer/user-auth'; import { initWorkspace } from '@/lib/site-initializer/workspace'; -import ServiceConfigurator from '@/services/configurator'; - const initQueryHelper = () => { const userStore = useUserStore(pinia); QueryHelper.init(computed(() => userStore.state.timezone)); @@ -54,12 +54,12 @@ const initRouter = (domainId?: string) => { )?.children; if (adminChildren) { - const dynamicAdminRoutes = ServiceConfigurator.getRoutes('admin'); + const dynamicAdminRoutes = generateRoutes('admin'); adminChildren.push(...dynamicAdminRoutes); } if (workspaceChildren) { - const dynamicWorkspaceRoutes = ServiceConfigurator.getRoutes('workspace'); + const dynamicWorkspaceRoutes = generateRoutes('workspace'); workspaceChildren.push(...dynamicWorkspaceRoutes); } @@ -89,11 +89,11 @@ const init = async () => { const domainId = await initDomain(config); const userId = await initUserAndAuth(config); const mergedConfig = await mergeConfig(config, domainId); - await ServiceConfigurator.initialize(mergedConfig); - await APIClientManager.initialize(mergedConfig); initDomainSettings(); await initModeSetting(); await initWorkspace(userId); + await featureSchemaManager.initialize(mergedConfig); + await APIClientManager.initialize(mergedConfig); initRouter(domainId); // prefetchResources(); initI18n(); diff --git a/apps/web/src/lib/site-initializer/merge-config.ts b/apps/web/src/lib/site-initializer/merge-config.ts index 91f71a55b7..18b229aed2 100644 --- a/apps/web/src/lib/site-initializer/merge-config.ts +++ b/apps/web/src/lib/site-initializer/merge-config.ts @@ -4,7 +4,7 @@ import type { PublicConfigGetParameters } from '@/api-clients/config/public-conf import { PUBLIC_CONFIG_NAMES } from '@/api-clients/config/public-config/schema/constant'; import type { PublicConfigModel } from '@/api-clients/config/public-config/schema/model'; -import type { GlobalServiceConfig } from '@/lib/config/global-config/type'; +import type { GlobalServiceConfig } from '@/lib/config/global-config/types/type'; export const mergeConfig = async (config, domainId: string): Promise => { const baseConfig = config.get('SERVICES') || {}; diff --git a/apps/web/src/lib/site-initializer/mode-setting.ts b/apps/web/src/lib/site-initializer/mode-setting.ts index 5ef0e21b86..e5fb638b1d 100644 --- a/apps/web/src/lib/site-initializer/mode-setting.ts +++ b/apps/web/src/lib/site-initializer/mode-setting.ts @@ -1,8 +1,6 @@ import { useAppContextStore } from '@/store/app-context/app-context-store'; import { pinia } from '@/store/pinia'; -import ServiceConfigurator from '@/services/configurator'; - export const initModeSetting = () => { // NOTE: this is to use pinia store outside vue component useAppContextStore(pinia); @@ -14,5 +12,4 @@ export const initModeSetting = () => { if (modePath === 'admin') { appContextStore.enterAdminMode(); } - ServiceConfigurator.getMenuList(modePath); }; diff --git a/apps/web/src/services/alert-manager/configurator.ts b/apps/web/src/services/alert-manager/configurator.ts index 8468767203..ff4bcdfef6 100644 --- a/apps/web/src/services/alert-manager/configurator.ts +++ b/apps/web/src/services/alert-manager/configurator.ts @@ -1,48 +1,69 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; +import type { RouteConfig } from 'vue-router'; + +import type { FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect } from '@/lib/config/global-config/types/type'; import type { Menu } from '@/lib/menu/config'; import { MENU_ID } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; - -import { useGnbStore } from '@/common/modules/navigations/stores/gnb-store'; import alertManagerRouteV1 from '@/services/alert-manager/v1/routes/routes'; import alertManagerRoute from '@/services/alert-manager/v2/routes/routes'; -import { useMyPageStore } from '@/services/my-page/stores/my-page-store'; -class AlertManagerConfigurator { - static getAdminRoutes() { - return null; - } - static getWorkspaceRoutes(version: string) { - return version === 'V1' ? alertManagerRouteV1 : alertManagerRoute; +class AlertManagerConfigurator implements FeatureConfiguratorType { + private version: 'V1' | 'V2' = 'V1'; + + readonly uiAffect: FeatureUiAffect[] = [ + { + feature: 'ALERT_MANAGER', + affects: [ + { + method: 'visibleAlertIcon', + version: 'V1', + }, + { + method: 'visibleUserNotification', + version: 'V2', + }, + ], + }, + ]; + + initialize(version: 'V1' | 'V2'): void { + this.version = version; } - static getAdminMenu(): Menu|null { - return null; + getRoutes(isAdmin?: boolean): RouteConfig|null { + if (isAdmin) return null; + + return this.version === 'V1' ? alertManagerRouteV1 : alertManagerRoute; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ - id: MENU_INFO_MAP[menuId].menuId, - needPermissionByRole: true, - })); - return { + getMenu(): FeatureMenuConfig { + const baseMenu: Menu = { id: MENU_ID.ALERT_MANAGER, needPermissionByRole: true, - subMenuList: subMenuIds, + subMenuList: [], + order: 6, }; - } - static applyUiAffects(settings: FeatureVersionSettingsType): void|null { - const gnbStore = useGnbStore(); - const myPageStore = useMyPageStore(); + if (this.version === 'V1') { + baseMenu.subMenuList = [ + { id: MENU_ID.ALERT_MANAGER_DASHBOARD, needPermissionByRole: true }, + { id: MENU_ID.ALERTS, needPermissionByRole: true }, + { id: MENU_ID.ESCALATION_POLICY, needPermissionByRole: true }, + ]; + } else { + baseMenu.subMenuList = [ + { id: MENU_ID.SERVICE, needPermissionByRole: true }, + { id: MENU_ID.ALERTS, needPermissionByRole: true }, + ]; + } - gnbStore.setVisibleAlertIcon(settings.uiAffects?.visibleAlertIcon); - myPageStore.setVisibleUserNotification(settings.uiAffects?.visibleUserNotification); + return { + menu: baseMenu, + adminMenu: null, + version: this.version, + }; } } -export default AlertManagerConfigurator; +export default new AlertManagerConfigurator(); diff --git a/apps/web/src/services/asset-inventory/components/CloudServiceDetailTabs.vue b/apps/web/src/services/asset-inventory/components/CloudServiceDetailTabs.vue index b47d8d59c0..16eb5dc043 100644 --- a/apps/web/src/services/asset-inventory/components/CloudServiceDetailTabs.vue +++ b/apps/web/src/services/asset-inventory/components/CloudServiceDetailTabs.vue @@ -14,6 +14,7 @@ import type { CloudServiceGetParameters } from '@/schema/inventory/cloud-service import type { CloudServiceModel } from '@/schema/inventory/cloud-service/model'; import { i18n } from '@/translations'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; import { MENU_ID } from '@/lib/menu/config'; @@ -38,7 +39,6 @@ import CloudServiceLogTab import CloudServiceTagsPanel from '@/services/asset-inventory/components/CloudServiceTagsPanel.vue'; import { ASSET_INVENTORY_ROUTE } from '@/services/asset-inventory/routes/route-constant'; -import { useCloudServiceDetailPageStore } from '@/services/asset-inventory/stores/cloud-service-detail-page-store'; import BoardTaskTable from '@/services/ops-flow/components/BoardTaskTable.vue'; import { useTaskManagementTemplateStore, @@ -61,8 +61,7 @@ const props = defineProps(); const allReferenceStore = useAllReferenceStore(); const allReferenceGetters = allReferenceStore.getters; const taskManagementTemplateStore = useTaskManagementTemplateStore(); -const cloudServiceDetailPageStore = useCloudServiceDetailPageStore(); -const cloudServiceDetailPageState = cloudServiceDetailPageStore.$state; +const globalConfigStore = useGlobalConfigStore(); const router = useRouter(); @@ -70,7 +69,7 @@ const { visibleContents } = useContentsAccessibility(MENU_ID.OPS_FLOW); /* Tabs */ const state = reactive({ - visibleAlertTab: computed(() => cloudServiceDetailPageState.visibleAlertTab), + visibleAlertTab: computed(() => globalConfigStore.state.schema.ALERT_MANAGER.uiAffects?.visibleAssetAlertTab), }); const singleItemTabState = reactive({ tabs: computed(() => { diff --git a/apps/web/src/services/asset-inventory/configurator.ts b/apps/web/src/services/asset-inventory/configurator.ts index 7cd1b3aa3b..6f7dc7a969 100644 --- a/apps/web/src/services/asset-inventory/configurator.ts +++ b/apps/web/src/services/asset-inventory/configurator.ts @@ -1,49 +1,66 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; +import type { RouteConfig } from 'vue-router'; + +import type { FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect } from '@/lib/config/global-config/types/type'; import type { Menu } from '@/lib/menu/config'; import { MENU_ID } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; import adminAssetInventoryRoutes from '@/services/asset-inventory/routes/admin/routes'; import assetInventoryRoute from '@/services/asset-inventory/routes/routes'; -import { useCloudServiceDetailPageStore } from '@/services/asset-inventory/stores/cloud-service-detail-page-store'; -class AssetInventoryConfigurator { - static getAdminRoutes() { - return adminAssetInventoryRoutes; - } +class AssetInventoryConfigurator implements FeatureConfiguratorType { + private version: 'V1' | 'V2' = 'V1'; + + readonly uiAffect: FeatureUiAffect[] = [ + { + feature: 'ALERT_MANAGER', + affects: [ + { + method: 'visibleAssetAlertTab', + version: 'V2', + }, + ], + }, + ]; - static getWorkspaceRoutes() { - return assetInventoryRoute; + initialize(version: 'V1' | 'V2'): void { + this.version = version; } - static getAdminMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.adminMenu || settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ id: MENU_INFO_MAP[menuId].menuId })); - return { - id: MENU_ID.ASSET_INVENTORY, - subMenuList: subMenuIds, - }; + // eslint-disable-next-line class-methods-use-this + getRoutes(isAdmin?: boolean): RouteConfig | null { + return isAdmin ? adminAssetInventoryRoutes : assetInventoryRoute; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ - id: MENU_INFO_MAP[menuId].menuId, - needPermissionByRole: true, - })); - return { + getMenu(): FeatureMenuConfig { + const baseMenu: Menu = { id: MENU_ID.ASSET_INVENTORY, needPermissionByRole: true, - subMenuList: subMenuIds, + subMenuList: [], + order: 4, }; - } - static applyUiAffects(settings: FeatureVersionSettingsType): void|null { - const cloudServiceDetailPageStore = useCloudServiceDetailPageStore(); - cloudServiceDetailPageStore.setVisibleAlertTab(settings.uiAffects?.visibleAlertTabAtDetail); + return { + menu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.CLOUD_SERVICE, needPermissionByRole: true }, + { id: MENU_ID.SERVER, needPermissionByRole: true }, + { id: MENU_ID.SECURITY, needPermissionByRole: true }, + { id: MENU_ID.COLLECTOR, needPermissionByRole: true }, + ], + }, + adminMenu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.CLOUD_SERVICE }, + { id: MENU_ID.SERVER }, + { id: MENU_ID.SECURITY }, + { id: MENU_ID.COLLECTOR }, + ], + }, + version: this.version, + }; } } -export default AssetInventoryConfigurator; +export default new AssetInventoryConfigurator(); diff --git a/apps/web/src/services/asset-inventory/stores/cloud-service-detail-page-store.ts b/apps/web/src/services/asset-inventory/stores/cloud-service-detail-page-store.ts index 58a4eefc22..f095bb0f76 100644 --- a/apps/web/src/services/asset-inventory/stores/cloud-service-detail-page-store.ts +++ b/apps/web/src/services/asset-inventory/stores/cloud-service-detail-page-store.ts @@ -15,7 +15,6 @@ import type { CloudServiceDetailPageParams } from '@/services/asset-inventory/ty export const useCloudServiceDetailPageStore = defineStore('page-cloud-service-detail', { state: () => ({ - visibleAlertTab: false as undefined | boolean, provider: '' as string, group: '' as string, name: undefined as undefined | string, @@ -47,8 +46,5 @@ export const useCloudServiceDetailPageStore = defineStore('page-cloud-service-de this.group = group; this.name = name; }, - setVisibleAlertTab(val?: boolean) { - this.visibleAlertTab = val; - }, }, }); diff --git a/apps/web/src/services/configurator.ts b/apps/web/src/services/configurator.ts deleted file mode 100644 index 014fc5fc99..0000000000 --- a/apps/web/src/services/configurator.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { RouteConfig } from 'vue-router'; - -import { isEmpty } from 'lodash'; - -import { useMenuStore } from '@/store/menu/menu-store'; - -import { FeatureSchemaManager } from '@/lib/config/global-config/feature-schema-manager'; -import type { FeatureSchemaType, GlobalServiceConfig } from '@/lib/config/global-config/type'; -import type { Menu } from '@/lib/menu/config'; -import { DEFAULT_ADMIN_MENU_LIST, DEFAULT_MENU_LIST } from '@/lib/menu/menu-architecture'; - -import adminAdvancedRoutes from '@/services/advanced/routes/admin/routes'; -import AlertManagerConfigurator from '@/services/alert-manager/configurator'; -import AssetInventoryConfigurator from '@/services/asset-inventory/configurator'; -import CostExplorerConfigurator from '@/services/cost-explorer/configurator'; -import DashboardConfigurator from '@/services/dashboards/configurator'; -import IamConfigurator from '@/services/iam/configurator'; -import adminInfoRoutes from '@/services/info/routes/admin/routes'; -import infoRoutes from '@/services/info/routes/routes'; -import OpsFlowConfigurator from '@/services/ops-flow/configurator'; -import ProjectConfigurator from '@/services/project/configurator'; -import ServiceAccountConfigurator from '@/services/service-account/configurator'; -import adminWorkspaceHomeRoutes from '@/services/workspace-home/routes/admin/routes'; -import workspaceHomeRoute from '@/services/workspace-home/routes/routes'; - - -class ServiceConfigurator { - private config: GlobalServiceConfig = {} as GlobalServiceConfig; - - private featureSchema: FeatureSchemaType = {} as FeatureSchemaType; - - async initialize(mergedConfig) { - this.config = mergedConfig; - - const featureSchemaManager = new FeatureSchemaManager(this.config); - const featureSchema = await featureSchemaManager.applyGlobalConfig(); - - if (isEmpty(this.config)) return; - - this.featureSchema = featureSchema; - } - - getRoutes(mode: string): RouteConfig[] { - const baseRoutes = mode === 'admin' - ? [adminWorkspaceHomeRoutes, adminAdvancedRoutes, adminInfoRoutes] - : [workspaceHomeRoute, infoRoutes]; - - Object.keys(this.featureSchema).forEach((serviceName) => { - const configurator = ServiceConfigurator.getFeatureConfigurator(serviceName); - if (configurator) { - const version = this.featureSchema[serviceName].currentVersion; - const route = mode === 'admin' - ? configurator.getAdminRoutes(version) - : configurator.getWorkspaceRoutes(version); - if (route && !baseRoutes.some((existingRoute) => existingRoute.path === route.path)) { - baseRoutes.push(route); - } - } - }); - - return baseRoutes; - } - - getMenuList(mode: string): Menu[] { - const menuStore = useMenuStore(); - - const menuList: Menu[] = mode === 'admin' ? [] : DEFAULT_MENU_LIST; - - Object.keys(this.featureSchema).forEach((serviceName) => { - const configurator = ServiceConfigurator.getFeatureConfigurator(serviceName); - if (configurator) { - const feature = this.featureSchema[serviceName]; - const versionSchema = feature[feature.currentVersion]; - configurator.applyUiAffects(versionSchema); - const serviceMenu = mode === 'admin' - ? configurator.getAdminMenu(versionSchema) - : configurator.getWorkspaceMenu(versionSchema); - if (serviceMenu && !menuList.some((existingRoute) => existingRoute.id === serviceMenu.id)) { - menuList.push(serviceMenu); - } - } - }); - - if (mode === 'admin') { - menuList.push(...DEFAULT_ADMIN_MENU_LIST); - } - - menuStore.setMenuList(menuList); - - return menuList; - } - - private static getFeatureConfigurator(featureName: string): any | null { - const configurators = { - DASHBOARDS: DashboardConfigurator, - PROJECT: ProjectConfigurator, - SERVICE_ACCOUNT: ServiceAccountConfigurator, - ASSET_INVENTORY: AssetInventoryConfigurator, - COST_EXPLORER: CostExplorerConfigurator, - ALERT_MANAGER: AlertManagerConfigurator, - OPS_FLOW: OpsFlowConfigurator, - IAM: IamConfigurator, - }; - - return configurators[featureName] || null; - } -} - -export default new ServiceConfigurator(); diff --git a/apps/web/src/services/cost-explorer/configurator.ts b/apps/web/src/services/cost-explorer/configurator.ts index a96463f2d4..25fddf6b6f 100644 --- a/apps/web/src/services/cost-explorer/configurator.ts +++ b/apps/web/src/services/cost-explorer/configurator.ts @@ -1,49 +1,66 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; +import type { RouteConfig } from 'vue-router'; + +import type { FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect } from '@/lib/config/global-config/types/type'; import type { Menu } from '@/lib/menu/config'; import { MENU_ID } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; import adminCostExplorerRoutes from '@/services/cost-explorer/routes/admin/routes'; import costExplorerRoutes from '@/services/cost-explorer/routes/routes'; -import { useBudgetDetailPageStore } from '@/services/cost-explorer/stores/budget-detail-page-store'; -class CostExplorerConfigurator { - static getAdminRoutes() { - return adminCostExplorerRoutes; - } +class CostExplorerConfigurator implements FeatureConfiguratorType { + private version: 'V1' | 'V2' = 'V1'; + + readonly uiAffect: FeatureUiAffect[] = [ + { + feature: 'ALERT_MANAGER', + affects: [ + { + method: 'visibleBudgetNotification', + version: 'V2', + }, + ], + }, + ]; - static getWorkspaceRoutes() { - return costExplorerRoutes; + initialize(version: 'V1' | 'V2'): void { + this.version = version; } - static getAdminMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.adminMenu || settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ id: MENU_INFO_MAP[menuId].menuId })); - return { - id: MENU_ID.COST_EXPLORER, - subMenuList: subMenuIds, - }; + // eslint-disable-next-line class-methods-use-this + getRoutes(isAdmin?: boolean): RouteConfig | null { + return isAdmin ? adminCostExplorerRoutes : costExplorerRoutes; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ - id: MENU_INFO_MAP[menuId].menuId, - needPermissionByRole: true, - })); - return { + getMenu(): FeatureMenuConfig { + const baseMenu: Menu = { id: MENU_ID.COST_EXPLORER, needPermissionByRole: true, - subMenuList: subMenuIds, + subMenuList: [], + order: 5, }; - } - static applyUiAffects(settings: FeatureVersionSettingsType): void|null { - const budgetDetailPageStore = useBudgetDetailPageStore(); - budgetDetailPageStore.setVisibleBudgetNotification(settings.uiAffects?.visibleBudgetNotification); + return { + menu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.COST_ANALYSIS, needPermissionByRole: true }, + { id: MENU_ID.BUDGET, needPermissionByRole: true }, + { id: MENU_ID.COST_REPORT, needPermissionByRole: true }, + ], + }, + adminMenu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.COST_ANALYSIS }, + { id: MENU_ID.COST_ADVANCED_SETTINGS }, + { id: MENU_ID.BUDGET }, + { id: MENU_ID.COST_REPORT }, + { id: MENU_ID.DATA_SOURCES }, + ], + }, + version: this.version, + }; } } -export default CostExplorerConfigurator; +export default new CostExplorerConfigurator(); diff --git a/apps/web/src/services/cost-explorer/pages/BudgetDetailPage.vue b/apps/web/src/services/cost-explorer/pages/BudgetDetailPage.vue index 6ab8ca7237..cb35a14e4a 100644 --- a/apps/web/src/services/cost-explorer/pages/BudgetDetailPage.vue +++ b/apps/web/src/services/cost-explorer/pages/BudgetDetailPage.vue @@ -7,6 +7,7 @@ import { PLink, PScopedNotification } from '@cloudforet/mirinae'; import type { BudgetModel } from '@/api-clients/cost-analysis/budget/schema/model'; import { i18n } from '@/translations'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import { useUserStore } from '@/store/user/user-store'; import ErrorHandler from '@/common/composables/error/errorHandler'; @@ -29,12 +30,13 @@ const props = withDefaults(defineProps(), { }); const userStore = useUserStore(); +const globalConfigStore = useGlobalConfigStore(); const budgetPageStore = useBudgetDetailPageStore(); const budgetPageState = budgetPageStore.$state; const state = reactive({ loading: true, budgetData: computed(() => budgetPageState.budgetData), - visibleBudgetNotification: computed(() => budgetPageState.visibleBudgetNotification), + visibleBudgetNotification: computed(() => globalConfigStore.state.schema.ALERT_MANAGER.uiAffects?.visibleBudgetNotification ?? false), isWorkspaceTarget: computed(() => (state.budgetData?.resource_group === 'WORKSPACE')), adminModeLink: computed(() => ({ name: ADMIN_COST_EXPLORER_ROUTE.BUDGET.DETAIL._NAME, diff --git a/apps/web/src/services/cost-explorer/stores/budget-detail-page-store.ts b/apps/web/src/services/cost-explorer/stores/budget-detail-page-store.ts index 728af149c0..b582f121a8 100644 --- a/apps/web/src/services/cost-explorer/stores/budget-detail-page-store.ts +++ b/apps/web/src/services/cost-explorer/stores/budget-detail-page-store.ts @@ -16,15 +16,11 @@ import ErrorHandler from '@/common/composables/error/errorHandler'; export const useBudgetDetailPageStore = defineStore('page-budget-detail', { state: () => ({ - visibleBudgetNotification: false as undefined | boolean, loading: true, budgetData: null as BudgetModel|null, budgetUsageData: null as BudgetUsageModel[]|null, }), actions: { - setVisibleBudgetNotification(val?: boolean) { - this.visibleBudgetNotification = val; - }, async getBudgetData(budgetId: string): Promise { this.loading = true; try { diff --git a/apps/web/src/services/dashboards/configurator.ts b/apps/web/src/services/dashboards/configurator.ts index 4c7df7f688..3ea2d9349b 100644 --- a/apps/web/src/services/dashboards/configurator.ts +++ b/apps/web/src/services/dashboards/configurator.ts @@ -1,32 +1,41 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; +import type { RouteConfig } from 'vue-router'; + +import type { + FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect, FeatureVersion, +} from '@/lib/config/global-config/types/type'; import type { Menu } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; +import { MENU_ID } from '@/lib/menu/config'; import adminDashboardsRoute from '@/services/dashboards/routes/admin/routes'; import dashboardsRoute from '@/services/dashboards/routes/routes'; -class DashboardConfigurator { - static getAdminRoutes() { - return adminDashboardsRoute; - } +class DashboardConfigurator implements FeatureConfiguratorType { + private version: FeatureVersion = 'V1'; - static getWorkspaceRoutes() { - return dashboardsRoute; - } + readonly uiAffect: FeatureUiAffect[] = []; - static getAdminMenu(settings: FeatureVersionSettingsType): Menu { - const menuId = Object.keys(settings.adminMenu || settings.menu)[0]; - return { id: MENU_INFO_MAP[menuId].menuId }; + initialize(version: FeatureVersion): void { + this.version = version; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menuId = Object.keys(settings.menu)[0]; - return { id: MENU_INFO_MAP[menuId].menuId, needPermissionByRole: true }; + // eslint-disable-next-line class-methods-use-this + getRoutes(isAdmin?: boolean): RouteConfig | null { + return isAdmin ? adminDashboardsRoute : dashboardsRoute; } - static applyUiAffects(): void|null { - return null; + getMenu(): FeatureMenuConfig { + const baseMenu: Menu = { + id: MENU_ID.DASHBOARDS, + needPermissionByRole: true, + subMenuList: [], + order: 1, + }; + + return { + menu: baseMenu, + version: this.version, + }; } } -export default DashboardConfigurator; +export default new DashboardConfigurator(); diff --git a/apps/web/src/services/iam/configurator.ts b/apps/web/src/services/iam/configurator.ts index 95164ce46e..cfed2d412a 100644 --- a/apps/web/src/services/iam/configurator.ts +++ b/apps/web/src/services/iam/configurator.ts @@ -1,55 +1,55 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; +import type { RouteConfig } from 'vue-router'; + +import type { FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect } from '@/lib/config/global-config/types/type'; import type { Menu } from '@/lib/menu/config'; import { MENU_ID } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; - -import adminIamRoutes, { ADMIN_USER_GROUP_ROUTE } from '@/services/iam/routes/admin/routes'; -import iamRoutes, { USER_GROUP_ROUTE } from '@/services/iam/routes/routes'; - -class IamConfigurator { - static getAdminRoutes(version: string) { - const adminRoutes = adminIamRoutes; - if (version === 'V1') { - adminRoutes.children?.push(ADMIN_USER_GROUP_ROUTE); - } - return adminRoutes; - } - static getWorkspaceRoutes(version: string) { - const routes = iamRoutes; - if (version === 'V1') { - routes.children?.push(USER_GROUP_ROUTE); - } - return routes; +import adminIamRoutes from '@/services/iam/routes/admin/routes'; +import iamRoutes from '@/services/iam/routes/routes'; + +class IamConfigurator implements FeatureConfiguratorType { + private version: 'V1' | 'V2' = 'V1'; + + readonly uiAffect: FeatureUiAffect[] = []; + + initialize(version: 'V1' | 'V2'): void { + this.version = version; } - static getAdminMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.adminMenu || settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ id: MENU_INFO_MAP[menuId].menuId })); - return { - id: MENU_ID.IAM, - subMenuList: subMenuIds, - }; + // eslint-disable-next-line class-methods-use-this + getRoutes(isAdmin?: boolean): RouteConfig | null { + return isAdmin ? adminIamRoutes : iamRoutes; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ - id: MENU_INFO_MAP[menuId].menuId, - needPermissionByRole: true, - })); - return { + getMenu(): FeatureMenuConfig { + const baseMenu: Menu = { id: MENU_ID.IAM, needPermissionByRole: true, - subMenuList: subMenuIds, + subMenuList: [], + order: 8, }; - } - static applyUiAffects(): void|null { - return null; + return { + menu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.USER, needPermissionByRole: true }, + { id: MENU_ID.USER_GROUP, needPermissionByRole: true }, + { id: MENU_ID.APP, needPermissionByRole: true }, + ], + }, + adminMenu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.USER }, + { id: MENU_ID.USER_GROUP }, + { id: MENU_ID.APP }, + { id: MENU_ID.ROLE }, + ], + }, + version: this.version, + }; } } -export default IamConfigurator; +export default new IamConfigurator(); diff --git a/apps/web/src/services/iam/constants/role-constant.ts b/apps/web/src/services/iam/constants/role-constant.ts index cfc3071b5b..fe7f75ccd0 100644 --- a/apps/web/src/services/iam/constants/role-constant.ts +++ b/apps/web/src/services/iam/constants/role-constant.ts @@ -3,7 +3,7 @@ import type { KeyItemSet } from '@cloudforet/mirinae/types/controls/search/query import { ROLE_TYPE } from '@/api-clients/identity/role/constant'; -import { FEATURES } from '@/lib/config/global-config/constants'; +import { FEATURES } from '@/lib/config/global-config/constants/constants'; import type { ExcelDataField } from '@/lib/helper/file-download-helper/type'; import { MENU_ID } from '@/lib/menu/config'; diff --git a/apps/web/src/services/iam/routes/admin/routes.ts b/apps/web/src/services/iam/routes/admin/routes.ts index 0a8bda3924..32f2697452 100644 --- a/apps/web/src/services/iam/routes/admin/routes.ts +++ b/apps/web/src/services/iam/routes/admin/routes.ts @@ -34,6 +34,15 @@ export const adminIamRoutesChildren = [ }, component: UserMainPage as any, }, + { + path: 'user-group', + name: ADMIN_IAM_ROUTE.USER_GROUP._NAME, + meta: { + menuId: MENU_ID.USER_GROUP, + translationId: MENU_INFO_MAP[MENU_ID.USER_GROUP].translationId, + }, + component: UserGroupMainPage as any, + }, { path: 'app', name: ADMIN_IAM_ROUTE.APP._NAME, diff --git a/apps/web/src/services/iam/routes/routes.ts b/apps/web/src/services/iam/routes/routes.ts index 2c798d33c5..3217d92cb9 100644 --- a/apps/web/src/services/iam/routes/routes.ts +++ b/apps/web/src/services/iam/routes/routes.ts @@ -35,6 +35,15 @@ export const iamRoutesChildren = [ }, component: UserMainPage as any, }, + { + path: 'user-group', + name: IAM_ROUTE.USER_GROUP._NAME, + meta: { + menuId: MENU_ID.USER_GROUP, + translationId: MENU_INFO_MAP[MENU_ID.USER_GROUP].translationId, + }, + component: UserGroupMainPage as any, + }, { path: 'app', name: IAM_ROUTE.APP._NAME, diff --git a/apps/web/src/services/iam/types/role-type.ts b/apps/web/src/services/iam/types/role-type.ts index 8352c2aae4..a728767c9e 100644 --- a/apps/web/src/services/iam/types/role-type.ts +++ b/apps/web/src/services/iam/types/role-type.ts @@ -2,7 +2,7 @@ import type { TranslateResult } from 'vue-i18n'; import type { RoleType } from '@/api-clients/identity/role/type'; -import type { FeatureKeyType } from '@/lib/config/global-config/type'; +import type { FeatureKeyType } from '@/lib/config/global-config/types/type'; import type { MenuId } from '@/lib/menu/config'; export interface RoleFormData { diff --git a/apps/web/src/services/my-page/components/NotificationAddForm.vue b/apps/web/src/services/my-page/components/NotificationAddForm.vue index 3822865c7a..4def96f229 100644 --- a/apps/web/src/services/my-page/components/NotificationAddForm.vue +++ b/apps/web/src/services/my-page/components/NotificationAddForm.vue @@ -15,6 +15,8 @@ import type { ChannelSchedule } from '@/schema/notification/type'; import type { UserChannelCreateParameters as UserChannelCreateParametersV1 } from '@/schema/notification/user-channel/api-verbs/create'; import { i18n } from '@/translations'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; + import { showSuccessMessage } from '@/lib/helper/notice-alert-helper'; import type { ScheduleSettingFormType } from '@/common/components/schedule-setting-form/schedule-setting-form'; @@ -23,7 +25,6 @@ import ErrorHandler from '@/common/composables/error/errorHandler'; import NotificationAddFormData from '@/services/my-page/components/NotificationAddFormData.vue'; import NotificationAddSchedule from '@/services/my-page/components/NotificationAddSchedule.vue'; import NotificationAddTopic from '@/services/my-page/components/NotificationAddTopic.vue'; -import { useMyPageStore } from '@/services/my-page/stores/my-page-store'; import type { NotificationAddFormDataPayload, NotificationAddFormTopicPayload, NotificationAddFormSchedulePayload } from '@/services/my-page/types/notification-add-form-type'; const props = withDefaults(defineProps<{ @@ -37,10 +38,10 @@ const props = withDefaults(defineProps<{ }); const router = useRouter(); -const myPageStore = useMyPageStore(); +const globalConfigStore = useGlobalConfigStore(); const state = reactive({ - visibleUserNotification: computed(() => myPageStore.state.visibleUserNotification), + visibleUserNotification: computed(() => globalConfigStore.state.schema.ALERT_MANAGER.uiAffects?.visibleUserNotification ?? false), isDataValid: false, notificationLevel: 'LV1' as NotificationLevel, // diff --git a/apps/web/src/services/my-page/components/NotificationChannelList.vue b/apps/web/src/services/my-page/components/NotificationChannelList.vue index 61a5fbb302..f05cb08b5f 100644 --- a/apps/web/src/services/my-page/components/NotificationChannelList.vue +++ b/apps/web/src/services/my-page/components/NotificationChannelList.vue @@ -27,6 +27,7 @@ import type { UserChannelListParameters as UserChannelListParametersV1 } from '@ import type { UserChannelModel as UserChannelModelV1 } from '@/schema/notification/user-channel/model'; import { i18n } from '@/translations'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; import type { PluginReferenceMap } from '@/store/reference/plugin-reference-store'; import { useUserStore } from '@/store/user/user-store'; @@ -37,7 +38,6 @@ import ErrorHandler from '@/common/composables/error/errorHandler'; import NotificationChannelItem from '@/services/my-page/components/NotificationChannelItem.vue'; import { MY_PAGE_ROUTE } from '@/services/my-page/routes/route-constant'; -import { useMyPageStore } from '@/services/my-page/stores/my-page-store'; import type { NotiChannelItem, NotiChannelItemV1 } from '@/services/my-page/types/notification-channel-item-type'; import { PROJECT_ROUTE_V1 } from '@/services/project/v1/routes/route-constant'; @@ -51,7 +51,7 @@ interface EnrichedProtocolItem extends ProtocolModel { } const allReferenceStore = useAllReferenceStore(); const userStore = useUserStore(); -const myPageStore = useMyPageStore(); +const globalConfigStore = useGlobalConfigStore(); const props = withDefaults(defineProps<{ projectId?: string; @@ -62,7 +62,7 @@ const props = withDefaults(defineProps<{ }); const route = useRoute(); const state = reactive({ - visibleUserNotification: computed(() => myPageStore.state.visibleUserNotification), + visibleUserNotification: computed(() => globalConfigStore.state.schema.ALERT_MANAGER.uiAffects?.visibleUserNotification ?? false), loading: true, channelLoading: true, userId: computed(() => (route.params.userId ? decodeURIComponent(route.params.userId) : userStore.state.userId)), diff --git a/apps/web/src/services/my-page/composables/notification-item.ts b/apps/web/src/services/my-page/composables/notification-item.ts index 2610f69de5..83b74cdf9c 100644 --- a/apps/web/src/services/my-page/composables/notification-item.ts +++ b/apps/web/src/services/my-page/composables/notification-item.ts @@ -13,12 +13,12 @@ import type { ProjectChannelUpdateParameters } from '@/schema/notification/proje import type { UserChannelUpdateParameters as UserChannelUpdateParametersV1 } from '@/schema/notification/user-channel/api-verbs/update'; import { i18n } from '@/translations'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; + import { showSuccessMessage } from '@/lib/helper/notice-alert-helper'; import ErrorHandler from '@/common/composables/error/errorHandler'; -import { useMyPageStore } from '@/services/my-page/stores/my-page-store'; - interface NotificationItemState { visibleUserNotification: boolean; isEditMode: boolean; @@ -30,11 +30,11 @@ type Emit = { (event: 'edit', value?: Data): void; }; -const myPageStore = useMyPageStore(); +const globalConfigStore = useGlobalConfigStore(); export const useNotificationItem = (_state: NotificationItemState, emit: Emit) => { const state = reactive({ - visibleUserNotification: computed(() => myPageStore.state.visibleUserNotification), + visibleUserNotification: computed(() => globalConfigStore.state.schema.ALERT_MANAGER.uiAffects?.visibleUserNotification ?? false), isEditMode: _state.isEditMode, dataForEdit: _state.dataForEdit, userChannelId: _state.userChannelId, diff --git a/apps/web/src/services/my-page/stores/my-page-store.ts b/apps/web/src/services/my-page/stores/my-page-store.ts deleted file mode 100644 index a300041b3e..0000000000 --- a/apps/web/src/services/my-page/stores/my-page-store.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { reactive } from 'vue'; - -import { defineStore } from 'pinia'; - -export const useMyPageStore = defineStore('my-page', () => { - const state = reactive({ - visibleUserNotification: false as undefined | boolean, - }); - - const mutations = { - setVisibleUserNotification: (val?: boolean) => { - state.visibleUserNotification = val; - }, - }; - - return { - state, - ...mutations, - }; -}); diff --git a/apps/web/src/services/ops-flow/configurator.ts b/apps/web/src/services/ops-flow/configurator.ts index 4e6d6c108b..71a85af069 100644 --- a/apps/web/src/services/ops-flow/configurator.ts +++ b/apps/web/src/services/ops-flow/configurator.ts @@ -1,47 +1,52 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; +import type { RouteConfig } from 'vue-router'; + +import type { FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect } from '@/lib/config/global-config/types/type'; import type { Menu } from '@/lib/menu/config'; import { MENU_ID } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; import adminOpsFlowRoutes from '@/services/ops-flow/routes/admin/routes'; import opsFlowRoutes from '@/services/ops-flow/routes/routes'; -class OpsFlowConfigurator { - static getAdminRoutes() { - return adminOpsFlowRoutes; - } +class OpsFlowConfigurator implements FeatureConfiguratorType { + private version: 'V1' | 'V2' = 'V1'; + + readonly uiAffect: FeatureUiAffect[] = []; - static getWorkspaceRoutes() { - return opsFlowRoutes; + initialize(version: 'V1' | 'V2'): void { + this.version = version; } - static getAdminMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.adminMenu || settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ id: MENU_INFO_MAP[menuId].menuId })); - return { - id: MENU_ID.OPS_FLOW, - subMenuList: subMenuIds, - }; + // eslint-disable-next-line class-methods-use-this + getRoutes(isAdmin?: boolean): RouteConfig { + return isAdmin ? adminOpsFlowRoutes : opsFlowRoutes; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menu = settings.menu; - const subMenuIds = Object.keys(menu).filter((menuId) => (menu)[menuId]) - .map((menuId) => ({ - id: MENU_INFO_MAP[menuId].menuId, - needPermissionByRole: true, - })); - return { + getMenu(): FeatureMenuConfig { + const baseMenu: Menu = { id: MENU_ID.OPS_FLOW, needPermissionByRole: true, - subMenuList: subMenuIds, + subMenuList: [], + order: 7, }; - } - static applyUiAffects(): void|null { - return null; + return { + menu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.OPS_FLOW_LANDING, needPermissionByRole: true }, + { id: MENU_ID.TASK_BOARD, needPermissionByRole: true }, + { id: MENU_ID.APP, needPermissionByRole: true }, + ], + }, + adminMenu: { + ...baseMenu, + subMenuList: [ + { id: MENU_ID.TASK_MANAGEMENT }, + ], + }, + version: this.version, + }; } } -export default OpsFlowConfigurator; +export default new OpsFlowConfigurator(); diff --git a/apps/web/src/services/project/configurator.ts b/apps/web/src/services/project/configurator.ts index 7eeb3e3879..b4f1728699 100644 --- a/apps/web/src/services/project/configurator.ts +++ b/apps/web/src/services/project/configurator.ts @@ -1,33 +1,47 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; -import type { Menu } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; +import type { RouteConfig } from 'vue-router'; + +import type { FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect } from '@/lib/config/global-config/types/type'; +import { MENU_ID } from '@/lib/menu/config'; import projectRoutesV1 from '@/services/project/v1/routes/routes'; -import { useProjectDetailPageStore } from '@/services/project/v1/stores/project-detail-page-store'; import projectRoutes from '@/services/project/v2/routes/routes'; -class ProjectConfigurator { - static getAdminRoutes() { - return null; - } +class ProjectConfigurator implements FeatureConfiguratorType { + private version: 'V1' | 'V2' = 'V1'; - static getWorkspaceRoutes(version: string) { - return version === 'V1' ? projectRoutesV1 : projectRoutes; - } + readonly uiAffect: FeatureUiAffect[] = [ + { + feature: 'ALERT_MANAGER', + affects: [ + { + method: 'visibleProjectAlertTab', + version: 'V1', + }, + ], + }, + ]; - static getAdminMenu(): Menu|null { - return null; + initialize(version: 'V1' | 'V2'): void { + this.version = version; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menuId = Object.keys(settings.menu)[0]; - return { id: MENU_INFO_MAP[menuId].menuId, needPermissionByRole: true }; + getRoutes(isAdmin?: boolean): RouteConfig|null { + if (isAdmin) return null; + return this.version === 'V1' ? projectRoutesV1 : projectRoutes; } - static applyUiAffects(settings: FeatureVersionSettingsType): void|null { - const projectDetailPageStore = useProjectDetailPageStore(); - projectDetailPageStore.setVisibleAlertTab(settings.uiAffects?.visibleAlertTabAtDetail); + getMenu(): FeatureMenuConfig { + return { + menu: { + id: MENU_ID.PROJECT, + needPermissionByRole: true, + subMenuList: [], + order: 2, + }, + adminMenu: null, + version: this.version, + }; } } -export default ProjectConfigurator; +export default new ProjectConfigurator(); diff --git a/apps/web/src/services/project/v1/components/ProjectDetailTabHeader.vue b/apps/web/src/services/project/v1/components/ProjectDetailTabHeader.vue index a0e5257b0a..699f3e6449 100644 --- a/apps/web/src/services/project/v1/components/ProjectDetailTabHeader.vue +++ b/apps/web/src/services/project/v1/components/ProjectDetailTabHeader.vue @@ -34,6 +34,7 @@ import { i18n } from '@/translations'; import { useAppContextStore } from '@/store/app-context/app-context-store'; import { useUserWorkspaceStore } from '@/store/app-context/workspace/user-workspace-store'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; import type { CostDataSourceReferenceMap } from '@/store/reference/cost-data-source-reference-store'; import type { UserReferenceMap } from '@/store/reference/user-reference-store'; @@ -84,6 +85,7 @@ const projectDetailPageStore = useProjectDetailPageStore(); const projectDetailPageState = projectDetailPageStore.state; const projectDetailPageGetters = projectDetailPageStore.getters; const projectTreeStore = useProjectTreeStore(); +const globalConfigStore = useGlobalConfigStore(); const userWorkspaceStore = useUserWorkspaceStore(); const allReferenceStore = useAllReferenceStore(); @@ -96,7 +98,7 @@ const queryHelper = new QueryHelper(); const storeState = reactive({ users: computed(() => allReferenceStore.getters.user), projects: computed(() => allReferenceStore.getters.project), - visibleAlertTab: computed(() => visibleContents.value && projectDetailPageState.visibleAlertTab), + visibleAlertTab: computed(() => visibleContents.value && (globalConfigStore.state.schema?.ALERT_MANAGER?.uiAffects?.visibleProjectAlertTab ?? false)), costDataSource: computed(() => allReferenceStore.getters.costDataSource), }); const state = reactive({ diff --git a/apps/web/src/services/project/v1/pages/ProjectDetailTabPage.vue b/apps/web/src/services/project/v1/pages/ProjectDetailTabPage.vue index c9fd3b2ceb..be9f6a0156 100644 --- a/apps/web/src/services/project/v1/pages/ProjectDetailTabPage.vue +++ b/apps/web/src/services/project/v1/pages/ProjectDetailTabPage.vue @@ -18,6 +18,7 @@ import { i18n } from '@/translations'; import { useAppContextStore } from '@/store/app-context/app-context-store'; import { useUserWorkspaceStore } from '@/store/app-context/workspace/user-workspace-store'; import { useDashboardStore } from '@/store/dashboard/dashboard-store'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; import type { ProjectGroupReferenceItem, ProjectGroupReferenceMap } from '@/store/reference/project-group-reference-store'; import { useUserStore } from '@/store/user/user-store'; @@ -53,6 +54,7 @@ const projectDetailPageState = projectDetailPageStore.state; const userWorkspaceStore = useUserWorkspaceStore(); const dashboardStore = useDashboardStore(); const userStore = useUserStore(); +const globalConfigStore = useGlobalConfigStore(); const { visibleContents } = useContentsAccessibility(MENU_ID.ALERT_MANAGER); @@ -63,7 +65,7 @@ const { } = useDashboardQuery(); const storeState = reactive({ - visibleAlertTab: computed(() => visibleContents.value && projectDetailPageState.visibleAlertTab), + visibleAlertTab: computed(() => visibleContents.value && (globalConfigStore.state.schema?.ALERT_MANAGER?.uiAffects?.visibleProjectAlertTab ?? false)), projectGroups: computed(() => allReferenceStore.getters.projectGroup), currentWorkspaceId: computed(() => userWorkspaceStore.getters.currentWorkspaceId), language: computed(() => userStore.state.language), diff --git a/apps/web/src/services/project/v1/pages/ProjectSummaryPage.vue b/apps/web/src/services/project/v1/pages/ProjectSummaryPage.vue index 7b2d017784..a67ff131c7 100644 --- a/apps/web/src/services/project/v1/pages/ProjectSummaryPage.vue +++ b/apps/web/src/services/project/v1/pages/ProjectSummaryPage.vue @@ -11,6 +11,8 @@ import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; import type { ProjectAlertConfigListParameters } from '@/schema/monitoring/project-alert-config/api-verbs/list'; import type { ProjectAlertConfigModel } from '@/schema/monitoring/project-alert-config/model'; +import { useGlobalConfigStore } from '@/store/global-config/global-config-store'; + import { MENU_ID } from '@/lib/menu/config'; import { useContentsAccessibility } from '@/common/composables/contents-accessibility'; @@ -26,21 +28,19 @@ import ProjectSummaryBillingWidget from '@/services/project/v1/components/Projec import ProjectSummaryPersonalHealthDashboardWidget from '@/services/project/v1/components/ProjectSummaryPersonalHealthDashboardWidget.vue'; import ProjectSummaryServiceAccountsWidget from '@/services/project/v1/components/ProjectSummaryServiceAccountsWidget.vue'; import ProjectSummaryTrustedAdvisorWidget from '@/services/project/v1/components/ProjectSummaryTrustedAdvisorWidget.vue'; -import { useProjectDetailPageStore } from '@/services/project/v1/stores/project-detail-page-store'; interface Props { id: string; } const props = defineProps(); -const projectDetailPageStore = useProjectDetailPageStore(); -const projectDetailPageState = projectDetailPageStore.state; +const globalConfigStore = useGlobalConfigStore(); const { visibleContents: visibleAssetContents } = useContentsAccessibility(MENU_ID.ASSET_INVENTORY); const { visibleContents: visibleAlertContents } = useContentsAccessibility(MENU_ID.ALERT_MANAGER); const state = reactive({ - visibleAlertTab: computed(() => visibleAlertContents.value && projectDetailPageState.visibleAlertTab), + visibleAlertTab: computed(() => visibleAlertContents.value && (globalConfigStore.state.schema?.ALERT_MANAGER?.uiAffects?.visibleProjectAlertTab ?? false)), hasAlertConfig: false, deprecatedNotiVisible: true, }); diff --git a/apps/web/src/services/project/v1/stores/project-detail-page-store.ts b/apps/web/src/services/project/v1/stores/project-detail-page-store.ts index 34374cd137..e403ce4658 100644 --- a/apps/web/src/services/project/v1/stores/project-detail-page-store.ts +++ b/apps/web/src/services/project/v1/stores/project-detail-page-store.ts @@ -26,7 +26,6 @@ export interface AlertCount { } interface ProjectDetailPageState { - visibleAlertTab?: boolean, loading: boolean, projectId?: string, currentProject?: ProjectModel, @@ -36,7 +35,6 @@ interface ProjectDetailPageState { } export const useProjectDetailPageStore = defineStore('page-project-detail', () => { const state = reactive({ - visibleAlertTab: false, loading: false, projectId: undefined, currentProject: undefined, @@ -54,9 +52,6 @@ export const useProjectDetailPageStore = defineStore('page-project-detail', () = }); /* mutations */ - const setVisibleAlertTab = (val?: boolean) => { - state.visibleAlertTab = val; - }; const setProjectId = (projectId?: string) => { state.projectId = projectId; }; @@ -125,7 +120,6 @@ export const useProjectDetailPageStore = defineStore('page-project-detail', () = }; const mutations = { - setVisibleAlertTab, setProjectId, setProject, setWebhookList, diff --git a/apps/web/src/services/service-account/configurator.ts b/apps/web/src/services/service-account/configurator.ts index 9ad06c7409..a62d046599 100644 --- a/apps/web/src/services/service-account/configurator.ts +++ b/apps/web/src/services/service-account/configurator.ts @@ -1,32 +1,36 @@ -import type { FeatureVersionSettingsType } from '@/lib/config/global-config/type'; -import type { Menu } from '@/lib/menu/config'; -import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; +import type { RouteConfig } from 'vue-router'; + +import type { FeatureConfiguratorType, FeatureMenuConfig, FeatureUiAffect } from '@/lib/config/global-config/types/type'; +import { MENU_ID } from '@/lib/menu/config'; import adminServiceAccountRoute from '@/services/service-account/routes/admin/routes'; import serviceAccountRoute from '@/services/service-account/routes/routes'; -class ServiceAccountConfigurator { - static getAdminRoutes() { - return adminServiceAccountRoute; - } +class ServiceAccountConfigurator implements FeatureConfiguratorType { + private version: 'V1' | 'V2' = 'V1'; - static getWorkspaceRoutes() { - return serviceAccountRoute; - } + readonly uiAffect: FeatureUiAffect[] = []; - static getAdminMenu(settings: FeatureVersionSettingsType): Menu { - const menuId = Object.keys(settings.adminMenu || settings.menu)[0]; - return { id: MENU_INFO_MAP[menuId].menuId }; + initialize(version: 'V1' | 'V2'): void { + this.version = version; } - static getWorkspaceMenu(settings: FeatureVersionSettingsType): Menu { - const menuId = Object.keys(settings.menu)[0]; - return { id: MENU_INFO_MAP[menuId].menuId, needPermissionByRole: true }; + // eslint-disable-next-line class-methods-use-this + getRoutes(isAdmin?: boolean): RouteConfig|null { + return isAdmin ? adminServiceAccountRoute : serviceAccountRoute; } - static applyUiAffects(): void|null { - return null; + getMenu(): FeatureMenuConfig { + return { + menu: { + id: MENU_ID.SERVICE_ACCOUNT, + needPermissionByRole: true, + subMenuList: [], + order: 3, + }, + version: this.version, + }; } } -export default ServiceAccountConfigurator; +export default new ServiceAccountConfigurator(); diff --git a/apps/web/src/store/app-context/app-context-store.ts b/apps/web/src/store/app-context/app-context-store.ts index e7b8a41bf1..50f2e40c2d 100644 --- a/apps/web/src/store/app-context/app-context-store.ts +++ b/apps/web/src/store/app-context/app-context-store.ts @@ -38,7 +38,6 @@ export const useAppContextStore = defineStore('app-context-store', () => { }, }; - return { getters, ...actions, diff --git a/apps/web/src/store/display/display-store.ts b/apps/web/src/store/display/display-store.ts index bd067dd055..ad24379dfb 100644 --- a/apps/web/src/store/display/display-store.ts +++ b/apps/web/src/store/display/display-store.ts @@ -29,7 +29,6 @@ import type { DisplayMenu, DisplayStoreState, SidebarProps, SidebarType, DisplayStoreGetters, } from '@/store/display/type'; -import { useMenuStore } from '@/store/menu/menu-store'; import { useUserStore } from '@/store/user/user-store'; import type { Menu, MenuId, MenuInfo } from '@/lib/menu/config'; @@ -38,6 +37,8 @@ import { MENU_INFO_MAP } from '@/lib/menu/menu-info'; import ErrorHandler from '@/common/composables/error/errorHandler'; +import { useGlobalConfigStore } from '../global-config/global-config-store'; + const verbose = false; const filterMenuByRoute = (menuList: DisplayMenu[], router: VueRouter): DisplayMenu[] => menuList.reduce((results, _menu) => { const userWorkspaceStore = useUserWorkspaceStore(); @@ -308,12 +309,12 @@ export const useDisplayStore = defineStore('display-store', () => { const getAllMenuList = (route?: Route): DisplayMenu[] => { const isMyPage = route?.path.startsWith('/my-page'); const appContextStore = useAppContextStore(); - const menuStore = useMenuStore(); + const globalConfigStore = useGlobalConfigStore(); const appContextState = appContextStore.$state; const userWorkspaceStore = useUserWorkspaceStore(); const isAdminMode = appContextState.getters.isAdminMode; const currentWorkspaceId = userWorkspaceStore.getters.currentWorkspaceId; - const menuList = menuStore.state.menuList; + const menuList = globalConfigStore.getters.menuList; let _allGnbMenuList: DisplayMenu[]; _allGnbMenuList = getDisplayMenuList(menuList, isAdminMode, currentWorkspaceId); diff --git a/apps/web/src/store/global-config/global-config-store.ts b/apps/web/src/store/global-config/global-config-store.ts new file mode 100644 index 0000000000..d6c8ba1745 --- /dev/null +++ b/apps/web/src/store/global-config/global-config-store.ts @@ -0,0 +1,85 @@ +import { computed, reactive } from 'vue'; + +import { orderBy } from 'lodash'; +import { defineStore } from 'pinia'; + +import { useAppContextStore } from '@/store/app-context/app-context-store'; + +import type { FeatureSchemaType } from '@/lib/config/global-config/types/type'; +import type { Menu, MenuId } from '@/lib/menu/config'; +import { DEFAULT_MENU_LIST, DEFAULT_ADMIN_MENU_LIST } from '@/lib/menu/menu-architecture'; + +interface GlobalConfigStoreState { + schema: FeatureSchemaType; +} +export type FlattenedMenuMap = Partial>; + +export const useGlobalConfigStore = defineStore('global-config-store', () => { + const appContextStore = useAppContextStore(); + + const state = reactive({ + schema: {} as FeatureSchemaType, + }); + + const _getters = reactive({ + isAdminMode: computed(() => appContextStore.getters.isAdminMode), + }); + + const getters = reactive({ + menuList: computed(() => { + const menuList: Menu[] = _getters.isAdminMode ? [] : DEFAULT_MENU_LIST; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Object.entries(state.schema).forEach(([_, featureSetting]) => { + if (featureSetting) { + const menu = _getters.isAdminMode ? featureSetting.adminMenu : featureSetting.menu; + if (menu && !menuList.some((existingMenu) => existingMenu.id === menu.id)) { + menuList.push(menu); + } + } + }); + + if (_getters.isAdminMode) { + menuList.push(...DEFAULT_ADMIN_MENU_LIST); + } + + const orderedMenus = menuList.filter((menu) => menu.order !== undefined); + const unorderedMenus = menuList.filter((menu) => menu.order === undefined); + + return [...orderBy(orderedMenus, ['order'], ['asc']), ...unorderedMenus]; + }), + generateFlattenedMenuMap: computed(() => { + const map: FlattenedMenuMap = {}; + + const getSubMenuIdsToMap = (menu: Menu, flattenedMenuMap: FlattenedMenuMap) => { + let results: MenuId[] = []; + const subMenuList = menu.subMenuList; + if (subMenuList) { + results = subMenuList.map((d) => d.id); + subMenuList.forEach((subMenu) => { + getSubMenuIdsToMap(subMenu, flattenedMenuMap); + }); + } + flattenedMenuMap[menu.id] = results; + }; + + getters.menuList.forEach((menu) => { + getSubMenuIdsToMap(menu, map); + }); + + return map; + }), + }); + + const actions = { + setSchema(schema: FeatureSchemaType) { + state.schema = schema; + }, + }; + + return { + state, + getters, + ...actions, + }; +}); diff --git a/apps/web/src/store/menu/menu-store.ts b/apps/web/src/store/menu/menu-store.ts deleted file mode 100644 index 5db2808142..0000000000 --- a/apps/web/src/store/menu/menu-store.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { computed, reactive } from 'vue'; - -import { defineStore } from 'pinia'; - -import type { Menu, MenuId } from '@/lib/menu/config'; - -export type FlattenedMenuMap = Partial>; - -interface MenuStoreState { - menuList: Menu[]; -} - -export const useMenuStore = defineStore('menu-store', () => { - const state = reactive({ - menuList: [], - }); - - const getters = reactive({ - generateFlattenedMenuMap: computed(() => { - const map: FlattenedMenuMap = {}; - - const getSubMenuIdsToMap = (menu: Menu, flattenedMenuMap: FlattenedMenuMap) => { - let results: MenuId[] = []; - const subMenuList = menu.subMenuList; - if (subMenuList) { - results = subMenuList.map((d) => d.id); - subMenuList.forEach((subMenu) => { - getSubMenuIdsToMap(subMenu, flattenedMenuMap); - }); - } - flattenedMenuMap[menu.id] = results; - }; - - state.menuList.forEach((menu) => { - getSubMenuIdsToMap(menu, map); - }); - - return map; - }), - }); - - const mutations = { - setMenuList(value: Menu[]) { - state.menuList = value; - }, - }; - - return { - state, - getters, - ...mutations, - }; -}); diff --git a/package-lock.json b/package-lock.json index ecfd98f62d..ef98c737a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cloudforet-console", - "version": "2.0.0-dev346", + "version": "2.0.0-dev347", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cloudforet-console", - "version": "2.0.0-dev346", + "version": "2.0.0-dev347", "workspaces": [ "apps/*", "packages/*" @@ -117,7 +117,7 @@ } }, "apps/web": { - "version": "2.0.0-dev346", + "version": "2.0.0-dev347", "license": "Apache-2.0", "dependencies": { "@amcharts/amcharts5": "^5.4.7", diff --git a/package.json b/package.json index 759fe8cd1a..e5e030a3a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudforet-console", - "version": "2.0.0-dev346", + "version": "2.0.0-dev347", "private": true, "workspaces": [ "apps/*",