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 bd0a06b6 BIGTOP-4400: Add job progress floating window (#200)
bd0a06b6 is described below
commit bd0a06b646bd7a026b49a5513d67638d119c60cd
Author: Fdefined <[email protected]>
AuthorDate: Mon Apr 7 16:17:01 2025 +0800
BIGTOP-4400: Add job progress floating window (#200)
---
bigtop-manager-ui/src/api/command/types.ts | 1 +
bigtop-manager-ui/src/api/types.ts | 2 +-
.../src/assets/images/svg/installing.svg | 77 +++---
.../src/assets/images/svg/processing.svg | 4 +
.../src/assets/images/svg/question.svg | 3 +-
.../images/svg/{processing.svg => remove.svg} | 7 +-
.../images/svg/{processing.svg => retry.svg} | 12 +-
.../components/service-configurator.vue | 67 +++--
.../src/components/{job => job-modal}/index.vue | 158 +++++-------
.../src/components/job/custom-progress.vue | 35 +--
bigtop-manager-ui/src/components/job/index.vue | 2 +-
.../src/components/log-view/index.vue | 36 +--
bigtop-manager-ui/src/locales/en_US/common.ts | 3 +-
bigtop-manager-ui/src/locales/en_US/job.ts | 6 +-
bigtop-manager-ui/src/locales/zh_CN/common.ts | 3 +-
bigtop-manager-ui/src/locales/zh_CN/job.ts | 6 +-
.../src/pages/cluster-manage/cluster/create.vue | 9 +-
.../src/pages/cluster-manage/cluster/index.vue | 23 +-
.../src/pages/cluster-manage/cluster/overview.vue | 32 ++-
.../cluster/service-management/components.vue | 19 +-
.../cluster/service-management/configs.vue | 61 +++--
.../cluster/service-management/index.vue | 25 +-
.../src/pages/cluster-manage/cluster/service.vue | 45 ++--
.../src/pages/cluster-manage/hosts/detail.vue | 20 +-
.../src/pages/cluster-manage/hosts/index.vue | 9 +-
.../src/pages/cluster-manage/hosts/overview.vue | 83 ++++---
.../pages/cluster-manage/infrastructures/index.vue | 50 ++--
bigtop-manager-ui/src/store/cluster/index.ts | 2 +
bigtop-manager-ui/src/store/installed/index.ts | 29 ++-
bigtop-manager-ui/src/store/job-progress/index.ts | 276 +++++++++++++++++++++
bigtop-manager-ui/src/store/service/index.ts | 12 +-
bigtop-manager-ui/src/store/stack/index.ts | 43 +++-
bigtop-manager-ui/src/styles/index.scss | 69 +++++-
33 files changed, 868 insertions(+), 361 deletions(-)
diff --git a/bigtop-manager-ui/src/api/command/types.ts
b/bigtop-manager-ui/src/api/command/types.ts
index 97313362..25550934 100644
--- a/bigtop-manager-ui/src/api/command/types.ts
+++ b/bigtop-manager-ui/src/api/command/types.ts
@@ -135,6 +135,7 @@ export interface AttrsReq {
export interface CommandVO {
id?: number
+ name?: string
state?: string
[property: string]: any
}
diff --git a/bigtop-manager-ui/src/api/types.ts
b/bigtop-manager-ui/src/api/types.ts
index 91d65f12..d879febf 100644
--- a/bigtop-manager-ui/src/api/types.ts
+++ b/bigtop-manager-ui/src/api/types.ts
@@ -29,7 +29,7 @@ export interface PageVO<T = any> {
}
export interface ListParams {
- orderBy?: string
+ order?: string
pageNum?: number
pageSize?: number
sort?: string
diff --git a/bigtop-manager-ui/src/assets/images/svg/installing.svg
b/bigtop-manager-ui/src/assets/images/svg/installing.svg
index a51ec205..03688318 100644
--- a/bigtop-manager-ui/src/assets/images/svg/installing.svg
+++ b/bigtop-manager-ui/src/assets/images/svg/installing.svg
@@ -19,40 +19,45 @@
-->
<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">
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M8 1.33337L8 2.66671">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M11.3332 2.22656L10.6665 3.38126">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M13.7734 4.66663L12.6187 5.33329">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M14.6668 8L13.3335 8">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M13.7734 11.3333L12.6187 10.6666">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M11.3332 13.7735L10.6665 12.6188">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M8 14.6667L8 13.3334">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M4.6665 13.7735L5.33317 12.6188">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M2.22656 11.3333L3.38127 10.6666">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M1.3335 8L2.66683 8">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M2.22656 4.66663L3.38127 5.33329">
- </path>
- <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M4.6665 2.22656L5.33317 3.38126">
- </path>
+ <g>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M8 1.33337L8 2.66671">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M11.3332 2.22656L10.6665 3.38126">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M13.7734 4.66663L12.6187 5.33329">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M14.6668 8L13.3335 8">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M13.7734 11.3333L12.6187 10.6666">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M11.3332 13.7735L10.6665 12.6188">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M8 14.6667L8 13.3334">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M4.6665 13.7735L5.33317 12.6188">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M2.22656 11.3333L3.38127 10.6666">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M1.3335 8L2.66683 8">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M2.22656 4.66663L3.38127 5.33329">
+ </path>
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M4.6665 2.22656L5.33317 3.38126">
+ </path>
+ <animateTransform values="360 8 8;0 8 8" keyTimes="0;1" dur="1.2"
repeatCount="indefinite" type="rotate"
+ attributeName="transform"></animateTransform>
+ </g>
+
</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/assets/images/svg/processing.svg
b/bigtop-manager-ui/src/assets/images/svg/processing.svg
index 015797f8..9f553893 100644
--- a/bigtop-manager-ui/src/assets/images/svg/processing.svg
+++ b/bigtop-manager-ui/src/assets/images/svg/processing.svg
@@ -21,8 +21,12 @@
viewBox="0 0 16 16" fill="none">
<path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
d="M1.3335 7.99992C1.3335 11.6818 4.31826 14.6666 8.00016 14.6666C11.6821
14.6666 14.6668 11.6818 14.6668 7.99992C14.6668 4.31802 11.6821 1.33325 8.00016
1.33325">
+ <animateTransform values="0 8 8;360 8 8" keyTimes="0;1" dur="1"
repeatCount="indefinite" type="rotate"
+ attributeName="transform"></animateTransform>
</path>
<path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
d="M12 8C12 5.79087 10.2091 4 8 4C5.79087 4 4 5.79087 4 8C4 10.2091
5.79087 12 8 12">
+ <animateTransform values="360 8 8;0 8 8" keyTimes="0;1" dur="1.2"
repeatCount="indefinite" type="rotate"
+ attributeName="transform"></animateTransform>
</path>
</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/assets/images/svg/question.svg
b/bigtop-manager-ui/src/assets/images/svg/question.svg
index 79a6923c..9dbac282 100644
--- a/bigtop-manager-ui/src/assets/images/svg/question.svg
+++ b/bigtop-manager-ui/src/assets/images/svg/question.svg
@@ -16,7 +16,8 @@
~ 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"
+-->
+<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">
<g opacity="0">
<rect x="0" y="0" width="16" height="16" fill="#000000">
diff --git a/bigtop-manager-ui/src/assets/images/svg/processing.svg
b/bigtop-manager-ui/src/assets/images/svg/remove.svg
similarity index 66%
copy from bigtop-manager-ui/src/assets/images/svg/processing.svg
copy to bigtop-manager-ui/src/assets/images/svg/remove.svg
index 015797f8..2879ebbe 100644
--- a/bigtop-manager-ui/src/assets/images/svg/processing.svg
+++ b/bigtop-manager-ui/src/assets/images/svg/remove.svg
@@ -19,10 +19,7 @@
-->
<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">
- <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M1.3335 7.99992C1.3335 11.6818 4.31826 14.6666 8.00016 14.6666C11.6821
14.6666 14.6668 11.6818 14.6668 7.99992C14.6668 4.31802 11.6821 1.33325 8.00016
1.33325">
- </path>
- <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M12 8C12 5.79087 10.2091 4 8 4C5.79087 4 4 5.79087 4 8C4 10.2091
5.79087 12 8 12">
+ <path stroke="rgba(51, 51, 51, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M3.5 8L12.8333 8">
</path>
</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/assets/images/svg/processing.svg
b/bigtop-manager-ui/src/assets/images/svg/retry.svg
similarity index 62%
copy from bigtop-manager-ui/src/assets/images/svg/processing.svg
copy to bigtop-manager-ui/src/assets/images/svg/retry.svg
index 015797f8..9b5199f3 100644
--- a/bigtop-manager-ui/src/assets/images/svg/processing.svg
+++ b/bigtop-manager-ui/src/assets/images/svg/retry.svg
@@ -17,12 +17,12 @@
~ 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">
- <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M1.3335 7.99992C1.3335 11.6818 4.31826 14.6666 8.00016 14.6666C11.6821
14.6666 14.6668 11.6818 14.6668 7.99992C14.6668 4.31802 11.6821 1.33325 8.00016
1.33325">
+<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12"
+ viewBox="0 0 12 12" fill="none">
+ <path stroke="rgba(22, 119, 255, 1)" stroke-width="1"
stroke-linejoin="round" stroke-linecap="round"
+ d="M9.18197 9.18197C8.36765 9.99632 7.24265 10.5 6 10.5C3.51472 10.5 1.5
8.48528 1.5 6C1.5 3.51472 3.51472 1.5 6 1.5C7.24265 1.5 8.36765 2.00368 9.18197
2.81803C9.59647 3.23252 10.5 4.25 10.5 4.25">
</path>
- <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
- d="M12 8C12 5.79087 10.2091 4 8 4C5.79087 4 4 5.79087 4 8C4 10.2091
5.79087 12 8 12">
+ <path stroke="rgba(22, 119, 255, 1)" stroke-width="1"
stroke-linejoin="round" stroke-linecap="round"
+ d="M10.5 2L10.5 4.25L8.25 4.25">
</path>
</svg>
\ No newline at end of file
diff --git
a/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
b/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
index 446d8a30..37073c1d 100644
---
a/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
+++
b/bigtop-manager-ui/src/components/create-service/components/service-configurator.vue
@@ -26,6 +26,7 @@
import type { ServiceConfigReq } from '@/api/command/types'
import type { ComponentVO } from '@/api/component/types'
import type { Key } from 'ant-design-vue/es/_util/type'
+ import type { Property } from '@/api/service/types'
interface Props {
isView?: boolean
@@ -49,16 +50,16 @@
})
const layout = shallowRef({
labelCol: {
- xs: { span: 24 },
- sm: { span: 8 },
- md: { span: 8 },
- lg: { span: 6 }
+ xs: { span: 23 },
+ sm: { span: 7 },
+ md: { span: 7 },
+ lg: { span: 5 }
},
wrapperCol: {
- xs: { span: 24 },
- sm: { span: 16 },
- md: { span: 16 },
- lg: { span: 18 }
+ xs: { span: 23 },
+ sm: { span: 15 },
+ md: { span: 15 },
+ lg: { span: 17 }
}
})
const serviceList = computed(() => selectedServices.value)
@@ -77,7 +78,7 @@
}
)
- const createNewConfigItem = () => {
+ const createNewProperty = () => {
return {
name: '',
displayName: '',
@@ -86,8 +87,15 @@
}
}
- const manualAddConfig = (config: ServiceConfigReq) => {
- config.properties?.push(createNewConfigItem())
+ const manualAddPropertyForConfig = (config: ServiceConfigReq) => {
+ config.properties?.push(createNewProperty())
+ }
+
+ const removeProperty = (property: Property, config: ServiceConfigReq) => {
+ const index = config.properties.findIndex((v) => v.name === property.name)
+ if (index != -1) {
+ config.properties.splice(index, 1)
+ }
}
const handleChange = (expandSelectedKeyPath: string) => {
@@ -157,7 +165,12 @@
<a-collapse v-model:active-key="activeKey" :bordered="false"
:ghost="true">
<a-collapse-panel v-for="config in filterConfigs" :key="config.id">
<template #extra>
- <a-button type="text" shape="circle"
@click.stop="manualAddConfig(config)">
+ <a-button
+ v-if="!$props.isView && !disabled"
+ type="text"
+ shape="circle"
+ @click.stop="manualAddPropertyForConfig(config)"
+ >
<template #icon>
<svg-icon name="plus_dark" />
</template>
@@ -166,12 +179,22 @@
<template #header>
<span>{{ config.name }}</span>
</template>
- <a-row v-for="(item, idx) in config.properties" :key="idx"
:gutter="[16, 0]" :wrap="true">
+ <a-row
+ v-for="(property, idx) in config.properties"
+ :key="idx"
+ justify="space-between"
+ :gutter="[16, 0]"
+ :wrap="true"
+ >
<a-col v-bind="layout.labelCol">
<a-form-item>
- <a-textarea v-if="item.isManual" v-model:value="item.name"
:auto-size="{ minRows: 1, maxRows: 5 }" />
- <span v-else style="overflow-wrap: break-word"
:title="item.displayName ?? item.name">
- {{ item.displayName ?? item.name }}
+ <a-textarea
+ v-if="property.isManual"
+ v-model:value="property.name"
+ :auto-size="{ minRows: 1, maxRows: 5 }"
+ />
+ <span v-else style="overflow-wrap: break-word"
:title="property.displayName ?? property.name">
+ {{ property.displayName ?? property.name }}
</span>
<!-- <template v-else>
@@ -190,9 +213,19 @@
</a-col>
<a-col v-bind="layout.wrapperCol">
<a-form-item>
- <a-textarea v-model:value="item.value" :auto-size="{
minRows: 1, maxRows: 5 }" />
+ <a-textarea v-model:value="property.value" :auto-size="{
minRows: 1, maxRows: 5 }" />
</a-form-item>
</a-col>
+ <a-button
+ v-if="!$props.isView && !disabled"
+ type="text"
+ shape="circle"
+ @click="removeProperty(property, config)"
+ >
+ <template #icon>
+ <svg-icon name="remove" />
+ </template>
+ </a-button>
</a-row>
</a-collapse-panel>
</a-collapse>
diff --git a/bigtop-manager-ui/src/components/job/index.vue
b/bigtop-manager-ui/src/components/job-modal/index.vue
similarity index 62%
copy from bigtop-manager-ui/src/components/job/index.vue
copy to bigtop-manager-ui/src/components/job-modal/index.vue
index f318fe34..1452077c 100644
--- a/bigtop-manager-ui/src/components/job/index.vue
+++ b/bigtop-manager-ui/src/components/job-modal/index.vue
@@ -18,30 +18,31 @@
-->
<script setup lang="ts">
- import { computed, ComputedRef, onActivated, onDeactivated, reactive, ref,
shallowRef } from 'vue'
+ import { computed, Reactive, reactive, ref, shallowRef, toRefs, watch } from
'vue'
import { useI18n } from 'vue-i18n'
- import { useRoute } from 'vue-router'
- import { TableColumnType, TableProps } from 'ant-design-vue'
- import { getJobList, getStageList, getTaskList } from '@/api/job'
+ import { TableColumnType } from 'ant-design-vue'
import useBaseTable from '@/composables/use-base-table'
import LogsView, { type LogViewProps } from '@/components/log-view/index.vue'
- import CustomProgress from './custom-progress.vue'
+ import CustomProgress from '@/components/job/custom-progress.vue'
import type { JobVO, StageVO, StateType, TaskListParams, TaskVO } from
'@/api/job/types'
+ import type { CommandRes, JobStageProgressItem } from '@/store/job-progress'
interface BreadcrumbItem {
id: string
- name: ComputedRef<string> | string
+ name: string
+ tableData: (JobVO | StageVO | TaskVO)[]
}
- const POLLING_INTERVAL = 3000
+
+ interface Props {
+ execRes: CommandRes
+ jobInfo: Reactive<Map<number, JobStageProgressItem>>
+ }
+
+ const props = defineProps<Props>()
+ const { execRes, jobInfo } = toRefs(props)
+
const { t } = useI18n()
- const route = useRoute()
- const pollingIntervalId = ref<any>(null)
- const breadcrumbs = ref<BreadcrumbItem[]>([
- {
- name: computed(() => t('job.job_list')),
- id: `clusterId-${route.params.id}`
- }
- ])
+ const breadcrumbs = ref<BreadcrumbItem[]>([])
const status = shallowRef<Record<StateType, string>>({
Pending: 'installing',
Processing: 'processing',
@@ -51,16 +52,10 @@
})
const apiMap = shallowRef([
{
- key: 'clusterId',
- api: getJobList
- },
- {
- key: 'jobId',
- api: getStageList
+ key: 'jobId'
},
{
- key: 'stageId',
- api: getTaskList
+ key: 'stageId'
}
])
const logsViewState = reactive<LogViewProps>({
@@ -97,99 +92,76 @@
ellipsis: true
}
])
- const apiParams = computed(
- (): TaskListParams =>
- breadcrumbs.value.reduce((pre, val, index) => {
- return Object.assign(pre, {
- [`${apiMap.value[index].key}`]:
val.id.replace(`${apiMap.value[index].key}-`, '')
- })
- }, {} as TaskListParams)
+ const apiParams = computed(() =>
+ breadcrumbs.value.reduce((pre, val, index) => {
+ const apiMapKey = apiMap.value[index].key
+ return Object.assign(pre, {
+ [`${apiMapKey}`]: val.id.replace(`${apiMapKey}-`, '')
+ })
+ }, {} as TaskListParams)
)
- const { dataSource, loading, filtersParams, paginationProps, onChange } =
useBaseTable<JobVO | StageVO | TaskVO>({
- columns: columns.value
+ const { dataSource, loading, paginationProps, onChange } =
useBaseTable<JobVO | StageVO | TaskVO>({
+ columns: columns.value,
+ rows: []
})
+ const currJobInfo = computed(() => jobInfo.value.get(execRes.value.jobId))
+
+ watch(
+ () => currJobInfo.value?.payLoad,
+ (val) => {
+ if (val) {
+ breadcrumbs.value[0] = {
+ name: val.name!,
+ id: `jobId-${val.id}`,
+ tableData: val.stages || []
+ }
+ dataSource.value = val.stages || []
+ }
+ },
+ {
+ immediate: true,
+ deep: true
+ }
+ )
+
const clickBreadcrumb = (breadcrumb: BreadcrumbItem) => {
if (breadcrumb.id !== currBreadcrumb.value?.id) {
const index = breadcrumbs.value.findIndex((v) => v.id === breadcrumb.id)
breadcrumbs.value.splice(index + 1, breadcrumbLen.value - index - 1)
- stopPolling()
- startPolling()
}
+ dataSource.value = breadcrumb.tableData
}
- const updateBreadcrumbs = (data: BreadcrumbItem) => {
+ const updateBreadcrumbs = (data: JobVO | StageVO | TaskVO) => {
if (breadcrumbLen.value + 1 > apiMap.value.length) {
// task log open
logsViewState.open = true
logsViewState.subTitle = data.name as string
logsViewState.payLoad = {
...apiParams.value,
- taskId: parseInt(data.id)
+ taskId: Number(data.id),
+ clusterId: currJobInfo.value?.clusterId ?? 0
}
return
}
- const currId = `${apiMap.value[breadcrumbLen.value].key}-${data.id}`
+ const breadcrumbKey = apiMap.value[breadcrumbLen.value].key
+ const currId = `${breadcrumbKey}-${data.id}`
const index = breadcrumbs.value.findIndex((v) => v.id === currId)
+ const newBreadcrumbItem = { id: currId, name: data.name! }
if (index === -1) {
- breadcrumbs.value.push({
- id: currId,
- name: data.name as string
- })
- stopPolling()
- startPolling()
- }
- }
-
- const getListData = async (isFirstCall = false) => {
- if (!paginationProps.value) {
- loading.value = false
- return
- }
- try {
- if (isFirstCall) {
- loading.value = true
+ if (breadcrumbKey === 'jobId') {
+ breadcrumbs.value.push(Object.assign(newBreadcrumbItem, { tableData:
data.stages }))
+ dataSource.value = data.stages
+ } else if (breadcrumbKey === 'stageId') {
+ breadcrumbs.value.push(Object.assign(newBreadcrumbItem, { tableData:
data.tasks }))
+ dataSource.value = data.tasks
}
- const { api } = apiMap.value[breadcrumbs.value.length - 1]
- const res = await api(apiParams.value, filtersParams.value)
- dataSource.value = res.content
- paginationProps.value.total = res.total
- } catch (error) {
- console.log('error :>> ', error)
- stopPolling()
- } finally {
- loading.value = false
+ } else {
+ dataSource.value = breadcrumbs.value[index].tableData
}
}
-
- const tableChange: TableProps['onChange'] = (...args) => {
- onChange(...args)
- stopPolling()
- startPolling()
- }
-
- const startPolling = () => {
- getListData(true)
- pollingIntervalId.value = setInterval(() => {
- getListData()
- }, POLLING_INTERVAL)
- }
-
- const stopPolling = () => {
- if (pollingIntervalId.value) {
- clearInterval(pollingIntervalId.value)
- pollingIntervalId.value = null
- }
- }
-
- onActivated(() => {
- startPolling()
- })
-
- onDeactivated(() => {
- stopPolling()
- })
</script>
<template>
@@ -200,6 +172,7 @@
v-for="breadcrumb in breadcrumbs"
:key="breadcrumb.id"
class="header-title"
+ style="font-size: 16px"
@click="clickBreadcrumb(breadcrumb)"
>
<a v-if="currBreadcrumb?.id != breadcrumb.id">{{ breadcrumb.name
}}</a>
@@ -212,7 +185,7 @@
:data-source="dataSource"
:columns="columns"
:pagination="paginationProps"
- @change="tableChange"
+ @change="onChange"
>
<template #bodyCell="{ record, text, column }">
<template v-if="column.key === 'name'">
@@ -228,6 +201,7 @@
</a-table>
<logs-view
v-model:open="logsViewState.open"
+ width="46%"
:pay-load="logsViewState.payLoad"
:sub-title="logsViewState.subTitle"
/>
diff --git a/bigtop-manager-ui/src/components/job/custom-progress.vue
b/bigtop-manager-ui/src/components/job/custom-progress.vue
index 897a8fc3..59971db9 100644
--- a/bigtop-manager-ui/src/components/job/custom-progress.vue
+++ b/bigtop-manager-ui/src/components/job/custom-progress.vue
@@ -20,18 +20,15 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { JobState } from '@/enums/state'
- import {
- MinusCircleFilled as Canceled,
- CheckCircleFilled as Successful,
- CloseCircleFilled as Failed
- } from '@ant-design/icons-vue'
interface ProgressProps {
state: keyof typeof JobState
progressData: number
}
- const props = withDefaults(defineProps<ProgressProps>(), {})
+ const props = withDefaults(defineProps<ProgressProps>(), {
+ progressData: 1
+ })
const progressConfig = computed(() => {
if (props.state === 'Pending') {
@@ -43,24 +40,25 @@
return {
progress: 100,
status: 'success',
- icon: Successful
+ icon: 'success'
}
} else if (props.state === 'Processing') {
return {
- progress: props.progressData,
+ progress: props.progressData ?? 1,
status: 'active'
}
} else if (props.state === 'Canceled') {
return {
progress: 100,
status: 'normal',
- icon: Canceled
+ icon: 'canceled',
+ strokeColor: '#faad14'
}
} else if (props.state === 'Failed') {
return {
progress: 100,
status: 'exception',
- icon: Failed
+ icon: 'failed'
}
} else {
return {
@@ -76,18 +74,25 @@
<a-progress
:percent="progressConfig.progress"
:status="progressConfig.status"
- :stroke-color="JobState[props.state]"
+ :stroke-color="progressConfig.strokeColor || ''"
>
<template #format="percent">
<span v-if="['Processing', 'Pending'].includes(props.state)"> {{
percent }} % </span>
- <component
- :is="progressConfig.icon"
+ <svg-icon
v-else-if="progressConfig.icon"
- :style="{ color: JobState[props.state] }"
+ :style="{ margin: 0, 'vertical-align': '-0.5em' }"
+ :name="progressConfig.icon"
/>
</template>
</a-progress>
</div>
</template>
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+ :deep(.ant-progress-line) {
+ margin-bottom: 0px !important;
+ .ant-progress-text {
+ line-height: 0;
+ }
+ }
+</style>
diff --git a/bigtop-manager-ui/src/components/job/index.vue
b/bigtop-manager-ui/src/components/job/index.vue
index f318fe34..dd849522 100644
--- a/bigtop-manager-ui/src/components/job/index.vue
+++ b/bigtop-manager-ui/src/components/job/index.vue
@@ -152,7 +152,7 @@
loading.value = true
}
const { api } = apiMap.value[breadcrumbs.value.length - 1]
- const res = await api(apiParams.value, filtersParams.value)
+ const res = await api(apiParams.value, { ...filtersParams.value, order:
'desc' })
dataSource.value = res.content
paginationProps.value.total = res.total
} catch (error) {
diff --git a/bigtop-manager-ui/src/components/log-view/index.vue
b/bigtop-manager-ui/src/components/log-view/index.vue
index 78e3b671..d1940ba5 100644
--- a/bigtop-manager-ui/src/components/log-view/index.vue
+++ b/bigtop-manager-ui/src/components/log-view/index.vue
@@ -42,6 +42,7 @@
export interface LogViewProps {
open: boolean
+ width?: string | number
payLoad?: PayLoad
subTitle?: string
}
@@ -154,10 +155,11 @@
<template>
<a-modal
- width="60%"
+ :width="$props.width || '60%'"
style="min-width: 400px"
:mask="false"
:open="open"
+ :z-index="99999"
:title="$t(`job.${title}`)"
@ok="handleOk"
@cancel="handleOk"
@@ -168,7 +170,7 @@
<template #footer>
<div class="log-load">
<div>
- <span v-show="loading" id="log-loading">{{ $t('job.log_loading')
}}</span>
+ <span v-show="loading" class="text-loading">{{ $t('job.log_loading')
}}</span>
</div>
<a-button key="submit" type="primary" @click="handleOk">
{{ $t('common.confirm') }}
@@ -218,34 +220,4 @@
margin: 0;
}
}
-
- @keyframes loading-dots {
- 0% {
- content: '';
- }
- 16% {
- content: '.';
- }
- 32% {
- content: '..';
- }
- 48% {
- content: '...';
- }
- 80% {
- content: '....';
- }
- 96% {
- content: '.....';
- }
- 100% {
- content: '......';
- }
- }
-
- #log-loading::after {
- content: '';
- display: inline-block;
- animation: loading-dots 1.5s infinite;
- }
</style>
diff --git a/bigtop-manager-ui/src/locales/en_US/common.ts
b/bigtop-manager-ui/src/locales/en_US/common.ts
index 89aeb245..19782df5 100644
--- a/bigtop-manager-ui/src/locales/en_US/common.ts
+++ b/bigtop-manager-ui/src/locales/en_US/common.ts
@@ -124,5 +124,6 @@ export default {
all: 'All',
disabled: 'Disabled',
restore: 'Restore',
- selected: 'Selected'
+ selected: 'Selected',
+ feature_not_supported: 'Feature not supported yet'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/job.ts
b/bigtop-manager-ui/src/locales/en_US/job.ts
index 3d015fda..535a7798 100644
--- a/bigtop-manager-ui/src/locales/en_US/job.ts
+++ b/bigtop-manager-ui/src/locales/en_US/job.ts
@@ -20,5 +20,9 @@
export default {
job_list: 'Job List',
task_log: 'Task Log',
- log_loading: 'Loading logs'
+ log_loading: 'Loading logs',
+ pending: 'Waiting to run',
+ processing: 'Running',
+ success: 'Run succeeded!',
+ failed: 'Run failed!'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/common.ts
b/bigtop-manager-ui/src/locales/zh_CN/common.ts
index 0fdfffec..f7aab381 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/common.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/common.ts
@@ -124,5 +124,6 @@ export default {
all: '全部',
disabled: '关闭',
restore: '恢复',
- selected: '选中'
+ selected: '选中',
+ feature_not_supported: '功能暂不支持'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/job.ts
b/bigtop-manager-ui/src/locales/zh_CN/job.ts
index a76d0677..04710a84 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/job.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/job.ts
@@ -20,5 +20,9 @@
export default {
job_list: '作业列表',
task_log: '任务日志',
- log_loading: '获取日志中'
+ log_loading: '获取日志中',
+ pending: '等待中',
+ processing: '运行中',
+ success: '运行成功!',
+ failed: '运行失败!'
}
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
index 37134201..944f2f98 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
@@ -19,10 +19,10 @@
<script setup lang="ts">
import { useMenuStore } from '@/store/menu'
+ import { message } from 'ant-design-vue'
import { computed, ref, shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { getInstalledStatus, installDependencies } from '@/api/hosts'
- import { message } from 'ant-design-vue'
import { InstalledStatusVO, Status } from '@/api/hosts/types'
import { execCommand } from '@/api/command'
import useSteps from '@/composables/use-steps'
@@ -30,7 +30,7 @@
import ComponentInfo from './components/component-info.vue'
import HostConfig from './components/host-config.vue'
import CheckWorkflow from './components/check-workflow.vue'
- import { ClusterCommandReq, type CommandRequest, type CommandVO, HostReq }
from '@/api/command/types'
+ import type { ClusterCommandReq, CommandRequest, CommandVO, HostReq } from
'@/api/command/types'
const { t } = useI18n()
const menuStore = useMenuStore()
@@ -51,6 +51,7 @@
stepData.value[2].every((v) => v.status === Status.Success) &&
hasUnknownHost.value
)
+ const getCompName = computed(() => components.value[current.value])
const isDone = computed(() => ['Successful',
'Failed'].includes(stepData.value[stepData.value.length - 1].state))
const steps = computed(() => [
'cluster.cluster_info',
@@ -59,10 +60,6 @@
'cluster.create'
])
- const getCompName = computed(() => {
- return components.value[current.value]
- })
-
const { current, stepsLimit, previousStep, nextStep } = useSteps(steps.value)
const updateData = (val: Partial<ClusterCommandReq> | any | HostReq[]) => {
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 4c42360a..61784027 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
@@ -22,21 +22,22 @@
import { useI18n } from 'vue-i18n'
import { useClusterStore } from '@/store/cluster'
import { storeToRefs } from 'pinia'
- import { execCommand } from '@/api/command'
- import { Command } from '@/api/command/types'
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'
const { t } = useI18n()
const router = useRouter()
+ const jobProgressStore = useJobProgress()
const clusterStore = useClusterStore()
const { currCluster, loading } = storeToRefs(clusterStore)
const activeKey = ref('1')
@@ -103,13 +104,17 @@
const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async ({ key
}) => {
try {
- await execCommand({
- command: key as keyof typeof Command,
- clusterId: currCluster.value.id,
- commandLevel: 'cluster'
- })
- await clusterStore.loadClusters()
- await clusterStore.getClusterDetail()
+ await jobProgressStore.processCommand(
+ {
+ command: key as keyof typeof Command,
+ clusterId: currCluster.value.id,
+ commandLevel: 'cluster'
+ },
+ async () => {
+ await clusterStore.loadClusters()
+ await clusterStore.getClusterDetail()
+ }
+ )
} catch (error) {
console.log('error :>> ', error)
}
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 714eb11e..848c244d 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
@@ -21,16 +21,19 @@
import { computed, onActivated, ref, shallowRef, useAttrs } from 'vue'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
- import { useServiceStore } from '@/store/service'
import { formatFromByte } from '@/utils/storage'
import { usePngImage } from '@/utils/tools'
import { CommonStatus, CommonStatusTexts } from '@/enums/state'
+ import { useServiceStore } from '@/store/service'
+ import { useJobProgress } from '@/store/job-progress'
+ import { Empty } from 'ant-design-vue'
import GaugeChart from './components/gauge-chart.vue'
import CategoryChart from './components/category-chart.vue'
import type { ClusterStatusType, ClusterVO } from '@/api/cluster/types'
- import { type MenuProps, Empty } from 'ant-design-vue'
- import type { ServiceListParams } from '@/api/service/types'
+ import type { ServiceListParams, 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'
type TimeRangeText = '1m' | '15m' | '30m' | '1h' | '6h' | '30h'
type TimeRangeItem = {
@@ -40,6 +43,7 @@
const { t } = useI18n()
const attrs = useAttrs() as ClusterVO
+ const jobProgressStore = useJobProgress()
const serviceStore = useServiceStore()
const currTimeRange = ref<TimeRangeText>('15m')
const chartData = ref({
@@ -54,6 +58,7 @@
3: 'unknown'
})
const { locateStackWithService, serviceNames } = storeToRefs(serviceStore)
+
const clusterDetail = computed(() => ({
...attrs,
totalMemory: formatFromByte(attrs.totalMemory as number),
@@ -109,21 +114,30 @@
const detailKeys = computed(() => Object.keys(baseConfig.value) as (keyof
ClusterVO)[])
const serviceOperates = computed(() => [
{
- action: 'start',
+ action: 'Start',
text: t('common.start', [t('common.service')])
},
{
- action: 'restart',
+ action: 'Restart',
text: t('common.restart', [t('common.service')])
},
{
- action: 'stop',
+ action: 'Stop',
text: t('common.stop', [t('common.service')])
}
])
- const handleServiceOperate: MenuProps['onClick'] = (item) => {
- console.log('item :>> ', item.key)
+ const handleServiceOperate = async (item: MenuItem, service: ServiceVO) => {
+ try {
+ await jobProgressStore.processCommand({
+ command: item.key as keyof typeof Command,
+ clusterId: attrs.id,
+ commandLevel: 'service',
+ serviceCommands: [{ serviceName: service.name!, installed: true }]
+ })
+ } catch (error) {
+ console.log('error :>> ', error)
+ }
}
const handleTimeRange = (time: TimeRangeItem) => {
@@ -229,7 +243,7 @@
<svg-icon name="more" style="margin: 0" />
</a-button>
<template #overlay>
- <a-menu @click="handleServiceOperate">
+ <a-menu @click="handleServiceOperate($event, service)">
<a-menu-item v-for="operate in serviceOperates"
:key="operate.action">
<span>{{ operate.text }}</span>
</a-menu-item>
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/components.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/components.vue
index 89ed8599..35505783 100644
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/components.vue
+++
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/components.vue
@@ -23,11 +23,11 @@
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { deleteComponent, getComponents } from '@/api/component'
- import { execCommand } from '@/api/command'
import { useStackStore } from '@/store/stack'
+ import { useJobProgress } from '@/store/job-progress'
import useBaseTable from '@/composables/use-base-table'
- import type { GroupItem } from '@/components/common/button-group/types'
import { message, Modal, type TableColumnType, type TableProps } from
'ant-design-vue'
+ import type { GroupItem } from '@/components/common/button-group/types'
import type { ComponentVO } from '@/api/component/types'
import type { FilterConfirmProps, FilterResetProps } from
'ant-design-vue/es/table/interface'
import type { Command, CommandRequest } from '@/api/command/types'
@@ -49,6 +49,7 @@
const POLLING_INTERVAL = 3000
const { t } = useI18n()
+ const jobProgressStore = useJobProgress()
const stackStore = useStackStore()
const route = useRoute()
const attrs = useAttrs()
@@ -58,7 +59,7 @@
const componentStatus = ref(['INSTALLING', 'SUCCESS', 'FAILED', 'UNKNOWN'])
const commandRequest = shallowRef<CommandRequest>({
command: 'Add',
- commandLevel: 'cluster',
+ commandLevel: 'component',
componentCommands: []
})
const state = reactive<TableState>({
@@ -233,14 +234,18 @@
hostnames: [row.hostname!]
})
await execOperation()
- getComponentList(true, true)
}
const execOperation = async () => {
try {
- await execCommand({ ...commandRequest.value, clusterId:
currServiceInfo.value.id })
- state.selectedRowKeys = []
- state.selectedRows = []
+ await jobProgressStore.processCommand(
+ { ...commandRequest.value, clusterId: currServiceInfo.value.id },
+ async () => {
+ getComponentList(true, true)
+ state.selectedRowKeys = []
+ state.selectedRows = []
+ }
+ )
} catch (error) {
console.log('error :>> ', error)
}
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/configs.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/configs.vue
index 66c9b094..8515026b 100644
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/configs.vue
+++
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/configs.vue
@@ -25,7 +25,7 @@
import { updateServiceConfigs } from '@/api/service'
import CaptureSnapshot from './components/capture-snapshot.vue'
import SnapshotManagement from './components/snapshot-management.vue'
- import type { ServiceConfig, ServiceVO } from '@/api/service/types'
+ import type { Property, ServiceConfig, ServiceVO } from '@/api/service/types'
interface ServieceInfo {
cluster: string
@@ -48,20 +48,20 @@
const debouncedOnSearch = ref()
const layout = shallowRef({
labelCol: {
- xs: { span: 24 },
- sm: { span: 8 },
- md: { span: 8 },
- lg: { span: 6 }
+ xs: { span: 23 },
+ sm: { span: 7 },
+ md: { span: 7 },
+ lg: { span: 5 }
},
wrapperCol: {
- xs: { span: 24 },
- sm: { span: 16 },
- md: { span: 16 },
- lg: { span: 18 }
+ xs: { span: 23 },
+ sm: { span: 15 },
+ md: { span: 15 },
+ lg: { span: 17 }
}
})
- const createNewConfigItem = () => {
+ const createNewProperty = () => {
return {
name: '',
displayName: '',
@@ -70,8 +70,15 @@
}
}
- const manualAddConfig = (config: ServiceConfig) => {
- config.properties?.push(createNewConfigItem())
+ const manualAddPropertyForConfig = (config: ServiceConfig) => {
+ config.properties?.push(createNewProperty())
+ }
+
+ const removeProperty = (property: Property, config: ServiceConfig) => {
+ const index = (config.properties || []).findIndex((v) => v.name ===
property.name)
+ if (index != -1) {
+ config.properties?.splice(index, 1)
+ }
}
const filterConfigurations = () => {
@@ -116,7 +123,8 @@
snapshotRef.value?.handleOpen({ id: serviceId, clusterId })
}
- onActivated(() => {
+ onActivated(async () => {
+ await getServiceDetail()
configs.value = attrs.configs as ServiceConfig[]
filterConfigs.value = [...configs.value]
activeKey.value = filterConfigs.value.map((v) => v.id!)
@@ -150,7 +158,7 @@
<a-collapse v-model:active-key="activeKey" :bordered="false"
:ghost="true">
<a-collapse-panel v-for="config in filterConfigs" :key="config.id">
<template #extra>
- <a-button type="text" shape="circle"
@click.stop="manualAddConfig(config)">
+ <a-button type="text" shape="circle"
@click.stop="manualAddPropertyForConfig(config)">
<template #icon>
<svg-icon name="plus_dark" />
</template>
@@ -159,20 +167,35 @@
<template #header>
<span>{{ config.name }}</span>
</template>
- <a-row v-for="(item, idx) in config.properties" :key="idx"
:gutter="[16, 0]" :wrap="true">
+ <a-row
+ v-for="(property, idx) in config.properties"
+ :key="idx"
+ justify="space-between"
+ :gutter="[16, 0]"
+ :wrap="true"
+ >
<a-col v-bind="layout.labelCol">
<a-form-item>
- <a-textarea v-if="item.isManual" v-model:value="item.name"
:auto-size="{ minRows: 1, maxRows: 5 }" />
- <span v-else style="overflow-wrap: break-word"
:title="item.displayName ?? item.name">
- {{ item.displayName ?? item.name }}
+ <a-textarea
+ v-if="property.isManual"
+ v-model:value="property.name"
+ :auto-size="{ minRows: 1, maxRows: 5 }"
+ />
+ <span v-else style="overflow-wrap: break-word"
:title="property.displayName ?? property.name">
+ {{ property.displayName ?? property.name }}
</span>
</a-form-item>
</a-col>
<a-col v-bind="layout.wrapperCol">
<a-form-item>
- <a-textarea v-model:value="item.value" :auto-size="{
minRows: 1, maxRows: 5 }" />
+ <a-textarea v-model:value="property.value" :auto-size="{
minRows: 1, maxRows: 5 }" />
</a-form-item>
</a-col>
+ <a-button type="text" shape="circle"
@click="removeProperty(property, config)">
+ <template #icon>
+ <svg-icon name="remove" />
+ </template>
+ </a-button>
</a-row>
</a-collapse-panel>
</a-collapse>
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/index.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/index.vue
index 92491d5f..144904cb 100644
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/index.vue
+++
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service-management/index.vue
@@ -21,14 +21,13 @@
import { computed, onMounted, provide, ref, shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
- // import { execCommand } from '@/api/command'
- // import { Command } from '@/api/command/types'
- // import { CommonStatus, CommonStatusTexts } from '@/enums/state'
import { useServiceStore } from '@/store/service'
+ import { useJobProgress } from '@/store/job-progress'
import { useRoute } from 'vue-router'
import Overview from './overview.vue'
import Components from './components.vue'
import Configs from './configs.vue'
+ import { 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 { ServiceVO } from '@/api/service/types'
@@ -43,6 +42,7 @@
const { t } = useI18n()
const route = useRoute()
const serviceStore = useServiceStore()
+ const jobProgressStore = useJobProgress()
const { loading } = storeToRefs(serviceStore)
const activeKey = ref('1')
const serviceDetail = shallowRef<ServiceVO>()
@@ -79,7 +79,8 @@
action: 'Stop',
text: t('common.stop', [t('common.service')])
}
- ]
+ ],
+ dropdownMenuClickEvent: (info) => dropdownMenuClick &&
dropdownMenuClick(info)
}
])
@@ -88,6 +89,22 @@
return components[parseInt(activeKey.value) - 1]
})
+ const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async ({ key
}) => {
+ try {
+ await jobProgressStore.processCommand(
+ {
+ command: key as keyof typeof Command,
+ clusterId: routeParams.value.id,
+ commandLevel: 'service',
+ serviceCommands: [{ serviceName: routeParams.value.service,
installed: true }]
+ },
+ getServiceDetail
+ )
+ } catch (error) {
+ console.log('error :>> ', error)
+ }
+ }
+
const getServiceDetail = async () => {
try {
loading.value = true
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 0f513c3e..03534884 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
@@ -25,15 +25,20 @@
import { useServiceStore } from '@/store/service'
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'
import type { ClusterVO } from '@/api/cluster/types'
+ import type { Command, CommandRequest } from '@/api/command/types'
+
+ type GroupItemActionType = keyof typeof Command | 'More'
const { t } = useI18n()
const attrs = useAttrs() as ClusterVO
const router = useRouter()
+ const jobProgressStore = useJobProgress()
const serviceStore = useServiceStore()
const { services, loading } = toRefs(serviceStore)
const statusColors = shallowRef<Record<ServiceStatusType, keyof typeof
CommonStatusTexts>>({
@@ -42,33 +47,33 @@
3: 'unknown'
})
- const actionGroups = shallowRef<GroupItem[]>([
+ const actionGroups = shallowRef<GroupItem<GroupItemActionType>[]>([
{
- action: 'start',
+ action: 'Start',
icon: 'start',
- clickEvent: (item) => {
- console.log('item :>> ', item?.action)
+ clickEvent: (item, args) => {
+ handleServiceOperate(item!.action!, args)
}
},
{
- action: 'stop',
+ action: 'Stop',
icon: 'stop',
- clickEvent: (item) => {
- console.log('item :>> ', item?.action)
+ clickEvent: (item, args) => {
+ handleServiceOperate(item!.action!, args)
}
},
{
- action: 'restart',
+ action: 'Restart',
icon: 'restart',
- clickEvent: (item) => {
- console.log('item :>> ', item?.action)
+ clickEvent: (item, args) => {
+ handleServiceOperate(item!.action!, args)
}
},
{
- action: 'more',
+ action: 'More',
icon: 'more_line',
- clickEvent: (item) => {
- console.log('item :>> ', item?.action)
+ clickEvent: (item, args) => {
+ handleServiceOperate(item!.action!, args)
}
}
])
@@ -115,6 +120,18 @@
}
])
+ const handleServiceOperate = (command: GroupItemActionType, service:
ServiceVO) => {
+ if (command !== 'More') {
+ const execCommandParams = {
+ command: command,
+ clusterId: attrs.id,
+ commandLevel: 'service',
+ serviceCommands: [{ serviceName: service.name, installed: true }]
+ } as CommandRequest
+ jobProgressStore.processCommand(execCommandParams, getServices)
+ }
+ }
+
const getServices = (filters?: ServiceListParams) => {
attrs.id != undefined && serviceStore.getServices(attrs.id, filters)
}
@@ -166,7 +183,7 @@
</div>
</div>
<div class="item-content" @click.stop>
- <button-group :auto="true" :space="0" :groups="actionGroups">
+ <button-group :auto="true" :space="0" :args="item"
:groups="actionGroups">
<template #icon="{ item: groupItem }">
<svg-icon :name="groupItem.icon || ''" />
</template>
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue
index 84d221e9..df6909ca 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/detail.vue
@@ -23,6 +23,7 @@
import { useRoute } from 'vue-router'
import { getHost } from '@/api/hosts'
import { CommonStatus, CommonStatusTexts } from '@/enums/state'
+ import { message } from 'ant-design-vue'
import Overview from './overview.vue'
import type { GroupItem } from '@/components/common/button-group/types'
import type { HostStatusType, HostVO } from '@/api/hosts/types.ts'
@@ -59,28 +60,19 @@
}
])
- const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async ({ key
}) => {
- console.log(key)
- // try {
- // await execCommand({
- // command: key as keyof typeof Command,
- // clusterId: currCluster.value.id,
- // commandLevel: 'cluster'
- // })
- // await clusterStore.loadClusters()
- // await clusterStore.getClusterDetail()
- // } catch (error) {
- // console.log('error :>> ', error)
- // }
+ const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async () => {
+ message.error(t('common.feature_not_supported'))
}
const setupHostInfo = async () => {
try {
loading.value = true
const hostId = route.query.hostId
+ const clusterId = route.query.clusterId
const data = await getHost({ id: Number(hostId) })
hostInfo.value = {
- ...data
+ ...data,
+ clusterId
}
} catch (error) {
console.log(error)
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 1b2269a4..942ee00a 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,11 @@
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 './install-dependencies.vue'
- import { useClusterStore } from '@/store/cluster'
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'
@@ -262,7 +262,12 @@
}
const viewHostDetail = (row: HostVO) => {
- router.push({ name: 'HostDetail', query: { hostId: row.id } })
+ const { clusterDisplayName, id: hostId } = row
+ const index = filtersOfClusterDisplayName.value.findIndex((v) => v.text
=== clusterDisplayName)
+ if (index != -1) {
+ const clusterId = filtersOfClusterDisplayName.value[index].value
+ router.push({ name: 'HostDetail', query: { hostId, clusterId } })
+ }
}
onUnmounted(() => {
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 e1c2f6be..ae19d107 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/overview.vue
@@ -20,16 +20,21 @@
<script setup lang="ts">
import { computed, ref, shallowRef, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n'
- import { Empty, MenuProps } from 'ant-design-vue'
+ import { Empty } from 'ant-design-vue'
import { formatFromByte } from '@/utils/storage.ts'
import { usePngImage } from '@/utils/tools'
import { CommonStatus, CommonStatusTexts } from '@/enums/state.ts'
+ import { useJobProgress } from '@/store/job-progress'
+ import { useStackStore } from '@/store/stack'
+ import { useInstalledStore } from '@/store/installed'
+ import { getComponentsByHost } from '@/api/hosts'
+ import { Command } from '@/api/command/types'
import CategoryChart from
'@/pages/cluster-manage/cluster/components/category-chart.vue'
import GaugeChart from
'@/pages/cluster-manage/cluster/components/gauge-chart.vue'
- import { getComponentsByHost } from '@/api/hosts'
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'
+ import type { MenuItem } from '@/store/menu/types'
type TimeRangeText = '1m' | '15m' | '30m' | '1h' | '6h' | '30h'
type TimeRangeItem = {
@@ -44,7 +49,9 @@
const props = defineProps<Props>()
const { hostInfo } = toRefs(props)
const { t } = useI18n()
- const currExecuteComponent = shallowRef<ComponentVO>({})
+ const stackStore = useStackStore()
+ const jobProgressStore = useJobProgress()
+ const installedStore = useInstalledStore()
const currTimeRange = ref<TimeRangeText>('15m')
const statusColors = shallowRef<Record<HostStatusType, keyof typeof
CommonStatusTexts>>({
1: 'healthy',
@@ -111,49 +118,66 @@
const detailKeys = computed((): (keyof HostVO)[] =>
Object.keys(baseConfig.value))
const componentOperates = computed(() => [
{
- action: 'start',
+ action: 'Start',
text: t('common.start', [t('common.component')])
},
{
- action: 'restart',
+ action: 'Restart',
text: t('common.restart', [t('common.component')])
},
{
- action: 'stop',
+ action: 'Stop',
text: t('common.stop', [t('common.component')])
}
])
- const handleHostOperate: MenuProps['onClick'] = (item) => {
- console.log('item :>> ', item.key)
- console.log(currExecuteComponent.value)
+ const handleHostOperate = async (item: MenuItem, component: ComponentVO) => {
+ const { serviceName } = component
+ const installedServiceMap =
Object.values(installedStore.installedServiceMap)
+ .flat()
+ .filter((v) => v.serviceName === serviceName)
+ if (installedServiceMap.length > 0) {
+ try {
+ await jobProgressStore.processCommand(
+ {
+ command: item.key as keyof typeof Command,
+ clusterId: installedServiceMap[0].clusterId,
+ commandLevel: 'component',
+ componentCommands: [{ componentName: component.name!, hostnames:
[component.hostname!] }]
+ },
+ getComponentInfo
+ )
+ } catch (error) {
+ console.log('error :>> ', error)
+ }
+ }
}
const handleTimeRange = (time: TimeRangeItem) => {
currTimeRange.value = time.text
}
- const recordClickComp = (comp: ComponentVO) => {
- currExecuteComponent.value = comp
+ const getComponentInfo = async () => {
+ try {
+ const data = await getComponentsByHost({ id: hostInfo.value.id! })
+ componentsFromCurrentHost.value = data.reduce((pre, val) => {
+ if (!pre.has(val.stack!)) {
+ pre.set(val.stack!, [val])
+ } else {
+ ;(pre.get(val.stack!) as ComponentVO[]).push(val)
+ }
+ return pre
+ }, new Map<string, ComponentVO[]>())
+ } catch (error) {
+ console.log(error)
+ }
}
watch(
() => hostInfo.value,
- async (val) => {
+ (val) => {
if (val.id) {
- try {
- const data = await getComponentsByHost({ id: val.id })
- componentsFromCurrentHost.value = data.reduce((pre, val) => {
- if (!pre.has(val.stack!)) {
- pre.set(val.stack!, [val])
- } else {
- ;(pre.get(val.stack!) as ComponentVO[]).push(val)
- }
- return pre
- }, new Map<string, ComponentVO[]>())
- } catch (error) {
- console.log(error)
- }
+ getComponentInfo()
}
}
)
@@ -239,12 +263,15 @@
<div v-for="comp in componentsFromCurrentHost.get(stack)"
:key="comp.id" class="component-item">
<a-avatar v-if="comp.serviceName"
:src="usePngImage(comp.serviceName.toLowerCase())" :size="22" />
<a-typography-text
:content="`${comp.serviceDisplayName}/${comp.displayName}`" />
- <a-dropdown :trigger="['click']">
- <a-button type="text" shape="circle" size="small"
@click="recordClickComp(comp)">
+ <a-dropdown
+
v-if="stackStore.stackRelationMap?.components[comp.name!].category != 'client'"
+ :trigger="['click']"
+ >
+ <a-button type="text" shape="circle" size="small">
<svg-icon name="more" style="margin: 0" />
</a-button>
<template #overlay>
- <a-menu @click="handleHostOperate">
+ <a-menu @click="handleHostOperate($event, comp)">
<a-menu-item v-for="operate in componentOperates"
:key="operate.action">
<span>{{ operate.text }}</span>
</a-menu-item>
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
index 1b5c0f75..cd257dfc 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/infrastructures/index.vue
@@ -24,14 +24,18 @@
import { CommonStatus, CommonStatusTexts } from '@/enums/state'
import { useServiceStore } from '@/store/service'
import { useRouter } from 'vue-router'
+ import { useJobProgress } from '@/store/job-progress'
+ import { Empty } from 'ant-design-vue'
import type { ServiceListParams, ServiceStatusType, ServiceVO } from
'@/api/service/types'
import type { GroupItem } from '@/components/common/button-group/types'
import type { FilterFormItem } from '@/components/common/filter-form/types'
- // import { execCommand } from '@/api/command';
- import type { Command } from '@/api/command/types'
+ import type { Command, CommandRequest } from '@/api/command/types'
+
+ type GroupItemActionType = keyof typeof Command | 'More'
const { t } = useI18n()
const router = useRouter()
+ const jobProgressStore = useJobProgress()
const serviceStore = useServiceStore()
const { services, loading } = toRefs(serviceStore)
@@ -62,49 +66,47 @@
]
}
])
- const actionGroups = shallowRef<GroupItem[]>([
+ const actionGroups = shallowRef<GroupItem<GroupItemActionType>[]>([
{
- action: 'start',
+ action: 'Start',
icon: 'start',
clickEvent: (item, args) => {
- console.log('item :>> ', item?.action)
- infraAction('Start', args.name)
+ infraAction(item!.action!, args)
}
},
{
- action: 'stop',
+ action: 'Stop',
icon: 'stop',
clickEvent: (item, args) => {
- console.log('item :>> ', item?.action)
- infraAction('Stop', args.name)
+ infraAction(item!.action!, args)
}
},
{
- action: 'restart',
+ action: 'Restart',
icon: 'restart',
clickEvent: (item, args) => {
- console.log('item :>> ', item?.action)
- infraAction('Restart', args.name)
+ infraAction(item!.action!, args)
}
},
{
- action: 'more',
+ action: 'More',
icon: 'more_line',
clickEvent: (item, args) => {
- console.log('item :>> ', item?.action)
- infraAction('More', args.name)
+ infraAction(item!.action!, args)
}
}
])
- const infraAction = async (command: keyof typeof Command, serviceName:
string) => {
- console.log(command, serviceName)
- // await execCommand({
- // command: command,
- // clusterId: 0,
- // commandLevel: "service",
- // serviceCommands: [{ serviceName: serviceName }]
- // })
+ const infraAction = async (command: GroupItemActionType, service: ServiceVO)
=> {
+ if (command !== 'More') {
+ const execCommandParams = {
+ command: command,
+ clusterId: 0,
+ commandLevel: 'service',
+ serviceCommands: [{ serviceName: service.name, installed: true }]
+ } as CommandRequest
+ jobProgressStore.processCommand(execCommandParams, getServices)
+ }
}
const getServices = async (filters?: ServiceListParams) => {
@@ -141,7 +143,7 @@
</div>
<div class="infra-body">
<filter-form :filter-items="filterFormItems" @filter="getServices" />
- <a-empty v-if="services.length == 0" style="width: 100%" />
+ <a-empty v-if="services.length == 0" style="width: 100%"
:image="Empty.PRESENTED_IMAGE_SIMPLE" />
<template v-else>
<a-card
v-for="item in services"
diff --git a/bigtop-manager-ui/src/store/cluster/index.ts
b/bigtop-manager-ui/src/store/cluster/index.ts
index e71a22dc..4380c728 100644
--- a/bigtop-manager-ui/src/store/cluster/index.ts
+++ b/bigtop-manager-ui/src/store/cluster/index.ts
@@ -62,6 +62,8 @@ export const useClusterStore = defineStore(
currCluster.value = await getCluster(parseInt(clusterId.value))
currCluster.value.id != undefined && (await
serviceStore.getServices(currCluster.value.id))
} catch (error) {
+ currCluster.value = {}
+ clusters.value = []
console.log('error :>> ', error)
} finally {
loading.value = false
diff --git a/bigtop-manager-ui/src/store/installed/index.ts
b/bigtop-manager-ui/src/store/installed/index.ts
index e56a2475..bb404d47 100644
--- a/bigtop-manager-ui/src/store/installed/index.ts
+++ b/bigtop-manager-ui/src/store/installed/index.ts
@@ -20,24 +20,37 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useServiceStore } from '@/store/service'
-import { ServiceVO } from '@/api/service/types.ts'
+import type { ServiceVO } from '@/api/service/types.ts'
+
+export interface InstalledMapItem {
+ serviceId: number
+ serviceName: string
+ serviceDisplayName: string
+ clusterId: number
+}
export const useInstalledStore = defineStore(
'installed',
() => {
const serviceStore = useServiceStore()
- const installedMap = ref<Record<string, string[]>>({})
+ const installedServiceMap = ref<Record<string, InstalledMapItem[]>>({})
const getInstalledNamesOrIdsOfServiceByKey = (key: string, flag: 'names' |
'ids' = 'names') => {
- return installedMap.value[key].map((value) => value.split('-')[flag ===
'names' ? 1 : 0])
+ return installedServiceMap.value[key].map((value) => {
+ if (flag === 'ids') {
+ return value.serviceId
+ } else {
+ return value.serviceName
+ }
+ })
}
const setInstalledMapKey = (key: string) => {
- installedMap.value[key] = []
+ installedServiceMap.value[key] = []
}
- const setInstalledMapKeyOfValue = (key: string, value: string[]) => {
- installedMap.value[key] = value
+ const setInstalledMapKeyOfValue = (key: string, value: InstalledMapItem[])
=> {
+ installedServiceMap.value[key] = value
}
const getServicesOfInfra = async () => {
@@ -58,7 +71,7 @@ export const useInstalledStore = defineStore(
return {
serviceStore,
- installedMap,
+ installedServiceMap,
getServicesOfInfra,
setInstalledMapKey,
setInstalledMapKeyOfValue,
@@ -70,7 +83,7 @@ export const useInstalledStore = defineStore(
persist: {
storage: sessionStorage,
key: 'installed',
- paths: ['installedMap']
+ paths: ['installedServiceMap']
}
}
)
diff --git a/bigtop-manager-ui/src/store/job-progress/index.ts
b/bigtop-manager-ui/src/store/job-progress/index.ts
new file mode 100644
index 00000000..597ad2b8
--- /dev/null
+++ b/bigtop-manager-ui/src/store/job-progress/index.ts
@@ -0,0 +1,276 @@
+/*
+ * 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 { notification, Progress, Avatar, Button, Modal } from 'ant-design-vue'
+import { computed, getCurrentInstance, h, reactive, shallowRef } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { defineStore } from 'pinia'
+import { useClusterStore } from '@/store/cluster'
+import { execCommand } from '@/api/command'
+import { getJobDetails } from '@/api/job'
+import SvgIcon from '@/components/common/svg-icon/index.vue'
+import JobModal from '@/components/job-modal/index.vue'
+import type { CommandRequest } from '@/api/command/types'
+import type { JobParams, JobVO } from '@/api/job/types'
+
+export type StatusType = 'processing' | 'success' | 'failed' | 'pending'
+export type ExecFunc = (params: JobParams) => Promise<JobVO | undefined>
+export type CommandRes = { name: string } & JobParams
+export interface JobStageProgressItem extends Partial<CommandRequest> {
+ percent: number
+ status: 'success' | 'normal' | 'active' | 'exception'
+ icon: string
+ desc: string
+ payLoad?: JobVO
+}
+
+type JobStageProgress = Record<StatusType, () => JobStageProgressItem>
+
+export const useJobProgress = defineStore('job-progress', () => {
+ const { t } = useI18n()
+ const instance = getCurrentInstance()
+ const clusterStore = useClusterStore()
+ const progressMap = reactive<Map<number, JobStageProgressItem>>(new Map())
+ const jobStageProgress = shallowRef<JobStageProgress>({
+ pending: () => ({
+ percent: 0,
+ status: 'normal',
+ icon: 'processing',
+ desc: 'job.pending'
+ }),
+ processing: () => ({
+ percent: 1,
+ status: 'active',
+ icon: 'processing',
+ desc: 'job.processing'
+ }),
+ success: () => ({
+ percent: 100,
+ status: 'success',
+ icon: 'success',
+ desc: 'job.success'
+ }),
+ failed: () => ({
+ percent: 100,
+ status: 'exception',
+ icon: 'failed',
+ desc: 'job.failed'
+ })
+ })
+
+ const jobProgressStyle = computed(() => ({
+ desc: { display: 'flex', fontSize: '12px', 'justify-content':
'space-between' },
+ avatar: { backgroundColor: 'transparent', border: 'none' },
+ icon: { padding: 0, margin: 0, height: '27px', width: '27px' },
+ retryBtn: {
+ display: 'flex',
+ padding: 0,
+ 'align-items': 'center',
+ gap: '4px',
+ fontSize: '12px',
+ height: 'auto',
+ border: 'none'
+ }
+ }))
+
+ const getClusterDisplayName = (clusterId: number) => {
+ const clusters = clusterStore.clusters
+ const index = clusters.findIndex((v) => v.id == clusterId)
+ if (index != -1) {
+ return clusters[index].displayName
+ }
+ return 'Global'
+ }
+
+ const createStateIcon = (execRes: CommandRes) => {
+ return h(
+ Avatar,
+ { size: 27, shape: 'circle', style: jobProgressStyle.value.avatar },
+ {
+ icon: () =>
+ h(SvgIcon, {
+ name: progressMap.get(execRes.jobId)!.icon!,
+ style: jobProgressStyle.value.icon
+ })
+ }
+ )
+ }
+
+ const createStateProgress = (execRes: CommandRes, nextAction?: (...args:
any) => void) => {
+ const progressData = progressMap.get(execRes.jobId)
+ return h('div', [
+ h(Progress, {
+ percent: progressData?.percent,
+ status: progressData?.status,
+ showInfo: false,
+ size: [300, 8]
+ }),
+ createStateDesc(execRes, nextAction)
+ ])
+ }
+
+ const createStateDesc = (execRes: CommandRes, nextAction?: (...args: any) =>
void) => {
+ const progressData = progressMap.get(execRes.jobId)
+ let retryJobOperation = h('span', `${progressData?.percent}%`)
+ if (progressData?.status === 'exception') {
+ retryJobOperation = h(
+ Button,
+ {
+ size: 'small',
+ type: 'link',
+ style: jobProgressStyle.value.retryBtn,
+ onClick: (e: Event) => retryJob(e, execRes, nextAction)
+ },
+ {
+ default: () => [
+ h('span', {}, t('common.retry')),
+ h(SvgIcon, {
+ name: 'retry',
+ style: { margin: 0 }
+ })
+ ]
+ }
+ )
+ }
+ return h('div', { style: jobProgressStyle.value.desc }, [
+ h('span', { class: { 'text-loading': progressData?.status === 'active' }
}, `${t(`${progressData?.desc}`)}`),
+ retryJobOperation
+ ])
+ }
+
+ const retryJob = (e: Event, execRes: CommandRes, nextAction?: (...args: any)
=> void) => {
+ e.stopPropagation()
+ Object.assign(progressMap.get(execRes.jobId)!,
jobStageProgress.value.processing())
+ pollJobDetails(execRes, getJobInstanceDetails, 1000, nextAction)
+ }
+
+ const getJobInstanceDetails = async (params: JobParams) => {
+ try {
+ const data = await getJobDetails(params)
+ return data
+ } catch (error) {
+ console.log('error :>> ', error)
+ }
+ }
+
+ const pollJobDetails = (
+ params: JobParams,
+ execFunc: ExecFunc,
+ interval: number = 1000,
+ nextAction?: (...args: any) => void
+ ) => {
+ let intervalId: NodeJS.Timeout | null = null
+ const cleanup = () => {
+ if (intervalId) {
+ clearInterval(intervalId)
+ intervalId = null
+ }
+ }
+
+ const checkStatus = async () => {
+ try {
+ const result = await execFunc(params)
+ if (!result) {
+ progressMap.set(params.jobId, Object.assign(params,
jobStageProgress.value.failed()))
+ cleanup()
+ return
+ }
+
+ const { state } = result
+ const jobId = params.jobId
+ const progressEntry = progressMap.get(jobId)
+ if (!progressEntry) {
+ cleanup()
+ return
+ }
+
+ const currentState = state === 'Successful' ? 'success' :
state!.toLowerCase()
+ const targetProgress = jobStageProgress.value[`${currentState}`]() ??
progressEntry.percent
+ const oldPercent = progressEntry?.percent ?? 0
+ const mergeProgressPercent = { ...targetProgress, percent: oldPercent
+ 2 }
+ progressEntry!.payLoad = result
+
+ if (state === 'Successful' || state === 'Failed') {
+ Object.assign(progressEntry!, targetProgress)
+ if (state === 'Successful') {
+ nextAction && nextAction()
+ closeNotification(params.jobId)
+ }
+ cleanup()
+ } else if (state === 'Processing') {
+ oldPercent < 90 && Object.assign(progressEntry, mergeProgressPercent)
+ }
+ } catch (error) {
+ console.log('error :>> ', error)
+ }
+ }
+ intervalId = setInterval(checkStatus, interval)
+ }
+
+ const openNotification = (execRes: CommandRes, nextAction?: (...args: any)
=> void) => {
+ pollJobDetails(execRes, getJobInstanceDetails, 1000, nextAction)
+ notification.open({
+ key: `${execRes.jobId}`,
+ message: `${getClusterDisplayName(execRes.clusterId)} - ${execRes.name}`,
+ duration: null,
+ placement: 'bottomRight',
+ style: { padding: '16px' },
+ class: 'job-progress-notification',
+ icon: () => createStateIcon(execRes),
+ description: () => createStateProgress(execRes, nextAction),
+ onClick: () => onClick(execRes)
+ })
+ }
+
+ const closeNotification = (key: string | number, delay = 10000) => {
+ setTimeout(() => {
+ notification.close(typeof key === 'number' ? `${key}` : key)
+ }, delay)
+ }
+
+ const processCommand = async (params: CommandRequest, nextAction?: (...args:
any) => void) => {
+ try {
+ const { id: jobId, name } = await execCommand(params)
+ if (jobId && name) {
+ progressMap.set(jobId, Object.assign(params,
jobStageProgress.value.processing()))
+ openNotification({ jobId, clusterId: params.clusterId!, name },
nextAction)
+ }
+ } catch (error) {
+ console.log('error :>> ', error)
+ }
+ }
+
+ const onClick = (execRes: CommandRes) => {
+ Modal.destroyAll()
+ Modal.confirm({
+ icon: null,
+ width: '980px',
+ zIndex: 9999,
+ mask: false,
+ closable: true,
+ centered: true,
+ appContext: instance?.appContext,
+ content: () => h(JobModal, { execRes, jobInfo: progressMap })
+ })
+ }
+
+ return {
+ processCommand
+ }
+})
diff --git a/bigtop-manager-ui/src/store/service/index.ts
b/bigtop-manager-ui/src/store/service/index.ts
index 47d45a15..3eb04458 100644
--- a/bigtop-manager-ui/src/store/service/index.ts
+++ b/bigtop-manager-ui/src/store/service/index.ts
@@ -21,7 +21,7 @@ import { defineStore, storeToRefs } from 'pinia'
import { computed, ref } from 'vue'
import { getService, getServiceList } from '@/api/service'
import { useStackStore } from '@/store/stack'
-import { useInstalledStore } from '@/store/installed'
+import { InstalledMapItem, useInstalledStore } from '@/store/installed'
import type { ServiceListParams, ServiceVO } from '@/api/service/types'
export const useServiceStore = defineStore(
@@ -33,9 +33,7 @@ export const useServiceStore = defineStore(
const total = ref(0)
const loading = ref(false)
const { stacks } = storeToRefs(stackStore)
-
const serviceNames = computed(() => services.value.map((v) => v.name))
- const serviceMap = computed(() => services.value.map((v) =>
`${v.id}-${v.name}`))
const locateStackWithService = computed(() => {
return stacks.value.filter((item) =>
item.services.some((service) => service.name &&
serviceNames.value.includes(service.name))
@@ -48,7 +46,13 @@ export const useServiceStore = defineStore(
const data = await getServiceList(clusterId, { ...filterParams,
pageNum: 1, pageSize: 100 })
services.value = data.content
total.value = data.total
- installedStore.setInstalledMapKeyOfValue(`${clusterId}`,
serviceMap.value)
+ const serviceMap = services.value.map((v) => ({
+ serviceId: v.id,
+ serviceName: v.name,
+ serviceDisplayName: v.displayName,
+ clusterId: clusterId
+ })) as InstalledMapItem[]
+ installedStore.setInstalledMapKeyOfValue(`${clusterId}`, serviceMap)
} catch (error) {
console.log('error :>> ', error)
} finally {
diff --git a/bigtop-manager-ui/src/store/stack/index.ts
b/bigtop-manager-ui/src/store/stack/index.ts
index bee272c2..358f74ab 100644
--- a/bigtop-manager-ui/src/store/stack/index.ts
+++ b/bigtop-manager-ui/src/store/stack/index.ts
@@ -17,27 +17,65 @@
* under the License.
*/
-import { ServiceVO } from '@/api/service/types'
import { getStacks } from '@/api/stack'
-import { StackVO } from '@/api/stack/types'
import { defineStore } from 'pinia'
import { shallowRef } from 'vue'
+import type { ComponentVO } from '@/api/component/types'
+import type { ServiceConfig, ServiceVO } from '@/api/service/types'
+import type { StackVO } from '@/api/stack/types'
export type ExpandServiceVO = ServiceVO & { order: number }
+interface StackRelationMap {
+ services: {
+ displayName: string
+ stack: string
+ components: string[]
+ configs: ServiceConfig
+ }
+ components: {
+ service: string
+ stack: string
+ } & ComponentVO
+}
export const useStackStore = defineStore(
'stack',
() => {
const stacks = shallowRef<StackVO[]>([])
+ const stackRelationMap = shallowRef<StackRelationMap>()
const loadStacks = async () => {
try {
stacks.value = await getStacks()
+ stackRelationMap.value = setupStackRelationMap(stacks.value)
} catch (error) {
console.log('error :>> ', error)
}
}
+ const setupStackRelationMap = (stacks: StackVO[]) => {
+ const relationMap = { services: {}, components: {} } as StackRelationMap
+ for (const { stackName, services } of stacks) {
+ for (const service of services) {
+ const { name, displayName, components, configs } = service
+ relationMap.services[name!] = {
+ displayName,
+ stack: stackName,
+ components: components!.map(({ name }) => name),
+ configs
+ }
+ for (const component of components!) {
+ relationMap.components[component.name!] = {
+ ...component,
+ service: name,
+ stack: stackName
+ }
+ }
+ }
+ }
+ return relationMap
+ }
+
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
@@ -45,6 +83,7 @@ export const useStackStore = defineStore(
return {
stacks,
+ stackRelationMap,
loadStacks,
getServicesByExclude
}
diff --git a/bigtop-manager-ui/src/styles/index.scss
b/bigtop-manager-ui/src/styles/index.scss
index 84c3bac3..fe88f1d9 100644
--- a/bigtop-manager-ui/src/styles/index.scss
+++ b/bigtop-manager-ui/src/styles/index.scss
@@ -69,4 +69,71 @@
width: 100%;
display: inline-block;
position: relative;
-}
\ No newline at end of file
+}
+
+
+.job-progress-notification{
+ &:hover{
+ background-color: $color-bg-text-hover;
+ cursor: pointer;
+ }
+ .ant-notification-notice-close{
+ top: 16px !important;
+ }
+ :where(.ant-notification-notice-content){
+
+ .ant-notification-notice-message,
+ .ant-notification-notice-description {
+ margin-left: 50px !important;
+ margin-bottom: 0 !important;
+ padding-inline-end: calc(24px / 2);
+ .ant-progress-line{
+ margin-bottom: 6px !important;
+ }
+ }
+
+ .ant-notification-notice-message{
+ font-size: 14px !important;
+ font-weight: 500 !important;
+ }
+
+ .ant-notification-notice-icon{
+ top: 50%;
+ transform: translateY(-50%);
+ margin: auto;
+ }
+ }
+}
+
+
+.text-loading::after {
+ content: '';
+ display: inline-block;
+ animation: loading-dots 1.5s infinite;
+}
+
+@keyframes loading-dots {
+ 0% {
+ content: '';
+ }
+ 16% {
+ content: '.';
+ }
+ 32% {
+ content: '..';
+ }
+ 48% {
+ content: '...';
+ }
+ 80% {
+ content: '....';
+ }
+ 96% {
+ content: '.....';
+ }
+ 100% {
+ content: '......';
+ }
+}
+
+