You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celeborn.apache.org by ni...@apache.org on 2024/03/13 12:17:37 UTC
(incubator-celeborn) branch main updated: [CELEBORN-1375] Introduce worker module for dashboard frontend
This is an automated email from the ASF dual-hosted git repository.
nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-celeborn.git
The following commit(s) were added to refs/heads/main by this push:
new 492ebde22 [CELEBORN-1375] Introduce worker module for dashboard frontend
492ebde22 is described below
commit 492ebde2292eb7023e13b22084f200fea8235125
Author: tiny-dust <id...@foxmail.com>
AuthorDate: Wed Mar 13 20:17:26 2024 +0800
[CELEBORN-1375] Introduce worker module for dashboard frontend
### What changes were proposed in this pull request?
Introduce worker module for dashboard frontend:

### Why are the changes needed?
The worker page provides Celeborn with worker lists and overview information.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Local test.
Closes #2382 from tiny-dust/CELEBORN-1307.
Authored-by: tiny-dust <id...@foxmail.com>
Signed-off-by: SteNicholas <pr...@163.com>
---
web/.vscode/extensions.json | 1 -
web/src/api/models/worker/index.ts | 7 +-
web/src/api/models/worker/types/worker.ts | 14 +++
web/src/api/types.ts | 5 -
web/src/assets/styles/index.scss | 4 +
web/src/{api/types.ts => composables/form.ts} | 30 +++---
web/src/composables/index.ts | 1 +
web/src/composables/pagination.ts | 102 ++++++++++----------
.../components/ApplicationSearchForm.vue | 21 ++---
web/src/views/application/index.vue | 14 +--
web/src/views/overview/index.vue | 2 +-
.../components/WorkerForm.vue} | 42 +++------
web/src/views/worker/components/WorkerTable.vue | 103 +++++++++++++++++++++
.../worker/components}/index.ts | 5 +-
web/src/views/worker/index.vue | 32 ++++++-
15 files changed, 254 insertions(+), 129 deletions(-)
diff --git a/web/.vscode/extensions.json b/web/.vscode/extensions.json
index 009a53485..93ea3e783 100644
--- a/web/.vscode/extensions.json
+++ b/web/.vscode/extensions.json
@@ -1,7 +1,6 @@
{
"recommendations": [
"Vue.volar",
- "Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
diff --git a/web/src/api/models/worker/index.ts b/web/src/api/models/worker/index.ts
index a0d542707..0934472f3 100644
--- a/web/src/api/models/worker/index.ts
+++ b/web/src/api/models/worker/index.ts
@@ -16,10 +16,15 @@
*/
import { api } from '@/api/request'
-import type { WorkerOverview } from './types'
+import type { PaginationType } from '@/api/types'
+import type { Worker, WorkerOverview } from './types'
export * from './types'
export const getWorkerOverview = () => {
return api<WorkerOverview>('/worker/overview', 'get')
}
+
+export const getWorkerList = () => {
+ return api<PaginationType<{ workerInfos: Worker[] }>>('/worker/list', 'get')
+}
diff --git a/web/src/api/models/worker/types/worker.ts b/web/src/api/models/worker/types/worker.ts
index 7e07c6319..59e593e06 100644
--- a/web/src/api/models/worker/types/worker.ts
+++ b/web/src/api/models/worker/types/worker.ts
@@ -21,3 +21,17 @@ export interface WorkerOverview {
lostNum: number
shutdownNum: number
}
+
+export interface Worker {
+ hostname: string
+ ip: string
+ state: string
+ startTime: string
+ heartbeatTime: string
+ rpcPort: number
+ restPort: number
+ fetchPort: number
+ pushPort: number
+ replicatePort: number
+ version: string
+}
diff --git a/web/src/api/types.ts b/web/src/api/types.ts
index d53e7579b..d7b0b194d 100644
--- a/web/src/api/types.ts
+++ b/web/src/api/types.ts
@@ -27,8 +27,3 @@ export type Options<V, R, P> = Partial<UseAxleOptions<V, R, P>>
export type PaginationType<T> = T & {
totalCount?: number
}
-
-export interface Page {
- pageSize: number
- pageNum: number
-}
diff --git a/web/src/assets/styles/index.scss b/web/src/assets/styles/index.scss
index 1d0fa17ac..423a15118 100644
--- a/web/src/assets/styles/index.scss
+++ b/web/src/assets/styles/index.scss
@@ -23,3 +23,7 @@ body,
width: 100vw;
height: 100vh;
}
+
+.n-descriptions-table {
+ table-layout: fixed;
+}
diff --git a/web/src/api/types.ts b/web/src/composables/form.ts
similarity index 65%
copy from web/src/api/types.ts
copy to web/src/composables/form.ts
index d53e7579b..c51ae4e4c 100644
--- a/web/src/api/types.ts
+++ b/web/src/composables/form.ts
@@ -15,20 +15,24 @@
* limitations under the License.
*/
-import type { UseAxleOptions } from '@varlet/axle/use'
-export interface Response<T> {
- data: T
- status: number
- statusText: string
-}
+import { getCurrentInstance, unref } from 'vue'
-export type Options<V, R, P> = Partial<UseAxleOptions<V, R, P>>
+export const useFormEvent = (formData: Record<string, any>) => {
+ const instance = getCurrentInstance()
-export type PaginationType<T> = T & {
- totalCount?: number
-}
+ const doSearch = () => {
+ instance?.emit('search', formData)
+ }
+
+ const resetSearch = () => {
+ Object.keys(formData).forEach((key) => {
+ formData[key] = ''
+ })
+ instance?.emit('reset', unref(formData))
+ }
-export interface Page {
- pageSize: number
- pageNum: number
+ return {
+ doSearch,
+ resetSearch
+ }
}
diff --git a/web/src/composables/index.ts b/web/src/composables/index.ts
index ef3072dbf..ef61d8094 100644
--- a/web/src/composables/index.ts
+++ b/web/src/composables/index.ts
@@ -15,4 +15,5 @@
* limitations under the License.
*/
+export * from './form'
export * from './pagination'
diff --git a/web/src/composables/pagination.ts b/web/src/composables/pagination.ts
index 5e7abbaac..2719f5923 100644
--- a/web/src/composables/pagination.ts
+++ b/web/src/composables/pagination.ts
@@ -16,91 +16,85 @@
*/
import type { PaginationProps } from 'naive-ui'
-import type { PaginationType } from '@/api/types'
-export interface UsePaginationOptions<T> {
- pageNum?: number
+export interface UsePaginationOptions {
+ currentPage?: number
pageSize?: number
- pageSizes?: number[]
- pageCount?: number
- totalCount?: number
- onLoadData?(params?: Record<string, any>): Promise<PaginationType<T>>
+ onLoadData?: Function
}
-export type SearchForm = Record<string, string>
-
-export function usePagination<T>(options: UsePaginationOptions<T> = {}) {
+export function usePagination(options: UsePaginationOptions) {
const {
- pageNum: defaultPageNum = 1,
+ currentPage: defaultCurrentPage = 1,
pageSize: defaultPageSize = 30,
- pageSizes: defaultPageSizes = [30, 50, 100],
- pageCount: defaultPageCount = 0,
- totalCount: defaultTotalCount = 0,
onLoadData
} = options
- const searchParams = ref<SearchForm | undefined>({})
- const pageNum = ref(defaultPageNum)
+ const pageSizes = [30, 50, 100]
+ const currentPage = ref(defaultCurrentPage)
const pageSize = ref(defaultPageSize)
- const pageSizes = ref(defaultPageSizes)
- const pageCount = ref(defaultPageCount)
- const totalCount = ref(defaultTotalCount)
-
- function mergeParams(searchForm?: SearchForm) {
- searchParams.value = searchForm
- return { params: { pageNum: pageNum.value, pageSize: pageSize.value, ...(searchForm || {}) } }
- }
+ const itemCount = ref(0)
+ const searchParams = ref({})
- async function updatePaginationState(newSearch?: SearchForm) {
- const { totalCount: total } = await onLoadData!(mergeParams(newSearch))
- totalCount.value = total || 0
- pageCount.value = Math.ceil(totalCount.value / pageSize.value)
+ function mergeParams(formData?: Record<string, string>) {
+ return {
+ ...formData,
+ pageNum: currentPage.value,
+ pageSize: pageSize.value
+ }
}
- function onUpdatePageChange(value: number) {
- pageNum.value = value
- updatePaginationState(searchParams.value)
+ function onUpdatePage(value: number) {
+ currentPage.value = value
+ loadData()
}
- function onUpdateSizeChange(value: number) {
+ function onUpdatePageSize(value: number) {
+ currentPage.value = 1
pageSize.value = value
- updatePaginationState(searchParams.value)
+ loadData()
}
- async function reset(newSearch?: SearchForm) {
- pageNum.value = defaultPageNum
+ function resetSearch(formData: Record<string, string>) {
+ currentPage.value = defaultCurrentPage
pageSize.value = defaultPageSize
- pageCount.value = defaultPageCount
- await updatePaginationState(newSearch)
+ itemCount.value = 0
+
+ searchParams.value = formData
+ loadData()
}
- async function search(newSearch?: SearchForm) {
- pageNum.value = 1
- await updatePaginationState(newSearch)
+ function doSearch(formData: Record<string, string>) {
+ currentPage.value = 1
+ searchParams.value = formData
+ loadData()
+ }
+
+ async function loadData() {
+ const params = mergeParams(searchParams.value)
+ const data = await onLoadData!({ params })
+ itemCount.value = data.totalCount
}
onMounted(() => {
- search()
+ loadData()
})
return {
- searchParams,
- pageNum,
+ currentPage,
pageSize,
- pageSizes,
- pageCount,
- totalCount,
- search,
- reset,
+ itemCount,
+ doSearch,
+ resetSearch,
pagination: computed<PaginationProps>(() => {
return {
- page: pageNum.value,
- pageSize: pageSize.value,
- pageSizes: pageSizes.value,
- pageCount: pageCount.value,
+ pageSizes,
showSizePicker: true,
- onChange: onUpdatePageChange,
- onPageSizeChange: onUpdateSizeChange
+ page: currentPage.value,
+ pageSize: pageSize.value,
+ itemCount: itemCount.value,
+ onChange: onUpdatePage,
+ onUpdatePageSize
}
})
}
diff --git a/web/src/views/application/components/ApplicationSearchForm.vue b/web/src/views/application/components/ApplicationSearchForm.vue
index 950c9adf2..6de56434b 100644
--- a/web/src/views/application/components/ApplicationSearchForm.vue
+++ b/web/src/views/application/components/ApplicationSearchForm.vue
@@ -17,24 +17,19 @@
-->
<script setup lang="ts">
-import type { ApplicationSearchModel } from '@/api/models/application/types'
+import { useFormEvent } from '@/composables'
defineOptions({
name: 'ApplicationSearchForm'
})
-const searchForm = ref<ApplicationSearchModel>({})
-
-const $emits = defineEmits(['do-search', 'do-reset'])
-
-const doSearch = () => {
- $emits('do-search', unref(searchForm))
-}
+const searchForm = reactive({
+ appId: '',
+ subUser: '',
+ tenant: ''
+})
-const resetSearch = () => {
- searchForm.value.appId = searchForm.value.subUser = searchForm.value.tenant = ''
- $emits('do-reset', unref(searchForm))
-}
+const { doSearch, resetSearch } = useFormEvent(searchForm)
</script>
<template>
@@ -44,7 +39,7 @@ const resetSearch = () => {
label-placement="left"
@keydown.enter="doSearch"
>
- <n-grid :x-gap="24" :y-gap="24" :cols="4">
+ <n-grid :x-gap="24" :y-gap="24" :cols="6">
<n-form-item-gi label="AppId" path="appId">
<n-input v-model:value="searchForm.appId" placeholder="" clearable />
</n-form-item-gi>
diff --git a/web/src/views/application/index.vue b/web/src/views/application/index.vue
index 10d745ec0..3e24d2996 100644
--- a/web/src/views/application/index.vue
+++ b/web/src/views/application/index.vue
@@ -17,15 +17,15 @@
-->
<script setup lang="ts">
-import { getApplicationOverview, getApplicationList } from '@/api'
-import { useHasLoading } from '@varlet/axle/use'
-import { ApplicationOverviewService } from '@/views/overview/components'
+import { getApplicationList, getApplicationOverview } from '@/api'
+import type { Application } from '@/api/models/application/types'
+import { usePagination } from '@/composables'
import {
ApplicationListService,
ApplicationSearchFormService
} from '@/views/application/components'
-import { usePagination } from '@/composables'
-import type { Application } from '@/api/models/application/types'
+import { ApplicationOverviewService } from '@/views/overview/components'
+import { useHasLoading } from '@varlet/axle/use'
defineOptions({
name: 'ApplicationView'
@@ -40,7 +40,7 @@ const { data: appOverview, loading: isAppOverviewLoading } = getApplicationOverv
const loading = useHasLoading(isAppOverviewLoading, isAppListLoading)
-const { pagination, reset, search } = usePagination<{ applicationInfos: Application[] }>({
+const { pagination, resetSearch, doSearch } = usePagination({
onLoadData: loadApplicationList
})
@@ -51,7 +51,7 @@ const applicationList = computed<Application[]>(() => appResponse.value?.applica
<n-spin :show="loading">
<n-flex :style="{ gap: '24px' }" vertical>
<ApplicationOverviewService :data="appOverview" />
- <ApplicationSearchFormService @do-reset="reset" @do-search="search" />
+ <ApplicationSearchFormService @reset="resetSearch" @search="doSearch" />
<ApplicationListService :data="applicationList" :pagination="pagination" />
</n-flex>
</n-spin>
diff --git a/web/src/views/overview/index.vue b/web/src/views/overview/index.vue
index ea8498083..d4db20b9b 100644
--- a/web/src/views/overview/index.vue
+++ b/web/src/views/overview/index.vue
@@ -24,6 +24,7 @@ import {
getStorageOverview,
getWorkerOverview
} from '@/api'
+import { useHasLoading } from '@varlet/axle/use'
import {
ApplicationOverviewService,
ClusterOverviewService,
@@ -31,7 +32,6 @@ import {
StorageOverviewService,
WorkerOverviewService
} from './components'
-import { useHasLoading } from '@varlet/axle/use'
defineOptions({
name: 'OverView'
diff --git a/web/src/views/application/components/ApplicationSearchForm.vue b/web/src/views/worker/components/WorkerForm.vue
similarity index 51%
copy from web/src/views/application/components/ApplicationSearchForm.vue
copy to web/src/views/worker/components/WorkerForm.vue
index 950c9adf2..cb8c3eb3b 100644
--- a/web/src/views/application/components/ApplicationSearchForm.vue
+++ b/web/src/views/worker/components/WorkerForm.vue
@@ -16,43 +16,25 @@
* limitations under the License.
-->
-<script setup lang="ts">
-import type { ApplicationSearchModel } from '@/api/models/application/types'
+<script lang="ts" setup>
+import { useFormEvent } from '@/composables'
-defineOptions({
- name: 'ApplicationSearchForm'
+const formData = reactive({
+ ip: '',
+ state: ''
})
-const searchForm = ref<ApplicationSearchModel>({})
-
-const $emits = defineEmits(['do-search', 'do-reset'])
-
-const doSearch = () => {
- $emits('do-search', unref(searchForm))
-}
-
-const resetSearch = () => {
- searchForm.value.appId = searchForm.value.subUser = searchForm.value.tenant = ''
- $emits('do-reset', unref(searchForm))
-}
+const { doSearch, resetSearch } = useFormEvent(formData)
</script>
<template>
- <n-form
- :model="searchForm"
- :show-feedback="false"
- label-placement="left"
- @keydown.enter="doSearch"
- >
- <n-grid :x-gap="24" :y-gap="24" :cols="4">
- <n-form-item-gi label="AppId" path="appId">
- <n-input v-model:value="searchForm.appId" placeholder="" clearable />
- </n-form-item-gi>
- <n-form-item-gi label="SubUser" path="subUser">
- <n-input v-model:value="searchForm.subUser" placeholder="" clearable />
+ <n-form :model="formData" :show-feedback="false" label-placement="left" @keydown.enter="doSearch">
+ <n-grid :x-gap="24" :y-gap="24" :cols="6">
+ <n-form-item-gi label="IP" path="ip">
+ <n-input v-model:value="formData.ip" placeholder="" clearable />
</n-form-item-gi>
- <n-form-item-gi label="Tenant" path="tenant">
- <n-input v-model:value="searchForm.tenant" placeholder="" clearable />
+ <n-form-item-gi label="State" path="state">
+ <n-select v-model:value="formData.state" placeholder="" clearable />
</n-form-item-gi>
<n-grid-item>
<n-flex>
diff --git a/web/src/views/worker/components/WorkerTable.vue b/web/src/views/worker/components/WorkerTable.vue
new file mode 100644
index 000000000..d4c6e8048
--- /dev/null
+++ b/web/src/views/worker/components/WorkerTable.vue
@@ -0,0 +1,103 @@
+<!--
+* 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 { Worker } from '@/api'
+import { NButton, type DataTableColumns, type PaginationProps } from 'naive-ui'
+import type { PropType } from 'vue'
+
+defineOptions({
+ name: 'WorkerTable'
+})
+
+defineProps({
+ data: {
+ type: Array as PropType<Array<Worker>>,
+ default: () => []
+ },
+ pagination: {
+ type: Object as PropType<PaginationProps>,
+ default: () => ({})
+ }
+})
+
+const columns: DataTableColumns<Worker> = [
+ {
+ title: 'Hostname/IP',
+ key: 'hostname',
+ render({ hostname, ip }) {
+ return `${hostname}/${ip}`
+ }
+ },
+ {
+ title: 'State',
+ key: 'state'
+ },
+ {
+ title: 'StartTime',
+ key: 'startTime'
+ },
+ {
+ title: 'HeartbeatTime',
+ key: 'heartbeatTime'
+ },
+ {
+ title: 'RPCPort',
+ key: 'rpcPort'
+ },
+ {
+ title: 'RestPort',
+ key: 'restPort'
+ },
+ {
+ title: 'FetchPort',
+ key: 'fetchPort'
+ },
+ {
+ title: 'PushPort',
+ key: 'pushPort'
+ },
+ {
+ title: 'ReplicatePort',
+ key: 'replicatePort'
+ },
+ {
+ title: 'Version',
+ key: 'version'
+ },
+ {
+ title: 'More',
+ key: 'more',
+ render: () => {
+ return h(
+ NButton,
+ {
+ text: true,
+ type: 'primary',
+ size: 'small'
+ },
+ { default: () => 'More' }
+ )
+ }
+ }
+]
+</script>
+
+<template>
+ <n-data-table :columns="columns" :data="data" remote :pagination="pagination" />
+</template>
diff --git a/web/src/composables/index.ts b/web/src/views/worker/components/index.ts
similarity index 84%
copy from web/src/composables/index.ts
copy to web/src/views/worker/components/index.ts
index ef3072dbf..843b8d446 100644
--- a/web/src/composables/index.ts
+++ b/web/src/views/worker/components/index.ts
@@ -15,4 +15,7 @@
* limitations under the License.
*/
-export * from './pagination'
+import WorkerFormService from './WorkerForm.vue'
+import WorkerTableService from './WorkerTable.vue'
+
+export { WorkerFormService, WorkerTableService }
diff --git a/web/src/views/worker/index.vue b/web/src/views/worker/index.vue
index 8005cf169..21eec85cd 100644
--- a/web/src/views/worker/index.vue
+++ b/web/src/views/worker/index.vue
@@ -17,11 +17,37 @@
-->
<script setup lang="ts">
+import { getWorkerList, getWorkerOverview } from '@/api'
+import { usePagination } from '@/composables'
+import { WorkerOverviewService } from '@/views/overview/components'
+import { useHasLoading } from '@varlet/axle/use'
+import { WorkerFormService, WorkerTableService } from './components'
+
defineOptions({
name: 'WorkerView'
})
-</script>
-<template>Welcome to Apache Celeborn. There is Worker.</template>
+const { data: overview, loading: isOverviewLoading } = getWorkerOverview().use()
+
+const {
+ data,
+ getData: onLoadData,
+ loading: isTableLoading
+} = getWorkerList().use({ immediate: false })
+
+const { pagination, doSearch, resetSearch } = usePagination({
+ onLoadData
+})
+
+const loading = useHasLoading(isOverviewLoading, isTableLoading)
+</script>
-<style scoped lang="scss"></style>
+<template>
+ <n-spin :show="loading">
+ <n-flex :style="{ gap: '24px' }" vertical>
+ <WorkerOverviewService :data="overview" />
+ <WorkerFormService @search="doSearch" @reset="resetSearch" />
+ <WorkerTableService :data="data?.workerInfos" :pagination="pagination" />
+ </n-flex>
+ </n-spin>
+</template>