diff --git a/README.md b/README.md
index 81756f6..94e2477 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
   <br>
   <small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
   <br><br>
-<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.9.28&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.7.0-brightgreen?style=for-the-badge&logo=linux">
+<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.9.29&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.7.0-brightgreen?style=for-the-badge&logo=linux">
   <br>
   <br>
   <p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。</p>	
diff --git a/cmd/dashboard/controller/common_page.go b/cmd/dashboard/controller/common_page.go
index 6b6c0b8..e26ac7f 100644
--- a/cmd/dashboard/controller/common_page.go
+++ b/cmd/dashboard/controller/common_page.go
@@ -294,11 +294,22 @@ func (cp *commonPage) terminal(c *gin.Context) {
 
 	deadlineCh := make(chan interface{})
 	go func() {
+		// 对方连接超时
 		connectDeadline := time.NewTimer(time.Second * 15)
 		<-connectDeadline.C
 		deadlineCh <- struct{}{}
 	}()
 
+	go func() {
+		// PING 保活
+		for {
+			if err = conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
+				return
+			}
+			time.Sleep(time.Second * 10)
+		}
+	}()
+
 	dataCh := make(chan []byte)
 	errorCh := make(chan error)
 	go func() {
@@ -318,10 +329,20 @@ func (cp *commonPage) terminal(c *gin.Context) {
 
 	var dataBuffer [][]byte
 	var distConn *websocket.Conn
+	checkDistConn := func() {
+		if distConn == nil {
+			if isAgent {
+				distConn = terminal.userConn
+			} else {
+				distConn = terminal.agentConn
+			}
+		}
+	}
 
 	for {
 		select {
 		case <-deadlineCh:
+			checkDistConn()
 			if distConn == nil {
 				return
 			}
@@ -329,14 +350,7 @@ func (cp *commonPage) terminal(c *gin.Context) {
 			return
 		case data := <-dataCh:
 			dataBuffer = append(dataBuffer, data)
-			if distConn == nil {
-				// 传递给对方
-				if isAgent {
-					distConn = terminal.userConn
-				} else {
-					distConn = terminal.agentConn
-				}
-			}
+			checkDistConn()
 			if distConn != nil {
 				for i := 0; i < len(dataBuffer); i++ {
 					err = distConn.WriteMessage(websocket.BinaryMessage, dataBuffer[i])
diff --git a/resource/template/dashboard/terminal.html b/resource/template/dashboard/terminal.html
index 896b988..4aea4d3 100644
--- a/resource/template/dashboard/terminal.html
+++ b/resource/template/dashboard/terminal.html
@@ -32,6 +32,41 @@
     <script src="https://cdn.jsdelivr.net/npm/xterm-addon-attach@0.6.0/lib/xterm-addon-attach.min.js"></script>
     <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script>
     <script>
+        let sendResizing = false;
+
+        function doResize() {
+            fitAddon.fit()
+            const w = fitAddon.proposeDimensions();
+            const prefix = new Int8Array([1]);
+            const resizeMessage = new TextEncoder().encode(JSON.stringify({
+                Rows: w.rows,
+                Cols: w.cols,
+            }));
+
+            var msg = new Int8Array(prefix.length + resizeMessage.length);
+            msg.set(prefix);
+            msg.set(resizeMessage, prefix.length);
+
+            socket.send(msg)
+        }
+
+        function sleep(ms) {
+            return new Promise(resolve => setTimeout(resolve, ms));
+        }
+
+        async function onResize() {
+            if (sendResizing) return;
+            sendResizing = true;
+            try {
+                await sleep(1500);
+                doResize();
+            } catch (error) {
+                console.log('resize', error);
+            } finally {
+                sendResizing = false
+            }
+        }
+
         const term = new Terminal({
             screenKeys: true,
             useStyle: true,
@@ -56,22 +91,6 @@
         socket.onerror = () => {
             alert('Terminal 连接失败,请检查 /terminal/* 的 WebSocket 反代情况')
         }
-
-        function onResize() {
-            fitAddon.fit()
-            const w = fitAddon.proposeDimensions();
-            const prefix = new Int8Array([1]);
-            const resizeMessage = new TextEncoder().encode(JSON.stringify({
-                Rows: w.rows,
-                Cols: w.cols,
-            }));
-
-            var msg = new Int8Array(prefix.length + resizeMessage.length);
-            msg.set(prefix);
-            msg.set(resizeMessage, prefix.length);
-
-            socket.send(msg)
-        }
     </script>
 </body>
 
diff --git a/service/dao/dao.go b/service/dao/dao.go
index e91268a..bb73d75 100644
--- a/service/dao/dao.go
+++ b/service/dao/dao.go
@@ -13,7 +13,7 @@ import (
 	pb "github.com/naiba/nezha/proto"
 )
 
-var Version = "v0.9.28" // !!记得修改 README 中的 badge 版本!!
+var Version = "v0.9.29" // !!记得修改 README 中的 badge 版本!!
 
 var (
 	Conf  *model.Config