Files
wecom_it_smart_desk/jumpserver-webcli/ANALYSIS.md
T

341 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JP-webcli 自动化部署工具 — 能力·限制·优化分析
> 分析日期:2026-06-25 | 基于 webcli_v7Playwright Web CLI 方案)
---
## 一、现有能力矩阵
### 1.1 已验证的核心能力
| 能力 | 实现方式 | 可靠性 | 备注 |
|------|---------|--------|------|
| 自动登录 JumpServer | Playwright 填写表单 | ✅ 稳定 | 用户名 + 密码 |
| OTP 双因素认证 | pyotp 自动生成 + 自动填写 | ✅ 稳定 | 发现"双回车"问题已修复 |
| 资产列表导航 | 文本匹配 + ElementUI 按钮定位 | ✅ 稳定 | 依赖 DOM 结构 |
| 连接对话框确认 | 多选择器回退 + JS fallback | ⚠️ 中等 | 7 种选择器兜底 |
| Web 终端命令输入 | `keyboard.type()` + `Enter` | ✅ 稳定 | 支持 >10KB base64 载荷 |
| SFTP 文件上传 | `set_input_files` | ⚠️ 中等 | 依赖 webkitdirectory 属性 |
| 截图保存 | `page.screenshot()` | ✅ 稳定 | 用于人工验证 |
| 分块 base64 传输 | 8 chunks × ~3-4KB | ✅ 稳定 | Hotfix#120 验证通过 |
### 1.2 已完成的部署验证
| Hotfix | 日期 | 关键验证 |
|--------|------|---------|
| #116 扫码获取 | 2026-06-22 | 首次 Web CLI 端到端部署 |
| #120 扫码自动确认 | 2026-06-23 | SFTP 分块上传 + 29KB 源码部署 |
| #48 Nginx Upstream | 2026-06-24 | 配置修复 + HTTPS 验证 |
| SSO 单点登录 | 2026-06-25 | 环境变量注入 |
---
## 二、当前限制与已知问题
### 2.1 P0 — 输出捕获不可靠(当前焦点)
**问题描述:**
- v7 使用 `terminal_page.keyboard.type()` 发送命令后,通过截图 + `.inner_html()` 读取输出
- 截图需要人工观察,无法程序化判断执行成功/失败
- DOM 文本提取 (`inner_html` / `text_content`) 依赖于 xterm.js 使用 **DOM 渲染器**;如果使用 Canvas/WebGL 渲染器,`.xterm-rows` 不存在
**失败模式:**
```
场景 A: xterm.js 使用 Canvas 渲染器
→ .xterm-rows 不存在 → text_content() 返回空 → 输出丢失
场景 B: 命令输出超过可视区域
→ inner_html() 只包含可视行 → 输出不完整
场景 C: 命令尚未执行完毕就读取
→ 捕获到不完整输出 → 误判为成功/失败
```
**解决方案(进行中):** 见第三节 xterm.js 结构化捕获方案
### 2.2 P1 — 硬编码路径和配置
| 问题 | 影响 | 修复方向 |
|------|------|---------|
| 脚本路径硬编码在 `~/.workbuddy/skills/` | 无法在不同机器间移植 | 改为项目相对路径 |
| `gen-deploy-commands.py` 中的容器名硬编码 | 容器名变化时部署脚本失效 | 参数化 |
| OTP secret 存储在脚本目录 | 多项目共享困难 | 统一凭证管理 |
### 2.3 P1 — 缺乏执行结果自动判定
**当前行为:** 截图保存后由人工观察判断执行结果
**期望行为:** 自动解析输出,判断 exit code、错误关键词、预期输出
**阻塞因素:** 首先需要可靠的结构化输出捕获(P0)
### 2.4 P2 — 环境依赖
| 依赖 | 影响 |
|------|------|
| Chrome/Chromium 浏览器 | 无法在无 GUI 服务器上运行 |
| Playwright (~500MB) | 首次安装磁盘占用大 |
| Windows 系统 | `keyboard.type()` 依赖系统键盘布局 |
| JumpServer 页面 DOM 结构 | ElementUI 版本升级可能破坏选择器 |
### 2.5 P2 — 缺乏健康检查和告警
- 无 JumpServer 页面可达性检查
- 部署失败无企微通知
- 无命令执行超时后的自动重试策略
---
## 三、xterm.js 结构化输出捕获方案(v8 设计)
### 3.1 核心原理
xterm.js 内部维护一个**行缓冲区**(`terminal.buffer.active`),所有终端输出都以结构化形式存储在此缓冲区中。通过 Playwright 的 `page.evaluate()` 可以直接访问该缓冲区。
```
┌─────────────────────────────────────────────┐
│ xterm.js 架构 │
│ │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ 输入事件 │───▶│ Terminal.write() │ │
│ │ keyboard │ │ Terminal.writeln() │ │
│ └──────────┘ └──────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ Buffer (内部) │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ buffer.active (主缓冲区) │ │ │
│ │ │ - getLine(y) → BufferLine │ │ │
│ │ │ - translateToString() → str │ │ │
│ │ │ - length, baseY, cursorX/Y │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ buffer.normal (回滚缓冲区) │ │ │
│ │ └────────────────────────────────┘ │ │
│ └──────────────┬───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ 渲染器 (Renderer) │ │
│ │ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ DOM 渲染 │ │ Canvas/WebGL渲染 │ │ │
│ │ │ .xterm- │ │ <canvas> 元素 │ │ │
│ │ │ rows │ │ │ │ │
│ │ └──────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ ↑ v7 从这里读文本 │
│ ┌────────────────────────────────────┐ │
│ │ v8 从这里读文本 → buffer.active │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```
**关键优势:** 无论 xterm.js 使用哪种渲染器(DOM/Canvas/WebGL),缓冲区始终可用——这是 xterm.js 的核心数据结构,不依赖渲染层。
### 3.2 探测脚本(已创建)
**文件:** `~/.workbuddy/skills/jumpserver-automation/scripts/probe_xterm_buffer_v2.py`
**运行方式:**
```powershell
cd $env:USERPROFILE\.workbuddy\skills\jumpserver-automation\scripts
& "C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe" probe_xterm_buffer_v2.py
```
**探测内容:**
1. **全局 xterm 实例**:搜索 `window` 上的 Terminal 实例 (`_core`, `buffer`, `options` 等特征属性),读 `buffer.active`
2. **DOM .xterm-rows 结构**:遍历 `.xterm-rows > div > span`
3. **辅助 textarea**:读取 `.xterm-helper-textarea`
4. **渲染器检测**:判断 DOM vs Canvas 渲染器,检测 React Fiber
5. **综合提取**:自动选择最佳方法并输出结构化文本
**输出文件:**
- `webcli_output/probe_v2_pre_cmd.json` — 命令执行前探测
- `webcli_output/probe_v2_results.json` — 综合结果
- `webcli_output/probe_v2_output.txt` — 纯文本终端输出
- `webcli_output/probe_v2_final.png` — 截图对照
### 3.3 预期结果与应对策略
| 探测结果 | 含义 | v8 策略 |
|---------|------|---------|
| `best_method = global_instance` | Terminal 实例在 window 上暴露 | ✅ 直接用 `buffer.active.getLine()` 读取 |
| `best_method = dom_rows` | DOM 渲染器可用 | ⚠️ 可工作但依赖渲染器选择,降级为备用方案 |
| `best_method = screen_textcontent` | 仅能通过 DOM 文本提取 | ❌ 需注入 Monkey Patch 拦截 `terminal.write()` |
| `best_method = null` | 所有方法均失败 | ❌ 需 Monkey Patch 或重新评估方案 |
### 3.4 v8 输出捕获核心代码(预览)
```python
def capture_terminal_output(page, wait_seconds=10):
"""
通过 xterm.js 缓冲区结构化捕获终端输出
返回: { "text": str, "line_count": int, "method": str, "cols": int, "rows": int }
"""
page.wait_for_timeout(wait_seconds * 1000)
result = page.evaluate("""
() => {
// 策略1: 查找全局 Terminal 实例
const xtermKeys = ['_core', 'buffer', 'options', 'cols', 'rows'];
const allKeys = Object.getOwnPropertyNames(window);
for (const key of allKeys) {
try {
const val = window[key];
if (!val || typeof val !== 'object') continue;
const matchCount = xtermKeys.filter(p => p in val).length;
if (matchCount >= 3 && val.buffer && val.buffer.active) {
const buf = val.buffer.active;
const lines = [];
const total = buf.length;
const start = Math.max(0, total - Math.max(val.rows, 100));
for (let y = start; y < total; y++) {
try { lines.push(buf.getLine(y).translateToString(true)); }
catch(e) { lines.push(''); }
}
return {
method: 'buffer.active',
text: lines.join('\\n'),
line_count: lines.length,
cols: val.cols,
rows: val.rows,
cursor: { x: buf.cursorX, y: buf.cursorY }
};
}
} catch(e) {}
}
// 策略2: DOM fallback
const rowsEl = document.querySelector('.xterm-rows');
if (rowsEl) {
const lines = [];
rowsEl.querySelectorAll(':scope > div').forEach(div => {
let text = '';
div.querySelectorAll('span').forEach(s => text += s.textContent || '');
lines.push(text);
});
return { method: 'dom_rows', text: lines.join('\\n'), line_count: lines.length };
}
return { method: 'none', text: '', line_count: 0 };
}
""")
return result
```
### 3.5 Monkey Patch 方案(兜底)
如果 xterm.js 实例完全没有暴露在全局作用域,可以在 `page.goto()` 之后、终端加载之前注入拦截代码:
```python
# 在页面加载时注入 Monkey Patch
page.add_init_script("""
// 拦截 xterm.js 的 write/writeln 方法
const origDefineProperty = Object.defineProperty;
let capturedOutput = [];
// 定时检查是否有 xterm 实例被创建
const observer = new MutationObserver(() => {
const term = document.querySelector('.xterm');
if (term && term.__xterm_captured !== true) {
term.__xterm_captured = true;
// 通过终端 DOM 元素的内部引用获取 Terminal 实例
// 这里的实现依赖具体的 JumpServer/Luna 版本
}
});
observer.observe(document.body, { childList: true, subtree: true });
window.__xterm_output = () => capturedOutput.join('\\n');
""")
```
---
## 四、不适用的场景分析
### 4.1 Web CLI 方案本质局限
| 场景 | 为什么不适用 | 替代方案 |
|------|------------|---------|
| **完全无人值守 CI/CD** | Playwright 需要 GUI 浏览器,headless 模式不可靠(JumpServer 部分元素需要可视化渲染) | SSH Key 认证(v17)或 Koko API |
| **大规模并行部署** | 每个浏览器实例 ~500MB 内存,并行 5+ 实例会耗尽资源 | Ansible/SSH 批量执行 |
| **跨平台(macOS/Linux** | `keyboard.type()` 受键盘布局影响;Chrome channel 名称不同 | 使用 `page.keyboard.insertText()` 替代 |
| **网络不稳定环境** | Web CLI 依赖持久 WebSocket 连接,断连后需重新登录+MFA | SSH 自带重连机制更可靠 |
| **非交互式后台任务** | Web CLI 本质是模拟人工操作,不适合长时间后台运行 | systemd timer / cron job |
| **JumpServer 不可用时** | 整个方案的前提条件不成立 | 直接 SSH 到目标服务器(内网场景) |
### 4.2 截图方式的不适用场景
| 场景 | 失败原因 |
|------|---------|
| 终端输出超长(>1000行) | 截图只能捕获可视区域 |
| 输出包含非 ASCII 字符渲染异常 | 截图无法提取文本内容用于程序判定 |
| 颜色/格式区分的信息 | 截图后颜色信息丢失(除非用彩色截图) |
| 需要 grep/正则匹配输出内容 | 截图是像素,无法进行文本搜索 |
---
## 五、优化路线图
### 近期(本周)
```
[P0] probe_xterm_buffer_v2 运行 + 分析结果
└→ 确认 xterm.js 缓冲区访问方式
[P0] webcli_v8: 结构化输出捕获
├─ 用 buffer.active 替代 inner_html
├─ 自动判断命令执行成功/失败
└─ 输出保存为结构化 JSON
[P1] webcli_v8: 命令执行超时 + 重试
├─ 等待策略从固定 sleep 改为轮询 buffer
└─ 失败自动重试(最多 3 次)
```
### 中期(本月)
```
[P1] 路径参数化 + 配置文件
├─ 所有硬编码路径→配置文件
└─ gen-deploy-commands.py 容器名参数化
[P1] 部署结果企微通知
└─ 成功/失败推送企微卡片消息
[P2] 健康检查脚本
└─ JumpServer 页面可达性检测
```
### 远期(探索)
```
[P2] SSH Key 认证方案 (v17)
└─ 完全无人值守自动化
[P2] JumpServer Koko API
└─ 不依赖浏览器的 WebSocket 终端连接
[P2] 跨平台支持
└─ macOS/Linux Playwright 兼容
```
---
## 六、下一步操作
1. **运行探测脚本**(需要你手动执行,因为需要 OTP + Chrome 浏览器):
```powershell
cd $env:USERPROFILE\.workbuddy\skills\jumpserver-automation\scripts
& "C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe" probe_xterm_buffer_v2.py
```
2. **查看探测结果**:检查 `webcli_output/probe_v2_results.json`,确认 `best_method` 字段
3. **根据结果选择 v8 实现方案**:
- 如果 `best_method = global_instance` → 直接用缓冲区读取,实现最简单
- 如果 `best_method = dom_rows` → 可用但需验证跨版本兼容性
- 如果 `best_method = null` 或 `screen_textcontent` → 需要 Monkey Patch 方案
4. **实现 webcli_v8.py**:基于探测结果选择最佳捕获策略