diff --git a/.helloagents/CHANGELOG.md b/.helloagents/CHANGELOG.md index 16244c3..cfa5584 100644 --- a/.helloagents/CHANGELOG.md +++ b/.helloagents/CHANGELOG.md @@ -2,7 +2,22 @@ ## [Unreleased] -- **[frontend]**: 支持将快捷指令从一个标签组拖到另一个标签组内,允许把未标记命令直接拖入目标标签组,并修正 `manual / name / last_used` 排序按钮状态映射 — by yinjianm +- **[frontend]**: 将状态监控中的 CPU 卡片升级为总 CPU `canvas` 历史图 + 每核心实时条卡,并在极窄侧栏下自动切换为单列布局 — by yinjianm + - 方案: [202604190351_status-monitor-cpu-total-and-per-core](archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/) + +- **[backend]**: 扩展 `StatusMonitorService` 的 `/proc/stat` 采样链路,新增 `cpuCorePercents` 每核心实时占用字段并与总 CPU 百分比一同下发 — by yinjianm + - 方案: [202604190351_status-monitor-cpu-total-and-per-core](archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/) + +- **[frontend]**: 将状态监控里的网络历史图和网络统计表固定改为上下堆叠,并通过压缩图表 canvas、表格间距与统计项内边距,把整个网络模块限制在 350px 以内 — by yinjianm + - 方案: [202604190358_status-monitor-network-vertical-stack](archive/2026-04/202604190358_status-monitor-network-vertical-stack/) + +- **[frontend]**: 为进程管理详细视图补充可点击表头排序,支持按 `PID / USER / STATE / CPU / MEM / START / COMMAND` 列切换升降序,并拉开关闭按钮与刷新区的安全间距 — by yinjianm + - 方案: [202604190352_process-manager-table-sort-and-close-spacing](archive/2026-04/202604190352_process-manager-table-sort-and-close-spacing/) + +- **[frontend]**: 将状态监控“进程管理”的总数从独立摘要项提升为标题区右侧胶囊,并将默认摘要区收敛为“运行中 / 休眠中”两项,减少默认卡片纵向占用 - by yinjianm + - 方案: [202604190349_process-total-pill-display](archive/2026-04/202604190349_process-total-pill-display/) + +- **[frontend]**: 支持将快捷指令从一个标签组拖到另一个标签组内,允许把未标记命令直接拖入目标标签组,并修正 `manual / name / last_used` 排序按钮状态映射 - by yinjianm - 方案: [202604190322_quickcommands-cross-group-drag-move](archive/2026-04/202604190322_quickcommands-cross-group-drag-move/) - **[frontend]**: 将状态监控中的内存与网络卡片响应式阈值统一收紧到 250px,并把网络卡片的 SVG 趋势线升级为可 hover 查看最近 24 个采样点的 canvas 历史图 — by yinjianm @@ -82,7 +97,10 @@ - 方案: [202603250614_terminal-ansi-color-effects](archive/2026-03/202603250614_terminal-ansi-color-effects/) ### 快速修改 -- **[frontend]**: 将状态监控模块区从默认并排调整回基于常用 300px 右栏比例的单列布局,并用弹性高度把普通卡控制在约 200、进程管理控制在约 400 的视觉比例 — by yinjianm +- **[frontend]**: 将状态监控“进程管理”的“运行中 / 休眠中”也收纳进标题区胶囊组,和“总数”一起以内联小显示呈现,不再保留独立摘要行 - by yinjianm + - 类型: 快速修改(无方案包) + - 文件: packages/frontend/src/components/StatusMonitor.vue:217-230,571-575,875-880 +- **[frontend]**: 将状态监控模块区从默认并排调整回基于常用 300px 右栏比例的单列布局,并用弹性高度把普通卡控制在约 200、进程管理控制在约 400 的视觉比例 - by yinjianm - 类型: 快速修改(无方案包) - 文件: packages/frontend/src/components/StatusMonitor.vue - **[frontend]**: 将状态监控模块区从大断点固定分列改为更高密度的 auto-fit 自适应网格,让内存/网络/磁盘在正常宽度下默认并排,只有非常窄时才回落为单列 — by yinjianm diff --git a/.helloagents/archive/2026-04/202604190349_process-total-pill-display/.status.json b/.helloagents/archive/2026-04/202604190349_process-total-pill-display/.status.json new file mode 100644 index 0000000..1898f17 --- /dev/null +++ b/.helloagents/archive/2026-04/202604190349_process-total-pill-display/.status.json @@ -0,0 +1 @@ +{"status":"completed","completed":3,"failed":0,"pending":0,"total":3,"done":3,"percent":100,"current":"已完成并等待归档","updated_at":"2026-04-19 04:00:00"} diff --git a/.helloagents/archive/2026-04/202604190349_process-total-pill-display/proposal.md b/.helloagents/archive/2026-04/202604190349_process-total-pill-display/proposal.md new file mode 100644 index 0000000..6790073 --- /dev/null +++ b/.helloagents/archive/2026-04/202604190349_process-total-pill-display/proposal.md @@ -0,0 +1,113 @@ +# 变更提案: process-total-pill-display + +## 元信息 +```yaml +类型: 优化 +方案类型: implementation +优先级: P2 +状态: 草案 +创建: 2026-04-19 +``` + +--- + +## 1. 需求 + +### 背景 +工作区右侧状态监控中的“进程管理”模块当前把“总数 / 运行中 / 休眠中”都放在独立摘要行里展示,其中“总数”作为单独卡片占用了一行摘要位。用户希望“总数”像现有 `monitor-module__pill` 一样以内联小胶囊显示,并挂在模块标题区域,减少纵向占用,让默认监控卡片更紧凑。 + +### 目标 +- 将“总数”从 `process-summary-strip` 中移出。 +- 在“进程管理”标题区右侧新增与现有监控胶囊一致的小显示。 +- 保持“运行中 / 休眠中”摘要和高占用进程预览逻辑不变。 + +### 约束条件 +```yaml +时间约束: 本次仅做局部前端样式与模板调整 +性能约束: 不新增数据请求,不改变状态监控刷新频率 +兼容性约束: 继续复用现有 StatusMonitor 深色监控视觉体系 +业务约束: 不修改进程统计来源和 process manager modal 行为 +``` + +### 验收标准 +- [ ] “总数”不再以独立 `process-summary-item` 单独占一行显示。 +- [ ] “总数”在“进程管理”模块标题右侧以内联胶囊展示,视觉风格与现有 `monitor-module__pill` 一致。 +- [ ] “运行中 / 休眠中”摘要仍正常显示,进程预览列表不受影响。 +- [ ] 前端构建通过。 + +--- + +## 2. 方案 + +### 技术方案 +在 `packages/frontend/src/components/StatusMonitor.vue` 中拆分进程摘要数据: +- 新增一个仅供标题区展示的总数文案或值。 +- 将 `processSummaryItems` 缩减为“运行中 / 休眠中”两项。 +- 在 `monitor-module--process` 的标题区追加一个 `monitor-module__pill` 样式节点,用于显示总数。 +- 视需要微调标题区布局与摘要区网格列数,保证小宽度下仍然稳定。 + +### 影响范围 +```yaml +涉及模块: + - frontend: 调整 StatusMonitor.vue 的模板、计算属性和局部样式 +预计变更文件: 1 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 标题区新增胶囊后在窄宽度下换行异常 | 低 | 复用现有 heading 布局并补充最小样式约束 | +| 摘要区列数变化导致视觉密度失衡 | 低 | 同步调整 `process-summary-strip` 的网格列定义 | + +--- + +## 3. 技术设计(可选) + +> 本次不涉及架构变更、API 设计或数据模型变更,N/A。 + +--- + +## 4. 核心场景 + +### 场景: 默认监控卡片中查看进程管理摘要 +**模块**: frontend +**条件**: 用户已进入 `/workspace`,右侧状态监控卡片正常渲染进程统计。 +**行为**: “总数”以内联胶囊形式显示在“进程管理”标题右侧,摘要区仅保留“运行中 / 休眠中”。 +**结果**: 模块纵向占用更紧凑,摘要信息层级更清晰。 + +--- + +## 5. 技术决策 + +### process-total-pill-display#D001: 将“总数”提升为标题区状态胶囊 +**日期**: 2026-04-19 +**状态**: 已采纳 +**背景**: “总数”属于模块级总览指标,相比“运行中 / 休眠中”更适合作为标题区状态而不是并列摘要项。 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 保持在摘要区 | 改动最小 | 继续占用摘要行,层级不突出 | +| B: 提升到标题区胶囊 | 更紧凑,和现有监控胶囊风格一致 | 需要调整标题区模板与布局 | +**决策**: 选择方案 B +**理由**: 更符合该指标的总览属性,也与用户明确指定的位置一致。 +**影响**: 影响 `StatusMonitor.vue` 内进程管理模块的标题区和摘要区布局。 + +--- + +## 6. 成果设计 + +### 设计方向 +- **美学基调**: 延续现有深色终端监控卡片风格,在不改变整体主题的前提下压缩信息层级。 +- **记忆点**: “进程总数”从块级摘要变成标题区状态胶囊,信息更像实时监控标签而不是表格式统计。 +- **参考**: 直接复用当前组件内已存在的 `monitor-module__pill` 视觉语言。 + +### 视觉要素 +- **配色**: 继续使用现有绿色轻发光胶囊配色。 +- **字体**: 沿用组件现有无衬线监控字体栈,不引入新字体。 +- **布局**: 标题区左侧保留标题文案,右侧增加总数胶囊;摘要区缩减为两列。 +- **动效**: N/A,本次不新增动画。 +- **氛围**: 保持控制台式深色监控观感,不做主题偏移。 + +### 技术约束 +- **可访问性**: 保持文本可读性,不用纯图标替代文字。 +- **响应式**: 标题区在窄容器下允许自然换行但不应破坏内容顺序。 diff --git a/.helloagents/archive/2026-04/202604190349_process-total-pill-display/tasks.md b/.helloagents/archive/2026-04/202604190349_process-total-pill-display/tasks.md new file mode 100644 index 0000000..38d126f --- /dev/null +++ b/.helloagents/archive/2026-04/202604190349_process-total-pill-display/tasks.md @@ -0,0 +1,47 @@ +# 任务清单: process-total-pill-display + +> **@status:** completed | 2026-04-19 03:54 + +```yaml +@feature: process-total-pill-display +@created: 2026-04-19 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 3 | 0 | 0 | 3 | + +--- + +## 任务列表 + +### 1. frontend 组件调整 + +- [√] 1.1 在 `packages/frontend/src/components/StatusMonitor.vue` 中把进程总数从摘要列表中拆分出来,并挂到模块标题区域 +- [√] 1.2 在 `packages/frontend/src/components/StatusMonitor.vue` 中调整进程摘要区布局,使“运行中 / 休眠中”保持紧凑展示 + - 依赖: 1.1 + +### 2. 验证 + +- [√] 2.1 执行前端构建验证,确认 `StatusMonitor.vue` 改动未引入编译错误 + - 依赖: 1.2 + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-04-19 03:49 | 创建方案包 | 已完成 | `create_package.py` 已生成 proposal/tasks 模板 | +| 2026-04-19 03:58 | 完成组件改动 | 已完成 | `StatusMonitor.vue` 已将总数提升到标题区胶囊,并将摘要区收敛为两项 | +| 2026-04-19 04:00 | 前端构建验证 | 已完成 | `npm --prefix "E:/code/vue/nexus-terminal/packages/frontend" run build` 通过 | + +--- + +## 执行备注 + +> 本次为局部 UI 优化,目标是减少“进程管理”默认卡片的纵向占用,不改变进程数据来源、刷新机制和 modal 行为。 diff --git a/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/.status.json b/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/.status.json new file mode 100644 index 0000000..c992364 --- /dev/null +++ b/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/.status.json @@ -0,0 +1 @@ +{"status":"completed","completed":6,"failed":0,"pending":0,"total":6,"done":6,"percent":100,"current":"总 CPU 历史图与每核心实时条卡已完成实现并通过前后端构建校验","updated_at":"2026-04-19 04:06:18"} diff --git a/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/proposal.md b/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/proposal.md new file mode 100644 index 0000000..f2ddcb6 --- /dev/null +++ b/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/proposal.md @@ -0,0 +1,138 @@ +# 变更提案: status-monitor-cpu-total-and-per-core + +## 元信息 +```yaml +类型: 优化 +方案类型: implementation +优先级: P1 +状态: 已确认 +创建: 2026-04-19 +``` + +--- + +## 1. 需求 + +### 背景 +当前 `/workspace` 右侧状态监控中,CPU 区只有一个装饰性的 SVG sparkline 和一条总占用进度条,无法像网络卡片那样直接查看近期历史图,也无法区分不同 CPU 核心的实时负载。用户明确要求将 CPU 区改成“总 CPU 历史图 + 不同核心分别显示”的结构,并已确认采用“保留总 CPU 历史图、下方按核心显示独立实时条卡”的方案。 + +### 目标 +- 将 CPU 卡片升级为 `canvas` 总 CPU 历史图,展示最近采样趋势。 +- 为每个 CPU 核心增加独立的实时显示条卡,明确区分不同核心当前占用。 +- 扩展后端状态采样 payload,提供每核心 CPU 百分比数据,前端直接消费。 +- 保持现有总 CPU 历史图缓存链路可用,不影响内存/网络/磁盘卡片。 + +### 约束条件 +```yaml +时间约束: 本轮限定为状态监控 CPU 链路,不扩展到设置页、审计或外部图表页面 +性能约束: 每核心仅传当前百分比,不增加每核心历史数组,避免 websocket payload 膨胀 +兼容性约束: 总 CPU 百分比和既有 cpuHistory 保持兼容,底部 StatusCharts 继续工作 +业务约束: 每核心数据来自现有 Linux /proc/stat 采样,不引入额外系统依赖 +``` + +### 验收标准 +- [ ] 后端状态 payload 新增每核心 CPU 百分比字段,并能与总 CPU 百分比一同返回。 +- [ ] 前端 CPU 卡片改为总 CPU 历史 `canvas` 图,显示最近采样趋势。 +- [ ] CPU 卡片下方按核心显示独立实时条卡,每个核心有单独标签和值。 +- [ ] 前后端构建通过;若本地无法完成登录后工作区的运行态验证,需要在执行备注中明确说明。 + +--- + +## 2. 方案 + +### 技术方案 +后端继续使用 `/proc/stat` 作为 CPU 采样源,但从只解析总 `cpu` 行扩展为同时解析 `cpu` 和 `cpuN` 行。服务端为每个会话缓存“上一次总 CPU 快照 + 每核心快照”,在下一轮采样时计算总百分比与每核心百分比,并通过 `status_update` 一起返回。前端类型定义增加 `cpuCorePercents`,CPU 卡片新增一个 `StatusMonitorCpuHistoryChart.vue` 子组件承载总 CPU `canvas` 历史图,`StatusMonitor.vue` 则在图下方或右侧渲染每核心实时条卡。 + +### 影响范围 +```yaml +涉及模块: + - backend: StatusMonitorService 的 CPU 采样与状态 payload 扩展 + - frontend: ServerStatus 类型、CPU 卡片结构与新图表子组件 + - frontend-i18n: 每核心标签与 CPU 历史图说明文案 +预计变更文件: 8 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 多核心服务器返回大量核心数据,导致 CPU 卡片过高 | 中 | 每核心条卡采用紧凑网格并允许卡片内部滚动 | +| 总 CPU 与各核心百分比计算方式不一致,导致数值观感异常 | 中 | 统一基于同一轮 `/proc/stat` 差值计算,采用相同的百分比裁剪规则 | +| 旧连接会话在首次采样前没有前序快照,导致每核心值为空 | 低 | 首轮统一返回 0,并在第二轮后进入稳定值 | + +--- + +## 3. 技术设计(可选) + +### 架构设计 +```mermaid +flowchart TD + A[/proc/stat cpu + cpuN/] --> B[StatusMonitorService] + B --> C[status.cpuPercent] + B --> D[status.cpuCorePercents[]] + C --> E[useStatusMonitor cpuHistory] + D --> F[StatusMonitor.vue core cards] + E --> G[StatusMonitorCpuHistoryChart.vue] +``` + +### 数据模型 +| 字段 | 类型 | 说明 | +|------|------|------| +| `cpuCorePercents` | `number[]` | 每个核心当前 CPU 使用率百分比,顺序对应 `cpu0..cpuN` | +| `cpuHistory` | `(number \| null)[]` | 现有总 CPU 历史采样,继续保留 | + +--- + +## 4. 核心场景 + +> 执行完成后同步到对应模块文档 + +### 场景: 侧栏中查看总 CPU 趋势 +**模块**: frontend +**条件**: 用户在 `/workspace` 查看右侧状态监控,当前会话已持续收到 CPU 状态采样。 +**行为**: CPU 卡片显示一个 `canvas` 总 CPU 历史图,用于观察最近采样趋势。 +**结果**: 用户可以不依赖底部大图,也能在 CPU 卡片中快速判断近期整体负载波动。 + +### 场景: 区分不同核心当前负载 +**模块**: frontend, backend +**条件**: 后端已为当前会话返回 `cpuCorePercents`。 +**行为**: CPU 卡片按核心渲染独立实时条卡,例如 `Core 1`、`Core 2`,每个核心都有单独百分比和进度条。 +**结果**: 用户可以快速识别负载是否集中在某些核心,而不是只能看总 CPU 百分比。 + +--- + +## 5. 技术决策 + +> 本方案涉及的技术决策,归档后成为决策的唯一完整记录 + +### status-monitor-cpu-total-and-per-core#D001: 每核心只传当前值,不维护每核心历史数组 +**日期**: 2026-04-19 +**状态**: ✅采纳 +**背景**: 用户要“总 CPU 历史图 + 每核心分别显示”。其中总图需要历史数据,但每核心展示仅要求分别显示,不一定需要每核心历史。 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 总 CPU 历史 + 每核心当前值(推荐) | 数据量小、实现稳、对侧栏更友好 | 不能直接查看单核心历史 | +| B: 总 CPU 历史 + 每核心历史数组 | 信息最全,后续可扩成每核心迷你图 | websocket payload 和前端渲染复杂度明显上升 | +**决策**: 选择方案 A +**理由**: 这是最符合当前需求和侧栏密度约束的实现路径,既满足“总历史图”,也满足“不同核心不同显示”,同时避免后端和前端缓存结构膨胀。 +**影响**: 扩展后端 CPU 采样与前端 CPU 卡片结构,但不影响现有内存/网络历史缓存策略。 + +--- + +## 6. 成果设计 + +### 设计方向 +- **美学基调**: 延续当前状态监控的深色控制台面板风格,把 CPU 区从“单条概览”提升为更接近真实监控屏的图表 + 分核条卡组合。 +- **记忆点**: 同一张 CPU 卡里上方是总历史图,下方是一组按核心编号排列的独立实时条卡。 +- **参考**: 视觉语言参考已完成的网络 `canvas` 历史图和现有 usage lane 组件风格。 + +### 视觉要素 +- **配色**: 保持 CPU 既有蓝色语义色作为总图和条卡主色,不引入额外杂色。 +- **字体**: 数值继续使用 monospace,核心标签保持当前状态监控字重体系。 +- **布局**: CPU 卡片采用“历史图 + 条卡网格”组合,窄宽度时自动退为单列。 +- **动效**: 图表关闭动画,仅保留 hover tooltip;条卡用静态宽度反馈,不做额外动画。 +- **氛围**: 保持深色服务器面板的克制风格,避免把 CPU 卡做成单独花哨模块。 + +### 技术约束 +- **可访问性**: 核心条卡需包含可读标签与数值,不只靠颜色区分。 +- **响应式**: 多核心条卡在窄侧栏下要自动换列或纵向堆叠。 diff --git a/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/tasks.md b/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/tasks.md new file mode 100644 index 0000000..5cd6ef5 --- /dev/null +++ b/.helloagents/archive/2026-04/202604190351_status-monitor-cpu-total-and-per-core/tasks.md @@ -0,0 +1,56 @@ +# 任务清单: status-monitor-cpu-total-and-per-core + +> **@status:** completed | 2026-04-19 04:08 + +```yaml +@feature: status-monitor-cpu-total-and-per-core +@created: 2026-04-19 +@status: completed +@mode: R3 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 6 | 0 | 0 | 6 | + +--- + +## 任务列表 + +### 1. 后端 CPU 状态扩展 + +- [√] 1.1 在 `packages/backend/src/services/status-monitor.service.ts` 中扩展 `/proc/stat` 解析,计算每核心 CPU 百分比并写入状态 payload | depends_on: [] +- [√] 1.2 运行 `@nexus-terminal/backend` 构建校验,确认每核心 CPU 字段扩展未破坏现有服务端类型与编译 | depends_on: [1.1] + +### 2. 前端 CPU 卡片重构 + +- [√] 2.1 在 `packages/frontend/src/types/server.types.ts` 与 `packages/frontend/src/composables/useStatusMonitor.ts` 中接入每核心 CPU 状态字段 | depends_on: [1.1] +- [√] 2.2 新增 `packages/frontend/src/components/StatusMonitorCpuHistoryChart.vue`,显示总 CPU canvas 历史图 | depends_on: [2.1] +- [√] 2.3 在 `packages/frontend/src/components/StatusMonitor.vue` 中将 CPU 卡片改为“总历史图 + 每核心实时条卡”布局 | depends_on: [2.2] +- [√] 2.4 在 `packages/frontend/src/locales/zh-CN.json`、`packages/frontend/src/locales/en-US.json`、`packages/frontend/src/locales/ja-JP.json` 中补充 CPU 历史与核心标签文案,并运行 `@nexus-terminal/frontend` 构建校验 | depends_on: [2.3] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-04-19 03:57 | design | completed | 已创建 implementation 方案包,范围锁定为总 CPU 历史图与每核心实时条卡的前后端联动实现 | +| 2026-04-19 04:00 | 1.1 | completed | 已扩展 `/proc/stat` 解析为总 CPU + `cpuN` 每核心快照,并将 `cpuCorePercents` 一并写入状态 payload | +| 2026-04-19 04:01 | 1.2 | completed | `npm --workspace @nexus-terminal/backend run build` 通过 | +| 2026-04-19 04:02 | 2.1 | completed | 已在前端状态类型与状态监控 composable 中接入 `cpuCorePercents` 并做数组归一化 | +| 2026-04-19 04:03 | 2.2 | completed | 已新增 `StatusMonitorCpuHistoryChart.vue`,用 canvas 展示最近 24 个总 CPU 历史采样点 | +| 2026-04-19 04:04 | 2.3 | completed | 已将 CPU 卡片改为“总历史图 + 每核心实时条卡”布局,并在极窄宽度下切换为单列 | +| 2026-04-19 04:06 | 2.4 | completed | 已补充中英日 CPU 文案;`npm --workspace @nexus-terminal/frontend run build` 通过,并额外确认 `npm --workspace @nexus-terminal/frontend run preview -- --host 127.0.0.1 --port 4173` 可返回 HTTP 200 | + +--- + +## 执行备注 + +> 记录执行过程中的重要说明、决策变更、风险提示等 + +- 本轮选择方案 A:保留总 CPU 历史图,但每核心仅展示当前实时值,不额外缓存每核心历史数组。 +- CPU 差值计算中的空闲时间按 `idle + iowait` 处理,避免高负载判断偏高。 +- 运行态限制: 已确认前端预览服务能正常启动并返回首页,但未在本地接入真实活动 SSH 会话,因此“带真实 CPU 数据的分核条卡与 tooltip 最终观感”仍建议在你的 `/workspace` 环境手动看一遍。 diff --git a/.helloagents/archive/2026-04/202604190352_process-manager-table-sort-and-close-spacing/proposal.md b/.helloagents/archive/2026-04/202604190352_process-manager-table-sort-and-close-spacing/proposal.md new file mode 100644 index 0000000..9da7df5 --- /dev/null +++ b/.helloagents/archive/2026-04/202604190352_process-manager-table-sort-and-close-spacing/proposal.md @@ -0,0 +1,161 @@ +# 变更提案: process-manager-table-sort-and-close-spacing + +## 元信息 +```yaml +类型: 优化 +方案类型: implementation +优先级: P1 +状态: 已完成 +创建: 2026-04-19 +完成: 2026-04-19 +``` + +--- + +## 1. 需求 + +### 背景 +当前 `ProcessManagerModal.vue` 已经能展示服务器进程明细、支持搜索和手动刷新,但明细表仍然是纯静态表头。用户进入进程管理详细视图后,无法按 `PID`、`CPU`、`MEM` 等列快速切换视角来定位高占用进程或按进程号排查问题。同时,右上角绝对定位的关闭按钮与顶部刷新控制区距离过近,在当前布局下容易产生视觉拥挤,影响操作清晰度。 + +### 目标 +- 允许点击进程管理表格头,按主要数据列切换排序显示。 +- 为当前激活的排序列显示明确的升序/降序方向标记。 +- 保持默认列表顺序与现有后端返回顺序一致,避免未点击表头前的展示语义回归。 +- 拉开关闭按钮与刷新区的安全间距,避免顶部工具区拥挤。 + +### 约束条件 +```yaml +时间约束: 本轮完成前端实现、构建验证与知识库同步 +性能约束: 不新增依赖,继续使用现有 Vue 计算属性完成前端本地排序 +兼容性约束: 不修改后端 process:list 消息结构,不改变默认未排序态 +业务约束: 所有主要数据列都支持排序,但操作列保持不可排序 +``` + +### 验收标准 +- [ ] 进程管理表头至少支持 `PID / USER / STATE / CPU / MEM / START / COMMAND` 点击排序。 +- [ ] 点击同一列表头时可以在升序和降序之间切换,并显示方向标记。 +- [ ] 数值列首次点击默认按更符合排障习惯的方向排序,文本列首次点击默认按字母升序排序。 +- [ ] 默认未点击表头时,进程列表仍保持现有后端返回顺序。 +- [ ] 关闭按钮与刷新区之间有明确安全间距,不再显得贴近或覆盖。 +- [ ] `npm run build --workspace @nexus-terminal/frontend` 通过。 + +--- + +## 2. 方案 + +### 技术方案 +本次改动只落在前端 `ProcessManagerModal.vue`。 + +第一部分是表格排序。新增本地排序状态 `sortKey + sortDirection`,基于当前 `processItems` 先执行搜索过滤,再在计算属性中按所选列进行稳定排序。默认 `sortKey=null`,保持后端原始顺序;点击表头后进入排序态。同一列重复点击则在升序/降序间切换,不同列首次点击则采用按列预设的默认方向,其中 `PID / CPU / MEM` 首次点击走降序或更符合当前字段的排障方向,文本列首次点击走升序。 + +第二部分是表头交互。把静态 `` 改成内嵌按钮的可点击表头,复用现有 `common.sortAscending` / `common.sortDescending` 文案做无障碍标签和 title,并为当前激活列显示 `fa-chevron-up/down` 方向标记,未激活列显示弱提示排序图标。 + +第三部分是顶部布局修正。在 toolbar 区域给右上角关闭按钮预留固定安全空间,同时把关闭按钮本身做成固定尺寸的独立点击区,避免它和右侧刷新按钮在视觉上挤在一起。 + +### 影响范围 +```yaml +涉及模块: + - frontend: ProcessManagerModal 需要新增本地排序状态、表头交互和顶部安全间距修正 + - knowledge-base: 需要同步 frontend 模块文档与 CHANGELOG +预计变更文件: 4-6 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 自动刷新后排序状态丢失,用户每次都要重新点击表头 | 中 | 排序状态存放在组件 ref 中,列表刷新后通过计算属性持续应用 | +| 默认直接改成某个列排序,导致现有展示语义回归 | 中 | 默认保持 `sortKey=null`,只在点击表头后进入排序态 | +| 关闭按钮安全区处理不当,导致窄屏下搜索区被额外压缩 | 低 | 只在 toolbar 右侧预留最小必要空间,并保留移动端纵向布局回退 | + +--- + +## 3. 技术设计 + +### 架构设计 +```mermaid +flowchart LR + A[processItems] --> B[搜索过滤] + B --> C{sortKey 是否为空} + C -->|是| D[保持原始顺序] + C -->|否| E[按 sortKey 和 sortDirection 排序] + E --> F[表格渲染 + 方向图标] +``` + +### 数据模型 +| 字段 | 类型 | 说明 | +|------|------|------| +| `ProcessSortKey` | `'pid' | 'user' | 'state' | 'cpu' | 'mem' | 'startedAt' | 'command'` | 进程表可排序字段集合 | +| `sortKey` | `ProcessSortKey \| null` | 当前激活排序列,`null` 表示保持后端原始顺序 | +| `sortDirection` | `'asc' \| 'desc'` | 当前排序方向 | + +--- + +## 4. 核心场景 + +### 场景: 按 CPU 或内存占用定位高占用进程 +**模块**: frontend +**条件**: 用户打开进程管理详细视图并查看当前服务器进程列表。 +**行为**: 用户点击 `CPU` 或 `MEM` 表头,表格立即按所选列排序,再次点击则切换方向。 +**结果**: 高占用进程可以被快速顶到表格前部,便于排障。 + +### 场景: 按 PID 或用户筛查进程 +**模块**: frontend +**条件**: 用户需要按 PID 区间或用户归属查找进程。 +**行为**: 用户点击 `PID` 或 `USER` 表头,表格按对应列排序,并显示当前方向图标。 +**结果**: 进程定位路径更短,不再依赖滚动肉眼扫描。 + +### 场景: 顶部工具区避免按钮挤压 +**模块**: frontend +**条件**: 用户打开进程管理详细视图,右上角显示关闭按钮,工具栏右侧显示刷新区。 +**行为**: 组件为关闭按钮保留独立安全区,并增加它与刷新控制区的水平间距。 +**结果**: 关闭按钮与刷新区视觉分离,点击目标更清晰。 + +--- + +## 5. 技术决策 + +### process-manager-table-sort-and-close-spacing#D001: 默认保持后端原始顺序,仅在点击表头后进入排序态 +**日期**: 2026-04-19 +**状态**: ✅采纳 +**背景**: 用户要求增加按列排序,但没有要求改变当前默认列表的初始顺序。直接默认按某列排序会改变已有视图语义。 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 默认直接按 CPU 或 PID 排序 | 打开即有更强导向性 | 会改变当前默认展示语义,带来回归风险 | +| B: 默认保持原顺序,点击后再进入排序态 | 兼容现有展示逻辑,只新增明确可控交互 | 首次使用需要多一次点击 | +**决策**: 选择方案 B +**理由**: 这是最保守也最清晰的扩展方式。用户获得排序能力,但现有默认列表不会被悄悄改写。 +**影响**: frontend + +### process-manager-table-sort-and-close-spacing#D002: 数值列和文本列使用不同的首次点击默认方向 +**日期**: 2026-04-19 +**状态**: ✅采纳 +**背景**: 进程表里 `CPU / MEM` 更常用于找“最大值”,而 `USER / COMMAND` 更符合按字母升序浏览。统一所有列首次点击方向会降低使用效率。 +**选项分析**: +| 选项 | 优点 | 缺点 | +|------|------|------| +| A: 所有列首次点击统一升序 | 规则简单 | 不符合 CPU / MEM 等排障高频场景 | +| B: 按列预设首次点击方向 | 更贴合实际使用心智 | 需要额外维护一份列配置 | +**决策**: 选择方案 B +**理由**: 进程管理是高频排障界面,优先服务常见操作路径比统一规则更重要。 +**影响**: frontend + +--- + +## 6. 成果设计 + +### 设计方向 +- **美学基调**: 延续现有深色控制台式 modal 风格,只做信息结构强化,不做风格重绘。 +- **记忆点**: 表头从纯文本升级为可点击的控制条,并以低干扰方向箭头提示当前排序状态。 +- **参考**: 复用仓库内连接页与仪表盘已有的排序按钮语言。 + +### 视觉要素 +- **配色**: 继续沿用当前表头前景色、hover 背景和边框色,不新增独立主题色。 +- **字体**: 继承现有表头大写小字号风格,方向图标作为次级提示。 +- **布局**: 保持原表格列宽和工具栏结构,仅在表头内部嵌入按钮、在 toolbar 右侧留出关闭按钮安全区。 +- **动效**: 复用当前 hover 和颜色过渡,方向图标不做额外动画。 +- **氛围**: 保持深色服务器控制台气质,让交互增强看起来像原生能力扩展。 + +### 技术约束 +- **可访问性**: 排序表头使用 button,提供 aria-label 和 title。 +- **响应式**: 继续兼容当前 860px 以下的 toolbar 纵向布局。 diff --git a/.helloagents/archive/2026-04/202604190352_process-manager-table-sort-and-close-spacing/tasks.md b/.helloagents/archive/2026-04/202604190352_process-manager-table-sort-and-close-spacing/tasks.md new file mode 100644 index 0000000..d74472e --- /dev/null +++ b/.helloagents/archive/2026-04/202604190352_process-manager-table-sort-and-close-spacing/tasks.md @@ -0,0 +1,54 @@ +# 任务清单: process-manager-table-sort-and-close-spacing + +> **@status:** completed | 2026-04-19 04:03 + +```yaml +@feature: process-manager-table-sort-and-close-spacing +@created: 2026-04-19 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 6 | 0 | 0 | 6 | + +--- + +## 任务列表 + +### 1. 方案与现状确认 + +- [√] 1.1 确认 `packages/frontend/src/components/ProcessManagerModal.vue` 当前仅支持搜索与刷新,不支持表头排序,并定位关闭按钮与刷新区的布局关系 | depends_on: [] + +### 2. 前端实现 + +- [√] 2.1 在 `packages/frontend/src/components/ProcessManagerModal.vue` 中新增本地排序状态与排序计算逻辑,支持主要数据列点击排序 | depends_on: [1.1] +- [√] 2.2 在 `packages/frontend/src/components/ProcessManagerModal.vue` 中把静态表头改为可点击排序按钮,并显示升降序方向标记 | depends_on: [2.1] +- [√] 2.3 在 `packages/frontend/src/components/ProcessManagerModal.vue` 中调整 toolbar 与关闭按钮的安全间距,避免关闭按钮与刷新区过近 | depends_on: [1.1] + +### 3. 验证与同步 + +- [√] 3.1 执行 `npm run build --workspace @nexus-terminal/frontend`,确认类型检查和构建通过 | depends_on: [2.2, 2.3] +- [√] 3.2 同步更新 `.helloagents/modules/frontend.md` 与 `.helloagents/CHANGELOG.md`,记录进程管理表头排序与顶部间距优化 | depends_on: [3.1] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-04-19 03:51 | EVALUATE | completed | 已按 R2 确认进程管理表需支持全量主要列排序,用户选择所有主要数据列可排序 | +| 2026-04-19 03:52 | DESIGN | completed | 已将用户补充的“关闭按钮与刷新区过近”并入同一方案包处理 | +| 2026-04-19 03:54 | 2.1 / 2.2 / 2.3 | completed | `ProcessManagerModal.vue` 已补齐本地排序状态、可点击表头方向标记和 toolbar 安全间距 | +| 2026-04-19 03:55 | validate_package | completed | `validate_package.py 202604190352_process-manager-table-sort-and-close-spacing --path E:/code/vue/nexus-terminal` 通过 | +| 2026-04-19 04:01 | 3.1 | completed | `npm run build --workspace @nexus-terminal/frontend` 通过;期间顺手将 `server.types.ts` 中 `cpuCorePercents` 放宽为只读数组以匹配当前 StatusMonitor 用法 | +| 2026-04-19 04:03 | 3.2 | completed | 已同步 frontend 模块文档与 CHANGELOG,准备归档方案包 | + +--- + +## 执行备注 + +> 默认顺序保持当前后端返回结果,只在点击表头后进入排序态,以避免进程表的初始展示语义回归;为打通前端构建,还顺手把现有 `ServerStatus.cpuCorePercents` 类型从可变数组放宽为只读数组,但不改变任何运行时行为。 diff --git a/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/.status.json b/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/.status.json new file mode 100644 index 0000000..d6eb11a --- /dev/null +++ b/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/.status.json @@ -0,0 +1 @@ +{"status":"completed","completed":4,"failed":0,"pending":0,"total":4,"done":4,"percent":100,"current":"网络模块上下堆叠与 350px 高度约束已完成,待归档方案包","updated_at":"2026-04-19 04:04:00"} diff --git a/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/proposal.md b/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/proposal.md new file mode 100644 index 0000000..6b7c5e5 --- /dev/null +++ b/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/proposal.md @@ -0,0 +1,109 @@ +# 变更提案: status-monitor-network-vertical-stack + +## 元信息 +```yaml +类型: 修复增强 +方案类型: implementation +优先级: P2 +状态: 已完成 +状态说明: 网络模块已固定为上下堆叠,并通过样式压缩将总高度限制在 350px 以内 +创建: 2026-04-19 +``` + +--- + +## 1. 需求 + +### 背景 +当前状态监控中的网络模块在大多数宽度下采用“左侧网络历史图 + 右侧网络统计表”的横向布局。用户明确要求这两块不再左右并排,而是始终改为上下排列;同时新增硬性约束,整个网络模块的总高度不能超过 `350px`,以免在右侧状态监控栏里挤占过多纵向空间。 + +### 目标 +- 让网络历史图和网络统计表在所有宽度下都改为上下堆叠。 +- 将网络模块总高度控制在 `350px` 以内。 +- 保持现有图表、接口名、上下行统计内容与主题风格不变,只调整布局和密度。 + +### 约束条件 +```yaml +范围约束: 只调整 packages/frontend/src/components/StatusMonitor.vue 与 StatusMonitorNetworkHistoryChart.vue 及相关知识库记录 +布局约束: 不再保留大宽度横向并排分栏,网络模块始终单列堆叠 +高度约束: 网络模块整体高度必须 <= 350px +验证约束: 当前项目存在登录态依赖,不使用 Playwright 做页面验收,改为构建与代码级布局审查 +兼容约束: 保持现有网络接口名、上下行图例、统计字段与容器查询响应式体系 +``` + +### 验收标准 +- [x] 网络历史图与网络统计表在所有宽度下都按上下顺序展示 +- [x] 网络模块总高度被显式限制在 `350px` 以内 +- [x] 图表 canvas 和统计表间距同步压缩,避免堆叠后出现纵向溢出 +- [x] 前端构建通过,frontend 模块文档与归档记录同步更新 + +--- + +## 2. 方案 + +### 技术方案 +将 `StatusMonitor.vue` 中网络模块的 `.module-split--network` 从双列 grid 改为单列 grid,并为 `.monitor-module--network` 增加 `max-height: 350px` 与更紧凑的 gap。同步压缩网络图和网络表的垂直占用:降低 `StatusMonitorNetworkHistoryChart.vue` 中 canvas 的固定高度,收紧 header 与 legend 间距;在 `StatusMonitor.vue` 中缩小 `network-table`、`network-stat-stack` 与 `network-stat` 的 padding/gap,让堆叠后的两块内容能稳定落在总高度约束内。 + +### 影响范围 +```yaml +涉及模块: + - frontend: StatusMonitor.vue + - frontend: StatusMonitorNetworkHistoryChart.vue + - frontend: .helloagents/modules/frontend.md +预计变更文件: 5-7 +``` + +### 风险评估 +| 风险 | 等级 | 应对 | +|------|------|------| +| 图表高度压缩过多后可读性下降 | 中 | 只收缩到能满足 350px 的最小必要值,保留标题、图例和折线区的基本层级 | +| 单列堆叠后网络表在窄栏里仍可能被内部 padding 撑高 | 低 | 同步压缩 `network-table` 和 `network-stat` 的 gap/padding,而不是只改父容器方向 | +| 仅用代码级验收可能遗漏运行态细节 | 低 | 在方案包和最终结果中明确标注本次未走 Playwright,建议在登录态环境做人工目视补验 | + +--- + +## 3. 技术设计(可选) + +> N/A,本次不涉及架构、API 或数据模型变更。 + +--- + +## 4. 核心场景 + +### 场景: 网络模块固定上下堆叠 +**模块**: frontend +**条件**: 用户在 `/workspace` 右侧状态监控面板查看网络模块 +**行为**: 网络历史图先显示在上方,网络统计表显示在下方,不再根据宽度切回左右双列 +**结果**: 网络模块信息阅读顺序变为自上而下,符合用户期望 + +### 场景: 网络模块高度受控 +**模块**: frontend +**条件**: 状态监控右侧栏按常规窄栏宽度渲染网络模块 +**行为**: 图表高度、卡片间距和统计项内边距同时收紧,使整个网络模块保持在 350px 以内 +**结果**: 网络模块不会因改成竖排而明显拉长整列布局 + +--- + +## 5. 技术决策 + +> 本方案不涉及新的长期技术决策;沿用现有状态监控组件拆分与容器查询体系。 + +--- + +## 6. 成果设计 + +### 设计方向 +- **美学基调**: 保持现有深色服务器监控面板风格,但把网络模块收紧为更紧凑的“上图下表”信息块,强调纵向节奏感 +- **记忆点**: 网络模块被压成一张纵向卡片,上方是折线历史,下方是双行上下行统计,整体更像窄屏监控屏的小组件 +- **参考**: 当前 `StatusMonitor.vue` 右侧监控栏和现有网络模块视觉体系 + +### 视觉要素 +- **配色**: 保留现有蓝色下行、绿色上行以及深色玻璃卡片背景 +- **字体**: 沿用当前模块内的 monospace 数值与默认界面字重层级 +- **布局**: 父容器改为单列堆叠,图表区高度收紧,表格区用更小 gap 和 padding 承接 +- **动效**: 不新增动画,保留现有 Chart.js 实时更新反馈 +- **氛围**: 维持当前深色科技感卡片和柔和描边,不改变网络模块的视觉语言 + +### 技术约束 +- **可访问性**: 标题、图例、上下行标签仍需完整可读,不因压缩而丢失语义 +- **响应式**: 不破坏现有容器查询体系,但网络模块不再依赖宽度阈值切换双列 diff --git a/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/tasks.md b/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/tasks.md new file mode 100644 index 0000000..37d51eb --- /dev/null +++ b/.helloagents/archive/2026-04/202604190358_status-monitor-network-vertical-stack/tasks.md @@ -0,0 +1,50 @@ +# 任务清单: status-monitor-network-vertical-stack + +> **@status:** completed | 2026-04-19 04:03 + +```yaml +@feature: status-monitor-network-vertical-stack +@created: 2026-04-19 +@status: completed +@mode: R2 +``` + +## 进度概览 + +| 完成 | 失败 | 跳过 | 总数 | +|------|------|------|------| +| 4 | 0 | 0 | 4 | + +--- + +## 任务列表 + +### 1. 方案与约束确认 + +- [√] 1.1 创建网络模块上下堆叠方案包并锁定“总高度 <= 350px”约束 | depends_on: [] + +### 2. 网络模块布局调整 + +- [√] 2.1 将网络模块父容器改为固定上下堆叠,不再保留左右双列布局 | depends_on: [1.1] +- [√] 2.2 压缩图表和网络表内部高度占用,使网络模块总高度不超过 350px | depends_on: [2.1] + +### 3. 验证与同步 + +- [√] 3.1 运行前端构建并同步 frontend 模块文档、CHANGELOG 与归档记录 | depends_on: [2.2] + +--- + +## 执行日志 + +| 时间 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2026-04-19 03:58 | 1.1 | 完成 | 创建 implementation 方案包,范围锁定为网络模块上下堆叠与 350px 高度约束 | +| 2026-04-19 04:02 | 2.1 | 完成 | `.module-split--network` 改为固定单列堆叠,不再按宽度切回双列 | +| 2026-04-19 04:03 | 2.2 | 完成 | 为网络模块增加 `max-height: 350px`,并同步压缩图表 canvas 与网络表间距 | +| 2026-04-19 04:04 | 3.1 | 完成 | `npm run build --workspace @nexus-terminal/frontend` 通过;本次按用户要求未使用 Playwright | + +--- + +## 执行备注 + +> 用户已确认网络图表和统计表在所有宽度下都要改为上下堆叠,并新增“网络模块总高度不超过 350px”的硬性限制。当前项目存在登录态依赖,本次不使用 Playwright 作为验收手段,运行态效果建议在有登录态的环境中人工补验一次。 diff --git a/.helloagents/archive/_index.md b/.helloagents/archive/_index.md index 3562b15..1c84cd1 100644 --- a/.helloagents/archive/_index.md +++ b/.helloagents/archive/_index.md @@ -7,6 +7,10 @@ | 时间戳 | 名称 | 类型 | 涉及模块 | 决策 | 结果 | |--------|------|------|---------|------|------| +| 202604190351 | status-monitor-cpu-total-and-per-core | implementation | frontend, backend | status-monitor-cpu-total-and-per-core#D001 | ✅完成 | +| 202604190358 | status-monitor-network-vertical-stack | implementation | frontend | - | ✅完成 | +| 202604190352 | process-manager-table-sort-and-close-spacing | implementation | frontend | process-manager-table-sort-and-close-spacing#D001, process-manager-table-sort-and-close-spacing#D002 | ✅完成 | +| 202604190349 | process-total-pill-display | - | - | - | ✅完成 | | 202604190322 | quickcommands-cross-group-drag-move | implementation | frontend | quickcommands-cross-group-drag-move#D001, quickcommands-cross-group-drag-move#D002 | ✅完成 | | 202604190319 | status-monitor-memory-network-canvas-history | - | - | - | ✅完成 | | 202604190208 | quickcommands-drag-reorder | - | - | - | ✅完成 | @@ -61,6 +65,9 @@ ## 按月归档 ### 2026-04 +- [202604190351_status-monitor-cpu-total-and-per-core](./2026-04/202604190351_status-monitor-cpu-total-and-per-core/) - 将状态监控 CPU 卡片升级为总 CPU canvas 历史图,并新增每核心实时条卡与后端 `cpuCorePercents` 采样链路 +- [202604190358_status-monitor-network-vertical-stack](./2026-04/202604190358_status-monitor-network-vertical-stack/) - 将状态监控网络模块固定改为“上方历史图 + 下方统计表”的纵向堆叠,并把总高度限制在 350px 以内 +- [202604190352_process-manager-table-sort-and-close-spacing](./2026-04/202604190352_process-manager-table-sort-and-close-spacing/) - 为进程管理详细视图补充可点击表头排序,并拉开关闭按钮与刷新区的安全间距 - [202604190322_quickcommands-cross-group-drag-move](./2026-04/202604190322_quickcommands-cross-group-drag-move/) - 支持将快捷指令从一个标签组拖到另一个标签组内,并允许把未标记命令直接拖入目标标签组 - [202604190319_status-monitor-memory-network-canvas-history](./2026-04/202604190319_status-monitor-memory-network-canvas-history/) - 将状态监控中的内存与网络卡片响应式阈值统一收紧到 250px,并把网络卡片的 SVG 趋势线升级为可 hover 查看最近 24 个采样点的 canvas 历史图 - [202604190210_connection-card-default-test-button](./2026-04/202604190210_connection-card-default-test-button/) - 将连接管理页 SSH 连接卡片的默认操作区调整为“连接 / 测试 / 更多”,并移除更多菜单中的重复测试入口 diff --git a/.helloagents/modules/backend.md b/.helloagents/modules/backend.md index f1cd0e4..2accc6f 100644 --- a/.helloagents/modules/backend.md +++ b/.helloagents/modules/backend.md @@ -66,8 +66,8 @@ ### 状态监控 **条件**: 前端工作区通过 WebSocket 订阅服务器状态。 -**行为**: `StatusMonitorService` 通过 SSH 读取 `free`、`df`、`/proc/stat`、`/proc/net/dev`、`date` 与 `/proc/uptime`,同时计算瞬时网速与默认网卡自开机以来的累计上下行字节数,并在常规 `status_update` 中附带服务器时区、运行秒数和轻量级进程摘要(总数、运行中、休眠中、Top 进程预览)。 -**结果**: 前端状态监控既能展示实时资源状态,也能直接展示服务器时区、运行时间和默认进程概览,而无需再为这些基础信息单独请求后端。 +**行为**: `StatusMonitorService` 通过 SSH 读取 `free`、`df`、`/proc/stat`、`/proc/net/dev`、`date` 与 `/proc/uptime`,同时计算瞬时网速与默认网卡自开机以来的累计上下行字节数,并在常规 `status_update` 中附带服务器时区、运行秒数和轻量级进程摘要(总数、运行中、休眠中、Top 进程预览);其中 CPU 采样当前会同时解析 `/proc/stat` 中的总 `cpu` 行与各 `cpuN` 行,为总 CPU 与每核心分别计算差值百分比,并把 `cpuCorePercents` 一并下发。 +**结果**: 前端状态监控既能展示实时资源状态,也能直接展示服务器时区、运行时间、默认进程概览以及每核心 CPU 实时占用,而无需再为这些基础信息单独请求后端。 ## 依赖关系 @@ -78,8 +78,8 @@ ### 状态监控字段扩展 **条件**: `StatusMonitorService` 为前端工作区持续轮询服务器状态。 -**行为**: 当前状态采集链路除 `free`、`df`、`/proc/stat` 与 `/proc/net/dev` 外,还会补充解析 `memFree`、`memCached`、`diskAvailable`、`diskMountPoint`、`diskFsType`、`diskDevice`,并基于 `/proc/diskstats` 计算根设备的磁盘读写速率;CPU 规格信息则会先读取 CPU 型号,再通过 `nproc`、`getconf _NPROCESSORS_ONLN`、`grep -c '^processor' /proc/cpuinfo` 与 `lscpu` 多级回退获取 `cpuCores`;本轮还新增服务器时区、运行时间和默认进程摘要采集。与此同时,`websocket/connection.ts` 新增 `process:list` 与 `process:signal` 消息分发,后端会在当前活动 SSH 会话上下文中执行 `ps` 与 `kill` 指令,返回完整进程列表及结束/强制结束结果。 -**结果**: 前端默认状态监控可以展示更完整的小屏监控信息,而“查看全部”进程管理 modal 也能沿同一 SSH 会话上下文安全复用进程查询与操作能力。 +**行为**: 当前状态采集链路除 `free`、`df`、`/proc/stat` 与 `/proc/net/dev` 外,还会补充解析 `memFree`、`memCached`、`diskAvailable`、`diskMountPoint`、`diskFsType`、`diskDevice`,并基于 `/proc/diskstats` 计算根设备的磁盘读写速率;CPU 规格信息则会先读取 CPU 型号,再通过 `nproc`、`getconf _NPROCESSORS_ONLN`、`grep -c '^processor' /proc/cpuinfo` 与 `lscpu` 多级回退获取 `cpuCores`;CPU 使用率则基于 `/proc/stat` 的总快照和各 `cpuN` 快照做会话级差值计算,空闲时间按 `idle + iowait` 处理,并新增 `cpuCorePercents` 字段供前端展示每核心实时占用;本轮还新增服务器时区、运行时间和默认进程摘要采集。与此同时,`websocket/connection.ts` 新增 `process:list` 与 `process:signal` 消息分发,后端会在当前活动 SSH 会话上下文中执行 `ps` 与 `kill` 指令,返回完整进程列表及结束/强制结束结果。 +**结果**: 前端默认状态监控可以展示更完整的小屏监控信息,包括总 CPU 历史趋势所依赖的总占用与每核心实时条卡所需的数据,而“查看全部”进程管理 modal 也能沿同一 SSH 会话上下文安全复用进程查询与操作能力。 ### 快捷指令顺序持久化 **条件**: 前端快捷指令视图提交分组拖拽、标签内命令拖拽或扁平列表命令拖拽结果。 diff --git a/.helloagents/modules/frontend.md b/.helloagents/modules/frontend.md index da3ef83..eab146d 100644 --- a/.helloagents/modules/frontend.md +++ b/.helloagents/modules/frontend.md @@ -58,8 +58,8 @@ ### 状态监控卡片 **条件**: 用户在 `/workspace` 右侧状态监控面板查看服务器资源状态。 -**行为**: `StatusMonitor.vue` 当前已从通用卡片栅格重排为更接近参考图的窄屏监控结构:顶部改为成对的信息条,资源概览改为带编号的紧凑使用率行,内存/网络/磁盘模块都采用明显的左右分区关系;其中内存卡片现在会在容器宽度大于等于 250px 时维持环形概览与统计块的高密度横向布局,仅在低于 250px 时切为手机式竖排;网络卡片则通过新增 `StatusMonitorNetworkHistoryChart.vue` 把原本的 SVG 趋势线替换为基于 Chart.js `canvas` 的最近 24 个采样点历史图,并在宽度大于等于 250px 时保持“左侧历史图 + 右侧统计表”的横向布局,低于 250px 再切为竖排;磁盘模块继续展示设备视觉块与紧凑磁盘摘要;默认视图底部继续保留“进程管理”概览与高占用进程预览,并通过“查看全部”打开 `ProcessManagerModal.vue`。该 modal 继续采用深色控制台式表格布局,支持搜索 PID / 用户 / 命令、自动刷新、手动刷新,以及对单个进程执行“结束”或“强制结束”操作,并通过当前活动 SSH 会话的 `wsManager` 与后端 `process:list` / `process:signal` 消息交互。 -**结果**: 前端状态监控形成了“更贴近参考图的默认小屏监控 + 独立进程管理页”的双层结构:默认面板不仅保持了侧栏内的高密度布局,还允许用户直接在网络卡片里查看近期网络历史波动,而完整进程管理继续独立存在,不挤占侧栏本体。 +**行为**: `StatusMonitor.vue` 当前已从通用卡片栅格重排为更接近参考图的窄屏监控结构:顶部改为成对的信息条,资源概览改为带编号的紧凑使用率行,内存/网络/磁盘模块都采用明显的左右分区关系;其中内存卡片现在会在容器宽度大于等于 250px 时维持环形概览与统计块的高密度横向布局,仅在低于 250px 时切为手机式竖排;网络卡片则通过新增 `StatusMonitorNetworkHistoryChart.vue` 把原本的 SVG 趋势线替换为基于 Chart.js `canvas` 的最近 24 个采样点历史图,并进一步固定为“上方历史图 + 下方统计表”的纵向堆叠结构,不再切回左右双列,同时通过压缩图表 canvas 高度、网络表间距与统计项 padding,把整个网络模块约束在 `350px` 以内;CPU 卡片也已升级为 `StatusMonitorCpuHistoryChart.vue` 驱动的总 CPU `canvas` 历史图,并在图表旁/下方用紧凑网格展示每个核心的独立实时条卡,在容器宽度低于 250px 时自动退化为单列;磁盘模块继续展示设备视觉块与紧凑磁盘摘要;默认视图底部继续保留“进程管理”概览与高占用进程预览,并通过“查看全部”打开 `ProcessManagerModal.vue`。其中进程统计当前已整体收纳到模块标题区右侧的一组 `monitor-module__pill` 胶囊中,统一展示“总数 / 运行中 / 休眠中”,不再额外保留独立摘要行,以减少默认卡片的纵向占用。该 modal 继续采用深色控制台式表格布局,支持搜索 PID / 用户 / 命令、自动刷新、手动刷新,以及对单个进程执行“结束”或“强制结束”操作,并通过当前活动 SSH 会话的 `wsManager` 与后端 `process:list` / `process:signal` 消息交互;当前还支持点击 `PID / USER / STATE / CPU / MEM / START / COMMAND` 表头做本地升降序排序,并为激活列显示方向标记,同时给右上角关闭按钮预留了独立安全区,避免与刷新区过近。 +**结果**: 前端状态监控形成了“更贴近参考图的默认小屏监控 + 独立进程管理页”的双层结构:默认面板不仅保持了侧栏内的高密度布局,还允许用户直接在网络和 CPU 卡片里查看近期历史波动,并在 CPU 区快速识别负载集中在哪些核心;完整进程管理继续独立存在,不挤占侧栏本体;进入进程管理详细视图后,也能更快按字段维度筛查进程。 ### 快捷指令拖拽排序 **条件**: 用户在 Workbench 的快捷指令视图中浏览分组或扁平命令列表,且当前未启用搜索过滤。 diff --git a/packages/backend/src/services/status-monitor.service.ts b/packages/backend/src/services/status-monitor.service.ts index f49cc79..cb152cc 100644 --- a/packages/backend/src/services/status-monitor.service.ts +++ b/packages/backend/src/services/status-monitor.service.ts @@ -6,6 +6,7 @@ import { settingsService } from '../settings/settings.service'; interface ServerStatus { cpuPercent?: number; cpuCores?: number; + cpuCorePercents?: number[]; memPercent?: number; memUsed?: number; // MB memTotal?: number; // MB @@ -63,6 +64,16 @@ interface DiskIoStats { }; } +interface CpuTimesSnapshot { + total: number; + idle: number; +} + +interface ParsedCpuStatSnapshot { + overall: CpuTimesSnapshot; + perCore: CpuTimesSnapshot[]; +} + const previousNetStats = new Map(); const previousDiskStats = new Map(); const monthMap: Record = { @@ -82,7 +93,7 @@ const monthMap: Record = { export class StatusMonitorService { private clientStates: Map; - private previousCpuStats = new Map(); + private previousCpuStats = new Map(); constructor(clientStates: Map) { this.clientStates = clientStates; @@ -159,30 +170,38 @@ export class StatusMonitorService { try { const procStatOutput = await this.executeSshCommand(sshClient, 'cat /proc/stat'); - const currentCpuTimes = this.parseProcStat(procStatOutput); + const currentCpuSnapshot = this.parseProcStat(procStatOutput); const now = Date.now(); - if (currentCpuTimes) { + if (currentCpuSnapshot) { + if (currentCpuSnapshot.perCore.length > 0) { + status.cpuCores = currentCpuSnapshot.perCore.length; + } + const prevCpuStats = this.previousCpuStats.get(sessionId); if (prevCpuStats && prevCpuStats.timestamp < now) { - const totalDiff = currentCpuTimes.total - prevCpuStats.total; - const idleDiff = currentCpuTimes.idle - prevCpuStats.idle; const timeDiffMs = now - prevCpuStats.timestamp; - if (totalDiff > 0 && timeDiffMs > 100) { - const usageRatio = 1.0 - (idleDiff / totalDiff); - status.cpuPercent = parseFloat((Math.max(0, Math.min(100, usageRatio * 100))).toFixed(1)); + if (timeDiffMs > 100) { + status.cpuPercent = this.calculateCpuPercent(prevCpuStats.overall, currentCpuSnapshot.overall); + status.cpuCorePercents = currentCpuSnapshot.perCore.map((coreSnapshot, index) => { + const previousCore = prevCpuStats.perCore[index]; + return previousCore ? this.calculateCpuPercent(previousCore, coreSnapshot) : 0; + }); } else { - status.cpuPercent = prevCpuStats.total > 0 ? status.cpuPercent : 0; + status.cpuPercent = 0; + status.cpuCorePercents = currentCpuSnapshot.perCore.map(() => 0); } } else { status.cpuPercent = 0; + status.cpuCorePercents = currentCpuSnapshot.perCore.map(() => 0); } - this.previousCpuStats.set(sessionId, { ...currentCpuTimes, timestamp: now }); + this.previousCpuStats.set(sessionId, { ...currentCpuSnapshot, timestamp: now }); } } catch (err) { status.cpuPercent = undefined; + status.cpuCorePercents = undefined; } try { @@ -664,25 +683,57 @@ export class StatusMonitorService { }); } - private parseProcStat(output: string): { total: number; idle: number } | null { + private calculateCpuPercent(previous: CpuTimesSnapshot, current: CpuTimesSnapshot): number { + const totalDiff = current.total - previous.total; + const idleDiff = current.idle - previous.idle; + + if (totalDiff <= 0) { + return 0; + } + + const usageRatio = 1.0 - (idleDiff / totalDiff); + return parseFloat((Math.max(0, Math.min(100, usageRatio * 100))).toFixed(1)); + } + + private parseProcStat(output: string): ParsedCpuStatSnapshot | null { try { - const cpuLine = output.split('\n').find(line => line.startsWith('cpu ')); - if (!cpuLine) { + const cpuLines = output + .split('\n') + .map(line => line.trim()) + .filter(line => /^cpu(?:\d+)?\s+/.test(line)); + + if (cpuLines.length === 0) { return null; } - const fields = cpuLine.trim().split(/\s+/).slice(1).map(Number); - if (fields.length < 4 || fields.slice(0, 4).some(isNaN)) { + let overall: CpuTimesSnapshot | null = null; + const perCore: CpuTimesSnapshot[] = []; + + for (const cpuLine of cpuLines) { + const parts = cpuLine.split(/\s+/); + const cpuLabel = parts[0]; + const fields = parts.slice(1).map(Number); + if (fields.length < 4 || fields.slice(0, 4).some(isNaN)) { + continue; + } + + const snapshot: CpuTimesSnapshot = { + idle: fields[3] + (fields[4] ?? 0), + total: fields.reduce((sum, value) => sum + (isNaN(value) ? 0 : value), 0), + }; + + if (cpuLabel === 'cpu') { + overall = snapshot; + } else { + perCore.push(snapshot); + } + } + + if (!overall) { return null; } - const idle = fields[3]; - const total = fields.reduce((sum, value) => sum + (isNaN(value) ? 0 : value), 0); - if (isNaN(total) || isNaN(idle)) { - return null; - } - - return { total, idle }; + return { overall, perCore }; } catch (e) { return null; } diff --git a/packages/frontend/src/components/ProcessManagerModal.vue b/packages/frontend/src/components/ProcessManagerModal.vue index 34e3b7c..b5ba4bc 100644 --- a/packages/frontend/src/components/ProcessManagerModal.vue +++ b/packages/frontend/src/components/ProcessManagerModal.vue @@ -7,6 +7,9 @@ import { useUiNotificationsStore } from '../stores/uiNotifications.store'; import type { ProcessListItem } from '../types/server.types'; import type { ProcessListResponsePayload, ProcessSignalResponsePayload, WebSocketMessage } from '../types/websocket.types'; +type ProcessSortKey = 'pid' | 'user' | 'state' | 'cpu' | 'mem' | 'startedAt' | 'command'; +type ProcessSortDirection = 'asc' | 'desc'; + const props = defineProps<{ isVisible: boolean; sessionId: string | null; @@ -30,6 +33,8 @@ const runningProcesses = ref(0); const sleepingProcesses = ref(0); const lastUpdatedAt = ref(null); const processError = ref(null); +const sortKey = ref(null); +const sortDirection = ref('asc'); let unregisterListResponse: (() => void) | null = null; let unregisterListError: (() => void) | null = null; @@ -54,6 +59,67 @@ const filteredProcesses = computed(() => { }); }); +const defaultSortDirections: Record = { + pid: 'asc', + user: 'asc', + state: 'asc', + cpu: 'desc', + mem: 'desc', + startedAt: 'desc', + command: 'asc', +}; + +const compareText = (left: string, right: string) => + left.localeCompare(right, undefined, { sensitivity: 'base', numeric: true }); + +const sortedProcesses = computed(() => { + const currentSortKey = sortKey.value; + if (!currentSortKey) { + return filteredProcesses.value; + } + + const directionFactor = sortDirection.value === 'asc' ? 1 : -1; + + return filteredProcesses.value + .map((item, index) => ({ item, index })) + .sort((leftEntry, rightEntry) => { + const left = leftEntry.item; + const right = rightEntry.item; + + let result = 0; + switch (currentSortKey) { + case 'pid': + result = left.pid - right.pid; + break; + case 'cpu': + result = left.cpu - right.cpu; + break; + case 'mem': + result = left.memMb - right.memMb; + break; + case 'user': + result = compareText(left.user, right.user); + break; + case 'state': + result = compareText(left.state, right.state); + break; + case 'startedAt': + result = compareText(left.startedAt, right.startedAt); + break; + case 'command': + result = compareText(left.command, right.command); + break; + } + + if (result !== 0) { + return result * directionFactor; + } + + return leftEntry.index - rightEntry.index; + }) + .map(({ item }) => item); +}); + const formatMemoryMb = (value: number): string => { if (!Number.isFinite(value)) { return t('statusMonitor.notAvailable'); @@ -71,6 +137,38 @@ const lastUpdatedText = computed(() => { return new Date(lastUpdatedAt.value).toLocaleTimeString(); }); +const toggleSort = (key: ProcessSortKey) => { + if (sortKey.value === key) { + sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'; + return; + } + + sortKey.value = key; + sortDirection.value = defaultSortDirections[key]; +}; + +const isSortedBy = (key: ProcessSortKey) => sortKey.value === key; + +const getSortIcon = (key: ProcessSortKey) => { + if (!isSortedBy(key)) { + return 'fas fa-sort'; + } + + return sortDirection.value === 'asc' ? 'fas fa-chevron-up' : 'fas fa-chevron-down'; +}; + +const getSortLabel = (key: ProcessSortKey, label: string) => { + if (!isSortedBy(key)) { + return label; + } + + return `${label} - ${ + sortDirection.value === 'asc' + ? t('common.sortAscending', '升序') + : t('common.sortDescending', '降序') + }`; +}; + const stateTone = (state: string) => { switch (state) { case 'R': @@ -285,25 +383,102 @@ onUnmounted(() => {
-
+
{{ t('statusMonitor.processManager.empty') }}
- - - - - - - + + + + + + + - +
{{ t('statusMonitor.processManager.columns.pid') }}{{ t('statusMonitor.processManager.columns.user') }}{{ t('statusMonitor.processManager.columns.state') }}{{ t('statusMonitor.processManager.columns.cpu') }}{{ t('statusMonitor.processManager.columns.mem') }}{{ t('statusMonitor.processManager.columns.start') }}{{ t('statusMonitor.processManager.columns.command') }} + + + + + + + + + + + + + + {{ t('statusMonitor.processManager.columns.actions') }}
{{ item.pid }} {{ item.user }} @@ -374,9 +549,21 @@ onUnmounted(() => { right: 14px; border: none; background: transparent; + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 10px; color: #cbd5e1; font-size: 18px; cursor: pointer; + transition: background-color 0.2s ease, color 0.2s ease; +} + +.process-modal-close:hover { + background: rgba(255, 255, 255, 0.08); + color: #f8fbff; } .process-modal-toolbar { @@ -384,6 +571,7 @@ onUnmounted(() => { gap: 12px; align-items: center; justify-content: space-between; + padding-right: 52px; } .process-modal-search { @@ -505,6 +693,30 @@ onUnmounted(() => { text-transform: uppercase; } +.process-sort-button { + display: inline-flex; + align-items: center; + gap: 8px; + width: 100%; + border: none; + background: transparent; + padding: 0; + color: inherit; + font: inherit; + letter-spacing: inherit; + text-transform: inherit; + cursor: pointer; +} + +.process-sort-button__icon { + color: rgba(159, 176, 191, 0.56); + font-size: 11px; +} + +.process-sort-button__icon--active { + color: #f8fbff; +} + .process-table__mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } @@ -569,6 +781,7 @@ onUnmounted(() => { .process-modal-toolbar { flex-direction: column; align-items: stretch; + padding-right: 52px; } .process-modal-controls { diff --git a/packages/frontend/src/components/StatusMonitor.vue b/packages/frontend/src/components/StatusMonitor.vue index a50d4dd..d2c8821 100644 --- a/packages/frontend/src/components/StatusMonitor.vue +++ b/packages/frontend/src/components/StatusMonitor.vue @@ -58,30 +58,34 @@
-
+
{{ t('statusMonitor.cpuLabel') }} -
CPU
-
- + {{ displayCpuCores }}
-
-
-
-
- {{ displayCpuCores }} - {{ cpuUsageLane.value }} +
+ + +
+
+
+
+ {{ item.label }} + {{ item.value }} +
+
+ +
-
- -
-
-
+ +
@@ -219,15 +223,15 @@ {{ t('statusMonitor.processManager.title') }}
{{ t('statusMonitor.processManager.subtitle') }}
- - - -
-
- {{ item.label }} - {{ item.value }} +
+
+ + {{ item.label }} {{ item.value }} + +
+
@@ -274,6 +278,7 @@ import { computed, ref, watch, type CSSProperties, type PropType } from 'vue'; import { useI18n } from 'vue-i18n'; import { storeToRefs } from 'pinia'; import ProcessManagerModal from './ProcessManagerModal.vue'; +import StatusMonitorCpuHistoryChart from './StatusMonitorCpuHistoryChart.vue'; import StatusCharts from './StatusCharts.vue'; import StatusMonitorNetworkHistoryChart from './StatusMonitorNetworkHistoryChart.vue'; import { useSessionStore } from '../stores/session.store'; @@ -509,28 +514,29 @@ const systemCardMetaItems = computed(() => [ { key: 'uptime', label: t('statusMonitor.uptimeLabel'), value: uptimeDisplay.value }, ]); -const cpuUsageLane = computed(() => ({ - value: `${Math.round(displayCpuPercent.value)}%`, - percent: displayCpuPercent.value, -})); +const cpuCoreItems = computed(() => { + const rawPercents = currentServerStatus.value?.cpuCorePercents; + const fallbackCoreCount = (() => { + const currentCores = currentServerStatus.value?.cpuCores; + if (typeof currentCores !== 'number' || !Number.isFinite(currentCores) || currentCores <= 0) { + return 0; + } + return Math.round(currentCores); + })(); -const buildSparklinePath = (samples: readonly number[], width: number, height: number, usableHeight: number): string => { - if (samples.length === 0) { - return `M0 ${height - 4} L${width} ${height - 4}`; - } + const normalizedPercents = Array.isArray(rawPercents) && rawPercents.length > 0 + ? rawPercents + : Array.from({ length: fallbackCoreCount }, () => 0); - const step = samples.length > 1 ? width / (samples.length - 1) : width; - - return samples.map((value, index) => { - const x = Number((index * step).toFixed(2)); - const y = Number((height - 4 - (value / 100) * usableHeight).toFixed(2)); - return `${index === 0 ? 'M' : 'L'}${x} ${y}`; - }).join(' '); -}; - -const cpuSparklinePath = computed(() => { - const samples = currentCpuHistory.value.slice(-24).map(value => clampPercent(value ?? 0)); - return buildSparklinePath(samples, 160, 28, 18); + return normalizedPercents.map((percent, index) => { + const clampedPercent = clampPercent(percent); + return { + key: `cpu-core-${index + 1}`, + label: t('statusMonitor.cpuCoreLabel', { index: index + 1 }), + value: `${Math.round(clampedPercent)}%`, + percent: clampedPercent, + }; + }); }); const networkFlowItems = computed(() => [ @@ -864,6 +870,21 @@ const copyIpToClipboard = async (ipAddress: string | null) => { gap: 12px; } +.monitor-module__heading-actions { + display: inline-flex; + flex-wrap: wrap; + justify-content: flex-end; + align-items: flex-start; + gap: 8px; +} + +.process-summary-pills { + display: inline-flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 8px; +} + .monitor-module__title { margin: 6px 0 0; color: #f8fbff; @@ -872,36 +893,6 @@ const copyIpToClipboard = async (ipAddress: string | null) => { line-height: 1.3; } -.cpu-module__hero { - display: grid; - grid-template-columns: auto minmax(96px, 1fr); - align-items: center; - gap: 12px; -} - -.cpu-module__sparkline { - height: 28px; - min-width: 0; - border-top: 1px solid rgba(148, 163, 184, 0.14); - border-bottom: 1px solid rgba(148, 163, 184, 0.1); -} - -.cpu-module__sparkline svg { - display: block; - width: 100%; - height: 100%; -} - -.cpu-module__sparkline-path { - fill: none; - stroke: #7dd3fc; - stroke-width: 2; - stroke-linecap: round; - stroke-linejoin: round; - filter: drop-shadow(0 0 6px rgba(125, 211, 252, 0.28)); -} - -.usage-lane-list, .memory-stat-stack, .network-stat-stack, .disk-stat-stack, @@ -994,6 +985,16 @@ const copyIpToClipboard = async (ipAddress: string | null) => { background: linear-gradient(90deg, #7dd3fc, #2563eb); } +.cpu-core-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); + align-content: start; + gap: 8px; + max-height: 208px; + overflow-y: auto; + padding-right: 2px; +} + .module-split { display: grid; gap: 12px; @@ -1004,11 +1005,22 @@ const copyIpToClipboard = async (ipAddress: string | null) => { align-items: stretch; } -.module-split--network { - grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); +.module-split--cpu { + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); align-items: stretch; } +.module-split--network { + grid-template-columns: 1fr; + align-content: start; + gap: 8px; +} + +.monitor-module--network { + max-height: 350px; + gap: 8px; +} + .memory-ring-panel, .disk-device-card, .disk-io-card, @@ -1126,9 +1138,9 @@ const copyIpToClipboard = async (ipAddress: string | null) => { .network-table { display: grid; - gap: 8px; - height: 100%; - padding: 10px 12px; + gap: 6px; + height: auto; + padding: 8px 10px; } .network-table__header, @@ -1143,17 +1155,21 @@ const copyIpToClipboard = async (ipAddress: string | null) => { } .network-table__header { - padding-bottom: 8px; + padding-bottom: 6px; border-bottom: 1px solid rgba(148, 163, 184, 0.1); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } .network-table__columns { - padding-top: 2px; + padding-top: 0; color: #9cb0c2; font-weight: 700; } +.network-stat-stack { + gap: 6px; +} + .network-table__columns span, .network-stat span, .disk-summary-table__head span, @@ -1171,7 +1187,7 @@ const copyIpToClipboard = async (ipAddress: string | null) => { border-radius: 10px; border: 1px solid rgba(148, 163, 184, 0.06); background: rgba(255, 255, 255, 0.03); - padding: 10px 10px; + padding: 8px 10px; } .network-stat__label { @@ -1189,7 +1205,7 @@ const copyIpToClipboard = async (ipAddress: string | null) => { .network-stat__value, .network-stat__total { color: #f8fbff; - font-size: 14px; + font-size: 13px; font-weight: 800; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } @@ -1337,31 +1353,6 @@ const copyIpToClipboard = async (ipAddress: string | null) => { color: #ffffff; } -.process-summary-strip { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 8px; -} - -.process-summary-item { - padding: 10px; -} - -.process-summary-item__label { - display: block; - font-weight: 700; - letter-spacing: 0.06em; - text-transform: uppercase; -} - -.process-summary-item__value { - display: block; - margin-top: 6px; - color: #f8fbff; - font-size: 20px; - font-weight: 800; -} - .process-preview-item { display: grid; gap: 8px; @@ -1448,6 +1439,7 @@ const copyIpToClipboard = async (ipAddress: string | null) => { @container (max-width: 250px) { .module-split--memory, + .module-split--cpu, .module-split--network, .disk-compact-top { grid-template-columns: 1fr; @@ -1457,6 +1449,10 @@ const copyIpToClipboard = async (ipAddress: string | null) => { grid-template-columns: 1fr; } + .cpu-core-grid { + grid-template-columns: 1fr; + } + .network-table__header, .network-table__columns, .network-stat, @@ -1487,9 +1483,6 @@ const copyIpToClipboard = async (ipAddress: string | null) => { white-space: normal; } - .cpu-module__hero { - grid-template-columns: 1fr; - } } @container (max-width: 360px) { diff --git a/packages/frontend/src/components/StatusMonitorCpuHistoryChart.vue b/packages/frontend/src/components/StatusMonitorCpuHistoryChart.vue new file mode 100644 index 0000000..e5116b5 --- /dev/null +++ b/packages/frontend/src/components/StatusMonitorCpuHistoryChart.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/packages/frontend/src/components/StatusMonitorNetworkHistoryChart.vue b/packages/frontend/src/components/StatusMonitorNetworkHistoryChart.vue index 7f8d585..e32f48b 100644 --- a/packages/frontend/src/components/StatusMonitorNetworkHistoryChart.vue +++ b/packages/frontend/src/components/StatusMonitorNetworkHistoryChart.vue @@ -259,22 +259,22 @@ onBeforeUnmount(() => {