This is an automated email from the ASF dual-hosted git repository.
wuzhiguo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/bigtop-manager.git
The following commit(s) were added to refs/heads/main by this push:
new 2a52fe95 BIGTOP-4430: Implement cardinality check for
component-to-host assignments (#219)
2a52fe95 is described below
commit 2a52fe954d6e9c29b842f1e0db2116922f95cace
Author: Fdefined <[email protected]>
AuthorDate: Fri May 30 14:46:00 2025 +0800
BIGTOP-4430: Implement cardinality check for component-to-host assignments
(#219)
---
.../components/component-assigner.vue | 48 ++--
.../components/service-configurator.vue | 15 +-
.../create-service/components/service-selector.vue | 140 ++++-----
.../components/use-create-service.ts | 312 ---------------------
.../src/components/create-service/create.vue | 107 ++++---
.../components/service-management/components.vue | 45 ++-
.../src/components/service-management/configs.vue | 24 +-
.../src/components/service-management/index.vue | 17 +-
.../src/components/service-management/overview.vue | 15 +-
bigtop-manager-ui/src/layouts/index.vue | 6 +-
bigtop-manager-ui/src/layouts/sider.vue | 15 +-
bigtop-manager-ui/src/locales/en_US/service.ts | 8 +-
bigtop-manager-ui/src/locales/zh_CN/service.ts | 8 +-
.../src/pages/cluster-manage/cluster/index.vue | 2 +-
.../src/pages/cluster-manage/cluster/service.vue | 2 +-
.../src/pages/cluster-manage/hosts/create.vue | 2 +-
.../src/pages/cluster-manage/hosts/index.vue | 4 +-
.../cluster-manage/hosts/install-dependencies.vue | 7 +-
.../src/pages/cluster-manage/hosts/overview.vue | 8 +-
.../pages/cluster-manage/infrastructures/index.vue | 5 +-
.../cluster-manage/infrastructures/service.vue | 2 -
bigtop-manager-ui/src/router/guard.ts | 5 +-
.../src/router/routes/modules/clusters.ts | 12 +-
bigtop-manager-ui/src/store/cluster/index.ts | 63 ++---
.../src/store/create-service/index.ts | 304 ++++++++++++++++++++
.../src/store/create-service/validation.ts | 105 +++++++
bigtop-manager-ui/src/store/installed/index.ts | 89 ------
bigtop-manager-ui/src/store/job-progress/index.ts | 8 +-
bigtop-manager-ui/src/store/menu/helper.ts | 24 --
bigtop-manager-ui/src/store/menu/index.ts | 21 +-
bigtop-manager-ui/src/store/service/index.ts | 61 +++-
bigtop-manager-ui/src/store/stack/index.ts | 26 +-
32 files changed, 774 insertions(+), 736 deletions(-)
diff --git
a/bigtop-manager-ui/src/components/create-service/components/component-assigner.vue
b/bigtop-manager-ui/src/components/create-service/components/component-assigner.vue
index 231d354a..9f8664f5 100644
---
a/bigtop-manager-ui/src/components/create-service/components/component-assigner.vue
+++
b/bigtop-manager-ui/src/components/create-service/components/component-assigner.vue
@@ -18,14 +18,16 @@
-->
<script setup lang="ts">
- import { useI18n } from 'vue-i18n'
import { computed, onActivated, reactive, ref, shallowRef } from 'vue'
+ import { useI18n } from 'vue-i18n'
import { TableColumnType, Empty } from 'ant-design-vue'
- import { HostVO } from '@/api/hosts/types'
import { getHosts } from '@/api/hosts'
- import useCreateService from './use-create-service'
+ import { useCreateServiceStore } from '@/store/create-service'
+ import { useServiceStore } from '@/store/service'
+ import { storeToRefs } from 'pinia'
import useBaseTable from '@/composables/use-base-table'
import TreeSelector from './tree-selector.vue'
+ import type { HostVO } from '@/api/hosts/types'
import type { FilterConfirmProps, FilterResetProps, TableRowSelection } from
'ant-design-vue/es/table/interface'
import type { Key } from 'ant-design-vue/es/_util/type'
@@ -36,6 +38,10 @@
}
const { t } = useI18n()
+ const createStore = useCreateServiceStore()
+ const serviceStore = useServiceStore()
+ const { stepContext, selectedServices, allComps, allCompsMeta } =
storeToRefs(createStore)
+
const searchInputRef = ref()
const currComp = ref<string>('')
const fieldNames = shallowRef({
@@ -48,15 +54,6 @@
searchedColumn: '',
selectedRowKeys: []
})
- const {
- clusterId,
- installedStore,
- allComps,
- allCompsMeta,
- creationModeType,
- selectedServices,
- updateHostsForComponent
- } = useCreateService()
const serviceList = computed(() =>
selectedServices.value.map((v) => ({
@@ -67,7 +64,9 @@
const currCompInfo = computed(() =>
allComps.value.get(currComp.value.split('/')[1]))
const currCompInfoMeta = computed(() =>
allCompsMeta.value.get(currComp.value.split('/')[1]))
- const installedServices = computed(() =>
installedStore.getInstalledNamesOrIdsOfServiceByKey(`${clusterId.value}`))
+ const installedServices = computed(() =>
+
serviceStore.getInstalledNamesOrIdsOfServiceByKey(`${stepContext.value.clusterId}`)
+ )
const hostsOfCurrComp = computed((): HostVO[] => {
const temp = currComp.value.split('/').at(-1)
return allComps.value.has(temp!) ? allComps.value.get(temp!)?.hosts ?? []
: []
@@ -115,7 +114,8 @@
return
}
try {
- const res = await getHosts({ ...filtersParams.value, clusterId:
clusterId.value })
+ const { clusterId } = stepContext.value
+ const res = await getHosts({ ...filtersParams.value, clusterId })
dataSource.value = res.content.map((v) => ({ ...v, name: v.id,
displayName: v.hostname }))
paginationProps.value.total = res.total
loading.value = false
@@ -133,8 +133,9 @@
})
const validateHostIsCheck = (host: HostVO) => {
+ const { type } = stepContext.value
const notAdd = currCompInfoMeta.value?.hosts.findIndex((v) => v.hostname
=== host.hostname) == -1
- if (creationModeType.value === 'component') {
+ if (type === 'component') {
return !currCompInfo.value?.uninstall && !notAdd
} else {
return installedServices.value.includes(currComp.value.split('/')[0]) &&
!notAdd
@@ -149,8 +150,21 @@
}
const onSelectChange: TableRowSelection['onChange'] = (selectedRowKeys,
selectedRows) => {
- allComps.value.has(currComp.value.split('/').at(-1)!) &&
updateHostsForComponent(currComp.value, selectedRows)
- state.selectedRowKeys = selectedRowKeys
+ const { cardinality, displayName } = currCompInfo.value || {}
+ const newCount = selectedRowKeys.length
+ const compKey = currComp.value.split('/').at(-1)!
+ const isInAllComps = allComps.value.has(compKey)
+ const shouldValidate = !!cardinality
+ const isValid = !shouldValidate ||
createStore.validCardinality(cardinality, newCount, displayName || '')
+
+ const isDeselecting = newCount < state.selectedRowKeys.length
+
+ if (isValid || isDeselecting) {
+ state.selectedRowKeys = selectedRowKeys
+ if (isInAllComps) {
+ createStore.setComponentHosts(currComp.value, selectedRows)
+ }
+ }
}
const resetSelectedRowKeys = (key: string) => {
diff --git
a/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
b/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
index 0fb1aa07..150a876c 100644
---
a/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
+++
b/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
@@ -19,10 +19,12 @@
<script setup lang="ts">
import { computed, onActivated, onDeactivated, ref, shallowRef, watch } from
'vue'
+ import { storeToRefs } from 'pinia'
import { debounce } from 'lodash'
import { Empty } from 'ant-design-vue'
import TreeSelector from './tree-selector.vue'
- import useCreateService from './use-create-service'
+ import { useCreateServiceStore } from '@/store/create-service'
+ import { useServiceStore } from '@/store/service'
import type { ServiceConfigReq } from '@/api/command/types'
import type { ComponentVO } from '@/api/component/types'
import type { Key } from 'ant-design-vue/es/_util/type'
@@ -36,7 +38,9 @@
isView: false
})
- const { clusterId, installedStore, selectedServices } = useCreateService()
+ const createStore = useCreateServiceStore()
+ const serviceStore = useServiceStore()
+ const { stepContext, selectedServices } = storeToRefs(createStore)
const searchStr = ref('')
const currService = ref<Key>('')
const configs = ref<ServiceConfigReq[]>([])
@@ -65,15 +69,16 @@
const serviceList = computed(() => selectedServices.value)
const disabled = computed(() => {
- return installedStore
- .getInstalledNamesOrIdsOfServiceByKey(`${clusterId.value}`)
+ const clusterId = stepContext.value.clusterId
+ return serviceStore
+ .getInstalledNamesOrIdsOfServiceByKey(`${clusterId}`)
.includes(currService.value.toString().split('/').at(-1)!)
})
// const disabled = computed(() => {
// return creationModeType.value === 'component'
// ? false
- // : installedStore
+ // : serviceStore
// .getInstalledNamesOrIdsOfServiceByKey(`${clusterId.value}`)
// .includes(currService.value.toString().split('/').at(-1)!)
// })
diff --git
a/bigtop-manager-ui/src/components/create-service/components/service-selector.vue
b/bigtop-manager-ui/src/components/create-service/components/service-selector.vue
index 20060470..65636bf2 100644
---
a/bigtop-manager-ui/src/components/create-service/components/service-selector.vue
+++
b/bigtop-manager-ui/src/components/create-service/components/service-selector.vue
@@ -20,7 +20,10 @@
<script setup lang="ts">
import { computed, onActivated, reactive, ref, shallowRef, toRefs } from
'vue'
import { usePngImage } from '@/utils/tools'
- import useCreateService from './use-create-service'
+ import { useStackStore } from '@/store/stack'
+ import { useServiceStore } from '@/store/service'
+ import { useCreateServiceStore } from '@/store/create-service'
+ import { storeToRefs } from 'pinia'
import type { ExpandServiceVO } from '@/store/stack'
import type { ComponentVO } from '@/api/component/types.ts'
import type { ServiceVO } from '@/api/service/types'
@@ -30,28 +33,20 @@
selectedData: ExpandServiceVO[]
}
+ const stackStore = useStackStore()
+ const createStore = useCreateServiceStore()
+ const serviceStore = useServiceStore()
const searchStr = ref('')
+ const spinning = ref(false)
const licenseOfConflictService = shallowRef(['AGPL-3.0', 'GPLv2'])
const state = reactive<State>({
isAddableData: [],
selectedData: []
})
- const {
- clusterId,
- routeParams,
- selectedServices,
- servicesOfInfra,
- servicesOfExcludeInfra,
- installedStore,
- creationMode,
- creationModeType,
- confirmServiceDependencies,
- setDataByCurrent
- } = useCreateService()
+ const { stepContext, selectedServices, infraServices, excludeInfraServices }
= storeToRefs(createStore)
const { isAddableData } = toRefs(state)
- const checkSelectedServicesOnlyInstalled = computed(
- () => selectedServices.value.filter((v: ExpandServiceVO) =>
!v.isInstalled).length === 0
- )
+ const targetServiceName = computed(() =>
serviceStore.serviceFlatMap[stepContext.value.serviceId].name!)
+ const checkIfInstalled = computed(() => selectedServices.value.filter((v) =>
!v.isInstalled).length === 0)
const filterAddableData = computed(() =>
isAddableData.value.filter(
(v) =>
@@ -92,22 +87,22 @@
const handleInstallItem = (item: ExpandServiceVO, from: ExpandServiceVO[],
to: ExpandServiceVO[]) => {
item.components = item.components?.map((v) => ({ ...v, hosts: [] }))
moveItem(from, to, item)
- setDataByCurrent(state.selectedData)
+ createStore.updateSelectedService(state.selectedData)
}
- const addInstallItem = async (item: ExpandServiceVO) => {
- const items = await confirmServiceDependencies(item)
+ const modifyInstallItems = async (type: 'add' | 'remove', item:
ExpandServiceVO) => {
+ const items = await createStore.confirmServiceDependencyAction(type, item)
if (items.length > 0) {
items.forEach((i) => {
- handleInstallItem(i, state.isAddableData, state.selectedData)
+ if (type === 'add') {
+ handleInstallItem(i, state.isAddableData, state.selectedData)
+ } else {
+ handleInstallItem(i, state.selectedData, state.isAddableData)
+ }
})
}
}
- const removeInstallItem = (item: ExpandServiceVO) => {
- handleInstallItem(item, state.selectedData, state.isAddableData)
- }
-
const splitSearchStr = (splitStr: string) => {
return splitStr.toString().split(new
RegExp(`(?<=${searchStr.value})|(?=${searchStr.value})`, 'i'))
}
@@ -119,7 +114,8 @@
if (!acc[name!]) {
acc[name!] = {
...item,
- hosts: [{ hostname, name: hostname, displayName: hostname }]
+ hosts: [{ hostname, name: hostname, displayName: hostname }],
+ cardinality:
stackStore.stackRelationMap?.components[item.name!].cardinality
}
} else {
acc[name!].hosts.push({ hostname, name: hostname, displayName:
hostname })
@@ -130,7 +126,8 @@
}
const initInstalledServicesDetail = async () => {
- const detailRes = await
installedStore.getInstalledServicesDetailByKey(`${clusterId.value}`)
+ const clusterId = stepContext.value.clusterId
+ const detailRes = await
serviceStore.getInstalledServicesDetailByKey(`${clusterId}`)
const detailMap = new Map<string, ExpandServiceVO>()
if (!detailRes) {
return detailMap
@@ -146,60 +143,77 @@
}
}
- const mergeInherentServiceAndInstalledService = (
+ const buildCompleteServiceInfo = (
installedServiceMap: Map<string, ExpandServiceVO>,
inherentServices: ServiceVO[]
) => {
- const inherentService = inherentServices.filter((v) => v.name ===
routeParams.value.service)[0]
- const installedService = {
...installedServiceMap.get(routeParams.value.service)! }
- installedService.license = inherentService.license
- const map = new Map(installedService.components!.map((item) => [item.name,
item]))
- inherentService.components!.forEach((item) => {
- !map.has(item.name) && map.set(item.name, { ...item, hosts: [],
uninstall: true })
- })
- installedService.components = [...map.values()]
- return installedService
+ const inherent = inherentServices.find((v) => v.name ===
targetServiceName.value)!
+ const installed = { ...installedServiceMap.get(targetServiceName.value)! }
+ const mergedComponentsMap = new Map(
+ (installed.components ?? []).map((component) => [component.name, {
...component }])
+ )
+ for (const component of inherent.components ?? []) {
+ if (!mergedComponentsMap.has(component.name)) {
+ mergedComponentsMap.set(component.name, { ...component, hosts: [],
uninstall: true })
+ }
+ }
+ installed.components = [...mergedComponentsMap.values()]
+ installed.license = inherent.license
+
+ return installed as ExpandServiceVO
}
- const addInstalledSymbolForSelectedServices = async (onlyInstalled: boolean)
=> {
- if (onlyInstalled) {
- const installedServiceMap = await initInstalledServicesDetail()
- const installedServiceNames =
installedStore.getInstalledNamesOrIdsOfServiceByKey(`${clusterId.value}`)
- const inherentServices = creationMode.value === 'internal' ?
servicesOfExcludeInfra.value : servicesOfInfra.value
+ const markSelectedAsInstalled = async (hasInstalled: boolean) => {
+ const { type, creationMode, clusterId } = stepContext.value
+ if (!hasInstalled) {
+ state.selectedData = [...selectedServices.value]
+ return
+ }
+ const installedServiceMap = await initInstalledServicesDetail()
+ const installedServiceNames =
serviceStore.getInstalledNamesOrIdsOfServiceByKey(`${clusterId}`)
+ const rawServices = creationMode === 'internal' ?
excludeInfraServices.value : infraServices.value
+ // Clone to prevent mutation of original data
+ const inherentServices = rawServices.map((s) => ({ ...s }))
- if (creationModeType.value === 'component' &&
installedServiceMap.has(routeParams.value.service)) {
- state.selectedData[0] =
mergeInherentServiceAndInstalledService(installedServiceMap, inherentServices)
- state.selectedData[0].isInstalled = true
- state.isAddableData = []
- } else {
- for (const service of inherentServices) {
- if (installedServiceNames.includes(service.name || '')) {
- Object.assign(service, installedServiceMap.get(service.name!))
+ if (type === 'component' &&
installedServiceMap.has(targetServiceName.value!)) {
+ const completeService = buildCompleteServiceInfo(installedServiceMap,
inherentServices)
+ state.selectedData[0] = { ...completeService, isInstalled: true }
+ state.isAddableData = []
+ } else {
+ for (const service of inherentServices) {
+ const name = service.name || ''
+ if (installedServiceNames.includes(name)) {
+ const installed = installedServiceMap.get(name)
+ if (installed) {
+ Object.assign(service, installed)
service.isInstalled = true
- state.selectedData.push(service as ExpandServiceVO)
- } else {
- state.isAddableData.push(service as ExpandServiceVO)
}
+ state.selectedData.push(service as ExpandServiceVO)
+ } else {
+ state.isAddableData.push(service as ExpandServiceVO)
}
}
- setDataByCurrent(state.selectedData)
- } else {
- state.selectedData = [...selectedServices.value]
}
+
+ createStore.updateSelectedService(state.selectedData)
+ createStore.setTempData(state.selectedData)
}
onActivated(async () => {
- await
addInstalledSymbolForSelectedServices(checkSelectedServicesOnlyInstalled.value)
+ spinning.value = true
+ markSelectedAsInstalled(checkIfInstalled.value).finally(() => {
+ spinning.value = false
+ })
})
defineExpose({
- addInstallItem
+ modifyInstallItems
})
</script>
<template>
<div class="service-selector">
- <div>
+ <a-spin :spinning="spinning">
<div class="list-title">
<div>{{ $t('service.select_service') }}</div>
<a-input v-model:value="searchStr"
:placeholder="$t('service.please_enter_search_keyword')" />
@@ -208,7 +222,7 @@
<template #renderItem="{ item }">
<a-list-item>
<template #actions>
- <a-button type="primary" @click="addInstallItem(item)">{{
$t('common.add') }}</a-button>
+ <a-button type="primary" @click="modifyInstallItems('add',
item)">{{ $t('common.add') }}</a-button>
</template>
<a-list-item-meta>
<template #title>
@@ -248,9 +262,9 @@
</a-list-item>
</template>
</a-list>
- </div>
+ </a-spin>
<a-divider type="vertical" class="divider" />
- <div>
+ <a-spin :spinning="spinning">
<div class="list-title">
<div>{{ $t('service.pending_installation_services') }}</div>
</div>
@@ -258,7 +272,7 @@
<template #renderItem="{ item }">
<a-list-item>
<template #actions>
- <a-button danger :disabled="item.isInstalled" type="primary"
@click="removeInstallItem(item)">
+ <a-button danger :disabled="item.isInstalled" type="primary"
@click="modifyInstallItems('remove', item)">
{{ $t('common.remove') }}
</a-button>
</template>
@@ -290,7 +304,7 @@
</a-list-item>
</template>
</a-list>
- </div>
+ </a-spin>
</div>
</template>
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 aaaeb25c..00000000
---
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<ExpandServiceVO[]>
-let selectedServicesMeta: Ref<ExpandServiceVO[]>
-let afterCreateRes: Ref<CommandVO>
-let servicesOfInfra: ComputedRef<ExpandServiceVO[]>
-let servicesOfExcludeInfra: ComputedRef<ExpandServiceVO[] | ServiceVO[]>
-let steps: ComputedRef<string[]>
-
-const setupStore = () => {
- scope.run(() => {
- selectedServices = ref<ExpandServiceVO[]>([])
- selectedServicesMeta = ref<ExpandServiceVO[]>([])
- 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<CommandRequest>({
- 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<string, CompItem>
- })
-
- 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<string, CompItem>
- })
-
- 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<string, ExpandServiceVO>,
- servicesOfInfra: ExpandServiceVO[],
- collected: ExpandServiceVO[]
- ): Promise<ProcessResult> => {
- 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 e687373b..101434bb 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 @@
-->
<script setup lang="ts">
- import { computed, onUnmounted, ref, shallowRef } from 'vue'
+ import { computed, onUnmounted, ref, shallowRef, watch } from 'vue'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
- import useCreateService from './components/use-create-service'
+ import { storeToRefs } from 'pinia'
+ import { useRoute } from 'vue-router'
+ import { StepContext, useCreateServiceStore } from '@/store/create-service'
import ServiceSelector from './components/service-selector.vue'
import ComponentAssigner from './components/component-assigner.vue'
import ServiceConfigurator from './components/service-configurator.vue'
import ComponentInstaller from './components/component-installer.vue'
const { t } = useI18n()
- const components = shallowRef<any[]>([ServiceSelector, ComponentAssigner,
ServiceConfigurator, ServiceConfigurator])
- const {
- scope,
- current,
- stepsLimit,
- steps,
- allComps,
- allCompsMeta,
- creationModeType,
- afterCreateRes,
- selectedServices,
- setDataByCurrent,
- previousStep,
- nextStep,
- createService,
- confirmServiceDependencies,
- addComponentForService
- } = useCreateService()
+ const route = useRoute()
+ const createStore = useCreateServiceStore()
+ const { current, stepsLimit, stepContext, selectedServices, createdPayload }
= storeToRefs(createStore)
+
const compRef = ref<any>()
+ const components = shallowRef<any[]>([ServiceSelector, ComponentAssigner,
ServiceConfigurator, ServiceConfigurator])
const currComp = computed(() => components.value[current.value])
- const noUninstallComponent = computed(() => {
- return (
- current.value === 1 &&
- creationModeType.value === 'component' &&
- Array.from(allComps.value).every(
- ([compName, { hosts }]) => hosts.length ===
allCompsMeta.value.get(compName)?.hosts?.length
- )
- )
- })
const validateServiceSelection = async () => {
- if (creationModeType.value === 'component') {
+ if (stepContext.value.type === 'component') {
return true
}
@@ -74,13 +54,13 @@
const validateDependenciesOfServiceSelection = async () => {
let selectedServiceNames = new Set(selectedServices.value.map((service) =>
service.name))
for (const selectedService of selectedServices.value) {
- const serviceDependencies = await
confirmServiceDependencies(selectedService)
+ const serviceDependencies = await
createStore.confirmServiceDependencyAction('add', selectedService)
if (serviceDependencies.length === 0) {
return false
}
for (const service of serviceDependencies) {
if (!selectedServiceNames.has(service.name)) {
- compRef.value.addInstallItem(service)
+ compRef.value.modifyInstallItems('add', service)
selectedServiceNames.add(service.name)
}
}
@@ -89,30 +69,52 @@
}
const validateComponentAssignments = () => {
- const allComponents = Array.from(allComps.value.values())
- const isValid = allComponents.every((comp) => comp?.hosts?.length > 0)
- if (!isValid) {
- message.error(t('service.component_host_assignment'))
+ let valid = true
+ for (const info of createStore.allComps.values()) {
+ if (!info.cardinality) {
+ continue
+ }
+ valid = createStore.validCardinality(info.cardinality,
info.hosts.length, info.displayName!)
+ if (!valid) {
+ return
+ }
}
- return isValid
+ return valid
}
const stepValidators = [validateServiceSelection,
validateComponentAssignments, () => true, () => true]
const proceedToNextStep = async () => {
- if (current.value < 3) {
- ;(await stepValidators[current.value]()) && nextStep()
+ const { type } = stepContext.value
+ if (current.value < 3 && (await stepValidators[current.value]())) {
+ createStore.nextStep()
} else if (current.value === 3) {
- const state = creationModeType.value === 'component' ? await
addComponentForService() : await createService()
+ const action = type === 'component' ?
createStore.attachComponentToService : createStore.createService
+ const state = await action()
if (state) {
- nextStep()
+ createStore.nextStep()
}
}
}
+ const setupStepCtx = () => {
+ const { id: clusterId, serviceId, creationMode, type } = route.params as
StepContext
+ createStore.setStepContext({ clusterId, serviceId, creationMode, type })
+ }
+
+ watch(
+ () => route,
+ () => {
+ createStore.$reset()
+ setupStepCtx()
+ },
+ {
+ immediate: true
+ }
+ )
+
onUnmounted(() => {
- scope.stop()
- setDataByCurrent([])
+ createStore.$reset()
})
</script>
@@ -121,7 +123,7 @@
<header-card>
<div class="steps-wrp">
<a-steps :current="current">
- <a-step v-for="step in steps" :key="step" :disabled="true">
+ <a-step v-for="step in createStore.steps" :key="step"
:disabled="true">
<template #title>
<span>{{ $t(step) }}</span>
</template>
@@ -130,29 +132,24 @@
</div>
</header-card>
<main-card>
- <template v-for="stepItem in steps" :key="stepItem.title">
- <div v-show="steps[current] === stepItem" class="step-title">
+ <template v-for="stepItem in createStore.steps" :key="stepItem.title">
+ <div v-show="createStore.steps[current] === stepItem"
class="step-title">
<h5>{{ $t(stepItem) }}</h5>
<section :class="{ 'step-content': current < stepsLimit }"></section>
</div>
</template>
<keep-alive>
- <component
- :is="currComp"
- ref="compRef"
- :is-view="current === 3"
- v-bind="{ ...$route.params, creationMode: $route.params.creationMode
|| 'internal' }"
- />
+ <component :is="currComp" ref="compRef" :is-view="current === 3" />
</keep-alive>
- <component-installer v-if="current == stepsLimit"
:step-data="afterCreateRes" />
+ <component-installer v-if="current == stepsLimit"
:step-data="createdPayload" />
<div class="step-action">
<a-space>
<a-button v-show="current != stepsLimit" @click="() =>
$router.go(-1)">{{ $t('common.exit') }}</a-button>
- <a-button v-show="current > 0 && current < stepsLimit"
type="primary" @click="previousStep">
+ <a-button v-show="current > 0 && current < stepsLimit"
type="primary" @click="createStore.previousStep">
{{ $t('common.prev') }}
</a-button>
<template v-if="current >= 0 && current <= stepsLimit - 1">
- <a-button :disabled="noUninstallComponent" type="primary"
@click="proceedToNextStep">
+ <a-button :disabled="false" type="primary"
@click="proceedToNextStep">
{{ $t('common.next') }}
</a-button>
</template>
diff --git a/bigtop-manager-ui/src/components/service-management/components.vue
b/bigtop-manager-ui/src/components/service-management/components.vue
index 2d852806..9fb8539f 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<ServiceVO> & { clusterId:
number }
const { stacks, stackRelationMap } = storeToRefs(stackStore)
const searchInputRef = ref()
const pollingIntervalId = ref<any>(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 346e2b89..06f997d7 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 @@
-->
<script setup lang="ts">
- import { onActivated, inject, useAttrs, shallowRef, ref, onDeactivated,
ComputedRef } from 'vue'
+ import { onActivated, inject, useAttrs, shallowRef, ref, onDeactivated }
from 'vue'
import { debounce } from 'lodash'
import { useI18n } from 'vue-i18n'
import { Empty, message } from 'ant-design-vue'
@@ -27,16 +27,8 @@
import SnapshotManagement from './components/snapshot-management.vue'
import type { Property, ServiceConfig, ServiceVO } from '@/api/service/types'
- interface ServieceInfo {
- cluster: string
- id: number
- service: string
- serviceId: number
- }
-
- const { routeParams }: { routeParams: ComputedRef<ServieceInfo> } =
inject('service') as any
- const attrs = useAttrs() as unknown as ServiceVO
const { t } = useI18n()
+ const attrs = useAttrs() as unknown as Required<ServiceVO> & { clusterId:
number }
const getServiceDetail = inject('getServiceDetail') as () => any
const searchStr = ref('')
const loading = ref(false)
@@ -99,9 +91,9 @@
const saveConfigs = async () => {
try {
- const { serviceId, id: clusterId } = routeParams.value
+ const { id, clusterId } = attrs
loading.value = true
- const data = await updateServiceConfigs({ id: serviceId, clusterId },
[...configs.value])
+ const data = await updateServiceConfigs({ id, clusterId },
[...configs.value])
if (data) {
message.success(t('common.update_success'))
getServiceDetail()
@@ -114,13 +106,13 @@
}
const onCaptureSnapshot = () => {
- const { serviceId, id: clusterId } = routeParams.value
- captureRef.value?.handleOpen({ id: serviceId, clusterId })
+ const { id, clusterId } = attrs
+ captureRef.value?.handleOpen({ id, clusterId })
}
const openSnapshotManagement = () => {
- const { serviceId, id: clusterId } = routeParams.value
- snapshotRef.value?.handleOpen({ id: serviceId, clusterId })
+ const { id, clusterId } = attrs
+ snapshotRef.value?.handleOpen({ id, clusterId })
}
onActivated(async () => {
diff --git a/bigtop-manager-ui/src/components/service-management/index.vue
b/bigtop-manager-ui/src/components/service-management/index.vue
index d02c5f12..0cf98bca 100644
--- a/bigtop-manager-ui/src/components/service-management/index.vue
+++ b/bigtop-manager-ui/src/components/service-management/index.vue
@@ -32,10 +32,8 @@
import type { GroupItem } from '@/components/common/button-group/types'
import type { ServiceVO } from '@/api/service/types'
- interface ServieceInfo {
- cluster: string
+ interface RouteParams {
id: number
- service: string
serviceId: number
}
@@ -43,10 +41,10 @@
const route = useRoute()
const serviceStore = useServiceStore()
const jobProgressStore = useJobProgress()
- const { loading } = storeToRefs(serviceStore)
+ const { loading, serviceMap } = storeToRefs(serviceStore)
const activeKey = ref('1')
const serviceDetail = shallowRef<ServiceVO>()
- const routeParams = computed(() => route.params as unknown as ServieceInfo)
+ const routeParams = computed(() => route.params as unknown as RouteParams)
const tabs = computed((): TabItem[] => [
{
key: '1',
@@ -90,13 +88,15 @@
})
const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async ({ key
}) => {
+ const { id: clusterId, serviceId } = routeParams.value
+ const serviceName = serviceMap.value[clusterId].filter((service) =>
Number(serviceId) === service.id)[0].name!
try {
await jobProgressStore.processCommand(
{
command: key as keyof typeof Command,
- clusterId: routeParams.value.id,
+ clusterId,
commandLevel: 'service',
- serviceCommands: [{ serviceName: routeParams.value.service,
installed: true }]
+ serviceCommands: [{ serviceName, installed: true }]
},
getServiceDetail
)
@@ -118,7 +118,6 @@
}
provide('getServiceDetail', getServiceDetail)
- provide('service', { routeParams })
onMounted(() => {
getServiceDetail()
@@ -136,7 +135,7 @@
<main-card v-model:active-key="activeKey" :tabs="tabs">
<template #tab-item>
<keep-alive>
- <component :is="getCompName" v-bind="serviceDetail"></component>
+ <component :is="getCompName" v-bind="{ ...serviceDetail, clusterId:
routeParams.id }"></component>
</keep-alive>
</template>
</main-card>
diff --git a/bigtop-manager-ui/src/components/service-management/overview.vue
b/bigtop-manager-ui/src/components/service-management/overview.vue
index 9ede9128..6fe460d5 100644
--- a/bigtop-manager-ui/src/components/service-management/overview.vue
+++ b/bigtop-manager-ui/src/components/service-management/overview.vue
@@ -32,7 +32,7 @@
}
const { t } = useI18n()
- const attrs = useAttrs()
+ const attrs = useAttrs() as unknown as Required<ServiceVO> & { clusterId:
number }
const currTimeRange = ref<TimeRangeText>('15m')
const chartData = ref({
chart1: [],
@@ -45,7 +45,6 @@
2: 'unhealthy',
3: 'unknown'
})
- const serviceDetail = computed(() => attrs as unknown as ServiceVO)
const serviceKeys = computed(() => Object.keys(baseConfig.value) as (keyof
ServiceVO)[])
const noChartData = computed(() => Object.values(chartData.value).every((v)
=> v.length === 0))
const timeRanges = computed((): TimeRangeItem[] => [
@@ -119,27 +118,27 @@
<a-tag
v-if="key === 'status'"
class="reset-tag"
-
:color="CommonStatus[statusColors[serviceDetail[key]]]"
+ :color="CommonStatus[statusColors[attrs[key]]]"
>
- <status-dot
:color="CommonStatus[statusColors[serviceDetail[key]]]" />
- {{ serviceDetail[key] &&
$t(`common.${statusColors[serviceDetail[key]]}`) }}
+ <status-dot
:color="CommonStatus[statusColors[attrs[key]]]" />
+ {{ attrs[key] &&
$t(`common.${statusColors[attrs[key]]}`) }}
</a-tag>
<a-typography-text
v-else-if="key === 'stack'"
class="desc-sub-item-desc-column"
- :content="serviceDetail[key]?.toLowerCase()"
+ :content="attrs[key]?.toLowerCase()"
/>
<a-typography-text
v-else-if="key === 'restartFlag'"
class="desc-sub-item-desc-column"
- :content="serviceDetail[key] ? $t('common.yes') :
$t('common.no')"
+ :content="attrs[key] ? $t('common.yes') :
$t('common.no')"
/>
<a-typography-text
v-else-if="['kerberos', 'metrics'].includes(key)"
class="desc-sub-item-desc-column"
:content="$t('common.disabled')"
/>
- <a-typography-text v-else
class="desc-sub-item-desc-column" :content="serviceDetail[key]" />
+ <a-typography-text v-else
class="desc-sub-item-desc-column" :content="attrs[key]" />
</div>
</template>
</div>
diff --git a/bigtop-manager-ui/src/layouts/index.vue
b/bigtop-manager-ui/src/layouts/index.vue
index 11e3604a..a9032d53 100644
--- a/bigtop-manager-ui/src/layouts/index.vue
+++ b/bigtop-manager-ui/src/layouts/index.vue
@@ -24,21 +24,21 @@
import { useUserStore } from '@/store/user'
import { useMenuStore } from '@/store/menu'
- import { useInstalledStore } from '@/store/installed'
import { useClusterStore } from '@/store/cluster'
import { useStackStore } from '@/store/stack'
+ import { useServiceStore } from '@/store/service'
import { onMounted } from 'vue'
const userStore = useUserStore()
const menuStore = useMenuStore()
- const installedStore = useInstalledStore()
+ const serviceStore = useServiceStore()
const stackStore = useStackStore()
const clusterStore = useClusterStore()
onMounted(async () => {
stackStore.loadStacks()
await clusterStore.loadClusters()
- installedStore.getServicesOfInfra()
+ serviceStore.getServicesOfInfra()
userStore.getUserInfo()
menuStore.setupMenu()
})
diff --git a/bigtop-manager-ui/src/layouts/sider.vue
b/bigtop-manager-ui/src/layouts/sider.vue
index 060a7b06..34a5cb34 100644
--- a/bigtop-manager-ui/src/layouts/sider.vue
+++ b/bigtop-manager-ui/src/layouts/sider.vue
@@ -32,7 +32,7 @@
const routeParamsLen = ref(0)
const openKeys = ref<string[]>(['clusters'])
const { siderMenuSelectedKey, headerSelectedKey, siderMenus,
routePathFromClusters } = storeToRefs(menuStore)
- const { clusters, clusterCount } = storeToRefs(clusterStore)
+ const { clusterCount, clusterMap } = storeToRefs(clusterStore)
const showCreateClusterBtn = computed(() => headerSelectedKey.value ===
'/cluster-manage')
const selectMenuKeyFromClusters = computed(() =>
siderMenuSelectedKey.value.includes(routePathFromClusters.value))
@@ -48,10 +48,13 @@
() => route,
(newRoute) => {
const { params, path, meta } = newRoute
+ const targetCluster = clusterMap.value[`${params.id}`]
routeParamsLen.value = Object.keys(params).length
if (path.includes(routePathFromClusters.value) && routeParamsLen.value >
0 && clusterCount.value > 0) {
- const cluster = clusters.value.find((v) => `${v.id}` === params.id)
- cluster && (siderMenuSelectedKey.value =
`${routePathFromClusters.value}/${cluster.name}/${cluster.id}`)
+ if (targetCluster) {
+ // siderMenuSelectedKey.value =
`${routePathFromClusters.value}/${targetCluster.name}/${targetCluster.id}`
+ siderMenuSelectedKey.value =
`${routePathFromClusters.value}/${targetCluster.id}`
+ }
} else {
siderMenuSelectedKey.value = meta.activeMenu ?? path
}
@@ -81,7 +84,7 @@
v-model="openKeys"
:selected-keys="[siderMenuSelectedKey]"
mode="inline"
- @select="({ key }) => menuStore.onSiderClick(key)"
+ @click="({ key }) => menuStore.onSiderClick(key)"
>
<template v-for="menuItem in siderMenus" :key="menuItem.path">
<a-sub-menu v-if="menuItem.name === 'Clusters'" :key="menuItem.path">
@@ -100,8 +103,8 @@
<span>{{ $t(menuItem.meta!.title!) }}</span>
</template>
<a-menu-item
- v-for="child in clusters"
- :key="`${routePathFromClusters}/${child.name}/${child.id}`"
+ v-for="child of clusterMap"
+ :key="`${routePathFromClusters}/${child.id}`"
:title="child.displayName"
>
<template #icon>
diff --git a/bigtop-manager-ui/src/locales/en_US/service.ts
b/bigtop-manager-ui/src/locales/en_US/service.ts
index b34603db..a64422f3 100644
--- a/bigtop-manager-ui/src/locales/en_US/service.ts
+++ b/bigtop-manager-ui/src/locales/en_US/service.ts
@@ -32,10 +32,14 @@ export default {
component_host_assignment: 'Assign at least one host for each component',
service_selection: 'Please select services to install',
dependencies_conflict_msg: '{0} requires infra service {1} to be installed
first',
- dependencies_msg: '{0} requires service {1}, add it also?',
+ dependencies_add_msg: '{0} requires service {1}, add it also?',
+ dependencies_remove_msg: '{0} requires service {1}, remove it also?',
capture_snapshot: 'Capture Snapshot',
snapshot_management: 'Snapshot Management',
snapshot_name: 'Name',
snapshot_description: 'Description',
- snapshot_notes: 'Snapshot Notes'
+ snapshot_notes: 'Snapshot Notes',
+ exact: '{0} requires exactly {1} host(s).',
+ range: '{0} requires between {1} and {2} host(s).',
+ minOnly: '{0} requires at least {1} host(s).'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/service.ts
b/bigtop-manager-ui/src/locales/zh_CN/service.ts
index 515c839e..5fde4be5 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/service.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/service.ts
@@ -32,10 +32,14 @@ export default {
component_host_assignment: '每个组件至少分配一个主机',
service_selection: '请选择需要安装的服务',
dependencies_conflict_msg: '{0} 依赖于基础服务 {1},请先前往安装',
- dependencies_msg: '{0} 依赖于服务 {1}, 是否一起安装?',
+ dependencies_add_msg: '{0} 依赖于服务 {1}, 是否一起安装?',
+ dependencies_remove_msg: '{0} 依赖于服务 {1}, 是否一起移除?',
capture_snapshot: '拍摄快照',
snapshot_management: '快照管理',
snapshot_name: '快照名',
snapshot_description: '快照描述',
- snapshot_notes: '快照备注'
+ snapshot_notes: '快照备注',
+ exact: '{0} 需要 {1} 个主机',
+ range: '{0} 需要 {1} 到 {2} 个主机',
+ minOnly: '{0} 至少需要 {1} 个主机'
}
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
index 61784027..5f056391 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
@@ -121,7 +121,7 @@
}
const addService: GroupItem['clickEvent'] = () => {
- router.push({ name: 'CreateService', params: { creationMode: 'internal' }
})
+ router.push({ name: 'CreateService', params: { id: currCluster.value.id,
creationMode: 'internal' } })
}
onMounted(() => {
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
index 03165b57..253b89ef 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
@@ -139,7 +139,7 @@
const viewServiceDetail = (payload: ServiceVO) => {
router.push({
name: 'ServiceDetail',
- params: { service: payload.name, serviceId: payload.id }
+ params: { serviceId: payload.id }
})
}
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
index 45d67fb2..027aaf45 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
@@ -380,7 +380,7 @@
const getClusterSelectOptions = async () => {
await nextTick()
- const formatClusters = clusterStore.clusters.map((v) => ({ ...v,
clusterId: v.id }))
+ const formatClusters = Object.values(clusterStore.clusterMap).map((v) =>
({ ...v, clusterId: v.id }))
autoFormRef.value?.setOptions('clusterId', formatClusters)
}
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
index 233cccfb..b184f20d 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
@@ -55,7 +55,7 @@
selectedRowKeys: []
})
const filtersOfClusterDisplayName = computed(() =>
- clusterStore.clusters.map((v) => ({
+ Object.values(clusterStore.clusterMap).map((v) => ({
text: v.displayName || v.name,
value: v.id!
}))
@@ -212,7 +212,7 @@
}
const editHost = (row: HostVO) => {
- const cluster = clusterStore.clusters.find((v) => v.name ===
row.clusterName)
+ const cluster = Object.values(clusterStore.clusterMap).find((v) => v.name
=== row.clusterName)
const formatHost = { ...row, displayName: row.clusterDisplayName,
clusterId: cluster?.id }
hostCreateRef.value?.handleOpen('EDIT', formatHost)
}
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/hosts/install-dependencies.vue
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/install-dependencies.vue
index 559bdbbf..c9c2d034 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/install-dependencies.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/install-dependencies.vue
@@ -48,7 +48,7 @@
const { t } = useI18n()
const emits = defineEmits<Emits>()
const clusterStore = useClusterStore()
- const { clusters } = storeToRefs(clusterStore)
+ const { clusterMap } = storeToRefs(clusterStore)
const installing = ref(false)
const open = ref(false)
const searchInputRef = ref()
@@ -60,7 +60,6 @@
selectedRowKeys: []
})
const allInstallSuccess = computed(() => dataSource.value.every((v) =>
v.status === Status.Success))
- const clusterNameMap = computed(() => new Map(clusters.value.map((v) =>
[v.id, v])))
const columns = computed((): TableColumnType<HostReq & { status: string }>[]
=> [
{
title: t('host.hostname'),
@@ -312,8 +311,8 @@
</template>
<template #bodyCell="{ record, column }">
<template v-if="column.key === 'clusterId'">
- <span
:title="`${clusterNameMap.get(record.clusterId)?.displayName}`">
- {{ clusterNameMap.get(record.clusterId)?.displayName }}
+ <span :title="`${clusterMap[record.clusterId]?.displayName}`">
+ {{ clusterMap[record.clusterId]?.displayName }}
</span>
</template>
<template v-if="column.key === 'status'">
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
index ae19d107..54fece85 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
@@ -24,9 +24,9 @@
import { formatFromByte } from '@/utils/storage.ts'
import { usePngImage } from '@/utils/tools'
import { CommonStatus, CommonStatusTexts } from '@/enums/state.ts'
+ import { useServiceStore } from '@/store/service'
import { useJobProgress } from '@/store/job-progress'
import { useStackStore } from '@/store/stack'
- import { useInstalledStore } from '@/store/installed'
import { getComponentsByHost } from '@/api/hosts'
import { Command } from '@/api/command/types'
import CategoryChart from
'@/pages/cluster-manage/cluster/components/category-chart.vue'
@@ -50,8 +50,8 @@
const { hostInfo } = toRefs(props)
const { t } = useI18n()
const stackStore = useStackStore()
+ const serviceStore = useServiceStore()
const jobProgressStore = useJobProgress()
- const installedStore = useInstalledStore()
const currTimeRange = ref<TimeRangeText>('15m')
const statusColors = shallowRef<Record<HostStatusType, keyof typeof
CommonStatusTexts>>({
1: 'healthy',
@@ -133,9 +133,9 @@
const handleHostOperate = async (item: MenuItem, component: ComponentVO) => {
const { serviceName } = component
- const installedServiceMap =
Object.values(installedStore.installedServiceMap)
+ const installedServiceMap = Object.values(serviceStore.serviceMap)
.flat()
- .filter((v) => v.serviceName === serviceName)
+ .filter((v) => v.name === serviceName)
if (installedServiceMap.length > 0) {
try {
await jobProgressStore.processCommand(
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
index 1824c0d0..e667fdb3 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
@@ -32,8 +32,7 @@
const router = useRouter()
const activeKey = ref('1')
const currCluster = shallowRef<ClusterVO>({
- id: 0,
- name: '0'
+ id: 0
})
const tabs = computed((): TabItem[] => [
@@ -62,7 +61,7 @@
})
const addService: GroupItem['clickEvent'] = () => {
- router.push({ name: 'CreateInfraService', params: { id: 0, cluster: 0,
creationMode: 'public' } })
+ router.push({ name: 'CreateInfraService', params: { id: 0, creationMode:
'public' } })
}
</script>
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 18a0d459..f014ee53 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 6f7030f9..e907185e 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 7e5139b9..be915a89 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 b9a77030..c42048dd 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<ClusterVO[]>([])
const loading = ref(false)
+ const clusters = ref<ClusterVO[]>([])
const currCluster = ref<ClusterVO>({})
+ const clusterMap = ref<Record<string, ClusterVO>>({})
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<string, ClusterVO>
+ )
+ } 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 00000000..385d0919
--- /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<ExpandServiceVO[]>([])
+ const selectedServicesMeta = ref<ExpandServiceVO[]>([])
+ const createdPayload = ref<CommandVO>({})
+ const stepContext = ref<StepContext>({
+ clusterId: 0,
+ serviceId: 0,
+ creationMode: 'internal'
+ })
+ const commandRequest = ref<CommandRequest>({
+ 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<string, CompItem>
+ })
+
+ 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<string, CompItem>
+ })
+
+ 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<ProcessResult> {
+ 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<string, ExpandServiceVO>,
+ servicesOfInfra: ExpandServiceVO[],
+ collected: ExpandServiceVO[]
+ ): Promise<ProcessResult> {
+ 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<string, CompItem>) {
+ 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 00000000..cf5c361e
--- /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 bb404d47..00000000
--- 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<Record<string, InstalledMapItem[]>>({})
-
- 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<ServiceVO[] | undefined> => {
- try {
- const serviceIds = getInstalledNamesOrIdsOfServiceByKey(key, 'ids')
- const allDetail = serviceIds?.map((id) =>
- serviceStore.getServiceDetail(Number(key), Number(id))
- ) as Promise<ServiceVO>[]
- 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 597ad2b8..e2bf4d1d 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 69810f3b..00000000
--- 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 ab265c16..1f0ed985 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 3eb04458..1c233cdb 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<ServiceVO[]>([])
+ const serviceMap = ref<Record<string, (ServiceVO & { clusterId: number
})[]>>({})
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<string, ServiceVO> = {}
+
+ 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<ServiceVO[] | undefined> => {
+ try {
+ const serviceIds = getInstalledNamesOrIdsOfServiceByKey(key, 'ids')
+ const allDetail = serviceIds?.map((id) =>
getServiceDetail(Number(key), Number(id))) as Promise<ServiceVO>[]
+ 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 99acbaa5..30aeda34 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!] = {