mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-18 14:04:52 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4f2b0f93d | |||
| 1fb8cc2fbc | |||
| 3ddf280400 | |||
| 961deb81dd | |||
| ae3bc41c88 |
+1
-1
@@ -10,7 +10,7 @@
|
||||
# ============================================
|
||||
|
||||
# 前端显示的版本号(可选,不填则显示默认版本)
|
||||
version: "v1.4.13"
|
||||
version: "v1.4.14"
|
||||
# 服务器配置
|
||||
server:
|
||||
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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 马上启动并输出提示符;
|
||||
|
||||
Reference in New Issue
Block a user