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 4c0de7c9 BIGTOP-4454: Add hostname duplication check on host addition
(#236)
4c0de7c9 is described below
commit 4c0de7c9d88b7dac4c6f5a252f3b76024f08e786
Author: Fdefined <[email protected]>
AuthorDate: Sat Jul 12 11:03:54 2025 +0800
BIGTOP-4454: Add hostname duplication check on host addition (#236)
---
bigtop-manager-ui/src/assets/images/svg/warn.svg | 25 ++
.../src/components/common/svg-icon/index.vue | 3 +-
.../create-cluster/components/host-manage.vue | 154 ++++++++-----
.../src/components/create-cluster/create.vue | 115 ++++++----
.../create-host/components/conflict-resolver.vue | 77 +++++++
.../create-host/components/parsed-preview.vue | 168 ++++++++++++++
.../hosts => components/create-host}/create.vue | 254 ++++++++-------------
.../create-host}/install-dependencies.vue | 8 +-
bigtop-manager-ui/src/layouts/sider.vue | 29 ++-
bigtop-manager-ui/src/locales/en_US/cluster.ts | 6 +-
bigtop-manager-ui/src/locales/zh_CN/cluster.ts | 6 +-
.../src/pages/cluster-manage/cluster/host.vue | 11 +-
.../src/pages/cluster-manage/hosts/index.vue | 15 +-
bigtop-manager-ui/src/router/guard.ts | 10 +-
.../src/router/routes/modules/clusters.ts | 2 +-
bigtop-manager-ui/src/store/cluster/index.ts | 4 -
16 files changed, 581 insertions(+), 306 deletions(-)
diff --git a/bigtop-manager-ui/src/assets/images/svg/warn.svg
b/bigtop-manager-ui/src/assets/images/svg/warn.svg
new file mode 100644
index 00000000..6568a199
--- /dev/null
+++ b/bigtop-manager-ui/src/assets/images/svg/warn.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"
+ viewBox="0 0 16 16" fill="none">
+ <circle cx=" 8" cy="8" r="7.33" stroke="rgba(250, 173, 20, 1)"
stroke-width="1.333" fill="none" />
+ <path d="M8 4.5v4" stroke="rgba(250, 173, 20, 1)" stroke-width="1.333"
stroke-linecap="round" />
+ <circle cx="8" cy="10.8" r="0.67" fill="rgba(250, 173, 20, 1)" />
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
b/bigtop-manager-ui/src/components/common/svg-icon/index.vue
index 0d3fc9d6..0421e613 100644
--- a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
+++ b/bigtop-manager-ui/src/components/common/svg-icon/index.vue
@@ -70,7 +70,8 @@
stop: IconSvgStop,
success: IconSvgSuccess,
unknown: IconSvgUnknown,
- 'carbon-language': IconSvgCarbonLanguage
+ 'carbon-language': IconSvgCarbonLanguage,
+ warn: IconSvgWarn
}
const props = defineProps<{ name: string | undefined }>()
diff --git
a/bigtop-manager-ui/src/components/create-cluster/components/host-manage.vue
b/bigtop-manager-ui/src/components/create-cluster/components/host-manage.vue
index e51f2617..9886e430 100644
--- a/bigtop-manager-ui/src/components/create-cluster/components/host-manage.vue
+++ b/bigtop-manager-ui/src/components/create-cluster/components/host-manage.vue
@@ -18,28 +18,34 @@
-->
<script setup lang="ts">
- import { TableColumnType } from 'ant-design-vue'
+ import { PaginationProps, TableColumnType } from 'ant-design-vue'
import { computed, reactive, ref, watch } from 'vue'
import { generateRandomId } from '@/utils/tools'
import { useI18n } from 'vue-i18n'
+
import useBaseTable from '@/composables/use-base-table'
- import HostCreate from '@/pages/cluster-manage/hosts/create.vue'
+ import HostCreate from '@/components/create-host/create.vue'
+
import type { FilterConfirmProps, FilterResetProps } from
'ant-design-vue/es/table/interface'
import type { GroupItem } from '@/components/common/button-group/types'
import type { HostReq } from '@/api/command/types'
type Key = string | number
+ type conflictItem = HostReq & { strategy: 'override' | 'keep' }
+
interface TableState {
selectedRowKeys: Key[]
searchText: string
searchedColumn: string
}
+
const { t } = useI18n()
const props = defineProps<{ stepData: HostReq[] }>()
const emits = defineEmits(['updateData'])
- const data = ref<HostReq[]>([])
+
const searchInputRef = ref()
const hostCreateRef = ref<InstanceType<typeof HostCreate> | null>(null)
+
const state = reactive<TableState>({
searchText: '',
searchedColumn: '',
@@ -86,43 +92,19 @@
}
])
- const { loading, dataSource, paginationProps, onChange } = useBaseTable({
- columns: columns.value,
- rows: data.value
- })
+ const { loading, dataSource, paginationProps, onChange } =
useBaseTable<HostReq>({ columns: columns.value, rows: [] })
const operations = computed((): GroupItem[] => [
- {
- text: 'edit',
- clickEvent: (_item, args) => editHost(args)
- },
- {
- text: 'remove',
- danger: true,
- clickEvent: (_item, args) => deleteHost(args)
- }
+ { text: 'edit', clickEvent: (_item, args) => updateHost('EDIT', args) },
+ { text: 'remove', danger: true, clickEvent: (_item, args) =>
deleteHost(args) }
])
- watch(
- () => props.stepData,
- (val) => {
- dataSource.value = val
- },
- {
- immediate: true
- }
- )
-
const isContain = (source: string, target: string): boolean => {
return source.toString().toLowerCase().includes(target.toLowerCase())
}
const onFilterDropdownOpenChange = (visible: boolean) => {
- if (visible) {
- setTimeout(() => {
- searchInputRef.value.focus()
- }, 100)
- }
+ visible && setTimeout(searchInputRef.value.focus(), 100)
}
const onSelectChange = (selectedRowKeys: Key[]) => {
@@ -140,60 +122,108 @@
state.searchText = ''
}
- const addHost = () => {
- hostCreateRef.value?.handleOpen('ADD')
+ const updateHost = (type: 'ADD' | 'EDIT', row?: HostReq) => {
+ hostCreateRef.value?.handleOpen(type, row)
}
- const editHost = (row: HostReq) => {
- hostCreateRef.value?.handleOpen('EDIT', row)
+ const deleteHost = (row?: HostReq) => {
+ if (row?.hostnames) {
+ dataSource.value = dataSource.value?.filter((v) => row!.key !== v.key)
+ } else {
+ dataSource.value = dataSource.value?.filter((v) =>
!state.selectedRowKeys.includes(v.key)) || []
+ }
+ updateStepData()
}
const updateStepData = () => {
- const res = dataSource.value.map((v) => {
- return {
- ...v,
- hostnames: [v.hostname]
- }
- })
+ Object.assign(paginationProps.value as PaginationProps, { total:
dataSource.value.length })
+ const res = dataSource.value.map((v) => ({ ...v, hostnames: [v.hostname]
}))
emits('updateData', res)
}
- const addHostSuccess = (type: 'ADD' | 'EDIT', item: HostReq) => {
+ /**
+ * Merges duplicate hostnames based on strategy.
+ * @param list
+ * @param duplicateHosts
+ * @param config host config
+ */
+ const mergeByStrategy = (list: HostReq[], duplicateHosts: conflictItem[],
config: HostReq): HostReq[] => {
+ const strategyMap = new Map<string, conflictItem>()
+ duplicateHosts.forEach((s) => strategyMap.set(s.hostname, s))
+
+ const existingHostnames = new Set<string>()
+ const mainPart: HostReq[] = []
+ const prependPart: HostReq[] = []
+
+ for (const item of list) {
+ existingHostnames.add(item.hostname)
+ const strategy = strategyMap.get(item.hostname)
+
+ if (!strategy) {
+ mainPart.push(item)
+ } else if (strategy.strategy === 'override') {
+ mainPart.push(generateNewHostItem(item.hostname, config))
+ } else {
+ mainPart.push(item)
+ }
+ }
+
+ for (const s of duplicateHosts) {
+ if (!existingHostnames.has(s.hostname)) {
+ prependPart.push(generateNewHostItem(s.hostname, config))
+ }
+ }
+
+ return [...prependPart, ...mainPart]
+ }
+
+ const generateNewHostItem = (hostname: string, config: HostReq): HostReq =>
({
+ ...config,
+ key: generateRandomId(),
+ hostname: hostname,
+ status: 'UNKNOWN'
+ })
+
+ /**
+ * Handles successful addition or editing of hosts.
+ */
+ const addHostSuccess = (type: 'ADD' | 'EDIT', item: HostReq, duplicateHosts:
any) => {
if (type === 'ADD') {
- const items = item.hostnames?.map((v) => {
- return {
- ...item,
- key: generateRandomId(),
- hostname: v,
- status: 'UNKNOWN'
- }
- }) as HostReq[]
- dataSource.value?.unshift(...items)
- } else {
+ if (duplicateHosts.length > 0) {
+ dataSource.value = mergeByStrategy(dataSource.value, duplicateHosts,
item)
+ } else {
+ const items = item.hostnames?.map((v) => generateNewHostItem(v, item))
as HostReq[]
+ dataSource.value?.unshift(...items)
+ }
+ }
+
+ if (type === 'EDIT') {
const index = dataSource.value.findIndex((data) => data.key === item.key)
+
if (index !== -1) {
dataSource.value[index] = item
}
}
+
updateStepData()
}
- const deleteHost = (row?: HostReq) => {
- if (!row?.key) {
- state.selectedRowKeys.length > 0 &&
- (dataSource.value = dataSource.value?.filter((v) =>
!state.selectedRowKeys.includes(v.key)) || [])
- } else {
- dataSource.value = dataSource.value?.filter((v) => row.key !== v.key)
+ watch(
+ () => props.stepData,
+ (val) => {
+ dataSource.value = JSON.parse(JSON.stringify(val))
+ },
+ {
+ immediate: true
}
- updateStepData()
- }
+ )
</script>
<template>
<div class="host-config">
<header>
<a-space :size="16">
- <a-button type="primary" @click="addHost">{{ $t('cluster.add_host')
}}</a-button>
+ <a-button type="primary" @click="updateHost('ADD')">{{
$t('cluster.add_host') }}</a-button>
<a-button type="primary" danger @click="deleteHost">{{
$t('common.bulk_remove') }}</a-button>
</a-space>
</header>
@@ -246,7 +276,7 @@
</template>
</template>
</a-table>
- <host-create ref="hostCreateRef" @on-ok="addHostSuccess" />
+ <host-create ref="hostCreateRef" :current-hosts="dataSource"
@on-ok="addHostSuccess" />
</div>
</template>
diff --git a/bigtop-manager-ui/src/components/create-cluster/create.vue
b/bigtop-manager-ui/src/components/create-cluster/create.vue
index b1863257..96090291 100644
--- a/bigtop-manager-ui/src/components/create-cluster/create.vue
+++ b/bigtop-manager-ui/src/components/create-cluster/create.vue
@@ -37,6 +37,7 @@
const { t } = useI18n()
const menuStore = useMenuStore()
+
const compRef = ref<any>()
const installing = ref(false)
const stepData = ref<[Partial<ClusterCommandReq>, any, HostReq[],
CommandVO]>([{}, {}, [], {}])
@@ -48,16 +49,17 @@
const installStatus = shallowRef<InstalledStatusVO[]>([])
const components = shallowRef<any[]>([ClusterBase, ComponentInfo,
HostManage, CheckWorkflow])
const isInstall = computed(() => current.value === 2)
- const hasUnknownHost = computed(() => stepData.value[2].filter((v) =>
v.status === Status.Unknown).length == 0)
-
+ const currentStepItems = computed(() => stepData.value[2])
+ const hasNoUnknownHosts = computed(() => currentStepItems.value.every((item)
=> item.status !== Status.Unknown))
const allInstallSuccess = computed(
() =>
- stepData.value[2].length != 0 &&
- stepData.value[2].every((v) => v.status === Status.Success) &&
- hasUnknownHost.value
+ currentStepItems.value.length > 0 &&
+ currentStepItems.value.every((item) => item.status === Status.Success) &&
+ hasNoUnknownHosts.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',
@@ -71,18 +73,6 @@
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()
@@ -101,42 +91,59 @@
}
}
+ /**
+ * Resolves installation dependencies by setting status and calling API.
+ * Starts polling to track progress.
+ */
const resolveInstallDependencies = async () => {
+ const targetStepData = stepData.value[current.value]
+
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()
+ stepData.value[current.value] = setInstallItemStatus(targetStepData,
Status.Installing)
+
+ const result = await installDependencies(stepData.value[current.value])
+ if (result) {
+ pollUntilInstalled()
+ }
} catch (error) {
+ console.error('Error resolving dependencies:', error)
installing.value = false
- console.log('error :>> ', error)
+ stepData.value[current.value] =
setInstallItemStatus(stepData.value[current.value], Status.Failed)
}
}
+ /**
+ * Records and updates current install status for items.
+ * @returns whether all items are no longer in "Installing" state
+ */
const recordInstalledStatus = async () => {
try {
const data = await getInstalledStatus()
installStatus.value = data
- stepData.value[current.value] =
mergeByHostname(stepData.value[current.value], data)
+ stepData.value[current.value] =
setInstallItemStatus(stepData.value[current.value], data)
+
return data.every((item) => item.status != Status.Installing)
} catch (error) {
- console.log('error :>> ', error)
+ console.error('Error recording installation status:', error)
}
}
+ /**
+ * Starts polling install status until all items are complete.
+ * @param interval polling interval in milliseconds (default: 1000)
+ */
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
- }))
+ stepData.value[current.value] =
setInstallItemStatus(stepData.value[current.value], Status.Installing)
isInitialized = true
}
const result = await recordInstalledStatus()
@@ -150,34 +157,46 @@
poll()
}
- const mergeByHostname = (arr1: any[], arr2: any[]): any[] => {
- const mergedMap = new Map<string, any>()
- for (const item of arr1) {
+ /**
+ * Merges install items with status.
+ * If status is an array, merge each item by hostname.
+ * If status is a string, apply it uniformly to all items.
+ *
+ * @param items original install item list
+ * @param status array of partial updates OR a uniform status string
+ * @returns merged install item list
+ */
+ const setInstallItemStatus = <T extends HostReq>(items: T[], status: T[] |
string): T[] => {
+ const mergedMap = new Map<string, T>()
+
+ for (const item of items) {
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 })
+
+ if (Array.isArray(status)) {
+ for (const item of status) {
+ const mergedMapItem = mergedMap.get(item.hostname)
+ mergedMap.set(item.hostname, mergedMapItem ? { ...mergedMapItem,
...item } : { ...item })
+ }
+ } else {
+ for (const [hostname, item] of mergedMap) {
+ mergedMap.set(hostname, { ...item, status })
}
}
+
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()
+ 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
+ }
}
onBeforeRouteLeave((_to, _from, next) => {
@@ -243,7 +262,7 @@
{{ $t('common.next') }}
</a-button>
</template>
- <a-button v-show="current === stepsLimit && isDone" type="primary"
@click="onSave"
+ <a-button v-show="current === stepsLimit && isDone" type="primary"
@click="() => menuStore.updateSider()"
>{{ $t('common.done') }}
</a-button>
</a-space>
diff --git
a/bigtop-manager-ui/src/components/create-host/components/conflict-resolver.vue
b/bigtop-manager-ui/src/components/create-host/components/conflict-resolver.vue
new file mode 100644
index 00000000..7889b570
--- /dev/null
+++
b/bigtop-manager-ui/src/components/create-host/components/conflict-resolver.vue
@@ -0,0 +1,77 @@
+<!--
+ ~ 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 { computed } from 'vue'
+ import { useI18n } from 'vue-i18n'
+
+ import type { HostReq } from '@/api/command/types'
+ import type { TableColumnType } from 'ant-design-vue'
+
+ type conflictItem = HostReq & { strategy: 'override' | 'keep' }
+
+ const props = defineProps<{ parsedHosts: conflictItem[]; duplicateHostnames:
string[] }>()
+ const emits = defineEmits<{ (event: 'update:parsedHosts', val:
conflictItem[]): void }>()
+
+ const options = ['keep', 'override']
+
+ const { t } = useI18n()
+ const columns = computed((): TableColumnType[] => [
+ {
+ title: t('host.hostname'),
+ dataIndex: 'hostname',
+ key: 'hostname',
+ ellipsis: true
+ },
+ {
+ title: t('common.operation'),
+ key: 'operation',
+ width: '200px',
+ fixed: 'right'
+ }
+ ])
+
+ const handleStrategyChange = (hostname: string, strategy: 'keep' |
'override') => {
+ const updated = props.parsedHosts.map((item) => (item.hostname ===
hostname ? { ...item, strategy } : item))
+ emits('update:parsedHosts', updated)
+ }
+</script>
+
+<template>
+ <div>
+ <a-table
+ :data-source="$props.parsedHosts.filter((v) =>
$props.duplicateHostnames.includes(v.hostname))"
+ :columns="columns"
+ >
+ <template #bodyCell="{ record, column }">
+ <template v-if="column.key === 'operation'">
+ <a-radio-group
+ :value="record.strategy"
+ style="display: flex"
+ @change="(e) => handleStrategyChange(record.hostname,
e.target.value)"
+ >
+ <a-radio v-for="opt in options" :key="opt" :value="opt">{{ opt
}}</a-radio>
+ </a-radio-group>
+ </template>
+ </template>
+ </a-table>
+ </div>
+</template>
+
+<style scoped></style>
diff --git
a/bigtop-manager-ui/src/components/create-host/components/parsed-preview.vue
b/bigtop-manager-ui/src/components/create-host/components/parsed-preview.vue
new file mode 100644
index 00000000..e910b02b
--- /dev/null
+++ b/bigtop-manager-ui/src/components/create-host/components/parsed-preview.vue
@@ -0,0 +1,168 @@
+<!--
+ ~ 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 { ref, shallowRef } from 'vue'
+ import { parseHostNamesAsPatternExpression } from '@/utils/array'
+ import ConflictResolver from './conflict-resolver.vue'
+
+ import type { HostReq } from '@/api/command/types'
+
+ type Data = HostReq & { hostname?: string }
+ type conflictItem = HostReq & { strategy: 'override' | 'keep' }
+
+ interface ParsedRes {
+ parsedData: Data
+ confirmStatus: boolean
+ duplicateHosts?: conflictItem[]
+ }
+
+ const props = defineProps<{ data: Data; isPublic: boolean | undefined }>()
+ const emits = defineEmits<{ (event: 'parsed', value: ParsedRes): void }>()
+
+ const open = ref(false)
+ const showConflictList = ref(false)
+ const parsedHostNames = ref<string[]>([])
+ const duplicateHostnames = ref<string[]>([])
+ const parsedHosts = ref<conflictItem[]>([])
+ const cacheCurrentHosts = shallowRef<Data[]>([])
+
+ const parsed = (currentHosts: Data[]) => {
+ resetState()
+ open.value = true
+ cacheCurrentHosts.value = currentHosts
+ parsedHostNames.value =
parseHostNamesAsPatternExpression(props.data.hostname || '')
+ parsedHosts.value = parsedHostNames.value.map((v) => ({ hostname: v,
strategy: 'override' }))
+ }
+
+ const getDuplicateHostnames = () => {
+ const currentHostNamse = cacheCurrentHosts.value.map((v) => v.hostname)
+ return parsedHostNames.value.filter((item) =>
currentHostNamse.includes(item))
+ }
+
+ const handleOk = () => {
+ const duplicates = getDuplicateHostnames()
+ duplicateHostnames.value = duplicates
+
+ const hasConflicts = duplicates.length > 0
+
+ if (!hasConflicts || showConflictList.value) {
+ emits('parsed', {
+ parsedData: { ...props.data, hostnames: parsedHostNames.value },
+ confirmStatus: true,
+ duplicateHosts: hasConflicts ? parsedHosts.value : []
+ })
+ resetState()
+ }
+
+ if (hasConflicts) {
+ showConflictList.value = true
+ }
+ }
+
+ const handleCancel = () => {
+ emits('parsed', { parsedData: { ...props.data }, confirmStatus: false,
duplicateHosts: [] })
+ resetState()
+ }
+
+ const resetState = () => {
+ open.value = false
+ showConflictList.value = false
+ parsedHostNames.value = []
+ duplicateHostnames.value = []
+ }
+
+ defineExpose({
+ parsed
+ })
+</script>
+
+<template>
+ <a-modal v-model:open="open" :centered="true" width="680px" :z-index="2000"
@ok="handleOk" @cancel="handleCancel">
+ <template #title>
+ <div class="icon">
+ <span class="icon-wrp">
+ <svg-icon :name="showConflictList ? 'warn' : 'success'"></svg-icon>
+ </span>
+ <span class="title">
+ {{
+ showConflictList
+ ? $t('cluster.duplicate_hostname',
[`${duplicateHostnames.length}`])
+ : $t('cluster.show_hosts_resolved')
+ }}
+ </span>
+ </div>
+ </template>
+ <div v-if="!showConflictList" class="content">
+ <div v-for="(name, index) in parsedHostNames" :key="index"
class="item">{{ name }}</div>
+ </div>
+ <div v-else>
+ <conflict-resolver v-model:parsedHosts="parsedHosts"
:duplicate-hostnames="duplicateHostnames" />
+ </div>
+ <template #footer>
+ <footer>
+ <a-space size="middle">
+ <a-button @click="handleCancel">
+ {{ $t('common.cancel') }}
+ </a-button>
+ <a-button v-if="showConflictList" @click="() => (showConflictList =
false)">
+ {{ $t('common.prev') }}
+ </a-button>
+ <a-button type="primary" @click="handleOk">
+ {{ $t('common.confirm') }}
+ </a-button>
+ </a-space>
+ </footer>
+ </template>
+ </a-modal>
+</template>
+
+<style lang="scss" scoped>
+ .content {
+ margin-inline-start: 2.125rem;
+ max-height: 31.25rem;
+ overflow: auto;
+ }
+
+ .title {
+ font-size: 1rem;
+ font-weight: 600;
+ }
+
+ .icon {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .icon-wrp {
+ font-size: 1.375rem;
+ font-weight: 600;
+ color: green;
+ }
+
+ .item {
+ margin: 0.625rem;
+ }
+
+ footer {
+ width: 100%;
+ @include flexbox($justify: flex-end);
+ }
+</style>
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
b/bigtop-manager-ui/src/components/create-host/create.vue
similarity index 74%
rename from bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
rename to bigtop-manager-ui/src/components/create-host/create.vue
index c202eb53..0036eec5 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
+++ b/bigtop-manager-ui/src/components/create-host/create.vue
@@ -18,21 +18,25 @@
-->
<script setup lang="ts">
- import { computed, h, nextTick, ref, watch } from 'vue'
+ import { computed, nextTick, ref, watch } from 'vue'
+ import { Rule } from 'ant-design-vue/es/form'
+ import { message } from 'ant-design-vue'
+ import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
- import { FormItemState } from '@/components/common/auto-form/types'
- import { CheckCircleOutlined, UploadOutlined } from '@ant-design/icons-vue'
- import { parseHostNamesAsPatternExpression } from '@/utils/array'
- import { message, Modal } from 'ant-design-vue'
+ import { UploadOutlined } from '@ant-design/icons-vue'
+
import { useLocaleStore } from '@/store/locale'
- import { storeToRefs } from 'pinia'
+ import { useClusterStore } from '@/store/cluster'
+
import { uploadFile } from '@/api/upload-file'
- import { Rule } from 'ant-design-vue/es/form'
import { updateHost } from '@/api/hosts'
- import { useClusterStore } from '@/store/cluster'
+
+ import ParsedPreview from './components/parsed-preview.vue'
+
import type { UploadProps } from 'ant-design-vue'
import type { HostReq } from '@/api/command/types'
import type { HostParams, HostVO } from '@/api/hosts/types'
+ import type { FormItemState } from '@/components/common/auto-form/types'
enum Mode {
EDIT = 'cluster.edit_host',
@@ -40,18 +44,28 @@
}
interface Emits {
- (event: 'onOk', type: keyof typeof Mode, value: HostReq | HostVO): void
+ (
+ event: 'onOk',
+ type: keyof typeof Mode,
+ value: HostReq | HostVO,
+ duplicateHosts?: HostReq & { strategy: 'override' | 'keep' }[]
+ ): void
}
- const props = withDefaults(defineProps<{ isPublic?: boolean; apiEditCaller?:
boolean }>(), {
- isPublic: false,
- apiEditCaller: false
- })
+ interface Props {
+ isPublic?: boolean
+ apiEditCaller?: boolean
+ currentHosts: HostReq[]
+ }
- const { t } = useI18n()
+ const props = withDefaults(defineProps<Props>(), { isPublic: false,
apiEditCaller: false })
const emits = defineEmits<Emits>()
+
+ const { t } = useI18n()
const localeStore = useLocaleStore()
const clusterStore = useClusterStore()
+ const { locale } = storeToRefs(localeStore)
+
const open = ref(false)
const loading = ref(false)
const mode = ref<keyof typeof Mode>('ADD')
@@ -59,9 +73,13 @@
const autoFormRef = ref<Comp.AutoFormInstance | null>(null)
const formValue = ref<HostReq & { hostname?: string }>({})
const fileName = ref('')
- const { locale } = storeToRefs(localeStore)
+ const previewRef = ref<InstanceType<typeof ParsedPreview> | null>()
+
const isEdit = computed(() => mode.value === 'EDIT')
+ /**
+ * Validates SSH password confirmation.
+ */
const checkSshPassword = async (_rule: Rule, value: string) => {
if (!value) {
return Promise.reject(t('common.enter_error',
[`${t('host.confirm_password')}`.toLowerCase()]))
@@ -73,6 +91,9 @@
}
}
+ /**
+ * Validates SSH key password confirmation.
+ */
const checkSshKeyPassword = async (_rule: Rule, value: string) => {
if (value != formValue.value?.sshKeyPassword) {
return Promise.reject(t('host.key_password_not_match'))
@@ -331,185 +352,81 @@
])
const filterFormItems = computed((): FormItemState[] => {
+ const baseItems = [...formItems.value]
+ const isPublic = props.isPublic
+
if (formValue.value.authType === '1') {
- const data = [...formItems.value]
- data.splice(2, 0, ...formItemsForSshPassword.value)
- return props.isPublic ? [...formItemsOfPublicHost.value, ...data] : data
+ baseItems.splice(2, 0, ...formItemsForSshPassword.value)
} else if (formValue.value.authType === '2') {
- const data = [...formItems.value]
- data.splice(2, 0, ...formItemsForSshKeyPassword.value)
- return props.isPublic ? [...formItemsOfPublicHost.value, ...data] : data
+ baseItems.splice(2, 0, ...formItemsForSshKeyPassword.value)
}
- return props.isPublic ? [...formItemsOfPublicHost.value,
...formItems.value] : [...formItems.value]
+
+ return isPublic ? [...formItemsOfPublicHost.value, ...baseItems] :
baseItems
})
watch(
- () => formValue.value,
- (val) => {
- if (val.inputType && val.inputType === '1') {
- hiddenItems.value = ['sshKeyString']
- } else if (val.inputType && val.inputType === '2') {
- hiddenItems.value = ['sshKeyFilename']
- } else {
- hiddenItems.value = []
+ () => formValue.value.inputType,
+ (inputType) => {
+ const hiddenMap: Record<string, string[]> = {
+ '1': ['sshKeyString'],
+ '2': ['sshKeyFilename']
}
- },
- {
- deep: true
+ hiddenItems.value = hiddenMap[inputType] ?? []
}
)
- const handleOpen = async (type: keyof typeof Mode, payload?: HostParams) => {
- open.value = true
- mode.value = type
- if (payload) {
- formValue.value = Object.assign(formValue.value, {
- ...payload,
- authType: `${payload?.authType ?? 1}`,
- inputType: `${payload?.inputType ?? 1}`
- })
- } else {
- Object.assign(formValue.value, {
- authType: '1',
- inputType: '1'
- })
- }
-
- props.isPublic && (await getClusterSelectOptions())
- }
-
- const getClusterSelectOptions = async () => {
- await nextTick()
- const formatClusters = Object.values(clusterStore.clusterMap).map((v) =>
({ ...v, clusterId: v.id }))
- autoFormRef.value?.setOptions('clusterId', formatClusters)
- }
-
+ /**
+ * Handles host editing.
+ */
const editHost = async (hostConfig: HostReq) => {
try {
const data = await updateHost(hostConfig)
if (data) {
- loading.value = false
message.success(t('common.update_success'))
emits('onOk', mode.value, formValue.value)
handleCancel()
}
} catch (error) {
- console.log('error :>> ', error)
+ console.error('Error editing host:', error)
} finally {
loading.value = false
}
}
+ /**
+ * Handles parsed data from preview.
+ */
+ const handleParsed = ({ parsedData, confirmStatus, duplicateHosts }: any) =>
{
+ if (confirmStatus) {
+ Object.assign(formValue.value, parsedData)
+ emits('onOk', mode.value, formValue.value, duplicateHosts)
+ handleCancel()
+ }
+ }
+
const handleOk = async () => {
const validate = await autoFormRef.value?.getFormValidation()
if (!validate) return
- try {
- if (!isEdit.value) {
- const confirmStatus = await showHosts()
- if (confirmStatus) {
- emits('onOk', mode.value, formValue.value)
- handleCancel()
- }
- } else {
- if (props.apiEditCaller) {
- loading.value = true
- await editHost(formValue.value)
- } else {
- emits('onOk', mode.value, formValue.value)
- handleCancel()
- }
- }
- } catch (e) {
- console.log(e)
- }
- }
- const createHostNameEls = (list: string[]) => {
- const elStyles = {
- contentStyle: {
- marginInlineStart: '2.125rem',
- width: '100%',
- maxHeight: '31.25rem',
- overflow: 'auto'
- },
- itemStyle: { margin: '0.625rem' },
- iconWrpStyle: {
- fontSize: '1.375rem',
- fontWeight: 600,
- color: 'green',
- marginInlineEnd: '0.75rem'
- },
- titleStyle: {
- fontSize: '1rem',
- fontWeight: 600
- },
- redfIconStyle: {
- display: 'flex',
- alignItems: 'center'
- }
+ if (!isEdit.value) {
+ previewRef.value?.parsed(props.currentHosts)
+ } else if (props.apiEditCaller) {
+ loading.value = true
+ await editHost(formValue.value)
+ } else {
+ emits('onOk', mode.value, formValue.value)
+ handleCancel()
}
- const items = list.map((item: string, idx: number) => {
- return h('li', { key: idx, style: elStyles.itemStyle }, item)
- })
- const iconWrpStyle = h(
- 'span',
- {
- style: elStyles.iconWrpStyle
- },
- [h(CheckCircleOutlined)]
- )
- const replaceTitle = h('span', { style: elStyles.titleStyle },
`${t('cluster.show_hosts_resolved')}`)
- const reDefineIcon = h('div', { style: elStyles.redfIconStyle },
[iconWrpStyle, replaceTitle])
- const box = h(
- 'div',
- {
- style: elStyles.contentStyle
- },
- items
- )
-
- return { box, reDefineIcon }
- }
-
- const showHosts = (): Promise<boolean> => {
- const hostList =
parseHostNamesAsPatternExpression(formValue.value.hostname as string)
- const { box: content, reDefineIcon: icon } = createHostNameEls(hostList)
- return new Promise((resolve) => {
- Modal.confirm({
- title: '',
- icon,
- centered: true,
- width: '31.25rem',
- content,
- onOk() {
- formValue.value.hostnames = hostList
- resolve(true)
- },
- onCancel() {
- resolve(false)
- Modal.destroyAll()
- }
- })
- })
}
const handleCancel = () => {
- formValue.value = {
- authType: '1',
- inputType: '1'
- }
+ formValue.value = { authType: '1', inputType: '1' }
fileName.value = ''
open.value = false
}
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
- // const isText = file.type === 'text/plain'
- const checkLimitSize = file.size / 1024 <= 10
- // if (!isText) {
- // message.error(t('common.file_type_error'))
- // return false
- // }
- if (!checkLimitSize) {
+ if (file.size / 1024 > 10) {
message.error(t('common.file_size_error'))
return false
}
@@ -533,6 +450,25 @@
}
}
+ const handleOpen = async (type: keyof typeof Mode, payload?: HostParams) => {
+ open.value = true
+ mode.value = type
+
+ formValue.value = payload
+ ? { ...payload, authType: `${payload?.authType ?? 1}`, inputType:
`${payload?.inputType ?? 1}` }
+ : { authType: '1', inputType: '1' }
+
+ if (props.isPublic) {
+ await getClusterSelectOptions()
+ }
+ }
+
+ const getClusterSelectOptions = async () => {
+ await nextTick()
+ const formatClusters = Object.values(clusterStore.clusterMap).map((v) =>
({ ...v, clusterId: v.id }))
+ autoFormRef.value?.setOptions('clusterId', formatClusters)
+ }
+
defineExpose({
handleOpen
})
@@ -591,13 +527,11 @@
</footer>
</template>
</a-modal>
+ <parsed-preview ref="previewRef" :is-public="$props.isPublic"
:data="formValue" @parsed="handleParsed" />
</div>
</template>
<style lang="scss" scoped>
- .question {
- cursor: pointer;
- }
.filename {
color: $color-primary;
padding-inline: $space-sm;
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/hosts/install-dependencies.vue
b/bigtop-manager-ui/src/components/create-host/install-dependencies.vue
similarity index 98%
rename from
bigtop-manager-ui/src/pages/cluster-manage/hosts/install-dependencies.vue
rename to bigtop-manager-ui/src/components/create-host/install-dependencies.vue
index adc60a9f..820d3c2b 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/install-dependencies.vue
+++ b/bigtop-manager-ui/src/components/create-host/install-dependencies.vue
@@ -26,8 +26,10 @@
import { useClusterStore } from '@/store/cluster'
import { generateRandomId } from '@/utils/tools'
import { execCommand } from '@/api/command'
- import HostCreate from '@/pages/cluster-manage/hosts/create.vue'
+
+ import HostCreate from './create.vue'
import useBaseTable from '@/composables/use-base-table'
+
import { type InstalledStatusVO, Status } from '@/api/hosts/types'
import type { FilterConfirmProps, FilterResetProps } from
'ant-design-vue/es/table/interface'
import type { GroupItem } from '@/components/common/button-group/types'
@@ -49,10 +51,12 @@
const emits = defineEmits<Emits>()
const clusterStore = useClusterStore()
const { clusterMap } = storeToRefs(clusterStore)
+
const installing = ref(false)
const open = ref(false)
const searchInputRef = ref()
const hostCreateRef = ref<InstanceType<typeof HostCreate> | null>(null)
+
const installStatus = shallowRef<InstalledStatusVO[]>([])
const state = reactive<TableState>({
searchText: '',
@@ -336,7 +340,7 @@
</template>
</a-table>
</a-modal>
- <host-create ref="hostCreateRef" :is-public="true" @on-ok="handleOk" />
+ <host-create ref="hostCreateRef" :current-hosts="dataSource"
:is-public="true" @on-ok="handleOk" />
</div>
</template>
diff --git a/bigtop-manager-ui/src/layouts/sider.vue
b/bigtop-manager-ui/src/layouts/sider.vue
index 1a70130d..adbe5d77 100644
--- a/bigtop-manager-ui/src/layouts/sider.vue
+++ b/bigtop-manager-ui/src/layouts/sider.vue
@@ -31,6 +31,7 @@
const clusterStore = useClusterStore()
const routeParamsLen = ref(0)
const openKeys = ref<string[]>(['clusters'])
+
const { siderMenuSelectedKey, headerSelectedKey, siderMenus,
routePathFromClusters } = storeToRefs(menuStore)
const { clusterCount, clusterMap } = storeToRefs(clusterStore)
@@ -49,29 +50,27 @@
*/
const handleRouteChange = async (newRoute: typeof route) => {
const token = localStorage.getItem('Token') ??
sessionStorage.getItem('Token') ?? undefined
+ const { params, path, meta, name: RouteName } = newRoute
if (!token) {
return
}
- const { params, path, meta, name } = newRoute
- const clusterId = params.id
- const isClusterPath = path.includes(routePathFromClusters.value)
- const isNotCreatingCluster = name !== 'CreateCluster'
-
routeParamsLen.value = Object.keys(params).length
- if (isClusterPath && clusterCount.value > 0 && isNotCreatingCluster) {
- if (clusterId && clusterMap.value[`${clusterId}`]) {
- siderMenuSelectedKey.value =
`${routePathFromClusters.value}/${clusterId}`
+ if (path.includes(routePathFromClusters.value) && RouteName !==
'CreateCluster') {
+ if (params.id && clusterMap.value[`${params.id}`]) {
+ siderMenuSelectedKey.value =
`${routePathFromClusters.value}/${params.id}`
openKeys.value.push(siderMenuSelectedKey.value)
- } else {
- siderMenuSelectedKey.value = ''
- menuStore.setupSider()
+ return
}
- } else {
- siderMenuSelectedKey.value = meta.activeMenu ?? path
+
+ siderMenuSelectedKey.value = ''
+ menuStore.setupSider()
+ return
}
+
+ siderMenuSelectedKey.value = meta.activeMenu ?? path
}
/**
@@ -89,8 +88,8 @@
router.push({ name: 'CreateCluster' })
}
- onMounted(async () => {
- await clusterStore.loadClusters()
+ onMounted(() => {
+ clusterStore.loadClusters()
})
watch(
diff --git a/bigtop-manager-ui/src/locales/en_US/cluster.ts
b/bigtop-manager-ui/src/locales/en_US/cluster.ts
index 11979d82..d3af59c6 100644
--- a/bigtop-manager-ui/src/locales/en_US/cluster.ts
+++ b/bigtop-manager-ui/src/locales/en_US/cluster.ts
@@ -36,5 +36,9 @@ export default {
view_log: 'View Log',
source: 'Repository',
show_hosts_resolved: 'Resolved Hostnames',
- cluster_unavailable_message: 'Sorry, you need to create cluster to use this
feature.'
+ duplicate_hostname: '{0} host name(s) are duplicated. Please choose how to
handle them.',
+ exist_duplicate_hostname: 'Duplicate host name found. Choose an action.',
+ cluster_unavailable_message: 'Sorry, you need to create cluster to use this
feature.',
+ keep: 'keep',
+ override: 'override'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/cluster.ts
b/bigtop-manager-ui/src/locales/zh_CN/cluster.ts
index 29a9cd70..a37580ff 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/cluster.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/cluster.ts
@@ -35,5 +35,9 @@ export default {
view_log: '查看日志',
source: '配置源',
show_hosts_resolved: '解析后的主机名列表',
- cluster_unavailable_message: '抱歉,你还没有集群,无法使用集群管理能力。'
+ duplicate_hostname: '有 {0} 个主机名重复,请选择处理方式',
+ exist_duplicate_hostname: '主机名重复,请选择处理方式',
+ cluster_unavailable_message: '抱歉,你还没有集群,无法使用集群管理能力。',
+ keep: '保留',
+ override: '覆盖'
}
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
index 1a2253af..bafe924b 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
@@ -26,8 +26,8 @@
import { useRouter } from 'vue-router'
import useBaseTable from '@/composables/use-base-table'
- import HostCreate from '@/pages/cluster-manage/hosts/create.vue'
- import InstallDependencies from
'@/pages/cluster-manage/hosts/install-dependencies.vue'
+ import HostCreate from '@/components/create-host/create.vue'
+ import InstallDependencies from
'@/components/create-host/install-dependencies.vue'
import type { FilterConfirmProps, FilterResetProps } from
'ant-design-vue/es/table/interface'
import type { GroupItem } from '@/components/common/button-group/types'
@@ -279,7 +279,12 @@
</template>
</template>
</a-table>
- <host-create ref="hostCreateRef" :api-edit-caller="true"
@on-ok="afterSetupHostConfig" />
+ <host-create
+ ref="hostCreateRef"
+ :current-hosts="dataSource"
+ :api-edit-caller="true"
+ @on-ok="afterSetupHostConfig"
+ />
<install-dependencies ref="installRef"
@on-install-success="getHostList(true)" />
</div>
</template>
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 f0cee396..1d651221 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/index.vue
@@ -22,11 +22,14 @@
import { computed, onMounted, onUnmounted, reactive, ref, shallowRef } from
'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
+
import { useClusterStore } from '@/store/cluster'
import * as hostApi from '@/api/hosts'
+
import useBaseTable from '@/composables/use-base-table'
- import HostCreate from '@/pages/cluster-manage/hosts/create.vue'
- import InstallDependencies from
'@/pages/cluster-manage/hosts/install-dependencies.vue'
+ import HostCreate from '@/components/create-host/create.vue'
+ import InstallDependencies from
'@/components/create-host/install-dependencies.vue'
+
import type { FilterConfirmProps, FilterResetProps, TableRowSelection } from
'ant-design-vue/es/table/interface'
import type { HostReq } from '@/api/command/types'
import type { GroupItem } from '@/components/common/button-group/types'
@@ -346,7 +349,13 @@
</template>
</template>
</a-table>
- <host-create ref="hostCreateRef" :api-edit-caller="true" :is-public="true"
@on-ok="afterSetupHostConfig" />
+ <host-create
+ ref="hostCreateRef"
+ :current-hosts="dataSource"
+ :api-edit-caller="true"
+ :is-public="true"
+ @on-ok="afterSetupHostConfig"
+ />
<install-dependencies ref="installRef"
@on-install-success="getHostList(true)" />
</div>
</template>
diff --git a/bigtop-manager-ui/src/router/guard.ts
b/bigtop-manager-ui/src/router/guard.ts
index 61824c91..7fce803c 100644
--- a/bigtop-manager-ui/src/router/guard.ts
+++ b/bigtop-manager-ui/src/router/guard.ts
@@ -18,20 +18,20 @@
*/
import type { Router } from 'vue-router'
+
function setCommonGuard(router: Router) {
const token = localStorage.getItem('Token') ??
sessionStorage.getItem('Token') ?? undefined
router.beforeEach(async (to, _from, next) => {
if (!token) {
- next()
- return
+ return next()
}
if (to.name === 'Clusters' && token) {
- next({ name: 'Default' })
- } else {
- next()
+ return next({ name: 'Default' })
}
+
+ return next()
})
}
diff --git a/bigtop-manager-ui/src/router/routes/modules/clusters.ts
b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
index 0651679e..beecba9d 100644
--- a/bigtop-manager-ui/src/router/routes/modules/clusters.ts
+++ b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
@@ -178,7 +178,7 @@ const routes: RouteRecordRaw[] = [
{
name: 'HostCreation',
path: 'add',
- component: () => import('@/pages/cluster-manage/hosts/create.vue'),
+ component: () => import('@/components/create-host/create.vue'),
meta: {
hidden: true,
activeMenu: '/cluster-manage/hosts/list'
diff --git a/bigtop-manager-ui/src/store/cluster/index.ts
b/bigtop-manager-ui/src/store/cluster/index.ts
index 6301711e..461047e7 100644
--- a/bigtop-manager-ui/src/store/cluster/index.ts
+++ b/bigtop-manager-ui/src/store/cluster/index.ts
@@ -21,14 +21,12 @@ import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { getCluster, getClusterList } from '@/api/cluster'
import { useServiceStore } from '@/store/service'
-import { useMenuStore } from '@/store/menu'
import type { ClusterVO } from '@/api/cluster/types.ts'
export const useClusterStore = defineStore(
'cluster',
() => {
const serviceStore = useServiceStore()
- const menuStore = useMenuStore()
const loading = ref(false)
const clusters = ref<ClusterVO[]>([])
const currCluster = ref<ClusterVO>({})
@@ -46,9 +44,7 @@ export const useClusterStore = defineStore(
{} as Record<string, ClusterVO>
)
} catch (error) {
- const token = localStorage.getItem('Token') ??
sessionStorage.getItem('Token') ?? undefined
clusterMap.value = {}
- token && menuStore.setupMenu()
console.error('Failed to get clusters:', error)
}
}