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 bab75900 BIGTOP-4452: Fix broken layout on login page (#234)
bab75900 is described below
commit bab759009f183096fbed25fd7dfd96207d01b34a
Author: Fdefined <[email protected]>
AuthorDate: Thu Jun 26 22:22:52 2025 +0800
BIGTOP-4452: Fix broken layout on login page (#234)
---
.../src/assets/images/svg/carbon-language.svg | 23 ++++
.../charts}/category-chart.vue | 99 +++++++-------
.../src/components/charts/gauge-chart.vue | 123 +++++++++++++++++
.../src/components/common/svg-icon/index.vue | 3 +-
.../create-cluster}/components/check-workflow.vue | 0
.../create-cluster}/components/cluster-base.vue | 0
.../create-cluster}/components/component-info.vue | 0
.../create-cluster/components/host-manage.vue} | 0
.../create-cluster}/components/set-source.vue | 0
.../create-cluster}/create.vue | 6 +-
.../src/components/create-service/create.vue | 4 +-
.../src/components/select-lang/login-lang.vue | 70 ++++++++++
.../src/components/service-management/overview.vue | 4 +-
bigtop-manager-ui/src/composables/use-chart.ts | 85 ++++++++++++
bigtop-manager-ui/src/layouts/index.vue | 21 ---
bigtop-manager-ui/src/layouts/sider.vue | 63 ++++++---
.../cluster/components/gauge-chart.vue | 148 ---------------------
.../src/pages/cluster-manage/cluster/host.vue | 31 ++---
.../src/pages/cluster-manage/cluster/index.vue | 91 ++++++-------
.../src/pages/cluster-manage/cluster/overview.vue | 115 +++++++---------
.../src/pages/cluster-manage/cluster/service.vue | 28 ++--
.../src/pages/cluster-manage/cluster/user.vue | 4 +
.../src/pages/cluster-manage/components/index.vue | 2 +-
.../src/pages/cluster-manage/hosts/overview.vue | 4 +-
bigtop-manager-ui/src/pages/login/index.vue | 13 +-
bigtop-manager-ui/src/router/guard.ts | 17 ++-
.../src/router/routes/modules/clusters.ts | 2 +-
bigtop-manager-ui/src/store/cluster/index.ts | 19 +--
bigtop-manager-ui/src/store/menu/index.ts | 89 ++++++++++---
bigtop-manager-ui/src/store/stack/index.ts | 27 +++-
bigtop-manager-ui/src/store/user/index.ts | 5 +-
bigtop-manager-ui/src/styles/index.scss | 29 ++++
32 files changed, 669 insertions(+), 456 deletions(-)
diff --git a/bigtop-manager-ui/src/assets/images/svg/carbon-language.svg
b/bigtop-manager-ui/src/assets/images/svg/carbon-language.svg
new file mode 100644
index 00000000..33988058
--- /dev/null
+++ b/bigtop-manager-ui/src/assets/images/svg/carbon-language.svg
@@ -0,0 +1,23 @@
+<?xml version="1.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
+ ~
+ ~ 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" width="1em" height="1em" viewBox="0 0
32 32">
+ <path fill="currentColor"
+ d="M27.85 29H30l-6-15h-2.35l-6 15h2.15l1.6-4h6.85zm-7.65-6l2.62-6.56L25.45
23zM18 7V5h-7V2H9v3H2v2h10.74a14.71 14.71 0 0 1-3.19 6.18A13.5 13.5 0 0 1 7.26
9h-2.1a16.47 16.47 0 0 0 3 5.58A16.84 16.84 0 0 1 3 18l.75 1.86A18.47 18.47 0 0
0 9.53 16a16.92 16.92 0 0 0 5.76 3.84L16 18a14.48 14.48 0 0 1-5.12-3.37A17.64
17.64 0 0 0 14.8 7z" />
+</svg>
\ No newline at end of file
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/category-chart.vue
b/bigtop-manager-ui/src/components/charts/category-chart.vue
similarity index 54%
rename from
bigtop-manager-ui/src/pages/cluster-manage/cluster/components/category-chart.vue
rename to bigtop-manager-ui/src/components/charts/category-chart.vue
index 6c07487f..c7efe490 100644
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/category-chart.vue
+++ b/bigtop-manager-ui/src/components/charts/category-chart.vue
@@ -18,42 +18,22 @@
-->
<script setup lang="ts">
- import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
- import * as echarts from 'echarts/core'
- import { GridComponent, GridComponentOption, TooltipComponent,
TooltipComponentOption } from 'echarts/components'
- import { LineChart, LineSeriesOption } from 'echarts/charts'
- import { UniversalTransition } from 'echarts/features'
- import { CanvasRenderer } from 'echarts/renderers'
-
- echarts.use([GridComponent, LineChart, TooltipComponent, CanvasRenderer,
UniversalTransition])
-
- type EChartsOption = echarts.ComposeOption<GridComponentOption |
TooltipComponentOption | LineSeriesOption>
+ import dayjs from 'dayjs'
+ import { computed, onMounted, toRefs, watchEffect } from 'vue'
+ import { type EChartsOption, useChart } from '@/composables/use-chart'
const props = defineProps<{
chartId: string
title: string
+ data?: any[]
+ timeDistance?: string
}>()
- const myChart = shallowRef<echarts.ECharts | null>(null)
-
- const generateTimeLabels = () => {
- const times = [] as Array<string>
- for (let h = 0; h < 24; h++) {
- for (let m = 0; m < 60; m += 15) {
- const hour = h.toString().padStart(2, '0')
- const minute = m.toString().padStart(2, '0')
- times.push(`${hour}:${minute}`)
- }
- }
- return times
- }
+ const { data, chartId, title, timeDistance } = toRefs(props)
+ const { initChart, setOptions } = useChart()
- const initCharts = () => {
- const chartDom = document.getElementById(`${props.chartId}`)
- myChart.value = echarts.init(chartDom, null, {
- devicePixelRatio: window.devicePixelRatio
- })
- const option: EChartsOption = {
+ const option = computed(
+ (): EChartsOption => ({
grid: {
top: '20px',
left: '40px',
@@ -64,10 +44,6 @@
trigger: 'axis',
backgroundColor: '#000',
borderColor: 'rgba(236,236,236,0.1)',
- // formatter: (params) => {
- // console.log('params :>> ', params)
- // return ''
- // },
textStyle: {
color: '#fff'
},
@@ -81,13 +57,12 @@
xAxis: [
{
type: 'category',
- data: generateTimeLabels(),
+ data: [],
axisPointer: {
type: 'line'
},
axisLabel: {
- interval: 14,
- fontSize: 8
+ fontSize: 10
}
}
],
@@ -111,37 +86,67 @@
],
series: [
{
- name: props.title,
+ name: title.value,
type: 'line',
- data: Array.from({ length: generateTimeLabels().length }, () =>
Math.floor(Math.random() * 0)),
+ data: [],
lineStyle: {
color: '#49A4FF',
width: 2
}
}
]
+ })
+ )
+
+ const intervalToMs = (interval: string): number => {
+ const unit = interval.replace(/\d+/g, '')
+ const value = parseInt(interval)
+
+ switch (unit) {
+ case 'm':
+ return value * 60 * 1000
+ case 'h':
+ return value * 60 * 60 * 1000
+ default:
+ throw new Error('Unsupported interval: ' + interval)
}
- option && myChart.value.setOption(option)
}
- const resizeChart = () => {
- myChart.value?.resize()
+ const getTimePoints = (interval: string = '15m'): string[] => {
+ const now = dayjs()
+ const gap = intervalToMs(interval)
+ const result: string[] = []
+
+ for (let i = 5; i >= 0; i--) {
+ const time = now.subtract(i * gap, 'millisecond')
+ result.push(time.format('HH:mm'))
+ }
+
+ return result
}
onMounted(() => {
- initCharts()
- window.addEventListener('resize', resizeChart, true)
+ const selector = document.getElementById(`${chartId.value}`)
+ selector && initChart(selector!, option.value)
})
- onBeforeUnmount(() => {
- window.removeEventListener('resize', resizeChart, true)
+ watchEffect(() => {
+ setOptions({
+ xAxis: [{ data: getTimePoints(timeDistance.value) || [] }]
+ })
+ })
+
+ watchEffect(() => {
+ setOptions({
+ series: [{ data: [{ value: data.value ?? [] }] }]
+ })
})
</script>
<template>
<div class="chart">
- <div class="chart-title">{{ $props.title }}</div>
- <div :id="$props.chartId" style="height: 260px; width: 100%"></div>
+ <div class="chart-title">{{ title }}</div>
+ <div :id="chartId" style="height: 260px; width: 100%"></div>
</div>
</template>
diff --git a/bigtop-manager-ui/src/components/charts/gauge-chart.vue
b/bigtop-manager-ui/src/components/charts/gauge-chart.vue
new file mode 100644
index 00000000..946da83c
--- /dev/null
+++ b/bigtop-manager-ui/src/components/charts/gauge-chart.vue
@@ -0,0 +1,123 @@
+<!--
+ ~ 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 { onMounted, shallowRef, toRefs, watchEffect } from 'vue'
+ import { type EChartsOption, useChart } from '@/composables/use-chart'
+
+ interface GaugeChartProps {
+ chartId: string
+ title: string
+ percent?: number
+ }
+
+ const props = withDefaults(defineProps<GaugeChartProps>(), { percent: 0 })
+ const { percent, chartId, title } = toRefs(props)
+ const { initChart, setOptions } = useChart()
+
+ const option = shallowRef<EChartsOption>({
+ series: [
+ {
+ type: 'gauge',
+ radius: '92%',
+ center: ['50%', '50%'],
+ axisLine: {
+ lineStyle: {
+ width: 14,
+ color: [
+ [0.3, '#67e0e3'],
+ [0.7, '#37a2da'],
+ [1, '#fd666d']
+ ]
+ }
+ },
+ pointer: {
+ width: 3,
+ length: '58%',
+ itemStyle: {
+ color: 'auto'
+ }
+ },
+ axisTick: {
+ distance: -50,
+ length: 40,
+ lineStyle: {
+ color: '#fff',
+ width: 1
+ }
+ },
+ splitLine: {
+ distance: -28,
+ length: 28,
+ lineStyle: {
+ color: '#fff',
+ width: 4
+ }
+ },
+ axisLabel: {
+ color: 'inherit',
+ distance: 22,
+ fontSize: 12
+ },
+ detail: {
+ valueAnimation: true,
+ formatter: '{value}%',
+ color: 'inherit',
+ fontSize: 18
+ },
+ data: [
+ {
+ value: 0 * 100
+ }
+ ]
+ }
+ ]
+ })
+
+ onMounted(() => {
+ const selector = document.getElementById(`${chartId.value}`)
+ selector && initChart(document.getElementById(`${chartId.value}`)!,
option.value)
+ })
+
+ watchEffect(() => {
+ setOptions({
+ series: [{ data: [{ value: percent.value.toFixed(2) === 'NaN' ? 0 :
percent.value.toFixed(2) }] }]
+ })
+ })
+</script>
+
+<template>
+ <div class="chart">
+ <div class="chart-title">{{ title }}</div>
+ <div :id="chartId" style="height: 260px; width: 100%"></div>
+ </div>
+</template>
+
+<style lang="scss" scoped>
+ .chart {
+ height: 300px;
+ box-sizing: border-box;
+ padding: 10px;
+ &-title {
+ text-align: start;
+ font-size: 12px;
+ font-weight: 600;
+ }
+ }
+</style>
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 42d2d0b2..0d3fc9d6 100644
--- a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
+++ b/bigtop-manager-ui/src/components/common/svg-icon/index.vue
@@ -69,7 +69,8 @@
start: IconSvgStart,
stop: IconSvgStop,
success: IconSvgSuccess,
- unknown: IconSvgUnknown
+ unknown: IconSvgUnknown,
+ 'carbon-language': IconSvgCarbonLanguage
}
const props = defineProps<{ name: string | undefined }>()
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/check-workflow.vue
b/bigtop-manager-ui/src/components/create-cluster/components/check-workflow.vue
similarity index 100%
rename from
bigtop-manager-ui/src/pages/cluster-manage/cluster/components/check-workflow.vue
rename to
bigtop-manager-ui/src/components/create-cluster/components/check-workflow.vue
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/cluster-base.vue
b/bigtop-manager-ui/src/components/create-cluster/components/cluster-base.vue
similarity index 100%
rename from
bigtop-manager-ui/src/pages/cluster-manage/cluster/components/cluster-base.vue
rename to
bigtop-manager-ui/src/components/create-cluster/components/cluster-base.vue
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/component-info.vue
b/bigtop-manager-ui/src/components/create-cluster/components/component-info.vue
similarity index 100%
rename from
bigtop-manager-ui/src/pages/cluster-manage/cluster/components/component-info.vue
rename to
bigtop-manager-ui/src/components/create-cluster/components/component-info.vue
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/host-config.vue
b/bigtop-manager-ui/src/components/create-cluster/components/host-manage.vue
similarity index 100%
rename from
bigtop-manager-ui/src/pages/cluster-manage/cluster/components/host-config.vue
rename to
bigtop-manager-ui/src/components/create-cluster/components/host-manage.vue
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/set-source.vue
b/bigtop-manager-ui/src/components/create-cluster/components/set-source.vue
similarity index 100%
rename from
bigtop-manager-ui/src/pages/cluster-manage/cluster/components/set-source.vue
rename to
bigtop-manager-ui/src/components/create-cluster/components/set-source.vue
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
b/bigtop-manager-ui/src/components/create-cluster/create.vue
similarity index 99%
rename from bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
rename to bigtop-manager-ui/src/components/create-cluster/create.vue
index 416323af..b1863257 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
+++ b/bigtop-manager-ui/src/components/create-cluster/create.vue
@@ -25,12 +25,11 @@
import { getInstalledStatus, installDependencies } from '@/api/hosts'
import { execCommand } from '@/api/command'
import { onBeforeRouteLeave } from 'vue-router'
-
import SvgIcon from '@/components/common/svg-icon/index.vue'
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 HostManage from './components/host-manage.vue'
import CheckWorkflow from './components/check-workflow.vue'
import { type InstalledStatusVO, Status } from '@/api/hosts/types'
@@ -47,8 +46,7 @@
})
const installStatus = shallowRef<InstalledStatusVO[]>([])
- const components = shallowRef<any[]>([ClusterBase, ComponentInfo,
HostConfig, CheckWorkflow])
-
+ 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)
diff --git a/bigtop-manager-ui/src/components/create-service/create.vue
b/bigtop-manager-ui/src/components/create-service/create.vue
index fe188ef6..596327b0 100644
--- a/bigtop-manager-ui/src/components/create-service/create.vue
+++ b/bigtop-manager-ui/src/components/create-service/create.vue
@@ -23,15 +23,13 @@
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
import { onBeforeRouteLeave, 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'
import SvgIcon from '@/components/common/svg-icon/index.vue'
- import { type StepContext, useCreateServiceStore } from
'@/store/create-service'
-
const { t } = useI18n()
const route = useRoute()
const createStore = useCreateServiceStore()
diff --git a/bigtop-manager-ui/src/components/select-lang/login-lang.vue
b/bigtop-manager-ui/src/components/select-lang/login-lang.vue
new file mode 100644
index 00000000..9f29b768
--- /dev/null
+++ b/bigtop-manager-ui/src/components/select-lang/login-lang.vue
@@ -0,0 +1,70 @@
+<!--
+ ~ 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 { useLocaleStore } from '@/store/locale'
+ import { Locale } from '@/store/locale/types.ts'
+ import { storeToRefs } from 'pinia'
+
+ const localeStore = useLocaleStore()
+ const { locale } = storeToRefs(localeStore)
+
+ const handleClick = ({ key }: { key: Locale }) => {
+ localeStore.setLocale(key)
+ }
+</script>
+
+<template>
+ <a-dropdown placement="bottom">
+ <div class="icon">
+ <svg-icon name="carbon-language" />
+ </div>
+ <template #overlay>
+ <a-menu :selected-keys="[locale]" @click="handleClick">
+ <a-menu-item key="en_US">
+ <template #icon>
+ <span>🇺🇸</span>
+ </template>
+ English
+ </a-menu-item>
+ <a-menu-item key="zh_CN">
+ <template #icon>
+ <span>🇨🇳</span>
+ </template>
+ ç®€ä½“ä¸æ–‡
+ </a-menu-item>
+ </a-menu>
+ </template>
+ </a-dropdown>
+</template>
+
+<style lang="scss" scoped>
+ .icon {
+ @include flexbox($justify: center, $align: center);
+ font-size: 16px;
+ cursor: pointer;
+ border-radius: 50%;
+ height: 36px;
+ width: 36px;
+
+ &:hover {
+ background-color: var(--hover-color);
+ }
+ }
+</style>
diff --git a/bigtop-manager-ui/src/components/service-management/overview.vue
b/bigtop-manager-ui/src/components/service-management/overview.vue
index 6fe460d5..3cabfb30 100644
--- a/bigtop-manager-ui/src/components/service-management/overview.vue
+++ b/bigtop-manager-ui/src/components/service-management/overview.vue
@@ -21,8 +21,8 @@
import { computed, ref, shallowRef, useAttrs } from 'vue'
import { useI18n } from 'vue-i18n'
import { CommonStatus, CommonStatusTexts } from '@/enums/state'
- import GaugeChart from
'@/pages/cluster-manage/cluster/components/gauge-chart.vue'
- import CategoryChart from
'@/pages/cluster-manage/cluster/components/category-chart.vue'
+ import GaugeChart from '@/components/charts/gauge-chart.vue'
+ import CategoryChart from '@/components/charts/category-chart.vue'
import type { ServiceVO, ServiceStatusType } from '@/api/service/types'
type TimeRangeText = '1m' | '15m' | '30m' | '1h' | '6h' | '30h'
diff --git a/bigtop-manager-ui/src/composables/use-chart.ts
b/bigtop-manager-ui/src/composables/use-chart.ts
new file mode 100644
index 00000000..c0c61aad
--- /dev/null
+++ b/bigtop-manager-ui/src/composables/use-chart.ts
@@ -0,0 +1,85 @@
+/*
+ * 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 * as echarts from 'echarts/core'
+import { GaugeChart, GaugeSeriesOption, LineChart, LineSeriesOption } from
'echarts/charts'
+import { GridComponent, GridComponentOption, TooltipComponent,
TooltipComponentOption } from 'echarts/components'
+import { UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
+
+export type EChartsOption = echarts.ComposeOption<
+ GaugeSeriesOption | GridComponentOption | TooltipComponentOption |
LineSeriesOption
+>
+
+echarts.use([
+ GaugeChart,
+ CanvasRenderer,
+ GridComponent,
+ LineChart,
+ TooltipComponent,
+ CanvasRenderer,
+ UniversalTransition
+])
+
+export const useChart = () => {
+ let resizeTimer: number | null = null
+ const chartsRef = shallowRef<echarts.ECharts | null>(null)
+ const opts = shallowRef({ devicePixelRatio: window.devicePixelRatio })
+
+ const initChart = (selector: HTMLElement, option: EChartsOption) => {
+ if (!selector) {
+ new Error('Selector is required to initialize the chart.')
+ }
+ chartsRef.value && chartsRef.value.dispose()
+ chartsRef.value = echarts.init(selector, null, opts.value)
+ chartsRef.value?.setOption(option)
+ }
+
+ const setOptions = (options: EChartsOption) => {
+ chartsRef.value?.setOption(options)
+ }
+
+ const resizeChart = () => {
+ if (resizeTimer) clearTimeout(resizeTimer)
+ resizeTimer = window.setTimeout(() => {
+ chartsRef.value?.resize()
+ }, 300)
+ }
+
+ const disposeChart = () => {
+ chartsRef.value?.dispose()
+ chartsRef.value = null
+ }
+
+ onMounted(() => {
+ window.addEventListener('resize', resizeChart, true)
+ })
+
+ onBeforeUnmount(() => {
+ window.removeEventListener('resize', resizeChart, true)
+ disposeChart()
+ })
+
+ return {
+ chartsRef,
+ initChart,
+ setOptions
+ }
+}
diff --git a/bigtop-manager-ui/src/layouts/index.vue
b/bigtop-manager-ui/src/layouts/index.vue
index 1b1d26ff..d97dee2c 100644
--- a/bigtop-manager-ui/src/layouts/index.vue
+++ b/bigtop-manager-ui/src/layouts/index.vue
@@ -21,27 +21,6 @@
import LayoutFooter from '@/layouts/footer.vue'
import LayoutHeader from '@/layouts/header.vue'
import LayoutSider from '@/layouts/sider.vue'
-
- import { useUserStore } from '@/store/user'
- import { useMenuStore } from '@/store/menu'
- 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 serviceStore = useServiceStore()
- const stackStore = useStackStore()
- const clusterStore = useClusterStore()
-
- onMounted(async () => {
- await stackStore.loadStacks()
- await clusterStore.loadClusters()
- await serviceStore.getServicesOfInfra()
- userStore.getUserInfo()
- menuStore.setupMenu()
- })
</script>
<template>
diff --git a/bigtop-manager-ui/src/layouts/sider.vue
b/bigtop-manager-ui/src/layouts/sider.vue
index 3e1991c6..1a70130d 100644
--- a/bigtop-manager-ui/src/layouts/sider.vue
+++ b/bigtop-manager-ui/src/layouts/sider.vue
@@ -18,10 +18,10 @@
-->
<script setup lang="ts">
+ import { computed, onMounted, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useMenuStore } from '@/store/menu/index'
import { storeToRefs } from 'pinia'
- import { computed, ref, watch } from 'vue'
import { useClusterStore } from '@/store/cluster'
import type { ClusterStatusType } from '@/api/cluster/types'
@@ -44,27 +44,39 @@
})
)
- watch(
- () => 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) {
- if (targetCluster) {
- // siderMenuSelectedKey.value =
`${routePathFromClusters.value}/${targetCluster.name}/${targetCluster.id}`
- siderMenuSelectedKey.value =
`${routePathFromClusters.value}/${targetCluster.id}`
- }
+ /**
+ * Handles route changes and updates the selected menu key.
+ */
+ const handleRouteChange = async (newRoute: typeof route) => {
+ const token = localStorage.getItem('Token') ??
sessionStorage.getItem('Token') ?? undefined
+
+ 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}`
+ openKeys.value.push(siderMenuSelectedKey.value)
} else {
- siderMenuSelectedKey.value = meta.activeMenu ?? path
+ siderMenuSelectedKey.value = ''
+ menuStore.setupSider()
}
- },
- {
- deep: true,
- immediate: true
+ } else {
+ siderMenuSelectedKey.value = meta.activeMenu ?? path
}
- )
+ }
+ /**
+ * Toggles the activated icon for menu items.
+ */
const toggleActivatedIcon = (menuItem: { key: string; icon: string }) => {
const { key, icon } = menuItem
const matchedRouteFromClusters = selectMenuKeyFromClusters.value &&
routeParamsLen.value > 0
@@ -76,12 +88,25 @@
const addCluster = () => {
router.push({ name: 'CreateCluster' })
}
+
+ onMounted(async () => {
+ await clusterStore.loadClusters()
+ })
+
+ watch(
+ () => [route, clusterCount],
+ (val) => {
+ const [newRoute] = val
+ handleRouteChange(newRoute as typeof route)
+ },
+ { deep: true, immediate: true }
+ )
</script>
<template>
<a-layout-sider class="sider">
<a-menu
- v-model="openKeys"
+ :open-keys="openKeys"
:selected-keys="[siderMenuSelectedKey]"
mode="inline"
@click="({ key }) => menuStore.onSiderClick(key)"
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/gauge-chart.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/gauge-chart.vue
deleted file mode 100644
index d2d2dcaa..00000000
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/gauge-chart.vue
+++ /dev/null
@@ -1,148 +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
- ~
- ~ 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 { onBeforeUnmount, onMounted, shallowRef } from 'vue'
- import * as echarts from 'echarts/core'
- import { GaugeChart, GaugeSeriesOption } from 'echarts/charts'
- import { CanvasRenderer } from 'echarts/renderers'
-
- echarts.use([GaugeChart, CanvasRenderer])
-
- type EChartsOption = echarts.ComposeOption<GaugeSeriesOption>
-
- const props = defineProps<{
- chartId: string
- title: string
- }>()
-
- const myChart = shallowRef<echarts.ECharts | null>(null)
-
- const initCharts = () => {
- const chartDom = document.getElementById(`${props.chartId}`)
- myChart.value = echarts.init(chartDom, null, {
- devicePixelRatio: window.devicePixelRatio
- })
- const option: EChartsOption = {
- series: [
- {
- type: 'gauge',
- radius: '90%',
- center: ['50%', '50%'],
- axisLine: {
- lineStyle: {
- width: 14,
- color: [
- [0.3, '#67e0e3'],
- [0.7, '#37a2da'],
- [1, '#fd666d']
- ]
- }
- },
- pointer: {
- itemStyle: {
- color: 'auto'
- }
- },
- axisTick: {
- distance: -50,
- length: 40,
- lineStyle: {
- color: '#fff',
- width: 1
- }
- },
- splitLine: {
- distance: -28,
- length: 28,
- lineStyle: {
- color: '#fff',
- width: 4
- }
- },
- axisLabel: {
- color: 'inherit',
- distance: 28,
- fontSize: 14
- },
- detail: {
- valueAnimation: true,
- formatter: '{value} %',
- color: 'inherit',
- fontSize: 18
- },
- data: [
- {
- value: 80
- }
- ]
- }
- ]
- }
-
- myChart.value?.setOption<echarts.EChartsCoreOption>({
- series: [
- {
- data: [
- {
- value: +(Math.random() * 100).toFixed(2)
- }
- ]
- }
- ]
- })
-
- option && myChart.value.setOption(option)
- }
-
- const resizeChart = async () => {
- setTimeout(() => {
- myChart.value?.resize()
- })
- }
-
- onMounted(() => {
- initCharts()
- window.addEventListener('resize', resizeChart, true)
- })
-
- onBeforeUnmount(() => {
- window.removeEventListener('resize', resizeChart, true)
- })
-</script>
-
-<template>
- <div class="chart">
- <div class="chart-title">{{ $props.title }}</div>
- <div :id="$props.chartId" style="height: 260px; width: 100%"></div>
- </div>
-</template>
-
-<style lang="scss" scoped>
- .chart {
- height: 300px;
- box-sizing: border-box;
- padding: 10px;
- &-title {
- text-align: start;
- font-size: 12px;
- font-weight: 600;
- }
- }
-</style>
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 a2b212d4..1a2253af 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
@@ -24,9 +24,11 @@
import { getHosts } from '@/api/hosts'
import * as hostApi from '@/api/hosts'
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 type { FilterConfirmProps, FilterResetProps } from
'ant-design-vue/es/table/interface'
import type { GroupItem } from '@/components/common/button-group/types'
import type { HostVO } from '@/api/hosts/types'
@@ -34,6 +36,7 @@
import type { HostReq } from '@/api/command/types'
type Key = string | number
+
interface TableState {
selectedRowKeys: Key[]
searchText: string
@@ -43,10 +46,12 @@
const { t } = useI18n()
const router = useRouter()
const attrs = useAttrs() as ClusterVO
+
const searchInputRef = ref()
const hostCreateRef = ref<InstanceType<typeof HostCreate> | null>(null)
const installRef = ref<InstanceType<typeof InstallDependencies> | null>(null)
const hostStatus = ref(['INSTALLING', 'SUCCESS', 'FAILED', 'UNKNOWN'])
+
const state = reactive<TableState>({
searchText: '',
searchedColumn: '',
@@ -93,18 +98,9 @@
ellipsis: true,
filterMultiple: false,
filters: [
- {
- text: t('common.success'),
- value: 1
- },
- {
- text: t('common.failed'),
- value: 2
- },
- {
- text: t('common.unknown'),
- value: 3
- }
+ { text: t('common.success'), value: 1 },
+ { text: t('common.failed'), value: 2 },
+ { text: t('common.unknown'), value: 3 }
]
},
{
@@ -121,15 +117,8 @@
})
const operations = computed((): GroupItem[] => [
- {
- text: 'edit',
- clickEvent: (_item, args) => handleEdit(args)
- },
- {
- text: 'remove',
- danger: true,
- clickEvent: (_item, args) => deleteHost([args.id])
- }
+ { text: 'edit', clickEvent: (_item, args) => handleEdit(args) },
+ { text: 'remove', danger: true, clickEvent: (_item, args) =>
deleteHost([args.id]) }
])
const onFilterDropdownOpenChange = (visible: boolean) => {
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 5f056391..fd056ec5 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
@@ -18,130 +18,113 @@
-->
<script setup lang="ts">
- import { computed, onMounted, ref, shallowRef } from 'vue'
+ import { computed, ref, shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { useClusterStore } from '@/store/cluster'
import { storeToRefs } from 'pinia'
import { CommonStatus, CommonStatusTexts } from '@/enums/state'
import { useRouter } from 'vue-router'
import { useJobProgress } from '@/store/job-progress'
+
import Overview from './overview.vue'
import Service from './service.vue'
import Host from './host.vue'
import User from './user.vue'
import Job from '@/components/job/index.vue'
+
import type { Command } from '@/api/command/types'
import type { TabItem } from '@/components/common/main-card/types'
import type { GroupItem } from '@/components/common/button-group/types'
- import type { ClusterStatusType } from '@/api/cluster/types'
+ import type { ClusterStatusType, ClusterVO } from '@/api/cluster/types'
const { t } = useI18n()
const router = useRouter()
const jobProgressStore = useJobProgress()
const clusterStore = useClusterStore()
- const { currCluster, loading } = storeToRefs(clusterStore)
+ const { loading } = storeToRefs(clusterStore)
+
const activeKey = ref('1')
+ const clusterInfo = ref<ClusterVO>({})
+
const statusColors = shallowRef<Record<ClusterStatusType, keyof typeof
CommonStatusTexts>>({
1: 'healthy',
2: 'unhealthy',
3: 'unknown'
})
+
+ /**
+ * Determines the component to render based on the active tab.
+ */
+ const getCompName = computed(() => [Overview, Service, Host, User,
Job][parseInt(activeKey.value) - 1])
+
const tabs = computed((): TabItem[] => [
- {
- key: '1',
- title: t('common.overview')
- },
- {
- key: '2',
- title: t('common.service')
- },
- {
- key: '3',
- title: t('common.host')
- },
- {
- key: '4',
- title: t('common.user')
- },
- {
- key: '5',
- title: t('common.job')
- }
+ { key: '1', title: t('common.overview') },
+ { key: '2', title: t('common.service') },
+ { key: '3', title: t('common.host') },
+ { key: '4', title: t('common.user') },
+ { key: '5', title: t('common.job') }
])
+
const actionGroup = computed<GroupItem[]>(() => [
{
shape: 'default',
type: 'primary',
text: t('common.add', [t('common.service')]),
- clickEvent: () => addService && addService()
+ clickEvent: () => addService!()
},
{
shape: 'default',
type: 'default',
text: t('common.more_operations'),
dropdownMenu: [
- {
- action: 'Start',
- text: t('common.start', [t('common.cluster')])
- },
- {
- action: 'Restart',
- text: t('common.restart', [t('common.cluster')])
- },
- {
- action: 'Stop',
- text: t('common.stop', [t('common.cluster')])
- }
+ { action: 'Start', text: t('common.start', [t('common.cluster')]) },
+ { action: 'Restart', text: t('common.restart', [t('common.cluster')])
},
+ { action: 'Stop', text: t('common.stop', [t('common.cluster')]) }
],
- dropdownMenuClickEvent: (info) => dropdownMenuClick &&
dropdownMenuClick(info)
+ dropdownMenuClickEvent: (info) => dropdownMenuClick!(info)
}
])
- const getCompName = computed(() => {
- const components = [Overview, Service, Host, User, Job]
- return components[parseInt(activeKey.value) - 1]
- })
-
+ /**
+ * Handles dropdown menu click events for cluster operations.
+ */
const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async ({ key
}) => {
try {
await jobProgressStore.processCommand(
{
command: key as keyof typeof Command,
- clusterId: currCluster.value.id,
+ clusterId: clusterInfo.value.id,
commandLevel: 'cluster'
},
async () => {
await clusterStore.loadClusters()
- await clusterStore.getClusterDetail()
}
)
} catch (error) {
- console.log('error :>> ', error)
+ console.error('Error processing command:', error)
+ } finally {
+ await clusterStore.getClusterDetail(clusterInfo.value.id!)
}
}
const addService: GroupItem['clickEvent'] = () => {
- router.push({ name: 'CreateService', params: { id: currCluster.value.id,
creationMode: 'internal' } })
+ router.push({ name: 'CreateService', params: { id: clusterInfo.value.id,
creationMode: 'internal' } })
}
-
- onMounted(() => {
- clusterStore.getClusterDetail()
- })
</script>
<template>
<a-spin :spinning="loading">
<header-card
- :title="currCluster.displayName"
+ :title="clusterInfo.displayName"
avatar="cluster"
- :status="CommonStatus[statusColors[currCluster.status as
ClusterStatusType]]"
- :desc="currCluster.desc"
+ :status="CommonStatus[statusColors[clusterInfo.status as
ClusterStatusType]]"
+ :desc="clusterInfo.desc"
:action-groups="actionGroup"
/>
<main-card v-model:active-key="activeKey" :tabs="tabs">
<template #tab-item>
<keep-alive>
- <component :is="getCompName" v-bind="currCluster"></component>
+ <component :is="getCompName" v-bind="clusterInfo"
v-model:payload="clusterInfo"></component>
</keep-alive>
</template>
</main-card>
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
index 05b7df00..3a20ab30 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
@@ -18,7 +18,7 @@
-->
<script setup lang="ts">
- import { computed, onActivated, ref, shallowRef, useAttrs, watch,
watchEffect } from 'vue'
+ import { computed, onActivated, ref, shallowRef, toRefs, watchEffect } from
'vue'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
import { formatFromByte } from '@/utils/storage'
@@ -27,13 +27,15 @@
import { useServiceStore } from '@/store/service'
import { useJobProgress } from '@/store/job-progress'
import { useStackStore } from '@/store/stack'
+ import { useClusterStore } from '@/store/cluster'
import { Empty } from 'ant-design-vue'
+ import { useRoute } from 'vue-router'
- import GaugeChart from './components/gauge-chart.vue'
- import CategoryChart from './components/category-chart.vue'
+ import GaugeChart from '@/components/charts/gauge-chart.vue'
+ import CategoryChart from '@/components/charts/category-chart.vue'
import type { ClusterStatusType, ClusterVO } from '@/api/cluster/types'
- import type { ServiceListParams, ServiceVO } from '@/api/service/types'
+ import type { ServiceVO } from '@/api/service/types'
import type { MenuItem } from '@/store/menu/types'
import type { StackVO } from '@/api/stack/types'
import type { Command } from '@/api/command/types'
@@ -44,11 +46,15 @@
time: string
}
+ const props = defineProps<{ payload: ClusterVO }>()
+ const emits = defineEmits<{ (event: 'update:payload', value: ClusterVO):
void }>()
+
const { t } = useI18n()
- const attrs = useAttrs() as ClusterVO
+ const route = useRoute()
const jobProgressStore = useJobProgress()
const stackStore = useStackStore()
const serviceStore = useServiceStore()
+ const clusterStore = useClusterStore()
const currTimeRange = ref<TimeRangeText>('15m')
const chartData = ref({
chart1: [],
@@ -64,40 +70,27 @@
const { serviceNames } = storeToRefs(serviceStore)
const locateStackWithService = shallowRef<StackVO[]>([])
+ const { payload } = toRefs(props)
+
const clusterDetail = computed(() => ({
- ...attrs,
- totalMemory: formatFromByte(attrs.totalMemory as number),
- totalDisk: formatFromByte(attrs.totalDisk as number)
+ ...payload.value,
+ totalMemory: formatFromByte(payload.value.totalMemory as number),
+ totalDisk: formatFromByte(payload.value.totalDisk as number)
}))
+
+ const clusterId = computed(() => route.params.id as unknown as number)
const noChartData = computed(() => Object.values(chartData.value).every((v)
=> v.length === 0))
const timeRanges = computed((): TimeRangeItem[] => [
- {
- text: '1m',
- time: ''
- },
- {
- text: '15m',
- time: ''
- },
- {
- text: '30m',
- time: ''
- },
- {
- text: '1h',
- time: ''
- },
- {
- text: '6h',
- time: ''
- },
- {
- text: '30h',
- time: ''
- }
+ { text: '1m', time: '' },
+ { text: '15m', time: '' },
+ { text: '30m', time: '' },
+ { text: '1h', time: '' },
+ { text: '6h', time: '' },
+ { text: '30h', time: '' }
])
- const baseConfig = computed((): Partial<Record<keyof ClusterVO, string>> => {
- return {
+
+ const baseConfig = computed(
+ (): Partial<Record<keyof ClusterVO, string>> => ({
status: t('overview.cluster_status'),
displayName: t('overview.cluster_name'),
desc: t('overview.cluster_desc'),
@@ -107,36 +100,29 @@
totalProcessor: t('overview.core_count'),
totalDisk: t('overview.disk_size'),
createUser: t('overview.creator')
- }
- })
- const unitOfBaseConfig = computed((): Partial<Record<keyof ClusterVO,
string>> => {
- return {
+ })
+ )
+
+ const unitOfBaseConfig = computed(
+ (): Partial<Record<keyof ClusterVO, string>> => ({
totalHost: t('overview.unit_host'),
totalService: t('overview.unit_service'),
totalProcessor: t('overview.unit_core')
- }
- })
+ })
+ )
+
const detailKeys = computed(() => Object.keys(baseConfig.value) as (keyof
ClusterVO)[])
const serviceOperates = computed(() => [
- {
- action: 'Start',
- text: t('common.start', [t('common.service')])
- },
- {
- action: 'Restart',
- text: t('common.restart', [t('common.service')])
- },
- {
- action: 'Stop',
- text: t('common.stop', [t('common.service')])
- }
+ { action: 'Start', text: t('common.start', [t('common.service')]) },
+ { action: 'Restart', text: t('common.restart', [t('common.service')]) },
+ { action: 'Stop', text: t('common.stop', [t('common.service')]) }
])
const handleServiceOperate = async (item: MenuItem, service: ServiceVO) => {
try {
await jobProgressStore.processCommand({
command: item.key as keyof typeof Command,
- clusterId: attrs.id,
+ clusterId: clusterId.value,
commandLevel: 'service',
serviceCommands: [{ serviceName: service.name!, installed: true }]
})
@@ -149,16 +135,13 @@
currTimeRange.value = time.text
}
- const getServices = (filters?: ServiceListParams) => {
- attrs.id != undefined && serviceStore.getServices(attrs.id, filters)
- }
-
const servicesFromCurrentCluster = (stack: StackVO) => {
return stack.services.filter((v) => serviceNames.value.includes(v.name))
}
- onActivated(() => {
- getServices()
+ onActivated(async () => {
+ await clusterStore.getClusterDetail(clusterId.value)
+ emits('update:payload', clusterStore.currCluster)
})
watchEffect(() => {
@@ -166,14 +149,6 @@
item.services.some((service) => service.name &&
serviceNames.value.includes(service.name))
)
})
-
- watch(
- () => attrs,
- () => {
- getServices()
- },
- { immediate: true, deep: true }
- )
</script>
<template>
@@ -199,7 +174,7 @@
<a-typography-text
class="desc-sub-item-desc-column"
type="secondary"
- :content="baseConfig[base]"
+ :content="baseConfig[base] ?? '--'"
/>
<a-tag
v-if="base === 'status'"
@@ -217,8 +192,8 @@
class="desc-sub-item-desc-column"
:content="
Object.keys(unitOfBaseConfig).includes(base)
- ? `${clusterDetail[base]}
${unitOfBaseConfig[base]}`
- : `${clusterDetail[base]}`
+ ? `${clusterDetail[base] ?? '--'}
${unitOfBaseConfig[base]}`
+ : `${clusterDetail[base] ?? '--'}`
"
/>
</div>
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 c45178e6..b9cb7922 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
@@ -26,7 +26,9 @@
import { CommonStatus, CommonStatusTexts } from '@/enums/state'
import { useRouter } from 'vue-router'
import { useJobProgress } from '@/store/job-progress'
+
import FilterForm from '@/components/common/filter-form/index.vue'
+
import type { GroupItem } from '@/components/common/button-group/types'
import type { FilterFormItem } from '@/components/common/filter-form/types'
import type { ServiceListParams, ServiceStatusType, ServiceVO } from
'@/api/service/types'
@@ -41,6 +43,7 @@
const jobProgressStore = useJobProgress()
const serviceStore = useServiceStore()
const { services, loading } = toRefs(serviceStore)
+
const statusColors = shallowRef<Record<ServiceStatusType, keyof typeof
CommonStatusTexts>>({
1: 'healthy',
2: 'unhealthy',
@@ -89,14 +92,8 @@
key: 'restartFlag',
label: t('service.required_restart'),
options: [
- {
- label: t('common.required'),
- value: true
- },
- {
- label: t('common.not_required'),
- value: false
- }
+ { label: t('common.required'), value: true },
+ { label: t('common.not_required'), value: false }
]
},
{
@@ -104,18 +101,9 @@
key: 'status',
label: t('common.status'),
options: [
- {
- label: t(`common.${statusColors.value[1]}`),
- value: 1
- },
- {
- label: t(`common.${statusColors.value[2]}`),
- value: 2
- },
- {
- label: t(`common.${statusColors.value[3]}`),
- value: 3
- }
+ { label: t(`common.${statusColors.value[1]}`), value: 1 },
+ { label: t(`common.${statusColors.value[2]}`), value: 2 },
+ { label: t(`common.${statusColors.value[3]}`), value: 3 }
]
}
])
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/user.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/user.vue
index fc967256..62d73e6f 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/user.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/user.vue
@@ -21,12 +21,15 @@
import { computed, onActivated, useAttrs } from 'vue'
import { useI18n } from 'vue-i18n'
import { getUserListOfService } from '@/api/cluster'
+
import useBaseTable from '@/composables/use-base-table'
+
import type { TableColumnType } from 'ant-design-vue'
import type { ClusterVO, ServiceUserVO } from '@/api/cluster/types'
const { t } = useI18n()
const attrs = useAttrs() as ClusterVO
+
const columns = computed((): TableColumnType[] => [
{
title: '#',
@@ -60,6 +63,7 @@
ellipsis: true
}
])
+
const { dataSource, loading, filtersParams, paginationProps, onChange } =
useBaseTable<ServiceUserVO>({
columns: columns.value,
rows: []
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/components/index.vue
b/bigtop-manager-ui/src/pages/cluster-manage/components/index.vue
index e020f244..e7b40c22 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/components/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/components/index.vue
@@ -19,7 +19,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted, watchEffect } from 'vue'
import useBaseTable from '@/composables/use-base-table'
- import SetSource from
'@/pages/cluster-manage/cluster/components/set-source.vue'
+ import SetSource from '@/components/create-cluster/components/set-source.vue'
import { useI18n } from 'vue-i18n'
import { useStackStore } from '@/store/stack'
import { storeToRefs } from 'pinia'
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 54fece85..ce1b8059 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
@@ -29,8 +29,8 @@
import { useStackStore } from '@/store/stack'
import { getComponentsByHost } from '@/api/hosts'
import { Command } from '@/api/command/types'
- import CategoryChart from
'@/pages/cluster-manage/cluster/components/category-chart.vue'
- import GaugeChart from
'@/pages/cluster-manage/cluster/components/gauge-chart.vue'
+ import GaugeChart from '@/components/charts/gauge-chart.vue'
+ import CategoryChart from '@/components/charts/category-chart.vue'
import type { HostStatusType, HostVO } from '@/api/hosts/types.ts'
import type { ClusterStatusType } from '@/api/cluster/types.ts'
import type { ComponentVO } from '@/api/component/types.ts'
diff --git a/bigtop-manager-ui/src/pages/login/index.vue
b/bigtop-manager-ui/src/pages/login/index.vue
index 907857e2..5719b2c7 100644
--- a/bigtop-manager-ui/src/pages/login/index.vue
+++ b/bigtop-manager-ui/src/pages/login/index.vue
@@ -24,10 +24,17 @@
import { getSalt, getNonce, login } from '@/api/login'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
- import SelectLang from '@/components/select-lang/index.vue'
+ import SelectLang from '@/components/select-lang/login-lang.vue'
import { deriveKey } from '@/utils/pbkdf2.ts'
+ import { useStackStore } from '@/store/stack'
+ import { useUserStore } from '@/store/user'
+ import { useMenuStore } from '@/store/menu'
const i18n = useI18n()
+ const stackStore = useStackStore()
+ const userStore = useUserStore()
+ const menuStore = useMenuStore()
+
const router = useRouter()
const formRef = shallowRef()
@@ -67,6 +74,10 @@
} else {
sessionStorage.setItem('Token', res.token)
}
+ userStore.getUserInfo()
+ stackStore.loadStacks()
+ menuStore.setupMenu()
+
message.success(i18n.t('login.login_success'))
router.push('/')
} catch (e) {
diff --git a/bigtop-manager-ui/src/router/guard.ts
b/bigtop-manager-ui/src/router/guard.ts
index e907185e..61824c91 100644
--- a/bigtop-manager-ui/src/router/guard.ts
+++ b/bigtop-manager-ui/src/router/guard.ts
@@ -17,25 +17,24 @@
* under the License.
*/
-import type { NavigationGuardNext, Router } from 'vue-router'
-import { useClusterStore } from '@/store/cluster'
+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
+ }
+
if (to.name === 'Clusters' && token) {
- checkClusterSelect(next)
+ next({ name: 'Default' })
} else {
next()
}
})
}
-function checkClusterSelect(next: NavigationGuardNext) {
- const clusterStore = useClusterStore()
- clusterStore.clusterCount === 0 && clusterStore.loadClusters()
- clusterStore.clusterCount === 0 && next({ name: 'Default' })
-}
-
function createRouterGuard(router: Router) {
/** common guard */
setCommonGuard(router)
diff --git a/bigtop-manager-ui/src/router/routes/modules/clusters.ts
b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
index be915a89..0651679e 100644
--- a/bigtop-manager-ui/src/router/routes/modules/clusters.ts
+++ b/bigtop-manager-ui/src/router/routes/modules/clusters.ts
@@ -59,7 +59,7 @@ const routes: RouteRecordRaw[] = [
{
name: 'CreateCluster',
path: 'create-cluster',
- component: () =>
import('@/pages/cluster-manage/cluster/create.vue'),
+ component: () => import('@/components/create-cluster/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 c42048dd..6301711e 100644
--- a/bigtop-manager-ui/src/store/cluster/index.ts
+++ b/bigtop-manager-ui/src/store/cluster/index.ts
@@ -19,21 +19,20 @@
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 { useMenuStore } from '@/store/menu'
import type { ClusterVO } from '@/api/cluster/types.ts'
export const useClusterStore = defineStore(
'cluster',
() => {
- const route = useRoute()
const serviceStore = useServiceStore()
+ const menuStore = useMenuStore()
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(() => Object.values(clusterMap.value).length)
const loadClusters = async () => {
@@ -47,23 +46,25 @@ export const useClusterStore = defineStore(
{} as Record<string, ClusterVO>
)
} catch (error) {
+ const token = localStorage.getItem('Token') ??
sessionStorage.getItem('Token') ?? undefined
clusterMap.value = {}
- console.log('error :>> ', error)
+ token && menuStore.setupMenu()
+ console.error('Failed to get clusters:', error)
}
}
- const getClusterDetail = async () => {
- if (clusterId.value == undefined) {
+ const getClusterDetail = async (clusterId: number) => {
+ if (clusterId == undefined) {
currCluster.value = {}
return
}
try {
loading.value = true
- currCluster.value = await getCluster(Number(clusterId.value))
- await serviceStore.getServices(Number(clusterId.value))
+ currCluster.value = await getCluster(clusterId)
+ await serviceStore.getServices(clusterId)
} catch (error) {
currCluster.value = {}
- console.log('error :>> ', error)
+ console.error('Failed to get cluster detail:', error)
} finally {
loading.value = false
}
diff --git a/bigtop-manager-ui/src/store/menu/index.ts
b/bigtop-manager-ui/src/store/menu/index.ts
index 1f0ed985..b25dd069 100644
--- a/bigtop-manager-ui/src/store/menu/index.ts
+++ b/bigtop-manager-ui/src/store/menu/index.ts
@@ -29,14 +29,19 @@ export const useMenuStore = defineStore(
const router = useRouter()
const route = useRoute()
const clusterStore = useClusterStore()
+
const baseRoutesMap = ref<Record<string, RouteRecordRaw>>({})
const headerMenus = ref<RouteRecordRaw[]>([])
const siderMenus = ref<RouteRecordRaw[]>([])
const headerSelectedKey = ref()
const siderMenuSelectedKey = ref()
const routePathFromClusters = shallowRef('/cluster-manage/clusters')
+
const clusterList = computed(() => Object.values(clusterStore.clusterMap))
+ /**
+ * Build the mapping of base routes from dynamic routes.
+ */
const buildMenuMap = () => {
baseRoutesMap.value = dr.reduce((buildRes, { path, name, meta, children
}) => {
!meta?.hidden && (buildRes[path] = { path, name, meta, children })
@@ -44,24 +49,45 @@ export const useMenuStore = defineStore(
}, {})
}
+ /**
+ * Setup the header menu based on the current route.
+ */
const setupHeader = () => {
- headerSelectedKey.value = route.matched[0].path ?? '/cluster-manage'
+ if (route.matched[0].path === '/login') {
+ headerSelectedKey.value = '/cluster-manage'
+ } else {
+ headerSelectedKey.value = route.matched[0].path ?? '/cluster-manage'
+ }
headerMenus.value = Object.values(baseRoutesMap.value)
siderMenus.value =
baseRoutesMap.value[headerSelectedKey.value]?.children || []
}
- const setupSider = () => {
+ /**
+ * Setup the sider menu and handle cluster-based navigation.
+ */
+ const setupSider = async () => {
siderMenus.value =
baseRoutesMap.value[headerSelectedKey.value]?.children || []
+
+ // If the first sider menu has a redirect, set it as the selected key
if (siderMenus.value[0]?.redirect) {
siderMenuSelectedKey.value = siderMenus.value[0].redirect
- } else {
- if (clusterList.value.length > 0) {
- const { id } = clusterList.value[0]
- onSiderClick(`${routePathFromClusters.value}/${id}`)
- } else {
- onSiderClick(`${routePathFromClusters.value}/default`)
- }
+ return
}
+
+ const targetPath =
+ clusterList.value.length > 0
+ ? `${routePathFromClusters.value}/${clusterList.value[0].id}`
+ : `${routePathFromClusters.value}/default`
+
+ navigateToSider(targetPath)
+ }
+
+ /**
+ * Navigate to a specific sider menu path.
+ * @param path - The path to navigate to.
+ */
+ const navigateToSider = (path: string) => {
+ router.push({ path })
}
const onHeaderClick = (key: string) => {
@@ -74,35 +100,66 @@ export const useMenuStore = defineStore(
router.push({ path: key })
}
+ /**
+ * Update the sider menu based on the last cluster in the list.
+ */
const updateSider = async () => {
- await clusterStore.loadClusters()
- const { id } = clusterList.value[clusterList.value.length - 1]
- await nextTick()
- onSiderClick(`${routePathFromClusters.value}/${id}`)
+ try {
+ await clusterStore.loadClusters()
+ const lastClusterId = clusterList.value[clusterList.value.length -
1]?.id
+ if (lastClusterId) {
+ await nextTick()
+ navigateToSider(`${routePathFromClusters.value}/${lastClusterId}`)
+ }
+ } catch (error) {
+ console.error('Error updating sider menu:', error)
+ }
}
- const setupMenu = async () => {
+ /**
+ * Initialize the menu by setting up the header and sider menus.
+ */
+ const setupMenu = () => {
buildMenuMap()
setupHeader()
setupSider()
}
+ const $reset = () => {
+ headerMenus.value = []
+ siderMenus.value = []
+ headerSelectedKey.value = undefined
+ siderMenuSelectedKey.value = undefined
+ baseRoutesMap.value = {}
+ clusterStore.$reset()
+ }
+
return {
headerMenus,
siderMenus,
headerSelectedKey,
siderMenuSelectedKey,
routePathFromClusters,
+ baseRoutesMap,
setupMenu,
+ setupSider,
updateSider,
onHeaderClick,
- onSiderClick
+ onSiderClick,
+ reset: $reset
}
},
{
persist: {
storage: localStorage,
- paths: ['headerMenus', 'siderMenus']
+ paths: [
+ 'headerMenus',
+ 'siderMenus',
+ 'siderMenuSelectedKey',
+ 'headerSelectedKey',
+ 'routePathFromClusters',
+ 'baseRoutesMap'
+ ]
}
}
)
diff --git a/bigtop-manager-ui/src/store/stack/index.ts
b/bigtop-manager-ui/src/store/stack/index.ts
index 30aeda34..32646c41 100644
--- a/bigtop-manager-ui/src/store/stack/index.ts
+++ b/bigtop-manager-ui/src/store/stack/index.ts
@@ -32,10 +32,12 @@ export type ServiceMap = {
configs: ServiceConfig
requiredServices: string[]
}
+
export type ComponentMap = {
service: string
stack: string
} & ComponentVO
+
export interface StackRelationMap {
services: ServiceMap
components: ComponentMap
@@ -47,20 +49,29 @@ export const useStackStore = defineStore(
const stacks = shallowRef<StackVO[]>([])
const stackRelationMap = shallowRef<StackRelationMap>()
- const loadStacks = async () => {
+ const loadStacks = async (): Promise<void> => {
try {
- stacks.value = await getStacks()
- stackRelationMap.value = setupStackRelationMap(stacks.value)
+ const fetchedStacks = await getStacks()
+ stacks.value = fetchedStacks
+ stackRelationMap.value = setupStackRelationMap(fetchedStacks)
} catch (error) {
- console.log('error :>> ', error)
+ console.error('Error loading stacks:', error)
}
}
- const setupStackRelationMap = (stacks: StackVO[]) => {
+ /**
+ * Set up the relation map for services and components based on stack data.
+ * @param stacks - Array of stack data.
+ * @returns A relation map containing services and components.
+ */
+ const setupStackRelationMap = (stacks: StackVO[]): StackRelationMap => {
const relationMap = { services: {}, components: {} } as StackRelationMap
+
for (const { stackName, services } of stacks) {
for (const service of services) {
const { name, displayName, components, configs } = service
+
+ // Map service data
relationMap.services[name!] = {
displayName,
stack: stackName,
@@ -68,6 +79,8 @@ export const useStackStore = defineStore(
configs,
requiredServices: service.requiredServices
}
+
+ // Map component data
for (const component of components!) {
relationMap.components[component.name!] = {
...component,
@@ -82,6 +95,7 @@ export const useStackStore = defineStore(
const getServicesByExclude = (exclude?: string[], isOrder = true):
ExpandServiceVO[] | ServiceVO[] => {
const filterData = stacks.value.flatMap((stack) =>
(exclude?.includes(stack.stackName) ? [] : stack.services))
+
return isOrder ? filterData.map((service, index) => ({ ...service,
order: index })) : filterData
}
@@ -94,7 +108,8 @@ export const useStackStore = defineStore(
},
{
persist: {
- storage: sessionStorage
+ storage: localStorage,
+ paths: ['stacks', 'stackRelationMap']
}
}
)
diff --git a/bigtop-manager-ui/src/store/user/index.ts
b/bigtop-manager-ui/src/store/user/index.ts
index 29ef16b3..4d38f0ee 100644
--- a/bigtop-manager-ui/src/store/user/index.ts
+++ b/bigtop-manager-ui/src/store/user/index.ts
@@ -50,6 +50,9 @@ export const useUserStore = defineStore(
}
},
{
- persist: false
+ persist: {
+ storage: localStorage,
+ paths: ['userVO']
+ }
}
)
diff --git a/bigtop-manager-ui/src/styles/index.scss
b/bigtop-manager-ui/src/styles/index.scss
index 4bcb02ea..4f319c23 100644
--- a/bigtop-manager-ui/src/styles/index.scss
+++ b/bigtop-manager-ui/src/styles/index.scss
@@ -19,6 +19,35 @@
@import './variables.scss';
+* {
+ outline: 0;
+}
+
+html {
+ --text-color: rgba(0, 0, 0, 0.85);
+ --text-color-desc: rgba(0, 0, 0, 0.45);
+ --bg-color: #fff;
+ --hover-color: rgba(0, 0, 0, 0.05);
+ --bg-color-container: #f0f2f5;
+ --c-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+}
+
+.m-0 {
+ margin: 0;
+}
+
+.p-0 {
+ padding: 0;
+}
+
+.hidden {
+ display: none;
+}
+
+.ant-table-body {
+ overflow-y: auto !important;
+}
+
#app {
width: 100%;
box-sizing: border-box;