提交 8a63c076 authored 作者: 陈泽健's avatar 陈泽健

docs(service-monitor): 新增 SSH 连接测试失败与检测项折叠展示的 PRD 文档

- PRD_问题处理_SSH连接测试失败.md: SSH 连接问题分析与处理记录
- PRD_计划执行_SSH连接测试失败修复.md: SSH 修复执行计划
- PRD_问题处理_检测项折叠展示.md: 报告折叠展示问题处理记录
- PRD_计划执行_检测项折叠展示修复.md: 折叠展示修复执行计划
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 4154b21d
# PRD 计划执行 — SSH 连接测试失败修复
## 概述
| 项目 | 内容 |
|------|------|
| 需求来源 | PRD_问题处理_SSH连接测试失败.md |
| 计划日期 | 2026-07-20 |
| 预计工时 | 2 小时 |
| 优先级 | P1(影响功能可用性) |
## 目标
1. 增强 SSH 连接测试的错误信息反馈,区分不同错误类型
2. 前端显示详细错误信息,帮助用户排查问题
3. 部署脚本自动检查并安装服务器端依赖
## 修改清单
### 阶段一:后端错误分类
#### 1.1 修改 `executor.py`
**文件**: `skill/code/web/service_monitor/utils/executor.py`
**改动点**:
1. 模块顶层检查 paramiko 是否可用:
```python
# 模块顶层
_HAS_PARAMIKO = True
try:
import paramiko
except ImportError:
_HAS_PARAMIKO = False
```
2. `SSHExecutor.__init__()` 检查 paramiko
```python
def __init__(self, ...):
if not _HAS_PARAMIKO:
raise DependencyError("paramiko 未安装,请在服务器执行 pip install paramiko")
```
3. 新增 `DependencyError` 异常类
4. `test_connection()` 返回更详细的错误分类
**错误分类映射**:
| 异常类型 | 错误消息 | 错误码 |
|----------|----------|--------|
| `DependencyError` | paramiko 未安装,请在服务器执行 pip install paramiko | DEPENDENCY_MISSING |
| `paramiko.AuthenticationException` | 认证失败:用户名或密码错误 | AUTH_FAILED |
| `paramiko.SSHException` | SSH 协议错误:{detail} | SSH_ERROR |
| `socket.timeout` / `TimeoutError` | 连接超时:目标不可达或端口未开放 | TIMEOUT |
| `ConnectionRefusedError` | 连接被拒绝:目标端口未开放或 SSH 服务未运行 | CONNECTION_REFUSED |
| `socket.gaierror` | 主机名解析失败:{detail} | DNS_ERROR |
| 其他 | SSH 连接失败:{detail} | UNKNOWN |
#### 1.2 修改 `target_service.py`
**文件**: `skill/code/web/service_monitor/services/target_service.py`
**改动点**:
`test_connection()` 返回值增加 `detail` `error_code` 字段:
```python
def test_connection(data: dict) -> dict:
"""测试远程目标 SSH 连通性(不落库)。
Returns: {
"success": bool,
"message": str,
"detail": str | None, # 详细错误信息
"error_code": str | None # 错误码
}
"""
```
#### 1.3 修改 `routes.py`
**文件**: `skill/code/web/service_monitor/routes.py`
**改动点**:
`api_test_connection()` 返回完整结果:
```python
@bp.route('/api/service-monitor/targets/test', methods=['POST'])
def api_test_connection():
guard = _require_admin_json()
if guard:
return guard
result = target_service.test_connection(request.get_json(force=True) or {})
return jsonify(result) # 直接返回完整结果
```
### 阶段二:前端显示详细错误
#### 2.1 修改 `targets.html`
**文件**: `skill/code/web/templates/service_monitor/targets.html`
**改动点**:
测试连接结果显示 `detail` 字段:
```javascript
async function testConn() {
const msg = document.getElementById('test-msg');
// ...
const d = await r.json();
msg.style.color = d.success ? 'var(--green)' : 'var(--red)';
if (d.success) {
msg.textContent = '✓ ' + (d.message || '连接成功');
} else {
// 显示详细错误
let errMsg = '✗ ' + (d.message || '连接失败');
if (d.detail) {
errMsg += ' (' + d.detail + ')';
}
msg.textContent = errMsg;
msg.title = d.error_code || ''; // 鼠标悬停显示错误码
}
}
```
### 阶段三:部署脚本增加依赖检查
#### 3.1 修改 `upload_to_server.py`
**文件**: `deploy/upload_to_server.py`
**改动点**:
在 `upload_files()` 开头增加依赖检查步骤:
```python
# 需要检查的 Python 包
REQUIRED_PACKAGES = ['paramiko', 'cryptography']
def _check_and_install_deps(ssh):
"""检查并安装服务器端 Python 依赖。"""
for pkg in REQUIRED_PACKAGES:
stdin, stdout, stderr = ssh.exec_command(
f"python3 -c 'import {pkg}' 2>&1"
)
result = stdout.read().decode()
if 'ModuleNotFoundError' in result or 'No module named' in result:
print(f" [INSTALL] {pkg} 未安装,正在安装...")
stdin, stdout, stderr = ssh.exec_command(
f"pip3 install --break-system-packages {pkg} 2>&1"
)
install_result = stdout.read().decode()
if 'Successfully installed' in install_result:
print(f" [OK] {pkg} 安装成功")
else:
print(f" [WARN] {pkg} 安装失败,请手动安装")
else:
print(f" [OK] {pkg} 已安装")
```
在 `upload_files()` 中调用:
```python
print("\n[2/5] Checking dependencies...")
_check_and_install_deps(ssh)
print("\n[3/5] Backing up...")
# ...
print("\n[4/5] Uploading files...")
# ...
print("\n[5/5] Restarting service...")
# ...
```
## 测试计划
### 单元测试
```bash
cd skill/code && python -m pytest -v
# 预期:218 用例全绿
```
### API 测试
1. **测试 paramiko 未安装**(模拟):
- 返回 `error_code: DEPENDENCY_MISSING`
2. **测试认证失败**:
```bash
curl -X POST http://192.168.5.60:8088/api/service-monitor/targets/test \
-H "Content-Type: application/json" \
-d '{"host":"192.168.5.44","port":22,"username":"wrong","password":"wrong"}'
# 预期:{"success":false,"message":"认证失败:用户名或密码错误","error_code":"AUTH_FAILED"}
```
3. **测试连接超时**:
```bash
curl -X POST http://192.168.5.60:8088/api/service-monitor/targets/test \
-H "Content-Type: application/json" \
-d '{"host":"192.168.5.99","port":22,"username":"root","password":"test"}'
# 预期:{"success":false,"message":"连接超时:目标不可达或端口未开放","error_code":"TIMEOUT"}
```
4. **测试连接成功**:
```bash
curl -X POST http://192.168.5.60:8088/api/service-monitor/targets/test \
-H "Content-Type: application/json" \
-d '{"host":"192.168.5.44","port":22,"username":"root","password":"Ubains@123"}'
# 预期:{"success":true,"message":"连接成功"}
```
### 前端验证
1. 浏览器访问 http://192.168.5.60:8088/service-monitor/targets
2. 新建远程目标,填写错误凭据,点击测试连接
3. 确认显示详细错误信息(如"认证失败:用户名或密码错误")
### 部署验证
1. 清理服务器上的 paramiko:
```bash
pip3 uninstall -y paramiko
```
2. 重新运行部署脚本:
```bash
cd deploy && SSH_PASSWORD='***' python upload_to_server.py
```
3. 确认输出包含 `[INSTALL] paramiko 未安装,正在安装...``[OK] paramiko 安装成功`
## 回滚方案
如果修复引入新问题:
1. 回退 `executor.py``target_service.py``routes.py``targets.html` 的改动
2. 重新部署旧版本代码
3. 服务器端手动安装 paramiko 作为临时方案
## 验收标准
1. ✅ 测试连接返回详细错误信息,区分不同错误类型
2. ✅ 前端显示具体错误原因
3. ✅ 部署脚本自动检查并安装 paramiko
4. ✅ 单元测试全部通过
5. ✅ API 测试和前端验证通过
## 元信息
- 编写人:Claude Code
- 审核人:待定
- 状态:待实施
\ No newline at end of file
# PRD — 计划执行:检测项折叠展示与进程列表优化
## 概述
| 项目 | 内容 |
|------|------|
| 需求来源 | PRD_问题处理_检测项折叠展示.md |
| 计划日期 | 2026-07-20 |
| 预计工时 | 4 小时 |
| 优先级 | P2(用户体验优化) |
## 目标
1. Java/Python 进程列表正确显示在报告中
2. 检测项按模块分组,默认折叠,点击展开
3. 异常项优先展示,正常项默认隐藏
---
## Phase 1:进程列表显示修复(1小时)
### 1.1 检查 parser 过滤逻辑
**文件**`skill/code/web/service_monitor/utils/parser.py`
**检查点**
- `_INVALID_VALUES` 是否误过滤了进程列表值
- 值中包含 `|` 分隔符是否被正确处理
**修改**:如有过滤问题,添加白名单或调整过滤逻辑
### 1.2 确认脚本输出格式
**文件**
- `skill/code/web/service_monitor/assets/service/28_java_check.sh`
- `skill/code/web/service_monitor/assets/service/29_python_check.sh`
**修改点**
- 进程列表输出格式:`USER PID CPU% MEM% COMMAND`,多行用 `|` 分隔
- 确保每行不超过 200 字符(避免前端截断)
### 1.3 更新 display_names
**文件**`skill/code/web/service_monitor/utils/display_names.py`
**新增 KEY**
```python
"JAVA_PROCESS_COUNT": "Java容器进程总数",
"JAVA_PROCESS_LIST": "Java容器进程列表",
"JAVA_PROCESSES_DETAIL": "Java进程详情",
"PYTHON_PROCESS_COUNT": "Python容器进程总数",
"PYTHON_PROCESS_LIST": "Python容器进程列表",
"PYTHON_PROCESSES_DETAIL": "Python进程详情",
```
---
## Phase 2:报告前端折叠展示改造(2小时)
### 2.1 修改报告模板
**文件**`skill/code/web/templates/service_monitor/report.html`
**改造思路**
```html
<!-- 模块分组折叠 -->
{% for module in modules %}
<div class="module-section">
<!-- 模块头部(可点击) -->
<div class="module-header" onclick="toggleModule(this)">
<span class="module-icon">
{% if module.has_error %}❌{% elif module.has_warning %}⚠️{% else %}✅{% endif %}
</span>
<span class="module-name">{{ module.name }}</span>
<span class="module-stats">
异常 {{ module.error_count }} / 警告 {{ module.warning_count }} / 正常 {{ module.normal_count }}
</span>
<span class="toggle-icon"></span>
</div>
<!-- 检测项列表(默认折叠,异常项展开) -->
<div class="module-body collapsed">
{% for item in module.items %}
<div class="check-item level-{{ item.level }}">
<span class="item-name">{{ item.display_name }}</span>
<span class="item-value">{{ item.value }}</span>
<span class="item-level">{{ item.level_text }}</span>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
```
### 2.2 添加 JavaScript 折叠逻辑
```javascript
function toggleModule(header) {
const body = header.nextElementSibling;
const icon = header.querySelector('.toggle-icon');
if (body.classList.contains('collapsed')) {
body.classList.remove('collapsed');
icon.textContent = '▲';
} else {
body.classList.add('collapsed');
icon.textContent = '▼';
}
}
// 页面加载时,自动展开有异常的模块
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.module-section').forEach(section => {
const hasError = section.querySelector('.module-icon').textContent.includes('❌');
const hasWarning = section.querySelector('.module-icon').textContent.includes('⚠️');
if (hasError || hasWarning) {
const header = section.querySelector('.module-header');
toggleModule(header);
}
});
});
```
### 2.3 添加 CSS 样式
```css
.module-section {
margin-bottom: 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.module-header {
display: flex;
align-items: center;
padding: 12px 16px;
background: #f5f5f5;
cursor: pointer;
user-select: none;
}
.module-header:hover {
background: #e8e8e8;
}
.module-body {
padding: 0 16px;
max-height: 2000px;
overflow: hidden;
transition: max-height 0.3s ease;
}
.module-body.collapsed {
max-height: 0;
padding: 0 16px;
}
.check-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.check-item:last-child {
border-bottom: none;
}
.check-item.level-critical { background: #fff0f0; }
.check-item.level-warning { background: #fff8e0; }
.check-item.level-normal { background: transparent; }
.toggle-icon {
margin-left: auto;
transition: transform 0.3s;
}
```
---
## Phase 3:进程列表特殊展示(1小时)
### 3.1 后端解析进程列表
**文件**`skill/code/web/service_monitor/services/report_service.py`
**修改**:解析 `*_PROCESS_LIST` 类型的 KEY,转换为结构化数据
```python
def parse_process_list(value: str) -> list:
"""解析进程列表字符串为结构化数据"""
processes = []
for line in value.split('|'):
parts = line.strip().split()
if len(parts) >= 5:
processes.append({
'user': parts[0],
'pid': parts[1],
'cpu': parts[2],
'mem': parts[3],
'command': ' '.join(parts[4:])
})
return processes
```
### 3.2 前端特殊渲染
**模板修改**
```html
{% if item.key ends with '_PROCESS_LIST' %}
<div class="process-list">
<div class="process-summary">共 {{ item.process_count }} 个进程</div>
<button onclick="toggleProcessList(this)">展开详情</button>
<table class="process-table" style="display: none;">
<thead>
<tr><th>用户</th><th>PID</th><th>CPU%</th><th>内存%</th><th>命令</th></tr>
</thead>
<tbody>
{% for proc in item.processes %}
<tr>
<td>{{ proc.user }}</td>
<td>{{ proc.pid }}</td>
<td>{{ proc.cpu }}</td>
<td>{{ proc.mem }}</td>
<td class="cmd">{{ proc.command }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<span class="item-value">{{ item.value }}</span>
{% endif %}
```
---
## 修改清单
| 阶段 | 文件 | 改动点 |
|------|------|--------|
| Phase 1 | `parser.py` | 检查过滤逻辑,确保进程列表不被过滤 |
| Phase 1 | `28_java_check.sh` | 确认进程列表输出格式 |
| Phase 1 | `29_python_check.sh` | 同上 |
| Phase 1 | `display_names.py` | 新增进程列表 KEY |
| Phase 2 | `report.html` | 折叠展示改造(HTML + CSS + JS) |
| Phase 3 | `report_service.py` | 解析进程列表为结构化数据 |
---
## 验证步骤
1. 运行全量巡检,检查 Java/Python 模块是否输出进程列表
2. 打开报告页面,确认模块默认折叠
3. 点击模块头部,确认能展开/折叠
4. 确认异常模块默认展开
5. 确认进程列表能正确展示
---
## 风险与回退
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 进程列表值过长 | 前端渲染慢 | 限制最多显示 20 个进程 |
| JS 兼容性问题 | 旧浏览器折叠失效 | 使用原生 JS,无依赖 |
| 改动影响现有报告 | 报告展示异常 | 保留原样式作为回退 |
\ No newline at end of file
# PRD 问题处理 — 服务监测模块 SSH 连接测试失败
## 问题信息
| 项目 | 内容 |
|------|------|
| 模块 | 服务监测模块 |
| 功能 | 远程目标 SSH 连接测试 |
| 发现时间 | 2026-07-20 |
| 严重程度 | 中(影响功能可用性,用户体验差) |
| 状态 | 已定位,待修复 |
## 现象
用户在服务监测平台新建 5 网段远程目标服务器(如 192.168.5.44),填写主机地址、端口、用户名、密码后,点击"测试连接"按钮,提示"SSH 连接失败"。
错误信息笼统,无法判断是网络问题、认证问题还是其他原因。
## 影响
1. **功能不可用**:无法验证远程服务器连通性,无法正常使用远程巡检功能
2. **用户体验差**:错误提示不清晰,用户无法自行排查问题
3. **运维困难**:无法区分网络/认证/依赖等不同类型的问题
## 根因分析
### 直接原因
服务器 5.60 上未安装 `paramiko` Python 包。
### 技术细节
1. `SSHExecutor._get_ssh()` 方法中 `import paramiko` 是懒加载,在运行时导入
2. 当 paramiko 未安装时,`import paramiko` 抛出 `ModuleNotFoundError`
3. 该异常被 `test_connection()``except Exception as e` 捕获
4. 捕获后只返回 `{"success": False, "message": str(e)}`,但 `str(e)` 被转换为"SSH 连接失败"
5. 用户看到的是笼统的"SSH 连接失败",无法得知是依赖缺失
### 代码路径
```
前端 targets.html
↓ testConn() POST /api/service-monitor/targets/test
routes.py api_test_connection()
↓ target_service.test_connection(request_json)
target_service.py test_connection()
↓ SSHExecutor(run_id="test", host, port, username, password)
↓ exe.test_connection()
executor.py SSHExecutor.test_connection()
↓ self._exec("echo OK", timeout=10)
↓ self._get_ssh()
↓ import paramiko ← ModuleNotFoundError (未安装)
异常向上传播
↓ ConnectionError("SSH 连接失败: ...")
返回 {"success": false, "message": "SSH 连接失败"}
```
### 为什么 paramiko 未安装
部署脚本 `deploy/upload_to_server.py` 只上传文件和重启服务,未检查服务器端 Python 依赖。
`requirements.txt` 虽然列出了 `paramiko``cryptography`,但服务器上没有执行 `pip install -r requirements.txt`
## 解决方案
### 临时修复(已执行)
1. 在服务器 5.60 上手动安装 paramiko:
```bash
pip3 install --break-system-packages paramiko
```
2. 重启服务
### 长期修复(代码改进)
1. **增强错误分类**`test_connection()` 区分不同错误类型,返回详细错误信息
2. **前端显示详细错误**:测试连接结果显示具体错误原因
3. **部署脚本增加依赖检查**:上传前检查 paramiko/cryptography,未安装则自动安装
4. **输出 PRD 文档**:记录问题和修复计划
## 验证
### 修复前
- 测试连接返回"SSH 连接失败",无具体信息
- 无法判断问题类型
### 修复后
- paramiko 未安装 → "paramiko 未安装,请在服务器执行 pip install paramiko"
- 认证失败 → "认证失败:用户名或密码错误"
- 连接超时 → "连接超时:目标不可达或端口未开放"
- 连接被拒绝 → "连接被拒绝:目标端口未开放或 SSH 服务未运行"
- 连接成功 → "连接成功"
## 元信息
- 记录人:Claude Code
- 责任人:czj
- 日志可定位:是(paramiko 导入失败会抛异常)
- 是否已解决:待代码修复
\ No newline at end of file
# PRD — 问题处理:检测项折叠展示与进程列表优化
## 概述
| 项目 | 内容 |
|------|------|
| 问题ID | service-monitor-ui-001 |
| 发现日期 | 2026-07-20 |
| 优先级 | P2(用户体验优化) |
| 影响范围 | 服务监测报告展示 |
## 问题描述
### 问题1:Java/Python 容器进程列表未显示
**现象**:用户在巡检报告中看不到 Java 和 Python 容器内的进程列表,无法判断容器内服务是否正常运行。
**影响**:无法直观看到容器内跑着哪些进程、CPU/内存占用情况。
**根因分析**
1. 脚本已输出 `JAVA_PROCESS_LIST``PYTHON_PROCESS_LIST` KEY
2. `display_names.py` 已配置中文显示名
3. 可能原因:
- 进程列表值太长(包含多行用 `|` 分隔),前端未正确渲染
- KEY 不在快速巡检的模块范围内(Java/Python 模块属于全量巡检)
- parser 可能过滤了特殊字符
### 问题2:检测项太多,报告可读性差
**现象**:全量巡检输出 193+ 项检测项,页面冗长,用户难以快速定位异常项。
**影响**
- 正常项淹没异常项
- 用户需要大量滚动才能看完报告
- 关键问题不突出
**期望效果**
- 默认只显示摘要和异常项
- 正常项折叠隐藏,点击展开查看
- 支持按模块分组折叠
## 需求目标
1. **进程列表正确显示**:Java/Python 检测项中展示容器内进程列表
2. **报告折叠展示**:检测项默认折叠,点击展开查看详情
3. **异常项优先**:异常/警告项默认展开,正常项默认折叠
## 解决方案
### 方案1:进程列表优化
**文件修改**
- `28_java_check.sh` / `29_python_check.sh`:确保进程列表输出格式正确
- `parser.py`:检查是否过滤了 `|` 分隔符
- `report.html`:前端渲染进程列表时支持换行显示
### 方案2:报告折叠展示
**前端改造**
```html
<!-- 报告页面改造思路 -->
<div class="report-section">
<!-- 模块头部(可点击折叠) -->
<div class="module-header" onclick="toggleModule('module-id')">
<span class="module-name">Java应用检测</span>
<span class="module-summary">正常 12 项 / 异常 1 项</span>
<span class="toggle-icon"></span>
</div>
<!-- 检测项列表(默认折叠) -->
<div class="module-items" id="module-id" style="display: none;">
<!-- 异常项默认展开 -->
<div class="item item-error">
<span class="item-name">JVM堆使用率</span>
<span class="item-value">92%</span>
<span class="item-level level-error">严重</span>
</div>
<!-- 正常项 -->
<div class="item item-normal" style="display: none;">
...
</div>
</div>
</div>
```
**交互逻辑**
1. 页面加载时,只显示模块摘要(模块名 + 异常计数)
2. 点击模块头部,展开/折叠该模块的所有检测项
3. 检测项中:
- 异常/警告项:默认显示
- 正常项:默认隐藏,有"显示正常项"按钮
### 方案3:进程列表特殊渲染
对于进程列表这种长文本项:
- 显示进程数摘要:`共 15 个进程`
- 点击查看详细列表(弹窗或展开)
- 列表按表格形式展示:用户、PID、CPU%、内存%、命令
## 验收标准
1. Java/Python 检测报告中能看到容器内进程列表
2. 报告页面默认折叠,只显示摘要
3. 异常项默认展开,正常项默认折叠
4. 点击模块头部能展开/折叠
5. 进程列表能清晰展示各进程信息
## 相关文件
| 文件 | 用途 |
|------|------|
| `templates/service_monitor/report.html` | 报告页面模板 |
| `assets/service/28_java_check.sh` | Java检测脚本 |
| `assets/service/29_python_check.sh` | Python检测脚本 |
| `utils/parser.py` | 检测结果解析 |
| `utils/display_names.py` | KEY显示名映射 |
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论