diff --git a/packages/backend/src/websocket/connection.ts b/packages/backend/src/websocket/connection.ts index 15342b9..20bd945 100644 --- a/packages/backend/src/websocket/connection.ts +++ b/packages/backend/src/websocket/connection.ts @@ -16,8 +16,10 @@ import { SshSuspendEntryRemovedResponse, SshSuspendNameEditedResponse, SshSuspendAutoTerminatedNotification, - SshMarkForSuspendRequest, // +++ 新增导入 - SshMarkedForSuspendAck, // +++ 新增导入 + SshMarkForSuspendRequest, + SshMarkedForSuspendAck, + SshUnmarkForSuspendRequest, // +++ 新增导入 +++ + SshUnmarkedForSuspendAck, // +++ 新增导入 +++ ClientState } from './types'; import { SshSuspendService } from '../services/ssh-suspend.service'; @@ -317,7 +319,8 @@ export function initializeConnectionHandler(wss: WebSocketServer, sshSuspendServ case 'SSH_MARK_FOR_SUSPEND': { const markPayload = payload as SshMarkForSuspendRequest['payload']; const sessionToMarkId = markPayload.sessionId; - console.log(`[WebSocket Handler] Received SSH_MARK_FOR_SUSPEND. UserID: ${ws.userId}, TargetSessionID: ${sessionToMarkId}`); + const initialBuffer = markPayload.initialBuffer; // +++ 获取 initialBuffer +++ + console.log(`[WebSocket Handler] Received SSH_MARK_FOR_SUSPEND. UserID: ${ws.userId}, TargetSessionID: ${sessionToMarkId}, InitialBuffer provided: ${!!initialBuffer}`); if (!ws.userId) { console.error(`[SSH_MARK_FOR_SUSPEND] 用户 ID 未定义。`); @@ -346,8 +349,16 @@ export function initializeConnectionHandler(wss: WebSocketServer, sshSuspendServ // 确保日志目录存在 (服务内部通常会做,但这里也可以调用一次) await temporaryLogStorageService.ensureLogDirectoryExists(); - // 可以在这里预先写入一个标记,表明日志开始记录 - await temporaryLogStorageService.writeToLog(logPathSuffix, `--- Log recording started for session ${sessionToMarkId} at ${new Date().toISOString()} ---\n`); + + // +++ 如果有 initialBuffer,先写入它 +++ + if (initialBuffer) { + // 确保 initialBuffer 后有一个换行符,以便后续日志在新行开始 + const formattedInitialBuffer = initialBuffer.endsWith('\n') ? initialBuffer : `${initialBuffer}\n`; + await temporaryLogStorageService.writeToLog(logPathSuffix, formattedInitialBuffer); + console.log(`[SSH_MARK_FOR_SUSPEND] 已将初始缓冲区写入日志 (会话: ${sessionToMarkId})。`); + } + // --- 移除自动添加的日志标记行 --- + // await temporaryLogStorageService.writeToLog(logPathSuffix, `--- Log recording continued for session ${sessionToMarkId} at ${new Date().toISOString()} ---\n`); console.log(`[SSH_MARK_FOR_SUSPEND] 会话 ${sessionToMarkId} 已成功标记待挂起。日志将记录到与 ${logPathSuffix} 关联的文件。`); const response: SshMarkedForSuspendAck = { @@ -365,6 +376,59 @@ export function initializeConnectionHandler(wss: WebSocketServer, sshSuspendServ } break; } + case 'SSH_UNMARK_FOR_SUSPEND': { + const unmarkPayload = payload as SshUnmarkForSuspendRequest['payload']; + const sessionToUnmarkId = unmarkPayload.sessionId; + console.log(`[WebSocket Handler] Received SSH_UNMARK_FOR_SUSPEND. UserID: ${ws.userId}, TargetSessionID: ${sessionToUnmarkId}`); + const ackPayloadBase = { sessionId: sessionToUnmarkId }; + + if (!ws.userId) { + console.error(`[SSH_UNMARK_FOR_SUSPEND] 用户 ID 未定义。`); + if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: false, error: '用户认证失败' } as SshUnmarkedForSuspendAck['payload'] })); + break; + } + + const activeSessionState = clientStates.get(sessionToUnmarkId); + if (!activeSessionState) { + console.warn(`[SSH_UNMARK_FOR_SUSPEND] 未找到会话: ${sessionToUnmarkId}`); + if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: false, error: '未找到要取消标记的会话' } as SshUnmarkedForSuspendAck['payload'] })); + break; + } + + if (!activeSessionState.isMarkedForSuspend) { + console.warn(`[SSH_UNMARK_FOR_SUSPEND] 会话 ${sessionToUnmarkId} 并未被标记为待挂起。`); + // 即使未标记,也回复成功,因为最终状态是“未标记” + if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: true, error: '会话本就未标记' } as SshUnmarkedForSuspendAck['payload'] })); + break; + } + + try { + activeSessionState.isMarkedForSuspend = false; + const logPathToDelete = activeSessionState.suspendLogPath; + activeSessionState.suspendLogPath = undefined; // 清除日志路径 + + if (logPathToDelete) { + await temporaryLogStorageService.deleteLog(logPathToDelete); + console.log(`[SSH_UNMARK_FOR_SUSPEND] 已删除会话 ${sessionToUnmarkId} 的临时挂起日志: ${logPathToDelete}`); + } + + console.log(`[SSH_UNMARK_FOR_SUSPEND] 会话 ${sessionToUnmarkId} 已成功取消标记。`); + const response: SshUnmarkedForSuspendAck = { + type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', + payload: { ...ackPayloadBase, success: true } + }; + if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(response)); + } catch (error: any) { + console.error(`[SSH_UNMARK_FOR_SUSPEND] 取消标记会话 ${sessionToUnmarkId} 失败:`, error); + // 尝试回滚状态(尽管可能意义不大,因为错误可能在删除日志时发生) + if (activeSessionState) { + activeSessionState.isMarkedForSuspend = true; // 保持标记状态 + // activeSessionState.suspendLogPath = logPathToDelete; // 如果需要,可以恢复路径,但删除失败更可能是问题 + } + if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'SSH_UNMARKED_FOR_SUSPEND_ACK', payload: { ...ackPayloadBase, success: false, error: error.message || '取消标记会话失败' } as SshUnmarkedForSuspendAck['payload'] })); + } + break; + } default: console.warn(`WebSocket:收到来自 ${ws.username} (会话: ${sessionId}) 的未知消息类型: ${type}`); if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'error', payload: `不支持的消息类型: ${type}` })); diff --git a/packages/backend/src/websocket/types.ts b/packages/backend/src/websocket/types.ts index 6e4cf1a..ddb3deb 100644 --- a/packages/backend/src/websocket/types.ts +++ b/packages/backend/src/websocket/types.ts @@ -23,8 +23,8 @@ export interface ClientState { // 导出以便 Service 可以导入 isShellReady?: boolean; // 新增:标记 Shell 是否已准备好处理输入和调整大小 isSuspendedByService?: boolean; // 新增:标记此会话是否已被 SshSuspendService 接管 isMarkedForSuspend?: boolean; // 新增:标记此会话是否已被用户请求挂起(等待断开连接) - suspendLogPath?: string; // 新增:如果标记挂起,则存储日志路径 - suspendLogWritableStream?: NodeJS.WritableStream; // 新增:用于写入挂起日志的流 + suspendLogPath?: string; // 新增:如果标记挂起,则存储日志路径 (基于原始 sessionId) + // suspendLogWritableStream?: NodeJS.WritableStream; // 移除,将直接使用 temporaryLogStorageService.writeToLog } export interface PortInfo { @@ -110,6 +110,14 @@ export interface SshMarkForSuspendRequest { type: "SSH_MARK_FOR_SUSPEND"; payload: { sessionId: string; // The ID of the active SSH session to be marked + initialBuffer?: string; // +++ 新增:可选的初始屏幕缓冲区内容 +++ + }; +} + +export interface SshUnmarkForSuspendRequest { + type: "SSH_UNMARK_FOR_SUSPEND"; + payload: { + sessionId: string; // The ID of the active SSH session to be unmarked }; } @@ -197,6 +205,15 @@ export interface SshMarkedForSuspendAck { }; } +export interface SshUnmarkedForSuspendAck { // +++ 新增 S2C 类型 +++ + type: "SSH_UNMARKED_FOR_SUSPEND_ACK"; + payload: { + sessionId: string; // The ID of the session that was unmarked + success: boolean; + error?: string; + }; +} + export interface SshSuspendAutoTerminatedNotification { type: "SSH_SUSPEND_AUTO_TERMINATED"; payload: { @@ -213,7 +230,8 @@ export type SshSuspendClientToServerMessages = | SshSuspendTerminateRequest | SshSuspendRemoveEntryRequest | SshSuspendEditNameRequest - | SshMarkForSuspendRequest; // Added new request type + | SshMarkForSuspendRequest + | SshUnmarkForSuspendRequest; // +++ 新增到联合类型 +++ // Union type for all server-to-client messages for SSH Suspend export type SshSuspendServerToClientMessages = @@ -225,7 +243,8 @@ export type SshSuspendServerToClientMessages = | SshSuspendEntryRemovedResponse | SshSuspendNameEditedResponse | SshSuspendAutoTerminatedNotification - | SshMarkedForSuspendAck; // Added new response type + | SshMarkedForSuspendAck + | SshUnmarkedForSuspendAck; // +++ 新增到联合类型 +++ // It might be useful to have a general type for incoming messages if not already present // For example, if you have a main message handler: diff --git a/packages/frontend/src/components/TerminalTabBar.vue b/packages/frontend/src/components/TerminalTabBar.vue index 56805e0..a6177c0 100644 --- a/packages/frontend/src/components/TerminalTabBar.vue +++ b/packages/frontend/src/components/TerminalTabBar.vue @@ -171,13 +171,20 @@ const handleContextMenuAction = (payload: { action: string; targetId: string | n // 注意:关闭左侧通常不包括当前标签本身 emitWorkspaceEvent('session:closeToLeft', { targetSessionId: targetId }); break; - case 'suspend-session': // 新增处理 suspend-session 动作 - // 确保 targetId 是字符串类型的 sessionId + case 'mark-for-suspend': // +++ 修改 action 名称 +++ if (typeof targetId === 'string') { - console.log(`[TabBar] Context menu action 'suspend-session' requested for session ID: ${targetId}`); - sessionStore.requestStartSshSuspend(targetId); + console.log(`[TabBar] Context menu action 'mark-for-suspend' requested for session ID: ${targetId}`); + sessionStore.requestStartSshSuspend(targetId); // 这个 action 现在是标记 } else { - console.warn(`[TabBar] 'suspend-session' action called with invalid targetId:`, targetId); + console.warn(`[TabBar] 'mark-for-suspend' action called with invalid targetId:`, targetId); + } + break; + case 'unmark-for-suspend': // +++ 新增 case +++ + if (typeof targetId === 'string') { + console.log(`[TabBar] Context menu action 'unmark-for-suspend' requested for session ID: ${targetId}`); + sessionStore.requestUnmarkSshSuspend(targetId); + } else { + console.warn(`[TabBar] 'unmark-for-suspend' action called with invalid targetId:`, targetId); } break; default: @@ -201,14 +208,17 @@ const contextMenuItems = computed(() => { const currentIndex = props.sessions.findIndex(s => s.sessionId === targetSessionIdValue); const totalTabs = props.sessions.length; - // 添加挂起会话菜单项(如果适用) + // 添加标记/取消标记挂起会话菜单项(如果适用) if (connectionInfo && connectionInfo.type === 'SSH') { - // 仅对活动的SSH会话显示 (可以进一步判断会话是否真的已连接等) - const isActiveSession = targetSessionState.wsManager.isConnected.value; // 检查 WebSocket 是否连接 - if (isActiveSession) { - items.push({ label: 'tabs.contextMenu.suspendSession', action: 'suspend-session' }); - // 为分隔符提供空的 label 和 action 以满足 MenuItem 类型 - items.push({ label: '', action: '', isSeparator: true }); + const isActiveSession = targetSessionState.wsManager.isConnected.value; + if (isActiveSession) { // 只对活动的SSH会话显示相关操作 + if (targetSessionState.isMarkedForSuspend) { + items.push({ label: 'tabs.contextMenu.unmarkForSuspend', action: 'unmark-for-suspend' }); + } else { + // 当未标记时,显示原来的“挂起”文本,但 action 触发新的标记流程 + items.push({ label: 'tabs.contextMenu.suspendSession', action: 'mark-for-suspend' }); + } + items.push({ label: '', action: '', isSeparator: true }); // 分隔符 } } diff --git a/packages/frontend/src/locales/en-US.json b/packages/frontend/src/locales/en-US.json index 6a33494..324abb4 100644 --- a/packages/frontend/src/locales/en-US.json +++ b/packages/frontend/src/locales/en-US.json @@ -1137,7 +1137,8 @@ "closeOthers": "Close Other Tabs", "closeRight": "Close Tabs to the Right", "closeLeft": "Close Tabs to the Left", - "suspendSession": "Suspend Session" + "suspendSession": "Suspend Session", + "unmarkForSuspend": "Unmark Suspend" }, "closeTabTooltip": "Close Tab", "newTabTooltip": "New Connection Tab" diff --git a/packages/frontend/src/locales/zh-CN.json b/packages/frontend/src/locales/zh-CN.json index 86af26d..3da0534 100644 --- a/packages/frontend/src/locales/zh-CN.json +++ b/packages/frontend/src/locales/zh-CN.json @@ -1139,7 +1139,8 @@ "closeOthers": "关闭其他标签页", "closeRight": "关闭右侧标签页", "closeLeft": "关闭左侧标签页", - "suspendSession": "挂起会话" + "suspendSession": "挂起会话", + "unmarkForSuspend": "取消挂起" }, "closeTabTooltip": "关闭标签页", "newTabTooltip": "新建连接标签页" diff --git a/packages/frontend/src/stores/session/actions/sshSuspendActions.ts b/packages/frontend/src/stores/session/actions/sshSuspendActions.ts index 6a432b8..a806d02 100644 --- a/packages/frontend/src/stores/session/actions/sshSuspendActions.ts +++ b/packages/frontend/src/stores/session/actions/sshSuspendActions.ts @@ -3,13 +3,15 @@ import { v4 as uuidv4 } from 'uuid'; import { sessions, suspendedSshSessions, isLoadingSuspendedSessions, activeSessionId } from '../state'; import type { MessagePayload, - SshMarkForSuspendReqMessage, // +++ 修改:导入新的请求类型 +++ + SshMarkForSuspendReqMessage, + SshUnmarkForSuspendReqMessage, // +++ 新增导入 +++ SshSuspendResumeReqMessage, SshSuspendTerminateReqMessage, SshSuspendRemoveEntryReqMessage, SshSuspendEditNameReqMessage, // S2C Payloads - SshMarkedForSuspendAckPayload, // +++ 新增:导入新的响应类型 +++ + SshMarkedForSuspendAckPayload, + SshUnmarkedForSuspendAckPayload, // +++ 新增导入 +++ SshSuspendListResponsePayload, SshSuspendResumedNotifPayload, SshOutputCachedChunkPayload, @@ -77,14 +79,28 @@ export const requestStartSshSuspend = (sessionId: string): void => { return; } - // 不再需要获取 initialBuffer + let initialBuffer = ''; // +++ 恢复 initialBuffer 的获取 +++ + if (session.terminalManager && session.terminalManager.terminalInstance && session.terminalManager.terminalInstance.value) { + const term = session.terminalManager.terminalInstance.value; + const buffer = term.buffer.active; + for (let i = 0; i < buffer.length; i++) { + initialBuffer += (buffer.getLine(i)?.translateToString(true) || '') + '\n'; + } + // 移除可能多余的最后一个换行符 + if (initialBuffer.endsWith('\n')) { + initialBuffer = initialBuffer.slice(0, -1); + } + console.log(`[${t('term.sshSuspend')}] 已获取会话 ${sessionId} 的初始屏幕缓冲区内容,长度: ${initialBuffer.length}`); + } else { + console.warn(`[${t('term.sshSuspend')}] 未能获取会话 ${sessionId} 的终端实例以提取初始缓冲区。`); + } - const message: SshMarkForSuspendReqMessage = { // +++ 修改:使用新的消息类型 +++ + const message: SshMarkForSuspendReqMessage = { type: 'SSH_MARK_FOR_SUSPEND', - payload: { sessionId }, + payload: { sessionId, initialBuffer: initialBuffer || undefined }, // +++ 将 initialBuffer 添加到 payload +++ }; session.wsManager.sendMessage(message); - console.log(`[${t('term.sshSuspend')}] 已发送 SSH_MARK_FOR_SUSPEND 请求 (会话 ID: ${sessionId})`); + console.log(`[${t('term.sshSuspend')}] 已发送 SSH_MARK_FOR_SUSPEND 请求 (会话 ID: ${sessionId}, 包含初始缓冲区: ${!!initialBuffer})`); // 前端在发送此请求后,会话应保持活动状态,直到用户关闭标签页或网络断开。 // 后端会在 WebSocket 关闭时处理实际的挂起。 // 用户界面上可以给一个提示,表明“此会话已标记,关闭后将尝试挂起”。 @@ -100,6 +116,38 @@ export const requestStartSshSuspend = (sessionId: string): void => { } }; +/** + * 请求取消标记一个会话为待挂起 + * @param sessionId 要取消标记的活动会话 ID + */ +export const requestUnmarkSshSuspend = (sessionId: string): void => { + const session = sessions.value.get(sessionId); + if (session && session.wsManager) { + if (!session.wsManager.isConnected.value) { + console.warn(`[${t('term.sshSuspend')}] WebSocket 未连接,无法请求取消标记挂起 (会话 ID: ${sessionId})。`); + useUiNotificationsStore().addNotification({ type: 'error', message: t('sshSuspend.notifications.wsNotConnectedError') }); + return; + } + if (!session.isMarkedForSuspend) { + console.warn(`[${t('term.sshSuspend')}] 会话 ${sessionId} 并未被标记为待挂起,无需取消。`); + // 可以选择不发送请求或发送一个让后端确认的请求 + // 为保持简单,如果前端状态已经是未标记,则不执行操作或仅给用户提示 + useUiNotificationsStore().addNotification({ type: 'info', message: t('sshSuspend.notifications.notMarkedWarning') }); + return; + } + + const message: SshUnmarkForSuspendReqMessage = { + type: 'SSH_UNMARK_FOR_SUSPEND', + payload: { sessionId }, + }; + session.wsManager.sendMessage(message); + console.log(`[${t('term.sshSuspend')}] 已发送 SSH_UNMARK_FOR_SUSPEND 请求 (会话 ID: ${sessionId})`); + } else { + console.warn(`[${t('term.sshSuspend')}] 未找到会话或 WebSocket 管理器 (会话 ID: ${sessionId}),无法请求取消标记挂起。`); + useUiNotificationsStore().addNotification({ type: 'error', message: t('sshSuspend.notifications.sessionNotFoundError') }); + } +}; + /** * 获取挂起的 SSH 会话列表 (通过 HTTP API) */ @@ -331,6 +379,32 @@ const handleSshMarkedForSuspendAck = (payload: SshMarkedForSuspendAckPayload): v } }; +const handleSshUnmarkedForSuspendAck = (payload: SshUnmarkedForSuspendAckPayload): void => { + const uiNotificationsStore = useUiNotificationsStore(); + console.log(`[${t('term.sshSuspend')}] 接到 SSH_UNMARKED_FOR_SUSPEND_ACK:`, payload); + const session = sessions.value.get(payload.sessionId); + + if (payload.success) { + if (session) { + session.isMarkedForSuspend = false; + } + uiNotificationsStore.addNotification({ + type: 'success', + message: t('sshSuspend.notifications.unmarkedSuccess', { id: payload.sessionId.slice(0,8) }), + }); + } else { + // 即便后端失败,如果前端之前是标记状态,也最好保持一致或提示用户检查 + // 但通常后端失败意味着前端状态可能与后端不一致,提示错误让用户知晓 + uiNotificationsStore.addNotification({ + type: 'error', + message: t('sshSuspend.notifications.unmarkError', { error: payload.error || t('term.unknownError') }), + }); + console.error(`[${t('term.sshSuspend')}] 取消标记会话 ${payload.sessionId} 失败: ${payload.error}`); + // 此处不自动回滚前端的 isMarkedForSuspend 状态,因为后端是权威源。 + // 如果后端说操作失败,那么会话可能仍然被后端认为是标记的(尽管这不应该发生,因为后端会先清除标记)。 + } +}; + const handleSshSuspendListResponse = (payload: SshSuspendListResponsePayload): void => { console.log(`[${t('term.sshSuspend')}] 接到 SSH_SUSPEND_LIST_RESPONSE,数量: ${payload.suspendSessions.length}`); suspendedSshSessions.value = payload.suspendSessions; @@ -536,8 +610,9 @@ export const registerSshSuspendHandlers = (wsManager: WsManagerInstance): void = // 注意:wsManager.onMessage 返回一个注销函数,如果需要,可以收集它们并在会话关闭时调用。 // 但通常这些处理器会随 wsManager 实例的生命周期一起存在。 - // wsManager.onMessage('SSH_SUSPEND_STARTED_RESP', (p: MessagePayload) => handleSshSuspendStartedResp(p as SshSuspendStartedRespPayload)); // 已移除 - wsManager.onMessage('SSH_MARKED_FOR_SUSPEND_ACK', (p: MessagePayload) => handleSshMarkedForSuspendAck(p as SshMarkedForSuspendAckPayload)); // +++ 新增处理器 +++ + // wsManager.onMessage('SSH_SUSPEND_STARTED_RESP', (p: MessagePayload) => handleSshSuspendStartedResp(p as SshSuspendStartedRespPayload)); + wsManager.onMessage('SSH_MARKED_FOR_SUSPEND_ACK', (p: MessagePayload) => handleSshMarkedForSuspendAck(p as SshMarkedForSuspendAckPayload)); + wsManager.onMessage('SSH_UNMARKED_FOR_SUSPEND_ACK', (p: MessagePayload) => handleSshUnmarkedForSuspendAck(p as SshUnmarkedForSuspendAckPayload)); // +++ 新增处理器 +++ wsManager.onMessage('SSH_SUSPEND_LIST_RESPONSE', (p: MessagePayload) => handleSshSuspendListResponse(p as SshSuspendListResponsePayload)); wsManager.onMessage('SSH_SUSPEND_RESUMED_NOTIF', (p: MessagePayload) => handleSshSuspendResumedNotif(p as SshSuspendResumedNotifPayload)); wsManager.onMessage('SSH_OUTPUT_CACHED_CHUNK', (p: MessagePayload) => handleSshOutputCachedChunk(p as SshOutputCachedChunkPayload)); diff --git a/packages/frontend/src/types/websocket.types.ts b/packages/frontend/src/types/websocket.types.ts index 981b7da..555fe5a 100644 --- a/packages/frontend/src/types/websocket.types.ts +++ b/packages/frontend/src/types/websocket.types.ts @@ -45,12 +45,23 @@ export interface SshSuspendEditNameReqPayload { customName: string; } -export interface SshMarkForSuspendReqPayload { // +++ 新增 +++ +export interface SshMarkForSuspendReqPayload { + sessionId: string; + initialBuffer?: string; // +++ 新增:可选的初始屏幕缓冲区内容 +++ +} + +export interface SshUnmarkForSuspendReqPayload { sessionId: string; } // --- Server to Client (S2C) Message Payloads --- -export interface SshMarkedForSuspendAckPayload { // +++ 新增 +++ +export interface SshMarkedForSuspendAckPayload { + sessionId: string; + success: boolean; + error?: string; +} + +export interface SshUnmarkedForSuspendAckPayload { // +++ 新增 +++ sessionId: string; success: boolean; error?: string; @@ -135,17 +146,27 @@ export interface SshSuspendEditNameReqMessage extends WebSocketMessage { payload: SshSuspendEditNameReqPayload; } -export interface SshMarkForSuspendReqMessage extends WebSocketMessage { // +++ 新增 +++ +export interface SshMarkForSuspendReqMessage extends WebSocketMessage { type: 'SSH_MARK_FOR_SUSPEND'; payload: SshMarkForSuspendReqPayload; } +export interface SshUnmarkForSuspendReqMessage extends WebSocketMessage { // +++ 新增 +++ + type: 'SSH_UNMARK_FOR_SUSPEND'; + payload: SshUnmarkForSuspendReqPayload; +} + // --- Specific S2C Message Interfaces --- -export interface SshMarkedForSuspendAckMessage extends WebSocketMessage { // +++ 新增 +++ +export interface SshMarkedForSuspendAckMessage extends WebSocketMessage { type: 'SSH_MARKED_FOR_SUSPEND_ACK'; payload: SshMarkedForSuspendAckPayload; } +export interface SshUnmarkedForSuspendAckMessage extends WebSocketMessage { // +++ 新增 +++ + type: 'SSH_UNMARKED_FOR_SUSPEND_ACK'; + payload: SshUnmarkedForSuspendAckPayload; +} + export interface SshSuspendStartedRespMessage extends WebSocketMessage { type: 'SSH_SUSPEND_STARTED'; payload: SshSuspendStartedRespPayload; @@ -194,10 +215,12 @@ export type SshSuspendC2SMessage = | SshSuspendTerminateReqMessage | SshSuspendRemoveEntryReqMessage | SshSuspendEditNameReqMessage - | SshMarkForSuspendReqMessage; // +++ 新增 +++ + | SshMarkForSuspendReqMessage + | SshUnmarkForSuspendReqMessage; // +++ 新增 +++ export type SshSuspendS2CMessage = - | SshMarkedForSuspendAckMessage // +++ 新增 +++ + | SshMarkedForSuspendAckMessage + | SshUnmarkedForSuspendAckMessage // +++ 新增 +++ | SshSuspendStartedRespMessage | SshSuspendListResponseMessage | SshSuspendResumedNotifMessage