This is an automated email from the ASF dual-hosted git repository.

zuston pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-uniffle.git


The following commit(s) were added to refs/heads/master by this push:
     new 3cf820b5d [#2048] feat(coordinator): Introduce dashboard page in 
dashboard web ui (#2049)
3cf820b5d is described below

commit 3cf820b5d5aaba22e1b75f37c9dc4ab8e3919b9d
Author: maobaolong <baoloong...@tencent.com>
AuthorDate: Thu Aug 22 19:58:56 2024 +0800

    [#2048] feat(coordinator): Introduce dashboard page in dashboard web ui 
(#2049)
    
    ### What changes were proposed in this pull request?
    
    Introduce dashboard page in dashboard web ui to show the version, start 
time and other information about dashboard.
    
    ### Why are the changes needed?
    
    Fix: #2048
    
    ### Does this PR introduce _any_ user-facing change?
    
    Dashboard UI
    
    ### How was this patch tested?
    
    <img width="1435" alt="image" 
src="https://github.com/user-attachments/assets/410cae37-ecf3-47ea-a769-a290514dec45";>
---
 dashboard/pom.xml                                  |   4 +
 .../apache/uniffle/dashboard/web/Dashboard.java    |  11 ++
 .../org/apache/uniffle/dashboard/web/lombok.config |  21 +++
 .../dashboard/web/resource/DashboardResource.java  |  78 +++++++++++
 .../dashboard/web/resource/WebResource.java        |   5 +
 .../WebResource.java => vo/DashboardConfVO.java}   |  19 ++-
 dashboard/src/main/webapp/src/api/api.js           |  10 ++
 .../src/main/webapp/src/components/LayoutPage.vue  |   4 +
 .../src/main/webapp/src/pages/DashboardPage.vue    | 154 +++++++++++++++++++++
 dashboard/src/main/webapp/src/router/index.js      |   6 +
 10 files changed, 301 insertions(+), 11 deletions(-)

diff --git a/dashboard/pom.xml b/dashboard/pom.xml
index cdf40439b..ddeb1eaf0 100644
--- a/dashboard/pom.xml
+++ b/dashboard/pom.xml
@@ -37,6 +37,10 @@
             <groupId>org.apache.uniffle</groupId>
             <artifactId>rss-server-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/Dashboard.java 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/Dashboard.java
index f3d75c524..bb8d1e25b 100644
--- a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/Dashboard.java
+++ b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/Dashboard.java
@@ -53,6 +53,7 @@ import org.apache.uniffle.dashboard.web.utils.DashboardUtils;
 public class Dashboard {
 
   private static final Logger LOG = LoggerFactory.getLogger(Dashboard.class);
+  private final long startTimeMs;
 
   private DashboardConf conf;
   // Jetty Server
@@ -62,6 +63,7 @@ public class Dashboard {
 
   public Dashboard(DashboardConf coordinatorConf) {
     this.conf = coordinatorConf;
+    this.startTimeMs = System.currentTimeMillis();
     initialization();
   }
 
@@ -120,6 +122,7 @@ public class Dashboard {
     servletHolder.setInitParameter(
         ServerProperties.PROVIDER_PACKAGES, 
"org.apache.uniffle.dashboard.web.resource");
     contextHandler.setAttribute("coordinatorServerAddresses", 
coordinatorServerAddresses);
+    contextHandler.setAttribute(Dashboard.class.getCanonicalName(), this);
     return contextHandler;
   }
 
@@ -159,4 +162,12 @@ public class Dashboard {
     }
     LOG.info("Dashboard http server started, listening on port {}", httpPort);
   }
+
+  public long getStartTimeMs() {
+    return startTimeMs;
+  }
+
+  public DashboardConf getConf() {
+    return conf;
+  }
 }
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/lombok.config 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/lombok.config
new file mode 100644
index 000000000..d5b979224
--- /dev/null
+++ b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/lombok.config
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+clear lombok.allArgsConstructor.flagUsage
+clear lombok.anyConstructor.flagUsage
+clear lombok.noArgsConstructor.flagUsage
+clear lombok.data.flagUsage
+clear lombok.builder.flagUsage
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/DashboardResource.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/DashboardResource.java
new file mode 100644
index 000000000..ac3f518dc
--- /dev/null
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/DashboardResource.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+package org.apache.uniffle.dashboard.web.resource;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletContext;
+
+import org.apache.hbase.thirdparty.javax.ws.rs.GET;
+import org.apache.hbase.thirdparty.javax.ws.rs.Path;
+import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.Context;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
+
+import org.apache.uniffle.common.util.Constants;
+import org.apache.uniffle.common.web.resource.BaseResource;
+import org.apache.uniffle.common.web.resource.Response;
+import org.apache.uniffle.dashboard.web.Dashboard;
+import org.apache.uniffle.dashboard.web.config.DashboardConf;
+import org.apache.uniffle.dashboard.web.vo.DashboardConfVO;
+
+@Produces({MediaType.APPLICATION_JSON})
+public class DashboardResource extends BaseResource {
+
+  @Context protected ServletContext servletContext;
+
+  @GET
+  @Path("/conf")
+  public Response<List<DashboardConfVO>> getDashboardConf() {
+    return execute(
+        () -> {
+          DashboardConf conf = getDashboard().getConf();
+          Set<Map.Entry<String, Object>> allEntry = conf.getAll();
+          List<DashboardConfVO> dashboardConfVOs = new ArrayList<>();
+          for (Map.Entry<String, Object> stringObjectEntry : allEntry) {
+            DashboardConfVO result =
+                new DashboardConfVO(
+                    stringObjectEntry.getKey(), 
String.valueOf(stringObjectEntry.getValue()));
+            dashboardConfVOs.add(result);
+          }
+          return dashboardConfVOs;
+        });
+  }
+
+  @GET
+  @Path("/info")
+  public Response<Map<String, Object>> getDashboardInfo() {
+    return execute(
+        () -> {
+          Map<String, Object> dashboardInfo = new HashMap<>();
+          dashboardInfo.put("version", Constants.VERSION_AND_REVISION_SHORT);
+          dashboardInfo.put("startTime", getDashboard().getStartTimeMs());
+          return dashboardInfo;
+        });
+  }
+
+  private Dashboard getDashboard() {
+    return (Dashboard) 
servletContext.getAttribute(Dashboard.class.getCanonicalName());
+  }
+}
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
index aaab37001..cc9093007 100644
--- 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
@@ -28,4 +28,9 @@ public class WebResource {
   public Class<CoordinatorResource> getGainCoordinatorsResource() {
     return CoordinatorResource.class;
   }
+
+  @Path("dashboard")
+  public Class<DashboardResource> getDashboardResource() {
+    return DashboardResource.class;
+  }
 }
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/vo/DashboardConfVO.java
similarity index 64%
copy from 
dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
copy to 
dashboard/src/main/java/org/apache/uniffle/dashboard/web/vo/DashboardConfVO.java
index aaab37001..ccbea86b1 100644
--- 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/vo/DashboardConfVO.java
@@ -15,17 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.uniffle.dashboard.web.resource;
+package org.apache.uniffle.dashboard.web.vo;
 
-import org.apache.hbase.thirdparty.javax.ws.rs.Path;
-import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
-import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
+import lombok.AllArgsConstructor;
+import lombok.Data;
 
-@Path("web")
-@Produces({MediaType.APPLICATION_JSON})
-public class WebResource {
-  @Path("coordinator")
-  public Class<CoordinatorResource> getGainCoordinatorsResource() {
-    return CoordinatorResource.class;
-  }
+@Data
+@AllArgsConstructor
+public class DashboardConfVO {
+  private String argumentKey;
+  private String argumentValue;
 }
diff --git a/dashboard/src/main/webapp/src/api/api.js 
b/dashboard/src/main/webapp/src/api/api.js
index c0a827f44..8ed6f44d8 100644
--- a/dashboard/src/main/webapp/src/api/api.js
+++ b/dashboard/src/main/webapp/src/api/api.js
@@ -16,6 +16,16 @@
  */
 
 import http from '@/utils/http'
+// Create a Dashboard information interface
+export function getDashboardInfo(params, headers) {
+  return http.get('/dashboard/info', params, headers, 1)
+}
+
+// Create a dashboard configuration file interface
+export function getDashboardConf(params, headers) {
+  return http.get('/dashboard/conf', params, headers, 1)
+}
+
 // Create a Coordinator information interface
 export function getCoordinatorServerInfo(params, headers) {
   return http.get('/coordinator/info', params, headers, 0)
diff --git a/dashboard/src/main/webapp/src/components/LayoutPage.vue 
b/dashboard/src/main/webapp/src/components/LayoutPage.vue
index ead2e7469..b96913022 100644
--- a/dashboard/src/main/webapp/src/components/LayoutPage.vue
+++ b/dashboard/src/main/webapp/src/components/LayoutPage.vue
@@ -36,6 +36,10 @@
                   <img src="../assets/uniffle-logo.png" alt="unffile" />
                 </div>
               </el-menu-item>
+              <el-menu-item index="/dashboardpage">
+                <el-icon><House /></el-icon>
+                <span>Dashboard</span>
+              </el-menu-item>
               <el-menu-item index="/coordinatorserverpage">
                 <el-icon><House /></el-icon>
                 <span>Coordinator</span>
diff --git a/dashboard/src/main/webapp/src/pages/DashboardPage.vue 
b/dashboard/src/main/webapp/src/pages/DashboardPage.vue
new file mode 100644
index 000000000..ef268901e
--- /dev/null
+++ b/dashboard/src/main/webapp/src/pages/DashboardPage.vue
@@ -0,0 +1,154 @@
+<!--
+  ~ 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.
+  -->
+
+<template>
+  <div class="demo-collapse">
+    <el-collapse v-model="pageData.activeNames" accordion:false>
+      <el-collapse-item title="Dashboard" name="1">
+        <div>
+          <el-descriptions class="margin-top" :column="3" :size="size" border>
+            <el-descriptions-item>
+              <template #label>
+                <div class="cell-item">
+                  <el-icon :style="iconStyle">
+                    <Wallet />
+                  </el-icon>
+                  Version
+                </div>
+              </template>
+              {{ pageData.dashboardInfo.version}}
+            </el-descriptions-item>
+            <el-descriptions-item>
+              <template #label>
+                <div class="cell-item">
+                  <el-icon :style="iconStyle">
+                    <Wallet />
+                  </el-icon>
+                  Start Time
+                </div>
+              </template>
+              <template #default>
+                {{ dateFormatter(null, null, pageData.dashboardInfo.startTime) 
}}
+              </template>
+            </el-descriptions-item>
+          </el-descriptions>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item title="Dashboard Properties" name="2">
+        <el-table :data="filteredTableData" stripe style="width: 100%">
+          <el-table-column prop="argumentKey" label="Name" min-width="380" />
+          <el-table-column prop="argumentValue" label="Value" min-width="380" 
:show-overflow-tooltip="true" />
+          <el-table-column align="right">
+            <template #header>
+              <el-input v-model="searchKeyword" size="small" placeholder="Type 
to search" />
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<script>
+import { ref, reactive, computed, onMounted } from 'vue'
+import {
+  getDashboardConf,
+  getDashboardInfo
+} from '@/api/api'
+import { dateFormatter } from '@/utils/common'
+
+export default {
+  setup() {
+    const pageData = reactive({
+      activeNames: ['1', '2'],
+      tableData: [],
+      dashboardInfo: {}
+    })
+
+    async function getDbConfPage() {
+      const res = await getDashboardConf()
+      pageData.tableData = res.data.data
+    }
+    async function getDbInfo() {
+      const res = await getDashboardInfo()
+      pageData.dashboardInfo = res.data.data
+    }
+
+    onMounted(() => {
+      getDbConfPage()
+      getDbInfo()
+    })
+
+    const size = ref('')
+    const iconStyle = computed(() => {
+      const marginMap = {
+        large: '8px',
+        default: '6px',
+        small: '4px'
+      }
+      return {
+        marginRight: marginMap[size.value] || marginMap.default
+      }
+    })
+    const blockMargin = computed(() => {
+      const marginMap = {
+        large: '32px',
+        default: '28px',
+        small: '24px'
+      }
+      return {
+        marginTop: marginMap[size.value] || marginMap.default
+      }
+    })
+
+    /**
+     * The following describes how to handle blacklist select events.
+     */
+    const searchKeyword = ref('')
+    const filteredTableData = computed(() => {
+      const keyword = searchKeyword.value.trim()
+      if (!keyword) {
+        return pageData.tableData
+      } else {
+        return pageData.tableData.filter((row) => {
+          return row.argumentValue.includes(keyword) || 
row.argumentKey.includes(keyword)
+        })
+      }
+    })
+
+    return {
+      pageData,
+      iconStyle,
+      blockMargin,
+      size,
+      filteredTableData,
+      searchKeyword,
+      dateFormatter
+    }
+  }
+}
+</script>
+<style>
+.cell-item {
+  display: flex;
+  align-items: center;
+}
+
+.margin-top {
+  margin-top: 20px;
+}
+</style>
diff --git a/dashboard/src/main/webapp/src/router/index.js 
b/dashboard/src/main/webapp/src/router/index.js
index 16ecaadc2..f0b6bb8e7 100644
--- a/dashboard/src/main/webapp/src/router/index.js
+++ b/dashboard/src/main/webapp/src/router/index.js
@@ -17,12 +17,18 @@
 
 import { createRouter, createWebHashHistory } from 'vue-router'
 import ApplicationPage from '@/pages/ApplicationPage.vue'
+import DashboardPage from '@/pages/DashboardPage.vue'
 import CoordinatorServerPage from '@/pages/CoordinatorServerPage.vue'
 import ShuffleServerPage from '@/pages/ShuffleServerPage.vue'
 import ExcludeNodeList from '@/pages/serverstatus/ExcludeNodeList'
 import NodeListPage from '@/pages/serverstatus/NodeListPage.vue'
 
 const routes = [
+  {
+    path: '/dashboardpage',
+    name: 'dashboardpage',
+    component: DashboardPage
+  },
   {
     path: '/coordinatorserverpage',
     name: 'coordinatorserverpage',

Reply via email to