feat(rdp): 新增 RDP 远程桌面/SMB/账单同步能力 + 清理编译产物

主要变更:
- RDP 远程桌面:新增 backend/pkg/rdp(RDP 引擎 + credssp/display/rle/trace)、
  backend/controller/rdp*(连接/凭证/文件/会话/实时)、frontend RDP 组件与 API
- Windows SMB 文件传输:backend/pkg/winsmb
- 云账单同步:backend/pkg/billingsync + billing_sync/aliyun_billing,前端账单详情
- Agent CLI Kubernetes 工具:kubernetes_action/get、pod_execute/logs
- 服务器导入助手:server_import_helpers、ImportCredentialSettings
- 国际化与组件重构(cloud/deploy/monitor/server/system 等多模块)
- 清理编译产物二进制:移除 backend/server、backend/agent/bin/aiops-agent-*
- .gitignore:新增 backend/agent/ 与 .cache/(避免 334MB go-build 缓存误入库)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
LoganandClaude committed 2026-07-04 01:50:36 +08:00
1 parent 777d9e4e71
commit 76ec9387f0
202 files changed
+16555 -2543

No files matched your search

+2
View File
@@ -13,6 +13,7 @@
| [API 接口](./api-reference.md) | 后端 API 接口文档 |
| [数据库设计](./database-schema.md) | 数据模型和表结构 |
| [开发日志](./dev-log.md) | 详细开发日志(按日期记录) |
| [Web RDP 远程桌面](./rdp.md) | 自研 RDP 协议栈、WebSocket 帧协议(归档:`/opt/rdp/`) |
| [Redis 缓存策略](./redis-caching-strategy.md) | Redis 缓存使用现状与优化方案 |
## 设计文档
@@ -45,6 +46,7 @@
| 日志告警 | AlertEngine 支持 `type=log` 规则,查询 Loki / ES 触发/恢复 |
| 远程执行 | 新增远程执行页(执行/上传),移除 ansible 命名 |
| 大模型双协议 | OpenAI 兼容 + Anthropic Messages 协议接入 |
| Web RDP 自研引擎 | 移除 grdp,SessionEngine 完整图形会话,见 [rdp.md](./rdp.md) |
## 部署速查
+51
View File
@@ -0,0 +1,51 @@
# 前端视觉与页面规范
本文档约定 AIOps 控制台的页面结构、视觉基础和复用组件。新增页面优先使用这些规范,已有页面逐步迁移。
## 设计目标
- 页面打开即用,避免营销式大标题和多余说明。
- 信息密度适中,优先支持扫描、筛选、对比和批量操作。
- 主题感知,颜色优先使用 Tailwind 语义色和 `var(--dg-*)` 变量。
- 同类页面保持一致:列表、详情、弹窗、空态、加载态和错误态不重复造轮子。
## 页面结构
标准业务页使用 `PageShell`:
1. `PageHeader`:标题、简短描述、主操作按钮。
2. `FilterBar`:搜索、筛选、刷新、批量操作。
3. 主内容区:表格、图表、详情或编辑器。
4. `PageSection`:页面内分组区域。
示例:
```tsx
<PageShell>
<PageHeader title="服务器" description="管理主机资产和 Agent 状态" actions={...} />
<FilterBar left={searchInput} right={refreshButton} />
<PageSection>{table}</PageSection>
</PageShell>
```
## 状态与数据展示
- 状态统一使用 `StatusBadge`,不要在页面中临时拼颜色。
- 指标卡片使用 `MetricCard`,用于概览数值、趋势和健康状态。
- 空态优先使用 `EmptyState`,需要明确下一步动作时传入 `action`。
常用状态:`success`、`warning`、`danger`、`info`、`neutral`、`running`、`offline`。
## 交互规范
- 删除、重置、停用等高危操作必须使用确认弹窗,不使用 `window.confirm()`。
- Dialog 必须包含 `DialogDescription`。
- 列表页操作按钮文案保持 2-4 字,图标按钮需要 tooltip。
- 加载中显示 skeleton 或 spinner,不能让页面无反馈。
## 代码约定
- 通用页面组件放在 `frontend/src/components/common/`。
- 基础原子组件继续放在 `frontend/src/components/ui/`。
- 页面组件使用 `PascalCase.tsx`,工具函数使用 `camelCase.ts`。
- 新增颜色进入主题变量,不在业务组件硬编码十六进制颜色。
+38
View File
@@ -1,5 +1,43 @@
# AIOps 开发日志
## 2026-07-03 Web RDP 自研图形引擎(移除 grdp)
### 完成项
| 项目 | 状态 | 说明 |
|------|------|------|
| 自研 SessionEngine | ✅ | `NewEngine()` → `SessionEngine`,完整 MCS + 位图 + 键鼠 |
| 移除 nakagami/grdp | ✅ | `go.mod` 无 grdp;`Dockerfile` 去掉 `third_party/` |
| 位图解压 | ✅ | `rle/` RLE + `mppc.go` MPPC + `bitmap_decode.go` PNG 编码 |
| FastPath 收发 | ✅ | `packet_io.go`、`fastpath_recv.go`、`input_fastpath.go` |
| 会话激活 | ✅ | FontMap 后 `SuppressOutput(ALLOW_DISPLAY_UPDATES)` |
| 文件传输 | ✅ | SMB 直传 + `rdpfilestage` 暂存 + `auto_exec` 回退 |
| 第三方备份 | ✅ | `/opt/rdp/third-party/`(grdp v0.8.7 + 参考实现) |
| 文档 | ✅ | `docs/rdp.md` + `/opt/rdp/CHANGELOG.md` |
### 新增 / 变更文件(`backend/pkg/rdp/`)
- `session_engine.go`、`session_connect.go`、`packet_io.go`
- `fastpath_recv.go`、`bitmap_decode.go`、`input_fastpath.go`、`mppc.go`
- `rle/`(`util.go`、`bpp1~4.go`、`entry.go`)
- `engine.go`:`NewEngine()` 指向 `SessionEngine`
- `transport/tpkt.go`:新增 `WriteRaw`
### 编译与部署
```bash
cd backend && CGO_ENABLED=1 go build .
./run.sh docker up # 已验证镜像构建通过
```
### 实测备注
- CredSSP/NLA 对 `192.168.14.172:3389` 稳定通过(~7ms)
- MCS 握手在频繁探测时偶发 `connection reset`(目标机限流/策略)
- 详细协议说明见 [docs/rdp.md](./rdp.md)
---
## 2026-07-01 文档同步 + PageShell 设计系统
### 完成项
+2 -2
View File
@@ -226,7 +226,7 @@ const { list, total } = await getServers({ page: 1 });
```bash
cd backend
go vet ./...
go build -o aiops-server .
go build -o aiops .
```
### 前端构建测试
@@ -262,4 +262,4 @@ python3 tests/integration_test.py --host localhost --port 8080
```go
log.Info("server started", zap.String("port", cfg.Port))
log.Error("database error", zap.Error(err))
```
```
+234
View File
@@ -0,0 +1,234 @@
# AIOps Web RDP 远程桌面
> 作者:Logan.Li
> 更新:2026-07-03
> 归档副本:`/opt/rdp/docs/rdp.md` · 变更记录:`/opt/rdp/CHANGELOG.md`
---
## 概述
AIOps 提供浏览器内 Windows 远程桌面(Canvas 渲染 + WebSocket)。当前已移除第三方 `grdp` 兜底实现,后端固定使用 AIOps 自研 RDP 协议栈。// Logan.Li
| 环境变量 | 行为 |
|----------|------|
| `RDP_ENGINE` | 已不再参与引擎选择,保留环境变量不会改变行为 |
| `RDP_DEBUG=1` | 打开 `[RDP:TRACE]` 协议级日志 |
| 层级 | 状态 | 路径 |
|------|------|------|
| X.224 / TPKT / TLS | ✅ 自研灰度 | `backend/pkg/rdp/transport/` |
| CredSSP / NTLMv2 (NLA) | ✅ 自研灰度 | `backend/pkg/rdp/credssp/` |
| MCS / GCC / 许可证 / 激活 | ✅ 自研灰度 | `backend/pkg/rdp/mcs_probe.go`、`session_connect.go` |
| FastPath 位图 / MPPC / RLE | ✅ 自研灰度 | `bitmap_decode.go`、`fastpath_recv.go`、`mppc.go`、`rle/` |
| 键鼠 / 剪贴板 / auto_exec | ✅ 自研灰度 | `input_fastpath.go` |
| 文件传输 (SMB + 暂存回退) | ✅ 自研 | `pkg/winsmb/`、`pkg/rdpfilestage/` |
| 前端 Canvas + 工具面板 | ✅ 自研 | `RdpDialog.tsx`、`RdpToolsPanel.tsx` |
自研协议栈不依赖第三方 RDP 实现,也不直接依赖控制器、数据库或 WebSocket。已删除的第三方目录备份在 `/opt/rdp/third-party/grdp-aiops-backup-20260703`。// Logan.Li
---
## 2026-07-03 变更记录
### 自研 RDP 引擎
| 变更项 | 说明 |
|--------|------|
| 引擎入口 | `NewEngine()` 固定返回 `SessionEngine` |
| 兜底依赖 | 已移除 `github.com/nakagami/grdp` 和 `backend/third_party` |
| Dockerfile | 不再复制 third_party 模块 |
| 新增模块 | `session_engine.go`、`session_connect.go`、`packet_io.go`、`fastpath_recv.go`、`bitmap_decode.go`、`input_fastpath.go`、`mppc.go`、`rle/` |
| 自研会话激活 | `ConfirmActive` 后可选读 FontMap,发送 `SuppressOutput(ALLOW_DISPLAY_UPDATES)` 触发服务端推帧 |
| 自研位图输出 | FastPath / SlowPath Bitmap + SurfaceCmd → PNG base64 → WebSocket `type:bitmap` |
| 自研输入通道 | Fast-Path Input:扫描码键鼠、Unicode 剪贴板、`auto_exec`(Win+R 粘贴命令) |
| 协议日志 | `RDP_DEBUG=1` 输出 `[RDP:TRACE]`,定位连接、bitmap、input 阶段 |
### 仍保留的探测 API(诊断用)
| 函数 | 用途 |
|------|------|
| `ProbeHandshake` | X.224 安全协议协商 |
| `ProbeCredSSP` | NLA 凭据验证 |
| `ProbeMCSConnect` | MCS/GCC 握手探测(读完即关连接) |
| `ProbeSessionFrames` | 建立会话后统计位图矩形数量 |
### 已知限制 / 待完善
- `mcs_probe.go` 超 700 行红线,后续需按 MCS/GCC/PDU 拆文件
- 未实现 cliprdr 虚拟通道双向同步、RDPGFX/H.264、动态虚拟通道
- 部分 Windows 主机在频繁探测后可能 `connection reset`(目标机策略/限流,非 NLA 失败)
---
## 架构与调用链
```
RdpDialog.tsx
│ WebSocket /ws/rdp?id=&width=&height=&token=
▼
controller/rdp.go
│ loadRDPCredential() → engine.Run()
▼
pkg/rdp/engine.go → SessionEngine
├── session_engine.go readLoop + handleEvent
├── session_connect.go OpenSession (CredSSP + MCS + SuppressOutput)
├── packet_io.go TPKT / FastPath 读写
├── fastpath_recv.go 服务端更新解析
├── bitmap_decode.go RLE/翻转 → PNG
├── input_fastpath.go 键鼠 / 剪贴板 / auto_exec
├── mppc.go MPPC 解压
└── rle/ 位图 RLE 解压算法
```
---
## 后端文件清单
### 协议栈 `backend/pkg/rdp/`
| 文件 | 说明 |
|------|------|
| `engine.go` | `Engine` 接口、`RDP_ENGINE` 引擎选择 |
| `client.go` | `ProbeHandshake` / `ProbeTLSHandshake` / `ProbeCredSSP` |
| `session_engine.go` | 图形会话主循环、帧推送、输入分发 |
| `session_connect.go` | `OpenSession`、SuppressOutput 激活 |
| `mcs_probe.go` | MCS Connect、GCC、ClientInfo、ConfirmActive 构建与探测 |
| `packet_io.go` | `ReadWirePacket`、`WriteFastPathInput` |
| `fastpath_recv.go` | FastPath 更新、MPPC、SurfaceCmd 解析 |
| `bitmap_decode.go` | 位图解码与 PNG 编码 |
| `input_fastpath.go` | Fast-Path Input 事件编码 |
| `mppc.go` | MPPC-64K 解压器 |
| `keymap.go` | 浏览器 `KeyboardEvent.code` → RDP 扫描码 |
| `transport/` | TPKT、X.224、TLS |
| `credssp/` | CredSSP + NTLMv2 + ASN.1 |
| `rle/` | RLE 位图解压(`DecompressInto`) |
### 控制器 / 模型
| 文件 | 说明 |
|------|------|
| `controller/rdp.go` | WebSocket 会话、审计录制 |
| `controller/rdp_session.go` | 会话列表 / 回放 / 终止 API |
| `controller/rdp_file.go` | 文件 list/upload/download/stage |
| `controller/rdp_cred.go` | 凭据加载辅助 |
| `model/rdp_session.go` | `RDPSession` 表 |
### 文件传输
| 文件 | 说明 |
|------|------|
| `pkg/winsmb/` | SMB 管理共享直传 `C:\Users\Public\AIOpsShare\Download` |
| `pkg/rdpfilestage/` | 服务端暂存 + 一次性 token |
| `frontend/.../RdpToolsPanel.tsx` | 工具面板(通用命令 / 文件 Tab) |
| `frontend/.../rdpConstants.ts` | 虚拟 Download 盘路径常量 |
---
## 前端
| 文件 | 说明 |
|------|------|
| `RdpDialog.tsx` | Canvas 渲染、WebSocket、键鼠捕获、工具栏 |
| `RdpToolsPanel.tsx` | 剪贴板同步、文件上传下载、auto_exec 回退 |
| `api/server/rdp-files.ts` | 文件传输 REST |
| `api/audit/rdp.ts` | 会话审计 API |
---
## WebSocket 帧协议
### 服务端 → 浏览器
```json
{ "type": "session", "message": "123", "width": 1280, "height": 800 }
{ "type": "status", "message": "正在连接 RDP ..." }
{ "type": "error", "message": "..." }
{ "type": "bitmap", "x": 0, "y": 0, "width": 64, "height": 64, "image": "data:image/png;base64,..." }
{ "type": "clipboard","text": "远端剪贴板内容" }
```
### 浏览器 → 服务端
```json
{ "type": "mouse_move", "x": 100, "y": 200 }
{ "type": "mouse_down", "x": 100, "y": 200, "button": 0 }
{ "type": "mouse_up", "x": 100, "y": 200, "button": 0 }
{ "type": "wheel", "x": 100, "y": 200, "delta_y": -120 }
{ "type": "key", "code": "KeyA", "key": "a", "down": true }
{ "type": "clipboard", "text": "本地剪贴板" }
{ "type": "auto_exec", "text": "C:\\Users\\Public\\AIOpsShare\\Download\\setup.exe" }
```
---
## CredSSP 多策略重试
每条策略使用**新建 TCP 连接**(`client.go`):
1. `v6 + binding`(默认)
2. `v6 + binding + UseCBT`
3. `v6 + binding + ForceMIC`
4. `v2 + raw`(兼容旧服务端)
关键修复:`credssp.subjectPublicKey()` 使用证书 `SubjectPublicKey` 原始 ASN.1,而非 PKIX SPKI。
---
## 外部项目借鉴边界
| 项目 | 可借鉴点 | 不直接采用原因 |
|------|----------|----------------|
| FreeRDP | 协议完整性、通道、编解码、客户端能力矩阵 | C 生态和体量过大,不适合直接嵌入 |
| Apache Guacamole | Web 网关、协议代理、录制与前端桥接分层 | 依赖 guacd/C,不符合单 Go 后端目标 |
| IronRDP | 现代模块化、状态机测试、安全边界 | Rust 生态,适合作为测试和分层参考 |
| Teleport | 身份、RBAC、审计、Desktop Gateway 架构 | 产品目标偏堡垒机,不是轻量 RDP client |
| tomatome/grdp / nakagami/grdp | Go 协议状态机、能力集、bitmap/input 参考 | 第三方实现已移除,仅保留 `/opt` 备份用于协议对照 |
## 第三方代码归档与文档
归档与变更记录目录:
```
/opt/rdp/
├── CHANGELOG.md # 变更记录
├── README.md # 索引
├── docs/rdp.md # 完整说明(与本文档同步)
└── third-party/
├── nakagami-grdp-v0.8.7/
├── rdp-web-refs/
└── grdpprobe/
```
> 第三方源码只能作为协议行为和工程分层参考;涉及 GPL 的项目不得直接搬运实现。// Logan.Li
---
## 验证命令
```bash
# 编译
cd backend && CGO_ENABLED=1 go build -o aiops .
# NLA 探测
go run /tmp/rdp_cred_probe.go # ProbeCredSSP
# 会话位图统计(需可达的 Windows:3389)
go run /tmp/rdp_session_probe_main.go
# Docker 端到端
./run.sh docker up
# 浏览器:服务器管理 → Windows 主机 → RDP 连接
```
---
## 相关路由
| 路径 | 说明 |
|------|------|
| `GET /ws/rdp` | WebSocket 图形会话 |
| `GET /api/v1/rdp-sessions` | 会话审计列表 |
| `GET /api/v1/servers/:id/rdp-files` | 远端文件列表 |
| `POST /api/v1/servers/:id/rdp-files/upload` | 上传 |
| `GET /api/v1/servers/:id/rdp-files/download` | 下载 |
+524
View File
@@ -0,0 +1,524 @@
# AIOps 终端与远程桌面设计说明
> 更新:2026-07-03
> 范围:SSH WebShell、Windows RDP、Kubernetes Pod exec、kubectl 集群终端、流水线 Workspace 终端、审计实时查看与历史回放。
> 本文用于记录当前实现、设计取舍与后续重构方向。// Logan.Li
---
## 1. 总体目标
AIOps 的终端体系不是单一功能,而是一组共享基础能力:
1. **实时交互**:浏览器通过 WebSocket 与后端建立交互式会话。
2. **尺寸自适应**:前端终端容器变化后执行 `fit()`,并同步 `cols/rows` 到远端 PTY。
3. **审计留痕**:会话起止、用户、资产、输入动作、命令、输出大小、录制数据可追溯。
4. **历史回放**:SSH 终端使用文本流回放,RDP 使用 Canvas 图形帧回放。
5. **统一体验**:SSH、Pod、kubectl、Workspace 终端共享 xterm.js 适配策略;RDP 使用 Canvas 独立渲染策略。
核心原则:
- 终端输入只走实时链路,审计只记录有价值的离散事件。
- `mousemove` 等高频低价值事件不持久化。
- RDP 历史回放采用节流图形帧,不保存每个实时帧,避免存储爆炸。
- 前端终端窗口不手写分散 resize 逻辑,统一走 `frontend/src/lib/xterm-utils.ts`。// Logan.Li
---
## 2. 统一链路
### 2.1 SSH / Pod / kubectl / Workspace
```
Browser xterm.js
├─ onData → WebSocket JSON {status:0,data}
├─ ResizeObserver + FitAddon
└─ resize → WebSocket JSON {status:1,cols,rows}
│
▼
Backend WebSocket Handler
├─ SSH: ssh.Session + PTY
├─ Pod exec: Kubernetes channel.k8s.io WebSocket
├─ kubectl: local kubectl process + PTY
└─ Workspace: pipeline workspace shell + PTY
│
▼
Audit / Recording
├─ terminal session metadata
├─ asciicast text stream
└─ command records
```
### 2.2 Windows RDP
```
Browser Canvas
├─ mouse/key/clipboard input → WebSocket JSON
└─ bitmap frames ← WebSocket binary RDPR
│
▼
controller/rdp.go
├─ load RDP credential
├─ create RDPSession
├─ start Engine.Run()
├─ broadcast live audit frames
└─ throttle snapshot frames for replay
│
▼
pkg/rdp Engine
└─ AIOps RDP protocol engine
```
RDP 与 xterm 终端不同:它没有 PTY 的 `cols/rows` 概念,主要优化点是输入事件合并、图形帧编码、Canvas 绘制和录屏抽样。// Logan.Li
---
## 3. 前端终端窗口适配
公共工具:`frontend/src/lib/xterm-utils.ts`
| 方法 | 作用 |
|------|------|
| `fitTerminal()` | 调用 FitAddon,并在 WebSocket 打开时发送 `{status:1,cols,rows}` |
| `mountXterm()` | 等容器渲染完成后挂载 xterm,延迟多次 fit,减少右侧黑边 |
| `attachTerminalResize()` | 通过 ResizeObserver 监听容器尺寸,节流 fit 和 resize 同步 |
已接入或应接入的组件:
| 组件 | 类型 | 说明 |
|------|------|------|
| `TerminalDialog.tsx` | SSH 单会话终端 | 资产列表打开 SSH |
| `WebShell.tsx` | SSH 多标签终端 | WebShell 页面 |
| `PodExecTerminal.tsx` | Pod exec | K8s Pod 容器终端 |
| `KubectlTerminal.tsx` | kubectl | 集群级 kubectl shell |
| `WorkspaceTermDialog.tsx` | Workspace shell | 流水线构建目录终端 |
| `LiveMonitorDialog.tsx` | SSH 实时审计 | 只读 xterm |
| `ReplayDialog.tsx` | SSH 历史回放 | asciicast 回放 |
终端容器统一使用:
```tsx
<div ref={containerRef} className="xterm-terminal-host" />
```
全局样式位置:`frontend/src/index.css`
```css
.xterm-terminal-host {
width: 100%;
overflow: hidden;
background: #0a0e14;
}
.xterm-terminal-host .xterm {
height: 100%;
}
```
注意事项:
- Dialog 动画会影响 xterm 首次测量,因此终端弹框禁用过渡。
- `fit()` 后必须同步后端 PTY 尺寸,否则只是前端铺满,远端程序仍按旧尺寸输出。
- `term.open()` 只能对同一个 xterm 实例执行一次,复用场景需要检查 `term.element`。// Logan.Li
---
## 4. SSH 终端实现
主要文件:
| 文件 | 说明 |
|------|------|
| `backend/controller/webshell.go` | SSH WebSocket、PTY、录制、命令审计 |
| `backend/model/terminal_session.go` | SSH 会话模型 |
| `backend/model/session_command.go` | 命令审计模型 |
| `frontend/src/components/server/TerminalDialog.tsx` | 单资产 SSH 终端 |
| `frontend/src/components/server/WebShell.tsx` | 多标签 SSH 终端 |
协议:
```json
{ "status": 0, "data": "ls -la\n" }
{ "status": 1, "cols": 120, "rows": 34 }
```
审计策略:
- 会话记录:开始时间、结束时间、用户、资产、状态、录制大小。
- 录制数据:asciicast 文本流。
- 命令记录:按回车提交后提取命令,不记录每个按键。
- 输出统计:下一条命令前累计 stdout 字节数写入上一条命令。
优化方向:
- 命令列表按会话聚合展示,减少碎片行。
- 对连续输入、粘贴、多行脚本增加摘要视图。
- 高危命令识别只用于展示和告警,不阻塞正常录制。// Logan.Li
---
## 5. Kubernetes Pod Exec
主要文件:
| 文件 | 说明 |
|------|------|
| `backend/controller/k8s_pod.go` | Pod exec WebSocket 代理 |
| `frontend/src/components/kubernetes/PodExecTerminal.tsx` | Pod 终端 UI |
链路:
```
xterm.js
└─ WebSocket JSON
└─ backend
└─ Kubernetes API WebSocket channel.k8s.io
```
K8s channel 映射:
| Channel | 方向 | 说明 |
|---------|------|------|
| 0 | client → pod | stdin |
| 1 | pod → client | stdout |
| 2 | pod → client | stderr |
| 3 | pod → client | error/status |
| 4 | client → pod | resize |
前端 resize JSON:
```json
{ "status": 1, "cols": 120, "rows": 34 }
```
后端转换为 K8s resize frame:
```json
{ "Width": 120, "Height": 34 }
```
注意事项:
- Pod exec 不应进入 SSH 命令审计表,除非未来设计 K8s 独立审计模型。
- 容器切换、shell 切换会重连 WebSocket。
- ESC 必须传给终端,Dialog 不能用 ESC 自动关闭。// Logan.Li
---
## 6. kubectl 集群终端
主要文件:
| 文件 | 说明 |
|------|------|
| `backend/controller/kubectl_shell.go` | kubectl shell WebSocket |
| `frontend/src/components/kubernetes/KubectlTerminal.tsx` | kubectl 终端 UI |
设计目标:
- 给运维提供集群级 kubectl shell。
- 使用与 SSH 相同的 xterm 自适应策略。
- 后端负责 kubeconfig/集群上下文隔离。
风险点:
- kubectl shell 权限大,必须依赖 RBAC、审计和操作权限控制。
- 后续应对危险命令做二次确认或审计标记。
- 多集群切换时不能复用旧进程上下文。// Logan.Li
---
## 7. Workspace 终端
主要文件:
| 文件 | 说明 |
|------|------|
| `backend/controller/workspace_files.go` | 流水线工作区文件/终端入口 |
| `frontend/src/components/deploy/WorkspaceTermDialog.tsx` | 工作区终端 UI |
用途:
- 进入流水线构建目录查看文件。
- 辅助排查构建产物、日志、临时文件。
设计约束:
- 默认目录必须限制在 pipeline workspace 内。
- 后端不能允许用户通过路径逃逸进入任意系统目录。
- 终端尺寸适配与 SSH 共用 `xterm-utils`。// Logan.Li
---
## 8. RDP 实现
主要文件:
| 文件 | 说明 |
|------|------|
| `backend/controller/rdp.go` | RDP WebSocket、会话、审计、实时帧推送 |
| `backend/controller/rdp_live.go` | RDP 实时审计广播 |
| `backend/controller/rdp_session.go` | RDP 会话列表、录制、终止 API |
| `backend/pkg/rdp/engine.go` | 引擎选择 |
| `backend/pkg/rdp/session_engine.go` | 自研 RDP 引擎主循环 |
| `frontend/src/components/server/RdpDialog.tsx` | RDP 实时操作窗口 |
| `frontend/src/components/system/RdpAuditViewer.tsx` | RDP 实时审计与历史回放 |
### 8.1 自研 RDP 引擎策略
当前后端固定使用自研 `SessionEngine`,不再通过 `RDP_ENGINE` 切换第三方兜底。已删除的 grdp 目录备份到:
```text
/opt/rdp/third-party/grdp-aiops-backup-20260703
```
仓内 `backend/third_party` 已清理,Dockerfile 不再复制本地 replace 模块。// Logan.Li
### 8.2 WebSocket 帧协议
服务端到浏览器:
```json
{ "type": "session", "message": "123", "width": 1024, "height": 800 }
{ "type": "status", "message": "正在连接 RDP ..." }
{ "type": "error", "message": "..." }
```
图像帧优先走二进制:
```text
RDPR + x:uint32 + y:uint32 + width:uint32 + height:uint32 + rgba:[]
```
历史回放帧走节流 PNG:
```json
{ "type": "bitmap", "x": 0, "y": 0, "width": 300, "height": 200, "image": "data:image/png;base64,..." }
```
浏览器到服务端:
```json
{ "type": "mouse_move", "x": 100, "y": 200 }
{ "type": "mouse_down", "x": 100, "y": 200, "button": 0 }
{ "type": "mouse_up", "x": 100, "y": 200, "button": 0 }
{ "type": "wheel", "x": 100, "y": 200, "delta_y": -120 }
{ "type": "key", "code": "KeyA", "key": "a", "down": true }
{ "type": "clipboard", "text": "..." }
```
### 8.3 输入优化
- 前端 `mousemove` 16ms 合并,只保留最新坐标。
- `mouse_down`、`mouse_up` 前先 flush 最新坐标,保证拖拽起止准确。
- 后端输入队列满时丢弃移动事件,关键事件优先。
- RDP 引擎处理输入时不打印每个 `mousemove`,避免日志拖慢实时操作。
### 8.4 图形优化
- 实时图形帧使用 RGBA 二进制直传,历史回放使用节流 PNG 快照。
- 前端 Canvas 使用 `putImageData()` 绘制二进制帧。
- 右下角帧统计节流刷新,避免每帧 React 重渲染。
- RDP 录屏只保存节流快照,不保存每个实时帧。
### 8.5 审计策略
持久化:
- 会话元数据:用户、资产、开始/结束时间、状态、录制大小。
- 离散操作:点击、滚轮、剪贴板、文件上传下载、输入摘要。
- 历史回放:节流图形快照。
不持久化:
- 高频 `mousemove`。
- 每个实时 RGBA 帧。
- 无业务含义的连续坐标变化。
拖拽建议记录:
```text
[RDP拖拽] button=left start=(x1,y1) end=(x2,y2) duration=850ms
```
普通点击建议记录:
```text
[RDP点击] button=left x=100 y=200
```
键盘输入建议合并:
```text
[RDP输入] abc*** # 敏感字段后续可脱敏
```
---
## 9. 审计页设计
当前菜单:`/audit/terminal`
建议子页签:
| 页签 | 内容 |
|------|------|
| 在线会话 | SSH 在线会话 + RDP 在线会话 |
| 历史会话 | SSH 历史 asciicast 回放 + RDP Canvas 回放 |
| 会话命令 | SSH 命令 + RDP 操作摘要,按会话聚合展示 |
### 9.1 在线会话
SSH:
- 使用 `LiveMonitorDialog`
- 后端 `/ws/audit/:sessionId`
- 只读 xterm
RDP:
- 使用 `RdpAuditViewer mode="live"`
- 后端 `/ws/rdp-audit/:sessionId`
- 只读 Canvas
### 9.2 历史会话
SSH:
- 使用 `ReplayDialog`
- 数据源:`terminalSessionApi.getRecord()`
- 格式:asciicast 文本
RDP:
- 使用 `RdpAuditViewer mode="replay"`
- 数据源:`rdpSessionApi.getRecord()`
- 格式:节流 PNG bitmap JSON
### 9.3 会话命令 / 操作摘要
问题:
- SSH 命令记录适合逐条展示。
- RDP 如果按键、点击、剪贴板都逐条展示,会出现大量低价值行。
建议:
1. 后端写入时尽量合并:
- 连续键盘输入合并为 `[RDP输入] ...`
- 拖拽只记录开始/结束。
- 点击和滚轮按短时间窗口合并。
2. 前端展示时按会话聚合:
- 默认展示会话摘要。
- 展开后看详细事件。
- 高危动作、文件传输、剪贴板单独标记。
---
## 10. 当前架构质量评估
### 10.1 做得好的地方
- `backend/pkg/rdp/domain` 定义了引擎接口,控制器不直接依赖协议细节。
- `transport/`、`credssp/`、`rle/`、`display/` 已经有基础分层。
- RDP 依赖已收敛到自研协议栈,构建不再需要 `backend/third_party`。
- xterm 终端已抽出公共适配工具,后续维护成本低。
- RDP 实时帧和历史回放帧开始分离,避免审计存储被实时帧撑爆。
### 10.2 需要优化的地方
| 文件 | 问题 | 建议 |
|------|------|------|
| `backend/pkg/rdp/mcs_probe.go` | 1664 行,严重超过红线 | 拆成 `mcs_connect.go`、`gcc_caps.go`、`confirm_active.go`、`client_info.go`、`probe.go` |
| `backend/controller/rdp.go` | 约 460 行,承担会话、审计、帧编码、快照 | 拆出 `rdp_sink.go`、`rdp_audit.go`、`rdp_input_audit.go` |
| `RdpDialog.tsx` | 实时渲染、输入、工具面板入口集中 | 后续拆出 `useRdpSocket()`、`useRdpCanvas()`、`useRdpInput()` |
| `PodExecTerminal.tsx` | 连接、UI、resize 混合 | 可抽 `useXtermWebSocket()` |
| `KubectlTerminal.tsx` | 与 Pod exec 重复 | 与 Pod/Workspace 共用 hook |
### 10.3 建议目标结构
后端 RDP:
```text
backend/pkg/rdp/
├── domain/
├── engine.go
├── session_engine.go
├── input/
│ ├── fastpath.go
│ └── keymap.go
├── graphics/
│ ├── framebuffer.go
│ ├── codec.go
│ └── rle/
├── mcs/
│ ├── connect.go
│ ├── gcc.go
│ ├── caps.go
│ └── active.go
├── credssp/
└── transport/
```
控制器:
```text
backend/controller/
├── rdp.go # 路由入口和会话编排
├── rdp_sink.go # WebSocket frame sink
├── rdp_live.go # 实时审计广播
├── rdp_input_audit.go # 点击/拖拽/输入摘要
├── rdp_session.go # 会话 REST API
├── rdp_file.go
└── rdp_cred.go
```
前端:
```text
frontend/src/components/terminal/
├── useXtermSocket.ts
├── XtermDialogShell.tsx
└── terminalTheme.ts
frontend/src/components/rdp/
├── useRdpSocket.ts
├── useRdpCanvas.ts
├── useRdpInput.ts
├── RdpDialog.tsx
└── RdpAuditViewer.tsx
```
---
## 11. 验证清单
终端类修改后建议验证:
```bash
cd frontend
npx tsc --noEmit
npm run build
```
后端修改后建议验证:
```bash
cd backend
go build -o aiops .
```
端到端:
```bash
./run.sh docker up
python3 tests/integration_test.py --host localhost --port 8080
```
手动检查:
- SSH 终端打开、缩放、全屏、输入、回放。
- Pod exec 打开、切 shell、缩放、全屏。
- kubectl 终端打开、缩放、全屏。
- Workspace 终端打开、缩放、全屏。
- RDP 打开、拖动窗口、文件夹拖拽、实时审计、历史回放。
- 会话命令页筛选、导出、从命令跳转回放。// Logan.Li