bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue (222 lines of code) (raw):
<!--
~ 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
~
~ http://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.
-->
<script setup lang="ts">
import { useMenuStore } from '@/store/menu'
import { message } from 'ant-design-vue'
import { computed, ref, shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { getInstalledStatus, installDependencies } from '@/api/hosts'
import { InstalledStatusVO, Status } from '@/api/hosts/types'
import { execCommand } from '@/api/command'
import useSteps from '@/composables/use-steps'
import ClusterBase from './components/cluster-base.vue'
import ComponentInfo from './components/component-info.vue'
import HostConfig from './components/host-config.vue'
import CheckWorkflow from './components/check-workflow.vue'
import type { ClusterCommandReq, CommandRequest, CommandVO, HostReq } from '@/api/command/types'
const { t } = useI18n()
const menuStore = useMenuStore()
const compRef = ref<any>()
const installing = ref(false)
const stepData = ref<[Partial<ClusterCommandReq>, any, HostReq[], CommandVO]>([{}, {}, [], {}])
const commandRequest = ref<CommandRequest>({
command: 'Add',
commandLevel: 'cluster'
})
const installStatus = shallowRef<InstalledStatusVO[]>([])
const components = shallowRef<any[]>([ClusterBase, ComponentInfo, HostConfig, CheckWorkflow])
const isInstall = computed(() => current.value === 2)
const hasUnknownHost = computed(() => stepData.value[2].filter((v) => v.status === Status.Unknown).length == 0)
const allInstallSuccess = computed(
() =>
stepData.value[2].length != 0 &&
stepData.value[2].every((v) => v.status === Status.Success) &&
hasUnknownHost.value
)
const getCompName = computed(() => components.value[current.value])
const isDone = computed(() => ['Successful', 'Failed'].includes(stepData.value[stepData.value.length - 1].state))
const steps = computed(() => [
'cluster.cluster_info',
'cluster.component_info',
'cluster.host_config',
'cluster.create'
])
const { current, stepsLimit, previousStep, nextStep } = useSteps(steps.value)
const updateData = (val: Partial<ClusterCommandReq> | any | HostReq[]) => {
stepData.value[current.value] = val
}
const createCluster = async () => {
try {
commandRequest.value.clusterCommand = stepData.value[0] as ClusterCommandReq
commandRequest.value.clusterCommand.hosts = stepData.value[2]
stepData.value[stepData.value.length - 1] = await execCommand(commandRequest.value)
return true
} catch (error) {
console.log('error :>> ', error)
return false
}
}
const prepareNextStep = async () => {
if (current.value === 0) {
const check = await compRef.value.check()
if (check) {
nextStep()
}
} else if (current.value === 2) {
if (!allInstallSuccess.value) {
await resolveInstallDependencies()
} else {
const isClusterExisting = await createCluster()
isClusterExisting && nextStep()
}
} else {
nextStep()
}
}
const resolveInstallDependencies = async () => {
if (stepData.value[2].length == 0) {
message.error(t('host.uninstallable'))
return
}
try {
installing.value = true
const data = await installDependencies(stepData.value[current.value])
data && pollUntilInstalled()
} catch (error) {
installing.value = false
console.log('error :>> ', error)
}
}
const recordInstalledStatus = async () => {
try {
const data = await getInstalledStatus()
installStatus.value = data
stepData.value[current.value] = mergeByHostname(stepData.value[current.value], data)
return data.every((item) => item.status != Status.Installing)
} catch (error) {
console.log('error :>> ', error)
}
}
const pollUntilInstalled = (interval: number = 1000): void => {
let isInitialized = false
let intervalId: NodeJS.Timeout
const poll = async () => {
if (!isInitialized) {
stepData.value[current.value] = stepData.value[current.value].map((item: HostReq) => ({
...item,
status: Status.Installing
}))
isInitialized = true
}
const result = await recordInstalledStatus()
if (result) {
installing.value = false
clearInterval(intervalId)
}
}
intervalId = setInterval(poll, interval)
poll()
}
const mergeByHostname = (arr1: any[], arr2: any[]): any[] => {
const mergedMap = new Map<string, any>()
for (const item of arr1) {
mergedMap.set(item.hostname, { ...item })
}
for (const item of arr2) {
if (mergedMap.has(item.hostname)) {
mergedMap.set(item.hostname, { ...mergedMap.get(item.hostname), ...item })
} else {
mergedMap.set(item.hostname, { ...item })
}
}
return Array.from(mergedMap.values())
}
// const changeStep = (step: number) => {
// if (current.value > step) {
// const previousCount = current.value - step
// Array.from({ length: previousCount }).forEach(() => previousStep())
// }
// if (current.value < step) {
// const nextCount = step - current.value
// Array.from({ length: nextCount }).forEach(() => prepareNextStep())
// }
// }
const onSave = () => {
menuStore.updateSider()
}
</script>
<template>
<div class="cluster-create">
<header-card>
<div class="steps-wrp">
<a-steps :current="current">
<a-step v-for="step in steps" :key="step" :disabled="true">
<template #title>
<span>{{ $t(step) }}</span>
</template>
</a-step>
</a-steps>
</div>
</header-card>
<main-card>
<template v-for="stepItem in steps" :key="stepItem.title">
<div v-show="steps[current] === stepItem" class="step-title">
<h5>{{ $t(stepItem) }}</h5>
<section :class="{ 'step-content': current < stepsLimit }"></section>
</div>
</template>
<component :is="getCompName" ref="compRef" :step-data="stepData[current]" @update-data="updateData" />
<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">
{{ $t('common.prev') }}
</a-button>
<template v-if="current >= 0 && current <= stepsLimit - 1">
<a-button
v-if="isInstall && !allInstallSuccess"
type="primary"
:disabled="installing"
@click="prepareNextStep"
>
{{ $t('cluster.install_dependencies') }}
</a-button>
<a-button v-else type="primary" @click="prepareNextStep">
{{ $t('common.next') }}
</a-button>
</template>
<a-button v-show="current === stepsLimit && isDone" type="primary" @click="onSave"
>{{ $t('common.done') }}
</a-button>
</a-space>
</div>
</main-card>
</div>
</template>
<style lang="scss" scoped>
.cluster-create {
min-width: 600px;
.header-card {
min-height: 80px;
}
.steps-wrp {
width: 100%;
height: 100%;
padding-inline: 6%;
}
}
.step-title {
h5 {
margin: 0;
font-size: 16px;
font-weight: 500;
letter-spacing: 0px;
line-height: 16px;
}
}
.step-content {
padding-block: $space-md;
}
.step-action {
text-align: end;
margin-top: $space-md;
}
</style>