2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 03:07:20 +08:00

feat: 终端支持ping

This commit is contained in:
2026-01-26 20:09:33 +08:00
parent 56c1dcbd44
commit 36f2bdaf81
3 changed files with 87 additions and 2 deletions

View File

@@ -22,6 +22,11 @@ type MessageResize struct {
Rows uint `json:"rows"`
}
// MessagePing ping 消息
type MessagePing struct {
Ping bool `json:"ping"`
}
// Turn PTY 终端
type Turn struct {
ctx context.Context
@@ -78,6 +83,7 @@ func (t *Turn) Close() {
// Handle 从 WebSocket 读取输入写入 PTY
func (t *Turn) Handle(ctx context.Context) error {
var resize MessageResize
var ping MessagePing
go func() { _ = t.Pipe(ctx) }()
@@ -92,6 +98,12 @@ func (t *Turn) Handle(ctx context.Context) error {
return fmt.Errorf("failed to read ws message: %w", err)
}
// 判断是否是 ping 消息
if err = json.Unmarshal(data, &ping); err == nil && ping.Ping {
_ = t.ws.Write(ctx, websocket.MessageText, []byte(`{"pong":true}`))
continue
}
// 判断是否是 resize 消息
if err = json.Unmarshal(data, &resize); err == nil {
if resize.Resize && resize.Columns > 0 && resize.Rows > 0 {

View File

@@ -17,6 +17,10 @@ type MessageResize struct {
Rows int `json:"rows"`
}
type MessagePing struct {
Ping bool `json:"ping"`
}
type Turn struct {
ctx context.Context
stdin io.WriteCloser
@@ -72,6 +76,7 @@ func (t *Turn) Close() {
func (t *Turn) Handle(ctx context.Context) error {
var resize MessageResize
var ping MessagePing
for {
select {
@@ -84,6 +89,12 @@ func (t *Turn) Handle(ctx context.Context) error {
return fmt.Errorf("reading ws message err: %v", err)
}
// 判断是否是 ping 消息
if err = json.Unmarshal(data, &ping); err == nil && ping.Ping {
_ = t.ws.Write(ctx, websocket.MessageText, []byte(`{"pong":true}`))
continue
}
// 判断是否是 resize 消息
if err = json.Unmarshal(data, &resize); err == nil {
if resize.Resize && resize.Columns > 0 && resize.Rows > 0 {

View File

@@ -34,6 +34,9 @@ interface TerminalTab {
ws: WebSocket | null
element: HTMLElement | null
connected: boolean
latency: number
pingTimer: ReturnType<typeof setInterval> | null
lastPingTime: number
}
// 状态
@@ -159,7 +162,10 @@ const addTab = async (hostId: number) => {
webglAddon: null,
ws: null,
element: null,
connected: false
connected: false,
latency: 0,
pingTimer: null,
lastPingTime: 0
}
tabs.value.push(tab)
activeTabId.value = tabId
@@ -253,6 +259,18 @@ const initTerminal = async (tabId: string) => {
tab.ws.onmessage = (ev) => {
const data: ArrayBuffer | string = ev.data
// 检查是否是 pong 响应
if (typeof data === 'string') {
try {
const json = JSON.parse(data)
if (json.pong) {
handlePong(tab)
return
}
} catch {
// 不是 JSON正常处理
}
}
tab.terminal?.write(typeof data === 'string' ? data : new Uint8Array(data))
}
@@ -288,6 +306,9 @@ const initTerminal = async (tabId: string) => {
tab.terminal.focus()
tab.connected = true
// 启动延迟检测
startPingTimer(tab)
tab.ws.onclose = () => {
tab.connected = false
tab.terminal?.write('\r\n' + $gettext('Connection closed. Please refresh.'))
@@ -308,6 +329,10 @@ const initTerminal = async (tabId: string) => {
// 销毁标签
const disposeTab = (tab: TerminalTab) => {
try {
if (tab.pingTimer) {
clearInterval(tab.pingTimer)
tab.pingTimer = null
}
tab.ws?.close()
tab.terminal?.dispose()
tab.fitAddon = null
@@ -402,6 +427,43 @@ const applyFontSettings = () => {
})
}
// 启动延迟检测定时器
const startPingTimer = (tab: TerminalTab) => {
// 立即执行一次
sendPing(tab)
// 每3秒检测一次
tab.pingTimer = setInterval(() => {
sendPing(tab)
}, 3000)
}
// 发送 ping
const sendPing = (tab: TerminalTab) => {
if (tab.ws?.readyState === WebSocket.OPEN) {
tab.lastPingTime = performance.now()
tab.ws.send(JSON.stringify({ ping: true }))
}
}
// 处理 pong 响应
const handlePong = (tab: TerminalTab) => {
if (tab.lastPingTime > 0) {
tab.latency = Math.round(performance.now() - tab.lastPingTime)
tab.lastPingTime = 0
}
}
// 渲染标签标题
const renderTabLabel = (tab: TerminalTab) => {
const latencyColor = tab.connected ? '#18a058' : '#d03050'
const icon = tab.connected ? '✓' : '✗'
return h('span', { class: 'tab-label' }, [
h('span', { style: { color: latencyColor, marginRight: '4px' } }, `${tab.latency} ms`),
h('span', { style: { color: latencyColor, marginRight: '4px' } }, icon),
tab.name
])
}
// 全屏切换
const toggleFullscreen = async () => {
const container = terminalContainer.value
@@ -479,7 +541,7 @@ onUnmounted(() => {
v-for="tab in tabs"
:key="tab.id"
:name="tab.id"
:tab="tab.name"
:tab="renderTabLabel(tab)"
display-directive="show:lazy"
>
</n-tab-pane>