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

xianjingfeng 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 fe2efe096 [#1401][part-1] The dashboard supports multiple Coordinator 
links. (#1449)
fe2efe096 is described below

commit fe2efe096e1c35395269c3f181aef15927aafa69
Author: yl09099 <[email protected]>
AuthorDate: Wed Jun 12 15:54:45 2024 +0800

    [#1401][part-1] The dashboard supports multiple Coordinator links. (#1449)
    
    ### What changes were proposed in this pull request?
    After multiple coordinators are deployed, a dashboard is added to link 
multiple coordinators.
    
    ### Why are the changes needed?
    Fix: #1401
    
    ### Does this PR introduce any user-facing change?
    No.
    
    ### How was this patch tested?
    No.
---
 conf/dashboard.conf                                | 19 +++++
 .../uniffle/dashboard/web/JettyServerFront.java    | 19 +++--
 .../dashboard/web/proxy/WebProxyServlet.java       | 12 +--
 .../dashboard/web/resource/BaseResource.java}      | 37 +++++----
 .../web/resource/CoordinatorResource.java          | 47 ++++++++++++
 .../uniffle/dashboard/web/resource/Response.java   | 70 +++++++++++++++++
 .../dashboard/web/resource/WebResource.java}       | 14 +++-
 .../dashboard/web/utils/DashboardUtils.java        | 45 +++++++++++
 dashboard/src/main/webapp/package.json             |  3 +-
 dashboard/src/main/webapp/src/api/api.js           | 54 +++++++------
 .../src/main/webapp/src/components/LayoutPage.vue  | 88 ++++++++++++++++------
 dashboard/src/main/webapp/src/main.js              |  4 +-
 .../src/{components => pages}/ApplicationPage.vue  | 20 ++++-
 .../CoordinatorServerPage.vue                      | 24 +++++-
 .../{components => pages}/ShuffleServerPage.vue    | 22 +++++-
 .../serverstatus}/ActiveNodeListPage.vue           | 17 ++++-
 .../serverstatus}/DecommissionednodeListPage.vue   | 16 +++-
 .../serverstatus}/DecommissioningNodeListPage.vue  | 18 ++++-
 .../serverstatus}/ExcludeNodeList.vue              | 16 +++-
 .../serverstatus}/LostNodeList.vue                 | 19 ++++-
 .../serverstatus}/UnhealthyNodeListPage.vue        | 17 ++++-
 dashboard/src/main/webapp/src/router/index.js      | 24 +++---
 .../request.js => store/useCurrentServerStore.js}  | 27 +++----
 dashboard/src/main/webapp/src/utils/http.js        | 43 +++++++----
 dashboard/src/main/webapp/src/utils/request.js     | 43 +++++++++--
 dashboard/src/main/webapp/vue.config.js            | 12 +++
 .../dashboard/web/utils/DashboardUtilsTest.java}   | 32 ++++----
 27 files changed, 599 insertions(+), 163 deletions(-)

diff --git a/conf/dashboard.conf b/conf/dashboard.conf
new file mode 100644
index 000000000..0b4658261
--- /dev/null
+++ b/conf/dashboard.conf
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+rss.dashboard.http.port 19997
+coordinator.web.address 
http://coordinator.hostname00:19998/,http://coordinator.hostname01:19998/,http://coordinator.hostname02:19998/
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
index 723ac16ae..bef348cce 100644
--- 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/JettyServerFront.java
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/JettyServerFront.java
@@ -18,11 +18,13 @@
 package org.apache.uniffle.dashboard.web;
 
 import java.net.BindException;
+import java.util.Map;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.jetty.proxy.ProxyServlet;
+import 
org.apache.hbase.thirdparty.org.glassfish.jersey.server.ServerProperties;
+import 
org.apache.hbase.thirdparty.org.glassfish.jersey.servlet.ServletContainer;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -44,6 +46,7 @@ import org.apache.uniffle.common.util.ExitUtils;
 import org.apache.uniffle.common.util.ThreadUtils;
 import org.apache.uniffle.dashboard.web.config.DashboardConf;
 import org.apache.uniffle.dashboard.web.proxy.WebProxyServlet;
+import org.apache.uniffle.dashboard.web.utils.DashboardUtils;
 
 public class JettyServerFront {
 
@@ -89,7 +92,10 @@ public class JettyServerFront {
     HandlerList handlers = new HandlerList();
     ResourceHandler resourceHandler = addResourceHandler();
     String coordinatorWebAddress = 
conf.getString(DashboardConf.COORDINATOR_WEB_ADDRESS);
-    ServletContextHandler servletContextHandler = 
addProxyHandler(coordinatorWebAddress);
+    Map<String, String> stringStringMap =
+        DashboardUtils.convertAddressesStrToMap(coordinatorWebAddress);
+
+    ServletContextHandler servletContextHandler = 
addProxyHandler(stringStringMap);
     handlers.setHandlers(new Handler[] {resourceHandler, 
servletContextHandler});
     server.setHandler(handlers);
   }
@@ -103,11 +109,14 @@ public class JettyServerFront {
     return resourceHandler;
   }
 
-  private static ServletContextHandler addProxyHandler(String 
coordinatorWebAddress) {
-    ProxyServlet proxyServlet = new WebProxyServlet(coordinatorWebAddress);
-    ServletHolder holder = new ServletHolder(proxyServlet);
+  private ServletContextHandler addProxyHandler(Map<String, String> 
coordinatorServerAddresses) {
     ServletContextHandler contextHandler = new ServletContextHandler();
+    ServletHolder holder = new ServletHolder(new 
WebProxyServlet(coordinatorServerAddresses));
     contextHandler.addServlet(holder, "/api/*");
+    ServletHolder servletHolder = 
contextHandler.addServlet(ServletContainer.class, "/*");
+    servletHolder.setInitParameter(
+        ServerProperties.PROVIDER_PACKAGES, 
"org.apache.uniffle.dashboard.web.resource");
+    contextHandler.setAttribute("coordinatorServerAddresses", 
coordinatorServerAddresses);
     return contextHandler;
   }
 
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
index 2877cc6ef..0bb980c91 100644
--- 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
@@ -17,6 +17,7 @@
 
 package org.apache.uniffle.dashboard.web.proxy;
 
+import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -28,12 +29,12 @@ import org.slf4j.LoggerFactory;
 
 public class WebProxyServlet extends ProxyServlet {
 
-  private String targetAddress;
-
   private static final Logger LOG = 
LoggerFactory.getLogger(WebProxyServlet.class);
 
-  public WebProxyServlet(String targetAddress) {
-    this.targetAddress = targetAddress;
+  private Map<String, String> coordinatorServerAddressesMap;
+
+  public WebProxyServlet(Map<String, String> coordinatorServerAddressesMap) {
+    this.coordinatorServerAddressesMap = coordinatorServerAddressesMap;
   }
 
   @Override
@@ -41,8 +42,9 @@ public class WebProxyServlet extends ProxyServlet {
     if (!validateDestination(clientRequest.getServerName(), 
clientRequest.getServerPort())) {
       return null;
     }
+    String targetAddress =
+        
coordinatorServerAddressesMap.get(clientRequest.getHeader("targetAddress"));
     StringBuilder target = new StringBuilder();
-
     if (targetAddress.endsWith("/")) {
       targetAddress = targetAddress.substring(0, targetAddress.length() - 1);
     }
diff --git a/dashboard/src/main/webapp/src/utils/http.js 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/BaseResource.java
similarity index 61%
copy from dashboard/src/main/webapp/src/utils/http.js
copy to 
dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/BaseResource.java
index 472f9ef1c..5ed197302 100644
--- a/dashboard/src/main/webapp/src/utils/http.js
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/BaseResource.java
@@ -15,25 +15,22 @@
  * 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);
+package org.apache.uniffle.dashboard.web.resource;
+
+import java.util.concurrent.Callable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class BaseResource {
+  private static final Logger LOG = 
LoggerFactory.getLogger(BaseResource.class);
+
+  protected <T> Response<T> execute(Callable<T> callable) {
+    try {
+      return Response.success(callable.call());
+    } catch (Throwable e) {
+      LOG.error("Call failure:", e);
+      return Response.fail(e.getMessage());
     }
+  }
 }
-export default http
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/CoordinatorResource.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/CoordinatorResource.java
new file mode 100644
index 000000000..2aed5ca0d
--- /dev/null
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/CoordinatorResource.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.uniffle.dashboard.web.resource;
+
+import java.util.Map;
+import javax.servlet.ServletContext;
+
+import org.apache.hbase.thirdparty.javax.ws.rs.GET;
+import org.apache.hbase.thirdparty.javax.ws.rs.Path;
+import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.Context;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
+
+@Produces({MediaType.APPLICATION_JSON})
+public class CoordinatorResource extends BaseResource {
+
+  @Context protected ServletContext servletContext;
+
+  @GET
+  @Path("/coordinatorServers")
+  public Response<Map<String, String>> getCoordinatorServers() {
+    return execute(
+        () -> {
+          Map<String, String> coordinatorServerAddresses = 
getCoordinatorServerAddresses();
+          return coordinatorServerAddresses;
+        });
+  }
+
+  private Map<String, String> getCoordinatorServerAddresses() {
+    return (Map<String, String>) 
servletContext.getAttribute("coordinatorServerAddresses");
+  }
+}
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/Response.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/Response.java
new file mode 100644
index 000000000..ef71a320b
--- /dev/null
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/Response.java
@@ -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.
+ */
+
+package org.apache.uniffle.dashboard.web.resource;
+
+public class Response<T> {
+  private static final int SUCCESS_CODE = 0;
+  private static final int ERROR_CODE = -1;
+  private int code;
+  private T data;
+  private String msg;
+
+  public Response() {}
+
+  public Response(int code, T data, String msg) {
+    this.code = code;
+    this.data = data;
+    this.msg = msg;
+  }
+
+  public static <T> Response<T> success(T data) {
+    return new Response<>(SUCCESS_CODE, data, "success");
+  }
+
+  public static <T> Response<T> fail(String msg) {
+    return new Response<>(ERROR_CODE, null, msg);
+  }
+
+  public static <T> Response<T> fail(String msg, int code) {
+    return new Response<>(code, null, msg);
+  }
+
+  public int getCode() {
+    return code;
+  }
+
+  public void setCode(int code) {
+    this.code = code;
+  }
+
+  public T getData() {
+    return data;
+  }
+
+  public void setData(T data) {
+    this.data = data;
+  }
+
+  public String getErrMsg() {
+    return msg;
+  }
+
+  public void setErrMsg(String errMsg) {
+    this.msg = errMsg;
+  }
+}
diff --git a/dashboard/src/main/webapp/vue.config.js 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
similarity index 64%
copy from dashboard/src/main/webapp/vue.config.js
copy to 
dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
index a007fc8ba..aaab37001 100644
--- a/dashboard/src/main/webapp/vue.config.js
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/resource/WebResource.java
@@ -15,5 +15,17 @@
  * limitations under the License.
  */
 
-module.exports ={
+package org.apache.uniffle.dashboard.web.resource;
+
+import org.apache.hbase.thirdparty.javax.ws.rs.Path;
+import org.apache.hbase.thirdparty.javax.ws.rs.Produces;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
+
+@Path("web")
+@Produces({MediaType.APPLICATION_JSON})
+public class WebResource {
+  @Path("coordinator")
+  public Class<CoordinatorResource> getGainCoordinatorsResource() {
+    return CoordinatorResource.class;
+  }
 }
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/utils/DashboardUtils.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/utils/DashboardUtils.java
new file mode 100644
index 000000000..69ddc88da
--- /dev/null
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/utils/DashboardUtils.java
@@ -0,0 +1,45 @@
+/*
+ * 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.utils;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DashboardUtils {
+  private static final Logger LOG = 
LoggerFactory.getLogger(DashboardUtils.class);
+
+  public static Map<String, String> convertAddressesStrToMap(String 
coordinatorAddressesStr) {
+    HashMap<String, String> coordinatorAddressMap = Maps.newHashMap();
+    String[] coordinators = coordinatorAddressesStr.split(",");
+    for (String coordinator : coordinators) {
+      try {
+        URL coordinatorURL = new URL(coordinator);
+        coordinatorAddressMap.put(coordinatorURL.getHost(), coordinator);
+      } catch (MalformedURLException e) {
+        LOG.error("The coordinator address is abnormal.", e);
+      }
+    }
+    return coordinatorAddressMap;
+  }
+}
diff --git a/dashboard/src/main/webapp/package.json 
b/dashboard/src/main/webapp/package.json
index 820c7379a..0ab9bfba4 100644
--- a/dashboard/src/main/webapp/package.json
+++ b/dashboard/src/main/webapp/package.json
@@ -32,6 +32,7 @@
     "core-js": "^3.8.3",
     "element-plus": "^2.3.6",
     "moment": "^2.29.4",
+    "pinia": "^2.1.7",
     "rimraf": "^5.0.1",
     "vue": "^3.2.13",
     "vue-resource": "^1.5.3",
@@ -42,7 +43,7 @@
     "@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",
+    "@vue/cli-service": "^5.0.8",
     "eslint": "^7.32.0",
     "eslint-plugin-vue": "^8.0.3",
     "node-sass": "^9.0.0",
diff --git a/dashboard/src/main/webapp/src/api/api.js 
b/dashboard/src/main/webapp/src/api/api.js
index d70e24b2b..8d4c7cd6f 100644
--- a/dashboard/src/main/webapp/src/api/api.js
+++ b/dashboard/src/main/webapp/src/api/api.js
@@ -16,63 +16,67 @@
  */
 
 import http from "@/utils/http";
-
 // Create a Coordinator information interface
-export function getCoordinatorServerInfo(params){
-    return http.get('/coordinator/info', params,{})
+export function getCoordinatorServerInfo(params,headers) {
+    return http.get('/coordinator/info', params, headers, 0)
 }
 
 // Create a coordinator configuration file interface
-export function getCoordinatorConf(params){
-    return http.get('/coordinator/conf', params,{})
+export function getCoordinatorConf(params,headers) {
+    return http.get('/coordinator/conf', params, headers, 0)
 }
 
 // Create an interface for the total number of nodes
-export function getShufflegetStatusTotal(params){
-    return http.get('/server/nodes/summary', params,{})
+export function getShufflegetStatusTotal(params,headers) {
+    return http.get('/server/nodes/summary', params, headers, 0)
 }
 
 // Create an interface for activeNodes
-export function getShuffleActiveNodes(params){
-    return http.get('/server/nodes?status=active', params,{})
+export function getShuffleActiveNodes(params,headers) {
+    return http.get('/server/nodes?status=active', params, headers, 0)
 }
 
 // Create an interface for lostNodes
-export function getShuffleLostList(params){
-    return http.get('/server/nodes?status=lost', params,{})
+export function getShuffleLostList(params,headers) {
+    return http.get('/server/nodes?status=lost', params, headers, 0)
 }
 
 // Create an interface for unhealthyNodes
-export function getShuffleUnhealthyList(params){
-    return http.get('/server/nodes?status=unhealthy', params,{})
+export function getShuffleUnhealthyList(params,headers) {
+    return http.get('/server/nodes?status=unhealthy', params, headers, 0)
 }
 
 // Create an interface for decommissioningNodes
-export function getShuffleDecommissioningList(params){
-    return http.get('/server/nodes?status=decommissioning', params,{})
+export function getShuffleDecommissioningList(params,headers) {
+    return http.get('/server/nodes?status=decommissioning', params, headers, 0)
 }
 
 // Create an interface for decommissionedNodes
-export function getShuffleDecommissionedList(params){
-    return http.get('/server/nodes?status=decommissioned', params,{})
+export function getShuffleDecommissionedList(params,headers) {
+    return http.get('/server/nodes?status=decommissioned', params, headers, 0)
 }
 
 // Create an interface for excludeNodes
-export function getShuffleExcludeNodes(params){
-    return http.get('/server/nodes?status=excluded', params,{})
+export function getShuffleExcludeNodes(params,headers) {
+    return http.get('/server/nodes?status=excluded', params, headers, 0)
 }
 
 // Total number of interfaces for new App
-export function getAppTotal(params){
-    return http.get('/app/total', params,{})
+export function getAppTotal(params,headers) {
+    return http.get('/app/total', params, headers, 0)
 }
 
 // Create an interface for the app basic information list
-export function getApplicationInfoList(params){
-    return http.get('/app/appInfos', params,{})
+export function getApplicationInfoList(params,headers) {
+    return http.get('/app/appInfos', params, headers, 0)
 }
 
 // Create an interface for the number of apps for a user
-export function getTotalForUser(params){
-    return http.get('/app/userTotal', params,{})
+export function getTotalForUser(params,headers) {
+    return http.get('/app/userTotal', params, headers, 0)
+}
+
+// Obtain the configured coordinator server list
+export function getAllCoordinatorAddrees(params,headers) {
+    return http.get('/coordinator/coordinatorServers', params, headers, 1)
 }
diff --git a/dashboard/src/main/webapp/src/components/LayoutPage.vue 
b/dashboard/src/main/webapp/src/components/LayoutPage.vue
index eb370829e..aa24a3847 100644
--- a/dashboard/src/main/webapp/src/components/LayoutPage.vue
+++ b/dashboard/src/main/webapp/src/components/LayoutPage.vue
@@ -23,33 +23,46 @@
           <el-col :span="24">
             <el-menu
                 :default-active="activeIndex1"
+                router
                 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">
+                active-text-color="#ffd04b">
               <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 index="/coordinatorserverpage">
+                <el-icon><House /></el-icon>
+                <span>Coordinator</span>
+              </el-menu-item>
+              <el-menu-item index="/shuffleserverpage">
+                <el-icon><Monitor /></el-icon>
+                <span>Shuffle Server</span>
+              </el-menu-item>
+              <el-menu-item index="/applicationpage">
+                <el-icon><Coin /></el-icon>
+                <span>Application</span>
+              </el-menu-item>
+              <el-sub-menu index="">
+                <template #title>Switching server</template>
+                <el-menu-item
+                    v-for="item in hostNameAndPorts"
+                    :key="item.label"
+                    index="/nullpage"
+                    @click="changeServer(item.label)">
+                  <span>{{ item.label }}</span>
                 </el-menu-item>
-              </router-link>
+              </el-sub-menu>
+              <el-menu-item index="">
+                <el-icon><SwitchFilled /></el-icon>
+                <label class="currentserver">
+                  current:{{ currentServerStore.currentServer }}
+                </label>
+              </el-menu-item>
             </el-menu>
           </el-col>
         </el-row>
@@ -62,25 +75,50 @@
 </template>
 
 <script>
-import {ref, onMounted} from 'vue'
+import {ref, reactive, onMounted} from 'vue'
+import {getAllCoordinatorAddrees} from '@/api/api'
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
     const activeIndex1 = ref('1')
+    const currentServerStore = useCurrentServerStore()
+    const hostNameAndPorts = reactive([
+      {
+        value: '',
+        label: ''
+      }
+    ])
 
-    function handleSelect() {
-      localStorage.setItem("menuId", JSON.stringify(activeIndex1))
+    function changeServer(key) {
+      currentServerStore.currentServer = key
     }
 
-    onMounted(() => {
+    async function getSelectCurrentServer() {
+      const res = await getAllCoordinatorAddrees()
+      const  selectCurrentServer = res.data.data
+      currentServerStore.currentServer = Object.keys(selectCurrentServer)[0]
+      hostNameAndPorts.length = 0
+      Object.entries(selectCurrentServer).forEach(([key, value]) => {
+        hostNameAndPorts.push({"value": value, "label": key})
+      })
+    }
 
+    onMounted(() => {
+      getSelectCurrentServer()
     })
-    return {activeIndex1, handleSelect}
+
+    return {
+      activeIndex1,
+      currentServerStore,
+      hostNameAndPorts,
+      changeServer,
+    }
   }
 }
 </script>
 
-<style>
+<style scoped>
 a {
   text-decoration: none;
   color: white;
@@ -95,4 +133,10 @@ a {
 .unffilelogo > img {
   height: 55px;
 }
+
+.currentserver {
+  font-family: "Andale Mono";
+  font-size: smaller;
+  color: yellow;
+}
 </style>
diff --git a/dashboard/src/main/webapp/src/main.js 
b/dashboard/src/main/webapp/src/main.js
index e6c92fb81..1c5e2f3f1 100644
--- a/dashboard/src/main/webapp/src/main.js
+++ b/dashboard/src/main/webapp/src/main.js
@@ -16,13 +16,15 @@
  */
 
 import {createApp} from 'vue';
+import {createPinia} from 'pinia'
 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)
+const pinia = createPinia()
 Object.keys(ElementPlusIconsVue).forEach(key => {
     app.component(key, ElementPlusIconsVue[key])
 })
-app.use(router).use(ElementPlus).mount('#app')
+app.use(router).use(pinia).use(ElementPlus).mount('#app')
diff --git a/dashboard/src/main/webapp/src/components/ApplicationPage.vue 
b/dashboard/src/main/webapp/src/pages/ApplicationPage.vue
similarity index 80%
rename from dashboard/src/main/webapp/src/components/ApplicationPage.vue
rename to dashboard/src/main/webapp/src/pages/ApplicationPage.vue
index 1729311b9..0a29fa897 100644
--- a/dashboard/src/main/webapp/src/components/ApplicationPage.vue
+++ b/dashboard/src/main/webapp/src/pages/ApplicationPage.vue
@@ -56,6 +56,7 @@ import {
   getTotalForUser
 } from "@/api/api";
 import {onMounted, reactive} from "vue";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -64,6 +65,7 @@ export default {
       userAppCount: [{}],
       appInfoData: [{appId: "", userName: "", updateTime: ""}]
     })
+    const currentServerStore = useCurrentServerStore()
 
     async function getApplicationInfoListPage() {
       const res = await getApplicationInfoList();
@@ -80,10 +82,22 @@ export default {
       pageData.apptotal = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable, state) => {
+      if (state.currentServer) {
+        getApplicationInfoListPage();
+        getTotalForUserPage();
+        getAppTotalPage();
+      }
+    })
+
     onMounted(() => {
-      getApplicationInfoListPage();
-      getTotalForUserPage();
-      getAppTotalPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getApplicationInfoListPage();
+        getTotalForUserPage();
+        getAppTotalPage();
+      }
     })
     return {pageData}
   }
diff --git a/dashboard/src/main/webapp/src/components/CoordinatorServerPage.vue 
b/dashboard/src/main/webapp/src/pages/CoordinatorServerPage.vue
similarity index 85%
rename from dashboard/src/main/webapp/src/components/CoordinatorServerPage.vue
rename to dashboard/src/main/webapp/src/pages/CoordinatorServerPage.vue
index ca30d47bb..a0a292790 100644
--- a/dashboard/src/main/webapp/src/components/CoordinatorServerPage.vue
+++ b/dashboard/src/main/webapp/src/pages/CoordinatorServerPage.vue
@@ -85,6 +85,7 @@
 <script>
 import {ref, reactive, computed, onMounted} from 'vue'
 import {getCoordinatorConf, getCoordinatorServerInfo} from "@/api/api";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -94,17 +95,31 @@ export default {
           serverInfo: {}
         }
     );
+    const currentServerStore= useCurrentServerStore()
+
     async function getCoordinatorServerConfPage() {
-      const res = await getCoordinatorConf();
+      const res = await getCoordinatorConf()
       pageData.tableData = res.data.data
     }
     async function getCoorServerInfo() {
-      const res = await getCoordinatorServerInfo();
+      const res = await getCoordinatorServerInfo()
       pageData.serverInfo = res.data.data
     }
+
+    //The system obtains data from global variables and requests the interface 
to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getCoordinatorServerConfPage();
+        getCoorServerInfo();
+      }
+    })
+
     onMounted(() => {
-      getCoordinatorServerConfPage();
-      getCoorServerInfo();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getCoordinatorServerConfPage();
+        getCoorServerInfo();
+      }
     })
     
     const size = ref('')
@@ -128,6 +143,7 @@ export default {
         marginTop: marginMap[size.value] || marginMap.default,
       }
     })
+
     return {
       pageData,
       iconStyle,
diff --git a/dashboard/src/main/webapp/src/components/ShuffleServerPage.vue 
b/dashboard/src/main/webapp/src/pages/ShuffleServerPage.vue
similarity index 86%
rename from dashboard/src/main/webapp/src/components/ShuffleServerPage.vue
rename to dashboard/src/main/webapp/src/pages/ShuffleServerPage.vue
index 1a8af5298..0c6ce6955 100644
--- a/dashboard/src/main/webapp/src/components/ShuffleServerPage.vue
+++ b/dashboard/src/main/webapp/src/pages/ShuffleServerPage.vue
@@ -102,7 +102,8 @@
 
 <script>
 import {onMounted, reactive} from 'vue'
-import {getShufflegetStatusTotal} from "@/api/api";
+import {getShufflegetStatusTotal} from "@/api/api"
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -116,14 +117,25 @@ export default {
         UNHEALTHY: 0
       }
     })
+    const currentServerStore= useCurrentServerStore()
 
     async function getShufflegetStatusTotalPage() {
       const res = await getShufflegetStatusTotal();
       dataList.allshuffleServerSize = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getShufflegetStatusTotalPage();
+      }
+    })
+
     onMounted(() => {
-      getShufflegetStatusTotalPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getShufflegetStatusTotalPage();
+      }
     })
     return {dataList}
   }
@@ -141,6 +153,7 @@ export default {
   font-weight: bolder;
   font-size: 30px;
   color: green;
+  text-decoration: none;
 }
 
 .decommissioningnode {
@@ -149,6 +162,7 @@ export default {
   font-weight: bolder;
   font-size: 30px;
   color: #00c4ff;
+  text-decoration: none;
 }
 
 .decommissionednode {
@@ -157,6 +171,7 @@ export default {
   font-weight: bolder;
   font-size: 30px;
   color: blue;
+  text-decoration: none;
 }
 
 .lostnode {
@@ -165,6 +180,7 @@ export default {
   font-weight: bolder;
   font-size: 30px;
   color: red;
+  text-decoration: none;
 }
 
 .unhealthynode {
@@ -173,6 +189,7 @@ export default {
   font-weight: bolder;
   font-size: 30px;
   color: #ff8800;
+  text-decoration: none;
 }
 
 .excludesnode {
@@ -180,5 +197,6 @@ export default {
   font-style: normal;
   font-weight: bolder;
   font-size: 30px;
+  text-decoration: none;
 }
 </style>
diff --git 
a/dashboard/src/main/webapp/src/components/shufflecomponent/ActiveNodeListPage.vue
 b/dashboard/src/main/webapp/src/pages/serverstatus/ActiveNodeListPage.vue
similarity index 78%
rename from 
dashboard/src/main/webapp/src/components/shufflecomponent/ActiveNodeListPage.vue
rename to 
dashboard/src/main/webapp/src/pages/serverstatus/ActiveNodeListPage.vue
index 703f15a53..e1dce0767 100644
--- 
a/dashboard/src/main/webapp/src/components/shufflecomponent/ActiveNodeListPage.vue
+++ b/dashboard/src/main/webapp/src/pages/serverstatus/ActiveNodeListPage.vue
@@ -27,7 +27,7 @@
       <el-table-column prop="availableMemory" label="AvailableMem" 
min-width="80" :formatter="memFormatter"/>
       <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" 
:formatter="dateFormatter"/>
+      <el-table-column prop="timestamp" label="RegistrationTime" 
min-width="80" :formatter="dateFormatter"/>
       <el-table-column prop="tags" label="Tags" min-width="80"/>
     </el-table>
   </div>
@@ -36,6 +36,7 @@
 import {onMounted, reactive} from 'vue'
 import { getShuffleActiveNodes} from "@/api/api";
 import {memFormatter, dateFormatter} from "@/utils/common";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -56,15 +57,27 @@ export default {
         }
       ]
     })
+    const currentServerStore= useCurrentServerStore()
 
     async function getShuffleActiveNodesPage() {
       const res = await getShuffleActiveNodes();
       pageData.tableData = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getShuffleActiveNodesPage()
+      }
+    })
+
     onMounted(() => {
-      getShuffleActiveNodesPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getShuffleActiveNodesPage();
+      }
     })
+
     return {pageData, memFormatter, dateFormatter}
   }
 }
diff --git 
a/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissionednodeListPage.vue
 
b/dashboard/src/main/webapp/src/pages/serverstatus/DecommissionednodeListPage.vue
similarity index 78%
rename from 
dashboard/src/main/webapp/src/components/shufflecomponent/DecommissionednodeListPage.vue
rename to 
dashboard/src/main/webapp/src/pages/serverstatus/DecommissionednodeListPage.vue
index 1a0fa63d3..c95da69bd 100644
--- 
a/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissionednodeListPage.vue
+++ 
b/dashboard/src/main/webapp/src/pages/serverstatus/DecommissionednodeListPage.vue
@@ -27,7 +27,7 @@
       <el-table-column prop="availableMemory" label="AvailableMem" 
min-width="80" :formatter="memFormatter"/>
       <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" 
:formatter="dateFormatter"/>
+      <el-table-column prop="timestamp" label="RegistrationTime" 
min-width="80" :formatter="dateFormatter"/>
       <el-table-column prop="tags" label="Tags" min-width="80"/>
     </el-table>
   </div>
@@ -36,6 +36,7 @@
 import {onMounted, reactive} from 'vue'
 import { getShuffleDecommissionedList } from "@/api/api";
 import {memFormatter, dateFormatter} from "@/utils/common";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore';
 
 export default {
   setup() {
@@ -56,14 +57,25 @@ export default {
         }
       ]
     })
+    const currentServerStore= useCurrentServerStore()
 
     async function getShuffleDecommissionedListPage() {
       const res = await getShuffleDecommissionedList();
       pageData.tableData = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getShuffleDecommissionedListPage();
+      }
+    })
+
     onMounted(() => {
-      getShuffleDecommissionedListPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getShuffleDecommissionedListPage();
+      }
     })
 
     return {pageData, memFormatter, dateFormatter}
diff --git 
a/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissioningNodeListPage.vue
 
b/dashboard/src/main/webapp/src/pages/serverstatus/DecommissioningNodeListPage.vue
similarity index 78%
rename from 
dashboard/src/main/webapp/src/components/shufflecomponent/DecommissioningNodeListPage.vue
rename to 
dashboard/src/main/webapp/src/pages/serverstatus/DecommissioningNodeListPage.vue
index 930f64304..d6d7a75e6 100644
--- 
a/dashboard/src/main/webapp/src/components/shufflecomponent/DecommissioningNodeListPage.vue
+++ 
b/dashboard/src/main/webapp/src/pages/serverstatus/DecommissioningNodeListPage.vue
@@ -27,7 +27,7 @@
       <el-table-column prop="availableMemory" label="AvailableMem" 
min-width="80" :formatter="memFormatter"/>
       <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" 
:formatter="dateFormatter"/>
+      <el-table-column prop="timestamp" label="RegistrationTime" 
min-width="80" :formatter="dateFormatter"/>
       <el-table-column prop="tags" label="Tags" min-width="80"/>
     </el-table>
   </div>
@@ -36,6 +36,7 @@
 import {onMounted, reactive} from 'vue'
 import { getShuffleDecommissioningList } from "@/api/api";
 import {memFormatter, dateFormatter} from "@/utils/common";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -56,16 +57,29 @@ export default {
         }
       ]
     })
+    const currentServerStore= useCurrentServerStore()
 
     async function getShuffleDecommissioningListPage() {
       const res = await getShuffleDecommissioningList();
       pageData.tableData = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getShuffleDecommissioningListPage();
+      }
+    })
+
+
     onMounted(() => {
-      getShuffleDecommissioningListPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getShuffleDecommissioningListPage();
+      }
     })
 
+
     return {pageData, memFormatter, dateFormatter}
   }
 }
diff --git 
a/dashboard/src/main/webapp/src/components/shufflecomponent/ExcludeNodeList.vue 
b/dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
similarity index 71%
rename from 
dashboard/src/main/webapp/src/components/shufflecomponent/ExcludeNodeList.vue
rename to dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
index 4c0286ee3..3566657f9 100644
--- 
a/dashboard/src/main/webapp/src/components/shufflecomponent/ExcludeNodeList.vue
+++ b/dashboard/src/main/webapp/src/pages/serverstatus/ExcludeNodeList.vue
@@ -25,6 +25,7 @@
 <script>
 import {onMounted, reactive} from 'vue'
 import { getShuffleExcludeNodes } from "@/api/api";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -35,14 +36,27 @@ export default {
         }
       ]
     })
+    const currentServerStore= useCurrentServerStore()
+
     async function getShuffleExcludeNodesPage() {
       const res = await getShuffleExcludeNodes();
       pageData.tableData = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getShuffleExcludeNodesPage();
+      }
+    })
+
     onMounted(() => {
-      getShuffleExcludeNodesPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getShuffleExcludeNodesPage();
+      }
     })
+
     return {pageData}
   }
 }
diff --git 
a/dashboard/src/main/webapp/src/components/shufflecomponent/LostNodeList.vue 
b/dashboard/src/main/webapp/src/pages/serverstatus/LostNodeList.vue
similarity index 78%
rename from 
dashboard/src/main/webapp/src/components/shufflecomponent/LostNodeList.vue
rename to dashboard/src/main/webapp/src/pages/serverstatus/LostNodeList.vue
index 902951cdc..adfef3f52 100644
--- a/dashboard/src/main/webapp/src/components/shufflecomponent/LostNodeList.vue
+++ b/dashboard/src/main/webapp/src/pages/serverstatus/LostNodeList.vue
@@ -27,7 +27,7 @@
       <el-table-column prop="availableMemory" label="AvailableMem" 
min-width="80" :formatter="memFormatter"/>
       <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" 
:formatter="dateFormatter"/>
+      <el-table-column prop="timestamp" label="RegistrationTime" 
min-width="80" :formatter="dateFormatter"/>
       <el-table-column prop="tags" label="Tags" min-width="80"/>
     </el-table>
   </div>
@@ -36,6 +36,7 @@
 import {onMounted, reactive} from 'vue'
 import { getShuffleLostList } from "@/api/api";
 import {memFormatter, dateFormatter} from "@/utils/common";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -57,15 +58,29 @@ export default {
       ]
     })
 
+    const currentServerStore= useCurrentServerStore()
+
     async function getShuffleLostListPage() {
       const res = await getShuffleLostList();
       pageData.tableData = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getShuffleLostListPage();
+      }
+    })
+
     onMounted(() => {
-      getShuffleLostListPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getShuffleLostListPage();
+      }
     })
 
+
+
     return {pageData, memFormatter, dateFormatter}
   }
 }
diff --git 
a/dashboard/src/main/webapp/src/components/shufflecomponent/UnhealthyNodeListPage.vue
 b/dashboard/src/main/webapp/src/pages/serverstatus/UnhealthyNodeListPage.vue
similarity index 78%
rename from 
dashboard/src/main/webapp/src/components/shufflecomponent/UnhealthyNodeListPage.vue
rename to 
dashboard/src/main/webapp/src/pages/serverstatus/UnhealthyNodeListPage.vue
index f66346dcd..627ce47cf 100644
--- 
a/dashboard/src/main/webapp/src/components/shufflecomponent/UnhealthyNodeListPage.vue
+++ b/dashboard/src/main/webapp/src/pages/serverstatus/UnhealthyNodeListPage.vue
@@ -27,7 +27,7 @@
       <el-table-column prop="availableMemory" label="AvailableMem" 
min-width="80" :formatter="memFormatter"/>
       <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" 
:formatter="dateFormatter"/>
+      <el-table-column prop="timestamp" label="RegistrationTime" 
min-width="80" :formatter="dateFormatter"/>
       <el-table-column prop="tags" label="Tags" min-width="80"/>
     </el-table>
   </div>
@@ -36,6 +36,7 @@
 import {onMounted, reactive} from 'vue'
 import { getShuffleUnhealthyList } from "@/api/api";
 import {memFormatter, dateFormatter} from "@/utils/common";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
 
 export default {
   setup() {
@@ -56,16 +57,28 @@ export default {
         }
       ]
     })
+    const currentServerStore= useCurrentServerStore()
 
     async function getShuffleUnhealthyListPage() {
       const res = await getShuffleUnhealthyList();
       pageData.tableData = res.data.data
     }
 
+    // The system obtains data from global variables and requests the 
interface to obtain new data after data changes.
+    currentServerStore.$subscribe((mutable,state)=>{
+      if (state.currentServer) {
+        getShuffleUnhealthyListPage();
+      }
+    })
+
     onMounted(() => {
-      getShuffleUnhealthyListPage();
+      // If the coordinator address to request is not found in the global 
variable, the request is not initiated.
+      if (currentServerStore.currentServer) {
+        getShuffleUnhealthyListPage();
+      }
     })
 
+
     return {pageData, memFormatter, dateFormatter}
   }
 }
diff --git a/dashboard/src/main/webapp/src/router/index.js 
b/dashboard/src/main/webapp/src/router/index.js
index 6bd21c360..5bccc8496 100644
--- a/dashboard/src/main/webapp/src/router/index.js
+++ b/dashboard/src/main/webapp/src/router/index.js
@@ -16,15 +16,15 @@
  */
 
 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'
+import ApplicationPage from '@/pages/ApplicationPage.vue'
+import CoordinatorServerPage from '@/pages/CoordinatorServerPage.vue'
+import ShuffleServerPage from '@/pages/ShuffleServerPage.vue'
+import ActiveNodeListPage from '@/pages/serverstatus/ActiveNodeListPage'
+import DecommissioningNodeListPage from 
'@/pages/serverstatus/DecommissioningNodeListPage'
+import DecommissionednodeListPage from 
'@/pages/serverstatus/DecommissionednodeListPage'
+import LostNodeList from '@/pages/serverstatus/LostNodeList'
+import UnhealthyNodeListPage from '@/pages/serverstatus/UnhealthyNodeListPage'
+import ExcludeNodeList from '@/pages/serverstatus/ExcludeNodeList'
 
 const routes = [
     {
@@ -59,6 +59,12 @@ const routes = [
         name: 'applicationpage',
         component: ApplicationPage,
     },
+    {
+        path: '/nullpage',
+        name: 'nullpage',
+        beforeEnter:(to,from,next)=>{next(false)},
+        component: ApplicationPage
+    },
 ]
 
 const router = createRouter({
diff --git a/dashboard/src/main/webapp/src/utils/request.js 
b/dashboard/src/main/webapp/src/store/useCurrentServerStore.js
similarity index 59%
copy from dashboard/src/main/webapp/src/utils/request.js
copy to dashboard/src/main/webapp/src/store/useCurrentServerStore.js
index 2e6b3c400..be6ab0807 100644
--- a/dashboard/src/main/webapp/src/utils/request.js
+++ b/dashboard/src/main/webapp/src/store/useCurrentServerStore.js
@@ -15,23 +15,14 @@
  * limitations under the License.
  */
 
-import axios from 'axios'
+import {defineStore} from 'pinia'
+import {ref} from 'vue'
 
-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;
+/**
+ * Create a global shared repository that allows you to share state across 
components/pages.
+ * @type {StoreDefinition<"overall", 
_ExtractStateFromSetupStore<{currentServer: Ref<UnwrapRef<string>>}>, 
_ExtractGettersFromSetupStore<{currentServer: Ref<UnwrapRef<string>>}>, 
_ExtractActionsFromSetupStore<{currentServer: Ref<UnwrapRef<string>>}>>}
+ */
+export const useCurrentServerStore = defineStore('overall', () => {
+    const currentServer = ref('')
+    return { currentServer }
 })
-export default axiosInstance;
diff --git a/dashboard/src/main/webapp/src/utils/http.js 
b/dashboard/src/main/webapp/src/utils/http.js
index 472f9ef1c..87ca359bb 100644
--- a/dashboard/src/main/webapp/src/utils/http.js
+++ b/dashboard/src/main/webapp/src/utils/http.js
@@ -16,24 +16,39 @@
  */
 
 import request from "@/utils/request";
+import {useCurrentServerStore} from '@/store/useCurrentServerStore'
+
+
 const http = {
-    get(url, params, headers) {
-        const config = {
-            method: 'GET',
-            url: url,
-            params: params,
-            headers: headers
+    get(url, params, headers, fontBackFlag) {
+        if (fontBackFlag == 0) {
+            // The system obtains the address of the Coordinator to be 
accessed from global variables.
+            const currentServerStore= useCurrentServerStore()
+            if (typeof headers !== 'undefined') {
+                headers['targetAddress'] = currentServerStore.currentServer;
+            } else {
+                headers = {}
+                headers['targetAddress'] = currentServerStore.currentServer;
+            }
+            return request.getBackEndAxiosInstance().get(url,{params,headers});
+        } else {
+            return 
request.getFrontEndAxiosInstance().get(url,{params,headers});
         }
-        return request(config);
     },
-    post(url, data, headers) {
-        const config = {
-            method: 'POST',
-            url: url,
-            data: data,
-            headers: headers
+    post(url, data, headers, fontBackFlag) {
+        if (fontBackFlag == 0) {
+            // The system obtains the address of the Coordinator to be 
accessed from global variables.
+            const currentServerStore= useCurrentServerStore()
+            if (typeof headers !== 'undefined') {
+                headers['targetAddress'] = currentServerStore.currentServer;
+            } else {
+                headers = {}
+                headers['targetAddress'] = currentServerStore.currentServer;
+            }
+            return request.getBackEndAxiosInstance().post(url,data,headers);
+        } else {
+            return request.getFrontEndAxiosInstance().post(url,data,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
index 2e6b3c400..5d7c72068 100644
--- a/dashboard/src/main/webapp/src/utils/request.js
+++ b/dashboard/src/main/webapp/src/utils/request.js
@@ -17,21 +17,54 @@
 
 import axios from 'axios'
 
-const axiosInstance = axios.create({
+/**
+ * The root directory, starting with web, is handled by the dashboard server's 
servlet
+ * @type {axios.AxiosInstance}
+ */
+const frontEndAxiosInstance = axios.create({
+    baseURL: '/web',
+    timeout: 10000
+})
+/**
+ * The root directory starts with API. The dashboard server reverse proxy 
requests Coordinator apis
+ * @type {axios.AxiosInstance}
+ */
+const backEndAxiosInstance=axios.create({
     baseURL: '/api',
-    timeout: 10000,
-    headers: {}
+    timeout: 10000
 })
 
-axiosInstance.interceptors.request.use(config => {
+const axiosInstance = {
+    getFrontEndAxiosInstance(){
+        return frontEndAxiosInstance
+    },
+    getBackEndAxiosInstance(){
+        return backEndAxiosInstance
+    }
+}
+
+frontEndAxiosInstance.interceptors.request.use(config => {
     config.headers['Content-type'] = 'application/json';
     config.headers['Accept'] = 'application/json';
     return config;
 })
 
-axiosInstance.interceptors.response.use(response => {
+backEndAxiosInstance.interceptors.request.use(config => {
+    config.headers['Content-type'] = 'application/json';
+    config.headers['Accept'] = 'application/json';
+    return config;
+})
+
+frontEndAxiosInstance.interceptors.response.use(response => {
     return response;
 }, error => {
     return error;
 })
+
+backEndAxiosInstance.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
index a007fc8ba..6bc469673 100644
--- a/dashboard/src/main/webapp/vue.config.js
+++ b/dashboard/src/main/webapp/vue.config.js
@@ -16,4 +16,16 @@
  */
 
 module.exports ={
+    // Proxies can be set up by configuring the vue.config.js file to proxy 
requests to the backend server.
+    devServer: {
+        host:'localhost',
+        port:8080,
+        proxy: {
+            '/': {
+                ws:false,
+                target: 'http://localhost:19997',
+                changeOrigin: true,
+            },
+        }
+    }
 }
diff --git a/dashboard/src/main/webapp/src/utils/request.js 
b/dashboard/src/test/java/org/apache/uniffle/dashboard/web/utils/DashboardUtilsTest.java
similarity index 57%
copy from dashboard/src/main/webapp/src/utils/request.js
copy to 
dashboard/src/test/java/org/apache/uniffle/dashboard/web/utils/DashboardUtilsTest.java
index 2e6b3c400..12f869473 100644
--- a/dashboard/src/main/webapp/src/utils/request.js
+++ 
b/dashboard/src/test/java/org/apache/uniffle/dashboard/web/utils/DashboardUtilsTest.java
@@ -15,23 +15,21 @@
  * limitations under the License.
  */
 
-import axios from 'axios'
+package org.apache.uniffle.dashboard.web.utils;
 
-const axiosInstance = axios.create({
-    baseURL: '/api',
-    timeout: 10000,
-    headers: {}
-})
+import java.util.Map;
 
-axiosInstance.interceptors.request.use(config => {
-    config.headers['Content-type'] = 'application/json';
-    config.headers['Accept'] = 'application/json';
-    return config;
-})
+import org.junit.jupiter.api.Test;
 
-axiosInstance.interceptors.response.use(response => {
-    return response;
-}, error => {
-    return error;
-})
-export default axiosInstance;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DashboardUtilsTest {
+  @Test
+  public void testConvertToMap() {
+    String coordinatorStr =
+        
"http://coordinator.hostname00:19998/,http://coordinator.hostname01:19998/,http://coordinator.hostname02:19998/,http://coordinator.hostname03:19998/";;
+    Map<String, String> coordinatorAddressMap =
+        DashboardUtils.convertAddressesStrToMap(coordinatorStr);
+    assertEquals(coordinatorAddressMap.size(), 4);
+  }
+}

Reply via email to