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',