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 acfec30ce [#960][part-2] feat(dashboard): Add a dashboard front-end
module. (#1055)
acfec30ce is described below
commit acfec30cebd4cd3685bc44cfde39512a49b397e1
Author: yl09099 <[email protected]>
AuthorDate: Thu Nov 16 14:35:01 2023 +0800
[#960][part-2] feat(dashboard): Add a dashboard front-end module. (#1055)
### What changes were proposed in this pull request?
Add a UI front-end file for dashboard, which have been verified it in
DIDI's production:



### Why are the changes needed?
Fix: #960
### Does this PR introduce _any_ user-facing change?
Add the web dashboard for coordinator
### How was this patch tested?
UT.
---
.gitignore | 5 +
dashboard/pom.xml | 96 +++++++++++
.../uniffle/dashboard/web/JettyServerFront.java | 130 +++++++++++++++
.../dashboard/web/config/DashboardConf.java | 68 ++++++++
dashboard/src/main/webapp/babel.config.js | 22 +++
dashboard/src/main/webapp/jsconfig.json | 37 +++++
dashboard/src/main/webapp/package.json | 73 ++++++++
.../src/main/webapp/packagescript/cleanfile.js | 33 ++++
.../src/main/webapp/packagescript/filecopy.js | 26 +++
.../src/main/webapp/packagescript/fileutils.js | 103 ++++++++++++
dashboard/src/main/webapp/public/favicon.ico | Bin 0 -> 9786 bytes
dashboard/src/main/webapp/public/index.html | 34 ++++
dashboard/src/main/webapp/src/App.vue | 30 ++++
dashboard/src/main/webapp/src/api/api.js | 78 +++++++++
.../src/main/webapp/src/assets/uniffle-logo.png | Bin 0 -> 13083 bytes
.../main/webapp/src/components/ApplicationPage.vue | 102 ++++++++++++
.../src/components/CoordinatorServerPage.vue | 149 +++++++++++++++++
.../src/main/webapp/src/components/LayoutPage.vue | 98 +++++++++++
.../webapp/src/components/ShuffleServerPage.vue | 184 +++++++++++++++++++++
.../shufflecomponent/ActiveNodeListPage.vue | 70 ++++++++
.../DecommissionednodeListPage.vue | 70 ++++++++
.../DecommissioningNodeListPage.vue | 70 ++++++++
.../shufflecomponent/ExcludeNodeList.vue | 49 ++++++
.../components/shufflecomponent/LostNodeList.vue | 70 ++++++++
.../shufflecomponent/UnhealthyNodeListPage.vue | 70 ++++++++
dashboard/src/main/webapp/src/main.js | 28 ++++
dashboard/src/main/webapp/src/router/index.js | 69 ++++++++
dashboard/src/main/webapp/src/utils/http.js | 39 +++++
dashboard/src/main/webapp/src/utils/request.js | 37 +++++
dashboard/src/main/webapp/vue.config.js | 27 +++
pom.xml | 1 +
31 files changed, 1868 insertions(+)
diff --git a/.gitignore b/.gitignore
index c1bd43acc..17b194d88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,11 @@ build/
deploy/kubernetes/integration-test/e2e/rss.yaml
deploy/kubernetes/integration-test/e2e/rss-controller.yaml
deploy/kubernetes/integration-test/e2e/rss-webhook.yaml
+dashboard/src/main/webapp/node_modules/
+dashboard/src/main/webapp/node/
+dashboard/src/main/webapp/dist/
+dashboard/src/main/resources/static/
+dashboard/src/main/webapp/package-lock.json
rust/experimental/server/target
rust/experimental/server/.idea
rust/experimental/server/._target
diff --git a/dashboard/pom.xml b/dashboard/pom.xml
new file mode 100644
index 000000000..a1237c1ba
--- /dev/null
+++ b/dashboard/pom.xml
@@ -0,0 +1,96 @@
+<?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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>uniffle-parent</artifactId>
+ <groupId>org.apache.uniffle</groupId>
+ <version>0.9.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>dashboard</artifactId>
+ <name>Apache Uniffle Dashboard</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.uniffle</groupId>
+ <artifactId>coordinator</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.github.eirslett</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.12.1</version>
+ <executions>
+ <execution>
+ <id>install node and npm</id>
+ <goals>
+ <goal>install-node-and-npm</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ <configuration>
+ <nodeVersion>v14.21.3</nodeVersion>
+ <npmVersion>6.14.18</npmVersion>
+
<nodeDownloadRoot>https://nodejs.org/dist/</nodeDownloadRoot>
+ </configuration>
+ </execution>
+ <!-- Execute the script to remove node_modules and
package-lock.json -->
+ <execution>
+ <id>npm run clean</id>
+ <goals>
+ <goal>npm</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ <configuration>
+ <arguments>run clean</arguments>
+ </configuration>
+ </execution>
+ <!-- npm install -->
+ <execution>
+ <id>npm install</id>
+ <goals>
+ <goal>npm</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ <configuration>
+ <arguments>install</arguments>
+ </configuration>
+ </execution>
+ <!-- Copy the file to src/main/resource/static after build
-->
+ <execution>
+ <id>npm run build</id>
+ <goals>
+ <goal>npm</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ <configuration>
+ <arguments>run build-copy</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ <configuration>
+ <workingDirectory>src/main/webapp</workingDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/JettyServerFront.java
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/JettyServerFront.java
new file mode 100644
index 000000000..7518f9591
--- /dev/null
+++
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/JettyServerFront.java
@@ -0,0 +1,130 @@
+/*
+ * 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;
+
+import java.net.BindException;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+
+import org.apache.uniffle.common.Arguments;
+import org.apache.uniffle.common.config.ReconfigurableBase;
+import org.apache.uniffle.common.util.ExitUtils;
+import org.apache.uniffle.common.util.ThreadUtils;
+import org.apache.uniffle.dashboard.web.config.DashboardConf;
+
+public class JettyServerFront {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(JettyServerFront.class);
+
+ private DashboardConf conf;
+ // Jetty Server
+ private Server server;
+ // FrontEnd Port
+ private int httpPort;
+
+ public JettyServerFront(DashboardConf coordinatorConf) {
+ this.conf = coordinatorConf;
+ initialization();
+ }
+
+ public static void main(String[] args) {
+ Arguments arguments = new Arguments();
+ CommandLine commandLine = new CommandLine(arguments);
+ commandLine.parseArgs(args);
+ String configFile = arguments.getConfigFile();
+ LOG.info("Start to init dashboard http server using config {}",
configFile);
+
+ // Load configuration from config files
+ final DashboardConf coodConf = new DashboardConf(configFile);
+ coodConf.setString(ReconfigurableBase.RECONFIGURABLE_FILE_NAME,
configFile);
+ JettyServerFront jettyServerFront = new JettyServerFront(coodConf);
+ jettyServerFront.start();
+ }
+
+ private void initialization() {
+ httpPort = conf.getInteger(DashboardConf.DASHBOARD_HTTP_PORT);
+ ExecutorThreadPool threadPool = createThreadPool(conf);
+ server = new Server(threadPool);
+ server.setStopAtShutdown(true);
+ server.setStopTimeout(conf.getLong(DashboardConf.DASHBOARD_STOP_TIMEOUT));
+ server.addBean(new ScheduledExecutorScheduler("jetty-thread-pool", true));
+ setRootServletHandler();
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ addHttpConnector(httpPort, httpConfig,
conf.getLong(DashboardConf.DASHBOARD_IDLE_TIMEOUT));
+ }
+
+ private void setRootServletHandler() {
+ final ContextHandler contextHandler = new ContextHandler("/");
+ ResourceHandler resourceHandler = new ResourceHandler();
+ resourceHandler.setDirectoriesListed(true);
+ resourceHandler.setBaseResource(
+
Resource.newResource(JettyServerFront.class.getClassLoader().getResource("static")));
+ resourceHandler.setWelcomeFiles(new String[] {"index.html"});
+ contextHandler.setHandler(resourceHandler);
+ server.setHandler(contextHandler);
+ }
+
+ private void addHttpConnector(int port, HttpConfiguration httpConfig, long
idleTimeout) {
+ ServerConnector httpConnector =
+ new ServerConnector(server, new HttpConnectionFactory(httpConfig));
+ httpConnector.setPort(port);
+ httpConnector.setIdleTimeout(idleTimeout);
+ server.addConnector(httpConnector);
+ }
+
+ private ExecutorThreadPool createThreadPool(DashboardConf conf) {
+ int corePoolSize = conf.getInteger(DashboardConf.DASHBOARD_CORE_POOL_SIZE);
+ int maxPoolSize = conf.getInteger(DashboardConf.DASHBOARD_MAX_POOL_SIZE);
+ ExecutorThreadPool pool =
+ new ExecutorThreadPool(
+ new ThreadPoolExecutor(
+ corePoolSize,
+ maxPoolSize,
+ 60L,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(),
+ ThreadUtils.getThreadFactory("DashboardServer")));
+ return pool;
+ }
+
+ public void start() {
+ try {
+ server.start();
+ server.join();
+ } catch (BindException e) {
+ ExitUtils.terminate(1, "Fail to dashboard http server", e, LOG);
+ } catch (Exception e) {
+ ExitUtils.terminate(1, "Fail to start dashboard http server", e, LOG);
+ }
+ LOG.info("Dashboard http server started, listening on port {}", httpPort);
+ }
+}
diff --git
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/config/DashboardConf.java
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/config/DashboardConf.java
new file mode 100644
index 000000000..f2a6cccf0
--- /dev/null
+++
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/config/DashboardConf.java
@@ -0,0 +1,68 @@
+/*
+ * 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.config;
+
+import org.apache.uniffle.common.config.ConfigOption;
+import org.apache.uniffle.common.config.ConfigOptions;
+import org.apache.uniffle.common.config.ConfigUtils;
+import org.apache.uniffle.common.config.RssBaseConf;
+
+public class DashboardConf extends RssBaseConf {
+
+ public static final ConfigOption<Integer> DASHBOARD_HTTP_PORT =
+ ConfigOptions.key("rss.dashboard.http.port")
+ .intType()
+ .defaultValue(19988)
+ .withDescription("http server http port");
+
+ public static final ConfigOption<Long> DASHBOARD_STOP_TIMEOUT =
+ ConfigOptions.key("rss.dashboard.stop.timeout")
+ .longType()
+ .defaultValue(30 * 1000L)
+ .withDescription("dashboard http server stop timeout (ms) ");
+
+ public static final ConfigOption<Long> DASHBOARD_IDLE_TIMEOUT =
+ ConfigOptions.key("rss.dashboard.http.idle.timeout")
+ .longType()
+ .defaultValue(30 * 1000L)
+ .withDescription("dashboard http server http idle timeout (ms) ");
+
+ public static final ConfigOption<Integer> DASHBOARD_CORE_POOL_SIZE =
+ ConfigOptions.key("rss.dashboard.corePool.size")
+ .intType()
+ .defaultValue(256)
+ .withDescription("dashboard http server corePool size");
+
+ public static final ConfigOption<Integer> DASHBOARD_MAX_POOL_SIZE =
+ ConfigOptions.key("rss.dashboard.maxPool.size")
+ .intType()
+ .defaultValue(256)
+ .withDescription("dashboard http server max pool size");
+
+ public DashboardConf(String fileName) {
+ super();
+ boolean ret = loadConfFromFile(fileName);
+ if (!ret) {
+ throw new IllegalStateException("Fail to load config file " + fileName);
+ }
+ }
+
+ public boolean loadConfFromFile(String fileName) {
+ return loadConfFromFile(fileName,
ConfigUtils.getAllConfigOptions(DashboardConf.class));
+ }
+}
diff --git a/dashboard/src/main/webapp/babel.config.js
b/dashboard/src/main/webapp/babel.config.js
new file mode 100644
index 000000000..0b134436b
--- /dev/null
+++ b/dashboard/src/main/webapp/babel.config.js
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+module.exports = {
+ presets: [
+ '@vue/cli-plugin-babel/preset'
+ ]
+}
diff --git a/dashboard/src/main/webapp/jsconfig.json
b/dashboard/src/main/webapp/jsconfig.json
new file mode 100644
index 000000000..bb01e412c
--- /dev/null
+++ b/dashboard/src/main/webapp/jsconfig.json
@@ -0,0 +1,37 @@
+{
+ "___asflicense__": [
+ "",
+ "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."
+ ],
+ "compilerOptions": {
+ "target": "es5",
+ "module": "esnext",
+ "baseUrl": "./",
+ "moduleResolution": "node",
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ },
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable",
+ "scripthost"
+ ]
+ }
+}
diff --git a/dashboard/src/main/webapp/package.json
b/dashboard/src/main/webapp/package.json
new file mode 100644
index 000000000..820c7379a
--- /dev/null
+++ b/dashboard/src/main/webapp/package.json
@@ -0,0 +1,73 @@
+{
+ "___asflicense__": [
+ "",
+ "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."
+ ],
+ "name": "Uniffle",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "serve": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "lint": "vue-cli-service lint",
+ "clean": "node packagescript/cleanfile.js",
+ "build-copy": "vue-cli-service build && node packagescript/filecopy.js"
+ },
+ "dependencies": {
+ "axios": "^1.4.0",
+ "core-js": "^3.8.3",
+ "element-plus": "^2.3.6",
+ "moment": "^2.29.4",
+ "rimraf": "^5.0.1",
+ "vue": "^3.2.13",
+ "vue-resource": "^1.5.3",
+ "vue-router": "^4.2.2"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.16",
+ "@babel/eslint-parser": "^7.12.16",
+ "@vue/cli-plugin-babel": "~5.0.0",
+ "@vue/cli-plugin-eslint": "~5.0.0",
+ "@vue/cli-service": "~5.0.0",
+ "eslint": "^7.32.0",
+ "eslint-plugin-vue": "^8.0.3",
+ "node-sass": "^9.0.0",
+ "sass-loader": "^13.3.1",
+ "vue-loader": "^17.2.2",
+ "vue-template-compiler": "^2.7.14"
+ },
+ "eslintConfig": {
+ "root": true,
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/vue3-essential",
+ "eslint:recommended"
+ ],
+ "parserOptions": {
+ "parser": "@babel/eslint-parser"
+ },
+ "rules": {}
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not dead",
+ "not ie 11"
+ ]
+}
diff --git a/dashboard/src/main/webapp/packagescript/cleanfile.js
b/dashboard/src/main/webapp/packagescript/cleanfile.js
new file mode 100644
index 000000000..e7cad78b5
--- /dev/null
+++ b/dashboard/src/main/webapp/packagescript/cleanfile.js
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+const fs = require('fs')
+const p = require('path')
+
+const nodeModulesPath = p.join(__dirname, '../node_modules')
+const lockJsonPath = p.join(__dirname, '../package-lock.json')
+
+if (fs.existsSync(nodeModulesPath)) {
+ const fileUtil = require('./fileutils')
+
+ //fileUtil.deleteFolderByRimraf(nodeModulesPath)
+ fileUtil.deleteFolder(nodeModulesPath)
+ console.log('Deleted node_modules successfully!')
+
+ fileUtil.deleteFile(lockJsonPath)
+ console.log('Delete package-lock.json successfully!')
+}
diff --git a/dashboard/src/main/webapp/packagescript/filecopy.js
b/dashboard/src/main/webapp/packagescript/filecopy.js
new file mode 100644
index 000000000..9966d0a86
--- /dev/null
+++ b/dashboard/src/main/webapp/packagescript/filecopy.js
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+const fileUtil = require('./fileutils')
+
+// Destination folder
+const staticDirectory = '../resources/static'
+// Delete
+fileUtil.deleteFolder(staticDirectory)
+// Copy
+fileUtil.copyFolder('./dist', staticDirectory)
+console.log('File copy successful!')
diff --git a/dashboard/src/main/webapp/packagescript/fileutils.js
b/dashboard/src/main/webapp/packagescript/fileutils.js
new file mode 100644
index 000000000..221d1c9d1
--- /dev/null
+++ b/dashboard/src/main/webapp/packagescript/fileutils.js
@@ -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.
+ */
+
+const fs = require('fs')
+const pathName = require("path");
+
+/**
+ * Delete folder
+ * @param path Path of the folder to be deleted
+ */
+function deleteFolder (path) {
+ let files = [];
+ if (fs.existsSync(path)) {
+ if (fs.statSync(path).isDirectory()) {
+ files = fs.readdirSync(path)
+ files.forEach((file) => {
+ const curPath = path + '/' + file;
+ if (fs.statSync(curPath).isDirectory()) {
+ deleteFolder(curPath)
+ } else {
+ fs.unlinkSync(curPath)
+ }
+ })
+ fs.rmdirSync(path)
+ } else {
+ fs.unlinkSync(path)
+ }
+ }
+}
+
+/**
+ * Delete file
+ * @param path Path of the file to be deleted
+ */
+function deleteFile (path) {
+ if (fs.existsSync(path)) {
+ if (fs.statSync(path).isDirectory()) {
+ deleteFolder(path)
+ } else {
+ fs.unlinkSync(path)
+ }
+ }
+}
+
+/**
+ * Copy the folder to the specified directory
+ * @param from Source directory
+ * @param to Target directory
+ */
+function copyFolder (from, to) {
+ let files = []
+ // Whether the file exists If it does not exist, it is created
+ if (fs.existsSync(to)) {
+ files = fs.readdirSync(from)
+ files.forEach((file) => {
+ const targetPath = from + '/' + file;
+ const toPath = to + '/' + file;
+
+ // Copy folder
+ if (fs.statSync(targetPath).isDirectory()) {
+ copyFolder(targetPath, toPath)
+ } else {
+ // Copy file
+ fs.copyFileSync(targetPath, toPath)
+ }
+ })
+ } else {
+ mkdirsSync(to)
+ copyFolder(from, to)
+ }
+}
+
+// Create a directory synchronization method recursively
+function mkdirsSync(dirname) {
+ if (fs.existsSync(dirname)) {
+ return true;
+ } else {
+ if (mkdirsSync(pathName.dirname(dirname))) {
+ fs.mkdirSync(dirname);
+ return true;
+ }
+ }
+}
+
+module.exports = {
+ deleteFolder,
+ deleteFile,
+ copyFolder
+}
diff --git a/dashboard/src/main/webapp/public/favicon.ico
b/dashboard/src/main/webapp/public/favicon.ico
new file mode 100644
index 000000000..f3a1d5e5d
Binary files /dev/null and b/dashboard/src/main/webapp/public/favicon.ico differ
diff --git a/dashboard/src/main/webapp/public/index.html
b/dashboard/src/main/webapp/public/index.html
new file mode 100644
index 000000000..d703f0fe3
--- /dev/null
+++ b/dashboard/src/main/webapp/public/index.html
@@ -0,0 +1,34 @@
+<!--
+ ~ 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.
+ -->
+
+<!DOCTYPE html>
+<html lang="">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
+ <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+ <title><%= htmlWebpackPlugin.options.title %></title>
+ </head>
+ <body>
+ <noscript>
+ <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't
work properly without JavaScript enabled. Please enable it to continue.</strong>
+ </noscript>
+ <div id="app"></div>
+ <!-- built files will be auto injected -->
+ </body>
+</html>
diff --git a/dashboard/src/main/webapp/src/App.vue
b/dashboard/src/main/webapp/src/App.vue
new file mode 100644
index 000000000..deaa982ad
--- /dev/null
+++ b/dashboard/src/main/webapp/src/App.vue
@@ -0,0 +1,30 @@
+<!--
+ ~ 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>
+ <LayoutPage />
+</template>
+
+<script>
+import LayoutPage from "@/components/LayoutPage";
+export default {
+ name: 'App',
+ components: {
+ LayoutPage
+ }
+}
+</script>
diff --git a/dashboard/src/main/webapp/src/api/api.js
b/dashboard/src/main/webapp/src/api/api.js
new file mode 100644
index 000000000..c346d1d2e
--- /dev/null
+++ b/dashboard/src/main/webapp/src/api/api.js
@@ -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.
+ */
+
+import http from "@/utils/http";
+
+// Create a Coordinator information interface
+export function getCoordinatorServerInfo(params){
+ return http.get('/coordinator/info', params,{})
+}
+
+// Create a coordinator configuration file interface
+export function getCoordinatorConf(params){
+ return http.get('/coordinator/conf', params,{})
+}
+
+// Create an interface for the total number of nodes
+export function getShufflegetStatusTotal(params){
+ return http.get('/server/nodes/summary', params,{})
+}
+
+// Create an interface for activeNodes
+export function getShuffleActiveNodes(params){
+ return http.get('/server/nodes?status=active', params,{})
+}
+
+// Create an interface for lostNodes
+export function getShuffleLostList(params){
+ return http.get('/server/nodes?status=lost', params,{})
+}
+
+// Create an interface for unhealthyNodes
+export function getShuffleUnhealthyList(params){
+ return http.get('/server/nodes?status=unhealthy', params,{})
+}
+
+// Create an interface for decommissioningNodes
+export function getShuffleDecommissioningList(params){
+ return http.get('/server/nodes?status=decommissioning', params,{})
+}
+
+// Create an interface for decommissionedNodes
+export function getShuffleDecommissionedList(params){
+ return http.get('/server/nodes?status=decommissioned', params,{})
+}
+
+// Create an interface for excludeNodes
+export function getShuffleExcludeNodes(params){
+ return http.get('/server/nodes?status=excluded', params,{})
+}
+
+// Total number of interfaces for new App
+export function getAppTotal(params){
+ return http.get('/app/total', params,{})
+}
+
+// Create an interface for the app basic information list
+export function getApplicationInfoList(params){
+ return http.get('/app/appinfos', params,{})
+}
+
+// Create an interface for the number of apps for a user
+export function getTotalForUser(params){
+ return http.get('/app/usertotal', params,{})
+}
diff --git a/dashboard/src/main/webapp/src/assets/uniffle-logo.png
b/dashboard/src/main/webapp/src/assets/uniffle-logo.png
new file mode 100644
index 000000000..828ce9aa7
Binary files /dev/null and
b/dashboard/src/main/webapp/src/assets/uniffle-logo.png differ
diff --git a/dashboard/src/main/webapp/src/components/ApplicationPage.vue
b/dashboard/src/main/webapp/src/components/ApplicationPage.vue
new file mode 100644
index 000000000..90f176218
--- /dev/null
+++ b/dashboard/src/main/webapp/src/components/ApplicationPage.vue
@@ -0,0 +1,102 @@
+<!--
+ ~ 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>
+ <el-row :gutter="20">
+ <el-col :span="4">
+ <el-card class="box-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span class="cardtile">APPS TOTAL</span>
+ </div>
+ </template>
+ <div class="appcnt">{{ pageData.apptotal.appTotality }}</div>
+ </el-card>
+ </el-col>
+ </el-row>
+ <el-divider/>
+ <el-tag>User App ranking</el-tag>
+ <div>
+ <el-table :data="pageData.userAppCount" height="250" style="width: 100%">
+ <el-table-column prop="userName" label="UserName" min-width="180"/>
+ <el-table-column prop="appNum" label="Totality" min-width="180"/>
+ </el-table>
+ </div>
+ <el-divider/>
+ <el-tag>Apps</el-tag>
+ <div>
+ <el-table :data="pageData.appInfoData" height="250" style="width: 100%">
+ <el-table-column prop="appId" label="AppId" min-width="180"/>
+ <el-table-column prop="userName" label="UserName" min-width="180"/>
+ <el-table-column prop="updateTime" label="Update Time"
min-width="180"/>
+ </el-table>
+ </div>
+ </div>
+</template>
+
+<script>
+import {
+ getApplicationInfoList,
+ getAppTotal,
+ getTotalForUser
+} from "@/api/api";
+import {onMounted, reactive} from "vue";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ apptotal: {},
+ userAppCount: [{}],
+ appInfoData: [{appId: "", userName: "", updateTime: ""}]
+ })
+
+ async function getApplicationInfoListPage() {
+ const res = await getApplicationInfoList();
+ console.log(res)
+ pageData.appInfoData = res.data.data
+ }
+
+ async function getTotalForUserPage() {
+ const res = await getTotalForUser();
+ console.log(res)
+ pageData.userAppCount = res.data.data
+ }
+
+ async function getAppTotalPage() {
+ const res = await getAppTotal();
+ pageData.apptotal = res.data.data
+ }
+
+ onMounted(() => {
+ getApplicationInfoListPage();
+ getTotalForUserPage();
+ getAppTotalPage();
+ })
+ return {pageData}
+ }
+}
+</script>
+
+<style>
+.appcnt {
+ font-family: "Lantinghei SC";
+ font-style: normal;
+ font-weight: bolder;
+ font-size: 30px;
+}
+</style>
diff --git a/dashboard/src/main/webapp/src/components/CoordinatorServerPage.vue
b/dashboard/src/main/webapp/src/components/CoordinatorServerPage.vue
new file mode 100644
index 000000000..76d9e657a
--- /dev/null
+++ b/dashboard/src/main/webapp/src/components/CoordinatorServerPage.vue
@@ -0,0 +1,149 @@
+<!--
+ ~ 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="Coordinator Server" 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">
+ <Platform/>
+ </el-icon>
+ Coordinatro ID
+ </div>
+ </template>
+ {{ pageData.serverInfo.coordinatorId }}
+ </el-descriptions-item>
+ <el-descriptions-item>
+ <template #label>
+ <div class="cell-item">
+ <el-icon :style="iconStyle">
+ <Link/>
+ </el-icon>
+ IP Address
+ </div>
+ </template>
+ {{ pageData.serverInfo.serverIp }}
+ </el-descriptions-item>
+ <el-descriptions-item>
+ <template #label>
+ <div class="cell-item">
+ <el-icon :style="iconStyle">
+ <Wallet/>
+ </el-icon>
+ Coordinator Server Port
+ </div>
+ </template>
+ {{ pageData.serverInfo.serverPort }}
+ </el-descriptions-item>
+ <el-descriptions-item>
+ <template #label>
+ <div class="cell-item">
+ <el-icon :style="iconStyle">
+ <Wallet/>
+ </el-icon>
+ Coordinator Web Port
+ </div>
+ </template>
+ {{ pageData.serverInfo.serverWebPort }}
+ </el-descriptions-item>
+ </el-descriptions>
+ </div>
+ </el-collapse-item>
+ <el-collapse-item title="Coordinator Properties" name="2">
+ <el-table :data="pageData.tableData" style="width: 100%">
+ <el-table-column prop="argumentKey" label="Name" min-width="380"/>
+ <el-table-column prop="argumentValue" label="Value" min-width="380"/>
+ </el-table>
+ </el-collapse-item>
+ </el-collapse>
+ </div>
+</template>
+
+<script>
+import {ref, reactive, computed, onMounted} from 'vue'
+import {getCoordinatorConf, getCoordinatorServerInfo} from "@/api/api";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ activeNames: ['1', '2'],
+ tableData: [],
+ serverInfo: {}
+ }
+ );
+ async function getCoordinatorServerConfPage() {
+ const res = await getCoordinatorConf();
+ pageData.serverInfo = res.data.data
+ }
+ async function getCoorServerInfo() {
+ const res = await getCoordinatorServerInfo();
+ pageData.tableData = res.data.data
+ }
+ onMounted(() => {
+ getCoordinatorServerConfPage();
+ getCoorServerInfo();
+ })
+
+ 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,
+ }
+ })
+ return {
+ pageData,
+ iconStyle,
+ blockMargin,
+ size
+ }
+ }
+}
+</script>
+<style>
+.cell-item {
+ display: flex;
+ align-items: center;
+}
+
+.margin-top {
+ margin-top: 20px;
+}
+</style>
diff --git a/dashboard/src/main/webapp/src/components/LayoutPage.vue
b/dashboard/src/main/webapp/src/components/LayoutPage.vue
new file mode 100644
index 000000000..eb370829e
--- /dev/null
+++ b/dashboard/src/main/webapp/src/components/LayoutPage.vue
@@ -0,0 +1,98 @@
+<!--
+ ~ 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="common-layout">
+ <el-container>
+ <el-header>
+ <el-row>
+ <el-col :span="24">
+ <el-menu
+ :default-active="activeIndex1"
+ class="el-menu-demo"
+ mode="horizontal"
+ background-color="#20B2AA"
+ box-shadow="0 -2px 8px 0 rgba(0,0,0,0.12)"
+ text-color="#fff"
+ active-text-color="#ffd04b"
+ @select="handleSelect">
+ <el-menu-item index="0">
+ <div class="unffilelogo">
+ <img src="../assets/uniffle-logo.png" alt="unffile">
+ </div>
+ </el-menu-item>
+ <router-link to="/coordinatorserverpage">
+ <el-menu-item index="1">
+ Coordinator
+ </el-menu-item>
+ </router-link>
+ <router-link to="/shuffleserverpage">
+ <el-menu-item index="2">
+ Shuffle Server
+ </el-menu-item>
+ </router-link>
+ <router-link to="/applicationpage">
+ <el-menu-item index="3">
+ Application
+ </el-menu-item>
+ </router-link>
+ </el-menu>
+ </el-col>
+ </el-row>
+ </el-header>
+ <el-main>
+ <router-view></router-view>
+ </el-main>
+ </el-container>
+ </div>
+</template>
+
+<script>
+import {ref, onMounted} from 'vue'
+
+export default {
+ setup() {
+ const activeIndex1 = ref('1')
+
+ function handleSelect() {
+ localStorage.setItem("menuId", JSON.stringify(activeIndex1))
+ }
+
+ onMounted(() => {
+
+ })
+ return {activeIndex1, handleSelect}
+ }
+}
+</script>
+
+<style>
+a {
+ text-decoration: none;
+ color: white;
+}
+
+.unffilelogo {
+ background-color: #20B2AA;
+ height: 100%;
+ position: relative;
+}
+
+.unffilelogo > img {
+ height: 55px;
+}
+</style>
diff --git a/dashboard/src/main/webapp/src/components/ShuffleServerPage.vue
b/dashboard/src/main/webapp/src/components/ShuffleServerPage.vue
new file mode 100644
index 000000000..4f319d0e2
--- /dev/null
+++ b/dashboard/src/main/webapp/src/components/ShuffleServerPage.vue
@@ -0,0 +1,184 @@
+<!--
+ ~ 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>
+ <el-row :gutter="20">
+ <el-col :span="4">
+ <router-link to="/shuffleserverpage/activeNodeList">
+ <el-card class="box-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span class="cardtile">Active</span>
+ </div>
+ </template>
+ <div class="activenode">{{
dataList.allshuffleServerSize.activeNode }}</div>
+ </el-card>
+ </router-link>
+ </el-col>
+ <el-col :span="4">
+ <router-link to="/shuffleserverpage/decommissioningNodeList">
+ <el-card class="box-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span class="cardtile">Decommissioning</span>
+ </div>
+ </template>
+ <div class="decommissioningnode">{{
dataList.allshuffleServerSize.decommissioningNode }}</div>
+ </el-card>
+ </router-link>
+ </el-col>
+ <el-col :span="4">
+ <router-link to="/shuffleserverpage/decommissionedNodeList">
+ <el-card class="box-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span class="cardtile">Decommissioned</span>
+ </div>
+ </template>
+ <div class="decommissionednode">{{
dataList.allshuffleServerSize.decommissionedNode }}</div>
+ </el-card>
+ </router-link>
+ </el-col>
+ <el-col :span="4">
+ <router-link to="/shuffleserverpage/lostNodeList">
+ <el-card class="box-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span class="cardtile">Lost</span>
+ </div>
+ </template>
+ <div class="lostnode">{{ dataList.allshuffleServerSize.lostNode
}}</div>
+ </el-card>
+ </router-link>
+ </el-col>
+ <el-col :span="4">
+ <router-link to="/shuffleserverpage/unhealthyNodeList">
+ <el-card class="box-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span class="cardtile">Unhealthy</span>
+ </div>
+ </template>
+ <div class="unhealthynode">{{
dataList.allshuffleServerSize.unhealthyNode }}</div>
+ </el-card>
+ </router-link>
+ </el-col>
+ <el-col :span="4">
+ <router-link to="/shuffleserverpage/excludeNodeList">
+ <el-card class="box-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <span class="cardtile">Excludes</span>
+ </div>
+ </template>
+ <div class="excludesnode">{{
dataList.allshuffleServerSize.excludesNode }}</div>
+ </el-card>
+ </router-link>
+ </el-col>
+ </el-row>
+ <el-divider/>
+ <el-row :gutter="24">
+ <div style="width: 100%">
+ <router-view></router-view>
+ </div>
+ </el-row>
+ </div>
+</template>
+
+<script>
+import {onMounted, reactive} from 'vue'
+import {getShufflegetStatusTotal} from "@/api/api";
+
+export default {
+ setup() {
+ const dataList = reactive({
+ allshuffleServerSize: {
+ activeNode: 0,
+ decommissionedNode: 0,
+ decommissioningNode: 0,
+ excludesNode: 0,
+ lostNode: 0,
+ unhealthyNode: 0
+ }
+ })
+
+ async function getShufflegetStatusTotalPage() {
+ const res = await getShufflegetStatusTotal();
+ dataList.allshuffleServerSize = res.data.data
+ }
+
+ onMounted(() => {
+ getShufflegetStatusTotalPage();
+ })
+ return {dataList}
+ }
+}
+</script>
+
+<style scoped>
+.cardtile {
+ font-size: larger;
+}
+
+.activenode {
+ font-family: "Lantinghei SC";
+ font-style: normal;
+ font-weight: bolder;
+ font-size: 30px;
+ color: green;
+}
+
+.decommissioningnode {
+ font-family: "Lantinghei SC";
+ font-style: normal;
+ font-weight: bolder;
+ font-size: 30px;
+ color: #00c4ff;
+}
+
+.decommissionednode {
+ font-family: "Lantinghei SC";
+ font-style: normal;
+ font-weight: bolder;
+ font-size: 30px;
+ color: blue;
+}
+
+.lostnode {
+ font-family: "Lantinghei SC";
+ font-style: normal;
+ font-weight: bolder;
+ font-size: 30px;
+ color: red;
+}
+
+.unhealthynode {
+ font-family: "Lantinghei SC";
+ font-style: normal;
+ font-weight: bolder;
+ font-size: 30px;
+ color: #ff8800;
+}
+
+.excludesnode {
+ font-family: "Lantinghei SC";
+ font-style: normal;
+ font-weight: bolder;
+ font-size: 30px;
+}
+</style>
diff --git
a/dashboard/src/main/webapp/src/components/shufflecomponent/ActiveNodeListPage.vue
b/dashboard/src/main/webapp/src/components/shufflecomponent/ActiveNodeListPage.vue
new file mode 100644
index 000000000..128fe0948
--- /dev/null
+++
b/dashboard/src/main/webapp/src/components/shufflecomponent/ActiveNodeListPage.vue
@@ -0,0 +1,70 @@
+<!--
+ ~ 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>
+ <el-table :data="pageData.tableData" height="550" style="width: 100%">
+ <el-table-column prop="id" label="Id" min-width="180"/>
+ <el-table-column prop="ip" label="IP" min-width="80"/>
+ <el-table-column prop="grpcPort" label="Port" min-width="80"/>
+ <el-table-column prop="usedMemory" label="UsedMem" min-width="80"/>
+ <el-table-column prop="preAllocatedMemory" label="PreAllocatedMem"
min-width="80"/>
+ <el-table-column prop="availableMemory" label="AvailableMem"
min-width="80"/>
+ <el-table-column prop="totalMemory" label="TotalMem" min-width="80"/>
+ <el-table-column prop="eventNumInFlush" label="FlushNum" min-width="80"/>
+ <el-table-column prop="status" label="Status" min-width="80"/>
+ <el-table-column prop="timestamp" label="ResigerTime" min-width="80"/>
+ <el-table-column prop="tags" label="Tags" min-width="80"/>
+ </el-table>
+ </div>
+</template>
+<script>
+import {onMounted, reactive} from 'vue'
+import { getShuffleActiveNodes} from "@/api/api";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ tableData: [
+ {
+ id: "",
+ ip: "",
+ grpcPort: 0,
+ usedMemory: 0,
+ preAllocatedMemory: 0,
+ availableMemory: 0,
+ totalMemory: 0,
+ eventNumInFlush: 0,
+ tags: "",
+ status: "",
+ timestamp: ""
+ }
+ ]
+ })
+
+ async function getShuffleActiveNodesPage() {
+ const res = await getShuffleActiveNodes();
+ pageData.tableData = res.data.data
+ }
+
+ onMounted(() => {
+ getShuffleActiveNodesPage();
+ })
+ return {pageData}
+ }
+}
+</script>
diff --git
a/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissionednodeListPage.vue
b/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissionednodeListPage.vue
new file mode 100644
index 000000000..d22fee178
--- /dev/null
+++
b/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissionednodeListPage.vue
@@ -0,0 +1,70 @@
+<!--
+ ~ 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>
+ <el-table :data="pageData.tableData" height="550" style="width: 100%">
+ <el-table-column prop="id" label="Id" min-width="180"/>
+ <el-table-column prop="ip" label="IP" min-width="80"/>
+ <el-table-column prop="grpcPort" label="Port" min-width="80"/>
+ <el-table-column prop="usedMemory" label="UsedMem" min-width="80"/>
+ <el-table-column prop="preAllocatedMemory" label="PreAllocatedMem"
min-width="80"/>
+ <el-table-column prop="availableMemory" label="AvailableMem"
min-width="80"/>
+ <el-table-column prop="totalMemory" label="TotalMem" min-width="80"/>
+ <el-table-column prop="eventNumInFlush" label="FlushNum" min-width="80"/>
+ <el-table-column prop="status" label="Status" min-width="80"/>
+ <el-table-column prop="timestamp" label="ResigerTime" min-width="80"/>
+ <el-table-column prop="tags" label="Tags" min-width="80"/>
+ </el-table>
+ </div>
+</template>
+<script>
+import {onMounted, reactive} from 'vue'
+import { getShuffleDecommissionedList } from "@/api/api";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ tableData: [
+ {
+ id: "",
+ ip: "",
+ grpcPort: 0,
+ usedMemory: 0,
+ preAllocatedMemory: 0,
+ availableMemory: 0,
+ totalMemory: 0,
+ eventNumInFlush: 0,
+ tags: "",
+ status: "",
+ timestamp: ""
+ }
+ ]
+ })
+
+ async function getShuffleDecommissionedListPage() {
+ const res = await getShuffleDecommissionedList();
+ pageData.tableData = res.data.data
+ }
+
+ onMounted(() => {
+ getShuffleDecommissionedListPage();
+ })
+ return {pageData}
+ }
+}
+</script>
diff --git
a/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissioningNodeListPage.vue
b/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissioningNodeListPage.vue
new file mode 100644
index 000000000..08e4063aa
--- /dev/null
+++
b/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissioningNodeListPage.vue
@@ -0,0 +1,70 @@
+<!--
+ ~ 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>
+ <el-table :data="pageData.tableData" height="550" style="width: 100%">
+ <el-table-column prop="id" label="Id" min-width="180"/>
+ <el-table-column prop="ip" label="IP" min-width="80"/>
+ <el-table-column prop="grpcPort" label="Port" min-width="80"/>
+ <el-table-column prop="usedMemory" label="UsedMem" min-width="80"/>
+ <el-table-column prop="preAllocatedMemory" label="PreAllocatedMem"
min-width="80"/>
+ <el-table-column prop="availableMemory" label="AvailableMem"
min-width="80"/>
+ <el-table-column prop="totalMemory" label="TotalMem" min-width="80"/>
+ <el-table-column prop="eventNumInFlush" label="FlushNum" min-width="80"/>
+ <el-table-column prop="status" label="Status" min-width="80"/>
+ <el-table-column prop="timestamp" label="ResigerTime" min-width="80"/>
+ <el-table-column prop="tags" label="Tags" min-width="80"/>
+ </el-table>
+ </div>
+</template>
+<script>
+import {onMounted, reactive} from 'vue'
+import { getShuffleDecommissioningList } from "@/api/api";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ tableData: [
+ {
+ id: "",
+ ip: "",
+ grpcPort: 0,
+ usedMemory: 0,
+ preAllocatedMemory: 0,
+ availableMemory: 0,
+ totalMemory: 0,
+ eventNumInFlush: 0,
+ tags: "",
+ status: "",
+ timestamp: ""
+ }
+ ]
+ })
+
+ async function getShuffleDecommissioningListPage() {
+ const res = await getShuffleDecommissioningList();
+ pageData.tableData = res.data.data
+ }
+
+ onMounted(() => {
+ getShuffleDecommissioningListPage();
+ })
+ return {pageData}
+ }
+}
+</script>
diff --git
a/dashboard/src/main/webapp/src/components/shufflecomponent/ExcludeNodeList.vue
b/dashboard/src/main/webapp/src/components/shufflecomponent/ExcludeNodeList.vue
new file mode 100644
index 000000000..6ba95524f
--- /dev/null
+++
b/dashboard/src/main/webapp/src/components/shufflecomponent/ExcludeNodeList.vue
@@ -0,0 +1,49 @@
+<!--
+ ~ 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>
+ <el-table :data="pageData.tableData" height="550" style="width: 100%">
+ <el-table-column prop="excludeNodeId" label="ExcludeNodeId"
min-width="180"/>
+ </el-table>
+ </div>
+</template>
+<script>
+import {onMounted, reactive} from 'vue'
+import { getShuffleExcludeNodes } from "@/api/api";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ tableData: [
+ {
+ excludeNodeId:""
+ }
+ ]
+ })
+ async function getShuffleExcludeNodesPage() {
+ const res = await getShuffleExcludeNodes();
+ pageData.tableData = res.data.data
+ }
+
+ onMounted(() => {
+ getShuffleExcludeNodesPage();
+ })
+ return {pageData}
+ }
+}
+</script>
diff --git
a/dashboard/src/main/webapp/src/components/shufflecomponent/LostNodeList.vue
b/dashboard/src/main/webapp/src/components/shufflecomponent/LostNodeList.vue
new file mode 100644
index 000000000..febbe9881
--- /dev/null
+++ b/dashboard/src/main/webapp/src/components/shufflecomponent/LostNodeList.vue
@@ -0,0 +1,70 @@
+<!--
+ ~ 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>
+ <el-table :data="pageData.tableData" height="550" style="width: 100%">
+ <el-table-column prop="id" label="Id" min-width="180"/>
+ <el-table-column prop="ip" label="IP" min-width="80"/>
+ <el-table-column prop="grpcPort" label="Port" min-width="80"/>
+ <el-table-column prop="usedMemory" label="UsedMem" min-width="80"/>
+ <el-table-column prop="preAllocatedMemory" label="PreAllocatedMem"
min-width="80"/>
+ <el-table-column prop="availableMemory" label="AvailableMem"
min-width="80"/>
+ <el-table-column prop="totalMemory" label="TotalMem" min-width="80"/>
+ <el-table-column prop="eventNumInFlush" label="FlushNum" min-width="80"/>
+ <el-table-column prop="status" label="Status" min-width="80"/>
+ <el-table-column prop="timestamp" label="ResigerTime" min-width="80"/>
+ <el-table-column prop="tags" label="Tags" min-width="80"/>
+ </el-table>
+ </div>
+</template>
+<script>
+import {onMounted, reactive} from 'vue'
+import { getShuffleLostList } from "@/api/api";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ tableData: [
+ {
+ id: "",
+ ip: "",
+ grpcPort: 0,
+ usedMemory: 0,
+ preAllocatedMemory: 0,
+ availableMemory: 0,
+ totalMemory: 0,
+ eventNumInFlush: 0,
+ tags: "",
+ status: "",
+ timestamp: ""
+ }
+ ]
+ })
+
+ async function getShuffleLostListPage() {
+ const res = await getShuffleLostList();
+ pageData.tableData = res.data.data
+ }
+
+ onMounted(() => {
+ getShuffleLostListPage();
+ })
+ return {pageData}
+ }
+}
+</script>
diff --git
a/dashboard/src/main/webapp/src/components/shufflecomponent/UnhealthyNodeListPage.vue
b/dashboard/src/main/webapp/src/components/shufflecomponent/UnhealthyNodeListPage.vue
new file mode 100644
index 000000000..c80ec3900
--- /dev/null
+++
b/dashboard/src/main/webapp/src/components/shufflecomponent/UnhealthyNodeListPage.vue
@@ -0,0 +1,70 @@
+<!--
+ ~ 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>
+ <el-table :data="pageData.tableData" height="550" style="width: 100%">
+ <el-table-column prop="id" label="Id" min-width="180"/>
+ <el-table-column prop="ip" label="IP" min-width="80"/>
+ <el-table-column prop="grpcPort" label="Port" min-width="80"/>
+ <el-table-column prop="usedMemory" label="UsedMem" min-width="80"/>
+ <el-table-column prop="preAllocatedMemory" label="PreAllocatedMem"
min-width="80"/>
+ <el-table-column prop="availableMemory" label="AvailableMem"
min-width="80"/>
+ <el-table-column prop="totalMemory" label="TotalMem" min-width="80"/>
+ <el-table-column prop="eventNumInFlush" label="FlushNum" min-width="80"/>
+ <el-table-column prop="status" label="Status" min-width="80"/>
+ <el-table-column prop="timestamp" label="ResigerTime" min-width="80"/>
+ <el-table-column prop="tags" label="Tags" min-width="80"/>
+ </el-table>
+ </div>
+</template>
+<script>
+import {onMounted, reactive} from 'vue'
+import { getShuffleUnhealthyList } from "@/api/api";
+
+export default {
+ setup() {
+ const pageData = reactive({
+ tableData: [
+ {
+ id: "",
+ ip: "",
+ grpcPort: 0,
+ usedMemory: 0,
+ preAllocatedMemory: 0,
+ availableMemory: 0,
+ totalMemory: 0,
+ eventNumInFlush: 0,
+ tags: "",
+ status: "",
+ timestamp: ""
+ }
+ ]
+ })
+
+ async function getShuffleUnhealthyListPage() {
+ const res = await getShuffleUnhealthyList();
+ pageData.tableData = res.data.data
+ }
+
+ onMounted(() => {
+ getShuffleUnhealthyListPage();
+ })
+ return {pageData}
+ }
+}
+</script>
diff --git a/dashboard/src/main/webapp/src/main.js
b/dashboard/src/main/webapp/src/main.js
new file mode 100644
index 000000000..e6c92fb81
--- /dev/null
+++ b/dashboard/src/main/webapp/src/main.js
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+import {createApp} from 'vue';
+import App from './App.vue'
+import ElementPlus from 'element-plus'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import 'element-plus/dist/index.css'
+import router from "@/router";
+const app = createApp(App)
+Object.keys(ElementPlusIconsVue).forEach(key => {
+ app.component(key, ElementPlusIconsVue[key])
+})
+app.use(router).use(ElementPlus).mount('#app')
diff --git a/dashboard/src/main/webapp/src/router/index.js
b/dashboard/src/main/webapp/src/router/index.js
new file mode 100644
index 000000000..6bd21c360
--- /dev/null
+++ b/dashboard/src/main/webapp/src/router/index.js
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+import {createRouter, createWebHashHistory} from "vue-router"
+import ApplicationPage from '@/components/ApplicationPage'
+import CoordinatorServerPage from '@/components/CoordinatorServerPage'
+import ShuffleServerPage from '@/components/ShuffleServerPage'
+import ActiveNodeListPage from
'@/components/shufflecomponent/ActiveNodeListPage'
+import DecommissioningNodeListPage from
'@/components/shufflecomponent/DecommissioningNodeListPage'
+import DecommissionednodeListPage from
'@/components/shufflecomponent/DecommissionednodeListPage'
+import LostNodeList from '@/components/shufflecomponent/LostNodeList'
+import UnhealthyNodeListPage from
'@/components/shufflecomponent/UnhealthyNodeListPage'
+import ExcludeNodeList from '@/components/shufflecomponent/ExcludeNodeList'
+
+const routes = [
+ {
+ path: '/coordinatorserverpage',
+ name: 'coordinatorserverpage',
+ component: CoordinatorServerPage
+ },
+ {
+ path: '/shuffleserverpage',
+ name: 'shuffleserverpage',
+ component: ShuffleServerPage,
+ redirect: '/shuffleserverpage/activeNodeList',
+ children: [
+ {path: '/shuffleserverpage/activeNodeList', name:
"activeNodeList", component: ActiveNodeListPage},
+ {
+ path: '/shuffleserverpage/decommissioningNodeList',
+ name: "decommissioningNodeList",
+ component: DecommissioningNodeListPage
+ },
+ {
+ path: '/shuffleserverpage/decommissionedNodeList',
+ name: "decommissionedNodeList",
+ component: DecommissionednodeListPage
+ },
+ {path: '/shuffleserverpage/lostNodeList', name: "lostNodeList",
component: LostNodeList},
+ {path: '/shuffleserverpage/unhealthyNodeList', name:
"unhealthyNodeList", component: UnhealthyNodeListPage},
+ {path: '/shuffleserverpage/excludeNodeList', name:
"excludeNodeList", component: ExcludeNodeList},
+ ]
+ },
+ {
+ path: '/applicationpage',
+ name: 'applicationpage',
+ component: ApplicationPage,
+ },
+]
+
+const router = createRouter({
+ history: createWebHashHistory(),
+ routes
+})
+
+export default router
diff --git a/dashboard/src/main/webapp/src/utils/http.js
b/dashboard/src/main/webapp/src/utils/http.js
new file mode 100644
index 000000000..472f9ef1c
--- /dev/null
+++ b/dashboard/src/main/webapp/src/utils/http.js
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+import request from "@/utils/request";
+const http = {
+ get(url, params, headers) {
+ const config = {
+ method: 'GET',
+ url: url,
+ params: params,
+ headers: headers
+ }
+ return request(config);
+ },
+ post(url, data, headers) {
+ const config = {
+ method: 'POST',
+ url: url,
+ data: data,
+ headers: headers
+ }
+ return request(config);
+ }
+}
+export default http
diff --git a/dashboard/src/main/webapp/src/utils/request.js
b/dashboard/src/main/webapp/src/utils/request.js
new file mode 100644
index 000000000..2e6b3c400
--- /dev/null
+++ b/dashboard/src/main/webapp/src/utils/request.js
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+import axios from 'axios'
+
+const axiosInstance = axios.create({
+ baseURL: '/api',
+ timeout: 10000,
+ headers: {}
+})
+
+axiosInstance.interceptors.request.use(config => {
+ config.headers['Content-type'] = 'application/json';
+ config.headers['Accept'] = 'application/json';
+ return config;
+})
+
+axiosInstance.interceptors.response.use(response => {
+ return response;
+}, error => {
+ return error;
+})
+export default axiosInstance;
diff --git a/dashboard/src/main/webapp/vue.config.js
b/dashboard/src/main/webapp/vue.config.js
new file mode 100644
index 000000000..64ab67d57
--- /dev/null
+++ b/dashboard/src/main/webapp/vue.config.js
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+module.exports ={
+ devServer:{
+ proxy:{
+ '/api':{
+ target:'http://localhost:9528',
+ changeOrigin:true
+ }
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index efbc46e88..944fc575e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -140,6 +140,7 @@
<module>client</module>
<module>integration-test/common</module>
<module>cli</module>
+ <module>dashboard</module>
</modules>
<dependencies>