# PRD — 左侧菜单布局改造与定时自动巡检

> 版本：v1.0 ｜ 日期：2026-07-20 ｜ 模块 ID：`service-monitor-layout-schedule`
> 依赖：APScheduler 3.x、croniter

---

## 1. 背景与目标

### 1.1 背景

服务监测模块已上线运行，检测模块从 17 个扩展到 30 个，功能入口逐渐增多。当前页面布局存在以下问题：

1. **布局拥挤**：【监测目标】和【巡检报告】挤在 index.html 一个页面，信息密度高
2. **导航不直观**：【目标管理】通过顶部按钮跳转，用户难以快速定位功能入口
3. **扩展性差**：新增功能（如定时任务）无处安放，只能继续往单页堆叠
4. **定时巡检缺失**：当前巡检完全手动触发，无法设置定时自动巡检

### 1.2 目标

1. **左侧菜单布局**：将页面改为左侧固定菜单 + 右侧内容区的经典后台布局，提升导航体验和可扩展性
2. **定时自动巡检**：支持配置定时巡检任务（cron 表达式），到时间自动触发巡检并生成报告
3. **页面拆分**：将【监测目标】和【巡检报告】拆为独立页面，各司其职

### 1.3 用户角色

| 角色 | 权限 |
|------|------|
| **管理员** | 可配置定时任务（增删改启停）、可管理监测目标、可触发巡检、可查看报告 |
| **普通用户** | 仅可查看监测目标、巡检报告、定时任务列表（不可操作） |

---

## 2. 核心概念

| 概念 | 说明 |
|------|------|
| **左侧菜单 (Sidebar)** | 固定在页面左侧的导航菜单，包含所有功能入口 |
| **基础模板 (Base Template)** | Jinja2 模板继承的父模板，包含侧边栏 + 顶部条 + 内容区骨架 |
| **定时任务 (Schedule)** | 一条定时巡检配置：目标 + 套件 + cron 表达式 + 启停状态 |
| **Cron 表达式** | 标准 5 段格式（分 时 日 月 周），如 `0 8 * * *` 表示每天 8:00 |
| **调度器 (Scheduler)** | APScheduler BackgroundScheduler，负责按 cron 表达式触发巡检 |

---

## 3. 功能需求

### 3.1 左侧菜单布局

- [F1] **侧边栏导航**：左侧固定菜单，包含 4 个入口（监测目标、巡检报告、定时任务、目标管理）
- [F2] **当前页高亮**：当前活跃菜单项视觉高亮，用户一目了然当前位置
- [F3] **权限控制**：目标管理菜单项仅管理员可见
- [F4] **移动端适配**：768px 以下侧边栏隐藏，顶部显示汉堡菜单按钮，点击滑出侧边栏
- [F5] **模板继承**：创建共享基础模板，所有页面继承，减少重复代码

### 3.2 页面拆分

- [F6] **监测目标页**：`/service-monitor`，只展示目标卡片网格 + 巡检入口
- [F7] **巡检报告页**：`/service-monitor/reports`，独立展示巡检报告列表（从 index.html 拆出）
- [F8] **报告列表增强**：支持按目标筛选、分页浏览

### 3.3 定时自动巡检

- [F9] **定时任务列表**：`/service-monitor/schedule`，展示所有定时任务（名称、目标、套件、cron、状态、下次执行时间）
- [F10] **创建定时任务**：管理员可创建定时任务（选择目标 + 套件 + cron 表达式）
- [F11] **编辑定时任务**：管理员可修改定时任务的名称、cron 表达式等
- [F12] **启用/禁用**：管理员可切换定时任务的启用/禁用状态
- [F13] **删除定时任务**：管理员可删除定时任务
- [F14] **Cron 辅助**：提供常用 cron 预设（每天/每周/每小时）+ 自定义输入 + 实时校验
- [F15] **自动执行**：到时间自动触发巡检，结果保存为报告
- [F16] **执行状态**：定时任务显示最近执行时间、执行状态、关联报告 ID

### 3.4 预留功能

- [F-预留] **执行历史**：定时任务的完整执行历史记录（本期仅显示最近一次）

---

## 4. 关键设计

### 4.1 布局结构

```
┌──────────┬──────────────────────────────┐
│ 🛠️ 运维  │  ☰  🏠 返回首页  用户名  退出  │
│──────────│──────────────────────────────│
│ 📊 监测  │                              │
│   目标   │                              │
│ 📋 报告  │     主内容区域               │
│ ⏰ 定时   │     {% block content %}      │
│          │     {% endblock %}           │
│ ⚙️ 目标  │                              │
│   管理   │                              │
└──────────┴──────────────────────────────┘
```

| 维度 | 设计 |
|------|------|
| 侧边栏宽度 | 200px，固定定位 |
| 侧边栏背景 | 深色渐变 `#1e293b → #334155` |
| 顶部条 | 保持现有渐变风格，去掉面包屑 |
| 内容区 | `margin-left: 200px`，`max-width: 1100px` |
| 移动端 | 768px 以下侧边栏隐藏，汉堡菜单滑出 |

### 4.2 菜单项定义

| 菜单项 | 路由 | 图标 | 权限 | active_menu 值 |
|--------|------|------|------|----------------|
| 监测目标 | `/service-monitor` | 📊 | 所有用户 | `targets` |
| 巡检报告 | `/service-monitor/reports` | 📋 | 所有用户 | `reports` |
| 定时任务 | `/service-monitor/schedule` | ⏰ | 所有用户 | `schedule` |
| 目标管理 | `/service-monitor/targets` | ⚙️ | 仅管理员 | `manage` |

### 4.3 定时任务数据模型

存储文件：`service_monitor/data/schedules.json`

```json
[{
  "id": "sched_abc12345",
  "name": "每日全量巡检",
  "target_id": "local",
  "target_name": "本机（当前服务器）",
  "suite": "full",
  "cron": "0 8 * * *",
  "enabled": true,
  "last_run_at": null,
  "last_run_status": null,
  "last_report_id": null,
  "next_run_at": "2026-07-21T08:00:00",
  "created_at": "2026-07-20T10:00:00",
  "created_by": "admin"
}]
```

### 4.4 APScheduler 集成方案

| 维度 | 方案 |
|------|------|
| 调度器类型 | `BackgroundScheduler`（APScheduler 3.x） |
| 触发器 | `CronTrigger.from_crontab()` 解析 5 段 cron |
| Job ID | `schedule_{schedule_id}` |
| 初始化时机 | Flask `create_app()` 末尾 |
| 持久化 | 内存 + JSON 文件手动同步，服务重启从 `schedules.json` 重新加载 |
| 执行函数 | 调用 `runner_service.run_inspection_sync()` 同步执行巡检 |

### 4.5 Cron 表达式校验

| 维度 | 方案 |
|------|------|
| 校验库 | `croniter`（轻量 cron 解析库） |
| 前端预设 | 每天 08:00 / 每 6 小时 / 每周一 08:00 / 自定义 |
| 后端校验 | `croniter` 解析失败抛异常，返回友好错误提示 |
| 下次执行时间 | `croniter.get_next()` 计算 |

---

## 5. 架构原则

### 5.1 模板继承体系

```
base.html（共享骨架：侧边栏 + 顶部条 + 内容区）
├── index.html（监测目标）
├── reports.html（巡检报告）
├── schedule.html（定时任务）
├── targets.html（目标管理）
├── run.html（巡检执行）
└── report.html（报告详情）
```

### 5.2 自包含子包原则（不变）

所有代码写在 `skill/code/web/service_monitor/` 子包内，不动 `routes/`、`services/`、`utils/`。
唯一例外：`server.py` 需增加 APScheduler 初始化调用（1 行代码）。

### 5.3 对外接口

| 接入点 | 位置 | 代码 |
|--------|------|------|
| Blueprint 注册 | `server.py` | `from service_monitor import bp; app.register_blueprint(bp)` |
| APScheduler 初始化 | `server.py` | `from service_monitor.services import schedule_service; schedule_service.init_scheduler(app)` |

---

## 6. 关键流程

### 6.1 创建定时任务

1. 管理员在定时任务页点击"新增"
2. 填写任务名、选择目标、选择套件、输入 cron 表达式
3. 前端调用 `POST /api/service-monitor/schedules`
4. `schedule_service.create_schedule()` 校验 cron → 生成 ID → 保存到 `schedules.json`
5. `schedule_service.init_scheduler()` 注册 APScheduler job
6. 返回定时任务详情，前端刷新列表

### 6.2 定时任务自动执行

1. APScheduler 到时间触发 `_execute_scheduled_job()`
2. 调用 `runner_service.run_inspection_sync(target_id, suite)`
3. 巡检执行完成，`report_service.save()` 保存报告
4. 更新 `schedules.json` 中的 `last_run_at` / `last_run_status` / `last_report_id`
5. 更新 `next_run_at`

### 6.3 启停定时任务

1. 管理员点击"禁用/启用"按钮
2. 前端调用 `POST /api/service-monitor/schedules/<id>/toggle`
3. `schedule_service.toggle_enabled()` 更新 `schedules.json`
4. 禁用：`scheduler.remove_job()`；启用：`scheduler.add_job()`

---

## 7. 安全与约束

- 定时任务操作（增删改启停）**仅管理员**可执行
- Cron 表达式需后端校验，防止恶意输入
- 定时巡检执行与手动巡检共享 `runner_service`，并发安全由 `_runs` 注册表保证
- SSH 凭据解密仅在执行时进行，不在定时任务配置中暴露

---

## 8. 决策确认

| 编号 | 决策 |
|------|------|
| Q1 | 布局采用左侧固定侧边栏（非可折叠），移动端用汉堡菜单 |
| Q2 | 定时任务用 APScheduler BackgroundScheduler（非 Celery，避免引入消息队列） |
| Q3 | Cron 表达式用 croniter 校验（APScheduler 内置 CronTrigger 也可解析，但 croniter 提供更友好的错误提示和 next_run 计算） |
| Q4 | 定时任务数据存 JSON 文件（与 targets.json 同级），不用数据库 |
| Q5 | 巡检报告页从 index.html 拆出为独立页面 `/service-monitor/reports` |
| Q6 | 模板继承用 Jinja2 `{% extends %}` + `{% block %}`，创建 `base.html` 共享骨架 |
| Q7 | APScheduler 初始化在 `create_app()` 末尾，不在 blueprint 导入时 |

---

## 9. 验收标准

- [ ] 左侧菜单正确显示 4 个入口，当前页高亮
- [ ] 目标管理菜单仅管理员可见
- [ ] 移动端（768px 以下）汉堡菜单可用，侧边栏滑出/收起
- [ ] 监测目标页只展示目标卡片，巡检报告页独立展示报告列表
- [ ] 管理员可创建/编辑/删除/启停定时任务
- [ ] Cron 表达式校验：合法表达式通过，非法表达式返回友好错误
- [ ] 定时任务到时间自动触发巡检，生成报告
- [ ] 定时任务列表显示下次执行时间、最近执行状态
- [ ] 服务重启后，已启用的定时任务自动恢复调度
- [ ] 部署到 5.60，APScheduler 安装成功，定时巡检全链路通
