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

feat(service-monitor): P3级检测模块补全 + 巡检防重复触发 + 移动端响应式优化

检测项丰富化移植(阶段五):
- 新增 56_fastdfs_check.sh FastDFS功能验证检测(重构版,仅状态检测)
- 新增 57_android_check.sh Android设备检测
- 新增 59_log_export_check.sh 配置日志导出检测
- 新增 60_repair_capability_check.sh 修复能力检测
- check_modules.py 注册 4 个新模块,移除 58_data_backup_check
- display_names.py 追加 85 个 KEY 映射
- parser.py 追加 7 个状态词映射

巡检防重复触发优化(阶段六):
- runner_service.py 新增 get_running_for_target() 查询运行中巡检
- routes.py 新增 /api/service-monitor/run/status/<id> 端点
- routes.py SSE 流接口增加 409 防重检查
- run.html 前端检查运行状态,阻止重复触发
- index.html 巡检按钮增加预检查逻辑

移动端响应式优化(阶段七):
- 9 个页面全面优化移动端布局
- base.html: 顶部栏/侧边栏紧凑化,用户名溢出省略
- index.html: 目标卡片/按钮布局优化
- report.html: 报告头纵向排列,状态文字防竖向换行
- reports/schedule/targets: 筛选栏纵向,表格卡片化
- run.html: 巡检卡片紧凑化,取消按钮全宽
- statistics.html: 概览卡片缩小,趋势筛选纵向

验证:218 个单元测试全绿,5.44 full 套件实测通过
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 d50d0709
# HANDOFF_服务监测 — 服务监测模块:检测项丰富化移植(阶段四完成) # HANDOFF_服务监测 — 服务监测模块:检测项丰富化移植 + 移动端响应式优化
> 最后更新:2026-07-22(P3 级移植完成:文件权限检测 + 定时任务预期检测,检测项丰富化移植全部完成)| 分支:troubleshoot-ai-assistant | 负责人:czj > 最后更新:2026-07-23(移动端响应式样式优化)| 分支:troubleshoot-ai-assistant | 负责人:czj
> 专属交接文档:聚焦服务监测模块(service_monitor)的全部历史与当前状态 > 专属交接文档:聚焦服务监测模块(service_monitor)的全部历史与当前状态
--- ---
...@@ -13,14 +13,21 @@ ...@@ -13,14 +13,21 @@
- **阶段一**:补全缺失项 + 重构 MySQL/Redis 深度检测脚本(见 2.5 节) - **阶段一**:补全缺失项 + 重构 MySQL/Redis 深度检测脚本(见 2.5 节)
- **阶段二**:移植安全合规深度检测 + 中间件连接检测模块(见 2.7-2.11 节) - **阶段二**:移植安全合规深度检测 + 中间件连接检测模块(见 2.7-2.11 节)
- **阶段三**:移植 P2 级检测模块:系统类型识别 + 平台服务检测 + 配置IP检测 + Console配置检测(见 2.12-2.15 节) - **阶段三**:移植 P2 级检测模块:系统类型识别 + 平台服务检测 + 配置IP检测 + Console配置检测(见 2.12-2.15 节)
- **阶段四(本次)**:移植 P3 级检测模块:文件权限检测 + 定时任务预期检测 - **阶段四**:移植 P3 级检测模块:文件权限检测 + 定时任务预期检测
- **阶段五**:移植剩余 P3 级检测模块:FastDFS功能验证 + Android设备检测 + 数据备份检测 + 配置日志导出检测 + 修复能力检测
- **阶段六**:巡检防重复触发优化 + 数据备份检测超时修复
- **阶段七(本次)**:模块清理 + 5.44 验证 + 移动端响应式样式优化
本轮会话完成了两个新检测模块的移植 本轮会话完成了以下工作
1. **文件权限检测**(54_file_permission_check.sh):检查关键文件/目录的权限是否正确(run.sh 应可执行、配置文件不应过宽),只检测不修复 1. **模块清理**:移除 58_data_backup_check(数据备份检测)模块
2. **定时任务预期检测**(55_cron_expected.sh):检查预期 crontab 条目是否存在(MySQL备份、ujava2守护),只检测不修改 2. **FastDFS 超时修复**:重构 56_fastdfs_check.sh,移除上传下载功能测试,只保留状态检测
3. **5.44 实际验证**:在 5.44 服务器执行 full 套件巡检,10 个新模块(50-60)全部正常执行
4. **移动端响应式优化**:9 个页面全面优化移动端布局
> **检测项丰富化移植全部完成**:阶段一至四共新增 10 个检测模块,full 套件模块数 30 → 36 > **检测项丰富化移植全部完成**:阶段一至五共新增 15 个检测模块,full 套件模块数 30 → 42(移除 58 后)
>
> **设计原则**:所有原版依赖 Windows 本地工具(pscp.exe/adb.exe)的模块,均改造为远程巡检检测(检测就绪状态/能力,不执行本地文件操作);所有修复类操作均改为只检测不修复
--- ---
...@@ -357,18 +364,236 @@ ...@@ -357,18 +364,236 @@
**验证结果**:218 个测试全绿 ✅ 语法检查通过 ✅ **已部署到 5.60** **验证结果**:218 个测试全绿 ✅ 语法检查通过 ✅ **已部署到 5.60**
### 2.18 FastDFS功能验证检测模块 ✅
**需求文档**`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`
**参考来源**`platform-auto-test/临时目录/服务监测/check_fdfs_arm.sh` + `check_fdfs_x86.sh`
**核心实现**
- `56_fastdfs_check.sh`(新增):FastDFS 功能验证检测脚本
- 容器检测:自动检测 Tracker/Storage 容器名称
- 端口检测:22122(Tracker) + 23000(Storage) + 8888(Nginx)
- 进程检测:容器内 fdfs_tracked/fdfs_storaged 进程
- 配置检测:/etc/fdfs/client.conf 存在性 + tracker_server 配置
- 功能测试:创建测试文件 → 上传 → 下载 → 完整性验证(MD5) → 清理
- HTTP 测试:8888 端口访问测试
- **只检测不修复**
- `display_names.py`:新增 20 个 `FDFS_*` KEY 映射
- `check_modules.py`:注册 `56_fastdfs_check` 模块
**验证结果**:218 个测试全绿 ✅ 语法检查通过 ✅
### 2.19 Android设备检测模块 ✅
**需求文档**`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`
**参考来源**`platform-auto-test/临时目录/服务监测/modules/AndroidCheck.psm1`
**核心实现**
- `57_android_check.sh`(新增):Android 设备检测脚本
- ADB 工具检测:检查系统 PATH + 常见安装位置
- ADB 版本获取:`adb version`
- ADB 服务状态:检查 adb server 是否运行
- 设备连接检测:`adb devices` 获取已连接设备列表
- 网络 ADB 能力:检查 adb tcpip 支持情况
- **只检测不操作**(原版依赖 adb.exe + Windows 本地操作,改造为远程检测)
- `display_names.py`:新增 12 个 `ANDROID_*` KEY 映射
- `check_modules.py`:注册 `57_android_check` 模块
**验证结果**:218 个测试全绿 ✅ 语法检查通过 ✅
### 2.20 数据备份检测模块 ✅
**需求文档**`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`
**参考来源**`platform-auto-test/临时目录/服务监测/modules/DataBackup.psm1` + `dump_one_db.sh` + `list_user_dbs.sh`
**核心实现**
- `58_data_backup_check.sh`(新增):数据备份检测脚本
- 备份脚本检测:检查常见备份脚本路径是否存在
- MySQL 备份检测:检查备份目录 + 最近备份文件时间/大小
- mysqldump 能力:检查宿主机或容器内 mysqldump 命令
- 应用备份检测:检查应用备份目录
- 定时备份任务:检查 crontab 中是否有备份任务
- 磁盘空间检测:检查备份目录所在分区可用空间
- **只检测不执行**(原版依赖 pscp.exe 下载到本地,改造为检测备份能力)
- `display_names.py`:新增 21 个 `BACKUP_*` KEY 映射
- `check_modules.py`:注册 `58_data_backup_check` 模块
**验证结果**:218 个测试全绿 ✅ 语法检查通过 ✅
### 2.21 配置日志导出检测模块 ✅
**需求文档**`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`
**参考来源**`platform-auto-test/临时目录/服务监测/modules/LogExport.psm1`
**核心实现**
- `59_log_export_check.sh`(新增):配置日志导出检测脚本
- 配置文件检测:新平台 15 个配置路径 + 传统平台 8 个配置路径
- 日志目录检测:新平台 13 个日志路径 + 传统平台 5 个日志路径
- 关键日志大小:检查 ERROR 日志文件大小
- 日志轮转配置:检查 logrotate.d 中是否有应用配置
- 导出能力检测:检查 tar + gzip 命令可用性
- **只检测不导出**(原版依赖 pscp.exe 下载到本地,改造为检测日志可获取性)
- `display_names.py`:新增 21 个 `LOGEXP_*` KEY 映射
- `check_modules.py`:注册 `59_log_export_check` 模块
**验证结果**:218 个测试全绿 ✅ 语法检查通过 ✅
### 2.22 修复能力检测模块 ✅
**需求文档**`Docs/需求文档/服务监测/PRD_需求文档_P3级移植.md`
**参考来源**`platform-auto-test/临时目录/服务监测/lib/shell/issue_handler.sh`
**核心实现**
- `60_repair_capability_check.sh`(新增):修复能力检测脚本
- 修复脚本检测:检查 issue_handler.sh 等修复脚本是否存在
- DNS 修复能力:检查 resolv.conf 可写性 + NetworkManager
- NTP 修复能力:检查 chrony/ntp 服务 + 配置文件可写性
- 端口修复能力:检查 firewalld/iptables 工具
- 权限修复能力:检查 chmod/chown + 关键目录可写性
- Docker 修复能力:检查 docker 命令 + 权限
- 服务重启能力:检查 systemctl/service 命令
- 日志清理能力:检查 logrotate/lsof/find 工具
- **只检测不修复**(原版是交互式修复工具,改造为检测修复工具链就绪状态)
- `display_names.py`:新增 25 个 `REPAIR_*` KEY 映射
- `parser.py`:补充新模块状态词映射(yes/no/上传失败/下载失败等)
- `check_modules.py`:注册 `60_repair_capability_check` 模块
**验证结果**:218 个测试全绿 ✅ 语法检查通过 ✅
### 2.23 巡检防重复触发优化 ✅
**问题**:用户执行全量巡检后返回首页再次进来,又重新触发巡检
**问题根因**
- `run.html:175``DOMContentLoaded` 无条件触发 `startInspection()`,页面每次加载都自动开始巡检
- 后端 `runner_service._runs``run_id` 索引,不按 `target_id` 索引,无法查询"该目标是否正在运行"
- 首页巡检按钮直接链接到 `/service-monitor/run/<id>`,每次都是全新页面导航
**修复内容**
1. **后端 `runner_service.py`**:新增 `get_running_for_target(target_id)` 函数
- 遍历 `_runs` 字典,查找 `target_id` 匹配且 `finished=False` 的运行记录
- 返回 `{"run_id", "target_id", "suite", "done", "total", "current"}``None`
2. **后端 `routes.py`**
- 新增 `GET /api/service-monitor/run/status/<target_id>` API 端点
- 返回 `{"running": true, ...}``{"running": false}`
- SSE 流接口增加 409 防重检查:同一目标已有未完成巡检时返回 409 错误
3. **前端 `run.html`**
- `DOMContentLoaded` 改为先调用状态 API 检查
- 如果已有巡检在运行:显示提示"该目标已有巡检正在运行 (x/y),请等待完成或取消后重试",不触发新巡检
- 如果没有运行中巡检:正常调用 `startInspection()`
4. **前端 `index.html`**
- 巡检按钮增加 `checkBeforeRun(href, targetId)` 函数
- 点击时先调用状态 API 检查,运行中则弹窗提示,否则正常跳转
- 修复 `e.currentTarget` 在异步回调后为 null 导致的 TypeError
**验证结果**:218 个测试全绿 ✅ **已部署到 5.60**
### 2.24 数据备份检测超时修复 ✅
**问题**:58_data_backup_check.sh 执行超过 5 分钟未完成
**问题根因**
- `find "$backup_dir" -name "*.sql*"` 无深度限制(`-maxdepth`),在大型备份目录下遍历整个目录树
- `find "$app_backup_dir" -type f | wc -l` 统计所有文件数,目录大时很慢
- `find ... -printf` 是 GNU find 扩展,部分系统不支持
**修复内容**
- 所有 `find` 命令添加 `-maxdepth 3` 限制搜索深度
- 兼容不支持 GNU `-printf` 的系统,用 `xargs ls -t` 备选方案查找最新文件
**验证结果**:语法检查通过 ✅ 218 个测试全绿 ✅ **已部署到 5.60**
### 2.25 数据备份检测模块移除 ✅
**原因**:58_data_backup_check.sh 在实际运行中超时,且备份能力检测价值有限
**实现内容**
- 删除 `58_data_backup_check.sh` 脚本文件(本地 + 5.60 服务器)
- 移除 `check_modules.py``58_data_backup_check` 模块注册
- 移除 `display_names.py` 中 21 个 `BACKUP_*` KEY 映射
**验证结果**:218 个测试全绿 ✅ **已部署到 5.60** ✅ full 套件模块数 43 → 42
### 2.26 FastDFS功能验证超时修复 ✅
**问题**:56_fastdfs_check.sh 执行超时,原脚本"功能测试"部分(上传→下载→MD5验证→清理)在异常情况下卡死
**问题根因**
- `fdfs_upload_file` / `fdfs_download_file` 命令无超时机制
- 服务异常时会无限等待
**修复内容**
- 重构 `56_fastdfs_check.sh`,移除上传下载功能测试,只保留状态检测
- 检测项:容器状态、端口检测(22122/23000/8888)、进程检测、配置文件检测、HTTP 探测
- HTTP 探测加 `--connect-timeout 3 --max-time 5` 超时保护
- 端口检测用宿主机 `ss` 替代容器内 `netstat`
- display_names.py 精简 KEY(移除上传/下载/完整性相关,保留 16 个)
**验证结果**:218 个测试全绿 ✅ **已部署到 5.60** ✅ 5.44 实测 11 项全正常
### 2.27 5.44 新模块实测验证 ✅
**验证方式**:在 5.44 服务器执行 full 套件巡检(报告 ID: 20260723_020607_63127b)
**验证结果**
- 检测项总数:521(上次 510,新增 11 项)
- 正常 500,警告 8,严重 13
- 10 个新模块全部正常执行,无超时/报错
| 模块 | 结果 |
|------|------|
| 50 系统类型识别 | ✅ 3项全正常 |
| 51 平台服务检测 | ⚠️ 2项警告(MQTT/Message 未监听,真实状态) |
| 52 配置IP检测 | ⚠️ 1项警告(有未授权IP) |
| 53 Console配置检测 | ✅ 4项全正常 |
| 54 文件权限检测 | ✅ 4项全正常 |
| 55 定时任务预期检测 | ⚠️ 1项警告(定时任务缺失) |
| 56 FastDFS功能验证 | ✅ 11项全正常(修复后首次验证通过) |
| 57 Android设备检测 | ✅ 8项全正常 |
| 59 配置日志导出检测 | ⚠️ 1项警告(logrotate 未配置) |
| 60 修复能力检测 | ⚠️ 1项警告(修复脚本不存在) |
### 2.28 移动端响应式样式优化 ✅
**问题**:移动端排版在多个页面存在拥挤、溢出、文字竖向换行等问题
**优化内容**(9 个页面):
| 页面 | 优化点 |
|------|--------|
| base.html | 顶部栏 padding 收窄、用户名溢出省略、退出按钮缩小、侧边栏菜单紧凑化 |
| index.html | 目标卡片 padding 收窄、巡检按钮纵向堆叠 |
| reports.html | 筛选栏纵向排列、报告行紧凑化、移动端隐藏【查看】按钮(点名称进入) |
| report.html | 报告头纵向排列、导出按钮全宽、异常项/明细行紧凑化、检测项状态文字 `white-space:nowrap` 防竖向换行 |
| statistics.html | 筛选栏纵向、概览卡片缩小、图表 padding 收窄、趋势筛选纵向 |
| schedule.html | 表格卡片化、操作按钮缩小、弹窗全宽、重复周期纵向 |
| targets.html | 表格卡片化、操作按钮缩小、弹窗全宽 |
| notification.html | 表单紧凑化、输入框缩小、保存按钮全宽 |
| run.html | 巡检卡片紧凑化、取消按钮全宽、模块列表字号缩小 |
**验证结果**:218 个测试全绿 ✅ **已部署到 5.60**
--- ---
## 3. 当前卡在哪 ## 3. 当前卡在哪
**无卡点**。阶段一至四全部完成,检测项丰富化移植全部完成。 **无卡点**。阶段一至全部完成。
**当前状态** **当前状态**
- 服务健康检查:status: ok, scheduler: running ✅ - 服务健康检查:status: ok, scheduler: running ✅
- 报告免登访问正常工作 ✅ - 报告免登访问正常工作 ✅
- 统计模块正常工作 ✅ - 统计模块正常工作 ✅
- 检测项趋势图表友好提示正常 ✅ - 检测项趋势图表友好提示正常 ✅
- display_names.py 已补全缺失 KEY(539+52+36+35+10=672 条)✅ - display_names.py 已补全缺失 KEY(移除 BACKUP_*/FDFS_* 部分 KEY)✅
- 23_mysql_depth.sh / 25_redis_depth.sh 已重构为 output_result 格式 ✅ - 23_mysql_depth.sh / 25_redis_depth.sh 已重构为 output_result 格式 ✅
- 46_security_deep.sh 安全合规深度检测模块 ✅ - 46_security_deep.sh 安全合规深度检测模块 ✅
- 39_middleware_conn.sh 中间件连接检测模块 ✅ - 39_middleware_conn.sh 中间件连接检测模块 ✅
...@@ -378,13 +603,19 @@ ...@@ -378,13 +603,19 @@
- 53_config_console_check.sh Console配置检测模块 ✅ - 53_config_console_check.sh Console配置检测模块 ✅
- 54_file_permission_check.sh 文件权限检测模块 ✅ - 54_file_permission_check.sh 文件权限检测模块 ✅
- 55_cron_expected.sh 定时任务预期检测模块 ✅ - 55_cron_expected.sh 定时任务预期检测模块 ✅
- full 套件 36 个模块(原 30 + 新 6)✅ - 56_fastdfs_check.sh FastDFS功能验证检测模块 ✅(已重构,移除超时功能测试)
- 57_android_check.sh Android设备检测模块 ✅
- ~~58_data_backup_check.sh~~ ❌ 已移除
- 59_log_export_check.sh 配置日志导出检测模块 ✅
- 60_repair_capability_check.sh 修复能力检测模块 ✅
- full 套件 42 个模块(原 30 + 新增 12)✅
- 218 个单元测试全绿 ✅ - 218 个单元测试全绿 ✅
- **已部署到 5.60** ✅(阶段一至四全部部署,36 个模块文件完整 - **已部署到 5.60** ✅(阶段一至七全部部署
- **5.44 实际运行验证通过**(阶段一+二,full 套件,中间件全部"运行中",安全模块发现真实问题)✅ - **5.44 实际运行验证通过**(阶段一至五,full 套件,10 个新模块全部正常)✅
- 状态等级误判已修复(LEVEL KEY 对齐 + 状态词映射兜底)✅ - 状态等级误判已修复(LEVEL KEY 对齐 + 状态词映射兜底)✅
- 中间件端口检测逻辑已修复(服务 ping 优先,不依赖容器内 ss)✅ - 中间件端口检测逻辑已修复(服务 ping 优先,不依赖容器内 ss)✅
- 5.44 目标凭据已配置(MySQL/Redis 密码加密注入)✅ - 5.44 目标凭据已配置(MySQL/Redis 密码加密注入)✅
- **移动端响应式优化完成**(9 个页面全部适配)✅
--- ---
...@@ -392,12 +623,15 @@ ...@@ -392,12 +623,15 @@
| 优先级 | 任务 | 说明 | | 优先级 | 任务 | 说明 |
|--------|------|------| |--------|------|------|
| ~~P1~~ | ~~Web 界面验证实际运行~~ | ✅ 已完成(5.44 full 套件检测通过,发现问题项正确标记严重) | | ~~P1~~ | ~~Web 界面验证实际运行~~ | ✅ 已完成(5.44 full 套件检测通过) |
| ~~P1~~ | ~~统计页面验证~~ | ✅ 已完成(新检测项出现在统计页面选择器中) | | ~~P1~~ | ~~统计页面验证~~ | ✅ 已完成 |
| ~~P2~~ | ~~继续移植 P2 优先级模块~~ | ✅ 已完成(系统类型识别/平台服务检测/配置IP检测/Console配置检测) | | ~~P2~~ | ~~继续移植 P2 优先级模块~~ | ✅ 已完成 |
| ~~P3~~ | ~~继续移植 P3 优先级模块~~ | ✅ 已完成(文件权限检测/定时任务预期检测) | | ~~P3~~ | ~~继续移植 P3 优先级模块~~ | ✅ 已完成 |
| P1 | 合并 troubleshoot-ai-assistant 分支到 master | 检测项丰富化移植全部完成,可合并 | | ~~P1~~ | ~~巡检防重复触发优化~~ | ✅ 已完成 |
| P2 | 在 5.44 实际运行验证新模块 | 验证 50-55 模块在真实环境运行正确 | | ~~P1~~ | ~~数据备份检测超时修复~~ | ❌ 已移除该模块 |
| ~~P1~~ | ~~合并 troubleshoot-ai-assistant 分支到 master~~ | ❌ 不合并,长期独立分支维护 |
| ~~P2~~ | ~~在 5.44 实际运行验证新模块~~ | ✅ 已完成(10 个新模块全部正常) |
| ~~P1~~ | ~~移动端响应式优化~~ | ✅ 已完成(9 个页面全部适配) |
| 持续 | 更新 HANDOFF_服务监测文档 | 每次会话结束前更新进度 | | 持续 | 更新 HANDOFF_服务监测文档 | 每次会话结束前更新进度 |
--- ---
...@@ -510,6 +744,9 @@ ...@@ -510,6 +744,9 @@
| 日期 | Commit | 说明 | | 日期 | Commit | 说明 |
|------|--------|------| |------|--------|------|
| 2026-07-23 | 待提交 | fix(service-monitor): 移除数据备份检测+FastDFS超时修复+移动端响应式优化 |
| 2026-07-23 | 待提交 | fix(service-monitor): 巡检防重复触发优化+数据备份检测超时修复 |
| 2026-07-22 | 待提交 | feat(service-monitor): P3级补全-FastDFS/Android/数据备份/日志导出/修复能力检测 |
| 2026-07-22 | `664d318d` | feat(service-monitor): P3级移植-文件权限检测/定时任务预期检测 | | 2026-07-22 | `664d318d` | feat(service-monitor): P3级移植-文件权限检测/定时任务预期检测 |
| 2026-07-22 | `dc42cffe` | feat(service-monitor): P2级移植-系统类型识别/平台服务检测/配置IP检测/Console配置检测 | | 2026-07-22 | `dc42cffe` | feat(service-monitor): P2级移植-系统类型识别/平台服务检测/配置IP检测/Console配置检测 |
| 2026-07-22 | `c0200115` | feat(service-monitor): 新增安全合规/中间件检测模块并修复状态等级误判 | | 2026-07-22 | `c0200115` | feat(service-monitor): 新增安全合规/中间件检测模块并修复状态等级误判 |
...@@ -584,14 +821,18 @@ feat(service-monitor): 新增安全合规/中间件检测模块并修复状态 ...@@ -584,14 +821,18 @@ feat(service-monitor): 新增安全合规/中间件检测模块并修复状态
| `service_monitor/utils/parser.py` | 追加 2 个状态词映射(监听/未监听) | | `service_monitor/utils/parser.py` | 追加 2 个状态词映射(监听/未监听) |
| `service_monitor/assets/config.sh.template` | 追加服务匹配模式 + 端口列表 + SERVER_IP 配置 | | `service_monitor/assets/config.sh.template` | 追加服务匹配模式 + 端口列表 + SERVER_IP 配置 |
### 本次修改文件(阶段四:P3 级移植 ### 本次修改文件(阶段五:P3 级补全
| 文件 | 改动 | | 文件 | 改动 |
|------|------| |------|------|
| `service_monitor/assets/system/54_file_permission_check.sh` | 新建 - 文件权限检测脚本(5 个检测项) | | `service_monitor/assets/service/56_fastdfs_check.sh` | 新建 - FastDFS功能验证检测脚本(上传/下载/完整性测试) |
| `service_monitor/assets/system/55_cron_expected.sh` | 新建 - 定时任务预期检测脚本(5 个检测项) | | `service_monitor/assets/service/57_android_check.sh` | 新建 - Android设备检测脚本(adb可用性+设备连接状态) |
| `service_monitor/utils/check_modules.py` | 追加 2 个 CheckModule 条目(54/55) | | `service_monitor/assets/system/58_data_backup_check.sh` | 新建 - 数据备份检测脚本(备份脚本+备份时间+备份能力) |
| `service_monitor/utils/display_names.py` | 追加 10 个 KEY 映射(`FILE_PERM_*` 5 个 + `CRON_EXPECTED_*` 5 个) | | `service_monitor/assets/system/59_log_export_check.sh` | 新建 - 配置日志导出检测脚本(配置文件+日志目录+导出能力) |
| `service_monitor/assets/system/60_repair_capability_check.sh` | 新建 - 修复能力检测脚本(修复工具链就绪状态) |
| `service_monitor/utils/check_modules.py` | 追加 5 个 CheckModule 条目(56/57/58/59/60) |
| `service_monitor/utils/display_names.py` | 追加 99 个 KEY 映射(FDFS/ANDROID/BACKUP/LOGEXP/REPAIR) |
| `service_monitor/utils/parser.py` | 追加新模块状态词映射 |
### 新增文档 ### 新增文档
......
#!/bin/bash
# 56_fastdfs_check.sh - FastDFS 功能验证检测
# 只检测服务状态,不执行实际上传下载操作(避免超时)
output_result() {
local key="$1"
local value="$2"
echo "${key}: ${value}"
}
# 从 config.sh 读取配置
CONTAINERS["tracker"]="tracker|utracker"
CONTAINERS["storage"]="storage|ustorage"
# 自动检测 FastDFS 容器
detect_containers() {
local tracker_container=""
local storage_container=""
# Tracker 容器
tracker_container=$(docker ps --format "{{.Names}}" | grep -iE "${CONTAINERS[tracker]}" | head -1)
if [[ -z "$tracker_container" ]]; then
tracker_container=$(docker ps -a --format "{{.Names}}" | grep -iE "${CONTAINERS[tracker]}" | head -1)
fi
# Storage 容器(可能与 Tracker 是同一个)
storage_container=$(docker ps --format "{{.Names}}" | grep -iE "${CONTAINERS[storage]}" | head -1)
if [[ -z "$storage_container" ]]; then
storage_container=$(docker ps -a --format "{{.Names}}" | grep -iE "${CONTAINERS[storage]}" | head -1)
fi
echo "$tracker_container|$storage_container"
}
# 主检测逻辑
main() {
local containers_raw
containers_raw=$(detect_containers)
local tracker_container="${containers_raw%%|*}"
local storage_container="${containers_raw##*|}"
# 1. 容器状态
if [[ -n "$tracker_container" ]]; then
local tracker_status
tracker_status=$(docker inspect -f '{{.State.Status}}' "$tracker_container" 2>/dev/null || echo "unknown")
output_result "FDFS_CONTAINER_TRACKER" "$tracker_container ($tracker_status)"
else
output_result "FDFS_CONTAINER_TRACKER" "未检测到"
fi
if [[ -n "$storage_container" ]] && [[ "$storage_container" != "$tracker_container" ]]; then
local storage_status
storage_status=$(docker inspect -f '{{.State.Status}}' "$storage_container" 2>/dev/null || echo "unknown")
output_result "FDFS_CONTAINER_STORAGE" "$storage_container ($storage_status)"
else
output_result "FDFS_CONTAINER_STORAGE" "与Tracker同容器"
fi
# 2. 端口检测(使用宿主机 ss,不依赖容器内命令)
local ports_ok=0
local ports_total=3
local ports_detail=""
# 22122 - Tracker
if ss -tln 2>/dev/null | grep -q ":22122 "; then
((ports_ok++))
ports_detail+="22122(Tracker):监听 "
else
ports_detail+="22122(Tracker):未监听 "
fi
# 23000 - Storage
if ss -tln 2>/dev/null | grep -q ":23000 "; then
((ports_ok++))
ports_detail+="23000(Storage):监听 "
else
ports_detail+="23000(Storage):未监听 "
fi
# 8888 - Nginx HTTP
if ss -tln 2>/dev/null | grep -q ":8888 "; then
((ports_ok++))
ports_detail+="8888(HTTP):监听"
else
ports_detail+="8888(HTTP):未监听"
fi
output_result "FDFS_PORTS_OK" "$ports_ok"
output_result "FDFS_PORTS_TOTAL" "$ports_total"
output_result "FDFS_PORTS_DETAIL" "$ports_detail"
if [[ $ports_ok -eq $ports_total ]]; then
output_result "FDFS_PORTS_LEVEL" "正常"
elif [[ $ports_ok -gt 0 ]]; then
output_result "FDFS_PORTS_LEVEL" "警告"
else
output_result "FDFS_PORTS_LEVEL" "严重"
fi
# 3. 进程检测(在 Tracker 容器内检查)
local target_container="${tracker_container:-$storage_container}"
if [[ -n "$target_container" ]]; then
local process_status=""
# 检查 fdfs_trackerd 进程
if docker exec "$target_container" ps aux 2>/dev/null | grep -q "[f]dfs_trackerd"; then
process_status+="trackerd:运行 "
else
process_status+="trackerd:未运行 "
fi
# 检查 fdfs_storaged 进程
if docker exec "$target_container" ps aux 2>/dev/null | grep -q "[f]dfs_storaged"; then
process_status+="storaged:运行"
else
process_status+="storaged:未运行"
fi
output_result "FDFS_PROCESS_STATUS" "$process_status"
if echo "$process_status" | grep -q "未运行"; then
output_result "FDFS_PROCESS_LEVEL" "警告"
else
output_result "FDFS_PROCESS_LEVEL" "正常"
fi
# 4. 配置文件检测
local client_conf="/etc/fdfs/client.conf"
if docker exec "$target_container" test -f "$client_conf" 2>/dev/null; then
output_result "FDFS_CONFIG_EXISTS" "yes"
local tracker_server
tracker_server=$(docker exec "$target_container" grep "^tracker_server" "$client_conf" 2>/dev/null | head -1 | awk -F= '{print $2}')
output_result "FDFS_TRACKER_SERVER" "${tracker_server:-未配置}"
output_result "FDFS_CONFIG_LEVEL" "正常"
else
output_result "FDFS_CONFIG_EXISTS" "no"
output_result "FDFS_TRACKER_SERVER" "配置文件不存在"
output_result "FDFS_CONFIG_LEVEL" "警告"
fi
else
output_result "FDFS_PROCESS_STATUS" "无容器"
output_result "FDFS_PROCESS_LEVEL" "严重"
output_result "FDFS_CONFIG_EXISTS" "no"
output_result "FDFS_TRACKER_SERVER" "无容器"
output_result "FDFS_CONFIG_LEVEL" "严重"
fi
# 5. HTTP 访问测试(简单探测,有超时)
if ss -tln 2>/dev/null | grep -q ":8888 "; then
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 5 "http://127.0.0.1:8888/" 2>/dev/null || echo "000")
if [[ "$http_code" =~ ^(200|403|404)$ ]]; then
# 200=正常, 403/404=服务在运行只是路径问题
output_result "FDFS_HTTP_STATUS" "服务响应"
output_result "FDFS_HTTP_CODE" "$http_code"
output_result "FDFS_HTTP_LEVEL" "正常"
elif [[ "$http_code" == "000" ]]; then
output_result "FDFS_HTTP_STATUS" "连接失败"
output_result "FDFS_HTTP_CODE" "timeout"
output_result "FDFS_HTTP_LEVEL" "警告"
else
output_result "FDFS_HTTP_STATUS" "异常响应"
output_result "FDFS_HTTP_CODE" "$http_code"
output_result "FDFS_HTTP_LEVEL" "警告"
fi
else
output_result "FDFS_HTTP_STATUS" "端口未监听"
output_result "FDFS_HTTP_CODE" "N/A"
output_result "FDFS_HTTP_LEVEL" "严重"
fi
# 6. 总体状态
if [[ $ports_ok -eq $ports_total ]] && [[ -n "$target_container" ]]; then
output_result "FDFS_STATUS" "正常"
output_result "FDFS_STATUS_LEVEL" "正常"
elif [[ $ports_ok -gt 0 ]]; then
output_result "FDFS_STATUS" "部分异常"
output_result "FDFS_STATUS_LEVEL" "警告"
else
output_result "FDFS_STATUS" "服务异常"
output_result "FDFS_STATUS_LEVEL" "严重"
fi
}
main
#!/bin/bash
################################################################################
# 57_android_check.sh — Android 设备检测
# 功能: 检测 adb 可用性、设备连接状态
# 参考: AndroidCheck.psm1
# 日期: 2026-07-22
# 说明: 只检测不操作,检查 ADB 服务和设备连接状态
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== ADB 工具检测 ====================
check_adb_available() {
local adb_path=""
local adb_ok="no"
# 1. 检查系统 PATH 中的 adb
if command -v adb >/dev/null 2>&1; then
adb_path=$(command -v adb)
adb_ok="yes"
fi
# 2. 检查常见安装位置
if [ "$adb_ok" = "no" ]; then
local adb_dirs=(
"/usr/local/bin/adb"
"/usr/bin/adb"
"/opt/android-sdk/platform-tools/adb"
"$HOME/Android/Sdk/platform-tools/adb"
"/data/tools/adb"
)
for dir in "${adb_dirs[@]}"; do
if [ -x "$dir" ]; then
adb_path="$dir"
adb_ok="yes"
break
fi
done
fi
output_result "ANDROID_ADB_EXISTS" "$adb_ok"
output_result "ANDROID_ADB_PATH" "${adb_path:-未找到}"
if [ "$adb_ok" = "yes" ]; then
# 获取 adb 版本
local adb_version
adb_version=$(adb version 2>/dev/null | head -1 || echo "未知")
output_result "ANDROID_ADB_VERSION" "$adb_version"
output_result "ANDROID_ADB_LEVEL" "正常"
else
output_result "ANDROID_ADB_VERSION" "未安装"
output_result "ANDROID_ADB_LEVEL" "警告"
fi
ANDROID_ADB_OK="$adb_ok"
}
# ==================== ADB 服务状态检测 ====================
check_adb_server() {
if [ "$ANDROID_ADB_OK" != "yes" ]; then
output_result "ANDROID_SERVER_STATUS" "ADB未安装"
output_result "ANDROID_SERVER_LEVEL" "警告"
return
fi
local server_running="no"
# 检查 adb server 是否运行
if pgrep -f "adb server" >/dev/null 2>&1 || pgrep -x "adb" >/dev/null 2>&1; then
server_running="yes"
fi
# 尝试启动 server(仅检测,不持续运行)
adb start-server >/dev/null 2>&1
if [ $? -eq 0 ]; then
server_running="yes"
fi
output_result "ANDROID_SERVER_STATUS" "$server_running"
if [ "$server_running" = "yes" ]; then
output_result "ANDROID_SERVER_LEVEL" "正常"
else
output_result "ANDROID_SERVER_LEVEL" "警告"
fi
}
# ==================== 设备连接检测 ====================
check_connected_devices() {
if [ "$ANDROID_ADB_OK" != "yes" ]; then
output_result "ANDROID_DEVICE_COUNT" "0"
output_result "ANDROID_DEVICE_LIST" "[]"
output_result "ANDROID_DEVICE_LEVEL" "警告"
return
fi
local device_count=0
local device_list=""
# 获取设备列表
local devices_output
devices_output=$(adb devices 2>/dev/null | grep -v "List of devices" | grep -v "^$" || echo "")
if [ -n "$devices_output" ]; then
while IFS= read -r line; do
local device_id device_status
device_id=$(echo "$line" | awk '{print $1}')
device_status=$(echo "$line" | awk '{print $2}')
if [ -n "$device_id" ] && [ "$device_id" != "daemon" ]; then
device_count=$((device_count + 1))
if [ -n "$device_list" ]; then
device_list="${device_list},"
fi
device_list="${device_list}{\"id\":\"${device_id}\",\"status\":\"${device_status}\"}"
fi
done <<< "$devices_output"
fi
output_result "ANDROID_DEVICE_COUNT" "$device_count"
output_result "ANDROID_DEVICE_LIST" "[${device_list}]"
if [ "$device_count" -gt 0 ]; then
output_result "ANDROID_DEVICE_LEVEL" "正常"
else
output_result "ANDROID_DEVICE_LEVEL" "警告"
fi
}
# ==================== 网络设备连接能力检测 ====================
check_network_adb_capability() {
if [ "$ANDROID_ADB_OK" != "yes" ]; then
output_result "ANDROID_NETWORK_CAPABLE" "no"
output_result "ANDROID_NETWORK_LEVEL" "警告"
return
fi
# 检查 adb 是否支持网络连接(tcpip)
local network_capable="yes"
# adb tcpip 命令存在性检查
if adb help 2>/dev/null | grep -q "tcpip"; then
network_capable="yes"
fi
output_result "ANDROID_NETWORK_CAPABLE" "$network_capable"
output_result "ANDROID_NETWORK_LEVEL" "正常"
}
# ==================== 主检测流程 ====================
main() {
log_info "开始 Android 设备检测..."
set +e
# 1. ADB 工具检测
check_adb_available
# 2. ADB 服务状态检测
check_adb_server
# 3. 已连接设备检测
check_connected_devices
# 4. 网络 ADB 能力检测
check_network_adb_capability
# 5. 综合状态
if [ "$ANDROID_ADB_OK" = "yes" ]; then
output_result "ANDROID_STATUS" "ADB已就绪"
output_result "ANDROID_STATUS_LEVEL" "正常"
else
output_result "ANDROID_STATUS" "ADB未安装"
output_result "ANDROID_STATUS_LEVEL" "警告"
fi
log_info "Android 设备检测完成"
}
main
\ No newline at end of file
#!/bin/bash
################################################################################
# 59_log_export_check.sh — 配置日志导出检测
# 功能: 检测关键配置文件和日志目录是否存在、大小、最近修改时间
# 参考: LogExport.psm1
# 日期: 2026-07-22
# 说明: 只检测不导出,验证配置和日志可获取性
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== 平台类型识别 ====================
detect_platform_type() {
[ -d "/data/services" ] && echo "new" || echo "old"
}
# ==================== 配置文件检测 ====================
check_config_files() {
local platform_type
platform_type=$(detect_platform_type)
local total=0
local exists=0
local missing_list=""
local -a config_paths
if [ "$platform_type" = "new" ]; then
config_paths=(
"/data/services/web/pc/pc-vue2-ai/static/config.json"
"/data/services/web/pc/pc-vue2-backstage/static/config.json"
"/data/services/web/pc/pc-vue2-main/static/config.json"
"/data/services/web/pc/pc-vue2-meetingV2/static/config.json"
"/data/services/api/auth/auth-sso-auth/config"
"/data/services/api/auth/auth-sso-gateway/config"
"/data/services/api/auth/auth-sso-system/config"
"/data/services/api/java-meeting/java-meeting2.0/config"
"/data/services/api/java-meeting/java-meeting-extapi/config"
"/data/services/api/python-cmdb/setting.conf"
"/data/middleware/nginx/config"
"/data/middleware/redis/config"
"/data/middleware/emqx/config"
"/data/middleware/mysql/conf"
"/data/middleware/nacos/conf"
)
else
config_paths=(
"/var/www/java/ubains-web-2.0/static/config.json"
"/var/www/java/ubains-web-admin/static/config.json"
"/var/www/java/api-java-meeting2.0/config"
"/var/www/java/external-meeting-api/config"
"/var/www/html/setting.conf"
"/var/www/java/nginx-conf.d"
"/var/www/redis/redis.conf"
"/var/www/emqx/config"
)
fi
for path in "${config_paths[@]}"; do
total=$((total + 1))
if [ -e "$path" ]; then
exists=$((exists + 1))
else
if [ -n "$missing_list" ]; then
missing_list="${missing_list},"
fi
missing_list="${missing_list}${path}"
fi
done
output_result "LOGEXP_CONFIG_TOTAL" "$total"
output_result "LOGEXP_CONFIG_EXISTS" "$exists"
output_result "LOGEXP_CONFIG_MISSING" "$((total - exists))"
if [ "$exists" -eq "$total" ]; then
output_result "LOGEXP_CONFIG_LEVEL" "正常"
elif [ "$exists" -gt 0 ]; then
output_result "LOGEXP_CONFIG_LEVEL" "警告"
else
output_result "LOGEXP_CONFIG_LEVEL" "严重"
fi
}
# ==================== 日志目录检测 ====================
check_log_dirs() {
local platform_type
platform_type=$(detect_platform_type)
local total=0
local exists=0
local oversized=0
local log_info_list=""
local -a log_paths
if [ "$platform_type" = "new" ]; then
log_paths=(
"/data/services/api/auth/auth-sso-auth/log.out"
"/data/services/api/auth/auth-sso-gateway/log.out"
"/data/services/api/auth/auth-sso-system/log.out"
"/data/services/api/java-meeting/java-meeting2.0/logs"
"/data/services/api/java-meeting/java-meeting-extapi/logs"
"/data/services/api/java-meeting/java-message-scheduling/logs"
"/data/services/api/java-meeting/java-mqtt/logs"
"/data/services/api/java-meeting/java-quartz/logs"
"/data/services/api/python-cmdb/log"
"/data/middleware/nginx/log"
"/data/middleware/emqx/log"
"/data/middleware/mysql/logs"
"/data/middleware/nacos/logs"
)
else
log_paths=(
"/var/www/java/api-java-meeting2.0/logs"
"/var/www/java/external-meeting-api/logs"
"/var/www/html/log"
"/var/www/emqx/log"
"/usr/local/docker/mysql/logs"
)
fi
for path in "${log_paths[@]}"; do
total=$((total + 1))
if [ -e "$path" ]; then
exists=$((exists + 1))
# 获取大小
local size_human
size_human=$(du -sh "$path" 2>/dev/null | cut -f1 || echo "0")
# 检查是否过大(>1GB)
local size_bytes
size_bytes=$(du -sb "$path" 2>/dev/null | cut -f1 || echo "0")
if [ "$size_bytes" -gt 1073741824 ]; then
oversized=$((oversized + 1))
fi
fi
done
output_result "LOGEXP_LOG_TOTAL" "$total"
output_result "LOGEXP_LOG_EXISTS" "$exists"
output_result "LOGEXP_LOG_MISSING" "$((total - exists))"
output_result "LOGEXP_LOG_OVERSIZED" "$oversized"
if [ "$exists" -eq "$total" ] && [ "$oversized" -eq 0 ]; then
output_result "LOGEXP_LOG_LEVEL" "正常"
elif [ "$exists" -gt 0 ]; then
output_result "LOGEXP_LOG_LEVEL" "警告"
else
output_result "LOGEXP_LOG_LEVEL" "严重"
fi
}
# ==================== 关键日志大小检测 ====================
check_log_sizes() {
local platform_type
platform_type=$(detect_platform_type)
local -a check_files
if [ "$platform_type" = "new" ]; then
check_files=(
"/data/services/api/java-meeting/java-meeting2.0/logs/ubains-ERROR.log"
"/data/services/api/java-meeting/java-meeting-extapi/logs/ubains-ERROR.log"
"/data/services/api/python-cmdb/log/error.log"
)
else
check_files=(
"/var/www/java/api-java-meeting2.0/logs/ubains-ERROR.log"
"/var/www/java/external-meeting-api/logs/ubains-ERROR.log"
"/var/www/html/log/error.log"
)
fi
local total_size=0
local file_count=0
for file in "${check_files[@]}"; do
if [ -f "$file" ]; then
local fsize
fsize=$(stat -c '%s' "$file" 2>/dev/null || echo "0")
total_size=$((total_size + fsize))
file_count=$((file_count + 1))
fi
done
# 转换为 MB
local total_mb=$((total_size / 1048576))
output_result "LOGEXP_ERROR_LOG_COUNT" "$file_count"
output_result "LOGEXP_ERROR_LOG_SIZE_MB" "$total_mb"
if [ "$total_mb" -gt 500 ]; then
output_result "LOGEXP_ERROR_LOG_LEVEL" "警告"
else
output_result "LOGEXP_ERROR_LOG_LEVEL" "正常"
fi
}
# ==================== 日志轮转配置检测 ====================
check_logrotate() {
local rotate_ok="no"
local rotate_config=""
# 检查 logrotate 配置
if [ -d "/etc/logrotate.d" ]; then
local app_configs
app_configs=$(ls /etc/logrotate.d/ 2>/dev/null | grep -iE "ubains|meeting|nginx|redis|mysql|emqx|nacos" | head -5 || echo "")
if [ -n "$app_configs" ]; then
rotate_ok="yes"
rotate_config=$(echo "$app_configs" | tr '\n' ',' | sed 's/,$//')
fi
fi
output_result "LOGEXP_LOGROTATE_OK" "$rotate_ok"
output_result "LOGEXP_LOGROTATE_CONFIG" "${rotate_config:-}"
if [ "$rotate_ok" = "yes" ]; then
output_result "LOGEXP_LOGROTATE_LEVEL" "正常"
else
output_result "LOGEXP_LOGROTATE_LEVEL" "警告"
fi
}
# ==================== 导出能力检测 ====================
check_export_capability() {
local tar_ok="no"
local gzip_ok="no"
# 检查打包工具
if command -v tar >/dev/null 2>&1; then
tar_ok="yes"
fi
if command -v gzip >/dev/null 2>&1; then
gzip_ok="yes"
fi
output_result "LOGEXP_TAR_AVAILABLE" "$tar_ok"
output_result "LOGEXP_GZIP_AVAILABLE" "$gzip_ok"
if [ "$tar_ok" = "yes" ] && [ "$gzip_ok" = "yes" ]; then
output_result "LOGEXP_EXPORT_CAPABLE" "yes"
output_result "LOGEXP_EXPORT_LEVEL" "正常"
else
output_result "LOGEXP_EXPORT_CAPABLE" "no"
output_result "LOGEXP_EXPORT_LEVEL" "警告"
fi
}
# ==================== 主检测流程 ====================
main() {
log_info "开始配置日志导出检测..."
set +e
# 1. 配置文件检测
check_config_files
# 2. 日志目录检测
check_log_dirs
# 3. 关键日志大小检测
check_log_sizes
# 4. 日志轮转配置检测
check_logrotate
# 5. 导出能力检测
check_export_capability
# 6. 综合状态
output_result "LOGEXP_STATUS" "已检测"
output_result "LOGEXP_STATUS_LEVEL" "正常"
log_info "配置日志导出检测完成"
}
main
\ No newline at end of file
#!/bin/bash
################################################################################
# 60_repair_capability_check.sh — 修复能力检测
# 功能: 检测修复脚本/工具是否就绪,不执行修复
# 参考: issue_handler.sh
# 日期: 2026-07-22
# 说明: 只检测不修复,验证修复工具链可用性
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== 平台类型识别 ====================
detect_platform_type() {
[ -d "/data/services" ] && echo "new" || echo "old"
}
# ==================== 修复脚本检测 ====================
check_repair_scripts() {
local scripts_ok="no"
local scripts_found=0
local scripts_list=""
# 常见修复脚本路径
local repair_script_paths=(
"/opt/troubleshoot/issue_handler.sh"
"/data/scripts/issue_handler.sh"
"/home/scripts/issue_handler.sh"
"/root/issue_handler.sh"
"/usr/local/bin/issue_handler.sh"
)
for script in "${repair_script_paths[@]}"; do
if [ -f "$script" ]; then
scripts_found=$((scripts_found + 1))
if [ -n "$scripts_list" ]; then
scripts_list="${scripts_list},"
fi
scripts_list="${scripts_list}${script}"
fi
done
if [ "$scripts_found" -gt 0 ]; then
scripts_ok="yes"
fi
output_result "REPAIR_SCRIPT_EXISTS" "$scripts_ok"
output_result "REPAIR_SCRIPT_COUNT" "$scripts_found"
output_result "REPAIR_SCRIPT_LIST" "${scripts_list:-}"
if [ "$scripts_ok" = "yes" ]; then
output_result "REPAIR_SCRIPT_LEVEL" "正常"
else
output_result "REPAIR_SCRIPT_LEVEL" "警告"
fi
}
# ==================== DNS 修复能力检测 ====================
check_dns_repair_capability() {
local capable="no"
local detail=""
# 检查 resolv.conf 是否可写
if [ -f "/etc/resolv.conf" ]; then
if [ -w "/etc/resolv.conf" ]; then
capable="yes"
detail="resolv.conf可写"
else
detail="resolv.conf只读"
fi
else
detail="resolv.conf不存在"
fi
# 检查 NetworkManager
if command -v nmcli >/dev/null 2>&1; then
detail="${detail},NetworkManager可用"
fi
output_result "REPAIR_DNS_CAPABLE" "$capable"
output_result "REPAIR_DNS_DETAIL" "${detail:-无工具}"
output_result "REPAIR_DNS_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== NTP 修复能力检测 ====================
check_ntp_repair_capability() {
local capable="no"
local detail=""
# 检查 chrony
if command -v chronyd >/dev/null 2>&1; then
capable="yes"
detail="chrony"
elif command -v ntpd >/dev/null 2>&1; then
capable="yes"
detail="ntp"
else
detail="未安装NTP服务"
fi
# 检查配置文件可写性
if [ -f "/etc/chrony.conf" ] && [ -w "/etc/chrony.conf" ]; then
detail="${detail},chrony.conf可写"
elif [ -f "/etc/ntp.conf" ] && [ -w "/etc/ntp.conf" ]; then
detail="${detail},ntp.conf可写"
fi
output_result "REPAIR_NTP_CAPABLE" "$capable"
output_result "REPAIR_NTP_DETAIL" "${detail:-无工具}"
output_result "REPAIR_NTP_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 端口修复能力检测 ====================
check_port_repair_capability() {
local capable="no"
local detail=""
# 检查防火墙工具
if command -v firewall-cmd >/dev/null 2>&1; then
capable="yes"
detail="firewalld"
elif command -v iptables >/dev/null 2>&1; then
capable="yes"
detail="iptables"
else
detail="无防火墙工具"
fi
output_result "REPAIR_PORT_CAPABLE" "$capable"
output_result "REPAIR_PORT_DETAIL" "${detail:-无工具}"
output_result "REPAIR_PORT_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 权限修复能力检测 ====================
check_permission_repair_capability() {
local capable="no"
local detail=""
# 检查 chmod/chown
if command -v chmod >/dev/null 2>&1 && command -v chown >/dev/null 2>&1; then
capable="yes"
detail="chmod+chown可用"
else
detail="权限工具缺失"
fi
# 检查关键目录是否可写
local platform_type
platform_type=$(detect_platform_type)
if [ "$platform_type" = "new" ]; then
if [ -w "/data/services/api" ] 2>/dev/null; then
detail="${detail},/data/services可写"
else
detail="${detail},/data/services不可写"
fi
else
if [ -w "/var/www/java" ] 2>/dev/null; then
detail="${detail},/var/www/java可写"
else
detail="${detail},/var/www/java不可写"
fi
fi
output_result "REPAIR_PERM_CAPABLE" "$capable"
output_result "REPAIR_PERM_DETAIL" "${detail:-无工具}"
output_result "REPAIR_PERM_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== Docker 修复能力检测 ====================
check_docker_repair_capability() {
local capable="no"
local detail=""
if command -v docker >/dev/null 2>&1; then
capable="yes"
# 检查 docker 权限
if docker ps >/dev/null 2>&1; then
detail="docker可用"
else
detail="docker无权限"
capable="no"
fi
else
detail="docker未安装"
fi
output_result "REPAIR_DOCKER_CAPABLE" "$capable"
output_result "REPAIR_DOCKER_DETAIL" "${detail:-无工具}"
output_result "REPAIR_DOCKER_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 服务重启能力检测 ====================
check_service_restart_capability() {
local capable="no"
local detail=""
if command -v systemctl >/dev/null 2>&1; then
capable="yes"
detail="systemctl"
elif command -v service >/dev/null 2>&1; then
capable="yes"
detail="service"
else
detail="无服务管理工具"
fi
output_result "REPAIR_SERVICE_CAPABLE" "$capable"
output_result "REPAIR_SERVICE_DETAIL" "${detail:-无工具}"
output_result "REPAIR_SERVICE_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 日志清理能力检测 ====================
check_log_cleanup_capability() {
local capable="no"
local detail=""
# 检查日志清理工具
local tools_found=0
if command -v logrotate >/dev/null 2>&1; then
tools_found=$((tools_found + 1))
detail="logrotate"
fi
if command -v lsof >/dev/null 2>&1; then
tools_found=$((tools_found + 1))
detail="${detail}+lsof"
fi
if command -v find >/dev/null 2>&1; then
tools_found=$((tools_found + 1))
detail="${detail}+find"
fi
if [ "$tools_found" -ge 2 ]; then
capable="yes"
fi
output_result "REPAIR_LOG_CLEAN_CAPABLE" "$capable"
output_result "REPAIR_LOG_CLEAN_DETAIL" "${detail:-工具不足}"
output_result "REPAIR_LOG_CLEAN_LEVEL" "$([ "$capable" = "yes" ] && echo "正常" || echo "警告")"
}
# ==================== 主检测流程 ====================
main() {
log_info "开始修复能力检测..."
set +e
# 1. 修复脚本检测
check_repair_scripts
# 2. DNS 修复能力
check_dns_repair_capability
# 3. NTP 修复能力
check_ntp_repair_capability
# 4. 端口修复能力
check_port_repair_capability
# 5. 权限修复能力
check_permission_repair_capability
# 6. Docker 修复能力
check_docker_repair_capability
# 7. 服务重启能力
check_service_restart_capability
# 8. 日志清理能力
check_log_cleanup_capability
# 9. 综合状态
output_result "REPAIR_STATUS" "已检测"
output_result "REPAIR_STATUS_LEVEL" "正常"
log_info "修复能力检测完成"
}
main
\ No newline at end of file
...@@ -305,6 +305,14 @@ def api_run_stream(): ...@@ -305,6 +305,14 @@ def api_run_stream():
target_id = request.args.get('target_id', '') target_id = request.args.get('target_id', '')
suite = request.args.get('suite', 'quick') suite = request.args.get('suite', 'quick')
# 防重复触发:同一目标已有未完成的巡检时拒绝新请求
existing = runner_service.get_running_for_target(target_id)
if existing:
err = {"event": "error",
"message": f"该目标已有巡检正在运行 ({existing['done']}/{existing['total']}),请等待完成或取消后重试",
"run_id": existing["run_id"]}
return jsonify(err), 409
@stream_with_context @stream_with_context
def generate(): def generate():
try: try:
...@@ -321,6 +329,24 @@ def api_run_stream(): ...@@ -321,6 +329,24 @@ def api_run_stream():
'Connection': 'keep-alive'}) 'Connection': 'keep-alive'})
@bp.route('/api/service-monitor/run/status/<target_id>', methods=['GET'])
def api_run_status(target_id):
"""查询指定目标是否有正在运行的巡检。
返回:
- {"running": true, "run_id", "done", "total", "current"} 或
- {"running": false}
"""
guard = _require_admin_json()
if guard:
return guard
run = runner_service.get_running_for_target(target_id)
if run:
return jsonify({"running": True, **run})
return jsonify({"running": False})
@bp.route('/api/service-monitor/run/<run_id>/cancel', methods=['POST']) @bp.route('/api/service-monitor/run/<run_id>/cancel', methods=['POST'])
def api_cancel_run(run_id): def api_cancel_run(run_id):
guard = _require_admin_json() guard = _require_admin_json()
......
...@@ -87,6 +87,27 @@ def get_run_status(run_id: str) -> Optional[dict]: ...@@ -87,6 +87,27 @@ def get_run_status(run_id: str) -> Optional[dict]:
} }
def get_running_for_target(target_id: str) -> Optional[dict]:
"""获取指定目标正在运行的巡检任务(如有)。
用于防重复触发:同一目标已有未完成的巡检时返回该运行记录,
前端据此决定是否阻止启动新巡检。
"""
with _runs_lock:
for run_id, run in _runs.items():
if (run.get("target_id") == target_id
and not run.get("finished")):
return {
"run_id": run_id,
"target_id": run.get("target_id"),
"suite": run.get("suite"),
"done": run.get("done", 0),
"total": run.get("total", 0),
"current": run.get("current", ""),
}
return None
def run_inspection(target_id: str, suite: str): def run_inspection(target_id: str, suite: str):
"""巡检生成器:yield SSE 事件 dict。 """巡检生成器:yield SSE 事件 dict。
......
...@@ -91,6 +91,10 @@ ALL_MODULES: List[CheckModule] = [ ...@@ -91,6 +91,10 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "54_file_permission_check.sh"), {SUITE_FULL}, "54_file_permission_check.sh"),
CheckModule("55_cron_expected", "定时任务预期检测", "system", CheckModule("55_cron_expected", "定时任务预期检测", "system",
{SUITE_FULL}, "55_cron_expected.sh"), {SUITE_FULL}, "55_cron_expected.sh"),
CheckModule("59_log_export_check", "配置日志导出检测", "system",
{SUITE_FULL}, "59_log_export_check.sh"),
CheckModule("60_repair_capability_check", "修复能力检测", "system",
{SUITE_FULL}, "60_repair_capability_check.sh"),
# ---- service 类 ---- # ---- service 类 ----
CheckModule("20_docker_basic", "Docker基础", "service", CheckModule("20_docker_basic", "Docker基础", "service",
{SUITE_QUICK, SUITE_FULL}, "20_docker_basic.sh"), {SUITE_QUICK, SUITE_FULL}, "20_docker_basic.sh"),
...@@ -130,6 +134,10 @@ ALL_MODULES: List[CheckModule] = [ ...@@ -130,6 +134,10 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "39_middleware_conn.sh"), {SUITE_FULL}, "39_middleware_conn.sh"),
CheckModule("51_service_check", "平台服务检测", "service", CheckModule("51_service_check", "平台服务检测", "service",
{SUITE_FULL}, "51_service_check.sh"), {SUITE_FULL}, "51_service_check.sh"),
CheckModule("56_fastdfs_check", "FastDFS功能验证", "service",
{SUITE_FULL}, "56_fastdfs_check.sh"),
CheckModule("57_android_check", "Android设备检测", "service",
{SUITE_FULL}, "57_android_check.sh"),
] ]
_BY_ID: Dict[str, CheckModule] = {m.id: m for m in ALL_MODULES} _BY_ID: Dict[str, CheckModule] = {m.id: m for m in ALL_MODULES}
......
...@@ -771,4 +771,89 @@ DISPLAY_NAMES: Dict[str, str] = { ...@@ -771,4 +771,89 @@ DISPLAY_NAMES: Dict[str, str] = {
"CRON_EXPECTED_TOTAL": "预期任务数", "CRON_EXPECTED_TOTAL": "预期任务数",
"CRON_EXPECTED_MISS_COUNT": "缺失任务数", "CRON_EXPECTED_MISS_COUNT": "缺失任务数",
"CRON_EXPECTED_MISS_LIST": "缺失任务列表", "CRON_EXPECTED_MISS_LIST": "缺失任务列表",
# ---- 56 FastDFS功能验证 ----
"FDFS_STATUS": "FastDFS状态",
"FDFS_STATUS_LEVEL": "FastDFS状态等级",
"FDFS_CONTAINER_TRACKER": "Tracker容器",
"FDFS_CONTAINER_STORAGE": "Storage容器",
"FDFS_PORTS_OK": "端口正常数",
"FDFS_PORTS_TOTAL": "端口总数",
"FDFS_PORTS_DETAIL": "端口详情",
"FDFS_PORTS_LEVEL": "端口检测等级",
"FDFS_PROCESS_STATUS": "进程状态",
"FDFS_PROCESS_LEVEL": "进程检测等级",
"FDFS_CONFIG_EXISTS": "配置文件存在",
"FDFS_CONFIG_LEVEL": "配置检测等级",
"FDFS_TRACKER_SERVER": "Tracker服务器",
"FDFS_HTTP_STATUS": "HTTP状态",
"FDFS_HTTP_LEVEL": "HTTP检测等级",
"FDFS_HTTP_CODE": "HTTP状态码",
# ---- 57 Android设备检测 ----
"ANDROID_STATUS": "Android检测状态",
"ANDROID_STATUS_LEVEL": "Android检测状态等级",
"ANDROID_ADB_EXISTS": "ADB工具存在",
"ANDROID_ADB_PATH": "ADB路径",
"ANDROID_ADB_VERSION": "ADB版本",
"ANDROID_ADB_LEVEL": "ADB检测等级",
"ANDROID_SERVER_STATUS": "ADB服务状态",
"ANDROID_SERVER_LEVEL": "ADB服务等级",
"ANDROID_DEVICE_COUNT": "已连接设备数",
"ANDROID_DEVICE_LIST": "设备列表",
"ANDROID_DEVICE_LEVEL": "设备检测等级",
"ANDROID_NETWORK_CAPABLE": "网络ADB能力",
"ANDROID_NETWORK_LEVEL": "网络ADB等级",
# ---- 59 配置日志导出检测 ----
"LOGEXP_STATUS": "日志导出检测状态",
"LOGEXP_STATUS_LEVEL": "日志导出检测状态等级",
"LOGEXP_CONFIG_TOTAL": "配置文件总数",
"LOGEXP_CONFIG_EXISTS": "配置文件存在数",
"LOGEXP_CONFIG_MISSING": "配置文件缺失数",
"LOGEXP_CONFIG_LEVEL": "配置文件等级",
"LOGEXP_LOG_TOTAL": "日志目录总数",
"LOGEXP_LOG_EXISTS": "日志目录存在数",
"LOGEXP_LOG_MISSING": "日志目录缺失数",
"LOGEXP_LOG_OVERSIZED": "日志过大数",
"LOGEXP_LOG_LEVEL": "日志目录等级",
"LOGEXP_ERROR_LOG_COUNT": "错误日志数",
"LOGEXP_ERROR_LOG_SIZE_MB": "错误日志大小MB",
"LOGEXP_ERROR_LOG_LEVEL": "错误日志等级",
"LOGEXP_LOGROTATE_OK": "日志轮转配置",
"LOGEXP_LOGROTATE_CONFIG": "轮转配置文件",
"LOGEXP_LOGROTATE_LEVEL": "日志轮转等级",
"LOGEXP_TAR_AVAILABLE": "tar可用",
"LOGEXP_GZIP_AVAILABLE": "gzip可用",
"LOGEXP_EXPORT_CAPABLE": "导出能力",
"LOGEXP_EXPORT_LEVEL": "导出能力等级",
# ---- 60 修复能力检测 ----
"REPAIR_STATUS": "修复能力检测状态",
"REPAIR_STATUS_LEVEL": "修复能力检测状态等级",
"REPAIR_SCRIPT_EXISTS": "修复脚本存在",
"REPAIR_SCRIPT_COUNT": "修复脚本数",
"REPAIR_SCRIPT_LIST": "修复脚本列表",
"REPAIR_SCRIPT_LEVEL": "修复脚本等级",
"REPAIR_DNS_CAPABLE": "DNS修复能力",
"REPAIR_DNS_DETAIL": "DNS修复详情",
"REPAIR_DNS_LEVEL": "DNS修复等级",
"REPAIR_NTP_CAPABLE": "NTP修复能力",
"REPAIR_NTP_DETAIL": "NTP修复详情",
"REPAIR_NTP_LEVEL": "NTP修复等级",
"REPAIR_PORT_CAPABLE": "端口修复能力",
"REPAIR_PORT_DETAIL": "端口修复详情",
"REPAIR_PORT_LEVEL": "端口修复等级",
"REPAIR_PERM_CAPABLE": "权限修复能力",
"REPAIR_PERM_DETAIL": "权限修复详情",
"REPAIR_PERM_LEVEL": "权限修复等级",
"REPAIR_DOCKER_CAPABLE": "Docker修复能力",
"REPAIR_DOCKER_DETAIL": "Docker修复详情",
"REPAIR_DOCKER_LEVEL": "Docker修复等级",
"REPAIR_SERVICE_CAPABLE": "服务重启能力",
"REPAIR_SERVICE_DETAIL": "服务重启详情",
"REPAIR_SERVICE_LEVEL": "服务重启等级",
"REPAIR_LOG_CLEAN_CAPABLE": "日志清理能力",
"REPAIR_LOG_CLEAN_DETAIL": "日志清理详情",
"REPAIR_LOG_CLEAN_LEVEL": "日志清理等级",
} }
...@@ -42,6 +42,13 @@ _VALUE_STATUS_MAP = { ...@@ -42,6 +42,13 @@ _VALUE_STATUS_MAP = {
"查询失败": WARNING, "无法读取": WARNING, "查询失败": WARNING, "无法读取": WARNING,
# 平台服务检测 / 端口检测状态词 # 平台服务检测 / 端口检测状态词
"监听": NORMAL, "未监听": CRITICAL, "监听": NORMAL, "未监听": CRITICAL,
# FastDFS 功能验证检测状态词
"yes": NORMAL, "no": WARNING,
"上传失败": CRITICAL, "下载失败": CRITICAL,
"命令不存在": CRITICAL, "配置文件缺失": CRITICAL,
# Android / 备份 / 日志导出 / 修复能力检测状态词
"ADB未安装": WARNING, "未安装": WARNING,
"无工具": WARNING, "工具不足": WARNING,
} }
# 无效值/未安装占位符(这些值表示服务未安装或检测失败,不作为正常检测项) # 无效值/未安装占位符(这些值表示服务未安装或检测失败,不作为正常检测项)
......
...@@ -191,10 +191,11 @@ ...@@ -191,10 +191,11 @@
} }
.topbar { .topbar {
left: 0; left: 0;
padding: 0 12px;
} }
.main-content { .main-content {
margin-left: 0; margin-left: 0;
padding: 84px 14px 28px; padding: 72px 12px 24px;
} }
.hamburger { .hamburger {
display: block; display: block;
...@@ -202,6 +203,29 @@ ...@@ -202,6 +203,29 @@
.topbar-home { .topbar-home {
display: none; display: none;
} }
.topbar-user {
font-size: 13px;
gap: 8px;
}
.topbar-user span {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btn-logout {
padding: 6px 10px;
font-size: 12px;
}
/* 侧边栏菜单项 */
.nav-item {
padding: 12px 16px;
font-size: 13px;
}
.sidebar-brand {
font-size: 16px;
padding: 16px;
}
} }
</style> </style>
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
......
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
</div> </div>
{% if is_admin %} {% if is_admin %}
<div class="target-actions"> <div class="target-actions">
<a href="/service-monitor/run/{{ t.id }}?suite=quick" class="btn-suite btn-quick">⚡ 快速巡检</a> <a href="/service-monitor/run/{{ t.id }}?suite=quick" class="btn-suite btn-quick" onclick="return checkBeforeRun('/service-monitor/run/{{ t.id }}?suite=quick', '{{ t.id }}')">⚡ 快速巡检</a>
<a href="/service-monitor/run/{{ t.id }}?suite=full" class="btn-suite btn-full">🔍 全量巡检</a> <a href="/service-monitor/run/{{ t.id }}?suite=full" class="btn-suite btn-full" onclick="return checkBeforeRun('/service-monitor/run/{{ t.id }}?suite=full', '{{ t.id }}')">🔍 全量巡检</a>
</div> </div>
{% else %} {% else %}
<div class="target-actions"> <div class="target-actions">
...@@ -66,6 +66,37 @@ ...@@ -66,6 +66,37 @@
} }
@media screen and (max-width: 1024px) { .target-grid { grid-template-columns: repeat(2, 1fr); } } @media screen and (max-width: 1024px) { .target-grid { grid-template-columns: repeat(2, 1fr); } }
@media screen and (max-width: 768px) { .target-grid { grid-template-columns: 1fr; } } @media screen and (max-width: 768px) {
.target-grid { grid-template-columns: 1fr; gap: 12px; }
.target-card { padding: 16px; }
.target-card-head { margin-bottom: 10px; }
.target-icon { font-size: 24px; }
.target-name { font-size: 15px; }
.target-meta { font-size: 12px; margin-bottom: 12px; }
.target-actions { flex-direction: column; gap: 8px; }
.btn-suite { padding: 10px 0; font-size: 13px; }
.section-head { margin-bottom: 12px; }
.section-title { font-size: 16px; }
.btn-add { padding: 8px 14px; font-size: 13px; }
}
</style> </style>
{% endblock %}
{% block extra_js %}
<script>
async function checkBeforeRun(href, targetId) {
try {
const resp = await fetch(`/api/service-monitor/run/status/${encodeURIComponent(targetId)}`, {credentials:'include'});
if (resp.ok) {
const status = await resp.json();
if (status.running) {
alert(`该目标已有巡检正在运行(${status.done}/${status.total}),请等待完成或取消后再试`);
return false;
}
}
} catch(err) {}
window.location.href = href;
return false;
}
</script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -36,8 +36,21 @@ ...@@ -36,8 +36,21 @@
.actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--gray-200); } .actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--gray-200); }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.config-section { margin-bottom: 14px; border-radius: 10px; }
.section-header { padding: 12px 14px; }
.section-title { font-size: 14px; }
.section-body { padding: 14px; }
.form-row { flex-direction: column; gap: 0; } .form-row { flex-direction: column; gap: 0; }
.form-row .form-group { margin-bottom: 14px; } .form-row .form-group { margin-bottom: 14px; }
.form-group input, .form-group select, .form-group textarea {
padding: 8px 10px; font-size: 13px;
}
.toggle-row { padding: 10px 0; }
.toggle-label { font-size: 13px; }
.btn-test { padding: 6px 12px; font-size: 12px; }
.btn-save { padding: 8px 16px; font-size: 13px; width: 100%; }
.actions { flex-direction: column; }
.section-head { margin-bottom: 12px; }
} }
</style> </style>
{% endblock %} {% endblock %}
......
...@@ -107,12 +107,28 @@ ...@@ -107,12 +107,28 @@
.long-value.expanded .full { display: inline; white-space: pre-wrap; word-break: break-all; } .long-value.expanded .full { display: inline; white-space: pre-wrap; word-break: break-all; }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.summary-cards { grid-template-columns: repeat(2,1fr); } .container { padding: 0; }
.abnormal-row { grid-template-columns: 24px 1fr auto; gap: 6px; } .report-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.abnormal-row .ab-threshold, .abnormal-row .ab-module { display: none; } .report-meta h2 { font-size: 16px; }
.item-row { grid-template-columns: 1fr 1fr; gap: 6px; } .export-wrap { width: 100%; }
.btn-export { width: 100%; }
.export-menu { position: fixed; left: 12px; right: 12px; top: auto; bottom: 12px; border-radius: 12px; }
.summary-cards { grid-template-columns: repeat(2, 1fr); gap: 8px; }
.sum-card { padding: 12px; }
.sum-card .num { font-size: 22px; }
.abnormal-row { grid-template-columns: 20px 1fr auto; gap: 6px; padding: 10px 12px; font-size: 13px; }
.abnormal-row .ab-threshold, .abnormal-row .ab-module, .abnormal-row .btn-fix { display: none; }
.ab-name { font-size: 13px; }
.module-header { padding: 12px 14px; }
.module-title { font-size: 14px; }
.module-mini { font-size: 12px; }
.item-row { grid-template-columns: 1fr auto; gap: 4px; padding: 8px 14px; font-size: 13px; }
.item-threshold { display: none; }
.item-status { white-space: nowrap; }
.process-table { font-size: 11px; } .process-table { font-size: 11px; }
.process-table .cmd-cell { max-width: 150px; } .process-table th, .process-table td { padding: 4px 6px; }
.process-table .cmd-cell { max-width: 120px; }
.guest-banner { flex-direction: column; text-align: center; gap: 8px; padding: 12px; }
} }
</style> </style>
{% endblock %} {% endblock %}
......
...@@ -104,9 +104,20 @@ ...@@ -104,9 +104,20 @@
.btn-batch-del:hover { background: #fecaca; } .btn-batch-del:hover { background: #fecaca; }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.report-row { grid-template-columns: 32px 1fr 1fr; gap: 6px; } .filter-bar { flex-direction: column; align-items: stretch; gap: 8px; }
.report-summary, .report-time { display: none; } .filter-bar select { width: 100%; padding: 8px 10px; font-size: 13px; }
.filter-bar { flex-wrap: wrap; } .report-count { margin-left: 0; text-align: right; font-size: 12px; }
.report-row {
grid-template-columns: 28px 1fr auto;
gap: 8px; padding: 12px 14px;
border-bottom: 2px solid var(--gray-200);
}
.report-summary, .report-time, .btn-view { display: none; }
.report-target { font-size: 14px; }
.report-suite { font-size: 12px; }
.batch-bar { flex-wrap: wrap; gap: 10px; padding: 10px 14px; }
.btn-batch-del { margin-left: 0; }
.section-head { margin-bottom: 12px; }
} }
</style> </style>
{% endblock %} {% endblock %}
......
...@@ -37,6 +37,20 @@ ...@@ -37,6 +37,20 @@
.done-ok { background: #dcfce7; color: #15803d; } .done-ok { background: #dcfce7; color: #15803d; }
.done-err { background: #fee2e2; color: var(--red); } .done-err { background: #fee2e2; color: var(--red); }
.btn-report { display: inline-block; margin-top: 12px; background: var(--primary); color: #fff; padding: 9px 22px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; } .btn-report { display: inline-block; margin-top: 12px; background: var(--primary); color: #fff; padding: 9px 22px; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; }
@media screen and (max-width: 768px) {
.run-card { padding: 16px; border-radius: 12px; }
.run-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.run-title { font-size: 15px; }
.run-title .suite-tag { font-size: 12px; padding: 2px 8px; }
.btn-cancel { width: 100%; padding: 8px; }
.progress-text { flex-direction: column; gap: 2px; font-size: 12px; }
.module-item { gap: 8px; padding: 10px 0; }
.module-status { font-size: 16px; width: 20px; }
.module-name { font-size: 13px; }
.module-info { font-size: 11px; }
.btn-report { padding: 8px 18px; font-size: 13px; }
}
</style> </style>
{% endblock %} {% endblock %}
...@@ -164,6 +178,36 @@ ...@@ -164,6 +178,36 @@
} }
} }
/* 显示“已有巡检正在运行”的占位状态(不触发新巡检) */
function showAlreadyRunning(runId, done, total) {
currentRunId = runId;
if (total) {
const pct = Math.round((done / total) * 100);
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('progress-count').textContent = `${done} / ${total}`;
}
document.getElementById('progress-current').textContent = '该目标已有巡检正在运行,请等待完成或取消后重试';
const banner = document.getElementById('done-banner');
const text = document.getElementById('done-text');
banner.classList.add('show');
banner.className = 'done-banner show done-err';
text.textContent = `⚠️ 该目标已有巡检正在运行${total ? `(${done}/${total})` : ''},可点击右上角“取消”终止已有巡检后再重试`;
}
async function checkAndStart() {
try {
const resp = await fetch(`/api/service-monitor/run/status/${encodeURIComponent(TARGET_ID)}`, {credentials:'include'});
if (resp.ok) {
const status = await resp.json();
if (status.running) {
showAlreadyRunning(status.run_id, status.done || 0, status.total || 0);
return;
}
}
} catch(e) { /* 查询失败则按原流程启动 */ }
startInspection();
}
async function cancelRun() { async function cancelRun() {
if (currentRunId) { if (currentRunId) {
try { await fetch(`/api/service-monitor/run/${currentRunId}/cancel`, {method:'POST', credentials:'include'}); } catch(e) {} try { await fetch(`/api/service-monitor/run/${currentRunId}/cancel`, {method:'POST', credentials:'include'}); } catch(e) {}
...@@ -172,6 +216,6 @@ ...@@ -172,6 +216,6 @@
finishRun(false, null, null, '巡检已取消'); finishRun(false, null, null, '巡检已取消');
} }
window.addEventListener('DOMContentLoaded', startInspection); window.addEventListener('DOMContentLoaded', checkAndStart);
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -86,11 +86,25 @@ ...@@ -86,11 +86,25 @@
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.table-head { display: none; } .table-head { display: none; }
.table-row { grid-template-columns: 1fr 1fr; gap: 6px; padding: 14px; } .table-row {
.modal { padding: 20px; max-width: 100%; } grid-template-columns: 1fr 1fr;
.repeat-group { flex-direction: column; } gap: 6px; padding: 14px;
border-bottom: 2px solid var(--gray-200);
}
.table-row .name { font-size: 15px; }
.schedule-desc { font-size: 12px; }
.actions { flex-wrap: wrap; gap: 4px; }
.btn-sm { padding: 4px 8px; font-size: 11px; }
.modal { padding: 16px; max-width: 100%; border-radius: 12px; }
.modal h3 { font-size: 16px; }
.repeat-group { flex-direction: column; gap: 6px; }
.repeat-btn { padding: 8px 0; font-size: 13px; }
.date-row { flex-direction: column; gap: 0; } .date-row { flex-direction: column; gap: 0; }
.date-row .form-group { margin-bottom: 14px; } .date-row .form-group { margin-bottom: 14px; }
.time-picker select { width: 70px; padding: 8px 6px; font-size: 13px; }
.weekday-btn { width: 38px; height: 32px; font-size: 12px; }
.section-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.btn-add { width: 100%; text-align: center; }
} }
</style> </style>
{% endblock %} {% endblock %}
......
...@@ -60,10 +60,21 @@ ...@@ -60,10 +60,21 @@
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.overview-cards { grid-template-columns: repeat(2,1fr); } .filter-bar { gap: 10px; padding: 12px; }
.filter-group { flex: 1; min-width: 140px; }
.filter-label { font-size: 13px; }
.filter-select, .filter-date { padding: 6px 10px; font-size: 13px; }
.btn-query { padding: 6px 14px; font-size: 13px; }
.overview-cards { grid-template-columns: repeat(2, 1fr); gap: 8px; }
.ov-card { padding: 12px; }
.ov-card .value { font-size: 22px; }
.ov-card .label { font-size: 12px; }
.trend-layout { flex-direction: column; } .trend-layout { flex-direction: column; }
.filter-bar { gap: 10px; } .chart-section { padding: 14px; margin-bottom: 14px; }
.chart-container { min-height: 280px; } .chart-title { font-size: 14px; margin-bottom: 12px; }
.chart-container { min-height: 250px; }
.item-selector { max-width: 100%; font-size: 13px; }
.trend-filters { flex-direction: column; gap: 10px; }
} }
</style> </style>
{% endblock %} {% endblock %}
......
...@@ -38,7 +38,18 @@ ...@@ -38,7 +38,18 @@
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.table-head { display: none; } .table-head { display: none; }
.table-row { grid-template-columns: 1fr 1fr; gap: 6px; padding: 14px; } .table-row {
grid-template-columns: 1fr 1fr;
gap: 6px; padding: 14px;
border-bottom: 2px solid var(--gray-200);
}
.table-row .name { font-size: 15px; }
.actions { flex-wrap: wrap; gap: 4px; }
.btn-sm { padding: 4px 8px; font-size: 11px; }
.modal { padding: 16px; max-width: 100%; border-radius: 12px; }
.modal h3 { font-size: 16px; }
.section-head { flex-direction: column; align-items: flex-start; gap: 10px; }
.btn-add { width: 100%; text-align: center; }
} }
</style> </style>
{% endblock %} {% endblock %}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论