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:
    
![image](https://github.com/apache/incubator-uniffle/assets/33595968/11c3fdaf-d2bd-4c67-8f90-9936de1d75fa)
    
![image](https://github.com/apache/incubator-uniffle/assets/33595968/f5ee5037-9f2c-4955-b086-f56520b02601)
    
![image](https://github.com/apache/incubator-uniffle/assets/33595968/c1d9c1b5-718b-47e8-a7f4-0d5a84188f2a)
    
    ### 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>

Reply via email to