# HANDOFF — 服务监测模块实施进度

> 最后更新：2026-07-16 ｜ 分支：troubleshoot-ai-assistant ｜ 模块：service-monitor
> 状态：**代码全部完成，218 测试全绿，待部署到 5.60**

---

## 1. 我们在做什么

为运维平台落地「服务监测」模块：监控本机/远程服务器系统资源与服务状态，输出巡检报告。
**只做监测不做修复**（修复接口预留，点击返回"开发中"）。

需求与计划文档：
- `Docs/需求文档/服务监测/PRD_需求文档_服务监测模块.md`
- `Docs/需求文档/服务监测/PRD_计划执行_服务监测模块.md`

### 关键决策（已与用户对齐）
- 代码**完全隔离**：所有代码进 `skill/code/web/service_monitor/` 子包，不动 `routes/`/`services/`/`utils/`/`config.json`/`container.py`，对外仅 `server.py` 一行注册
- 本期**核心子集**：system(01/02/03/04) + Docker-basic + MySQL-basic + Redis-basic
- bash 模块**搬进项目**当静态资源 + config 外置模板
- 容器名**模糊匹配**（不同服务器容器名不一致，用 grep -iE 模式发现）
- 远程 SSH 用 **paramiko**；SSH 密码 **Fernet 加密存储**
- 进度反馈 **SSE**；报告默认保留 **14 天**可配置
- **快速巡检 / 全量巡检** 两套件
- **角色控制**：普通用户仅查看（隐藏操作入口），管理员可操作

---

## 2. 已完成（全部 7 阶段）

### ✅ 阶段一：子包骨架 + bash 资产搬迁
- 建子包目录 `service_monitor/{__init__,routes,services/,utils/,assets/,data/,tests/}`
- 搬迁核心 bash 模块到 `assets/`：common.sh + config.sh.template + system/{01,02,03,04} + service/{20,22,24}
- 改造模块头部 `LIB_DIR="${LIB_DIR:-/tmp/check_modules}"`（7个模块，支持注入）
- config.sh.template：容器名改匹配模式、密码改占位符、阈值可覆盖、新增 resolve_container
- 端到端本地执行验证通过（KEY:VALUE 输出正常）

### ✅ 阶段二：utils 层
- `paths.py` — 模块私有路径常量 + ensure_dirs()
- `crypto.py` — Fernet 加密/解密（MONITOR_ENC_KEY > 派生自 SECRET_KEY > 兜底）
- `check_modules.py` — CheckModule 清单 + get_suite() + render_config()（shlex.quote 防注入）
- `parser.py` — 移植 Parse-ModuleResult（过滤脏行 + KEY:VALUE 解析 + 状态判定，优先 *_LEVEL）
- `thresholds.py` — 阈值表 + judge_status()（数值/百分比比较，严重/警告分级）
- `display_names.py` — KEY→中文显示名（核心子集）
- `executor.py` — BaseExecutor/LocalExecutor(subprocess)/SSHExecutor(paramiko)

### ✅ 阶段三：services 层
- `target_service.py` — 目标 CRUD + 连通性测试 + make_executor + resolve_credentials（解密）
- `runner_service.py` — 巡检编排（生成器 yield SSE 事件）+ cancel_run + get_run_status
- `report_service.py` — 报告 save/get/list/delete/cleanup_expired/export_md/export_json

### ✅ 阶段四：routes 层 + 接入 + 删旧占位
- `routes.py` — 16 条路由（4页面 + 12API），角色控制完整
- 删除旧占位 `routes/service_monitor.py` + `templates/service_monitor.html`
- `server.py` 已有 `from service_monitor import bp`（之前占位就改好了）

### ✅ 阶段五：前端模板（UI 已与用户确认）
- `index.html` — 目标卡片 + 报告列表（普通用户隐藏操作按钮）
- `targets.html` — 目标 CRUD 弹窗 + 连通性测试
- `run.html` — 进度条 + 模块清单 + SSE 实时反馈 + 完成跳报告
- `report.html` — 汇总卡片 + 异常置顶 + 分模块折叠 + 导出MD/JSON + 修复预留按钮

### ✅ 阶段六：测试
- 模块私有测试 55 用例（parser/crypto/check_modules/target_service/report_service/routes_sm）
- **全套 218 测试全绿**（原 163 + 新增 55，未破坏现有测试）
- `pytest.ini` 新增 testpath `web/service_monitor/tests`
- 子包 conftest 注入 tmp 数据目录隔离

### ✅ 阶段七：部署与依赖
- `requirements.txt` 加 paramiko + cryptography
- `.env.example` 加 MONITOR_ENC_KEY 说明（含生成命令）
- `deploy/upload_to_server.py` 加 `RECURSIVE_DIRS_TO_UPLOAD` + `_upload_dir_recursive()`（递归上传 service_monitor，排除 tests/data/__pycache__）
- `.gitignore` 加服务监测运行时数据忽略（targets.json/reports/*.json，保留 .gitkeep）
- `CLAUDE.md` 更新项目结构说明
- 本机 test_client 冒烟全通过（页面200/权限403/API正常）

---

## 3. 待办（部署）

代码完成，**尚未部署到 5.60**。部署步骤：

```bash
# 1. 服务器需装依赖（paramiko 已在 deploy 用，cryptography 需确认）
#    服务器执行：pip install paramiko cryptography
#    （或确认 paramiko 已装则 cryptography 作为其依赖已存在）

# 2. 上传代码（用 ! 前缀在会话内执行，SSH_PASSWORD 环境变量）
! cd deploy && SSH_PASSWORD='***' python upload_to_server.py

# 3. 权威验证
cd deploy && python verify_deployment.py

# 4. 手动验证服务监测
#    - 浏览器访问 http://192.168.5.60:8088/service-monitor
#    - 本地目标快速巡检 → 看报告
#    - （可选）新增远程目标测连通 + 远程巡检
```

---

## 4. 任务进度

| # | 阶段 | 状态 |
|---|------|------|
| 1 | 子包骨架 + bash 资产搬迁 | ✅ |
| 2 | utils 层 | ✅ |
| 3 | services 层 | ✅ |
| 4 | routes 层 + 接入 + 删旧占位 | ✅ |
| 5 | 前端模板 | ✅ |
| 6 | 测试（55 用例，全套 218 绿） | ✅ |
| 7 | 部署与依赖 | ✅ 代码完成，待部署 |

---

## 5. 踩过的坑 / 注意事项

1. **模块头部 LIB_DIR 写死**：原脚本 `LIB_DIR="/tmp/check_modules"` 覆盖环境变量，改 `${LIB_DIR:-...}` 才能注入
2. **common.sh 加载路径**：source `$LIB_DIR/lib/config.sh`，executor 必须布置成 `<workdir>/lib/` 结构
3. **MySQL/Redis 模块已内置模糊匹配**：`grep -i "${CONTAINERS[mysql]}"`，config 容器名值直接当模式用
4. **config 凭据占位符**：模板里 `MYSQL_PASSWORD=__MYSQL_PASSWORD__`（不加引号），渲染时用 shlex.quote 输出安全引用；若模板自带引号会双引号套单引号出错
5. **shlex.quote 空值**：返回 `''`（空字符串带引号），bash 合法
6. **测试 fixture session 覆盖**：user/admin 不能共用同一 client 实例（session 互相覆盖），改为各自独立 test_client()
7. **测试断言 `'password' not in json`**：太严，`has_password` 字段含子串；改为检查不返回 `password_enc`/`password` 明文字段
8. **upload 递归上传**：原 `DIRS_TO_UPLOAD` 只上传一层文件，service_monitor 多层子目录需 `_upload_dir_recursive`；排除 tests/data/__pycache__
9. **运行时 data 目录**：服务器首次部署无 data/，靠 `ensure_dirs()` 自动创建（mkdir parents=True）
10. **Blueprint 端点名**：`service-monitor.xxx`（横线），url_for 用全名
11. **Docker basic 的 check_container_status**：用精确名 `grep -q "^${name}$"`，模式下可能匹配不到逐容器项；本期可接受（还有 service 状态/资源等通用项）

---

## 6. 关键文件速查

### 新增子包
```
skill/code/web/service_monitor/
├── __init__.py              # from .routes import bp
├── routes.py               # 16 条路由
├── services/{__init__,target_service,runner_service,report_service}.py
├── utils/{__init__,paths,crypto,check_modules,parser,thresholds,display_names,executor}.py
├── assets/{config.sh.template, common.sh, system/{01,02,03,04}*.sh, service/{20,22,24}*.sh}
├── data/{.gitkeep, reports/.gitkeep}    # 运行时数据，gitignore
└── tests/{__init__,conftest,test_parser,test_crypto,test_check_modules,test_target_service,test_report_service,test_routes_sm}.py
templates/service_monitor/{index,targets,run,report}.html
```

### 修改的全局文件（仅这些）
- `server.py` — `from service_monitor import bp as service_monitor_bp`（占位时就改好）
- `skill/code/requirements.txt` — +paramiko +cryptography
- `skill/code/pytest.ini` — testpaths 加 web/service_monitor/tests
- `deploy/upload_to_server.py` — +RECURSIVE_DIRS_TO_UPLOAD + _upload_dir_recursive
- `.env.example` — +MONITOR_ENC_KEY
- `.gitignore` — +服务监测运行时数据忽略
- `CLAUDE.md` — 项目结构说明

### 已删除
- `skill/code/web/routes/service_monitor.py`（旧占位路由）
- `skill/code/web/templates/service_monitor.html`（旧占位模板）
