Files
nexus-terminal/packages/frontend/src/stores/session/actions/sessionActions.ts
T
Baobhan Sith 61e7b64333 update
2025-05-10 01:23:47 +08:00

297 lines
14 KiB
TypeScript

// packages/frontend/src/stores/session/actions/sessionActions.ts
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useConnectionsStore, type ConnectionInfo } from '../../connections.store'; // 路径: packages/frontend/src/stores/connections.store.ts
import { sessions, activeSessionId } from '../state';
import { generateSessionId } from '../utils';
import type { SessionState, SshTerminalInstance, StatusMonitorInstance, DockerManagerInstance, SftpManagerInstance, WsManagerInstance } from '../types';
// Composables for manager creation - 路径相对于此文件
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'; // 新增:导入 SSH 挂起处理器注册函数
// getOrCreateSftpManager 将在 sftpManagerActions.ts 中定义,并在主 store 中协调
// --- 辅助函数 (特定于此模块的 actions) ---
const findConnectionInfo = (connectionId: number | string, connectionsStore: ReturnType<typeof useConnectionsStore>): ConnectionInfo | undefined => {
return connectionsStore.connections.find(c => c.id === Number(connectionId));
};
// --- Actions ---
export const openNewSession = (
connectionId: number | string,
dependencies: {
connectionsStore: ReturnType<typeof useConnectionsStore>;
t: ReturnType<typeof useI18n>['t'];
},
existingSessionId?: string // 新增:可选的预定义会话 ID
) => {
const { connectionsStore, t } = dependencies;
console.log(`[SessionActions] 请求打开新会话: ${connectionId}${existingSessionId ? `, 使用预定义 ID: ${existingSessionId}` : ''}`);
const connInfo = findConnectionInfo(connectionId, connectionsStore);
if (!connInfo) {
console.error(`[SessionActions] 无法打开新会话:找不到 ID 为 ${connectionId} 的连接信息。`);
// TODO: 向用户显示错误
return;
}
const newSessionId = existingSessionId || generateSessionId();
const dbConnId = String(connInfo.id);
// 1. 创建管理器实例
const isResume = !!existingSessionId; // 如果提供了 existingSessionId,则为恢复流程
const wsManager = createWebSocketConnectionManager(
newSessionId,
dbConnId,
t,
{ isResumeFlow: isResume } // 传递 isResumeFlow 选项
);
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 = {
sessionId: newSessionId,
connectionId: dbConnId,
connectionName: connInfo.name || connInfo.host,
wsManager: wsManager,
sftpManagers: new Map<string, SftpManagerInstance>(), // 初始化 Map
terminalManager: terminalManager,
statusMonitorManager: statusMonitorManager,
dockerManager: dockerManager,
editorTabs: ref([]),
activeEditorTabId: ref(null),
commandInputContent: ref(''),
disposables: [], // 初始化 disposables 数组
};
// 3. 添加到 Map 并激活
const newSessionsMap = new Map(sessions.value);
newSessionsMap.set(newSessionId, newSession);
sessions.value = newSessionsMap;
activeSessionId.value = newSessionId;
console.log(`[SessionActions] 已创建新会话实例: ${newSessionId} for connection ${dbConnId}`);
// +++ 在连接前设置 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;
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;
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<typeof useConnectionsStore>;
router: ReturnType<typeof useRouter>;
openRdpModalAction: (connection: ConnectionInfo) => void; // 来自 modalActions
openVncModalAction: (connection: ConnectionInfo) => void; // 来自 modalActions
t: ReturnType<typeof useI18n>['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<typeof useConnectionsStore>;
t: ReturnType<typeof useI18n>['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;
}
activeSessionId.value = null;
};