From d3e8d598b8b5b438f61b062f4c6887129bc7efd5 Mon Sep 17 00:00:00 2001 From: yinjianm Date: Sun, 29 Mar 2026 23:01:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E9=87=8D=E6=9E=84=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E5=8C=BA=E7=BB=88=E7=AB=AF=E4=B8=8E=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 SSH 顶部标签改为服务器级切换入口,并把同服务器下的 多终端切换、新增与关闭下沉到终端面板内部,修正服务器与 终端的视觉层级 同时将 Workbench 导航改为左侧图标栏,并为终端标签右键菜单 补充“关闭全部”动作,完善相关多语言文案与工作区事件处理 --- .helloagents/CHANGELOG.md | 4 +- .../.status.json | 1 + .../proposal.md | 98 +++++++++ .../tasks.md | 54 +++++ .../.status.json | 1 + .../proposal.md | 116 ++++++++++ .../tasks.md | 50 +++++ .../proposal.md | 72 +++++++ .../tasks.md | 42 ++++ .helloagents/archive/_index.md | 4 + .helloagents/modules/frontend.md | 2 +- packages/frontend/src/App.vue | 2 +- .../src/components/LayoutRenderer.vue | 105 ++++++++- .../src/components/TerminalTabBar.vue | 199 ++++++++++-------- .../src/components/WorkspaceWorkbench.vue | 142 +++++++------ .../src/composables/workspaceEvents.ts | 3 +- packages/frontend/src/locales/en-US.json | 4 +- packages/frontend/src/locales/ja-JP.json | 13 ++ packages/frontend/src/locales/zh-CN.json | 4 +- packages/frontend/src/views/WorkspaceView.vue | 10 + 20 files changed, 762 insertions(+), 164 deletions(-) create mode 100644 .helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/.status.json create mode 100644 .helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/proposal.md create mode 100644 .helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/tasks.md create mode 100644 .helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/.status.json create mode 100644 .helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/proposal.md create mode 100644 .helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/tasks.md create mode 100644 .helloagents/archive/2026-03/202603292300_terminal-tab-close-all/proposal.md create mode 100644 .helloagents/archive/2026-03/202603292300_terminal-tab-close-all/tasks.md diff --git a/.helloagents/CHANGELOG.md b/.helloagents/CHANGELOG.md index 6592d23..2f9fd8a 100644 --- a/.helloagents/CHANGELOG.md +++ b/.helloagents/CHANGELOG.md @@ -8,7 +8,9 @@ - 2026-03-25:继续微调 `/workspace` Workbench,新增默认“快捷指令”标签、调整三栏宽度到更接近 xterminal 参考图,并修复终端区域鼠标悬停时指针异常消失的问题。 ### 修复 -- **[frontend]**: 修复文件管理器右键菜单的回归关闭竞态,避免“终端 / 上传 / 压缩”子菜单在展开或点击前被捕获阶段监听提前关闭 — by yinjianm +- **[frontend]**: 将 `/workspace` 的 SSH 多终端展示从顶部组头胶囊改为“顶部只切服务器、终端面板内部切换同服务器多个终端”,修正服务器与终端的视觉层级 - by yinjianm + - 方案: [202603292139_terminal-server-internal-tabs](archive/2026-03/202603292139_terminal-server-internal-tabs/) +- **[frontend]**: 修复文件管理器右键菜单的回归关闭竞态,避免“终端 / 上传 / 压缩”子菜单在展开或点击前被捕获阶段监听提前关闭 - by yinjianm - 方案: [202603260527_file-manager-context-submenu-regression](archive/2026-03/202603260527_file-manager-context-submenu-regression/) - **[frontend]**: 修复文件管理器右键子菜单点击无反应、拖拽上传目标不明确,以及目录删除后持续报 `No such file` 的稳定性问题 — by yinjianm - 方案: [202603260324_file-manager-delete-upload-stability](archive/2026-03/202603260324_file-manager-delete-upload-stability/) diff --git a/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/.status.json b/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/.status.json new file mode 100644 index 0000000..37b95fc --- /dev/null +++ b/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/.status.json @@ -0,0 +1 @@ +{"status":"completed","completed":4,"failed":0,"pending":0,"total":4,"done":4,"percent":100,"current":"前端改造与构建验证已完成,待归档","updated_at":"2026-03-29 22:15:00"} diff --git a/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/proposal.md b/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/proposal.md new file mode 100644 index 0000000..3f6c344 --- /dev/null +++ b/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/proposal.md @@ -0,0 +1,98 @@ +# 变更提案: terminal-server-internal-tabs + +## 元信息 +```yaml +类型: 优化 +方案类型: implementation +优先级: P1 +状态: 进行中 +创建: 2026-03-29 +``` + +--- + +## 1. 需求 + +### 背景 +当前工作区顶部的 `TerminalTabBar` 同时承担“服务器切换”和“同服务器多终端切换”两类职责,导致展示层把一个服务器组整体渲染成顶栏胶囊。实际交互上,同一 SSH 服务器已经支持打开多个终端,但视觉层级仍然是“全局会话标签”,与用户参考图中“先切到某台服务器,再在该服务器页面内部切换多个终端”的模型不一致。 + +### 目标 +- 顶部标签栏降级为服务器级切换入口,不再直接承载该服务器下的终端子标签。 +- 当前激活服务器的终端切换、关闭和新增入口下沉到终端内容区内部。 +- 保持现有 `sessionId`、WebSocket、SSH session 的一对一模型不变,仅调整展示和切换逻辑。 +- 维持现有工作区布局、RDP/VNC 行为以及其他面板对 `activeSessionId` 的兼容。 + +### 约束条件 +```yaml +范围约束: 仅修改 frontend 展示层与少量会话派生逻辑,不改 backend 协议 +兼容性约束: RDP/VNC 会话继续沿用现有顶部会话切换方式 +实现约束: 复用已有 session store、terminalIndex 与 activeSessionId,不引入新的全局状态库 +布局约束: 桌面端和移动端都需保持可用,避免破坏现有 LayoutRenderer 结构 +``` + +### 验收标准 +- [ ] 顶部标签栏对 SSH 连接只展示服务器级入口,不再展示“服务器头 + 终端 1/终端 2”的组合胶囊。 +- [ ] 在某个 SSH 服务器对应的终端面板内部可以查看、切换、新增并关闭该服务器下的多个终端。 +- [ ] 当前活动服务器切换后,终端面板内部标签同步切换到该服务器的活动终端。 +- [ ] `packages/frontend` 可完成至少一次构建或类型级验证,且没有新增的模板/属性错误。 + +--- + +## 2. 方案 + +### 技术方案 +保留现有基于 `activeSessionId` 的主工作区数据流,不重构 session store。将 `TerminalTabBar.vue` 的 SSH 展示重心调整为“按连接聚合的服务器标签”,点击服务器时激活该连接当前活动终端或最后一个终端;同时在终端面板区域新增一个轻量的内部终端切换条,只展示当前活动服务器对应的 SSH 子终端,并在该区域提供新增/关闭能力。`LayoutRenderer.vue` 继续负责所有终端实例的 `keep-alive` 渲染,但在终端背景层上方新增当前服务器内部导航。 + +### 影响范围 +```yaml +涉及模块: + - frontend: TerminalTabBar 顶部服务器切换逻辑调整 + - frontend: LayoutRenderer 终端面板内部导航与当前服务器终端切换 + - frontend: i18n 文案补充 +预计变更文件: 3-5 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 顶部栏从会话级改成服务器级后,现有拖拽排序语义可能变弱 | 中 | SSH 仅保留服务器级聚合展示;非 SSH 仍沿用原会话级逻辑 | +| 终端内容区内切换条如果直接耦合 Terminal 实例,可能影响现有 `keep-alive` 行为 | 中 | 保持实例渲染仍在 `LayoutRenderer`,内部切换条只驱动 `activeSessionId` | +| 其他依赖 `activeSessionId` 的面板可能在服务器切换时出现会话选择不一致 | 低 | 继续以活动终端作为全局活动会话,不新增第二套“活动服务器”状态 | + +--- + +## 3. 技术决策 + +### terminal-server-internal-tabs#D001: 以“活动服务器 + 活动终端”双层视图重组展示,而不重构底层会话模型 +**日期**: 2026-03-29 +**状态**: 采纳 +**背景**: 现有问题是 UI 归属错误,不是多终端能力缺失;底层已经支持同连接多会话。 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 仅继续美化顶部分组标签 | 改动最小 | 仍然违背“终端属于服务器页面内部”的交互模型 | +| B: 新增活动服务器层,顶部只切服务器,终端面板内部切换子终端 | 贴合目标,复用现有 session 模型,回滚边界清晰 | 需要同时改顶部栏和终端面板两处展示 | +| C: 重构为嵌套 session/group store | 结构概念更完整 | 改动过大,超出这次局部 UI 纠偏需求 | +**决策**: 选择方案 B。 +**理由**: 用户要求的是展示逻辑回到“服务器页面内部开多个终端”,方案 B 可以在不碰后端和不重写 store 的前提下完成目标。 +**影响**: 主要影响 `TerminalTabBar.vue`、`LayoutRenderer.vue` 以及相关文案。 + +--- + +## 4. 成果设计 + +### 设计方向 +- **美学基调**: 延续当前深色运维工作台风格,把顶部服务器入口做得更克制,把终端面板内部导航做成更贴近“本机多标签终端”的工具感界面。 +- **记忆点**: 服务器是外层入口,终端是内层工作标签,两层层级明确分离。 +- **参考**: 用户提供的终端参考图,核心不是照搬皮肤,而是复用“服务器内多终端标签”的交互结构。 + +### 视觉要素 +- **配色**: 保留当前暗色背景和绿色活跃态,但把高亮集中在当前服务器与当前终端两个层级,避免整条胶囊都发光。 +- **字体**: 沿用项目现有字体体系,不做额外字体扩展。 +- **布局**: 顶部横条显示服务器级入口;终端面板顶部增加内嵌次级标签条,紧贴终端区域。 +- **动效**: 保持现有 hover/active 过渡即可,不新增重动画。 +- **氛围**: 强化“终端工作台”而不是“胶囊式全局标签”的层次感。 + +### 技术约束 +- **可访问性**: 内部标签与新增按钮保留 `title`/状态色提示。 +- **响应式**: 移动端不强行塞入复杂双层标签,优先保持能切换和新增。 diff --git a/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/tasks.md b/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/tasks.md new file mode 100644 index 0000000..f3d80c5 --- /dev/null +++ b/.helloagents/archive/2026-03/202603292139_terminal-server-internal-tabs/tasks.md @@ -0,0 +1,54 @@ +# 任务清单: terminal-server-internal-tabs + +> **@status:** completed | 2026-03-29 22:59 + +```yaml +@feature: terminal-server-internal-tabs +@created: 2026-03-29 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 4 | 0 | 0 | 4 | + +--- + +## 任务列表 + +### 1. 方案与现状确认 + +- [√] 1.1 复核 `TerminalTabBar.vue`、`LayoutRenderer.vue` 与 `sessionActions.ts` 的现状,锁定“顶部服务器切换 + 面板内终端切换”的最小实现范围 | depends_on: [] + +### 2. 顶部服务器级切换改造 + +- [√] 2.1 在 `packages/frontend/src/components/TerminalTabBar.vue` 中将 SSH 顶部标签改为服务器级入口,并保留非 SSH 会话的现有行为 | depends_on: [1.1] + +### 3. 终端面板内切换改造 + +- [√] 3.1 在 `packages/frontend/src/components/LayoutRenderer.vue` 中新增当前服务器内部终端切换条,支持切换/新增/关闭该服务器下的终端 | depends_on: [2.1] +- [√] 3.2 补充 `packages/frontend/src/locales/zh-CN.json` 与 `packages/frontend/src/locales/en-US.json` 的相关文案 | depends_on: [3.1] + +### 4. 验证与同步 + +- [√] 4.1 运行前端构建并同步知识库变更说明 | depends_on: [3.2] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-03-29 21:39 | 方案包创建 | 完成 | 已创建 `202603292139_terminal-server-internal-tabs` 并确认本轮按 R2 执行 | +| 2026-03-29 21:54 | 2.1 / 3.1 / 3.2 | 完成 | 顶部 SSH 标签改为服务器级入口,终端面板内新增当前服务器终端切换条与新增按钮 | +| 2026-03-29 22:15 | 4.1 | 完成 | `npm --prefix packages/frontend run build` 通过;仅保留既有 Vite chunk size / dynamic import 提示 | + +--- + +## 执行备注 + +- 本轮只调整 SSH 多终端的展示归属,不改变后端协议、不扩展到 RDP/VNC 会话模型。 +- 顶部拖拽在存在 SSH 聚合展示时已临时禁用,避免“可见服务器项”与“底层会话项”不一致造成错误拖拽。 diff --git a/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/.status.json b/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/.status.json new file mode 100644 index 0000000..f1e0830 --- /dev/null +++ b/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/.status.json @@ -0,0 +1 @@ +{"status":"completed","completed":4,"failed":0,"pending":0,"total":4,"done":4,"percent":100,"current":"Completed - workbench left icon rail implemented and verified","updated_at":"2026-03-29 22:53:00"} diff --git a/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/proposal.md b/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/proposal.md new file mode 100644 index 0000000..ce66ca7 --- /dev/null +++ b/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/proposal.md @@ -0,0 +1,116 @@ +# 变更提案: workbench-left-icon-rail + +## 元信息 +```yaml +类型: 优化 +方案类型: implementation +优先级: P1 +状态: 已完成 +创建: 2026-03-29 +``` + +--- + +## 1. 需求 + +### 背景 +当前 `WorkspaceWorkbench.vue` 将“快捷指令 / 文件 / 历史命令 / 编辑器”渲染为顶部 2x2 文本按钮组。用户希望参考文件管理器截图中的窄侧边栏交互,将这组入口移动到容器最左侧,并改为小图标自上而下竖排展示,以更接近桌面资源管理器的导航感。 + +### 目标 +在不改变现有四个工作区面板切换逻辑的前提下: +- 把 Workbench 导航从顶部网格按钮改为左侧纵向 icon rail。 +- 默认仅显示图标,使用 tooltip 暴露标签文本。 +- 保留当前激活态高亮,让用户能清晰识别当前面板。 +- 保持右侧内容区和文件管理、历史命令、编辑器等现有功能链路不变。 + +### 约束条件 +```yaml +时间约束: 本轮只做现有布局重排和样式优化,不扩展新的功能入口。 +性能约束: 不引入额外依赖,不增加面板切换时的运行时开销。 +兼容性约束: 保持现有四个 tab 的切换逻辑与现有国际化文案兼容。 +业务约束: 文件、历史命令、编辑器、快捷指令的功能实现保持原样,仅调整导航表现。 +``` + +### 验收标准 +- [ ] Workbench 左侧出现固定窄栏,四个入口图标从上到下竖排展示。 +- [ ] 未激活项仅显示图标,悬停可通过 `title` 查看名称;激活态可被明显识别。 +- [ ] 右侧 header 和内容面板仍能正常切换,文件管理和编辑器上下文不回归。 +- [ ] `npm run build --workspace=@nexus-terminal/frontend` 通过。 + +--- + +## 2. 方案 + +### 技术方案 +仅修改 `packages/frontend/src/components/WorkspaceWorkbench.vue`: +- 将当前顶部导航按钮区从 header 中拆出,改为与内容区并列的左侧 rail 容器。 +- 每个 tab 按钮改成固定宽高的图标按钮,使用 `:title="tab.label"` 提供 tooltip。 +- 右侧保留现有 title / session 信息区域与内容切换区域,避免影响内部组件树。 +- 通过 scoped CSS 增加 rail 的背景、分隔线、激活态和 hover 态,形成更接近参考图的资源管理器式侧栏。 + +### 影响范围 +```yaml +涉及模块: + - frontend: 工作区 Workbench 导航布局和视觉样式调整 +预计变更文件: 1 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 左侧 rail 挤压内容区,导致窄屏显示不佳 | 中 | 控制 rail 宽度并保留右侧内容 `min-w-0`,以现有容器自适应布局为主 | +| 导航区 DOM 重排影响现有 header 层级或滚动区域 | 中 | 只调整 `WorkspaceWorkbench.vue` 外层结构,不触碰四个面板组件内部实现 | +| 样式修改导致激活态辨识不足 | 低 | 保留主色高亮、边框和背景差异,且 tooltip 始终可用 | + +--- + +## 3. 技术设计(可选) + +本次不涉及 API、数据模型或架构层改动,保持 N/A。 + +--- + +## 4. 核心场景 + +### 场景: 工作区左侧图标导航 +**模块**: frontend +**条件**: 用户进入 `/workspace`,Workbench 区域已渲染。 +**行为**: 用户通过左侧竖排图标点击切换快捷指令、文件、历史命令和编辑器。 +**结果**: 右侧内容区域切换到对应面板,左侧当前图标高亮,未激活项保持极简图标外观。 + +--- + +## 5. 技术决策 + +### workbench-left-icon-rail#D001: Workbench 导航改为极简左侧图标栏 +**日期**: 2026-03-29 +**状态**: 已采纳 +**背景**: 用户明确要求参考文件管理器样式,把 Workbench 入口放到容器最左边,并选择“仅显示小图标,文字全部隐藏,仅用 tooltip 显示名称”。 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 左侧极简图标栏 | 最贴近参考图,占用空间小,视觉上更像桌面导航 | 需要通过 tooltip 补足文字语义 | +| B: 左侧图标加短文字 | 可读性更高 | 占宽更大,和参考图差异更明显 | +**决策**: 选择方案 A +**理由**: 该方案完全符合用户确认的展示方式,同时对现有组件逻辑影响最小,只需调整导航容器和按钮样式即可。 +**影响**: 影响 `WorkspaceWorkbench.vue` 的布局结构和样式,不改变四个子面板组件的实现。 + +--- + +## 6. 成果设计 + +### 设计方向 +- **美学基调**: 工业化桌面工具栏,强调“窄、稳、冷静”的资源管理器式侧边导航,而不是卡片化 dashboard 按钮区。 +- **记忆点**: Workbench 最左侧的细窄图标栏与右侧内容区形成明确分栏,四个入口像原生桌面工具栏一样垂直排列。 +- **参考**: 用户提供的文件管理器截图;顶部文字按钮组向左侧 icon rail 收敛。 + +### 视觉要素 +- **配色**: 继承现有 `bg-header`、`border-border`、`bg-background` 与 `primary` 主题变量,激活态用主色背景和浅阴影做聚焦。 +- **字体**: 导航主交互不直接展示文字,tooltip 继续使用现有项目字体体系,无新增字体依赖。 +- **布局**: 左侧固定窄栏,顶部到下方竖排排列四个图标按钮;右侧为 header + 内容面板两层结构。 +- **动效**: 保留当前按钮 hover / active 的颜色过渡,避免大幅动画影响工具型界面的稳定感。 +- **氛围**: 通过左侧窄栏边界和轻微层次阴影,强化“桌面工作区工具箱”观感。 + +### 技术约束 +- **可访问性**: 每个图标按钮保留 `title` 文本,确保隐藏标签后仍可识别;激活态需要有清晰视觉差异。 +- **响应式**: rail 采用固定窄宽度,右侧内容保持 `flex-1 min-w-0`,避免内容区域被挤爆。 diff --git a/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/tasks.md b/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/tasks.md new file mode 100644 index 0000000..e079909 --- /dev/null +++ b/.helloagents/archive/2026-03/202603292247_workbench-left-icon-rail/tasks.md @@ -0,0 +1,50 @@ +# 任务清单: workbench-left-icon-rail + +> **@status:** completed | 2026-03-29 22:55 + +```yaml +@feature: workbench-left-icon-rail +@created: 2026-03-29 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 4 | 0 | 0 | 4 | + +--- + +## 任务列表 + +### 1. 方案设计 + +- [√] 1.1 完成 Workbench 左侧 icon rail 的布局与交互方案整理 | depends_on: [] +- [√] 1.2 补全方案包 `proposal.md` 与 `tasks.md`,并通过包结构校验 | depends_on: [1.1] + +### 2. 开发实施 + +- [√] 2.1 在 `packages/frontend/src/components/WorkspaceWorkbench.vue` 中将顶部 tab 按钮组改为左侧竖排图标栏 | depends_on: [1.2] +- [√] 2.2 运行前端构建验证布局改动未破坏类型检查和打包 | depends_on: [2.1] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-03-29 22:47 | 1.1 | completed | 已确认采用“仅显示小图标 + tooltip”的左侧竖排导航方案 | +| 2026-03-29 22:49 | 1.2 | completed | 已补全 proposal/tasks 并通过 validate_package.py 校验 | +| 2026-03-29 22:52 | 2.1 | completed | 已将顶部 tab 网格改为 Workbench 左侧竖排 icon rail | +| 2026-03-29 22:53 | 2.2 | completed | `npm run build --workspace=@nexus-terminal/frontend` 通过,仅保留既有 chunk warning | + +--- + +## 执行备注 + +> 记录执行过程中的重要说明、决策变化、风险提示等 + +- 本次改动目标是最小化重排,仅调整 Workbench 导航容器,不扩散到文件管理、历史命令和编辑器子组件内部。 +- 构建验证通过,Vite 仍输出仓库既有的动态导入与大 chunk 警告,但未阻断本次交付。 diff --git a/.helloagents/archive/2026-03/202603292300_terminal-tab-close-all/proposal.md b/.helloagents/archive/2026-03/202603292300_terminal-tab-close-all/proposal.md new file mode 100644 index 0000000..d1d6f89 --- /dev/null +++ b/.helloagents/archive/2026-03/202603292300_terminal-tab-close-all/proposal.md @@ -0,0 +1,72 @@ +# 变更提案: terminal-tab-close-all + +## 元信息 +```yaml +类型: 修复 +方案类型: implementation +优先级: P2 +状态: 已完成 +创建: 2026-03-29 +``` + +--- + +## 1. 需求 +### 背景 +当前终端标签右键菜单已经支持关闭当前、关闭其他、关闭左侧和关闭右侧,但缺少“一次性关闭全部终端标签”的入口。用户希望在工作区内直接关闭所有服务器的终端标签,而不是仅针对当前服务器分组操作。 + +### 目标 +- 在终端标签右键菜单中新增“关闭全部”。 +- 点击后关闭当前工作区内全部终端标签。 +- 保持现有右键菜单事件链路与会话清理逻辑,不引入新的后端依赖。 + +### 约束条件 +```yaml +范围约束: 仅修改 frontend 菜单、工作区事件和本地化文案 +行为约束: “关闭全部”作用域为当前工作区全部终端标签,不区分服务器 +实现约束: 复用现有 session store 的 cleanupAllSessions 和 Workspace 事件总线 +``` + +### 验收标准 +- [x] 终端标签右键菜单在多标签场景下显示“关闭全部”。 +- [x] 点击“关闭全部”后,当前工作区中的全部终端标签被关闭。 +- [x] `packages/frontend` 构建通过,未引入新的类型错误。 + +--- + +## 2. 方案 + +### 技术方案 +沿用现有 `TerminalTabBar.vue -> workspaceEvents.ts -> WorkspaceView.vue -> session.store.ts` 的前端事件链,新增 `session:closeAll` 事件。`TerminalTabBar.vue` 负责在多标签场景下渲染菜单项并发出事件,`WorkspaceView.vue` 统一接收后调用 `sessionStore.cleanupAllSessions()` 完成全部会话关闭,文案同步更新到中英文 locale 文件。 + +### 影响范围 +```yaml +涉及模块: + - frontend: TerminalTabBar 右键菜单项 + - frontend: workspaceEvents 工作区事件类型 + - frontend: WorkspaceView 全部关闭处理 + - frontend: zh-CN / en-US 本地化文案 +预计变更文件: 5 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 关闭全部复用 `cleanupAllSessions()` 时影响现有退出清理逻辑 | 低 | 仅在用户主动点击菜单时调用,且该逻辑已用于工作区卸载清理 | +| 右键菜单新增项后多语言缺失导致展示 key | 低 | 同步补充 `zh-CN` 与 `en-US` 文案 | + +--- + +## 3. 技术决策 +### terminal-tab-close-all#D001: 复用工作区级事件总线和现有全部清理能力 +**日期**: 2026-03-29 +**状态**: 采纳 +**背景**: 当前终端标签关闭动作已全部走 `WorkspaceView` 中转处理,`session.store` 已存在 `cleanupAllSessions()` 可安全关闭全部会话。 +**备选方案**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 在 `TerminalTabBar.vue` 内循环逐个关闭 | 改动直观 | 组件直接操作关闭细节,事件层次不一致 | +| B: 新增 `session:closeAll` 事件并由 `WorkspaceView` 调用 `cleanupAllSessions()` | 与现有 close/closeOthers 链路一致,复用现有清理逻辑 | 需要补一个事件类型 | +**决策**: 选择方案 B。 +**理由**: 保持终端标签菜单与工作区事件模型一致,避免在组件层直接堆叠会话清理逻辑。 + diff --git a/.helloagents/archive/2026-03/202603292300_terminal-tab-close-all/tasks.md b/.helloagents/archive/2026-03/202603292300_terminal-tab-close-all/tasks.md new file mode 100644 index 0000000..8929955 --- /dev/null +++ b/.helloagents/archive/2026-03/202603292300_terminal-tab-close-all/tasks.md @@ -0,0 +1,42 @@ +# 任务清单: terminal-tab-close-all + +```yaml +@feature: terminal-tab-close-all +@created: 2026-03-29 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 4 | 0 | 0 | 4 | + +--- + +## 任务列表 + +### 1. 现状确认 +- [x] 1.1 复核 `TerminalTabBar.vue`、`workspaceEvents.ts` 与 `WorkspaceView.vue` 的现有关闭链路,确认新增菜单项可复用工作区事件处理 | depends_on: [] + +### 2. 菜单与事件扩展 +- [x] 2.1 在 `packages/frontend/src/components/TerminalTabBar.vue` 中新增“关闭全部”菜单项和 `close-all` 动作 | depends_on: [1.1] +- [x] 2.2 在 `packages/frontend/src/composables/workspaceEvents.ts` 中补充 `session:closeAll` 事件类型,并在 `packages/frontend/src/views/WorkspaceView.vue` 中接入全部关闭处理 | depends_on: [2.1] + +### 3. 文案与验证 +- [x] 3.1 更新 `packages/frontend/src/locales/zh-CN.json` 与 `packages/frontend/src/locales/en-US.json` 文案 | depends_on: [2.2] +- [x] 3.2 运行 `npm --prefix packages/frontend run build` 验证改动 | depends_on: [3.1] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-03-29 | 1.1 | 完成 | 确认终端关闭动作统一经 Workspace 事件总线流转 | +| 2026-03-29 | 2.1 | 完成 | 新增 `close-all` 菜单动作 | +| 2026-03-29 | 2.2 | 完成 | 新增 `session:closeAll` 事件并接入 `cleanupAllSessions()` | +| 2026-03-29 | 3.1 | 完成 | 补充中英文菜单文案 | +| 2026-03-29 | 3.2 | 完成 | 前端构建通过,仅保留既有 chunk warning | + diff --git a/.helloagents/archive/_index.md b/.helloagents/archive/_index.md index 4427c36..292d372 100644 --- a/.helloagents/archive/_index.md +++ b/.helloagents/archive/_index.md @@ -7,6 +7,9 @@ | 时间戳 | 名称 | 类型 | 涉及模块 | 决策 | 结果 | |--------|------|------|---------|------|------| +| 202603292139 | terminal-server-internal-tabs | - | - | - | ✅完成 | +| 202603292247 | workbench-left-icon-rail | - | - | - | ✅完成 | +| 202603292300 | terminal-tab-close-all | implementation | frontend | terminal-tab-close-all#D001 | ✅完成 | | 202603260527 | file-manager-context-submenu-regression | implementation | frontend | file-manager-context-submenu-regression#D001 | ✅完成 | | 202603260324 | file-manager-delete-upload-stability | implementation | frontend, backend | file-manager-delete-upload-stability#D001 | ✅完成 | | 202603260310 | file-manager-root-sibling-bootstrap | implementation | frontend | file-manager-root-sibling-bootstrap#D001 | ✅完成 | @@ -40,6 +43,7 @@ ## 按月归档 ### 2026-03 +- [202603292300_terminal-tab-close-all](./2026-03/202603292300_terminal-tab-close-all/) - 为终端标签右键菜单补充“关闭全部”,并复用现有工作区会话清理链路 - [202603260527_file-manager-context-submenu-regression](./2026-03/202603260527_file-manager-context-submenu-regression/) - 修复文件管理器右键菜单回归关闭竞态,恢复终端、上传、压缩等子菜单展开与点击 - [202603260324_file-manager-delete-upload-stability](./2026-03/202603260324_file-manager-delete-upload-stability/) - 修复文件管理器右键子菜单点击、拖拽上传目标确认、目录删除模式选择与删除后路径失效回退 - [202603260234_folder-upload-auto-zip](./2026-03/202603260234_folder-upload-auto-zip/) - 为文件管理器补齐上传文件夹入口,选择目录后先打包为 zip,再上传并自动触发远端解压 diff --git a/.helloagents/modules/frontend.md b/.helloagents/modules/frontend.md index 9ee1abb..c533674 100644 --- a/.helloagents/modules/frontend.md +++ b/.helloagents/modules/frontend.md @@ -36,7 +36,7 @@ ### 工作区交互 **条件**: 用户进入 `/workspace` 或相关管理页面。 -**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。`CommandInputBar.vue` 当前已将底部命令框升级为支持会话级草稿保留的多行 `textarea`:普通 `Enter` 插入换行,`Ctrl+Shift+Enter` 发送当前命令,输入框会按内容自动增高至约 6 行,超出后在输入框内部滚动,并继续兼容快捷指令/命令历史同步与选中发送逻辑。快捷指令相关能力目前由 `AddEditQuickCommandForm.vue`、`QuickCommandsView.vue` 与新增的 `utils/quickCommandTemplate.ts` 协同实现:编辑弹窗左侧既可维护自定义 `${变量名}`,也提供 `${{date}}`、`${{time}}`、`${{timestamp}}`、`${{week}}`、`${{uuid}}`、`${{random:8}}`、`${{clipboard}}`、`${{password}}` 等动态变量的一键插入;实际执行时会统一走共享解析器,覆盖编辑弹窗执行、列表直接执行、粘贴到命令输入框和发送到全部服务器等链路,并对未定义变量、无法读取的剪贴板或不可用密码给出非阻断告警。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button`、`text-button-text`、`hover:bg-button-hover`、`hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;该视图当前还支持命令项右键菜单,并已修正为实底卡片式上下文菜单,提供立即执行、粘贴到命令输入框(不自动发送)、复制命令、发送到全部服务器、编辑和删除等动作。`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复,并在渲染层为带 `xterm-fg-*` class 或内联 `style.color` 的显式前景色字符打标记,让终端文字描边/阴影仅作用于默认前景文本,不覆盖 ANSI 彩色输出;`session.store` 当前会为同一 SSH 连接下的新终端分配递增的 `terminalIndex`,`TerminalTabBar.vue` 则进一步把连续同连接会话渲染成“服务器组头 + 终端子标签 + 组尾新增按钮”,全局 `+` 只负责选择其他服务器,从而让“单连接默认 1 个终端、可继续追加多个终端”的关系在顶部标签栏里更接近参考图;`ConnectionsView.vue` 已升级为“左侧范围树 + 顶部搜索工具条 + 右侧结果列表”的双栏管理台,当前左侧进一步支持基于标签名路径分隔符推导的多级标签树、树节点展开状态持久化、分组 scope 恢复,以及树工具栏中的展开全部、收起全部和重置范围控制;近期又补上了独立的左侧树搜索、命中节点及祖先路径过滤、命中链路自动展开、节点计数高亮,以及更接近资源管理器的树头部布局;本轮继续为树节点加入 hover 工具按钮、资源管理器式分隔标题行与拖拽重排占位反馈;右侧结果列表则同时支持顶部排序控件、列头点击排序,并将行内操作整理为“连接”主按钮加“更多”菜单(编辑/测试/克隆/删除);`FileManager.vue` 当前已进一步收敛为固定 `/` 根节点的单栏资源管理器树,组件加载时会优先拉取 `/` 目录,树中按“目录在前、文件在后”同时显示目录和文件节点,点击目录只展开与聚焦,点击文件则沿用现有工作区文件打开链路;文件右键菜单链路则已补齐图标化菜单结构、危险态删除项、终端子菜单(执行 `cd` 命令到终端 / 新建终端到当前目录)、复制文件名与复制绝对路径等动作,并继续复用现有下载、权限、新建、上传和删除逻辑,同时又新增了独立“上传文件夹”入口:前端会先将本地目录打包为 zip,再复用现有 `sftp:upload` 链路上传,并在上传成功后自动调用远端解压、尝试清理临时压缩包;外部拖拽文件或目录上传时,则会按鼠标当前悬停的目录作为目标路径,其中目录同样走“先压缩再上传”的路径,从而显著降低小文件很多时的扫描与上传耗时;本轮又补上了拖拽上传前的目标路径确认、桌面端右键子菜单点击展开,以及目录删除时“仅删空目录 / 强制递归删除”的显式二选一;当前右键菜单的关闭职责已经收敛到 `FileManagerContextMenu.vue` 组件层处理,`useFileManagerContextMenu.ts` 不再额外注册捕获阶段的全局点击关闭监听,以避免“终端 / 上传 / 压缩”等带子菜单项在展开或点击前被提前关闭;同时 `useSftpActions.ts` 会在删除目录后自动回退当前或待加载的失效路径,避免文件树持续对已删除目录刷出 `No such file`。样式编辑器中的终端文字描边/阴影默认开关也已与新的黑绿终端风格保持默认开启。 +**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 继续整合快捷指令、命令历史、文件管理和编辑器四个面板,但导航入口已从顶部文本 tab 收敛为最左侧窄 icon rail,四个入口图标自上而下竖排排列、默认仅显示图标并通过 tooltip 暴露名称,默认激活快捷指令。`CommandInputBar.vue` 当前已将底部命令框升级为支持会话级草稿保留的多行 `textarea`:普通 `Enter` 插入换行,`Ctrl+Shift+Enter` 发送当前命令,输入框会按内容自动增高至约 6 行,超出后在输入框内部滚动,并继续兼容快捷指令/命令历史同步与选中发送逻辑。快捷指令相关能力目前由 `AddEditQuickCommandForm.vue`、`QuickCommandsView.vue` 与新增的 `utils/quickCommandTemplate.ts` 协同实现:编辑弹窗左侧既可维护自定义 `${变量名}`,也提供 `${{date}}`、`${{time}}`、`${{timestamp}}`、`${{week}}`、`${{uuid}}`、`${{random:8}}`、`${{clipboard}}`、`${{password}}` 等动态变量的一键插入;实际执行时会统一走共享解析器,覆盖编辑弹窗执行、列表直接执行、粘贴到命令输入框和发送到全部服务器等链路,并对未定义变量、无法读取的剪贴板或不可用密码给出非阻断告警。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button`、`text-button-text`、`hover:bg-button-hover`、`hover:bg-border` 等主题变量类,避免写死黑白 hover 色值;该视图当前还支持命令项右键菜单,并已修正为实底卡片式上下文菜单,提供立即执行、粘贴到命令输入框(不自动发送)、复制命令、发送到全部服务器、编辑和删除等动作。`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复,并在渲染层为带 `xterm-fg-*` class 或内联 `style.color` 的显式前景色字符打标记,让终端文字描边/阴影仅作用于默认前景文本,不覆盖 ANSI 彩色输出;`session.store` 当前会为同一 SSH 连接下的新终端分配递增的 `terminalIndex`。当前顶部 `TerminalTabBar.vue` 已改为服务器级入口:SSH 项只负责在不同服务器之间切换,全局 `+` 继续负责选择其他服务器;同一服务器下的多个终端则下沉到 `LayoutRenderer.vue` 的终端面板内部,以次级标签条承载切换、关闭和新增,从而让“进入服务器后再管理该服务器的多个终端”成为主要交互模型。当前终端标签右键菜单继续复用 `WorkspaceView.vue` 中转的会话关闭链路,除关闭当前、关闭其他、关闭左右侧外,也支持直接触发“关闭全部”来清空当前工作区中的全部终端标签。`ConnectionsView.vue` 已升级为“左侧范围树 + 顶部搜索工具条 + 右侧结果列表”的双栏管理台,当前左侧进一步支持基于标签名路径分隔符推导的多级标签树、树节点展开状态持久化、分组 scope 恢复,以及树工具栏中的展开全部、收起全部和重置范围控制;近期又补上了独立的左侧树搜索、命中节点及祖先路径过滤、命中链路自动展开、节点计数高亮,以及更接近资源管理器的树头部布局;本轮继续为树节点加入 hover 工具按钮、资源管理器式分隔标题行与拖拽重排占位反馈;右侧结果列表则同时支持顶部排序控件、列头点击排序,并将行内操作整理为“连接”主按钮加“更多”菜单(编辑/测试/克隆/删除);`FileManager.vue` 当前已进一步收敛为固定 `/` 根节点的单栏资源管理器树,组件加载时会优先拉取 `/` 目录,树中按“目录在前、文件在后”同时显示目录和文件节点,点击目录只展开与聚焦,点击文件则沿用现有工作区文件打开链路;文件右键菜单链路则已补齐图标化菜单结构、危险态删除项、终端子菜单(执行 `cd` 命令到终端 / 新建终端到当前目录)、复制文件名与复制绝对路径等动作,并继续复用现有下载、权限、新建、上传和删除逻辑,同时又新增了独立“上传文件夹”入口:前端会先将本地目录打包为 zip,再复用现有 `sftp:upload` 链路上传,并在上传成功后自动调用远端解压、尝试清理临时压缩包;外部拖拽文件或目录上传时,则会按鼠标当前悬停的目录作为目标路径,其中目录同样走“先压缩再上传”的路径,从而显著降低小文件很多时的扫描与上传耗时;本轮又补上了拖拽上传前的目标路径确认、桌面端右键子菜单点击展开,以及目录删除时“仅删空目录 / 强制递归删除”的显式二选一;当前右键菜单的关闭职责已经收敛到 `FileManagerContextMenu.vue` 组件层处理,`useFileManagerContextMenu.ts` 不再额外注册捕获阶段的全局点击关闭监听,以避免“终端 / 上传 / 压缩”等带子菜单项在展开或点击前被提前关闭;同时 `useSftpActions.ts` 会在删除目录后自动回退当前或待加载的失效路径,避免文件树持续对已删除目录刷出 `No such file`。样式编辑器中的终端文字描边/阴影默认开关也已与新的黑绿终端风格保持默认开启。 **结果**: 页面逻辑分散在 `views/`、`components/`、`stores/` 与 `composables/`,其中工作区终端行为和标签交互优先落在 `session.store.ts`、`session/actions/sessionActions.ts`、`session/getters.ts`、`TerminalTabBar.vue`、`WorkspaceView.vue`、`Terminal.vue` 与相关 locale 文件。 ### 仪表盘总览 diff --git a/packages/frontend/src/App.vue b/packages/frontend/src/App.vue index 63cae50..db689f9 100644 --- a/packages/frontend/src/App.vue +++ b/packages/frontend/src/App.vue @@ -286,8 +286,8 @@ const isElementVisibleAndFocusable = (element: HTMLElement): boolean => { Project Logo {{ t('nav.dashboard') }} - {{ t('nav.terminal') }} + {{ t('nav.terminal') }} diff --git a/packages/frontend/src/components/LayoutRenderer.vue b/packages/frontend/src/components/LayoutRenderer.vue index 0ec01e6..03419e1 100644 --- a/packages/frontend/src/components/LayoutRenderer.vue +++ b/packages/frontend/src/components/LayoutRenderer.vue @@ -12,6 +12,7 @@ import { useSettingsStore } from '../stores/settings.store'; import { useAppearanceStore } from '../stores/appearance.store'; // +++ Import appearance store +++ import { useSidebarResize } from '../composables/useSidebarResize'; import { storeToRefs } from 'pinia'; +import type { SessionTabInfoWithStatus } from '../stores/session/types'; // --- Props --- @@ -66,7 +67,7 @@ const { terminalCustomHTML, } = storeToRefs(appearanceStore); -const { activeSession } = storeToRefs(sessionStore); +const { activeSession, sessionTabsWithStatus } = storeToRefs(sessionStore); const { workspaceSidebarPersistentBoolean, getSidebarPaneWidth } = storeToRefs(settingsStore); const { sidebarPanes } = storeToRefs(layoutStore); const { orderedTabs: editorTabsFromStore, activeTabId: activeEditorTabIdFromStore } = storeToRefs(fileEditorStore); // <-- Get editor state @@ -125,6 +126,47 @@ const hasSshSessions = computed(() => { return false; }); +const activeTerminalConnectionId = computed(() => { + if (!props.activeSessionId) { + return null; + } + + const sessionState = sessionStore.sessions.get(props.activeSessionId); + if (!sessionState?.terminalManager) { + return null; + } + + return sessionState.connectionId; +}); + +const activeTerminalSessions = computed(() => { + if (!activeTerminalConnectionId.value) { + return []; + } + + return sessionTabsWithStatus.value.filter((session) => { + const sessionState = sessionStore.sessions.get(session.sessionId); + return session.connectionId === activeTerminalConnectionId.value && Boolean(sessionState?.terminalManager); + }); +}); + +const activeTerminalConnectionName = computed(() => activeTerminalSessions.value[0]?.connectionName ?? ''); + +const openTerminalSibling = () => { + if (!activeTerminalConnectionId.value) { + return; + } + + sessionStore.handleOpenNewSession(activeTerminalConnectionId.value); +}; + +const closeTerminalSession = (sessionId: string) => { + sessionStore.closeSession(sessionId); +}; + +const getTerminalSessionTitle = (session: SessionTabInfoWithStatus) => + `${session.connectionName} / ${t('terminalTabBar.terminalBadge', { index: session.terminalIndex })}`; + // 面板标签 (Similar to LayoutConfigurator) const paneLabels = computed(() => ({ connections: t('layout.pane.connections', '连接列表'), @@ -573,9 +615,67 @@ onBeforeUnmount(() => { diff --git a/packages/frontend/src/components/TerminalTabBar.vue b/packages/frontend/src/components/TerminalTabBar.vue index 9abf8ab..2e2aa8b 100644 --- a/packages/frontend/src/components/TerminalTabBar.vue +++ b/packages/frontend/src/components/TerminalTabBar.vue @@ -72,6 +72,64 @@ const activeConnectionId = computed(() => { return sessionStore.sessions.get(props.activeSessionId)?.connectionId ?? null; }); +const getConnectionInfoById = (connectionId: string) => + connectionsStore.connections.find((connection) => connection.id === Number(connectionId)) ?? null; + +const isSshConnection = (connectionId: string) => getConnectionInfoById(connectionId)?.type === 'SSH'; + +const getConnectionSessions = (connectionId: string) => + draggableSessions.value.filter((session) => session.connectionId === connectionId); + +const getRepresentativeSessionId = (connectionId: string, fallbackSessionId: string) => { + if (activeConnectionId.value === connectionId && props.activeSessionId) { + return props.activeSessionId; + } + + return getConnectionSessions(connectionId)[0]?.sessionId ?? fallbackSessionId; +}; + +const getConnectionSessionCount = (connectionId: string) => getConnectionSessions(connectionId).length; + +const shouldRenderTopLevelItem = (session: SessionTabInfoWithStatus, index: number) => { + if (!isSshConnection(session.connectionId)) { + return true; + } + + return isGroupStart(index); +}; + +const hasCollapsedSshGroups = computed(() => + draggableSessions.value.some((session, index) => isSshConnection(session.connectionId) && !isGroupStart(index)) +); + +const activateTopLevelItem = (session: SessionTabInfoWithStatus) => { + if (isSshConnection(session.connectionId)) { + activateSession(getRepresentativeSessionId(session.connectionId, session.sessionId)); + return; + } + + activateSession(session.sessionId); +}; + +const showTopLevelContextMenu = (event: MouseEvent, session: SessionTabInfoWithStatus) => { + const targetSessionId = isSshConnection(session.connectionId) + ? getRepresentativeSessionId(session.connectionId, session.sessionId) + : session.sessionId; + + showContextMenu(event, targetSessionId); +}; + +const getTopLevelItemTitle = (session: SessionTabInfoWithStatus) => { + if (!isSshConnection(session.connectionId)) { + return `${session.connectionName} / ${t('terminalTabBar.terminalBadge', { index: session.terminalIndex })}`; + } + + return t('terminalTabBar.serverEntryTitle', { + name: session.connectionName, + count: getConnectionSessionCount(session.connectionId), + }); +}; + const openConnectionPicker = () => { showConnectionListPopup.value = true; }; @@ -85,27 +143,6 @@ const isGroupStart = (index: number) => { return Boolean(currentSession && (!previousSession || previousSession.connectionId !== currentSession.connectionId)); }; -const isGroupEnd = (index: number) => { - const currentSession = getSessionAtIndex(index); - const nextSession = getSessionAtIndex(index + 1); - - return Boolean(currentSession && (!nextSession || nextSession.connectionId !== currentSession.connectionId)); -}; - -const getConnectionInfoById = (connectionId: string) => - connectionsStore.connections.find((connection) => connection.id === Number(connectionId)) ?? null; - -const canOpenSiblingTerminal = (connectionId: string) => getConnectionInfoById(connectionId)?.type === 'SSH'; - -const openNewTerminalForConnection = (connectionId: string) => { - const connectionInfo = getConnectionInfoById(connectionId); - if (!connectionInfo || connectionInfo.type !== 'SSH') { - return; - } - - sessionStore.handleOpenNewSession(connectionInfo.id); -}; - // + Watch prop changes to update local state watch(() => props.sessions, (newSessions) => { // Create a shallow copy to avoid modifying the prop directly @@ -484,93 +521,75 @@ onBeforeUnmount(() => { ghost-class="opacity-50" drag-class="opacity-75" animation="150" - :disabled="props.isMobile" + :disabled="props.isMobile || hasCollapsedSshGroups" >