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:
    ![image](https://github.com/apache/incubator-celeborn/assets/49502875/92c76f50-b0a2-4050-af50-2a30c7eea94a)
    
    ### 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>