import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useConnectionsStore, type ConnectionInfo } from '../../connections.store'; import { sessions, activeSessionId } from '../state'; import { generateSessionId } from '../utils'; import type { SessionState, SshTerminalInstance, StatusMonitorInstance, DockerManagerInstance, SftpManagerInstance, WsManagerInstance } from '../types'; import { createWebSocketConnectionManager } from '../../../composables/useWebSocketConnection'; import { createSshTerminalManager, type SshTerminalDependencies } from '../../../composables/useSshTerminal'; import { createStatusMonitorManager, type StatusMonitorDependencies } from '../../../composables/useStatusMonitor'; import { createDockerManager, type DockerManagerDependencies } from '../../../composables/useDockerManager'; import { registerSshSuspendHandlers } from './sshSuspendActions'; const SESSION_ORDER_STORAGE_KEY = 'sessionOrder'; // --- 辅助函数 (特定于此模块的 actions) --- const findConnectionInfo = (connectionId: number | string, connectionsStore: ReturnType): ConnectionInfo | undefined => { return connectionsStore.connections.find(c => c.id === Number(connectionId)); }; const getNextTerminalIndex = (connectionId: string): number => { let maxTerminalIndex = 0; sessions.value.forEach((session) => { if (session.connectionId === connectionId) { maxTerminalIndex = Math.max(maxTerminalIndex, session.terminalIndex || 0); } }); return maxTerminalIndex + 1; }; const getOrderedSessionIds = (): string[] => { const savedOrderStr = localStorage.getItem(SESSION_ORDER_STORAGE_KEY); let savedOrder: string[] = []; if (savedOrderStr) { try { savedOrder = JSON.parse(savedOrderStr); } catch (error) { console.error('[SessionActions] 解析 sessionOrder 失败,回退到创建顺序。', error); savedOrder = []; } } const sessionList = Array.from(sessions.value.values()); if (savedOrder.length === 0) { return sessionList .sort((a, b) => a.createdAt - b.createdAt) .map((session) => session.sessionId); } return sessionList .sort((a, b) => { const indexA = savedOrder.indexOf(a.sessionId); const indexB = savedOrder.indexOf(b.sessionId); if (indexA === -1 && indexB === -1) return a.createdAt - b.createdAt; if (indexA === -1) return 1; if (indexB === -1) return -1; return indexA - indexB; }) .map((session) => session.sessionId); }; const saveOrderedSessionIds = (sessionIds: string[]) => { localStorage.setItem(SESSION_ORDER_STORAGE_KEY, JSON.stringify(sessionIds)); }; const insertSessionIdIntoOrder = (sessionId: string, connectionId: string) => { const orderedSessionIds = getOrderedSessionIds(); const nextOrder = [...orderedSessionIds]; if (nextOrder.includes(sessionId)) { return; } let insertionIndex = nextOrder.length; for (let index = nextOrder.length - 1; index >= 0; index -= 1) { const orderedSession = sessions.value.get(nextOrder[index]); if (orderedSession?.connectionId === connectionId) { insertionIndex = index + 1; break; } } nextOrder.splice(insertionIndex, 0, sessionId); saveOrderedSessionIds(nextOrder); }; const replaceSessionIdInOrder = (previousSessionId: string, nextSessionId: string) => { const orderedSessionIds = getOrderedSessionIds(); const updatedOrder = orderedSessionIds.map((sessionId) => (sessionId === previousSessionId ? nextSessionId : sessionId)); saveOrderedSessionIds(Array.from(new Set(updatedOrder))); }; const removeSessionIdFromOrder = (sessionId: string) => { const orderedSessionIds = getOrderedSessionIds().filter((orderedSessionId) => orderedSessionId !== sessionId); saveOrderedSessionIds(orderedSessionIds); }; // --- Actions --- export const openNewSession = ( connectionOrId: ConnectionInfo | number | string, dependencies: { connectionsStore: ReturnType; t: ReturnType['t']; }, existingSessionId?: string // 可选的预定义会话 ID ) => { const { connectionsStore, t } = dependencies; let connInfo: ConnectionInfo | undefined; let connIdForLog: string | number; if (typeof connectionOrId === 'object' && connectionOrId !== null && 'id' in connectionOrId) { connInfo = connectionOrId as ConnectionInfo; connIdForLog = connInfo.id; } else { connIdForLog = connectionOrId as number | string; connInfo = findConnectionInfo(connIdForLog, connectionsStore); } console.log(`[SessionActions] 请求打开新会话: ${connIdForLog}${existingSessionId ? `, 使用预定义 ID: ${existingSessionId}` : ''}`); if (!connInfo) { console.error(`[SessionActions] 无法打开新会话:找不到 ID 为 ${connIdForLog} 的连接信息。`); // TODO: 向用户显示错误 return; } const newSessionId = existingSessionId || generateSessionId(); const dbConnId = String(connInfo.id); // connInfo is now guaranteed to be defined here const terminalIndex = getNextTerminalIndex(dbConnId); // 1. 创建管理器实例 const isResume = !!existingSessionId; // 如果提供了 existingSessionId,则为恢复流程 // 稍后创建 wsManager,先创建 SessionState 对象的一部分 const newSessionPartial: Omit & { wsManager?: WsManagerInstance } = { sessionId: newSessionId, connectionId: dbConnId, connectionName: connInfo.name || connInfo.host, terminalIndex, editorTabs: ref([]), activeEditorTabId: ref(null), commandInputContent: ref(''), isCommandRunning: ref(false), terminalInputBuffer: ref(''), isMarkedForSuspend: false, createdAt: Date.now(), disposables: [], }; const wsManager = createWebSocketConnectionManager( newSessionId, // 这个 sessionId 在 wsManager 内部使用,可能与 SessionState.sessionId 不同步(如果后者被后端更新) dbConnId, t, { isResumeFlow: isResume, getIsMarkedForSuspend: () => { return !!newSessionPartial.isMarkedForSuspend; } } ); newSessionPartial.wsManager = wsManager; // 将 wsManager 添加回部分对象 const sshTerminalDeps: SshTerminalDependencies = { sendMessage: wsManager.sendMessage, onMessage: wsManager.onMessage, isConnected: wsManager.isConnected, }; const terminalManager = createSshTerminalManager(newSessionId, sshTerminalDeps, t); const statusMonitorDeps: StatusMonitorDependencies = { onMessage: wsManager.onMessage, isConnected: wsManager.isConnected, }; const statusMonitorManager = createStatusMonitorManager(newSessionId, statusMonitorDeps); const dockerManagerDeps: DockerManagerDependencies = { sendMessage: wsManager.sendMessage, onMessage: wsManager.onMessage, isConnected: wsManager.isConnected, }; const dockerManager = createDockerManager(newSessionId, dockerManagerDeps, { t }); // 2. 完成 SessionState 对象 const newSession: SessionState = { ...newSessionPartial, // 包含 sessionId, connectionId, connectionName, wsManager, editorTabs, etc. wsManager: wsManager, // 确保 wsManager 被正确赋值 sftpManagers: new Map(), terminalManager: terminalManager, statusMonitorManager: statusMonitorManager, dockerManager: dockerManager, }; // newSession.isMarkedForSuspend 已经在 newSessionPartial 中初始化为 false // 3. 添加到 Map 并激活 const newSessionsMap = new Map(sessions.value); newSessionsMap.set(newSessionId, newSession); sessions.value = newSessionsMap; insertSessionIdIntoOrder(newSessionId, dbConnId); activeSessionId.value = newSessionId; console.log(`[SessionActions] 已创建新会话实例: ${newSessionId} for connection ${dbConnId} (terminal #${terminalIndex})`); // +++ 在连接前设置 ssh:connected 处理器以更新 sessionId +++ const originalFrontendSessionIdForHandler = newSessionId; // 捕获初始ID给闭包 const unregisterConnectedHandler = wsManager.onMessage('ssh:connected', (connectedPayload: any) => { const backendSID = connectedPayload.sessionId as string; const backendCID = String(connectedPayload.connectionId); console.log(`[SessionActions/ssh:connected] 收到消息。前端初始SID: ${originalFrontendSessionIdForHandler}, 后端SID: ${backendSID}, 后端CID: ${backendCID}`); const sessionToUpdate = sessions.value.get(originalFrontendSessionIdForHandler); if (sessionToUpdate) { if (sessionToUpdate.connectionId !== backendCID) { console.warn(`[SessionActions/ssh:connected] 后端CID ${backendCID} 与会话 ${originalFrontendSessionIdForHandler} 的期望CID ${sessionToUpdate.connectionId} 不匹配。终止SID更新。`); return; } if (backendSID && backendSID !== originalFrontendSessionIdForHandler) { console.log(`[SessionActions/ssh:connected] 会话ID需要更新:从 ${originalFrontendSessionIdForHandler} 到 ${backendSID}。`); const currentSessions = new Map(sessions.value); currentSessions.delete(originalFrontendSessionIdForHandler); sessionToUpdate.sessionId = backendSID; // 更新会话对象内部的sessionId currentSessions.set(backendSID, sessionToUpdate); sessions.value = currentSessions; replaceSessionIdInOrder(originalFrontendSessionIdForHandler, backendSID); if (activeSessionId.value === originalFrontendSessionIdForHandler) { activeSessionId.value = backendSID; console.log(`[SessionActions/ssh:connected] 活动会话ID已更新为 ${backendSID}。`); } console.log(`[SessionActions/ssh:connected] 会话存储已更新,新键为 ${backendSID}。`); } else if (backendSID === originalFrontendSessionIdForHandler) { console.log(`[SessionActions/ssh:connected] 后端SID ${backendSID} 与前端SID匹配。无需重新键控。`); } else { console.error(`[SessionActions/ssh:connected] 从后端收到的 ssh:connected 消息中缺少有效的sessionId。Payload:`, connectedPayload); } } else { console.warn(`[SessionActions/ssh:connected] 当处理后端SID ${backendSID} 时,在存储中未找到对应的前端初始SID ${originalFrontendSessionIdForHandler} 的会话。`); } // 此处理器主要用于初始的 sessionId 同步,通常在第一次收到 ssh:connected 后就可以注销, // 以避免后续可能的意外重连消息再次触发此逻辑。 // 但如果 backendID 保证在 ssh:connected 时才首次确定,则保留可能也无害。 // 为简单起见,暂不在此处自动注销。注销将在 closeSession 中处理。 }); if (newSession.disposables) { newSession.disposables.push(unregisterConnectedHandler); } // 4. 启动 WebSocket 连接 const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsHostAndPort = window.location.host; const wsUrl = `${protocol}//${wsHostAndPort}/ws/`; console.log(`[SessionActions] Generated WebSocket URL: ${wsUrl}`); wsManager.connect(wsUrl); console.log(`[SessionActions] 已为会话 ${newSessionId} 启动 WebSocket 连接。`); // 注册 SSH 挂起相关的 WebSocket 消息处理器 // 确保只对 SSH 类型的连接注册 (虽然 wsManager 本身不包含类型信息,但 openNewSession 通常只为 SSH 调用) // 如果 connInfo 存在且类型为 SSH,则注册 if (connInfo && connInfo.type === 'SSH') { registerSshSuspendHandlers(wsManager); console.log(`[SessionActions] 已为 SSH 会话 ${newSessionId} 注册 SSH 挂起处理器。`); } else if (connInfo) { console.log(`[SessionActions] 会话 ${newSessionId} 类型为 ${connInfo.type},不注册 SSH 挂起处理器。`); } }; export const activateSession = (sessionId: string) => { if (sessions.value.has(sessionId)) { if (activeSessionId.value !== sessionId) { activeSessionId.value = sessionId; console.log(`[SessionActions] 已激活会话: ${sessionId}`); } else { console.log(`[SessionActions] 会话 ${sessionId} 已经是活动状态。`); } } else { console.warn(`[SessionActions] 尝试激活不存在的会话 ID: ${sessionId}`); } }; export const closeSession = (sessionId: string) => { console.log(`[SessionActions] 请求关闭会话 ID: ${sessionId}`); const sessionToClose = sessions.value.get(sessionId); if (!sessionToClose) { console.warn(`[SessionActions] 尝试关闭不存在的会话 ID: ${sessionId}`); return; } // 1. 调用实例上的清理和断开方法 sessionToClose.wsManager.disconnect(); console.log(`[SessionActions] 已为会话 ${sessionId} 调用 wsManager.disconnect()`); sessionToClose.sftpManagers.forEach((manager, instanceId) => { manager.cleanup(); console.log(`[SessionActions] 已为会话 ${sessionId} 的 sftpManager (实例 ${instanceId}) 调用 cleanup()`); }); sessionToClose.sftpManagers.clear(); sessionToClose.terminalManager.cleanup(); // 调用存储在会话中的所有清理函数 if (sessionToClose.disposables && Array.isArray(sessionToClose.disposables)) { sessionToClose.disposables.forEach(dispose => { try { dispose(); } catch (e) { console.error(`[SessionActions] 清理disposable时出错:`, e); } }); sessionToClose.disposables = []; // 清空数组 console.log(`[SessionActions] 已为会话 ${sessionId} 调用所有disposables。`); } console.log(`[SessionActions] 已为会话 ${sessionId} 调用 terminalManager.cleanup()`); sessionToClose.statusMonitorManager.cleanup(); console.log(`[SessionActions] 已为会话 ${sessionId} 调用 statusMonitorManager.cleanup()`); sessionToClose.dockerManager.cleanup(); console.log(`[SessionActions] 已为会话 ${sessionId} 调用 dockerManager.cleanup()`); // 2. 从 Map 中移除会话 const newSessionsMap = new Map(sessions.value); newSessionsMap.delete(sessionId); sessions.value = newSessionsMap; removeSessionIdFromOrder(sessionId); console.log(`[SessionActions] 已从 Map 中移除会话: ${sessionId}`); // 3. 切换活动标签页 if (activeSessionId.value === sessionId) { const remainingSessions = Array.from(sessions.value.keys()); const nextActiveId = remainingSessions.length > 0 ? remainingSessions[remainingSessions.length - 1] : null; activeSessionId.value = nextActiveId; console.log(`[SessionActions] 关闭活动会话后,切换到: ${nextActiveId}`); } }; export const handleConnectRequest = ( connection: ConnectionInfo, dependencies: { connectionsStore: ReturnType; router: ReturnType; openRdpModalAction: (connection: ConnectionInfo) => void; // 来自 modalActions openVncModalAction: (connection: ConnectionInfo) => void; // 来自 modalActions t: ReturnType['t']; } ) => { const { connectionsStore, router, openRdpModalAction, openVncModalAction, t } = dependencies; if (connection.type === 'RDP') { openRdpModalAction(connection); } else if (connection.type === 'VNC') { openVncModalAction(connection); } else { const connIdStr = String(connection.id); let activeAndDisconnected = false; if (activeSessionId.value) { const currentActiveSession = sessions.value.get(activeSessionId.value); if (currentActiveSession && currentActiveSession.connectionId === connIdStr) { const currentStatus = currentActiveSession.wsManager.connectionStatus.value; console.log(`[SessionActions] 点击的是当前活动会话 ${activeSessionId.value},状态: ${currentStatus}`); if (currentStatus === 'disconnected' || currentStatus === 'error') { activeAndDisconnected = true; console.log(`[SessionActions] 活动会话 ${activeSessionId.value} 已断开或出错,尝试重连...`); const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsHostAndPort = window.location.host; const wsUrl = `${protocol}//${wsHostAndPort}/ws/`; console.log(`[SessionActions handleConnectRequest] Generated WebSocket URL for reconnect: ${wsUrl}`); currentActiveSession.wsManager.connect(wsUrl); activateSession(activeSessionId.value); router.push({ name: 'Workspace' }); } } } if (!activeAndDisconnected) { console.log(`[SessionActions] 不满足重连条件或点击了其他连接,将打开新会话 for ID: ${connIdStr}`); openNewSession(connIdStr, { connectionsStore, t }); router.push({ name: 'Workspace' }); } } }; export const handleOpenNewSession = ( connectionId: number | string, dependencies: { connectionsStore: ReturnType; t: ReturnType['t']; } ) => { console.log(`[SessionActions] handleOpenNewSession called for ID: ${connectionId}`); openNewSession(connectionId, dependencies); // existingSessionId 将为 undefined,因此会生成新的 }; export const cleanupAllSessions = () => { console.log('[SessionActions] 清理所有会话...'); sessions.value.forEach((_session, sessionId) => { closeSession(sessionId); }); // sessions.value.clear(); // closeSession 内部会逐个删除,这里不需要重复clear,但确认Map为空 if (sessions.value.size > 0) { // 以防万一 const newSessionsMap = new Map(sessions.value); newSessionsMap.clear(); sessions.value = newSessionsMap; } saveOrderedSessionIds([]); activeSessionId.value = null; };