Compare commits

..

5 Commits

Author SHA1 Message Date
公明 d4f2b0f93d Update version to v1.4.14 in config.yaml 2026-04-13 21:33:41 +08:00
公明 1fb8cc2fbc Add files via upload 2026-04-13 18:11:04 +08:00
公明 3ddf280400 Add files via upload 2026-04-13 17:53:55 +08:00
公明 961deb81dd Add files via upload 2026-04-10 16:46:44 +08:00
公明 ae3bc41c88 Add files via upload 2026-04-10 16:44:49 +08:00
5 changed files with 62 additions and 18 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
# ============================================
# 前端显示的版本号(可选,不填则显示默认版本)
version: "v1.4.13"
version: "v1.4.14"
# 服务器配置
server:
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
+20 -3
View File
@@ -3,6 +3,7 @@
package handler
import (
"encoding/json"
"net/http"
"os"
"os/exec"
@@ -13,6 +14,13 @@ import (
"github.com/gorilla/websocket"
)
// terminalResize is sent by the frontend when the xterm.js terminal is resized.
type terminalResize struct {
Type string `json:"type"`
Cols uint16 `json:"cols"`
Rows uint16 `json:"rows"`
}
// wsUpgrader 仅用于系统设置中的终端 WebSocket,会复用已有的登录保护(JWT 中间件在上层路由组)
var wsUpgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
@@ -37,12 +45,13 @@ func (h *TerminalHandler) RunCommandWS(c *gin.Context) {
}
cmd := exec.Command(shell)
cmd.Env = append(os.Environ(),
"COLUMNS=256",
"LINES=40",
"COLUMNS=80",
"LINES=24",
"TERM=xterm-256color",
)
ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Cols: ptyCols, Rows: ptyRows})
// Use 80x24 as a safe default; the frontend will send the actual size immediately after connecting.
ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Cols: 80, Rows: 24})
if err != nil {
return
}
@@ -84,6 +93,14 @@ func (h *TerminalHandler) RunCommandWS(c *gin.Context) {
if len(data) == 0 {
continue
}
// Check if this is a resize message (JSON with type:"resize")
if msgType == websocket.TextMessage && len(data) > 0 && data[0] == '{' {
var resize terminalResize
if json.Unmarshal(data, &resize) == nil && resize.Type == "resize" && resize.Cols > 0 && resize.Rows > 0 {
_ = pty.Setsize(ptmx, &pty.Winsize{Cols: resize.Cols, Rows: resize.Rows})
continue
}
}
if _, err := ptmx.Write(data); err != nil {
_ = cmd.Process.Kill()
break
+4 -13
View File
@@ -1524,8 +1524,7 @@ header {
justify-content: center;
gap: 6px;
padding: 8px 14px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
background: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 20px;
color: #666;
@@ -2083,7 +2082,6 @@ header {
overflow: hidden;
display: none;
z-index: 15;
backdrop-filter: blur(6px);
animation: mentionFadeIn 0.15s ease-out;
box-sizing: border-box;
}
@@ -2270,9 +2268,7 @@ header {
.login-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
background: rgba(245, 245, 245, 0.85);
display: none;
align-items: center;
justify-content: center;
@@ -2428,7 +2424,6 @@ header {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
overflow: auto;
animation: fadeIn 0.2s ease-in;
}
@@ -9807,8 +9802,7 @@ header {
font-size: 0.92rem;
font-weight: 600;
color: var(--text-primary);
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(6px);
background: #ffffff;
}
.webshell-memo-input {
flex: 1;
@@ -10726,8 +10720,7 @@ header {
flex-shrink: 0;
padding: 12px 24px;
border-bottom: 1px solid rgba(0,0,0,0.06);
background: rgba(255,255,255,0.95);
backdrop-filter: blur(10px);
background: #ffffff;
box-shadow: 0 1px 0 rgba(255,255,255,0.8) inset;
}
@@ -12097,7 +12090,6 @@ header {
display: flex;
flex-direction: column;
animation: slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(20px);
text-align: left;
}
@@ -12156,7 +12148,6 @@ header {
display: flex;
flex-direction: column;
animation: slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(20px);
}
@keyframes slideUp {
+16 -1
View File
@@ -1280,6 +1280,12 @@ async function showBatchQueueDetail(queueId) {
</div>`;
}
// 保存滚动位置,防止刷新时滚动条弹回顶部
const modalBody = content.closest('.modal-body');
const tasksList = content.querySelector('.batch-queue-tasks-list');
const savedModalBodyScrollTop = modalBody ? modalBody.scrollTop : 0;
const savedTasksListScrollTop = tasksList ? tasksList.scrollTop : 0;
content.innerHTML = `
<div class="batch-queue-detail-info">
${queue.title ? `<div class="detail-item">
@@ -1338,8 +1344,17 @@ async function showBatchQueueDetail(queueId) {
</div>
`;
// 恢复滚动位置
if (savedModalBodyScrollTop > 0 && modalBody) {
modalBody.scrollTop = savedModalBodyScrollTop;
}
const newTasksList = content.querySelector('.batch-queue-tasks-list');
if (savedTasksListScrollTop > 0 && newTasksList) {
newTasksList.scrollTop = savedTasksListScrollTop;
}
modal.style.display = 'block';
// 如果队列正在运行,自动刷新
if (queue.status === 'running') {
startBatchQueueRefresh(queueId);
+21
View File
@@ -121,6 +121,13 @@
ws.onopen = function () {
if (tab.term) {
tab.term.focus();
// Send the actual terminal dimensions to the backend immediately
// so the PTY size matches what xterm.js is displaying.
if (tab.term.cols && tab.term.rows) {
try {
ws.send(JSON.stringify({ type: 'resize', cols: tab.term.cols, rows: tab.term.rows }));
} catch (e) {}
}
}
};
@@ -225,6 +232,14 @@
}
}
function sendResize() {
if (tab.ws && tab.ws.readyState === WebSocket.OPEN && term.cols && term.rows) {
try {
tab.ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
} catch (e) {}
}
}
term.onData(function (data) {
// Ctrl+L:本地清屏,同时把 ^L 也发给后端
if (data === '\x0c') {
@@ -235,6 +250,12 @@
sendToWS(data);
});
// Notify backend when the terminal is resized so the PTY dimensions stay in sync.
// This is critical for full-screen programs like vi/vim/less to render correctly.
term.onResize(function (size) {
sendResize();
});
tab.term = term;
tab.fitAddon = fitAddon;
// 立即建立 WebSocket,让后端 PTY/Shell 马上启动并输出提示符;