diff --git a/tunnel-server/pom.xml b/tunnel-server/pom.xml
index b56dc0270..bb846aaf1 100644
--- a/tunnel-server/pom.xml
+++ b/tunnel-server/pom.xml
@@ -70,6 +70,18 @@
 			<artifactId>commons-lang3</artifactId>
 		</dependency>
 
+        <dependency>
+            <groupId>it.ozimov</groupId>
+            <artifactId>embedded-redis</artifactId>
+            <version>0.7.3</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/ArthasProperties.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/ArthasProperties.java
index a84fc6247..4b4c41ee7 100644
--- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/ArthasProperties.java
+++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/ArthasProperties.java
@@ -17,6 +17,13 @@ public class ArthasProperties {
 
     private Server server;
 
+    private EmbeddedRedis embeddedRedis;
+
+    /**
+     * supoort apps.html/agents.html
+     */
+    private boolean enableDetatilPages = false;
+
     public Server getServer() {
         return server;
     }
@@ -25,6 +32,22 @@ public class ArthasProperties {
         this.server = server;
     }
 
+    public EmbeddedRedis getEmbeddedRedis() {
+        return embeddedRedis;
+    }
+
+    public void setEmbeddedRedis(EmbeddedRedis embeddedRedis) {
+        this.embeddedRedis = embeddedRedis;
+    }
+
+    public boolean isEnableDetatilPages() {
+        return enableDetatilPages;
+    }
+
+    public void setEnableDetatilPages(boolean enableDetatilPages) {
+        this.enableDetatilPages = enableDetatilPages;
+    }
+
     public static class Server {
         /**
          * tunnel server listen host
@@ -81,4 +104,40 @@ public class ArthasProperties {
 
     }
 
+    /**
+     * for test
+     * 
+     * @author hengyunabc 2020-11-03
+     *
+     */
+    public static class EmbeddedRedis {
+        private boolean enabled = false;
+        private String host = "127.0.0.1";
+        private int port = 6379;
+
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
+
+        public String getHost() {
+            return host;
+        }
+
+        public void setHost(String host) {
+            this.host = host;
+        }
+
+        public int getPort() {
+            return port;
+        }
+
+        public void setPort(int port) {
+            this.port = port;
+        }
+    }
+
 }
diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/EmbeddedRedisConfiguration.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/EmbeddedRedisConfiguration.java
new file mode 100644
index 000000000..83452acf1
--- /dev/null
+++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/EmbeddedRedisConfiguration.java
@@ -0,0 +1,38 @@
+package com.alibaba.arthas.tunnel.server.app.configuration;
+
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.alibaba.arthas.tunnel.server.app.configuration.ArthasProperties.EmbeddedRedis;
+
+import redis.embedded.RedisServer;
+
+/**
+ * 
+ * @author hengyunabc 2020-11-03
+ *
+ */
+@Configuration
+@AutoConfigureBefore(TunnelClusterStoreConfiguration.class)
+public class EmbeddedRedisConfiguration {
+
+    @Bean(initMethod = "start", destroyMethod = "stop")
+    @ConditionalOnMissingBean
+    @ConditionalOnProperty(prefix = "arthas", name = { "embedded-redis.enabled" })
+    public RedisServer embeddedRedisServer(ArthasProperties arthasProperties) {
+        EmbeddedRedis embeddedRedis = arthasProperties.getEmbeddedRedis();
+
+        RedisServer redisServer = RedisServer.builder().port(embeddedRedis.getPort()).bind(embeddedRedis.getHost())
+                .build();
+        return redisServer;
+
+    }
+
+    public static void main(String[] args) {
+        RedisServer redisServer = new RedisServer();
+        redisServer.start();
+    }
+}
diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/DetailAPIController.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/DetailAPIController.java
new file mode 100644
index 000000000..c05fee350
--- /dev/null
+++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/DetailAPIController.java
@@ -0,0 +1,91 @@
+package com.alibaba.arthas.tunnel.server.app.web;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.alibaba.arthas.tunnel.server.AgentClusterInfo;
+import com.alibaba.arthas.tunnel.server.app.configuration.ArthasProperties;
+import com.alibaba.arthas.tunnel.server.cluster.TunnelClusterStore;
+
+/**
+ * 
+ * @author hengyunabc 2020-11-03
+ *
+ */
+@Controller
+public class DetailAPIController {
+
+    private final static Logger logger = LoggerFactory.getLogger(DetailAPIController.class);
+
+    @Autowired
+    ArthasProperties arthasProperties;
+
+    @Autowired(required = false)
+    private TunnelClusterStore tunnelClusterStore;
+
+    @RequestMapping("/api/tunnelApps")
+    @ResponseBody
+    public Set<String> tunnelApps(HttpServletRequest request, Model model) {
+        if (!arthasProperties.isEnableDetatilPages()) {
+            throw new IllegalAccessError("not allow");
+        }
+
+        Set<String> result = new HashSet<String>();
+
+        if (tunnelClusterStore != null) {
+            Collection<String> agentIds = tunnelClusterStore.allAgentIds();
+
+            for (String id : agentIds) {
+                String appName = findAppNameFromAgentId(id);
+                if (appName != null) {
+                    result.add(appName);
+                } else {
+                    logger.warn("illegal agentId: " + id);
+                }
+            }
+
+        }
+
+        return result;
+    }
+
+    @RequestMapping("/api/tunnelAgentInfo")
+    @ResponseBody
+    public Map<String, AgentClusterInfo> tunnelAgentIds(@RequestParam(value = "app", required = true) String appName,
+            HttpServletRequest request, Model model) {
+        if (!arthasProperties.isEnableDetatilPages()) {
+            throw new IllegalAccessError("not allow");
+        }
+
+        if (tunnelClusterStore != null) {
+            Map<String, AgentClusterInfo> agentInfos = tunnelClusterStore.agentInfo(appName);
+
+            return agentInfos;
+        }
+
+        return Collections.emptyMap();
+    }
+
+    private static String findAppNameFromAgentId(String id) {
+        int index = id.indexOf('_');
+        if (index < 0 || index >= id.length()) {
+            return null;
+        }
+
+        return id.substring(0, index);
+    }
+}
diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/StatController.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/StatController.java
index 11cb411a8..409ef1a29 100644
--- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/StatController.java
+++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/StatController.java
@@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 /**
- * 
+ * arthas agent数据回报的演示接口
  * @author hengyunabc 2019-09-24
  *
  */
diff --git a/tunnel-server/src/main/resources/application.properties b/tunnel-server/src/main/resources/application.properties
index 3e6b1f8de..c56becb64 100755
--- a/tunnel-server/src/main/resources/application.properties
+++ b/tunnel-server/src/main/resources/application.properties
@@ -7,4 +7,9 @@ arthas.server.port=7777
 management.endpoints.web.exposure.include=*
 
 # default user name
-spring.security.user.name=arthas
\ No newline at end of file
+spring.security.user.name=arthas
+
+
+#arthas.enable-detatil-pages=true
+#arthas.embedded-redis.enabled=true
+#spring.redis.host=127.0.0.1
\ No newline at end of file
diff --git a/tunnel-server/src/main/resources/static/agents.html b/tunnel-server/src/main/resources/static/agents.html
new file mode 100644
index 000000000..a5505c8ea
--- /dev/null
+++ b/tunnel-server/src/main/resources/static/agents.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+    <!-- Required meta tags -->
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="https://g.alicdn.com/code/lib/twitter-bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
+        crossorigin="anonymous">
+
+    <script src="https://g.alicdn.com/code/lib/vue/2.6.4/vue.min.js" integrity="sha256-isEQDc5Dw7wea1s5iMZjBvPuYzjzMrvtlPwE6LtavFA=" crossorigin="anonymous"></script>
+    <script src="https://g.alicdn.com/code/lib/axios/0.18.0/axios.min.js"></script>
+
+    <title>Arthas Tutorials</title>
+
+    <style>
+        /* This is all that's required */
+        .dropdown-item-checked::before {
+        position: absolute;
+        left: .4rem;
+        content: '✓';
+        font-weight: 600;
+        }
+    </style>
+</head>
+
+<body>
+    <!-- Optional JavaScript -->
+    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
+    <script src="https://g.alicdn.com/code/lib/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+    <script src="https://g.alicdn.com/code/lib/popper.js/1.14.7/umd/popper.min.js" integrity="sha512-5WvZa4N7Jq3TVNCp4rjcBMlc6pT3lZ7gVxjtI6IkKW+uItSa+rFgtFljvZnCxQGj8SUX5DHraKE6Mn/4smK1Cg==" crossorigin="anonymous"></script>
+    <script src="https://g.alicdn.com/code/lib/twitter-bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k"
+        crossorigin="anonymous"></script>
+
+    <div id="app">
+
+        <table class="table table-hover issue-tracker">
+            <thead>
+                <tr>
+                    <th>IP</th>
+                    <th>AgentId</th>
+                    <th>Version</th>
+                </tr>
+            </thead>
+            <tbody>
+            <tr v-for="(agentInfo, agentId) in agentInfos">
+                <td>
+                    <a class="btn btn-primary btn-xs" v-bind:href="tunnelWebConsoleLink(agentId, agentInfo.clientConnectHost)">{{agentInfo.host}}</a>
+                </td>
+                <td>
+                    <span class="label label-default">{{agentId}}</span>
+                </td>
+                <td>
+                    <span class="label label-default">{{agentInfo.arthasVersion}}</span>
+                </td>
+            </tr>
+            </tbody>
+         </table>
+
+    </div>
+</body>
+
+
+<script>
+    var app = new Vue({
+        el: '#app',
+        data: {
+            agentInfos: [],
+        },
+        methods: {
+            fetchMyApps: function () {
+                var vm = this;
+                var params = new window.URLSearchParams(window.location.search);
+                var appName = params.get('app');
+                axios.get('/api/tunnelAgentInfo?app=' + appName)
+                        .then(function (response) {
+                            vm.agentInfos = response.data
+                        })
+                        .catch(function (error) {
+                            console.log('api error ' + error)
+                        })
+            },
+            tunnelWebConsoleLink: function (agentId, targetServer) {
+                return "/?targetServer=" + targetServer + "&agentId=" + agentId;
+            }
+        },
+        mounted: function() {
+            this.fetchMyApps()
+        }
+    })
+
+</script>
+</html>
\ No newline at end of file
diff --git a/tunnel-server/src/main/resources/static/apps.html b/tunnel-server/src/main/resources/static/apps.html
new file mode 100644
index 000000000..285c3261d
--- /dev/null
+++ b/tunnel-server/src/main/resources/static/apps.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+    <!-- Required meta tags -->
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="https://g.alicdn.com/code/lib/twitter-bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
+        crossorigin="anonymous">
+
+    <script src="https://g.alicdn.com/code/lib/vue/2.6.4/vue.min.js" integrity="sha256-isEQDc5Dw7wea1s5iMZjBvPuYzjzMrvtlPwE6LtavFA=" crossorigin="anonymous"></script>
+    <script src="https://g.alicdn.com/code/lib/axios/0.18.0/axios.min.js"></script>
+
+    <title>Arthas Tutorials</title>
+
+    <style>
+        /* This is all that's required */
+        .dropdown-item-checked::before {
+        position: absolute;
+        left: .4rem;
+        content: '✓';
+        font-weight: 600;
+        }
+    </style>
+</head>
+
+<body>
+    <!-- Optional JavaScript -->
+    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
+    <script src="https://g.alicdn.com/code/lib/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+    <script src="https://g.alicdn.com/code/lib/popper.js/1.14.7/umd/popper.min.js" integrity="sha512-5WvZa4N7Jq3TVNCp4rjcBMlc6pT3lZ7gVxjtI6IkKW+uItSa+rFgtFljvZnCxQGj8SUX5DHraKE6Mn/4smK1Cg==" crossorigin="anonymous"></script>
+    <script src="https://g.alicdn.com/code/lib/twitter-bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k"
+        crossorigin="anonymous"></script>
+
+    <div id="app">
+
+        <table class="table table-hover issue-tracker">
+            <thead>
+                <tr>
+                    <th>应用名</th>
+                </tr>
+            </thead>
+            <tbody>
+            <tr v-for="myApp in myApps">
+                <td>
+                    <a class="btn btn-primary btn-xs" v-bind:href="detailLink(myApp)">{{myApp}}</a>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+
+    </div>
+</body>
+
+
+<script>
+    var app = new Vue({
+        el: '#app',
+        data: {
+            myApps: {},
+        },
+        methods: {
+            fetchMyApps: function () {
+                var vm = this
+                axios.get('/api/tunnelApps')
+                        .then(function (response) {
+                            vm.myApps = response.data
+                        })
+                        .catch(function (error) {
+                            console.log('api error ' + error)
+                        })
+            },
+            detailLink: function (appName) {
+                return "agents.html?app=" + appName;
+            }
+        },
+        mounted: function() {
+            this.fetchMyApps()
+        }
+    })
+
+</script>
+</html>
\ No newline at end of file