-
+
{{ $t('service.select_service') }}
@@ -208,7 +222,7 @@
- {{ $t('common.add') }}
+ {{ $t('common.add') }}
@@ -248,9 +262,9 @@
-
+
-
+
{{ $t('service.pending_installation_services') }}
@@ -258,7 +272,7 @@
-
+
{{ $t('common.remove') }}
@@ -290,7 +304,7 @@
-
+
diff --git a/bigtop-manager-ui/src/components/create-service/components/use-create-service.ts b/bigtop-manager-ui/src/components/create-service/components/use-create-service.ts
deleted file mode 100644
index aaaeb25cd..000000000
--- a/bigtop-manager-ui/src/components/create-service/components/use-create-service.ts
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { computed, ComputedRef, createVNode, effectScope, Ref, ref, watch } from 'vue'
-import { message, Modal } from 'ant-design-vue'
-import { useI18n } from 'vue-i18n'
-import { useRoute } from 'vue-router'
-import { ExpandServiceVO, useStackStore } from '@/store/stack'
-import { useInstalledStore } from '@/store/installed'
-import { execCommand } from '@/api/command'
-import useSteps from '@/composables/use-steps'
-import SvgIcon from '@/components/common/svg-icon/index.vue'
-import type { HostVO } from '@/api/hosts/types'
-import type { CommandRequest, CommandVO, ServiceCommandReq } from '@/api/command/types'
-import type { ServiceVO } from '@/api/service/types'
-import type { ComponentVO } from '@/api/component/types'
-
-interface ProcessResult {
- success: boolean
- conflictService?: ExpandServiceVO
-}
-
-interface RouteParams {
- service: string
- serviceId: number
- id: number
- cluster: string
-}
-
-interface CompItem extends ComponentVO {
- hosts: HostVO[]
-}
-
-const scope = effectScope()
-let isChange = false
-let selectedServices: Ref
-let selectedServicesMeta: Ref
-let afterCreateRes: Ref
-let servicesOfInfra: ComputedRef
-let servicesOfExcludeInfra: ComputedRef
-let steps: ComputedRef
-
-const setupStore = () => {
- scope.run(() => {
- selectedServices = ref([])
- selectedServicesMeta = ref([])
- afterCreateRes = ref<{ clusterId: number } & CommandVO>({ id: undefined, clusterId: 0 })
- servicesOfInfra = computed(() => useStackStore().getServicesByExclude(['bigtop', 'extra']) as ExpandServiceVO[])
- servicesOfExcludeInfra = computed(() => useStackStore().getServicesByExclude(['infra']))
- steps = computed(() => [
- 'service.select_service',
- 'service.assign_component',
- 'service.configure_service',
- 'service.service_overview',
- 'service.install_component'
- ])
- })
-}
-
-const useCreateService = () => {
- if (!isChange) {
- setupStore()
- isChange = true
- }
- const installedStore = useInstalledStore()
- const route = useRoute()
- const { t } = useI18n()
- const processedServices = ref(new Set())
- const { current, stepsLimit, previousStep, nextStep } = useSteps(steps.value)
- const clusterId = computed(() => Number(route.params.id))
- const creationMode = computed(() => route.params.creationMode as 'internal' | 'public')
- const creationModeType = computed(() => route.params.type)
- const routeParams = computed(() => route.params as unknown as RouteParams)
-
- const commandRequest = ref({
- command: 'Add',
- commandLevel: 'service',
- clusterId: clusterId.value
- })
-
- const allComps = computed(() => {
- return new Map(
- selectedServices.value.flatMap((s) =>
- s.components!.map((comp) => [
- comp.name,
- { serviceName: s.name, serviceDisplayName: s.displayName, serviceId: s.id, ...comp }
- ])
- )
- ) as Map
- })
-
- const allCompsMeta = computed(() => {
- return new Map(
- selectedServicesMeta.value.flatMap((s) =>
- s.components!.map((comp) => [
- comp.name,
- { serviceName: s.name, serviceDisplayName: s.displayName, serviceId: s.id, ...comp }
- ])
- )
- ) as Map
- })
-
- watch(
- () => selectedServices.value,
- () => {
- processedServices.value = new Set(selectedServices.value.map((v) => v.name))
- },
- {
- deep: true
- }
- )
-
- const setDataByCurrent = (val: ExpandServiceVO[]) => {
- selectedServices.value = val
- selectedServicesMeta.value = JSON.parse(JSON.stringify(val))
- }
-
- const updateHostsForComponent = (compName: string, hosts: HostVO[]) => {
- const [serviceName, componentName] = compName.split('/')
- const service = selectedServices.value.find((svc) => svc.name === serviceName)
- if (!service) return false
- const component = service.components?.find((comp) => comp.name === componentName)
- if (!component) return false
- component.hosts = hosts
- }
-
- const transformServiceData = (services: ExpandServiceVO[]) => {
- return services.map((service) => ({
- serviceName: service.name,
- installed: service.isInstalled === undefined ? false : service.isInstalled,
- componentHosts: (service.components || []).map((component) => ({
- componentName: component.name,
- hostnames: (component.hosts || []).map((host: HostVO) => host.hostname)
- })),
- configs: service.configs
- })) as ServiceCommandReq[]
- }
-
- // Validate services from infra
- const validServiceFromInfra = (targetService: ExpandServiceVO, requiredServices: string[]) => {
- const servicesOfInfraNames = servicesOfInfra.value.map((v) => v.name)
- const installedServicesOfInfra = installedStore.getInstalledNamesOrIdsOfServiceByKey('0', 'names')
- const set = new Set(installedServicesOfInfra)
- const missServices = requiredServices.reduce((acc, name) => {
- !set.has(name) && servicesOfInfraNames.includes(name) && acc.push(name)
- return acc
- }, [] as string[])
-
- if (missServices.length === 0) return false
- message.error(t('service.dependencies_conflict_msg', [targetService.displayName!, missServices.join(',')]))
- return true
- }
-
- const processDependencies = async (
- targetService: ExpandServiceVO,
- serviceMap: Map,
- servicesOfInfra: ExpandServiceVO[],
- collected: ExpandServiceVO[]
- ): Promise => {
- const dependencies = targetService.requiredServices || []
-
- if (creationMode.value === 'internal' && validServiceFromInfra(targetService, dependencies)) {
- return {
- success: false,
- conflictService: targetService
- }
- }
-
- for (const serviceName of dependencies) {
- const dependency = serviceMap.get(serviceName)
- if (!dependency || processedServices.value.has(dependency.name!)) continue
-
- if (dependency.isInstalled) continue
-
- const shouldAdd = await confirmRequiredServicesToInstall(targetService, dependency)
- if (!shouldAdd) return { success: false }
-
- collected.push(dependency)
- processedServices.value.add(dependency.name!)
- const result = await processDependencies(dependency, serviceMap, servicesOfInfra, collected)
-
- if (!result.success) {
- collected.splice(collected.indexOf(dependency), 1)
- processedServices.value.delete(dependency.name!)
- return result
- }
- }
- return { success: true }
- }
-
- const getServiceMap = (services: ServiceVO[]) => {
- return new Map(services.map((s) => [s.name as string, s as ExpandServiceVO]))
- }
-
- const handlePreSelectedServiceDependencies = async (preSelectedService: ExpandServiceVO) => {
- const serviceMap =
- creationMode.value == 'public'
- ? getServiceMap(servicesOfInfra.value)
- : getServiceMap(servicesOfExcludeInfra.value)
-
- const result: ExpandServiceVO[] = []
- const dependenciesSuccess = await processDependencies(preSelectedService, serviceMap, servicesOfInfra.value, result)
- if (dependenciesSuccess.success) {
- result.unshift(preSelectedService)
- return result
- }
- return []
- }
-
- const confirmServiceDependencies = async (preSelectedService: ExpandServiceVO) => {
- const { requiredServices } = preSelectedService
- if (!requiredServices) {
- return [preSelectedService]
- }
- if (creationMode.value === 'internal' && validServiceFromInfra(preSelectedService, requiredServices)) {
- return []
- } else {
- return await handlePreSelectedServiceDependencies(preSelectedService)
- }
- }
-
- const confirmRequiredServicesToInstall = (targetService: ExpandServiceVO, requiredService: ExpandServiceVO) => {
- return new Promise((resolve) => {
- Modal.confirm({
- content: t('service.dependencies_msg', [targetService.displayName, requiredService.displayName]),
- icon: createVNode(SvgIcon, { name: 'unknown' }),
- cancelText: t('common.no'),
- okText: t('common.yes'),
- onOk: () => resolve(true),
- onCancel: () => {
- Modal.destroyAll()
- return resolve(false)
- }
- })
- })
- }
-
- const createService = async () => {
- try {
- commandRequest.value.serviceCommands = transformServiceData(selectedServices.value)
- afterCreateRes.value = await execCommand(commandRequest.value)
- Object.assign(afterCreateRes.value, { clusterId })
- return true
- } catch (error) {
- console.log('error :>> ', error)
- return false
- }
- }
-
- const addComponentForService = async () => {
- try {
- commandRequest.value.commandLevel = 'component'
- commandRequest.value.componentCommands = []
- for (const [compName, comp] of allComps.value) {
- commandRequest.value.componentCommands?.push({
- componentName: compName!,
- hostnames: comp.hosts.map((v) => v.hostname!)
- })
- }
- afterCreateRes.value = await execCommand(commandRequest.value)
- Object.assign(afterCreateRes.value, { clusterId })
- return true
- } catch (error) {
- console.log('error :>> ', error)
- return false
- }
- }
-
- return {
- steps,
- clusterId,
- current,
- stepsLimit,
- selectedServices,
- selectedServicesMeta,
- servicesOfExcludeInfra,
- servicesOfInfra,
- installedStore,
- allComps,
- afterCreateRes,
- scope,
- creationMode,
- creationModeType,
- routeParams,
- allCompsMeta,
- setDataByCurrent,
- addComponentForService,
- updateHostsForComponent,
- confirmServiceDependencies,
- createService,
- previousStep,
- nextStep
- }
-}
-
-export default useCreateService
diff --git a/bigtop-manager-ui/src/components/create-service/create.vue b/bigtop-manager-ui/src/components/create-service/create.vue
index e687373b5..101434bbb 100644
--- a/bigtop-manager-ui/src/components/create-service/create.vue
+++ b/bigtop-manager-ui/src/components/create-service/create.vue
@@ -18,48 +18,28 @@
-->
@@ -121,7 +123,7 @@
-
+
{{ $t(step) }}
@@ -130,29 +132,24 @@
-
-
+
+
{{ $t(stepItem) }}
-
+
-
+
$router.go(-1)">{{ $t('common.exit') }}
-
+
{{ $t('common.prev') }}
-
+
{{ $t('common.next') }}
diff --git a/bigtop-manager-ui/src/components/service-management/components.vue b/bigtop-manager-ui/src/components/service-management/components.vue
index 2d8528064..9fb8539ff 100644
--- a/bigtop-manager-ui/src/components/service-management/components.vue
+++ b/bigtop-manager-ui/src/components/service-management/components.vue
@@ -31,6 +31,7 @@
import type { ComponentVO } from '@/api/component/types'
import type { FilterConfirmProps, FilterResetProps } from 'ant-design-vue/es/table/interface'
import type { Command, CommandRequest } from '@/api/command/types'
+ import type { ServiceVO } from '@/api/service/types'
type Key = string | number
interface TableState {
@@ -40,20 +41,13 @@
selectedRows: ComponentVO[]
}
- interface ServieceInfo {
- cluster: string
- id: number
- service: string
- serviceId: number
- }
-
const POLLING_INTERVAL = 3000
const { t } = useI18n()
const jobProgressStore = useJobProgress()
const stackStore = useStackStore()
const route = useRoute()
const router = useRouter()
- const attrs = useAttrs()
+ const attrs = useAttrs() as unknown as Required & { clusterId: number }
const { stacks, stackRelationMap } = storeToRefs(stackStore)
const searchInputRef = ref()
const pollingIntervalId = ref(null)
@@ -70,7 +64,6 @@
selectedRows: []
})
- const currServiceInfo = computed(() => route.params as unknown as ServieceInfo)
const componentsFromStack = computed(
() =>
new Map(
@@ -90,7 +83,7 @@
key: 'name',
ellipsis: true,
filterMultiple: false,
- filters: [...(componentsFromStack.value.get(currServiceInfo.value.service)?.values() || [])]?.map((v) => ({
+ filters: [...(componentsFromStack.value.get(attrs.name)?.values() || [])]?.map((v) => ({
text: v?.displayName || '',
value: v?.name || ''
}))
@@ -201,14 +194,14 @@
state.searchText = selectedKeys[0] as string
state.searchedColumn = dataIndex
stopPolling()
- startPolling()
+ startPolling(true, true)
}
const handleReset = (clearFilters: (param?: FilterResetProps) => void) => {
clearFilters({ confirm: true })
state.searchText = ''
stopPolling()
- startPolling()
+ startPolling(true, true)
}
const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async ({ key }) => {
@@ -241,14 +234,11 @@
const execOperation = async () => {
try {
- await jobProgressStore.processCommand(
- { ...commandRequest.value, clusterId: currServiceInfo.value.id },
- async () => {
- getComponentList(true, true)
- state.selectedRowKeys = []
- state.selectedRows = []
- }
- )
+ await jobProgressStore.processCommand({ ...commandRequest.value, clusterId: attrs.clusterId }, async () => {
+ getComponentList(true, true)
+ state.selectedRowKeys = []
+ state.selectedRows = []
+ })
} catch (error) {
console.log('error :>> ', error)
}
@@ -259,7 +249,7 @@
title: t('common.delete_msg'),
async onOk() {
try {
- const data = await deleteComponent({ clusterId: currServiceInfo.value.id, id: row.id! })
+ const data = await deleteComponent({ clusterId: attrs.clusterId, id: row.id! })
if (data) {
message.success(t('common.delete_success'))
getComponentList(true, true)
@@ -272,8 +262,8 @@
}
const getComponentList = async (isReset = false, isFirstCall = false) => {
- const { id: clusterId, serviceId } = currServiceInfo.value
- if (attrs.id == undefined || !paginationProps.value) {
+ const { clusterId, id: serviceId } = attrs
+ if (!paginationProps.value) {
loading.value = false
return
}
@@ -301,8 +291,8 @@
startPolling()
}
- const startPolling = () => {
- getComponentList(true, true)
+ const startPolling = (isReset = false, isFirstCall = false) => {
+ getComponentList(isReset, isFirstCall)
pollingIntervalId.value = setInterval(() => {
getComponentList()
}, POLLING_INTERVAL)
@@ -316,9 +306,8 @@
}
const addComponent = () => {
- const { cluster: clusterId } = route.params
- const creationMode = clusterId == '0' ? 'public' : 'internal'
- const routerName = clusterId == '0' ? 'CreateInfraComponent' : 'CreateComponent'
+ const creationMode = Number(attrs.clusterId) === 0 ? 'public' : 'internal'
+ const routerName = Number(attrs.clusterId) === 0 ? 'CreateInfraComponent' : 'CreateComponent'
router.push({
name: routerName,
params: { ...route.params, creationMode, type: 'component' }
diff --git a/bigtop-manager-ui/src/components/service-management/configs.vue b/bigtop-manager-ui/src/components/service-management/configs.vue
index 346e2b897..06f997d76 100644
--- a/bigtop-manager-ui/src/components/service-management/configs.vue
+++ b/bigtop-manager-ui/src/components/service-management/configs.vue
@@ -18,7 +18,7 @@
-->
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/service.vue b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/service.vue
index 18a0d459b..f014ee532 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/service.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/service.vue
@@ -119,8 +119,6 @@
name: 'InfraServiceDetail',
params: {
id: clusterInfo.id,
- service: payload.name,
- cluster: clusterInfo.name,
serviceId: payload.id
}
})
diff --git a/bigtop-manager-ui/src/router/guard.ts b/bigtop-manager-ui/src/router/guard.ts
index 6f7030f96..e907185e8 100644
--- a/bigtop-manager-ui/src/router/guard.ts
+++ b/bigtop-manager-ui/src/router/guard.ts
@@ -20,8 +20,9 @@
import type { NavigationGuardNext, Router } from 'vue-router'
import { useClusterStore } from '@/store/cluster'
function setCommonGuard(router: Router) {
- router.beforeEach(async (to, from, next) => {
- if (to.name === 'Clusters' && from.name !== 'Login') {
+ const token = localStorage.getItem('Token') ?? sessionStorage.getItem('Token') ?? undefined
+ router.beforeEach(async (to, _from, next) => {
+ if (to.name === 'Clusters' && token) {
checkClusterSelect(next)
} else {
next()
diff --git a/bigtop-manager-ui/src/router/routes/modules/clusters.ts b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
index 7e5139b99..be915a892 100644
--- a/bigtop-manager-ui/src/router/routes/modules/clusters.ts
+++ b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
@@ -50,7 +50,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'ClusterDetail',
- path: ':cluster/:id',
+ path: ':id',
component: () => import('@/pages/cluster-manage/cluster/index.vue'),
meta: {
hidden: true
@@ -66,7 +66,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'CreateService',
- path: ':cluster/:id/create-service/:creationMode?',
+ path: ':id/create-service/:creationMode?',
component: () => import('@/components/create-service/create.vue'),
meta: {
hidden: true
@@ -74,7 +74,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'ServiceDetail',
- path: ':cluster/:id/service-detail/:service/:serviceId',
+ path: ':id/service-detail/:serviceId',
component: () => import('@/components/service-management/index.vue'),
meta: {
hidden: true
@@ -82,7 +82,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'CreateComponent',
- path: ':cluster/:id/create-component/:service/:serviceId/:creationMode?/:type',
+ path: ':id/create-component/:serviceId/:creationMode?/:type',
component: () => import('@/components/create-service/create.vue'),
meta: {
hidden: true
@@ -119,7 +119,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'InfraServiceDetail',
- path: 'create-infra-service/service-detail/:id/:cluster/:service/:serviceId',
+ path: 'create-infra-service/service-detail/:id/:serviceId',
component: () => import('@/components/service-management/index.vue'),
meta: {
hidden: true,
@@ -128,7 +128,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'CreateInfraComponent',
- path: '/create-infra-service/create-infra-component/:id/:cluster/:service/:serviceId/:creationMode/:type',
+ path: '/create-infra-service/create-infra-component/:id/:serviceId/:creationMode/:type',
component: () => import('@/components/create-service/create.vue'),
meta: {
hidden: true,
diff --git a/bigtop-manager-ui/src/store/cluster/index.ts b/bigtop-manager-ui/src/store/cluster/index.ts
index b9a770300..c42048ddc 100644
--- a/bigtop-manager-ui/src/store/cluster/index.ts
+++ b/bigtop-manager-ui/src/store/cluster/index.ts
@@ -17,51 +17,50 @@
* under the License.
*/
-import { computed, ref, watch } from 'vue'
+import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { useRoute } from 'vue-router'
import { getCluster, getClusterList } from '@/api/cluster'
import { useServiceStore } from '@/store/service'
-import { useInstalledStore } from '@/store/installed'
import type { ClusterVO } from '@/api/cluster/types.ts'
export const useClusterStore = defineStore(
'cluster',
() => {
const route = useRoute()
- const installedStore = useInstalledStore()
const serviceStore = useServiceStore()
- const clusters = ref([])
const loading = ref(false)
+ const clusters = ref([])
const currCluster = ref({})
+ const clusterMap = ref>({})
const clusterId = computed(() => (route.params.id as string) || undefined)
- const clusterCount = computed(() => clusters.value.length)
+ const clusterCount = computed(() => Object.values(clusterMap.value).length)
- watch(
- () => clusters.value,
- (val) => {
- val.forEach((cluster) => {
- installedStore.setInstalledMapKey(`${cluster.id}`)
- })
+ const loadClusters = async () => {
+ try {
+ const clusterList = await getClusterList()
+ clusterMap.value = clusterList.reduce(
+ (pre, cluster) => {
+ pre[cluster.id!] = cluster
+ return pre
+ },
+ {} as Record
+ )
+ } catch (error) {
+ clusterMap.value = {}
+ console.log('error :>> ', error)
}
- )
-
- const addCluster = async () => {
- await loadClusters()
- }
-
- const delCluster = async () => {
- await loadClusters()
}
const getClusterDetail = async () => {
if (clusterId.value == undefined) {
+ currCluster.value = {}
return
}
try {
loading.value = true
- currCluster.value = await getCluster(parseInt(clusterId.value))
- await serviceStore.getServices(currCluster.value.id!)
+ currCluster.value = await getCluster(Number(clusterId.value))
+ await serviceStore.getServices(Number(clusterId.value))
} catch (error) {
currCluster.value = {}
console.log('error :>> ', error)
@@ -70,30 +69,30 @@ export const useClusterStore = defineStore(
}
}
- const loadClusters = async () => {
- try {
- clusters.value = await getClusterList()
- } catch (error) {
- clusters.value.length = 0
- console.log('error :>> ', error)
- }
+ const addCluster = async () => {
+ await loadClusters()
+ }
+
+ const delCluster = async () => {
+ await loadClusters()
}
return {
clusters,
+ clusterMap,
loading,
currCluster,
clusterCount,
- addCluster,
- delCluster,
loadClusters,
- getClusterDetail
+ getClusterDetail,
+ addCluster,
+ delCluster
}
},
{
persist: {
storage: sessionStorage,
- paths: ['clusters']
+ paths: ['clusterMap']
}
}
)
diff --git a/bigtop-manager-ui/src/store/create-service/index.ts b/bigtop-manager-ui/src/store/create-service/index.ts
new file mode 100644
index 000000000..385d09197
--- /dev/null
+++ b/bigtop-manager-ui/src/store/create-service/index.ts
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { computed, ref, shallowRef } from 'vue'
+import { defineStore } from 'pinia'
+import useSteps from '@/composables/use-steps'
+import { useValidations } from './validation'
+import { ExpandServiceVO, useStackStore } from '@/store/stack'
+import { execCommand } from '@/api/command'
+import type { ServiceVO } from '@/api/service/types'
+import type { CommandRequest, CommandVO, ComponentCommandReq, ServiceCommandReq } from '@/api/command/types'
+import type { HostVO } from '@/api/hosts/types'
+import type { ComponentVO } from '@/api/component/types'
+
+const STEPS_TITLES = [
+ 'service.select_service',
+ 'service.assign_component',
+ 'service.configure_service',
+ 'service.service_overview',
+ 'service.install_component'
+]
+
+export interface ProcessResult {
+ success: boolean
+ conflictService?: ExpandServiceVO
+}
+
+export interface StepContext {
+ clusterId: number
+ serviceId: number
+ creationMode: 'internal' | 'public'
+ type?: 'component'
+ [propName: string]: any
+}
+
+export interface CompItem extends ComponentVO {
+ hosts: HostVO[]
+}
+
+export const useCreateServiceStore = defineStore(
+ 'service-create',
+ () => {
+ const validations = useValidations()
+ const stackStore = useStackStore()
+
+ const steps = shallowRef(STEPS_TITLES)
+ const selectedServices = ref([])
+ const selectedServicesMeta = ref([])
+ const createdPayload = ref({})
+ const stepContext = ref({
+ clusterId: 0,
+ serviceId: 0,
+ creationMode: 'internal'
+ })
+ const commandRequest = ref({
+ command: 'Add',
+ commandLevel: 'service'
+ })
+ const { current, stepsLimit, previousStep, nextStep } = useSteps(steps.value)
+ const infraServices = computed(() => stackStore.getServicesByExclude(['bigtop', 'extra']) as ExpandServiceVO[])
+ const excludeInfraServices = computed(() => stackStore.getServicesByExclude(['infra']))
+ const infraServiceNames = computed(() => infraServices.value.map((v) => v.name!))
+ const processedServices = computed(() => new Set(selectedServices.value.map((v) => v.name)))
+ const creationMode = computed(() => stepContext.value.creationMode)
+ const currentClusterId = computed(() => (creationMode.value === 'internal' ? stepContext.value.clusterId : 0))
+
+ const allComps = computed(() => {
+ return new Map(
+ selectedServices.value.flatMap((s) =>
+ s.components!.map((comp) => {
+ return [comp.name, { serviceName: s.name, serviceDisplayName: s.displayName, serviceId: s.id, ...comp }]
+ })
+ )
+ ) as Map
+ })
+
+ const allCompsMeta = computed(() => {
+ return new Map(
+ selectedServicesMeta.value.flatMap((s) =>
+ s.components!.map((comp) => [
+ comp.name,
+ { serviceName: s.name, serviceDisplayName: s.displayName, serviceId: s.id, ...comp }
+ ])
+ )
+ ) as Map
+ })
+
+ function getServiceMap(services: ServiceVO[]) {
+ return new Map(services.map((s) => [s.name as string, s as ExpandServiceVO]))
+ }
+
+ function updateSelectedService(partial: ExpandServiceVO[]) {
+ selectedServices.value = partial
+ }
+
+ function setTempData(partial: ExpandServiceVO[]) {
+ selectedServicesMeta.value = JSON.parse(JSON.stringify(partial))
+ }
+
+ function setStepContext(partial: StepContext) {
+ stepContext.value = partial
+ }
+
+ async function confirmServiceDependencyAction(type: 'add' | 'remove', preSelectedService: ExpandServiceVO) {
+ const { requiredServices } = preSelectedService
+ if (!requiredServices && type === 'add') {
+ return [preSelectedService]
+ }
+ const valid = validations.validServiceFromInfra(preSelectedService, requiredServices!, infraServiceNames.value)
+ if (type === 'add' && creationMode.value === 'internal' && valid) {
+ return []
+ } else {
+ return await handleServiceDependencyConfirm(type, preSelectedService)
+ }
+ }
+
+ async function handleServiceDependencyConfirm(type: 'add' | 'remove', preSelectedService: ExpandServiceVO) {
+ let dependenciesSuccess: ProcessResult = { success: false }
+ const result: ExpandServiceVO[] = []
+ if (type === 'add') {
+ const target = creationMode.value === 'public' ? infraServices.value : excludeInfraServices.value
+ const serviceMap = getServiceMap(target)
+ dependenciesSuccess = await processDependencies(preSelectedService, serviceMap, infraServices.value, result)
+ } else {
+ dependenciesSuccess = await notifyDependents(preSelectedService, result)
+ }
+ if (dependenciesSuccess.success) {
+ result.unshift(preSelectedService)
+ return result
+ }
+ return []
+ }
+
+ async function notifyDependents(
+ targetService: ExpandServiceVO,
+ collected: ExpandServiceVO[]
+ ): Promise {
+ for (const service of selectedServices.value) {
+ if (!service.requiredServices?.includes(targetService.name!)) continue
+
+ const shouldRemove = await validations.confirmDependencyAddition('remove', service, targetService)
+ if (!shouldRemove) return { success: false }
+ collected.push(service)
+
+ const result = await notifyDependents(service, collected)
+ if (!result.success) {
+ collected.splice(collected.indexOf(service), 1)
+ processedServices.value.delete(service.name!)
+ return result
+ }
+ }
+ return { success: true }
+ }
+
+ async function processDependencies(
+ targetService: ExpandServiceVO,
+ serviceMap: Map,
+ servicesOfInfra: ExpandServiceVO[],
+ collected: ExpandServiceVO[]
+ ): Promise {
+ const dependencies = targetService.requiredServices || []
+
+ const valid = validations.validServiceFromInfra(targetService, dependencies, infraServiceNames.value)
+ if (creationMode.value === 'internal' && valid) {
+ return { success: false, conflictService: targetService }
+ }
+
+ for (const serviceName of dependencies) {
+ const dependency = serviceMap.get(serviceName)
+ if (!dependency || processedServices.value.has(dependency.name!)) continue
+
+ if (dependency.isInstalled) continue
+
+ const shouldAdd = await validations.confirmDependencyAddition('add', targetService, dependency)
+ if (!shouldAdd) return { success: false }
+
+ collected.push(dependency)
+ processedServices.value.add(dependency.name!)
+ const result = await processDependencies(dependency, serviceMap, servicesOfInfra, collected)
+ if (!result.success) {
+ collected.splice(collected.indexOf(dependency), 1)
+ processedServices.value.delete(dependency.name!)
+ return result
+ }
+ }
+ return { success: true }
+ }
+
+ function formatServiceData(services: ExpandServiceVO[]) {
+ return services.map((service) => ({
+ serviceName: service.name,
+ installed: service.isInstalled === undefined ? false : service.isInstalled,
+ componentHosts: (service.components || []).map((component) => ({
+ componentName: component.name,
+ hostnames: (component.hosts || []).map((host: HostVO) => host.hostname)
+ })),
+ configs: service.configs
+ })) as ServiceCommandReq[]
+ }
+
+ function formatComponentData(components: Map) {
+ const componentCommands = [] as ComponentCommandReq[]
+ for (const [compName, comp] of components) {
+ componentCommands?.push({
+ componentName: compName!,
+ hostnames: comp.hosts.map((v) => v.hostname!)
+ })
+ }
+ return componentCommands
+ }
+
+ function setComponentHosts(compName: string, hosts: HostVO[]) {
+ const [serviceName, componentName] = compName.split('/')
+ const service = selectedServices.value.find((svc) => svc.name === serviceName)
+ if (!service) return false
+ const component = service.components?.find((comp) => comp.name === componentName)
+ if (!component) return false
+ component.hosts = hosts
+ }
+
+ async function createService() {
+ try {
+ commandRequest.value.serviceCommands = formatServiceData(selectedServices.value)
+ createdPayload.value = await execCommand({ ...commandRequest.value, clusterId: currentClusterId.value })
+ Object.assign(createdPayload.value, { clusterId: currentClusterId.value })
+ return true
+ } catch (error) {
+ console.log('error :>> ', error)
+ return false
+ }
+ }
+
+ async function attachComponentToService() {
+ try {
+ commandRequest.value.commandLevel = 'component'
+ commandRequest.value.componentCommands = formatComponentData(allComps.value)
+ createdPayload.value = await execCommand({ ...commandRequest.value, clusterId: currentClusterId.value })
+ Object.assign(createdPayload.value, { clusterId: currentClusterId.value })
+ return true
+ } catch (error) {
+ console.log('error :>> ', error)
+ return false
+ }
+ }
+
+ function $reset() {
+ current.value = 0
+ selectedServices.value = []
+ selectedServicesMeta.value = []
+ createdPayload.value = {}
+ stepContext.value = {
+ clusterId: 0,
+ serviceId: 0,
+ creationMode: 'internal'
+ }
+ commandRequest.value = {
+ command: 'Add',
+ commandLevel: 'service'
+ }
+ }
+
+ return {
+ steps,
+ selectedServices,
+ stepContext,
+ infraServices,
+ excludeInfraServices,
+ current,
+ stepsLimit,
+ nextStep,
+ previousStep,
+ allComps,
+ allCompsMeta,
+ updateSelectedService,
+ setStepContext,
+ setTempData,
+ confirmServiceDependencyAction,
+ setComponentHosts,
+ createService,
+ createdPayload,
+ attachComponentToService,
+ $reset,
+ validCardinality: validations.validCardinality
+ }
+ },
+ {
+ persist: false
+ }
+)
diff --git a/bigtop-manager-ui/src/store/create-service/validation.ts b/bigtop-manager-ui/src/store/create-service/validation.ts
new file mode 100644
index 000000000..cf5c361e9
--- /dev/null
+++ b/bigtop-manager-ui/src/store/create-service/validation.ts
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { message, Modal } from 'ant-design-vue'
+import { createVNode } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useServiceStore } from '@/store/service'
+import SvgIcon from '@/components/common/svg-icon/index.vue'
+import type { ExpandServiceVO } from '@/store/stack'
+
+export function useValidations() {
+ const { t } = useI18n()
+ const serviceStore = useServiceStore()
+
+ // Validate services from infra
+ function validServiceFromInfra(
+ targetService: ExpandServiceVO,
+ requiredServices: string[],
+ infraServiceNames: string[]
+ ) {
+ const installedInfra = serviceStore.getInstalledNamesOrIdsOfServiceByKey('0', 'names')
+ const set = new Set(installedInfra)
+ const missServices = requiredServices.reduce((acc, name) => {
+ !set.has(name) && infraServiceNames.includes(name) && acc.push(name)
+ return acc
+ }, [] as string[])
+
+ if (missServices.length === 0) return false
+ message.error(t('service.dependencies_conflict_msg', [targetService.displayName!, missServices.join(',')]))
+ return true
+ }
+
+ function confirmDependencyAddition(
+ type: 'add' | 'remove',
+ targetService: ExpandServiceVO,
+ requiredService: ExpandServiceVO
+ ) {
+ const content = type === 'add' ? 'dependencies_add_msg' : 'dependencies_remove_msg'
+ return new Promise((resolve) => {
+ Modal.confirm({
+ content: t(`service.${content}`, [targetService.displayName, requiredService.displayName]),
+ icon: createVNode(SvgIcon, { name: 'unknown' }),
+ cancelText: t('common.no'),
+ okText: t('common.yes'),
+ onOk: () => resolve(true),
+ onCancel: () => {
+ Modal.destroyAll()
+ return resolve(false)
+ }
+ })
+ })
+ }
+
+ function validCardinality(cardinality: string, count: number, displayName: string): boolean {
+ if (/^\d+$/.test(cardinality)) {
+ const expected = parseInt(cardinality, 10)
+ if (count != expected) {
+ message.error(t('service.exact', [displayName, expected]))
+ return false
+ }
+ }
+
+ if (/^\d+-\d+$/.test(cardinality)) {
+ const [minStr, maxStr] = cardinality.split('-')
+ const min = parseInt(minStr, 10)
+ const max = parseInt(maxStr, 10)
+ if (count < min || count > max) {
+ message.error(t('service.range', [displayName, min, max]))
+ return false
+ }
+ }
+
+ if (/^\d+\+$/.test(cardinality)) {
+ const min = parseInt(cardinality.slice(0, -1), 10)
+ if (count < min) {
+ message.error(t('service.minOnly', [displayName, min]))
+ return false
+ }
+ }
+
+ return true
+ }
+
+ return {
+ confirmDependencyAddition,
+ validCardinality,
+ validServiceFromInfra
+ }
+}
diff --git a/bigtop-manager-ui/src/store/installed/index.ts b/bigtop-manager-ui/src/store/installed/index.ts
deleted file mode 100644
index bb404d472..000000000
--- a/bigtop-manager-ui/src/store/installed/index.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { defineStore } from 'pinia'
-import { ref } from 'vue'
-import { useServiceStore } from '@/store/service'
-import type { ServiceVO } from '@/api/service/types.ts'
-
-export interface InstalledMapItem {
- serviceId: number
- serviceName: string
- serviceDisplayName: string
- clusterId: number
-}
-
-export const useInstalledStore = defineStore(
- 'installed',
- () => {
- const serviceStore = useServiceStore()
- const installedServiceMap = ref>({})
-
- const getInstalledNamesOrIdsOfServiceByKey = (key: string, flag: 'names' | 'ids' = 'names') => {
- return installedServiceMap.value[key].map((value) => {
- if (flag === 'ids') {
- return value.serviceId
- } else {
- return value.serviceName
- }
- })
- }
-
- const setInstalledMapKey = (key: string) => {
- installedServiceMap.value[key] = []
- }
-
- const setInstalledMapKeyOfValue = (key: string, value: InstalledMapItem[]) => {
- installedServiceMap.value[key] = value
- }
-
- const getServicesOfInfra = async () => {
- await serviceStore.getServices(0)
- }
-
- const getInstalledServicesDetailByKey = async (key: string): Promise => {
- try {
- const serviceIds = getInstalledNamesOrIdsOfServiceByKey(key, 'ids')
- const allDetail = serviceIds?.map((id) =>
- serviceStore.getServiceDetail(Number(key), Number(id))
- ) as Promise[]
- return await Promise.all(allDetail)
- } catch (error) {
- console.log(error)
- }
- }
-
- return {
- serviceStore,
- installedServiceMap,
- getServicesOfInfra,
- setInstalledMapKey,
- setInstalledMapKeyOfValue,
- getInstalledNamesOrIdsOfServiceByKey,
- getInstalledServicesDetailByKey
- }
- },
- {
- persist: {
- storage: sessionStorage,
- key: 'installed',
- paths: ['installedServiceMap']
- }
- }
-)
diff --git a/bigtop-manager-ui/src/store/job-progress/index.ts b/bigtop-manager-ui/src/store/job-progress/index.ts
index 597ad2b81..e2bf4d1d2 100644
--- a/bigtop-manager-ui/src/store/job-progress/index.ts
+++ b/bigtop-manager-ui/src/store/job-progress/index.ts
@@ -90,12 +90,8 @@ export const useJobProgress = defineStore('job-progress', () => {
}))
const getClusterDisplayName = (clusterId: number) => {
- const clusters = clusterStore.clusters
- const index = clusters.findIndex((v) => v.id == clusterId)
- if (index != -1) {
- return clusters[index].displayName
- }
- return 'Global'
+ const targetCluster = clusterStore.clusterMap[clusterId]
+ return targetCluster ? targetCluster.displayName : 'Global'
}
const createStateIcon = (execRes: CommandRes) => {
diff --git a/bigtop-manager-ui/src/store/menu/helper.ts b/bigtop-manager-ui/src/store/menu/helper.ts
deleted file mode 100644
index 69810f3b1..000000000
--- a/bigtop-manager-ui/src/store/menu/helper.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { MenuItem } from './types'
-
-export const findActivePath = (menu: MenuItem): string | undefined => {
- return menu?.children && menu?.children.length > 0 ? findActivePath(menu.children[0]) : menu?.key
-}
diff --git a/bigtop-manager-ui/src/store/menu/index.ts b/bigtop-manager-ui/src/store/menu/index.ts
index ab265c160..1f0ed9855 100644
--- a/bigtop-manager-ui/src/store/menu/index.ts
+++ b/bigtop-manager-ui/src/store/menu/index.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { nextTick, ref, shallowRef } from 'vue'
+import { computed, nextTick, ref, shallowRef } from 'vue'
import { RouteRecordRaw, useRoute, useRouter } from 'vue-router'
import { dynamicRoutes as dr } from '@/router/routes/index'
import { defineStore } from 'pinia'
@@ -35,6 +35,7 @@ export const useMenuStore = defineStore(
const headerSelectedKey = ref()
const siderMenuSelectedKey = ref()
const routePathFromClusters = shallowRef('/cluster-manage/clusters')
+ const clusterList = computed(() => Object.values(clusterStore.clusterMap))
const buildMenuMap = () => {
baseRoutesMap.value = dr.reduce((buildRes, { path, name, meta, children }) => {
@@ -46,17 +47,17 @@ export const useMenuStore = defineStore(
const setupHeader = () => {
headerSelectedKey.value = route.matched[0].path ?? '/cluster-manage'
headerMenus.value = Object.values(baseRoutesMap.value)
- siderMenus.value = baseRoutesMap.value[headerSelectedKey.value].children || []
+ siderMenus.value = baseRoutesMap.value[headerSelectedKey.value]?.children || []
}
const setupSider = () => {
- siderMenus.value = baseRoutesMap.value[headerSelectedKey.value].children || []
- if (siderMenus.value[0].redirect) {
+ siderMenus.value = baseRoutesMap.value[headerSelectedKey.value]?.children || []
+ if (siderMenus.value[0]?.redirect) {
siderMenuSelectedKey.value = siderMenus.value[0].redirect
} else {
- if (clusterStore.clusters[0]) {
- const { id, name } = clusterStore.clusters[0]
- onSiderClick(`${routePathFromClusters.value}/${name}/${id}`)
+ if (clusterList.value.length > 0) {
+ const { id } = clusterList.value[0]
+ onSiderClick(`${routePathFromClusters.value}/${id}`)
} else {
onSiderClick(`${routePathFromClusters.value}/default`)
}
@@ -75,9 +76,9 @@ export const useMenuStore = defineStore(
const updateSider = async () => {
await clusterStore.loadClusters()
- const { id, name } = clusterStore.clusters[clusterStore.clusterCount - 1]
+ const { id } = clusterList.value[clusterList.value.length - 1]
await nextTick()
- onSiderClick(`${routePathFromClusters.value}/${name}/${id}`)
+ onSiderClick(`${routePathFromClusters.value}/${id}`)
}
const setupMenu = async () => {
@@ -100,7 +101,7 @@ export const useMenuStore = defineStore(
},
{
persist: {
- storage: sessionStorage,
+ storage: localStorage,
paths: ['headerMenus', 'siderMenus']
}
}
diff --git a/bigtop-manager-ui/src/store/service/index.ts b/bigtop-manager-ui/src/store/service/index.ts
index 3eb044588..1c233cdb1 100644
--- a/bigtop-manager-ui/src/store/service/index.ts
+++ b/bigtop-manager-ui/src/store/service/index.ts
@@ -21,15 +21,14 @@ import { defineStore, storeToRefs } from 'pinia'
import { computed, ref } from 'vue'
import { getService, getServiceList } from '@/api/service'
import { useStackStore } from '@/store/stack'
-import { InstalledMapItem, useInstalledStore } from '@/store/installed'
import type { ServiceListParams, ServiceVO } from '@/api/service/types'
export const useServiceStore = defineStore(
'service',
() => {
const stackStore = useStackStore()
- const installedStore = useInstalledStore()
const services = ref([])
+ const serviceMap = ref>({})
const total = ref(0)
const loading = ref(false)
const { stacks } = storeToRefs(stackStore)
@@ -40,20 +39,26 @@ export const useServiceStore = defineStore(
)
})
+ const serviceFlatMap = computed(() => {
+ const result: Record = {}
+
+ for (const services of Object.values(serviceMap.value)) {
+ for (const service of services) {
+ result[service.id!] = service
+ }
+ }
+ return result
+ })
+
const getServices = async (clusterId: number, filterParams?: ServiceListParams) => {
try {
loading.value = true
const data = await getServiceList(clusterId, { ...filterParams, pageNum: 1, pageSize: 100 })
services.value = data.content
total.value = data.total
- const serviceMap = services.value.map((v) => ({
- serviceId: v.id,
- serviceName: v.name,
- serviceDisplayName: v.displayName,
- clusterId: clusterId
- })) as InstalledMapItem[]
- installedStore.setInstalledMapKeyOfValue(`${clusterId}`, serviceMap)
+ serviceMap.value[clusterId] = data.content.map((service) => ({ ...service, clusterId }))
} catch (error) {
+ serviceMap.value = {}
console.log('error :>> ', error)
} finally {
loading.value = false
@@ -68,16 +73,48 @@ export const useServiceStore = defineStore(
}
}
+ const getServicesOfInfra = async () => {
+ await getServices(0)
+ }
+
+ const getInstalledNamesOrIdsOfServiceByKey = (key: string, flag: 'names' | 'ids' = 'names') => {
+ return Object.values(serviceMap.value[key] || {}).map((service: ServiceVO) => {
+ if (flag === 'ids') {
+ return service.id
+ } else {
+ return service.name
+ }
+ })
+ }
+
+ const getInstalledServicesDetailByKey = async (key: string): Promise => {
+ try {
+ const serviceIds = getInstalledNamesOrIdsOfServiceByKey(key, 'ids')
+ const allDetail = serviceIds?.map((id) => getServiceDetail(Number(key), Number(id))) as Promise[]
+ return await Promise.all(allDetail)
+ } catch (error) {
+ console.log(error)
+ }
+ }
+
return {
+ serviceMap,
services,
loading,
+ serviceNames,
+ locateStackWithService,
+ serviceFlatMap,
getServices,
getServiceDetail,
- serviceNames,
- locateStackWithService
+ getServicesOfInfra,
+ getInstalledServicesDetailByKey,
+ getInstalledNamesOrIdsOfServiceByKey
}
},
{
- persist: false
+ persist: {
+ storage: sessionStorage,
+ paths: ['serviceMap']
+ }
}
)
diff --git a/bigtop-manager-ui/src/store/stack/index.ts b/bigtop-manager-ui/src/store/stack/index.ts
index 99acbaa5d..30aeda34f 100644
--- a/bigtop-manager-ui/src/store/stack/index.ts
+++ b/bigtop-manager-ui/src/store/stack/index.ts
@@ -25,17 +25,20 @@ import type { ServiceConfig, ServiceVO } from '@/api/service/types'
import type { StackVO } from '@/api/stack/types'
export type ExpandServiceVO = ServiceVO & { order: number }
+export type ServiceMap = {
+ displayName: string
+ stack: string
+ components: string[]
+ configs: ServiceConfig
+ requiredServices: string[]
+}
+export type ComponentMap = {
+ service: string
+ stack: string
+} & ComponentVO
export interface StackRelationMap {
- services: {
- displayName: string
- stack: string
- components: string[]
- configs: ServiceConfig
- }
- components: {
- service: string
- stack: string
- } & ComponentVO
+ services: ServiceMap
+ components: ComponentMap
}
export const useStackStore = defineStore(
@@ -62,7 +65,8 @@ export const useStackStore = defineStore(
displayName,
stack: stackName,
components: components!.map(({ name }) => name),
- configs
+ configs,
+ requiredServices: service.requiredServices
}
for (const component of components!) {
relationMap.components[component.name!] = {