From 1a6ea421e667161c363b9518f0ea032032db3b1d Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Tue, 15 Apr 2025 23:16:00 +0800 Subject: [PATCH] update --- .../src/services/status-monitor.service.ts | 16 +- .../frontend/src/components/FileManager.vue | 716 +++++++++--------- .../frontend/src/components/StatusMonitor.vue | 51 +- packages/frontend/src/components/Terminal.vue | 1 + .../src/components/TerminalTabBar.vue | 146 ++++ .../components/WorkspaceConnectionList.vue | 17 +- .../src/composables/useFileUploader.ts | 18 +- .../src/composables/useSftpActions.ts | 275 +++---- .../src/composables/useSshTerminal.ts | 171 ++++- .../src/composables/useStatusMonitor.ts | 123 ++- .../src/composables/useWebSocketConnection.ts | 220 +++--- packages/frontend/src/locales/en.json | 6 +- packages/frontend/src/locales/zh.json | 11 +- packages/frontend/src/stores/session.store.ts | 252 ++++++ .../frontend/src/types/websocket.types.ts | 2 +- packages/frontend/src/views/WorkspaceView.vue | 325 ++++---- 16 files changed, 1435 insertions(+), 915 deletions(-) create mode 100644 packages/frontend/src/components/TerminalTabBar.vue create mode 100644 packages/frontend/src/stores/session.store.ts diff --git a/packages/backend/src/services/status-monitor.service.ts b/packages/backend/src/services/status-monitor.service.ts index eb3f487..26e863c 100644 --- a/packages/backend/src/services/status-monitor.service.ts +++ b/packages/backend/src/services/status-monitor.service.ts @@ -50,14 +50,14 @@ export class StatusMonitorService { startStatusPolling(sessionId: string, interval: number = DEFAULT_POLLING_INTERVAL): void { const state = this.clientStates.get(sessionId); if (!state || !state.sshClient) { - console.warn(`[StatusMonitor] 无法为会话 ${sessionId} 启动状态轮询:状态无效或 SSH 客户端不存在。`); + //console.warn(`[StatusMonitor] 无法为会话 ${sessionId} 启动状态轮询:状态无效或 SSH 客户端不存在。`); return; } if (state.statusIntervalId) { - console.warn(`[StatusMonitor] 会话 ${sessionId} 的状态轮询已在运行中。`); + //console.warn(`[StatusMonitor] 会话 ${sessionId} 的状态轮询已在运行中。`); return; } - console.log(`[StatusMonitor] 为会话 ${sessionId} 启动状态轮询,间隔 ${interval}ms`); + //console.warn(`[StatusMonitor] 为会话 ${sessionId} 启动状态轮询,间隔 ${interval}ms`); this.fetchAndSendServerStatus(sessionId); // 立即执行一次 state.statusIntervalId = setInterval(() => { this.fetchAndSendServerStatus(sessionId); @@ -71,7 +71,7 @@ export class StatusMonitorService { stopStatusPolling(sessionId: string): void { const state = this.clientStates.get(sessionId); if (state?.statusIntervalId) { - console.log(`[StatusMonitor] 停止会话 ${sessionId} 的状态轮询。`); + //console.warn(`[StatusMonitor] 停止会话 ${sessionId} 的状态轮询。`); clearInterval(state.statusIntervalId); state.statusIntervalId = undefined; previousNetStats.delete(sessionId); // 清理网络统计缓存 @@ -85,7 +85,7 @@ export class StatusMonitorService { private async fetchAndSendServerStatus(sessionId: string): Promise { const state = this.clientStates.get(sessionId); if (!state || !state.sshClient || state.ws.readyState !== WebSocket.OPEN) { - console.warn(`[StatusMonitor] 无法获取会话 ${sessionId} 的状态,停止轮询。原因:状态无效、SSH断开或WS关闭。`); + //console.warn(`[StatusMonitor] 无法获取会话 ${sessionId} 的状态,停止轮询。原因:状态无效、SSH断开或WS关闭。`); this.stopStatusPolling(sessionId); return; } @@ -94,7 +94,7 @@ export class StatusMonitorService { const status = await this.fetchServerStatus(state.sshClient, sessionId); state.ws.send(JSON.stringify({ type: 'status_update', payload: { connectionId: state.dbConnectionId, status } })); } catch (error: any) { - console.error(`[StatusMonitor] 获取会话 ${sessionId} 服务器状态失败:`, error); + //console.warn(`[StatusMonitor] 获取会话 ${sessionId} 服务器状态失败:`, error); state.ws.send(JSON.stringify({ type: 'status_error', payload: { connectionId: state.dbConnectionId, message: `获取状态失败: ${error.message}` } })); } } @@ -308,13 +308,13 @@ export class StatusMonitorService { stream.on('close', (code: number, signal?: string) => { // Don't reject on non-zero exit code, as some commands might return non-zero normally // if (code !== 0) { - // console.warn(`[StatusMonitor] Command '${command}' exited with code ${code}`); + // //console.warn(`[StatusMonitor] Command '${command}' exited with code ${code}`); // } resolve(output.trim()); }).on('data', (data: Buffer) => { output += data.toString('utf8'); }).stderr.on('data', (data: Buffer) => { - console.warn(`[StatusMonitor] Command '${command}' stderr: ${data.toString('utf8').trim()}`); + //console.warn(`[StatusMonitor] Command '${command}' stderr: ${data.toString('utf8').trim()}`); }); }); }); diff --git a/packages/frontend/src/components/FileManager.vue b/packages/frontend/src/components/FileManager.vue index 6426c1d..13b832d 100644 --- a/packages/frontend/src/components/FileManager.vue +++ b/packages/frontend/src/components/FileManager.vue @@ -1,62 +1,85 @@ @@ -79,18 +79,19 @@ interface ServerStatus { osName?: string; // 操作系统名称 } -// Props 用于接收父组件传递的状态数据和错误信息 +// 更新 Props 定义 const props = defineProps<{ - statusData: ServerStatus | null; - error?: string | null; + sessionId: string; // 添加会话 ID + serverStatus: ServerStatus | null; // 更改名称从 statusData 到 serverStatus + statusError?: string | null; // 更改名称从 error 到 statusError }>(); // --- 缓存状态 --- const cachedCpuModel = ref(null); const cachedOsName = ref(null); -// 监听传入的 statusData 变化以更新缓存 -watch(() => props.statusData, (newData) => { +// 监听传入的 serverStatus 变化以更新缓存 (更新引用) +watch(() => props.serverStatus, (newData) => { if (newData) { // 仅当新数据有效时更新缓存 if (newData.cpuModel !== undefined && newData.cpuModel !== null && newData.cpuModel !== '') { @@ -103,15 +104,15 @@ watch(() => props.statusData, (newData) => { // 如果 newData 为 null (例如断开连接),不清除缓存 }, { immediate: true }); // 立即执行一次以初始化缓存 -// --- 显示计算属性 (包含缓存逻辑) --- +// --- 显示计算属性 (包含缓存逻辑) - 更新引用 --- const displayCpuModel = computed(() => { // 优先使用当前有效数据,否则回退到缓存,最后是 'N/A' - return (props.statusData?.cpuModel ?? cachedCpuModel.value) || 'N/A'; + return (props.serverStatus?.cpuModel ?? cachedCpuModel.value) || 'N/A'; }); const displayOsName = computed(() => { // 优先使用当前有效数据,否则回退到缓存,最后是 'N/A' - return (props.statusData?.osName ?? cachedOsName.value) || 'N/A'; + return (props.serverStatus?.osName ?? cachedOsName.value) || 'N/A'; }); @@ -133,9 +134,9 @@ const formatKbToGb = (kb?: number): string => { return `${gb.toFixed(1)} GB`; }; -// 计算属性用于显示内存信息 +// 计算属性用于显示内存信息 (更新引用) const memDisplay = computed(() => { - const data = props.statusData; + const data = props.serverStatus; if (!data || data.memUsed === undefined || data.memTotal === undefined) return 'N/A'; // 检查数据有效性 const percent = data.memPercent !== undefined ? `(${data.memPercent.toFixed(1)}%)` : ''; // 确保 MB 值在是整数时不显示小数 @@ -144,9 +145,9 @@ const memDisplay = computed(() => { return `${usedMb} MB / ${totalMb} MB ${percent}`; }); -// 计算属性用于显示磁盘信息 +// 计算属性用于显示磁盘信息 (更新引用) const diskDisplay = computed(() => { - const data = props.statusData; + const data = props.serverStatus; if (!data || data.diskUsed === undefined || data.diskTotal === undefined) return 'N/A'; // 检查数据有效性 // 百分比代表已用空间 const percent = data.diskPercent !== undefined ? `(${data.diskPercent.toFixed(1)}%)` : ''; @@ -154,9 +155,9 @@ const diskDisplay = computed(() => { return `${formatKbToGb(data.diskUsed)} / ${formatKbToGb(data.diskTotal)} ${percent}`; }); -// 计算属性用于显示 Swap 信息 +// 计算属性用于显示 Swap 信息 (更新引用) const swapDisplay = computed(() => { - const data = props.statusData; + const data = props.serverStatus; // 处理 swap 可能为 undefined 或 0 的情况 const used = data?.swapUsed ?? 0; const total = data?.swapTotal ?? 0; diff --git a/packages/frontend/src/components/Terminal.vue b/packages/frontend/src/components/Terminal.vue index 61ebc39..44a0613 100644 --- a/packages/frontend/src/components/Terminal.vue +++ b/packages/frontend/src/components/Terminal.vue @@ -7,6 +7,7 @@ import 'xterm/css/xterm.css'; // 引入 xterm 样式 // 定义 props 和 emits const props = defineProps<{ + sessionId: string; // 会话 ID stream?: ReadableStream; // 用于接收来自 WebSocket 的数据流 (可选) options?: object; // xterm 的配置选项 }>(); diff --git a/packages/frontend/src/components/TerminalTabBar.vue b/packages/frontend/src/components/TerminalTabBar.vue new file mode 100644 index 0000000..3929308 --- /dev/null +++ b/packages/frontend/src/components/TerminalTabBar.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/packages/frontend/src/components/WorkspaceConnectionList.vue b/packages/frontend/src/components/WorkspaceConnectionList.vue index 66b54c4..255ff8a 100644 --- a/packages/frontend/src/components/WorkspaceConnectionList.vue +++ b/packages/frontend/src/components/WorkspaceConnectionList.vue @@ -7,7 +7,12 @@ import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store import { useTagsStore, TagInfo } from '../stores/tags.store'; // 定义事件 -const emit = defineEmits(['connect-request', 'request-add-connection', 'request-edit-connection']); // 添加新事件 +const emit = defineEmits([ + 'connect-request', // 左键单击 - 请求激活或替换当前标签 + 'open-new-session', // 中键单击 - 请求在新标签中打开 + 'request-add-connection', // 右键菜单 - 添加 + 'request-edit-connection' // 右键菜单 - 编辑 +]); const { t } = useI18n(); // const router = useRouter(); // 不再需要 @@ -127,6 +132,12 @@ onMounted(() => { connectionsStore.fetchConnections(); tagsStore.fetchTags(); }); + +// 处理中键点击(在新标签页打开) +const handleOpenInNewTab = (connectionId: number) => { + emit('open-new-session', connectionId); + closeContextMenu(); // 如果右键菜单是打开的,也关闭它 +};