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 ade59c2f BIGTOP-4371: Add cluster management page (#179)
ade59c2f is described below
commit ade59c2fe3f75581c6a6b20228e47c8d2668c4ad
Author: Fdefined <[email protected]>
AuthorDate: Wed Feb 19 20:41:42 2025 +0800
BIGTOP-4371: Add cluster management page (#179)
---
bigtop-manager-ui/src/api/cluster/index.ts | 13 +-
bigtop-manager-ui/src/api/cluster/types.ts | 12 +-
bigtop-manager-ui/src/api/command/types.ts | 12 +-
bigtop-manager-ui/src/api/hosts/index.ts | 7 +-
bigtop-manager-ui/src/api/hosts/types.ts | 20 +-
bigtop-manager-ui/src/api/job/index.ts | 70 ++--
bigtop-manager-ui/src/api/job/types.ts | 20 +-
bigtop-manager-ui/src/api/request-util.ts | 11 +-
bigtop-manager-ui/src/api/service/index.ts | 70 ++--
bigtop-manager-ui/src/api/service/types.ts | 8 +-
bigtop-manager-ui/src/api/sse/index.ts | 37 --
bigtop-manager-ui/src/api/types.ts | 7 +
bigtop-manager-ui/src/api/user/types.ts | 1 -
.../images/svg/bottom-activated.svg} | 40 +-
bigtop-manager-ui/src/assets/images/svg/copy.svg | 29 ++
.../index.vue => assets/images/svg/download.svg} | 46 +--
.../src/components/common/button-group/index.vue | 2 +-
.../src/components/common/filter-form/index.vue | 120 ++++--
.../src/components/common/header-card/index.vue | 4 +-
.../src/components/common/main-card/index.vue | 5 +-
.../src/components/common/status-dot/index.vue | 12 +-
.../src/components/common/svg-icon/index.vue | 1 +
.../src/components/job/custom-progress.vue | 93 +++++
bigtop-manager-ui/src/components/job/index.vue | 237 ++++++++++++
.../src/components/log-view/index.vue | 251 +++++++++++++
.../src/composables/use-base-table.ts | 30 +-
bigtop-manager-ui/src/enums/state.ts | 39 +-
bigtop-manager-ui/src/layouts/sider.vue | 7 +-
bigtop-manager-ui/src/locales/en_US/cluster.ts | 22 +-
bigtop-manager-ui/src/locales/en_US/common.ts | 32 +-
bigtop-manager-ui/src/locales/en_US/host.ts | 6 +-
bigtop-manager-ui/src/locales/en_US/index.ts | 6 +-
bigtop-manager-ui/src/locales/en_US/job.ts | 4 +-
bigtop-manager-ui/src/locales/en_US/llm-config.ts | 4 +-
.../locales/{zh_CN/job.ts => en_US/overview.ts} | 20 +-
.../{api/sse/types.ts => locales/en_US/service.ts} | 11 +-
bigtop-manager-ui/src/locales/en_US/user.ts | 1 +
bigtop-manager-ui/src/locales/zh_CN/common.ts | 18 +-
bigtop-manager-ui/src/locales/zh_CN/host.ts | 8 +-
bigtop-manager-ui/src/locales/zh_CN/index.ts | 6 +-
bigtop-manager-ui/src/locales/zh_CN/job.ts | 4 +-
.../src/locales/zh_CN/{job.ts => overview.ts} | 20 +-
.../src/locales/zh_CN/{job.ts => service.ts} | 4 +-
bigtop-manager-ui/src/locales/zh_CN/user.ts | 1 +
.../cluster/components/check-workflow.vue | 38 +-
.../cluster/components/cluster-base.vue | 4 +-
.../cluster/components/component-info.vue | 6 +-
.../cluster-manage/cluster/components/mock.ts | 187 ----------
.../src/pages/cluster-manage/cluster/create.vue | 13 +-
.../src/pages/cluster-manage/cluster/host.vue | 146 +++++---
.../src/pages/cluster-manage/cluster/index.vue | 95 +++--
.../src/pages/cluster-manage/cluster/job.vue | 94 -----
.../src/pages/cluster-manage/cluster/overview.vue | 409 ++++++++++++---------
.../src/pages/cluster-manage/cluster/service.vue | 116 +++---
.../src/pages/cluster-manage/cluster/user.vue | 67 ++--
.../src/pages/cluster-manage/hosts/create.vue | 17 +-
bigtop-manager-ui/src/store/cluster/index.ts | 44 ++-
bigtop-manager-ui/src/store/service/index.ts | 65 ++++
bigtop-manager-ui/src/styles/index.scss | 6 +
bigtop-manager-ui/src/utils/tools.ts | 20 +
.../tests/__composables__/use-table.test.ts | 1 -
61 files changed, 1693 insertions(+), 1006 deletions(-)
diff --git a/bigtop-manager-ui/src/api/cluster/index.ts
b/bigtop-manager-ui/src/api/cluster/index.ts
index ed38562f..ac5206c8 100644
--- a/bigtop-manager-ui/src/api/cluster/index.ts
+++ b/bigtop-manager-ui/src/api/cluster/index.ts
@@ -18,9 +18,10 @@
*/
import request from '@/api/request.ts'
-import type { ClusterVO, UpdateClusterParam } from './types'
+import type { PageVO, ListParams } from '@/api/types'
+import type { ClusterVO, ServiceUserVO, UpdateClusterParam } from './types'
-export const getCluster = (id: number): Promise<ClusterVO[]> => {
+export const getCluster = (id: number): Promise<ClusterVO> => {
return request({
method: 'get',
url: `/clusters/${id}`
@@ -41,3 +42,11 @@ export const getClusterList = (): Promise<ClusterVO[]> => {
url: '/clusters'
})
}
+
+export const getUserListOfService = (id: number, params: ListParams):
Promise<PageVO<ServiceUserVO[]>> => {
+ return request({
+ method: 'get',
+ url: `/clusters/${id}/services/users`,
+ params
+ })
+}
diff --git a/bigtop-manager-ui/src/api/cluster/types.ts
b/bigtop-manager-ui/src/api/cluster/types.ts
index b9f8beb5..4ddb4769 100644
--- a/bigtop-manager-ui/src/api/cluster/types.ts
+++ b/bigtop-manager-ui/src/api/cluster/types.ts
@@ -19,6 +19,8 @@
export type UpdateClusterParam = { name: string; desc: string }
+export type ClusterStatusType = 1 | 2 | 3
+
export interface ClusterVO {
createUser?: string
desc?: string
@@ -26,7 +28,7 @@ export interface ClusterVO {
id?: number
name?: string
rootDir?: string
- status?: number
+ status?: ClusterStatusType
totalDisk?: number
totalHost?: number
totalMemory?: number
@@ -35,3 +37,11 @@ export interface ClusterVO {
type?: number
userGroup?: string
}
+
+export interface ServiceUserVO {
+ desc?: string
+ displayName?: string
+ user?: string
+ userGroup?: string
+ [property: string]: any
+}
diff --git a/bigtop-manager-ui/src/api/command/types.ts
b/bigtop-manager-ui/src/api/command/types.ts
index 30f59990..ca24cdbe 100644
--- a/bigtop-manager-ui/src/api/command/types.ts
+++ b/bigtop-manager-ui/src/api/command/types.ts
@@ -20,8 +20,8 @@
export interface CommandRequest {
clusterCommand?: ClusterCommandReq
clusterId?: number
- command: Command
- commandLevel: CommandLevel
+ command: keyof typeof Command
+ commandLevel: keyof typeof CommandLevel
componentCommands?: ComponentCommandReq[]
customCommand?: string
hostCommands?: HostCommandReq[]
@@ -70,10 +70,10 @@ export enum Command {
}
export enum CommandLevel {
- Cluster = 'cluster',
- Component = 'component',
- Host = 'host',
- Service = 'service'
+ cluster = 'cluster',
+ component = 'component',
+ host = 'host',
+ service = 'service'
}
export interface ComponentCommandReq {
diff --git a/bigtop-manager-ui/src/api/hosts/index.ts
b/bigtop-manager-ui/src/api/hosts/index.ts
index f970167e..0f401364 100644
--- a/bigtop-manager-ui/src/api/hosts/index.ts
+++ b/bigtop-manager-ui/src/api/hosts/index.ts
@@ -18,12 +18,13 @@
*/
import request from '@/api/request.ts'
-import { HostParams, HostVO, HostVOList, InstalledStatusVO } from
'@/api/hosts/types.ts'
+import type { HostListParams, HostParams, HostVO, HostVOList,
InstalledStatusVO } from '@/api/hosts/types.ts'
-export const getHosts = (): Promise<HostVOList> => {
+export const getHosts = (params?: HostListParams): Promise<HostVOList> => {
return request({
method: 'get',
- url: '/hosts'
+ url: '/hosts',
+ params
})
}
diff --git a/bigtop-manager-ui/src/api/hosts/types.ts
b/bigtop-manager-ui/src/api/hosts/types.ts
index 519a3c29..06e67b0a 100644
--- a/bigtop-manager-ui/src/api/hosts/types.ts
+++ b/bigtop-manager-ui/src/api/hosts/types.ts
@@ -54,6 +54,17 @@ export interface HostVO {
[property: string]: any
}
+export interface HostListParams {
+ clusterId?: number
+ hostname?: string
+ ipv4?: string
+ orderBy?: string
+ pageNum?: number
+ pageSize?: number
+ sort?: string
+ status?: number
+}
+
export interface HostParams {
agentDir?: string
authType?: number | string // '1-password,2-key,3-no_auth',
@@ -77,8 +88,15 @@ export interface InstalledStatusVO {
}
export enum Status {
- Failed = 'FAILED',
Installing = 'INSTALLING',
Success = 'SUCCESS',
+ Failed = 'FAILED',
Unknown = 'UNKNOW'
}
+
+export enum BaseStatus {
+ 'INSTALLING',
+ 'SUCCESS',
+ 'FAILED',
+ 'UNKNOW'
+}
diff --git a/bigtop-manager-ui/src/api/job/index.ts
b/bigtop-manager-ui/src/api/job/index.ts
index c7aaa052..ebfc4f35 100644
--- a/bigtop-manager-ui/src/api/job/index.ts
+++ b/bigtop-manager-ui/src/api/job/index.ts
@@ -16,47 +16,57 @@
* specific language governing permissions and limitations
* under the License.
*/
-import request from '@/api/request.ts'
-import type { JobParams, JobList, TaskLogParams, TaskParams, JobVO, StageVO,
TaskVO } from './types'
+import type {
+ JobParams,
+ JobList,
+ TaskLogParams,
+ JobVO,
+ JobListParams,
+ TaskListParams,
+ StageListParams,
+ StageList,
+ TaskList,
+ LogsRes
+} from './types'
+import type { ListParams } from '../types'
+import { get, post } from '../request-util'
+import axios, { type AxiosProgressEvent, type CancelTokenSource } from 'axios'
+import request from '../request'
-export const retryJob = (pathParams: JobParams): Promise<JobVO> => {
- return request({
- method: 'post',
- url: `/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/retry`
- })
+export const retryJob = (pathParams: JobParams) => {
+ return
post<JobVO>(`/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/retry`)
}
-export const getJobList = (clusterId: number): Promise<JobList> => {
- return request({
- method: 'get',
- url: `/clusters/${clusterId}/jobs`
- })
+export const getJobDetails = (pathParams: JobParams) => {
+ return
get<JobVO>(`/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}`)
}
-export const getJobDetails = (pathParams: JobParams): Promise<JobVO> => {
- return request({
- method: 'get',
- url: `/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}`
- })
+export const getJobList = (pathParams: JobListParams, params: ListParams) => {
+ return get<JobList>(`/clusters/${pathParams.clusterId}/jobs`, params)
}
-export const getStageList = (pathParams: JobParams): Promise<StageVO[]> => {
- return request({
- method: 'get',
- url: `/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/stages`
- })
+export const getStageList = (pathParams: StageListParams, params: ListParams)
=> {
+ return
get<StageList>(`/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/stages`,
params)
}
-export const getTaskList = (pathParams: TaskParams): Promise<TaskVO[]> => {
- return request({
- method: 'get',
- url:
`/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/stages/${pathParams.stageId}/tasks`
- })
+export const getTaskList = (pathParams: TaskListParams, params: ListParams) =>
{
+ return get<TaskList>(
+
`/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/stages/${pathParams.stageId}/tasks`,
+ params
+ )
}
-export const getTaskLog = (pathParams: TaskLogParams): Promise<string[]> => {
- return request({
+export const getTaskLog = (pathParams: TaskLogParams, func: Function): LogsRes
=> {
+ const source: CancelTokenSource = axios.CancelToken.source()
+
+ const promise = request({
method: 'get',
- url:
`/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/stages/${pathParams.stageId}/tasks/${pathParams.taskId}/log`
+ url:
`/clusters/${pathParams.clusterId}/jobs/${pathParams.jobId}/stages/${pathParams.stageId}/tasks/${pathParams.taskId}/log`,
+ responseType: 'stream',
+ timeout: 0,
+ cancelToken: source.token,
+ onDownloadProgress: (progressEvent: AxiosProgressEvent) =>
func(progressEvent)
})
+
+ return { promise, cancel: source.cancel }
}
diff --git a/bigtop-manager-ui/src/api/job/types.ts
b/bigtop-manager-ui/src/api/job/types.ts
index 86e54555..cbfd2764 100644
--- a/bigtop-manager-ui/src/api/job/types.ts
+++ b/bigtop-manager-ui/src/api/job/types.ts
@@ -18,22 +18,21 @@
*/
import type { PageVO } from '@/api/types'
+import { JobState } from '@/enums/state'
export type JobList = PageVO<JobVO>
export type StageList = PageVO<StageVO>
+export type TaskList = PageVO<TaskVO>
+
export type JobParams = { clusterId: number; jobId: number }
export type TaskParams = JobParams & { stageId: number }
export type TaskLogParams = TaskParams & { taskId: number }
-export type StateType = keyof typeof State
+export type StateType = keyof typeof JobState
-export enum State {
- 'Pending' = 'pending',
- 'Processing' = 'processing',
- 'Successful' = 'successful',
- 'Failed' = 'failed',
- 'Canceled' = 'canceled'
-}
+export type JobListParams = { clusterId: number }
+export type StageListParams = JobParams
+export type TaskListParams = TaskParams
export interface JobVO {
createTime?: string
@@ -65,3 +64,8 @@ export interface TaskVO {
updateTime?: string
[property: string]: any
}
+
+export interface LogsRes {
+ promise: Promise<any>
+ cancel: () => void
+}
diff --git a/bigtop-manager-ui/src/api/request-util.ts
b/bigtop-manager-ui/src/api/request-util.ts
index 7a2da40e..a2086b7b 100644
--- a/bigtop-manager-ui/src/api/request-util.ts
+++ b/bigtop-manager-ui/src/api/request-util.ts
@@ -16,23 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-import type { ResponseEntity } from './types'
import request from './request'
import { AxiosRequestConfig } from 'axios'
-const get = <T>(url: string, config?: AxiosRequestConfig):
Promise<ResponseEntity<T>> => {
- return request.get(url, config)
+const get = <T, U = any>(url: string, params?: U, config?:
AxiosRequestConfig): Promise<T> => {
+ return request.get(url, { ...config, params })
}
-const post = <T, U = any>(url: string, data?: U, config?: AxiosRequestConfig):
Promise<ResponseEntity<T>> => {
+const post = <T, U = any>(url: string, data?: U, config?: AxiosRequestConfig):
Promise<T> => {
return request.post(url, data, config)
}
-const put = <T, U = any>(url: string, data: U, config?: AxiosRequestConfig):
Promise<ResponseEntity<T>> => {
+const put = <T, U = any>(url: string, data: U, config?: AxiosRequestConfig):
Promise<T> => {
return request.put(url, data, config)
}
-const del = <T>(url: string, config?: AxiosRequestConfig):
Promise<ResponseEntity<T>> => {
+const del = <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
return request.delete(url, config)
}
diff --git a/bigtop-manager-ui/src/api/service/index.ts
b/bigtop-manager-ui/src/api/service/index.ts
index 6642f094..507061e7 100644
--- a/bigtop-manager-ui/src/api/service/index.ts
+++ b/bigtop-manager-ui/src/api/service/index.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import type { ListParams } from '../types'
import type {
ServiceParams,
ServiceVO,
@@ -24,66 +25,43 @@ import type {
ServiceConfigSnapshot,
ServiceList,
SnapshotData,
- SnapshotRecovery
+ SnapshotRecovery,
+ ServiceListParams
} from './types'
-import request from '@/api/request.ts'
+import { get, post, del } from '@/api/request-util'
-export const getServiceList = (clusterId: number): Promise<ServiceList> => {
- return request({
- method: 'get',
- url: `/clusters/${clusterId}/services`
- })
+export const getServiceList = (clusterId: number, params?: ListParams &
ServiceListParams) => {
+ return get<ServiceList>(`/clusters/${clusterId}/services`, params)
}
-export const getService = (params: ServiceParams): Promise<ServiceVO> => {
- return request({
- method: 'get',
- url: `/clusters/${params.clusterId}/services/${params.id}`
- })
+export const getService = (pathParams: ServiceParams) => {
+ return
get<ServiceVO>(`/clusters/${pathParams.clusterId}/services/${pathParams.id}`)
}
-export const getServiceConfigs = (params: ServiceParams):
Promise<ServiceConfig[]> => {
- return request({
- method: 'get',
- url: `/clusters/${params.clusterId}/services/${params.id}/configs`
- })
+export const getServiceConfigs = (pathParams: ServiceParams) => {
+ return
get<ServiceConfig[]>(`/clusters/${pathParams.clusterId}/services/${pathParams.id}/configs`)
}
-export const updateServiceConfigs = (params: ServiceParams, data:
ServiceConfig): Promise<ServiceConfig[]> => {
- return request({
- method: 'post',
- url: `/clusters/${params.clusterId}/services/${params.id}/configs`,
- data
- })
+export const updateServiceConfigs = (pathParams: ServiceParams, data:
ServiceConfig) => {
+ return
post<ServiceConfig[]>(`/clusters/${pathParams.clusterId}/services/${pathParams.id}/configs`,
data)
}
-export const getServiceConfigSnapshotsList = (params: ServiceParams):
Promise<ServiceConfigSnapshot[]> => {
- return request({
- method: 'get',
- url: `/clusters/${params.clusterId}/services/${params.id}/config-snapshots`
- })
+export const getServiceConfigSnapshotsList = (pathParams: ServiceParams) => {
+ return
get<ServiceConfigSnapshot[]>(`/clusters/${pathParams.clusterId}/services/${pathParams.id}/config-snapshots`)
}
-export const takeServiceConfigSnapshot = (
- params: ServiceParams,
- data: SnapshotData
-): Promise<ServiceConfigSnapshot> => {
- return request({
- method: 'post',
- url:
`/clusters/${params.clusterId}/services/${params.id}/config-snapshots`,
+export const takeServiceConfigSnapshot = (pathParams: ServiceParams, data:
SnapshotData) => {
+ return post<ServiceConfigSnapshot>(
+
`/clusters/${pathParams.clusterId}/services/${pathParams.id}/config-snapshots`,
data
- })
+ )
}
-export const recoveryServiceConfigSnapshot = (params: SnapshotRecovery):
Promise<ServiceConfig[]> => {
- return request({
- method: 'post',
- url:
`/clusters/${params.clusterId}/services/${params.id}/config-snapshots/${params.snapshotId}`
- })
+export const recoveryServiceConfigSnapshot = (pathParams: SnapshotRecovery):
Promise<ServiceConfig[]> => {
+ return post<ServiceConfig[]>(
+
`/clusters/${pathParams.clusterId}/services/${pathParams.id}/config-snapshots/${pathParams.snapshotId}`
+ )
}
-export const deleteServiceConfigSnapshot = (params: SnapshotRecovery):
Promise<boolean> => {
- return request({
- method: 'delete',
- url:
`/clusters/${params.clusterId}/services/${params.id}/config-snapshots/${params.snapshotId}`
- })
+export const deleteServiceConfigSnapshot = (pathParams: SnapshotRecovery):
Promise<boolean> => {
+ return
del(`/clusters/${pathParams.clusterId}/services/${pathParams.id}/config-snapshots/${pathParams.snapshotId}`)
}
diff --git a/bigtop-manager-ui/src/api/service/types.ts
b/bigtop-manager-ui/src/api/service/types.ts
index 2afefea7..9ecd25a8 100644
--- a/bigtop-manager-ui/src/api/service/types.ts
+++ b/bigtop-manager-ui/src/api/service/types.ts
@@ -24,6 +24,12 @@ export type ServiceList = PageVO<ServiceVO>
export type ServiceParams = { clusterId: number; id: number }
export type SnapshotData = { desc?: string; name?: string }
export type SnapshotRecovery = ServiceParams & { snapshotId: string }
+export type ServiceStatusType = 1 | 2 | 3
+export type ServiceListParams = {
+ name?: string
+ restartFlag?: boolean
+ status?: ServiceStatusType
+}
export interface ServiceVO {
components?: ComponentVO[]
@@ -35,7 +41,7 @@ export interface ServiceVO {
requiredServices?: string[]
restartFlag?: boolean
stack?: string
- status?: number
+ status: ServiceStatusType
user?: string
version?: string
}
diff --git a/bigtop-manager-ui/src/api/sse/index.ts
b/bigtop-manager-ui/src/api/sse/index.ts
deleted file mode 100644
index 6b288b3b..00000000
--- a/bigtop-manager-ui/src/api/sse/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import axios, { type AxiosProgressEvent, type CancelTokenSource } from 'axios'
-import request from '@/api/request.ts'
-import type { LogsRes } from './types'
-
-export const getLogs = (clusterId: number, id: number, func: Function):
LogsRes => {
- const source: CancelTokenSource = axios.CancelToken.source()
-
- const promise = request({
- method: 'get',
- url: `/sse/clusters/${clusterId}/tasks/${id}/log`,
- responseType: 'stream',
- timeout: 0,
- cancelToken: source.token,
- onDownloadProgress: (progressEvent: AxiosProgressEvent) =>
func(progressEvent)
- })
-
- return { promise, cancel: source.cancel }
-}
diff --git a/bigtop-manager-ui/src/api/types.ts
b/bigtop-manager-ui/src/api/types.ts
index 007cbb3a..91d65f12 100644
--- a/bigtop-manager-ui/src/api/types.ts
+++ b/bigtop-manager-ui/src/api/types.ts
@@ -27,3 +27,10 @@ export interface PageVO<T = any> {
total: number
content: T[]
}
+
+export interface ListParams {
+ orderBy?: string
+ pageNum?: number
+ pageSize?: number
+ sort?: string
+}
diff --git a/bigtop-manager-ui/src/api/user/types.ts
b/bigtop-manager-ui/src/api/user/types.ts
index 9692e5cf..5ce2bcdc 100644
--- a/bigtop-manager-ui/src/api/user/types.ts
+++ b/bigtop-manager-ui/src/api/user/types.ts
@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-
export interface UserVO {
id: number
username: string
diff --git a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
b/bigtop-manager-ui/src/assets/images/svg/bottom-activated.svg
similarity index 57%
copy from bigtop-manager-ui/src/components/common/svg-icon/index.vue
copy to bigtop-manager-ui/src/assets/images/svg/bottom-activated.svg
index c18cc3f3..46a1a50d 100644
--- a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/bottom-activated.svg
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
@@ -16,36 +17,9 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { computed } from 'vue'
-
- interface SvgIconProps {
- prefix?: string
- name: string
- color?: string
- }
-
- const props = withDefaults(defineProps<SvgIconProps>(), {
- prefix: 'icon',
- color: '#000'
- })
-
- const symbolId = computed(() => `#${props.prefix}-${props.name}`)
-</script>
-
-<template>
- <svg class="svg-icon" aria-hidden="true">
- <use :xlink:href="symbolId" :fill="color" />
- </svg>
-</template>
-
-<style lang="scss" scoped>
- .svg-icon {
- height: 1.2em;
- width: 1.2em;
- margin: 0 6px;
- vertical-align: -0.25em;
- overflow: hidden;
- }
-</style>
+<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="9.33349609375"
+ height="5.33331298828125" viewBox="0 0 9.33349609375 5.33331298828125"
fill="none">
+ <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M8.66675 0.666656L4.66675 4.66666L0.666748 0.666656">
+ </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/assets/images/svg/copy.svg
b/bigtop-manager-ui/src/assets/images/svg/copy.svg
new file mode 100644
index 00000000..bee76261
--- /dev/null
+++ b/bigtop-manager-ui/src/assets/images/svg/copy.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"
+ viewBox="0 0 16 16" fill="none">
+ <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M4.33301 4.14395L4.33301 2.60425C4.33301 2.08648 4.75274 1.66675
5.27051 1.66675L13.3955 1.66675C13.9133 1.66675 14.333 2.08648 14.333
2.60425L14.333 10.7292C14.333 11.247 13.9133 11.6667 13.3955 11.6667L11.8384
11.6667">
+ </path>
+ <path
+ d="M10.7295 4.33325L2.60449 4.33325C2.08673 4.33325 1.66699 4.75299
1.66699 5.27075L1.66699 13.3958C1.66699 13.9135 2.08673 14.3333 2.60449
14.3333L10.7295 14.3333C11.2473 14.3333 11.667 13.9135 11.667 13.3958L11.667
5.27075C11.667 4.75299 11.2473 4.33325 10.7295 4.33325Z"
+ stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round">
+ </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
b/bigtop-manager-ui/src/assets/images/svg/download.svg
similarity index 54%
copy from bigtop-manager-ui/src/components/common/svg-icon/index.vue
copy to bigtop-manager-ui/src/assets/images/svg/download.svg
index c18cc3f3..eff45651 100644
--- a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
+++ b/bigtop-manager-ui/src/assets/images/svg/download.svg
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
@@ -16,36 +17,15 @@
~ specific language governing permissions and limitations
~ under the License.
-->
-
-<script setup lang="ts">
- import { computed } from 'vue'
-
- interface SvgIconProps {
- prefix?: string
- name: string
- color?: string
- }
-
- const props = withDefaults(defineProps<SvgIconProps>(), {
- prefix: 'icon',
- color: '#000'
- })
-
- const symbolId = computed(() => `#${props.prefix}-${props.name}`)
-</script>
-
-<template>
- <svg class="svg-icon" aria-hidden="true">
- <use :xlink:href="symbolId" :fill="color" />
- </svg>
-</template>
-
-<style lang="scss" scoped>
- .svg-icon {
- height: 1.2em;
- width: 1.2em;
- margin: 0 6px;
- vertical-align: -0.25em;
- overflow: hidden;
- }
-</style>
+<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="M2 8.00277L2 14L14 14L14 8">
+ </path>
+ <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M11 7.66675L8 10.6667L5 7.66675">
+ </path>
+ <path stroke="rgba(22, 119, 255, 1)" stroke-width="1.3333333333333333"
stroke-linejoin="round" stroke-linecap="round"
+ d="M7.99707 2L7.99707 10.6667">
+ </path>
+</svg>
\ No newline at end of file
diff --git a/bigtop-manager-ui/src/components/common/button-group/index.vue
b/bigtop-manager-ui/src/components/common/button-group/index.vue
index 75b3f581..b2dd417f 100644
--- a/bigtop-manager-ui/src/components/common/button-group/index.vue
+++ b/bigtop-manager-ui/src/components/common/button-group/index.vue
@@ -68,7 +68,7 @@
</span>
</a-button>
<template #overlay>
- <a-menu @click="item.dropdownMenuClickEvent">
+ <a-menu v-if="!item.disabled" @click="item.dropdownMenuClickEvent">
<a-menu-item v-for="actionItem in item.dropdownMenu"
:key="actionItem.action">
{{ actionItem.text }}
</a-menu-item>
diff --git a/bigtop-manager-ui/src/components/common/filter-form/index.vue
b/bigtop-manager-ui/src/components/common/filter-form/index.vue
index 7562ea37..05c56bad 100644
--- a/bigtop-manager-ui/src/components/common/filter-form/index.vue
+++ b/bigtop-manager-ui/src/components/common/filter-form/index.vue
@@ -18,8 +18,10 @@
-->
<script setup lang="ts">
+ import { MenuProps } from 'ant-design-vue'
+ import { ref, toRefs, computed, shallowRef, toRaw } from 'vue'
+ import { isEqual, cloneDeep } from 'lodash'
import type { FilterFormItem } from './types'
- import { ref, toRefs } from 'vue'
interface FilterFormPops {
filterItems: FilterFormItem[]
@@ -29,7 +31,17 @@
const emits = defineEmits(['filter'])
const { filterItems } = toRefs(props)
- const formatFilterFormItems = ref(
+ const tempFilterParams = shallowRef({})
+ const filterParams = ref(
+ filterItems.value.reduce(
+ (pre, value) => {
+ return Object.assign(pre, { [`${value.key}`]: undefined })
+ },
+ {} as Record<string, any>
+ )
+ )
+
+ const formatFilterFormItems = computed(() =>
filterItems.value.map((v) => {
const formatData = {
...v,
@@ -43,57 +55,83 @@
})
)
- const filterParams = ref(
- filterItems.value.map((v) => ({
- [`${v.key}`]: ''
- }))
- )
-
- const checkSelected = ({ item, itemIdx }: any) => {
- return filterParams.value[itemIdx][item.key] === ''
+ const openChange = (open: boolean) => {
+ if (open) {
+ tempFilterParams.value = cloneDeep(toRaw(filterParams.value))
+ } else {
+ !isEqual(tempFilterParams.value, filterParams.value) &&
confirmFilterParams()
+ }
}
- const resetFilter = ({ item, itemIdx }: any) => {
- filterParams.value[itemIdx][item.key] = ''
- confirmFilterParams()
+ const confirmFilterParams = () => {
+ emits('filter', filterParams.value)
}
- const onSelect = ({ item, key }: any, payload: any) => {
- filterParams.value[payload.itemIdx][item.id] = key
- confirmFilterParams()
+ const onSelect: MenuProps['onSelect'] = ({ item, key }) => {
+ filterParams.value[`${item.id}`] = key
}
- const confirmFilterParams = () => {
- const filters = filterParams.value.reduce((pre, val) => {
- Object.assign(pre, val)
- return pre
- }, {} as any)
- emits('filter', filters)
+ const resetFilter = (item: any) => {
+ filterParams.value[item.key] = undefined
}
</script>
<template>
<div class="filter-form">
- <template v-for="(item, itemIdx) in formatFilterFormItems" :key="item">
- <a-dropdown>
+ <template v-for="item in formatFilterFormItems" :key="item">
+ <a-dropdown :trigger="['click']" @open-change="openChange">
<span @click.prevent>
<div class="filter-form-label">
- <span :style="{ color: !checkSelected({ item, itemIdx }) ?
'var(--color-primary)' : 'initial' }">
+ <span :style="{ color: filterParams[`${item.key}`] != undefined ?
'var(--color-primary)' : 'initial' }">
{{ item.label }}
</span>
- <svg-icon name="bottom" style="padding: 6px 4px; margin-left: 8px"
/>
+ <svg-icon
+ :name="filterParams[`${item.key}`] === undefined ? 'bottom' :
'bottom-activated'"
+ style="padding: 6px 4px; margin-left: 8px"
+ />
</div>
</span>
<template #overlay>
<template v-if="item.type === 'status'">
- <a-menu :selectable="true" :items="item.options"
@select="onSelect($event, { item, itemIdx })"> </a-menu>
+ <div>
+ <a-menu :selected-keys="[filterParams[item.key]]"
:selectable="true" @select="onSelect">
+ <a-menu-item
+ v-for="menuItem in item.options"
+ :id="item.key"
+ :key="menuItem.key"
+ :title="menuItem.label"
+ @click.stop
+ >
+ <a-radio :checked="filterParams[item.key] === menuItem.key">
+ <span>{{ menuItem.label }}</span>
+ </a-radio>
+ </a-menu-item>
+ <a-space class="status-option">
+ <a-button
+ :disabled="filterParams[`${item.key}`] === undefined"
+ size="small"
+ type="link"
+ @click.stop="resetFilter(item)"
+ >
+ {{ $t('common.reset') }}
+ </a-button>
+ <a-button size="small" type="primary"
@click="confirmFilterParams">
+ {{ $t('common.ok') }}
+ </a-button>
+ </a-space>
+ </a-menu>
+ </div>
</template>
<template v-else-if="item.type === 'search'">
<div class="search" @click.stop>
- <a-input v-model:value="filterParams[itemIdx][item.key]"
:placeholder="`搜索${item.label}`" @click.stop />
- <a-space @click.stop>
- <a-button size="small" @click="resetFilter({ item, itemIdx
})">重置</a-button>
- <a-button size="small" type="primary"
@click="confirmFilterParams">搜索</a-button>
+ <a-input
+ v-model:value="filterParams[item.key]"
+
:placeholder="`${$t('common.enter_error')}${item.label.toLowerCase()}`"
+ @click.stop
+ />
+ <a-space class="search-option">
+ <a-button size="small" @click.stop="resetFilter(item)">{{
$t('common.reset') }}</a-button>
+ <a-button size="small" type="primary"
@click="confirmFilterParams">{{ $t('common.search') }}</a-button>
</a-space>
</div>
</template>
@@ -113,6 +151,17 @@
min-height: 32px;
}
+ .status-option {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ border-top: 1px solid #f0f0f0;
+ padding: 8px 4px 4px 4px;
+ button {
+ width: 100%;
+ }
+ }
+
.search {
background-color: $color-bg-base;
box-shadow: $box-shadow-drawer-up;
@@ -122,12 +171,21 @@
flex-direction: column;
gap: $space-sm;
align-items: flex-end;
+ &-option {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ button {
+ width: 100%;
+ }
+ }
}
.filter-form {
width: 100%;
display: flex;
gap: $space-sm;
+ margin-bottom: $space-md;
&-label {
display: inline-block;
cursor: pointer;
diff --git a/bigtop-manager-ui/src/components/common/header-card/index.vue
b/bigtop-manager-ui/src/components/common/header-card/index.vue
index d11d81bb..d6fd6274 100644
--- a/bigtop-manager-ui/src/components/common/header-card/index.vue
+++ b/bigtop-manager-ui/src/components/common/header-card/index.vue
@@ -41,8 +41,8 @@
showStatus: true,
avatar: '',
title: '',
- desc: '暂无描述',
- status: 'success',
+ desc: '',
+ status: '',
actionGroups: () => {
return []
}
diff --git a/bigtop-manager-ui/src/components/common/main-card/index.vue
b/bigtop-manager-ui/src/components/common/main-card/index.vue
index f5efb7a7..8fa3a275 100644
--- a/bigtop-manager-ui/src/components/common/main-card/index.vue
+++ b/bigtop-manager-ui/src/components/common/main-card/index.vue
@@ -44,13 +44,12 @@
<div class="main-card">
<slot>
<a-tabs :active-key="activeKey" :tab-bar-gutter="0"
@change="handleChange">
- <a-tab-pane v-for="tab in tabs" :key="tab.key" :tab="tab.title">
- <slot name="tab-item"></slot>
- </a-tab-pane>
+ <a-tab-pane v-for="tab in tabs" :key="tab.key" :tab="tab.title">
</a-tab-pane>
<template #renderTabBar="{ DefaultTabBar, ...props }">
<component :is="DefaultTabBar" v-bind="props" />
</template>
</a-tabs>
+ <slot name="tab-item"></slot>
</slot>
</div>
</template>
diff --git a/bigtop-manager-ui/src/components/common/status-dot/index.vue
b/bigtop-manager-ui/src/components/common/status-dot/index.vue
index 96c401a3..5fa4860c 100644
--- a/bigtop-manager-ui/src/components/common/status-dot/index.vue
+++ b/bigtop-manager-ui/src/components/common/status-dot/index.vue
@@ -18,21 +18,15 @@
-->
<script setup lang="ts">
+ import { StatusType, StatusColors } from '@/enums/state'
import { computed } from 'vue'
interface StatusDot {
size?: number
- color?: keyof typeof Colors
+ color?: StatusType
content?: string
}
- enum Colors {
- success = 'var(--color-success)',
- error = 'var(--color-error)',
- warning = 'var(--color-warning)',
- default = 'var(--color-primary)'
- }
-
const props = withDefaults(defineProps<StatusDot>(), {
color: 'default',
size: 8,
@@ -43,7 +37,7 @@
width: props.size + 'px',
height: props.size + 'px',
fontSize: fontSize.value + 'px',
- backgroundColor: Colors[props.color]
+ backgroundColor: StatusColors[props.color]
}))
</script>
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 c18cc3f3..db973d4a 100644
--- a/bigtop-manager-ui/src/components/common/svg-icon/index.vue
+++ b/bigtop-manager-ui/src/components/common/svg-icon/index.vue
@@ -47,5 +47,6 @@
margin: 0 6px;
vertical-align: -0.25em;
overflow: hidden;
+ flex-shrink: 0;
}
</style>
diff --git a/bigtop-manager-ui/src/components/job/custom-progress.vue
b/bigtop-manager-ui/src/components/job/custom-progress.vue
new file mode 100644
index 00000000..897a8fc3
--- /dev/null
+++ b/bigtop-manager-ui/src/components/job/custom-progress.vue
@@ -0,0 +1,93 @@
+<!--
+ ~ 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 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 progressConfig = computed(() => {
+ if (props.state === 'Pending') {
+ return {
+ progress: 0,
+ status: 'normal'
+ }
+ } else if (props.state === 'Successful') {
+ return {
+ progress: 100,
+ status: 'success',
+ icon: Successful
+ }
+ } else if (props.state === 'Processing') {
+ return {
+ progress: props.progressData,
+ status: 'active'
+ }
+ } else if (props.state === 'Canceled') {
+ return {
+ progress: 100,
+ status: 'normal',
+ icon: Canceled
+ }
+ } else if (props.state === 'Failed') {
+ return {
+ progress: 100,
+ status: 'exception',
+ icon: Failed
+ }
+ } else {
+ return {
+ progress: 0,
+ status: 'status'
+ }
+ }
+ })
+</script>
+
+<template>
+ <div>
+ <a-progress
+ :percent="progressConfig.progress"
+ :status="progressConfig.status"
+ :stroke-color="JobState[props.state]"
+ >
+ <template #format="percent">
+ <span v-if="['Processing', 'Pending'].includes(props.state)"> {{
percent }} % </span>
+ <component
+ :is="progressConfig.icon"
+ v-else-if="progressConfig.icon"
+ :style="{ color: JobState[props.state] }"
+ />
+ </template>
+ </a-progress>
+ </div>
+</template>
+
+<style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/components/job/index.vue
b/bigtop-manager-ui/src/components/job/index.vue
new file mode 100644
index 00000000..f318fe34
--- /dev/null
+++ b/bigtop-manager-ui/src/components/job/index.vue
@@ -0,0 +1,237 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+
+<script setup lang="ts">
+ import { computed, ComputedRef, onActivated, onDeactivated, reactive, ref,
shallowRef } 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 useBaseTable from '@/composables/use-base-table'
+ import LogsView, { type LogViewProps } from '@/components/log-view/index.vue'
+ import CustomProgress from './custom-progress.vue'
+ import type { JobVO, StageVO, StateType, TaskListParams, TaskVO } from
'@/api/job/types'
+
+ interface BreadcrumbItem {
+ id: string
+ name: ComputedRef<string> | string
+ }
+ const POLLING_INTERVAL = 3000
+ 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 status = shallowRef<Record<StateType, string>>({
+ Pending: 'installing',
+ Processing: 'processing',
+ Failed: 'failed',
+ Canceled: 'canceled',
+ Successful: 'success'
+ })
+ const apiMap = shallowRef([
+ {
+ key: 'clusterId',
+ api: getJobList
+ },
+ {
+ key: 'jobId',
+ api: getStageList
+ },
+ {
+ key: 'stageId',
+ api: getTaskList
+ }
+ ])
+ const logsViewState = reactive<LogViewProps>({
+ open: false
+ })
+ const breadcrumbLen = computed(() => breadcrumbs.value.length)
+ const currBreadcrumb = computed(() => breadcrumbs.value.at(-1))
+ const columns = computed((): TableColumnType[] => [
+ {
+ title: '#',
+ width: '48px',
+ key: 'index',
+ customRender: ({ index }) => `${index + 1}`
+ },
+ {
+ title: t('common.name'),
+ key: 'name',
+ dataIndex: 'name',
+ ellipsis: true
+ },
+ {
+ title: t('common.status'),
+ key: 'state',
+ dataIndex: 'state'
+ },
+ {
+ title: t('common.create_time'),
+ dataIndex: 'createTime',
+ ellipsis: true
+ },
+ {
+ title: t('common.update_time'),
+ dataIndex: 'updateTime',
+ 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 { dataSource, loading, filtersParams, paginationProps, onChange } =
useBaseTable<JobVO | StageVO | TaskVO>({
+ columns: columns.value
+ })
+
+ 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()
+ }
+ }
+
+ const updateBreadcrumbs = (data: BreadcrumbItem) => {
+ 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)
+ }
+ return
+ }
+ const currId = `${apiMap.value[breadcrumbLen.value].key}-${data.id}`
+ const index = breadcrumbs.value.findIndex((v) => v.id === currId)
+ 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
+ }
+ 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
+ }
+ }
+
+ 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>
+ <div class="job">
+ <header>
+ <a-breadcrumb>
+ <a-breadcrumb-item
+ v-for="breadcrumb in breadcrumbs"
+ :key="breadcrumb.id"
+ class="header-title"
+ @click="clickBreadcrumb(breadcrumb)"
+ >
+ <a v-if="currBreadcrumb?.id != breadcrumb.id">{{ breadcrumb.name
}}</a>
+ <span v-else>{{ breadcrumb.name }}</span>
+ </a-breadcrumb-item>
+ </a-breadcrumb>
+ </header>
+ <a-table
+ :loading="loading"
+ :data-source="dataSource"
+ :columns="columns"
+ :pagination="paginationProps"
+ @change="tableChange"
+ >
+ <template #bodyCell="{ record, text, column }">
+ <template v-if="column.key === 'name'">
+ <a-typography-link underline @click="updateBreadcrumbs(record)">
+ <span :title="record.name">{{ record.name }}</span>
+ </a-typography-link>
+ </template>
+ <template v-if="column.key === 'state'">
+ <custom-progress v-if="breadcrumbLen === 1" :key="record.id"
:state="text" :progress-data="record.progress" />
+ <svg-icon v-else :name="status[record.state as
StateType]"></svg-icon>
+ </template>
+ </template>
+ </a-table>
+ <logs-view
+ v-model:open="logsViewState.open"
+ :pay-load="logsViewState.payLoad"
+ :sub-title="logsViewState.subTitle"
+ />
+ </div>
+</template>
+
+<style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/components/log-view/index.vue
b/bigtop-manager-ui/src/components/log-view/index.vue
new file mode 100644
index 00000000..78e3b671
--- /dev/null
+++ b/bigtop-manager-ui/src/components/log-view/index.vue
@@ -0,0 +1,251 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+
+<script setup lang="ts">
+ import { computed, ref, watch } from 'vue'
+ import { copyText, scrollToBottom } from '@/utils/tools'
+ import { message } from 'ant-design-vue'
+ import { useI18n } from 'vue-i18n'
+ import { getTaskLog } from '@/api/job'
+ import type { AxiosProgressEvent, Canceler } from 'axios'
+
+ type EventListener = (val: any) => void
+
+ interface Option {
+ icon: 'copy' | 'download'
+ text: string
+ on: Record<string, EventListener>
+ }
+
+ interface PayLoad {
+ clusterId: number
+ jobId: number
+ stageId: number
+ taskId: number
+ }
+
+ export interface LogViewProps {
+ open: boolean
+ payLoad?: PayLoad
+ subTitle?: string
+ }
+
+ const props = defineProps<LogViewProps>()
+ const emits = defineEmits(['update:open'])
+
+ const { t } = useI18n()
+ const title = ref('task_log')
+ const loading = ref(false)
+ const viewRef = ref<HTMLElement | null>(null)
+ const parsedLog = ref<string | null>(null)
+ const logMessage = ref<string>('')
+ const canceler = ref<Canceler>()
+ const options = computed((): Option[] => {
+ return [
+ {
+ icon: 'copy',
+ text: 'common.copy',
+ on: {
+ click: () => copyLogs()
+ }
+ },
+ {
+ icon: 'download',
+ text: 'common.download',
+ on: {
+ click: () => downloadLogs()
+ }
+ }
+ ]
+ })
+
+ watch(logMessage, (val) => {
+ parsedLog.value = val
+ .split('\n\n')
+ .map((s) => {
+ return s.substring(5)
+ })
+ .join('\n')
+ })
+
+ watch(
+ () => props.open,
+ (val) => {
+ val ? getLogMessage() : cancelSseConnect()
+ !val && (logMessage.value = '')
+ }
+ )
+
+ const getLogMessage = async () => {
+ if (!props.payLoad) {
+ return
+ }
+ try {
+ loading.value = true
+ const { cancel, promise } = getTaskLog({ ...props.payLoad },
getLogProgress)
+ canceler.value = cancel
+ promise.then(onLogComplete)
+ } catch (error) {
+ console.log('error :>> ', error)
+ loading.value = false
+ }
+ }
+
+ const onLogComplete = (res: string | undefined) => {
+ loading.value = res == undefined
+ cancelSseConnect()
+ }
+
+ const getLogProgress = ({ event }: AxiosProgressEvent) => {
+ logMessage.value = event.target.responseText
+ scrollToBottom(viewRef.value)
+ }
+
+ const cancelSseConnect = () => {
+ canceler.value && canceler.value()
+ }
+
+ const copyLogs = () => {
+ if (!parsedLog.value) {
+ return
+ }
+ copyText(parsedLog.value)
+ .then(() => {
+ message.success(`${t('common.copied')}`)
+ })
+ .catch((err: Error) => {
+ message.error(`${t('common.copy_fail')}`)
+ console.log('err :>> ', err)
+ })
+ }
+
+ const downloadLogs = () => {
+ const blob = new Blob([parsedLog.value as BlobPart], { type: 'text/plain'
})
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = 'log.txt'
+ document.body.appendChild(a)
+ a.click()
+ document.body.removeChild(a)
+ URL.revokeObjectURL(url)
+ }
+
+ const handleOk = () => {
+ emits('update:open', false)
+ }
+</script>
+
+<template>
+ <a-modal
+ width="60%"
+ style="min-width: 400px"
+ :mask="false"
+ :open="open"
+ :title="$t(`job.${title}`)"
+ @ok="handleOk"
+ @cancel="handleOk"
+ >
+ <template #closeIcon>
+ <svg-icon style="margin: 0" name="close" />
+ </template>
+ <template #footer>
+ <div class="log-load">
+ <div>
+ <span v-show="loading" id="log-loading">{{ $t('job.log_loading')
}}</span>
+ </div>
+ <a-button key="submit" type="primary" @click="handleOk">
+ {{ $t('common.confirm') }}
+ </a-button>
+ </div>
+ </template>
+ <section>
+ <span class="sub-title">{{ props.subTitle || '' }}</span>
+ <a-space :size="16" class="options">
+ <div v-for="option in options" :key="option.icon" v-on="option.on">
+ <svg-icon :name="option.icon" />
+ <a-typography-link underline :content="$t(option.text)" />
+ </div>
+ </a-space>
+ </section>
+ <article ref="viewRef">
+ <pre>{{ parsedLog }}</pre>
+ </article>
+ </a-modal>
+</template>
+
+<style lang="scss" scoped>
+ .log-load {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+ section {
+ display: flex;
+ align-content: flex-end;
+ justify-content: space-between;
+ margin-bottom: $space-sm;
+ color: rgba(0, 0, 0, 0.45);
+ }
+ article {
+ height: 500px;
+ overflow: auto;
+ pre {
+ font-size: 12px;
+ padding: 8px 16px;
+ line-height: 32px;
+ min-height: 500px;
+ overflow-y: hidden;
+ background-color: rgba(0, 0, 0, 0.88);
+ height: auto;
+ color: #fff;
+ 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/composables/use-base-table.ts
b/bigtop-manager-ui/src/composables/use-base-table.ts
index 2d359b4b..5bb04cbf 100644
--- a/bigtop-manager-ui/src/composables/use-base-table.ts
+++ b/bigtop-manager-ui/src/composables/use-base-table.ts
@@ -18,14 +18,16 @@
*/
import { ref, onUnmounted } from 'vue'
-import type { TablePaginationConfig, TableColumnType } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
+import { processData } from '@/utils/tools'
+import type { PaginationProps, TableColumnType, TableProps } from
'ant-design-vue'
+import type { FilterValue } from 'ant-design-vue/es/table/interface'
-type PaginationProps = TablePaginationConfig | false | undefined
+type PaginationType = PaginationProps | false | undefined
export interface UseBaseTableProps<T = any> {
columns: TableColumnType[]
rows?: T[]
- pagination?: PaginationProps
+ pagination?: PaginationType
}
const useBaseTable = <T>(props: UseBaseTableProps<T>) => {
const { columns, rows, pagination } = props
@@ -33,7 +35,7 @@ const useBaseTable = <T>(props: UseBaseTableProps<T>) => {
const loading = ref(false)
const dataSource = ref<T[]>(rows || [])
const columnsProp = ref<TableColumnType[]>(columns)
- const paginationProps = ref<PaginationProps>({
+ const paginationProps = ref<PaginationType>({
current: 1,
pageSize: 10,
total: dataSource.value.length,
@@ -42,19 +44,30 @@ const useBaseTable = <T>(props: UseBaseTableProps<T>) => {
pageSizeOptions: ['10', '20', '30', '40', '50'],
showTotal: (total) => `${t('common.total', [total])}`
})
+ const filtersParams = ref<Record<string, FilterValue | null | number |
undefined>>({
+ pageNum: paginationProps.value ? paginationProps.value.current : undefined,
+ pageSize: paginationProps.value ? paginationProps.value.pageSize :
undefined
+ })
- // merge pagination config
+ // Merge pagination config
if (pagination === undefined && paginationProps.value) {
- paginationProps.value = Object.assign(paginationProps.value, pagination)
+ Object.assign(paginationProps.value, pagination)
} else {
paginationProps.value = false
}
- const onChange = (pagination: TablePaginationConfig) => {
+ const onChange: TableProps['onChange'] = (pagination, filters) => {
+ // Collect params of filters
+ Object.assign(filtersParams.value, processData(filters))
if (!paginationProps.value) {
return
}
- paginationProps.value = Object.assign(paginationProps.value, pagination)
+ Object.assign(paginationProps.value, pagination)
+ // Update value of params about pagination
+ Object.assign(filtersParams.value, {
+ pageNum: pagination.current,
+ pageSize: pagination.pageSize
+ })
}
const resetState = () => {
@@ -80,6 +93,7 @@ const useBaseTable = <T>(props: UseBaseTableProps<T>) => {
dataSource,
loading,
paginationProps,
+ filtersParams,
onChange,
resetState
}
diff --git a/bigtop-manager-ui/src/enums/state.ts
b/bigtop-manager-ui/src/enums/state.ts
index 003b044c..5f4cee82 100644
--- a/bigtop-manager-ui/src/enums/state.ts
+++ b/bigtop-manager-ui/src/enums/state.ts
@@ -17,26 +17,31 @@
* under the License.
*/
-export type MaintainState = 'Uninstalled' | 'Installed' | 'Maintained' |
'Started' | 'Stopped'
+export type StatusType = keyof typeof StatusColors
-export enum State {
- Pending = '#1677ff',
- Processing = '#1677fe',
- Successful = '#52c41a',
- Failed = '#ff4d4f',
- Canceled = '#80868b'
+export enum StatusColors {
+ success = 'var(--color-success)',
+ error = 'var(--color-error)',
+ warning = 'var(--color-warning)',
+ default = 'var(--color-primary)'
}
-export enum CommonState {
- normal = '#52c41a',
- abnormal = '#ff4d4f',
- maintained = '#d9d9d9'
+export enum CommonStatus {
+ healthy = 'success',
+ unhealthy = 'error',
+ unknow = 'warning'
}
-export enum CurrState {
- Installed,
- Started,
- Maintained,
- Uninstalled,
- Stopped
+export enum CommonStatusTexts {
+ 'healthy',
+ 'unhealthy',
+ 'unknow'
+}
+
+export enum JobState {
+ 'Pending' = 'pending',
+ 'Processing' = 'processing',
+ 'Successful' = 'successful',
+ 'Failed' = 'failed',
+ 'Canceled' = 'canceled'
}
diff --git a/bigtop-manager-ui/src/layouts/sider.vue
b/bigtop-manager-ui/src/layouts/sider.vue
index 363f10d5..bdb988b0 100644
--- a/bigtop-manager-ui/src/layouts/sider.vue
+++ b/bigtop-manager-ui/src/layouts/sider.vue
@@ -23,6 +23,7 @@
import { RouteExceptions } from '@/enums'
import { useMenuStore } from '@/store/menu'
import type { MenuItem } from '@/store/menu/types'
+ import type { ClusterStatusType } from '@/api/cluster/types'
interface Props {
siderMenuSelectedKey: string
@@ -34,13 +35,11 @@
siderMenus: () => []
})
- type ClusterType = 1 | 2 | 3
-
const { siderMenuSelectedKey, siderMenus } = toRefs(props)
const router = useRouter()
const menuStore = useMenuStore()
const emits = defineEmits(['onSiderClick'])
- const clusterStatus = ref<Record<ClusterType, string>>({
+ const clusterStatus = ref<Record<ClusterStatusType, string>>({
1: 'success',
2: 'error',
3: 'warning'
@@ -83,7 +82,7 @@
<div
style="height: 10px; margin-inline: 7px; display: flex;
justify-content: center; align-items: flex-end"
>
- <status-dot :size="8" :color="clusterStatus[child.status as
ClusterType] as any" />
+ <status-dot :size="8" :color="clusterStatus[child.status as
ClusterStatusType] as any" />
</div>
</template>
<div>
diff --git a/bigtop-manager-ui/src/locales/en_US/cluster.ts
b/bigtop-manager-ui/src/locales/en_US/cluster.ts
index 879cfa11..a05ff116 100644
--- a/bigtop-manager-ui/src/locales/en_US/cluster.ts
+++ b/bigtop-manager-ui/src/locales/en_US/cluster.ts
@@ -17,23 +17,23 @@
* under the License.
*/
export default {
- base_info: 'Basic information',
- cluster_config: 'Cluster configuration',
+ base_info: 'Basic Information',
+ cluster_config: 'Cluster Configuration',
cluster_management: 'Cluster',
component_info: 'Stack',
host_config: 'Hosts',
create: 'Create',
name: 'Name',
- display_name: 'Display name',
+ display_name: 'Display Name',
description: 'Description',
- root_directory: 'Root directory',
- user_group: 'User group',
- config_source: 'Configuration source',
- install_dependencies: 'Install dependencies',
- add_host: 'Add host',
- edit_host: 'Edit host',
- view_log: 'View log',
+ root_directory: 'Root Directory',
+ user_group: 'User Group',
+ config_source: 'Configuration Source',
+ install_dependencies: 'Install Dependencies',
+ add_host: 'Add Host',
+ edit_host: 'Edit Host',
+ view_log: 'View Log',
source: 'Source',
- show_hosts_resolved: 'Hostnames after resolved',
+ show_hosts_resolved: 'Resolved Hostnames',
cluster_unavailable_message: 'Sorry, you need to create cluster to use this
feature.'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/common.ts
b/bigtop-manager-ui/src/locales/en_US/common.ts
index 47650f45..86a78908 100644
--- a/bigtop-manager-ui/src/locales/en_US/common.ts
+++ b/bigtop-manager-ui/src/locales/en_US/common.ts
@@ -33,7 +33,7 @@ export default {
rename: 'Rename',
delete: 'Delete',
remove: 'Remove',
- bulk_remove: 'Bulk remove',
+ bulk_remove: 'Bulk Remove',
disable: 'Disable',
enable: 'Enable',
submit: 'Submit',
@@ -45,28 +45,28 @@ export default {
installing: 'Installing...',
finish: 'Finish',
more: 'More',
- view_all: 'View all',
+ view_all: 'View All',
select_tips: 'Please select',
name: 'Name',
version: 'Version',
desc: 'Description',
os: 'OS',
arch: 'Arch',
- base_url: 'Base URL',
+ base_url: 'Address',
+ cluster: 'Cluster',
host: 'Host',
service: 'Service',
stack: 'Stack',
stage: 'Stage',
search: 'Search',
reset: 'Reset',
- restart: 'Restart {0}',
required: 'Required',
- not_required: 'Not required',
+ not_required: 'Not Required',
progress: 'Progress',
status_change_success: 'Status changed successfully',
- test_success: 'Test successful !',
- created: 'Created successfully !',
- update_success: 'Update successfully !',
+ test_success: 'Test successful!',
+ created: 'Created!',
+ update_success: 'Updated!',
update_fail: 'Update fail',
error_unknown: 'Unknown error',
error_network: 'Network error',
@@ -77,9 +77,10 @@ export default {
update_time: 'Update Time',
notification: 'Notification',
copy: 'Copy',
- copy_success: 'Copy successfully !',
+ copied: 'Copied!',
+ download: 'Download',
copy_fail: 'Copy failed!',
- delete_success: 'Delete successfully !',
+ delete_success: 'Deleted!',
delete_fail: 'Delete failed!',
delete_confirm_title: 'Delete Confirm',
delete_confirm_content: 'Delete will not be recoverable, are you sure you
want to delete {0} ?',
@@ -96,5 +97,14 @@ export default {
file_size_error: 'File size cannot exceed 10KB!',
upload_success: 'File uploaded successfully!',
upload_failed: 'File upload failed!',
- password_not_match: 'Passwords do not match.'
+ password_not_match: 'Passwords do not match.',
+ overview: 'Overview',
+ user: 'User',
+ job: 'Job',
+ start: 'Start {0}',
+ restart: 'Restart {0}',
+ stop: 'Stop {0}',
+ add: 'Add {0}',
+ more_operations: 'More Operations',
+ ok: 'Ok'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/host.ts
b/bigtop-manager-ui/src/locales/en_US/host.ts
index 9eb41395..22ae699b 100644
--- a/bigtop-manager-ui/src/locales/en_US/host.ts
+++ b/bigtop-manager-ui/src/locales/en_US/host.ts
@@ -33,10 +33,14 @@ export default {
auth_method: 'Authentication Method',
password_auth: 'Password',
key_auth: 'Key',
+ ip_address: 'IP Address',
+ component_count: 'Component Count',
no_auth: 'No Authentication',
agent_path: 'Agent Path',
ssh_port: 'SSH Port',
grpc_port: 'GRPC Port',
description: 'Description',
- key_password_not_match: 'Key Passwords do not match.'
+ key_password_not_match: 'Key Passwords do not match.',
+ default_agent_path: 'Agent application installed path, default to /opt',
+ default_grpc_port: 'Agent application grpc port, default to 8835'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/index.ts
b/bigtop-manager-ui/src/locales/en_US/index.ts
index 5f6166be..bc44b73b 100644
--- a/bigtop-manager-ui/src/locales/en_US/index.ts
+++ b/bigtop-manager-ui/src/locales/en_US/index.ts
@@ -26,6 +26,8 @@ import aiAssistant from '@/locales/en_US/ai-assistant.ts'
import cluster from '@/locales/en_US/cluster.ts'
import host from '@/locales/en_US/host.ts'
import job from '@/locales/en_US/job.ts'
+import overview from '@/locales/en_US/overview'
+import service from '@/locales/en_US/service'
export default {
common,
@@ -36,5 +38,7 @@ export default {
aiAssistant,
cluster,
host,
- job
+ job,
+ overview,
+ service
}
diff --git a/bigtop-manager-ui/src/locales/en_US/job.ts
b/bigtop-manager-ui/src/locales/en_US/job.ts
index 95caf7b8..3d015fda 100644
--- a/bigtop-manager-ui/src/locales/en_US/job.ts
+++ b/bigtop-manager-ui/src/locales/en_US/job.ts
@@ -18,5 +18,7 @@
*/
export default {
- job_list: 'Job List'
+ job_list: 'Job List',
+ task_log: 'Task Log',
+ log_loading: 'Loading logs'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/llm-config.ts
b/bigtop-manager-ui/src/locales/en_US/llm-config.ts
index c23e35f1..f128d116 100644
--- a/bigtop-manager-ui/src/locales/en_US/llm-config.ts
+++ b/bigtop-manager-ui/src/locales/en_US/llm-config.ts
@@ -17,8 +17,8 @@
* under the License.
*/
export default {
- add_authorization: 'Add authorization',
- edit_authorization: 'Edit authorization',
+ add_authorization: 'Add Authorization',
+ edit_authorization: 'Edit Authorization',
test: 'Test',
unavailable: 'Unavailable',
active: 'Active',
diff --git a/bigtop-manager-ui/src/locales/zh_CN/job.ts
b/bigtop-manager-ui/src/locales/en_US/overview.ts
similarity index 62%
copy from bigtop-manager-ui/src/locales/zh_CN/job.ts
copy to bigtop-manager-ui/src/locales/en_US/overview.ts
index 06cd7807..fde5bedc 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/job.ts
+++ b/bigtop-manager-ui/src/locales/en_US/overview.ts
@@ -16,7 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-
export default {
- job_list: '作业列表'
+ basic_info: 'Basic Information',
+ service_info: 'Service Information',
+ detail: 'Details',
+ cluster_status: 'Status',
+ cluster_name: 'Name',
+ cluster_desc: 'Description',
+ host_count: 'Host Count',
+ service_count: 'Service Count',
+ memory: 'Memory',
+ chart: 'Chart',
+ core_count: 'Processor Count',
+ disk_size: 'Disk Size',
+ creator: 'Creator',
+ memory_usage: 'Memory Usage',
+ cpu_usage: 'Cpu Usage',
+ unit_host: 'Hosts',
+ unit_service: 'Services',
+ unit_processor: 'Processors'
}
diff --git a/bigtop-manager-ui/src/api/sse/types.ts
b/bigtop-manager-ui/src/locales/en_US/service.ts
similarity index 83%
rename from bigtop-manager-ui/src/api/sse/types.ts
rename to bigtop-manager-ui/src/locales/en_US/service.ts
index 8e169f04..26d0b328 100644
--- a/bigtop-manager-ui/src/api/sse/types.ts
+++ b/bigtop-manager-ui/src/locales/en_US/service.ts
@@ -16,12 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-export interface LogsRes {
- promise: Promise<any>
- cancel: () => void
-}
-export interface ChatMessagesRes {
- promise: Promise<any>
- cancel: () => void
+export default {
+ name: 'Service Name',
+ required_restart: 'Restart'
}
diff --git a/bigtop-manager-ui/src/locales/en_US/user.ts
b/bigtop-manager-ui/src/locales/en_US/user.ts
index dab2f04a..29f36bd5 100644
--- a/bigtop-manager-ui/src/locales/en_US/user.ts
+++ b/bigtop-manager-ui/src/locales/en_US/user.ts
@@ -22,6 +22,7 @@ export default {
settings: 'Settings',
logout: 'Log Out',
username: 'Username',
+ user_group: 'User Group',
nickname: 'Nickname',
user_list: 'User List',
set_nickname_valid: 'Nickname should not be empty'
diff --git a/bigtop-manager-ui/src/locales/zh_CN/common.ts
b/bigtop-manager-ui/src/locales/zh_CN/common.ts
index 30123e19..3005772f 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/common.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/common.ts
@@ -53,13 +53,12 @@ export default {
os: '系统',
arch: '架构',
base_url: '地址',
+ cluster: '集群',
host: '主机',
service: '服务',
stack: '组件栈',
stage: '阶段',
search: '搜索',
- reset: '重置',
- restart: '重启{0}',
required: '需重启',
not_required: '无需重启',
progress: '进度',
@@ -77,7 +76,8 @@ export default {
update_time: '更新时间',
notification: '通知',
copy: '复制',
- copy_success: '复制成功',
+ copied: '已复制',
+ download: '下载',
copy_fail: '复制失败',
delete_success: '删除成功',
delete_fail: '删除失败',
@@ -96,5 +96,15 @@ export default {
file_size_error: '文件大小不能超过 10KB!',
upload_success: '文件上传成功!',
upload_failed: '文件上传失败!',
- password_not_match: '密码不一致。'
+ password_not_match: '密码不一致。',
+ overview: '概览',
+ user: '用户',
+ job: '作业',
+ start: '启动{0}',
+ restart: '重启{0}',
+ stop: '停止{0}',
+ add: '添加{0}',
+ reset: '重置',
+ more_operations: '其他操作',
+ ok: '确认'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/host.ts
b/bigtop-manager-ui/src/locales/zh_CN/host.ts
index 30692cfa..51b4d662 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/host.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/host.ts
@@ -33,10 +33,14 @@ export default {
auth_method: '认证方式',
password_auth: '密码',
key_auth: '密钥',
+ ip_address: 'IP 地址',
+ component_count: '组件数',
no_auth: '无认证',
agent_path: 'Agent路径',
ssh_port: 'SSH端口',
- grpc_port: 'gRPC端口',
+ grpc_port: 'GRPC端口',
description: '备注',
- key_password_not_match: '密钥口令不一致。'
+ key_password_not_match: '密钥口令不一致。',
+ default_agent_path: '代理应用程序安装路径,默认为/opt',
+ default_grpc_port: '代理应用程序gRPC端口,默认为8835'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/index.ts
b/bigtop-manager-ui/src/locales/zh_CN/index.ts
index cbb01b6e..5aa15877 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/index.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/index.ts
@@ -26,6 +26,8 @@ import aiAssistant from '@/locales/zh_CN/ai-assistant.ts'
import cluster from '@/locales/zh_CN/cluster.ts'
import host from '@/locales/zh_CN/host.ts'
import job from '@/locales/zh_CN/job.ts'
+import overview from '@/locales/zh_CN/overview.ts'
+import service from '@/locales/zh_CN/service.ts'
export default {
common,
@@ -36,5 +38,7 @@ export default {
aiAssistant,
cluster,
host,
- job
+ job,
+ overview,
+ service
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/job.ts
b/bigtop-manager-ui/src/locales/zh_CN/job.ts
index 06cd7807..a76d0677 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/job.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/job.ts
@@ -18,5 +18,7 @@
*/
export default {
- job_list: '作业列表'
+ job_list: '作业列表',
+ task_log: '任务日志',
+ log_loading: '获取日志中'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/job.ts
b/bigtop-manager-ui/src/locales/zh_CN/overview.ts
similarity index 62%
copy from bigtop-manager-ui/src/locales/zh_CN/job.ts
copy to bigtop-manager-ui/src/locales/zh_CN/overview.ts
index 06cd7807..ced7f376 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/job.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/overview.ts
@@ -16,7 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-
export default {
- job_list: '作业列表'
+ basic_info: '基本信息',
+ service_info: '服务信息',
+ detail: '详情',
+ cluster_status: '集群状态',
+ cluster_name: '集群名',
+ cluster_desc: '集群备注',
+ host_count: '主机数',
+ service_count: '服务数',
+ memory: '内存',
+ chart: '图表',
+ core_count: '核心数',
+ disk_size: '磁盘大小',
+ creator: '创建人',
+ memory_usage: '内存使用率',
+ cpu_usage: 'CPU使用率',
+ unit_host: '个主机',
+ unit_service: '个服务',
+ unit_processor: '个核心'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/job.ts
b/bigtop-manager-ui/src/locales/zh_CN/service.ts
similarity index 93%
copy from bigtop-manager-ui/src/locales/zh_CN/job.ts
copy to bigtop-manager-ui/src/locales/zh_CN/service.ts
index 06cd7807..e2f6ad20 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/job.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/service.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-
export default {
- job_list: '作业列表'
+ name: '服务名',
+ required_restart: '需要重启'
}
diff --git a/bigtop-manager-ui/src/locales/zh_CN/user.ts
b/bigtop-manager-ui/src/locales/zh_CN/user.ts
index 808f7971..1fe47abd 100644
--- a/bigtop-manager-ui/src/locales/zh_CN/user.ts
+++ b/bigtop-manager-ui/src/locales/zh_CN/user.ts
@@ -22,6 +22,7 @@ export default {
settings: '个人设置',
logout: '退出登录',
username: '用户名',
+ user_group: '用户组',
nickname: '昵称',
user_list: '用户列表',
set_nickname_valid: '昵称不能为空'
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/check-workflow.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/check-workflow.vue
index 847ef120..8bb2b193 100644
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/check-workflow.vue
+++
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/check-workflow.vue
@@ -18,10 +18,11 @@
-->
<script setup lang="ts">
- import { computed, onMounted, ref, shallowRef, toRefs } from 'vue'
- import type { JobVO, StateType } from '@/api/job/types'
+ import { computed, onMounted, reactive, ref, shallowRef, toRefs } from 'vue'
import { getJobDetails, retryJob } from '@/api/job'
import { CommandVO } from '@/api/command/types'
+ import LogsView, { type LogViewProps } from '@/components/log-view/index.vue'
+ import type { JobVO, StageVO, StateType, TaskVO } from '@/api/job/types'
const props = defineProps<{ stepData: CommandVO }>()
const emits = defineEmits(['updateData'])
@@ -29,6 +30,9 @@
const activeKey = ref<number[]>([])
const jobDetail = ref<JobVO>({})
const spinning = ref(false)
+ const logsViewState = reactive<LogViewProps>({
+ open: false
+ })
const status = shallowRef<Record<StateType, string>>({
Pending: 'installing',
Processing: 'processing',
@@ -92,6 +96,23 @@
}, interval)
}
+ const viewLogs = (stage: StageVO, task: TaskVO) => {
+ const { id: jobId } = stepData.value
+ const { id: stageId } = stage
+ const { id: taskId } = task
+ if (jobId === undefined || stageId === undefined || taskId === undefined) {
+ return
+ }
+ logsViewState.payLoad = {
+ clusterId: 0,
+ jobId,
+ stageId,
+ taskId
+ }
+ logsViewState.open = true
+ logsViewState.subTitle = task.name
+ }
+
onMounted(() => {
pollJobDetails(getJobInstanceDetails)
})
@@ -118,13 +139,22 @@
<span>{{ task.name }}</span>
<a-space :size="16">
<svg-icon :name="task.state && status[task.state]"></svg-icon>
- <a-button v-if="task.state && !['Canceled',
'Pending'].includes(task.state)" type="link">
+ <a-button
+ v-if="task.state && !['Canceled',
'Pending'].includes(task.state)"
+ type="link"
+ @click="viewLogs(stage, task)"
+ >
{{ $t('cluster.view_log') }}
</a-button>
</a-space>
</div>
</a-collapse-panel>
</a-collapse>
+ <logs-view
+ v-model:open="logsViewState.open"
+ :pay-load="logsViewState.payLoad"
+ :sub-title="logsViewState.subTitle"
+ />
</div>
</a-spin>
</template>
@@ -153,8 +183,8 @@
}
}
.stage-item {
+ margin-right: 68px;
@include flexbox($justify: space-between, $align: center);
- padding-right: 65px;
}
.task-item {
height: 45px;
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/cluster-base.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/cluster-base.vue
index 82a9d1e2..2db924d8 100644
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/cluster-base.vue
+++
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/cluster-base.vue
@@ -18,11 +18,11 @@
-->
<script setup lang="ts">
- import type { FormItemState } from '@/components/common/auto-form/types'
import { computed, ref, shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
- import type { ClusterCommandReq } from '@/api/command/types'
import { pick } from '@/utils/tools'
+ import type { ClusterCommandReq } from '@/api/command/types'
+ import type { FormItemState } from '@/components/common/auto-form/types'
type ClusterCommandReqKey = keyof ClusterCommandReq
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/component-info.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/component-info.vue
index 0f8fe394..3e6cd684 100644
---
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/component-info.vue
+++
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/component-info.vue
@@ -19,13 +19,13 @@
<script setup lang="ts">
import { computed, ref, shallowRef, watchEffect } from 'vue'
- import type { TableColumnType } from 'ant-design-vue'
- import useBaseTable from '@/composables/use-base-table'
- import SetSource from './set-source.vue'
import { useStackStore } from '@/store/stack'
import { storeToRefs } from 'pinia'
import { ServiceVO } from '@/api/service/types'
import { useI18n } from 'vue-i18n'
+ import useBaseTable from '@/composables/use-base-table'
+ import SetSource from './set-source.vue'
+ import type { TableColumnType } from 'ant-design-vue'
const { t } = useI18n()
const stackStore = useStackStore()
diff --git
a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/mock.ts
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/mock.ts
deleted file mode 100644
index 4ccb391a..00000000
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/components/mock.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export function generateTestData(count = 100) {
- const names = ['componentA', 'componentB', 'componentC', 'componentD',
'componentE']
- const versions = ['1.0.0', '2.3.1', '3.2.4', '4.0.0', '5.1.2']
- const compStacks = [
- 'React, Redux, Webpack',
- 'Vue, Vuex, Vite',
- 'Angular, RxJS, NgRx',
- 'Node.js, Express, MongoDB',
- 'Svelte, Rollup, Firebase'
- ]
- const descriptions = [
- '这是一个用于状态管理的组件',
- '这是一个用于数据展示的组件',
- '这是一个用于实时数据处理的组件',
- '这是一个后端服务组件',
- '这是一个前端开发框架'
- ]
-
- const testData = Array.from({ length: count }, (_, i) => ({
- key: (i + 1).toString(),
- name: names[Math.floor(Math.random() * names.length)],
- version: versions[Math.floor(Math.random() * versions.length)],
- compStack: compStacks[Math.floor(Math.random() * compStacks.length)],
- descrip: descriptions[Math.floor(Math.random() * descriptions.length)]
- }))
-
- return testData
-}
-
-export function generateTableHostData(numRows = 100) {
- const hostnames = ['Server-A', 'Server-B', 'Server-C', 'Server-D',
'Server-E']
- const nodeNames = ['host-A', 'host-B', 'host-C', 'host-D']
- const ipAddresses = ['192.168.1.1', '192.168.1.2', '192.168.1.3',
'192.168.1.4', '192.168.1.5']
- const systems = 'Rocky Linux 8.8'
- const architecture = 'x86_64'
- const remarks = ['Main server', 'Backup server', 'Database server', 'Web
server', 'Test server']
- const statuses = ['installing', 'success', 'error', 'unknow']
-
- const tableData = []
-
- for (let i = 0; i < numRows; i++) {
- tableData.push({
- key: i + 1,
- name: hostnames[Math.floor(Math.random() * hostnames.length)],
- system: `${systems}-${i}`,
- architecture: `${architecture}-${i}`,
- componentCount: `${i} components`,
- nodeName: nodeNames[Math.floor(Math.random() * nodeNames.length)],
- address: ipAddresses[Math.floor(Math.random() * ipAddresses.length)],
- remark: remarks[Math.floor(Math.random() * remarks.length)],
- status: statuses[Math.floor(Math.random() * statuses.length)]
- })
- }
-
- return tableData
-}
-
-export function getCheckWorkflows(stageCount: number, maxTasksPerStage:
number, maxStatus: number): Array<any> {
- const data = []
-
- for (let i = 0; i < stageCount; i++) {
- const taskCount = Math.floor(Math.random() * maxTasksPerStage) + 1
- const tasks = []
-
- for (let j = 0; j < taskCount; j++) {
- tasks.push({
- id: j,
- name: `task${j + 1}`,
- status: Math.floor(Math.random() * maxStatus) + 1
- })
- }
-
- data.push({
- id: i,
- name: `stage${i + 1}`,
- status: Math.floor(Math.random() * maxStatus) + 1,
- tasks
- })
- }
-
- return data
-}
-
-export enum StatusColors {
- success = 'success',
- error = 'error',
- unknow = 'warning'
-}
-
-export enum StatusTexts {
- success = 'healthy',
- error = 'unhealthy',
- unknow = 'unknown'
-}
-
-export type ServiceStatus = keyof typeof StatusColors
-
-export interface ServiceItem {
- key: string | number
- serviceName: string
- version: string
- restart: boolean
- status: ServiceStatus
-}
-
-export function getServices(): ServiceItem[] {
- const statusList = ['success', 'error', 'unknow']
- const serviceNames = [
- 'GraFana',
- 'Flink',
- 'Kafka',
- 'ZooKeeper',
- 'Hadoop',
- 'Hbase',
- 'Hive',
- 'MySQL',
- 'Spark',
- 'Solr',
- 'Seatunnel',
- 'Tez',
- 'Prometheus'
- ]
- return Array.from({ length: serviceNames.length }, (_, i) => ({
- key: i,
- serviceName: serviceNames[i],
- version: `${serviceNames[i].toLowerCase()}1.0.${i}`,
- restart: Math.floor(Math.random() * 2) == 0,
- status: statusList[Math.floor(Math.random() * statusList.length)] as
ServiceStatus
- }))
-}
-
-export interface UserListItem {
- serviceName: string
- userName: string
- userGroup: string
- descrip: string
-}
-
-export function getUserList(count: number = 20): UserListItem[] {
- return Array.from({ length: count }, (_, i) => ({
- key: i,
- serviceName: `serviceName-${i}`,
- userName: `user-${i}`,
- userGroup: `userGroup-${i}`,
- descrip: 'descrip-descrip-descrip'
- }))
-}
-
-type JobStatus = 'success' | 'exception' | 'normal' | 'active'
-export interface JobListItem {
- name: string
- status: JobStatus
- progress: number
- createTime: string
- updateTime: string
-}
-
-export function getJobList(count: number = 20): JobListItem[] {
- const status = ['success', 'exception', 'normal', 'active']
- return Array.from({ length: count }, (_, i) => ({
- key: i,
- name: `name-${i}`,
- progress: Math.floor(Math.random() * 100),
- status: status[Math.floor(Math.random() * status.length)] as JobStatus,
- createTime: '2024-09-19 11:11:11',
- updateTime: '2024-09-19 11:11:11'
- }))
-}
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 3fbee40d..a9428be5 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/create.vue
@@ -29,14 +29,7 @@
import ComponentInfo from './components/component-info.vue'
import HostConfig from './components/host-config.vue'
import CheckWorkflow from './components/check-workflow.vue'
- import {
- ClusterCommandReq,
- Command,
- CommandLevel,
- HostReq,
- type CommandRequest,
- type CommandVO
- } from '@/api/command/types'
+ import { ClusterCommandReq, HostReq, type CommandRequest, type CommandVO }
from '@/api/command/types'
const { t } = useI18n()
const menuStore = useMenuStore()
@@ -45,8 +38,8 @@
const installing = ref(false)
const stepData = ref<[Partial<ClusterCommandReq>, any, HostReq[],
CommandVO]>([{}, {}, [], {}])
const commandRequest = ref<CommandRequest>({
- command: Command.Add,
- commandLevel: CommandLevel.Cluster
+ command: 'Add',
+ commandLevel: 'cluster'
})
const installStatus = shallowRef<InstalledStatusVO[]>([])
const components = shallowRef<any[]>([ClusterBase, ComponentInfo,
HostConfig, CheckWorkflow])
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 f39243de..4637b96b 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue
@@ -18,95 +18,98 @@
-->
<script setup lang="ts">
- import { TableColumnType } from 'ant-design-vue'
- import { computed, reactive, ref } from 'vue'
- import { generateTableHostData } from './components/mock'
+ import { TableColumnType, TableProps } from 'ant-design-vue'
+ import { computed, onActivated, reactive, ref, useAttrs } from 'vue'
+ import { useI18n } from 'vue-i18n'
+ import { getHosts } from '@/api/hosts'
import useBaseTable from '@/composables/use-base-table'
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'
+ import type { ClusterVO } from '@/api/cluster/types'
type Key = string | number
interface TableState {
selectedRowKeys: Key[]
searchText: string
- searchedColumn: string
+ searchedColumn: keyof HostVO
}
+ const { t } = useI18n()
+ const attrs = useAttrs() as ClusterVO
const searchInputRef = ref()
- const data = ref<any[]>(generateTableHostData(50))
+ const hostStatus = ref(['INSTALLING', 'SUCCESS', 'FAILED', 'UNKNOW'])
const state = reactive<TableState>({
searchText: '',
searchedColumn: '',
selectedRowKeys: []
})
- const columns = computed((): TableColumnType[] => [
+ const columns = computed((): TableColumnType<HostVO>[] => [
{
- title: '节点',
- dataIndex: 'nodeName',
- key: 'nodeName',
+ title: t('host.hostname'),
+ dataIndex: 'hostname',
+ key: 'hostname',
ellipsis: true,
customFilterDropdown: true,
- onFilter: (value, record) => isContain(record.nodeName, value as string),
onFilterDropdownOpenChange: (visible) =>
onFilterDropdownOpenChange(visible)
},
{
- title: 'IP地址',
- dataIndex: 'address',
- key: 'address',
+ title: t('host.ip_address'),
+ dataIndex: 'ipv4',
+ key: 'ipv4',
ellipsis: true,
customFilterDropdown: true,
- onFilter: (value, record) => isContain(record.address, value as string),
onFilterDropdownOpenChange: (visible) =>
onFilterDropdownOpenChange(visible)
},
{
- title: '系统',
- dataIndex: 'system',
+ title: t('common.os'),
+ dataIndex: 'os',
ellipsis: true
},
{
- title: '架构',
- dataIndex: 'architecture',
+ title: t('common.arch'),
+ dataIndex: 'arch',
ellipsis: true
},
{
- title: '组件数',
- dataIndex: 'componentCount',
+ title: t('host.component_count'),
+ dataIndex: 'componentNum',
ellipsis: true
},
{
- title: '状态',
+ title: t('common.status'),
dataIndex: 'status',
key: 'status',
width: '160px',
ellipsis: true,
+ filterMultiple: false,
filters: [
{
- text: '正常',
- value: 'success'
+ text: t('common.success'),
+ value: 1
},
{
- text: '异常',
- value: 'error'
+ text: t('common.failed'),
+ value: 2
},
{
- text: '未知',
- value: 'unknow'
+ text: t('common.unknow'),
+ value: 3
}
- ],
- onFilter: (value, record) => record.status.indexOf(value) === 0
+ ]
},
{
- title: '操作',
+ title: t('common.action'),
key: 'operation',
width: '160px',
fixed: 'right'
}
])
- const { loading, paginationProps, onChange } = useBaseTable({
+ const { loading, dataSource, filtersParams, paginationProps, onChange } =
useBaseTable<HostVO>({
columns: columns.value,
- rows: data.value
+ rows: []
})
const operations = computed((): GroupItem[] => [
@@ -121,10 +124,6 @@
}
])
- const isContain = (source: string, target: string) => {
- return source.toString().toLowerCase().includes(target.toLowerCase())
- }
-
const onFilterDropdownOpenChange = (visible: boolean) => {
if (visible) {
setTimeout(() => {
@@ -140,7 +139,7 @@
const handleSearch = (selectedKeys: Key[], confirm: (param?:
FilterConfirmProps) => void, dataIndex: string) => {
confirm()
state.searchText = selectedKeys[0] as string
- state.searchedColumn = dataIndex as string
+ state.searchedColumn = dataIndex
}
const handleReset = (clearFilters: (param?: FilterResetProps) => void) => {
@@ -159,12 +158,42 @@
console.log('row :>> ', row)
}
}
+
+ const getHostList = async (isReset = false) => {
+ loading.value = true
+ if (attrs.id == undefined || !paginationProps.value) {
+ loading.value = false
+ return
+ }
+ if (isReset) {
+ paginationProps.value.current = 1
+ }
+ try {
+ const res = await getHosts({ ...filtersParams.value, clusterId: attrs.id
})
+ dataSource.value = res.content
+ paginationProps.value.total = res.total
+ loading.value = false
+ } catch (error) {
+ console.log('error :>> ', error)
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const tableChange: TableProps['onChange'] = (pagination, filters, ...args)
=> {
+ onChange(pagination, filters, ...args)
+ getHostList()
+ }
+
+ onActivated(() => {
+ getHostList()
+ })
</script>
<template>
<div class="host">
<header>
- <div class="host-title">{{ $t('host.host_list') }}</div>
+ <div class="header-title">{{ $t('host.host_list') }}</div>
<a-space :size="16">
<a-button type="primary" danger @click="handleDelete">{{
$t('common.bulk_remove') }}</a-button>
<a-button type="primary">{{ $t('cluster.add_host') }}</a-button>
@@ -172,11 +201,11 @@
</header>
<a-table
:loading="loading"
- :data-source="data"
+ :data-source="dataSource"
:columns="columns"
:pagination="paginationProps"
:row-selection="{ selectedRowKeys: state.selectedRowKeys, onChange:
onSelectChange }"
- @change="onChange"
+ @change="tableChange"
>
<template #customFilterDropdown="{ setSelectedKeys, selectedKeys,
confirm, clearFilters, column }">
<div class="search">
@@ -187,8 +216,10 @@
@change="(e: any) => setSelectedKeys(e.target?.value ?
[e.target?.value] : [])"
@press-enter="handleSearch(selectedKeys, confirm,
column.dataIndex)"
/>
- <div class="search-btn">
- <a-button size="small" @click="handleReset(clearFilters)"> {{
$t('common.reset') }} </a-button>
+ <div class="search-option">
+ <a-button size="small" @click="handleReset(clearFilters)">
+ {{ $t('common.reset') }}
+ </a-button>
<a-button type="primary" size="small"
@click="handleSearch(selectedKeys, confirm, column.dataIndex)">
{{ $t('common.search') }}
</a-button>
@@ -200,11 +231,12 @@
<svg-icon v-else :name="filtered ? 'filter_activated' : 'filter'" />
</template>
<template #bodyCell="{ record, column }">
- <template v-if="column.key === 'nodeName'">
- <a-typography-link underline> {{ record.nodeName }}
</a-typography-link>
+ <template v-if="column.key === 'hostname'">
+ <a-typography-link underline> {{ record.hostname }}
</a-typography-link>
</template>
<template v-if="column.key === 'status'">
- <svg-icon :name="record.status" />
+ <svg-icon style="margin-left: 0"
:name="hostStatus[record.status].toLowerCase()" />
+ <span>{{ $t(`common.${hostStatus[record.status].toLowerCase()}`)
}}</span>
</template>
<template v-if="column.key === 'operation'">
<button-group
@@ -215,7 +247,7 @@
:args="record"
group-shape="default"
group-type="link"
- ></button-group>
+ />
</template>
</template>
</a-table>
@@ -223,23 +255,21 @@
</template>
<style lang="scss" scoped>
- .host {
- header {
- margin-bottom: $space-md;
- }
- &-title {
- font-size: 14px;
- font-weight: 500;
- line-height: 22px;
- margin-bottom: $space-md;
- }
+ header {
+ margin-bottom: $space-md;
}
.search {
display: grid;
gap: $space-sm;
padding: $space-sm;
- &-btn {
- @include flexbox($justify: flex-end, $gap: $space-md);
+ &-option {
+ width: 100%;
+ display: grid;
+ gap: $space-sm;
+ grid-template-columns: 1fr 1fr;
+ button {
+ width: 100%;
+ }
}
}
</style>
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 6017e998..71ab061b 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/index.vue
@@ -18,65 +18,76 @@
-->
<script setup lang="ts">
- import { computed, ref } from 'vue'
- import { useRoute } from 'vue-router'
- import type { GroupItem } from '@/components/common/button-group/types'
- import type { TabItem } from '@/components/common/main-card/types'
+ import { computed, onMounted, ref, shallowRef } from 'vue'
+ 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 Overview from './overview.vue'
import Service from './service.vue'
import Host from './host.vue'
import User from './user.vue'
- import Job from './job.vue'
+ import Job from '@/components/job/index.vue'
+ 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 route = useRoute()
- const title = computed(() => route.params.cluster as string)
- const desc = ref('我是描述')
+ const { t } = useI18n()
+ const clusterStore = useClusterStore()
+ const { currCluster, loading } = storeToRefs(clusterStore)
const activeKey = ref('1')
- const tabs = ref<TabItem[]>([
+ const statusColors = shallowRef<Record<ClusterStatusType, keyof typeof
CommonStatusTexts>>({
+ 1: 'healthy',
+ 2: 'unhealthy',
+ 3: 'unknow'
+ })
+ const tabs = computed((): TabItem[] => [
{
key: '1',
- title: '概览'
+ title: t('common.overview')
},
{
key: '2',
- title: '服务'
+ title: t('common.service')
},
{
key: '3',
- title: '主机'
+ title: t('common.host')
},
{
key: '4',
- title: '用户'
+ title: t('common.user')
},
{
key: '5',
- title: '作业'
+ title: t('common.job')
}
])
const actionGroup = computed<GroupItem[]>(() => [
{
shape: 'default',
type: 'primary',
- text: '添加服务',
+ text: t('common.add', [t('common.service')]),
clickEvent: () => addService && addService()
},
{
shape: 'default',
type: 'default',
- text: '其他操作',
+ text: t('common.more_operations'),
dropdownMenu: [
{
- action: 'start',
- text: '启动集群'
+ action: 'Start',
+ text: t('common.start', [t('common.cluster')])
},
{
- action: 'restart',
- text: '重启集群'
+ action: 'Restart',
+ text: t('common.restart', [t('common.cluster')])
},
{
- action: 'stop',
- text: '停止集群'
+ action: 'Stop',
+ text: t('common.stop', [t('common.cluster')])
}
],
dropdownMenuClickEvent: (info) => dropdownMenuClick &&
dropdownMenuClick(info)
@@ -88,22 +99,46 @@
return componnts[parseInt(activeKey.value) - 1]
})
- const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = ({ key }) => {
- console.log('key :>> ', key)
+ const dropdownMenuClick: GroupItem['dropdownMenuClickEvent'] = async ({ key
}) => {
+ try {
+ await execCommand({
+ command: key as keyof typeof Command,
+ clusterId: currCluster.value.id,
+ commandLevel: 'cluster'
+ })
+ clusterStore.loadClusters()
+ clusterStore.getClusterDetail()
+ } catch (error) {
+ console.log('error :>> ', error)
+ }
}
const addService: GroupItem['clickEvent'] = () => {
console.log('add :>> ')
}
+
+ onMounted(() => {
+ clusterStore.getClusterDetail()
+ })
</script>
<template>
- <header-card :title="title" avatar="cluster" :desc="desc"
:action-groups="actionGroup" />
- <main-card v-model:active-key="activeKey" :tabs="tabs">
- <template #tab-item>
- <component :is="getCompName"></component>
- </template>
- </main-card>
+ <a-spin :spinning="loading">
+ <header-card
+ :title="currCluster.displayName"
+ avatar="cluster"
+ :status="CommonStatus[statusColors[currCluster.status as
ClusterStatusType]]"
+ :desc="currCluster.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>
+ </keep-alive>
+ </template>
+ </main-card>
+ </a-spin>
</template>
<style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/cluster/job.vue
b/bigtop-manager-ui/src/pages/cluster-manage/cluster/job.vue
deleted file mode 100644
index 90a1eb21..00000000
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/job.vue
+++ /dev/null
@@ -1,94 +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 { type JobListItem, getJobList } from './components/mock'
- import { ref } from 'vue'
- import type { TableColumnType } from 'ant-design-vue'
- import useBaseTable from '@/composables/use-base-table'
-
- const columns: TableColumnType[] = [
- {
- title: '#',
- width: '48px',
- key: 'index',
- customRender: ({ index }) => {
- return `${index + 1}`
- }
- },
- {
- title: '名称',
- key: 'name',
- dataIndex: 'name',
- ellipsis: true
- },
- {
- title: '状态',
- key: 'status',
- dataIndex: 'status'
- },
- {
- title: '创建时间',
- dataIndex: 'createTime',
- ellipsis: true
- },
- {
- title: '更新时间',
- dataIndex: 'updateTime',
- ellipsis: true
- }
- ]
- const data = ref<JobListItem[]>(getJobList(50))
- const { columnsProp, dataSource, loading, paginationProps, onChange } =
useBaseTable({
- columns,
- rows: data.value
- })
-</script>
-
-<template>
- <div class="job">
- <header>
- <div class="host-title">{{ $t('job.job_list') }}</div>
- </header>
- <a-table
- :loading="loading"
- :data-source="dataSource"
- :columns="columnsProp"
- :pagination="paginationProps"
- @change="onChange"
- >
- <template #bodyCell="{ record, column }">
- <template v-if="column.key === 'name'">
- <a-typography-link underline> {{ record.name }} </a-typography-link>
- </template>
- <template v-if="column.key === 'status'">
- <a-progress :percent="record.progress" :status="record.status" />
- </template>
- </template>
- </a-table>
- </div>
-</template>
-
-<style lang="scss" scoped>
- .job {
- header {
- margin-bottom: $space-md;
- }
- }
-</style>
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 c7daf834..3ec9aed4 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/overview.vue
@@ -18,89 +18,46 @@
-->
<script setup lang="ts">
- import { ref } from 'vue'
- import type { MenuProps } from 'ant-design-vue'
+ import { computed, 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 { CommonStatus, CommonStatusTexts } from '@/enums/state'
import GaugeChart from './components/gauge-chart.vue'
import CategoryChart from './components/category-chart.vue'
-
- const baseConfig = {
- clusterStatus: '集群状态',
- clusterName: '集群名',
- clusterDesc: '集群备注',
- hostCount: '主机数',
- serviceCount: '服务数',
- memory: '内存',
- coreCount: '核心数',
- diskSize: '磁盘大小',
- creator: '创建人'
- } as any
-
- const baseInfo = {
- clusterStatus: 'succsss',
- clusterName: '集群名集群名集群名集群名集群名集群名集群名',
- clusterDesc: '集群备注',
- hostCount: '15个主机',
- serviceCount: '15个服务',
- memory: '内存',
- coreCount: '15个核心',
- diskSize: '磁盘大小',
- creator: '创建人'
- } as any
-
- const serviceStack = [
- {
- stackId: 1,
- stackName: 'Bigtop Stack',
- stackVersion: 'bigtop-3.3.0',
- services: [
- {
- id: 1,
- name: 'HDFS'
- },
- {
- id: 2,
- name: 'Hive'
- }
- ]
- },
- {
- stackId: 2,
- stackName: 'Extra Stack',
- stackVersion: 'extra-1.0.0',
- services: [
- {
- id: 1,
- name: 'Doris'
- },
- {
- id: 2,
- name: 'SeaTunnel'
- }
- ]
- }
- ]
-
- const serviceOperates = [
- {
- action: 'start',
- text: '启动服务'
- },
- {
- action: 'restart',
- text: '重启服务'
- },
- {
- action: 'stop',
- text: '停止服务'
- }
- ]
+ import type { ClusterStatusType, ClusterVO } from '@/api/cluster/types'
+ import type { MenuProps } from 'ant-design-vue'
type TimeRangeText = '1m' | '15m' | '30m' | '1h' | '6h' | '30h'
type TimeRangeItem = {
text: TimeRangeText
time: string
}
- const timeRanges: TimeRangeItem[] = [
+
+ const { t } = useI18n()
+ const attrs = useAttrs() as ClusterVO
+ const serviceStore = useServiceStore()
+ const currTimeRange = ref<TimeRangeText>('15m')
+ const chartData = ref({
+ chart1: [],
+ chart2: [],
+ chart3: [],
+ chart4: []
+ })
+ const statusColors = shallowRef<Record<ClusterStatusType, keyof typeof
CommonStatusTexts>>({
+ 1: 'healthy',
+ 2: 'unhealthy',
+ 3: 'unknow'
+ })
+ const { locateStackWithService } = storeToRefs(serviceStore)
+ const clusterDetail = computed(() => ({
+ ...attrs,
+ totalMemory: formatFromByte(attrs.totalMemory as number),
+ totalDisk: formatFromByte(attrs.totalDisk as number)
+ }))
+ const noChartData = computed(() => Object.values(chartData.value).every((v)
=> v.length === 0))
+ const timeRanges = computed((): TimeRangeItem[] => [
{
text: '1m',
time: ''
@@ -125,9 +82,43 @@
text: '30h',
time: ''
}
- ]
-
- const currTimeRange = ref<TimeRangeText>('15m')
+ ])
+ const baseConfig = computed((): Partial<Record<keyof ClusterVO, string>> => {
+ return {
+ status: t('overview.cluster_status'),
+ displayName: t('overview.cluster_name'),
+ desc: t('overview.cluster_desc'),
+ totalHost: t('overview.host_count'),
+ totalService: t('overview.service_count'),
+ totalMemory: t('overview.memory'),
+ totalProcessor: t('overview.core_count'),
+ totalDisk: t('overview.disk_size'),
+ createUser: t('overview.creator')
+ }
+ })
+ const unitOfBaseConfig = computed((): Partial<Record<keyof ClusterVO,
string>> => {
+ return {
+ totalHost: t('overview.unit_host'),
+ totalService: t('overview.unit_service'),
+ totalProcessor: t('overview.unit_processor')
+ }
+ })
+ const detailKeys = computed(() => Object.keys(baseConfig.value) as (keyof
ClusterVO)[])
+ const serviceStack = computed(() => locateStackWithService.value)
+ 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')])
+ }
+ ])
const handleServiceOperate: MenuProps['onClick'] = (item) => {
console.log('item :>> ', item.key)
@@ -142,66 +133,99 @@
<div class="dashboard">
<a-row :gutter="[50, 16]" :wrap="true">
<a-col :xs="24" :sm="24" :md="24" :lg="10" :xl="7" style="display: flex;
flex-direction: column; gap: 24px">
- <!-- base info -->
- <a-descriptions layout="vertical" bordered>
+ <div class="base-info">
+ <div class="box-title">
+ <a-typography-text strong :content="$t('overview.basic_info')" />
+ </div>
+ <div>
+ <a-descriptions layout="vertical" bordered>
+ <a-descriptions-item>
+ <template #label>
+ <div class="desc-sub-label">
+ <a-typography-text strong :content="$t('overview.detail')"
/>
+ </div>
+ </template>
+ <div class="desc-sub-item-wrp">
+ <div class="desc-sub-item">
+ <template v-for="base in detailKeys" :key="base">
+ <div class="desc-sub-item-desc">
+ <a-typography-text
+ class="desc-sub-item-desc-column"
+ type="secondary"
+ :content="baseConfig[base]"
+ />
+ <a-tag
+ v-if="base === 'status'"
+ class="reset-tag"
+
:color="CommonStatus[statusColors[clusterDetail[base] as ClusterStatusType]]"
+ >
+ <status-dot
:color="CommonStatus[statusColors[clusterDetail[base] as ClusterStatusType]]" />
+ {{
+ clusterDetail[base] &&
+ $t(`common.${statusColors[clusterDetail[base] as
ClusterStatusType]}`)
+ }}
+ </a-tag>
+ <a-typography-text
+ v-else
+ class="desc-sub-item-desc-column"
+ :content="
+ Object.keys(unitOfBaseConfig).includes(base)
+ ? `${clusterDetail[base]}
${unitOfBaseConfig[base]}`
+ : `${clusterDetail[base]}`
+ "
+ />
+ </div>
+ </template>
+ </div>
+ </div>
+ </a-descriptions-item>
+ </a-descriptions>
+ </div>
+ </div>
+ <!-- service info -->
+ <template v-if="serviceStack.length == 0">
+ <div class="service-info">
+ <div class="box-title">
+ <a-typography-text strong :content="$t('overview.service_info')"
/>
+ </div>
+ <div class="box-empty">
+ <a-empty />
+ </div>
+ </div>
+ </template>
+ <a-descriptions v-else layout="vertical" bordered :column="1">
<template #title>
- <a-typography-text strong content="基本信息" />
+ <a-typography-text strong :content="$t('overview.service_info')" />
</template>
- <a-descriptions-item>
+ <a-descriptions-item v-for="stack in serviceStack"
:key="stack.stackName">
<template #label>
<div class="desc-sub-label">
- <a-typography-text strong content="详情" />
+ <a-typography-text strong :content="stack.stackName" />
+ <a-typography-text type="secondary"
:content="stack.stackVersion" />
</div>
</template>
- <div class="desc-sub-item-wrp">
- <div class="desc-sub-item">
- <template v-for="base in Object.keys(baseInfo)" :key="base">
- <div class="desc-sub-item-desc">
- <a-typography-text type="secondary"
:content="baseConfig[base]" />
- <a-typography-text :content="baseInfo[base]" />
- </div>
+ <div v-for="service in stack.services" :key="service.id"
class="service-item">
+ <a-avatar shape="square" :size="16" />
+ <a-typography-text :content="service.displayName" />
+ <a-dropdown :trigger="['click']">
+ <a-button type="text" shape="circle" size="small">
+ <svg-icon name="more" style="margin: 0" />
+ </a-button>
+ <template #overlay>
+ <a-menu @click="handleServiceOperate">
+ <a-menu-item v-for="operate in serviceOperates"
:key="operate.action">
+ <span>{{ operate.text }}</span>
+ </a-menu-item>
+ </a-menu>
</template>
- </div>
+ </a-dropdown>
</div>
</a-descriptions-item>
</a-descriptions>
- <!-- service info -->
- <a-descriptions layout="vertical" bordered :column="1">
- <template #title>
- <a-typography-text strong content="服务信息" />
- </template>
- <template v-for="stack in serviceStack" :key="stack.stackId">
- <a-descriptions-item>
- <template #label>
- <div class="desc-sub-label">
- <a-typography-text strong :content="stack.stackName" />
- <a-typography-text type="secondary"
:content="stack.stackVersion" />
- </div>
- </template>
- <div v-for="service in stack.services" :key="service.id"
class="service-item">
- <a-avatar shape="square" :size="16" />
- <a-typography-text :content="service.name" />
-
- <a-dropdown :trigger="['click']">
- <a-button type="text" shape="circle" size="small">
- <svg-icon name="more" style="margin: 0" />
- </a-button>
- <template #overlay>
- <a-menu @click="handleServiceOperate">
- <a-menu-item v-for="operate in serviceOperates"
:key="operate.action">
- <span>{{ operate.text }}</span>
- </a-menu-item>
- </a-menu>
- </template>
- </a-dropdown>
- </div>
- </a-descriptions-item>
- </template>
- </a-descriptions>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="14" :xl="17">
- <div class="chart-title">
- <a-typography-text strong content="图表" />
+ <div class="box-title">
+ <a-typography-text strong :content="$t('overview.chart')" />
<a-space :size="12">
<div
v-for="time in timeRanges"
@@ -215,25 +239,30 @@
</div>
</a-space>
</div>
- <a-row class="chart-wrp">
+ <template v-if="noChartData">
+ <div class="box-empty">
+ <a-empty />
+ </div>
+ </template>
+ <a-row v-else class="box-content">
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="chart-item-wrp">
- <gauge-chart chart-id="chart1" title="内存使用率" />
+ <gauge-chart chart-id="chart1"
:title="$t('overview.memory_usage')" />
</div>
</a-col>
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="chart-item-wrp">
- <gauge-chart chart-id="chart2" title="CPU 使用率" />
+ <gauge-chart chart-id="chart2" :title="$t('overview.cpu_usage')"
/>
</div>
</a-col>
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="chart-item-wrp">
- <category-chart chart-id="chart4" title="CPU 使用率" />
+ <category-chart chart-id="chart4"
:title="$t('overview.cpu_usage')" />
</div>
</a-col>
<a-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="chart-item-wrp">
- <category-chart chart-id="chart3" title="内存使用率" />
+ <category-chart chart-id="chart3"
:title="$t('overview.memory_usage')" />
</div>
</a-col>
</a-row>
@@ -243,68 +272,78 @@
</template>
<style lang="scss" scoped>
- .dashboard {
- .service-item {
- display: grid;
- grid-template-columns: auto 1fr auto;
- gap: $space-md;
- align-items: center;
- padding: $space-md;
- border-bottom: 1px solid #e5e5e5;
- }
-
- .chart-title {
- display: flex;
- justify-content: space-between;
+ .box {
+ &-title {
+ @include flexbox($justify: space-between);
margin-bottom: 20px;
}
- .time-range {
- padding-inline: 6px;
- border-radius: 4px;
- text-align: center;
- cursor: pointer;
- user-select: none;
- outline: none;
- transition: background-color 0.3s;
-
- &:hover {
- color: $color-primary-text-hover;
- }
- &-activated {
- color: $color-primary-text;
- }
- }
-
- .chart-wrp {
+ &-content {
border-radius: 8px;
overflow: hidden;
box-sizing: border-box;
border: 1px solid $color-border;
}
- .chart-item-wrp {
+ &-empty {
+ @include flexbox($justify: center, $align: center);
+ min-height: 200px;
+ border-radius: 8px;
+ box-sizing: border-box;
border: 1px solid $color-border;
- margin-right: -1px;
- margin-bottom: -1px;
+ }
+ }
- &:first-child {
- border-left: 0;
- border-top: 0;
- }
+ .time-range {
+ padding-inline: 6px;
+ border-radius: 4px;
+ text-align: center;
+ cursor: pointer;
+ user-select: none;
+ outline: none;
+ transition: background-color 0.3s;
- &:not(:last-child) {
- border-right: 0;
- }
+ &:hover {
+ color: $color-primary-text-hover;
+ }
+ &-activated {
+ color: $color-primary-text;
+ }
+ }
- &:nth-child(n + 3):not(:nth-child(4)) {
- border-bottom: 0;
- border-left: 0;
- }
+ .service-item {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ gap: $space-md;
+ align-items: center;
+ padding: $space-md;
+ border-bottom: 1px solid #3b2020;
+ }
+
+ .chart-item-wrp {
+ border: 1px solid $color-border;
+ margin-right: -1px;
+ margin-bottom: -1px;
+
+ &:first-child {
+ border-left: 0;
+ border-top: 0;
+ }
+
+ &:not(:last-child) {
+ border-right: 0;
+ }
+
+ &:nth-child(n + 3):not(:nth-child(4)) {
+ border-bottom: 0;
+ border-left: 0;
}
+ }
+ .dashboard {
:deep(.ant-descriptions-view) {
overflow: hidden;
+ border-color: $color-border;
.ant-descriptions-item-label {
padding: 0;
}
@@ -314,17 +353,23 @@
}
.desc-sub-item-wrp {
- display: flex;
- gap: $space-md;
+ @include flexbox($gap: $space-md);
padding: $space-md;
+ :deep(.ant-tag) {
+ width: fit-content;
+ @include flexbox($align: center, $gap: 4px);
+ }
}
.desc-sub-item {
- @include flexbox($direction: column, $gap: $space-md);
+ display: grid;
+ grid-template-columns: max-content 1fr;
+ gap: 16px;
&-desc {
- display: grid;
- grid-template-columns: 56px auto;
- gap: 30px;
+ display: contents;
+ &-column {
+ text-align: left;
+ }
}
}
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 007a9cb2..f8062f57 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/service.vue
@@ -18,16 +18,26 @@
-->
<script setup lang="ts">
- import { computed, onMounted, ref, shallowRef } from 'vue'
- import { getServices, StatusColors, StatusTexts, type ServiceItem } from
'./components/mock'
+ import { computed, onActivated, shallowRef, toRefs, useAttrs } from 'vue'
import { usePngImage } from '@/utils/tools'
import { useI18n } from 'vue-i18n'
+ import { useServiceStore } from '@/store/service'
+ import { CommonStatus, CommonStatusTexts } from '@/enums/state'
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 } from
'@/api/service/types'
+ import type { ClusterVO } from '@/api/cluster/types'
const { t } = useI18n()
- const data = ref<ServiceItem[]>([])
+ const attrs = useAttrs() as ClusterVO
+ const serviceStore = useServiceStore()
+ const { services, loading } = toRefs(serviceStore)
+ const statusColors = shallowRef<Record<ServiceStatusType, keyof typeof
CommonStatusTexts>>({
+ 1: 'healthy',
+ 2: 'unhealthy',
+ 3: 'unknow'
+ })
const actionGroups = shallowRef<GroupItem[]>([
{
action: 'start',
@@ -63,19 +73,19 @@
{
type: 'search',
key: 'serviceName',
- label: '服务名'
+ label: t('service.name')
},
{
type: 'status',
- key: 'restart',
- label: '需要重启',
+ key: 'restartFlag',
+ label: t('service.required_restart'),
options: [
{
- label: '需重启',
+ label: t('common.required'),
value: 1
},
{
- label: '无需重启',
+ label: t('common.not_required'),
value: 2
}
]
@@ -83,73 +93,71 @@
{
type: 'status',
key: 'status',
- label: '状态',
+ label: t('common.status'),
options: [
{
- label: t(`common.${StatusTexts.success}`),
- value: StatusTexts.success
+ label: t(`common.${statusColors.value[1]}`),
+ value: 1
},
{
- label: t(`common.${StatusTexts.error}`),
- value: StatusTexts.error
+ label: t(`common.${statusColors.value[2]}`),
+ value: 2
},
{
- label: t(`common.${StatusTexts.unknow}`),
- value: StatusTexts.unknow
+ label: t(`common.${statusColors.value[3]}`),
+ value: 3
}
]
}
])
- const onFilter = (filters: any) => {
- console.log('filters :>> ', filters)
+ const getServices = (filters?: ServiceListParams) => {
+ attrs.id != undefined && serviceStore.getServices(attrs.id, filters)
}
- onMounted(() => {
- data.value = getServices()
+ onActivated(() => {
+ getServices()
})
</script>
<template>
- <div class="service">
- <filter-form :filter-items="filterFormItems" @filter="onFilter" />
- <a-card v-for="item in data" :key="item.key" :hoverable="true"
class="service-item">
- <div class="header">
- <div class="header-base-wrp">
- <a-avatar
- v-if="item.serviceName"
- :src="usePngImage(item.serviceName.toLowerCase())"
- :size="42"
- class="header-icon"
- />
- <div class="header-base-title">
- <span>{{ `${item.serviceName}` }}</span>
- <span class="small-gray">{{ item.version }}</span>
+ <a-spin :spinning="loading" class="service">
+ <filter-form :filter-items="filterFormItems" @filter="getServices" />
+ <a-empty v-if="services.length == 0" style="width: 100%" />
+ <template v-else>
+ <a-card v-for="item in services" :key="item.id" :hoverable="true"
class="service-item">
+ <div class="header">
+ <div class="header-base-wrp">
+ <a-avatar v-if="item.name"
:src="usePngImage(item.name.toLowerCase())" :size="42" class="header-icon" />
+ <div class="header-base-title">
+ <span>{{ `${item.displayName}` }}</span>
+ <span class="small-gray">{{ item.version }}</span>
+ </div>
+ <div class="header-base-status">
+ <a-tag :color="statusColors[item.status]">
+ <div class="header-base-status-inner">
+ <status-dot :color="CommonStatus[statusColors[item.status]]"
/>
+ <span class="small">{{
$t(`common.${CommonStatusTexts[item.status]}`) }}</span>
+ </div>
+ </a-tag>
+ </div>
</div>
- <div class="header-base-status">
- <a-tag :color="StatusColors[item.status]">
- <div class="header-base-status-inner">
- <status-dot :color="StatusColors[item.status]" />
- <span class="small">{{
$t(`common.${StatusTexts[item.status]}`) }}</span>
- </div>
- </a-tag>
+ <div class="header-restart-status">
+ <span class="small-gray">{{ `${$t('common.restart')}` }}</span>
+ <status-dot :color="CommonStatus[statusColors[item.status]]" />
+ <span class="small">{{ `${item.restartFlag ? $t('common.required')
: $t('common.not_required')}` }}</span>
</div>
</div>
- <div class="header-restart-status">
- <span class="small-gray">{{ `${$t('common.restart')}` }}</span>
- <status-dot :color="StatusColors[item.status]" />
- <span class="small">{{ `${item.restart ? $t('common.required') :
$t('common.not_required')}` }}</span>
+ <div class="item-content">
+ <button-group :auto="true" :space="0" :groups="actionGroups">
+ <template #icon="{ item: groupItem }">
+ <svg-icon :name="groupItem.icon || ''" />
+ </template>
+ </button-group>
</div>
- </div>
- <div class="item-content">
- <button-group :auto="true" :space="0" :groups="actionGroups">
- <template #icon="{ item: groupItem }">
- <svg-icon :name="groupItem.icon || ''" />
- </template>
- </button-group>
- </div>
- </a-card>
- </div>
+ </a-card>
+ </template>
+ </a-spin>
</template>
<style lang="scss" scoped>
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 2955093d..fc967256 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/cluster/user.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/cluster/user.vue
@@ -18,12 +18,16 @@
-->
<script setup lang="ts">
- import { type UserListItem, getUserList } from './components/mock'
- import { ref } from 'vue'
- import type { TableColumnType } from 'ant-design-vue'
+ 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 columns: TableColumnType[] = [
+ const { t } = useI18n()
+ const attrs = useAttrs() as ClusterVO
+ const columns = computed((): TableColumnType[] => [
{
title: '#',
width: '48px',
@@ -33,55 +37,68 @@
}
},
{
- title: '服务名',
- dataIndex: 'serviceName',
+ title: t('service.name'),
+ dataIndex: 'displayName',
width: '20%',
ellipsis: true
},
{
- title: '用户名',
- dataIndex: 'userName',
+ title: t('user.username'),
+ dataIndex: 'user',
width: '15%',
ellipsis: true
},
{
- title: '用户组',
+ title: t('user.user_group'),
dataIndex: 'userGroup',
width: '20%',
ellipsis: true
},
{
- title: '描述',
- dataIndex: 'descrip',
+ title: t('common.desc'),
+ dataIndex: 'desc',
ellipsis: true
}
- ]
- const data = ref<UserListItem[]>(getUserList(50))
- const { columnsProp, dataSource, loading, paginationProps, onChange } =
useBaseTable({
- columns,
- rows: data.value
+ ])
+ const { dataSource, loading, filtersParams, paginationProps, onChange } =
useBaseTable<ServiceUserVO>({
+ columns: columns.value,
+ rows: []
+ })
+
+ const loadUserListOfService = async () => {
+ if (attrs.id == undefined || !paginationProps.value) {
+ loading.value = false
+ return
+ }
+ try {
+ const data = await getUserListOfService(attrs.id, filtersParams.value)
+ dataSource.value = data.content
+ paginationProps.value.total = data.total
+ } catch (error) {
+ console.log('error :>> ', error)
+ } finally {
+ loading.value = false
+ }
+ }
+
+ onActivated(() => {
+ loadUserListOfService()
})
</script>
<template>
<div class="user">
<header>
- <div class="host-title">{{ $t('user.user_list') }}</div>
+ <div class="header-title">{{ $t('user.user_list') }}</div>
</header>
<a-table
:loading="loading"
:data-source="dataSource"
- :columns="columnsProp"
+ :columns="columns"
:pagination="paginationProps"
@change="onChange"
></a-table>
</div>
</template>
-<style lang="scss" scoped>
- .user {
- header {
- margin-bottom: $space-md;
- }
- }
-</style>
+<style lang="scss" scoped></style>
diff --git a/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
b/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
index 85ed4a0a..31b357be 100644
--- a/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
+++ b/bigtop-manager-ui/src/pages/cluster-manage/hosts/create.vue
@@ -249,14 +249,13 @@
{
type: 'input',
field: 'agentDir',
- slot: 'agentDirSlot',
formItemProps: {
name: 'agentDir',
label: t('host.agent_path')
},
controlProps: {
disabled: isEdit.value,
- placeholder: t('common.enter_error',
[`${t('host.agent_path')}`.toLowerCase()])
+ placeholder: t('host.default_agent_path')
}
},
{
@@ -273,13 +272,12 @@
{
type: 'input',
field: 'grpcPort',
- slot: 'grpcPortSlot',
formItemProps: {
name: 'grpcPort',
label: t('host.grpc_port')
},
controlProps: {
- placeholder: t('common.enter_error',
[`${t('host.grpc_port')}`.toLowerCase()])
+ placeholder: t('host.default_grpc_port')
}
},
{
@@ -506,7 +504,7 @@
</a-upload>
</a-form-item>
</template>
- <template #agentDirSlot="{ item, state }">
+ <!-- <template #agentDirSlot="{ item, state }">
<a-form-item>
<template #label>
<div class="question">
@@ -518,8 +516,8 @@
</template>
<a-input v-bind="item.controlProps"
v-model:value="state[item.field]" />
</a-form-item>
- </template>
- <template #grpcPortSlot="{ item, state }">
+ </template> -->
+ <!-- <template #grpcPortSlot="{ item, state }">
<a-form-item>
<template #label>
<div class="question">
@@ -531,7 +529,7 @@
</template>
<a-input v-bind="item.controlProps"
v-model:value="state[item.field]" />
</a-form-item>
- </template>
+ </template> -->
</auto-form>
<template #footer>
<footer>
@@ -555,7 +553,6 @@
}
footer {
width: 100%;
- display: flex;
- justify-content: flex-end;
+ @include flexbox($justify: flex-end);
}
</style>
diff --git a/bigtop-manager-ui/src/store/cluster/index.ts
b/bigtop-manager-ui/src/store/cluster/index.ts
index 5c8459c6..f994d139 100644
--- a/bigtop-manager-ui/src/store/cluster/index.ts
+++ b/bigtop-manager-ui/src/store/cluster/index.ts
@@ -17,49 +17,65 @@
* under the License.
*/
+import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
-import { ClusterVO } from '@/api/cluster/types.ts'
-import { ref } from 'vue'
-import { getClusterList } from '@/api/cluster'
+import { useRoute } from 'vue-router'
+import { getCluster, getClusterList } from '@/api/cluster'
+import { useServiceStore } from '@/store/service'
+import type { ClusterVO } from '@/api/cluster/types.ts'
export const useClusterStore = defineStore(
'cluster',
() => {
+ const route = useRoute()
+ const serviceStore = useServiceStore()
const clusters = ref<ClusterVO[]>([])
- const count = ref(0)
+ const loading = ref(false)
+ const currCluster = ref<ClusterVO>({})
+ const clusterId = computed(() => (route.params.id as string) || undefined)
const addCluster = async () => {
- count.value = count.value + 1
await loadClusters()
}
const delCluster = async () => {
- if (count.value < 0) {
- count.value = 0
+ await loadClusters()
+ }
+
+ const getClusterDetail = async () => {
+ if (clusterId.value == undefined) {
return
}
- count.value = count.value - 1
- await loadClusters()
+ try {
+ loading.value = true
+ currCluster.value = await getCluster(parseInt(clusterId.value))
+ currCluster.value.id != undefined &&
serviceStore.getServices(currCluster.value.id)
+ } catch (error) {
+ console.log('error :>> ', error)
+ } finally {
+ loading.value = false
+ }
}
const loadClusters = async () => {
- const data = await getClusterList()
- clusters.value = data as ClusterVO[]
+ clusters.value = await getClusterList()
}
return {
clusters,
- count,
+ loading,
+ currCluster,
addCluster,
delCluster,
- loadClusters
+ loadClusters,
+ getClusterDetail
}
},
{
persist: false
// persist: {
// storage: sessionStorage,
- // paths: ['clusters', 'count']
+ // paths: ['clusters',]
// }
}
)
diff --git a/bigtop-manager-ui/src/store/service/index.ts
b/bigtop-manager-ui/src/store/service/index.ts
new file mode 100644
index 00000000..330903f3
--- /dev/null
+++ b/bigtop-manager-ui/src/store/service/index.ts
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { defineStore, storeToRefs } from 'pinia'
+import { computed, ref } from 'vue'
+import { getServiceList } from '@/api/service'
+import { useStackStore } from '@/store/stack/index'
+import type { ServiceListParams, ServiceVO } from '@/api/service/types'
+
+export const useServiceStore = defineStore(
+ 'service',
+ () => {
+ const stackStore = useStackStore()
+ const services = ref<ServiceVO[]>([])
+ const total = ref(0)
+ const loading = ref(false)
+ const { stacks } = storeToRefs(stackStore)
+
+ const serviceNames = computed(() => services.value.map((v) => v.name))
+ const locateStackWithService = computed(() =>
+ stacks.value.filter((item) =>
+ item.services.some((service) => service.name &&
serviceNames.value.includes(service.name))
+ )
+ )
+
+ const getServices = async (clusterId: number, filterParams?:
ServiceListParams) => {
+ try {
+ loading.value = true
+ const data = await getServiceList(clusterId, { ...filterParams,
pageNum: 1, pageSize: 50 })
+ services.value = data.content
+ total.value = data.total
+ } catch (error) {
+ console.log('error :>> ', error)
+ } finally {
+ loading.value = false
+ }
+ }
+
+ return {
+ services,
+ loading,
+ getServices,
+ locateStackWithService
+ }
+ },
+ {
+ persist: false
+ }
+)
diff --git a/bigtop-manager-ui/src/styles/index.scss
b/bigtop-manager-ui/src/styles/index.scss
index f2bf2d1f..6689600f 100644
--- a/bigtop-manager-ui/src/styles/index.scss
+++ b/bigtop-manager-ui/src/styles/index.scss
@@ -54,3 +54,9 @@
:where(.ant-modal-header) {
margin-bottom: 16px !important;
}
+
+
+.header-title {
+ font-weight: 500;
+ margin-bottom: $space-md;
+}
diff --git a/bigtop-manager-ui/src/utils/tools.ts
b/bigtop-manager-ui/src/utils/tools.ts
index 9092937a..53c8f059 100644
--- a/bigtop-manager-ui/src/utils/tools.ts
+++ b/bigtop-manager-ui/src/utils/tools.ts
@@ -93,3 +93,23 @@ export const pick = <T extends object, K extends keyof
T>(obj: T, keys: K[]): Pi
{} as Pick<T, K>
)
}
+
+type Data = { [key: string]: any }
+type Result = { [key: string]: any | undefined }
+
+export const processData = (data: Data): Result => {
+ const result: Result = {}
+ if (!data) {
+ return result
+ }
+ for (const [key, value] of Object.entries(data)) {
+ if (value === null) {
+ result[key] = undefined
+ } else if (Array.isArray(value)) {
+ result[key] = value[0] || undefined
+ } else {
+ result[key] = value
+ }
+ }
+ return result
+}
diff --git a/bigtop-manager-ui/tests/__composables__/use-table.test.ts
b/bigtop-manager-ui/tests/__composables__/use-table.test.ts
index 006a537f..60a76270 100644
--- a/bigtop-manager-ui/tests/__composables__/use-table.test.ts
+++ b/bigtop-manager-ui/tests/__composables__/use-table.test.ts
@@ -71,7 +71,6 @@ describe('useBaseTable', () => {
}
expect(result.paginationProps.value).toEqual(expect.objectContaining(expectedPaginationProps))
// Test showTotal function separately
- console.log('result.paginationProps.value.showTotal(10) :>> ',
result.paginationProps.value.showTotal(10))
expect(result.paginationProps.value.showTotal(10)).toBe('Total: 10')
})