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

feat(service-monitor): 新增安全合规/中间件检测模块并修复状态等级误判

检测项丰富化移植(阶段二):
- 新增 39_middleware_conn.sh 中间件连接检测(Redis/MySQL/EMQX/FastDFS)
- 新增 46_security_deep.sh 安全合规深度检测(9类:弱密码/基线/可疑文件等)
- check_modules.py 注册 2 个新模块
- display_names.py 追加 36 个 KEY 映射
- thresholds.py 追加 6 个安全阈值
- config.sh.template 追加安全白名单 + EMQX 端口配置

状态等级误判修复:
- 39/46 脚本 LEVEL KEY 对齐为 <值KEY>_LEVEL 格式(与 _resolve_status 约定一致)
- 23_mysql_depth / 25_redis_depth 重构为 output_result 格式
- parser.py _VALUE_STATUS_MAP 补充 11 个异常状态词映射(兜底)

中间件端口检测逻辑重构:
- 改用服务自带工具 ping 优先(redis-cli/mysqladmin/curl API)
- 避免容器内无 ss/netstat 导致误判"端口未监听"

服务管理模块(附带):
- routes/service_manage.py 新增授权/升级/信息页面路由
- templates/service_manage/ 新增 4 个模板

文档:更新 HANDOFF_服务监测.md 记录修复全过程 + 检测项丰富化 PRD
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 3f48af4c
# 需求文档:检测项丰富化(参考目录移植)
## 文档信息
- 创建时间:2026-07-22
- 需求来源:用户需求
- 关联模块:service_monitor
- 参考目录:`E:\GithubData\ubains-module-test\platform-auto-test\临时目录\服务监测\`
---
## 1. 背景与目标
### 1.1 背景
当前服务监测模块有 30 个检测脚本(13 系统 + 17 服务),display_names.py 收录 539 个检测项 KEY。用户希望对比参考目录(老版 PowerShell + Shell 巡检工具集)继续丰富检测项。
参考目录是平台自动化测试项目中的旧版巡检工具,采用 PowerShell 模块 + Shell 脚本混合架构,检测维度比当前项目更广。
### 1.2 目标
1. **补全已有脚本输出但未收录的 KEY**:当前部分脚本输出的 KEY 未在 display_names.py 中定义
2. **重构 depth 脚本**:`23_mysql_depth.sh` 和 `25_redis_depth.sh` 使用 `echo` 而非 `output_result`,需重构以适配标准解析器
3. **移植参考目录特有的全新检测类别**:18 类新检测项
---
## 2. 当前项目已有检测类别(30 个脚本)
### 系统类(13)
01_system_basic, 02_cpu_check, 03_memory_check, 04_disk_check, 05_oom_check, 06_process_check, 07_network_check, 11_scheduled_tasks, 12_port_check, 40_comprehensive_diagnosis, 43_security_compliance, 44_system_logs, 45_time_sync
### 服务类(17)
20_docker_basic, 21_docker_deep, 22_mysql_basic, 23_mysql_depth, 24_redis_basic, 25_redis_depth, 26_emqx_basic, 27_emqx_deep, 28_java_check, 29_python_check, 30_nginx_check, 31_nacos_check, 33_app_logs, 34_nginx_deep, 35_nacos_deep, 37_ustorage_check, 38_utracker_check
---
## 3. 阶段一:补全已有脚本缺失的 KEY
### 3.1 display_names.py 缺失项
| 模块 | 缺失 KEY | 中文名 |
|------|---------|--------|
| 03_memory | VM_PGMajFAULT | 主缺页错误数 |
| 21_docker_deep | DOCKER_DAEMON_CPU | Docker守护进程CPU |
| 21_docker_deep | DOCKER_DAEMON_MEM | Docker守护进程内存 |
| 34_nginx_deep | NGINX_CONTAINER_LEVEL | Nginx容器级别 |
| 35_nacos_deep | NACOS_CONTAINER_LEVEL | Nacos容器级别 |
### 3.2 depth 脚本重构
**23_mysql_depth.sh** 当前用 `echo "KEY:value"`,需改为 `output_result "KEY" "value"`,KEY 加 `MYSQL_DEPTH_` 前缀避免冲突:
| 当前 KEY | 重构后 KEY | 中文名 |
|---------|-----------|--------|
| hit_rate | MYSQL_DEPTH_BUFFER_POOL_HIT_RATE | 缓冲池命中率 |
| slow_enabled | MYSQL_DEPTH_SLOW_QUERY_ENABLED | 慢查询日志开启 |
| slow_count | MYSQL_DEPTH_SLOW_QUERY_COUNT | 慢查询数量 |
| conn_rate | MYSQL_DEPTH_CONNECTION_USAGE | 连接使用率 |
| io_running | MYSQL_DEPTH_SLAVE_IO_RUNNING | 从库IO线程 |
| sql_running | MYSQL_DEPTH_SLAVE_SQL_RUNNING | 从库SQL线程 |
| seconds_behind | MYSQL_DEPTH_SLAVE_DELAY | 主从延迟秒数 |
| table_count | MYSQL_DEPTH_TABLE_COUNT | 表数量 |
| qps | MYSQL_DEPTH_QPS | 每秒查询数 |
| uptime | MYSQL_DEPTH_UPTIME_DAYS | 运行天数 |
| ... | ... | ... |
**25_redis_depth.sh** 同理,KEY 加 `REDIS_DEPTH_` 前缀。
---
## 4. 阶段二:移植全新检测类别(18 类)
### P1 优先级(深度检测 + 安全 + 中间件连接)
| 编号 | 新模块 | 检测项 | 参考来源 |
|------|--------|--------|---------|
| 46 | MySQL深度检测(重构) | 缓冲池命中率、慢查询、连接使用率、主从复制、TOP大表、QPS基线 | check_mysql_deep.sh, MiddlewareCheck.psm1 |
| 47 | Redis深度检测(重构) | RDB/AOF持久化、内存碎片率、缓存命中率、主从复制、键空间统计 | check_redis.sh, MiddlewareCheck.psm1 |
| 48 | 安全合规深度检测 | MySQL Root访问范围、空密码用户、Redis密码、EMQX默认密码、SUID文件、异常crontab、SSH暴力破解、异常端口 | SecurityCheck.psm1 |
| 49 | 中间件连接检测 | MQTT订阅、MQTT推送、Redis连接、MySQL连接、FastDFS上传下载 | middleware_check.sh, MiddlewareCheck.psm1 |
### P2 优先级(平台服务检测)
| 编号 | 新模块 | 检测项 | 参考来源 |
|------|--------|--------|---------|
| 50 | 系统类型识别 | ujava/upython/upython_voice 变体识别 | ServerProfile.psm1 |
| 51 | 平台服务检测 | auth/gateway/system/meeting/mqtt/quartz/message/extapi 服务状态 | ServiceCheck.psm1 |
| 52 | 配置IP检测 | 硬编码IP扫描、配置文件IP检查 | ConfigIPCheck.psm1, config_check.sh |
| 53 | Console配置检测 | true→false 配置检查 | check_server_health.sh |
### P3 优先级(辅助功能)
| 编号 | 新模块 | 检测项 | 参考来源 |
|------|--------|--------|---------|
| 54 | 文件权限检测 | 关键文件权限检查 | FilePermission.psm1 |
| 55 | 定时任务预期检测 | 预期任务缺失检测 | check_server_health.sh |
| 56 | FastDFS功能验证 | 上传/下载/完整性测试 | check_fdfs_arm.sh |
| 57 | Android设备检测 | adb连接、日志导出 | AndroidCheck.psm1 |
| 58 | 数据备份检测 | MySQL dump、打包、下载 | DataBackup.psm1 |
| 59 | 配置日志导出 | 远程配置/日志拉取 | LogExport.psm1 |
| 60 | 修复能力检测 | DNS/NTP/端口/容器修复链 | issue_handler.sh |
---
## 5. 技术方案
### 5.1 每个新模块的完整实现
每个新检测类别需要:
1. **bash 脚本**:`assets/system/XX_name.sh` 或 `assets/service/XX_name.sh`
- 遵循现有脚本格式:加载 config.sh + common.sh,使用 `output_result "KEY" "value"`
- 容器名用 `CONTAINERS[name]` 模糊匹配模式
2. **模块注册**:`check_modules.py` 的 `ALL_MODULES` 列表新增 `CheckModule` 条目
3. **显示名**:`display_names.py` 新增 KEY→中文名映射
4. **阈值**(如适用):`thresholds.py` 新增阈值规则
5. **config 模板**(如需凭据):`config.sh.template` 新增容器/凭据配置
6. **上传清单**:`deploy/upload_to_server.py` 确认脚本会被上传(assets 目录已整体上传)
### 5.2 移植注意事项
- **参考目录是 PowerShell**:需翻译为 bash,保留检测逻辑
- **参考目录是 Shell 老格式**:需适配 `output_result` 标准输出
- **容器名匹配**:参考目录用精确名,本项目用 `grep -iE` 模糊匹配
- **凭据注入**:参考目录硬编码,本项目用 `__占位符__` + executor 渲染
- **阈值**:参考目录内嵌,本项目用 `thresholds.py` 集中管理
### 5.3 测试策略
每个新模块:
1. 本地语法检查:`bash -n script.sh`
2. 单元测试:`check_modules.test_get_suite` 等已有测试通过
3. 部署后在目标机实际运行验证
4. 统计页面确认新检测项出现在选择器中
---
## 6. 验收标准
| 标准 | 验证方法 |
|------|---------|
| display_names.py 缺失项补全 | 脚本输出 KEY 都有中文名 |
| depth 脚本重构完成 | 用 output_result 格式,统计页面可识别 |
| 新模块 bash 脚本创建 | 语法检查通过 |
| check_modules.py 注册 | get_suite('full') 包含新模块 |
| display_names.py 新增映射 | 新 KEY 有中文名 |
| 统计页面显示新检测项 | 选择器中出现新模块和检测项 |
| 单元测试通过 | pytest 全绿 |
---
## 7. 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 工作量大,单会话无法完成 | 进度缓慢 | 分阶段实现,每阶段独立可部署 |
| bash 脚本在目标机环境差异 | 检测失败 | 容器名模糊匹配 + 命令存在性检查 |
| 参考目录逻辑复杂 | 移植出错 | 保留核心检测逻辑,简化边缘功能 |
| 新增 KEY 命名冲突 | 解析混乱 | 加模块前缀(MYSQL_DEPTH_ 等) |
---
## 8. 执行路线图
- **阶段一**(本次):补全 display_names 缺失项 + 重构 2 个 depth 脚本
- **阶段二**:P1 优先级 4 个新模块(MySQL深度/Redis深度/安全合规深度/中间件连接)
- **阶段三**:P2 优先级 4 个新模块(系统类型/平台服务/配置IP/Console配置)
- **阶段四**:P3 优先级 7 个新模块(文件权限/定时任务预期/FastDFS/Android/数据备份/日志导出/修复能力)
此差异已折叠。
......@@ -2,10 +2,16 @@
"""
service_manage.py — 服务管理模块路由
占位模块,后续实现服务授权、服务升级、服务信息管理功能。
页面路由:/service-manage/*
- /service-manage → 重定向到服务授权
- /service-manage/authorization → 服务授权
- /service-manage/upgrade → 服务升级
- /service-manage/info → 服务信息
权限:仅管理员可访问
"""
from flask import Blueprint, render_template, session
from flask import Blueprint, render_template, session, redirect, url_for
from decorators import page_login_required
from utils.logger import get_logger
......@@ -15,9 +21,71 @@ logger = get_logger(__name__)
bp = Blueprint('service-manage', __name__)
# ============================================================
# 内部辅助
# ============================================================
def _current_user():
return session.get('user', {})
def _is_admin():
return _current_user().get('role') == 'admin'
# ============================================================
# 页面路由
# ============================================================
@bp.route('/service-manage')
@page_login_required
def service_manage_page():
"""服务管理主页(占位)"""
user = session.get('user', {})
return render_template('service_manage.html', user=user)
def page_index():
"""服务管理主页 → 重定向到服务授权"""
if not _is_admin():
return redirect('/')
return redirect(url_for('service-manage.page_authorization'))
@bp.route('/service-manage/authorization')
@page_login_required
def page_authorization():
"""服务授权页面"""
if not _is_admin():
return redirect('/')
user = _current_user()
return render_template(
'service_manage/authorization.html',
user=user,
is_admin=True,
active_menu='authorization',
)
@bp.route('/service-manage/upgrade')
@page_login_required
def page_upgrade():
"""服务升级页面"""
if not _is_admin():
return redirect('/')
user = _current_user()
return render_template(
'service_manage/upgrade.html',
user=user,
is_admin=True,
active_menu='upgrade',
)
@bp.route('/service-manage/info')
@page_login_required
def page_info():
"""服务信息页面"""
if not _is_admin():
return redirect('/')
user = _current_user()
return render_template(
'service_manage/info.html',
user=user,
is_admin=True,
active_menu='info',
)
......@@ -51,6 +51,20 @@ PYTHON_LOG_PATH="${PYTHON_LOG_PATH:-/data/services/api/python*/log}"
NGINX_LOG_PATH="${NGINX_LOG_PATH:-/data/middleware/nginx/log}"
NACOS_LOG_PATH="${NACOS_LOG_PATH:-/data/middleware/nacos/logs}"
# ==================== 安全白名单 ====================
# SUID 文件白名单(逗号分隔)
SUID_WHITELIST="${SUID_WHITELIST:-/usr/bin/sudo,/usr/bin/passwd,/usr/bin/su,/usr/bin/ping,/usr/bin/mount,/usr/bin/umount,/usr/bin/newgrp,/usr/bin/chsh,/usr/bin/chfn,/usr/bin/gpasswd,/usr/lib/openssh/ssh-keysign,/usr/libexec/openssh/ssh-keysign}"
# 端口白名单 - 新平台(逗号分隔)
PORT_WHITELIST_NEW="${PORT_WHITELIST_NEW:-22,80,443,1883,3306,6379,8083,8084,8888,18083}"
# 端口白名单 - 旧平台(逗号分隔)
PORT_WHITELIST_OLD="${PORT_WHITELIST_OLD:-22,80,443,1883,3306,6379,8081,8082,8443,9001}"
# ==================== EMQX 端口配置 ====================
EMQX_MQTT_PORT="${EMQX_MQTT_PORT:-1883}"
EMQX_DASHBOARD_PORT="${EMQX_DASHBOARD_PORT:-18083}"
# ==================== 函数 ====================
# 获取配置值
......
......@@ -79,6 +79,8 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "44_system_logs.sh"),
CheckModule("45_time_sync", "时间同步", "system",
{SUITE_FULL}, "45_time_sync.sh"),
CheckModule("46_security_deep", "安全合规深度检测", "system",
{SUITE_FULL}, "46_security_deep.sh"),
# ---- service 类 ----
CheckModule("20_docker_basic", "Docker基础", "service",
{SUITE_QUICK, SUITE_FULL}, "20_docker_basic.sh"),
......@@ -114,6 +116,8 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "37_ustorage_check.sh"),
CheckModule("38_utracker_check", "UTracker容器监测", "service",
{SUITE_FULL}, "38_utracker_check.sh"),
CheckModule("39_middleware_conn", "中间件连接检测", "service",
{SUITE_FULL}, "39_middleware_conn.sh"),
]
_BY_ID: Dict[str, CheckModule] = {m.id: m for m in ALL_MODULES}
......
......@@ -70,6 +70,7 @@ DISPLAY_NAMES: Dict[str, str] = {
"TRANSPARENT_HUGEPAGE": "透明大页状态",
"VM_PSWPIN": "换入页面数",
"VM_PSWPOUT": "换出页面数",
"VM_PGMajFAULT": "主缺页错误数",
"MEMORY_STATUS": "内存状态",
# ---- 04 磁盘检测 ----
......@@ -129,6 +130,60 @@ DISPLAY_NAMES: Dict[str, str] = {
"MYSQL_CHARSET": "MySQL字符集",
"MYSQL_COLLATION": "MySQL排序规则",
# ---- 23 MySQL深度检测 ----
"MYSQL_DEPTH_UPTIME_DAYS": "MySQL深度-运行天数",
"MYSQL_DEPTH_QPS": "MySQL深度-QPS",
"MYSQL_DEPTH_TPS": "MySQL深度-TPS",
"MYSQL_DEPTH_CONNECTION_USAGE": "MySQL深度-连接使用率",
"MYSQL_DEPTH_CONNECTIONS": "MySQL深度-连接数",
"MYSQL_DEPTH_SLOW_QUERY_COUNT": "MySQL深度-慢查询数",
"MYSQL_DEPTH_SLOW_QUERY_ENABLED": "MySQL深度-慢查询日志",
"MYSQL_DEPTH_DEADLOCKS": "MySQL深度-死锁数",
"MYSQL_DEPTH_BUFFER_POOL_HIT_RATE": "MySQL深度-缓冲池命中率",
"MYSQL_DEPTH_CACHE_HIT_RATE": "MySQL深度-表缓存命中率",
"MYSQL_DEPTH_ACTIVE_QUERIES": "MySQL深度-活跃查询数",
"MYSQL_DEPTH_BINLOG_STATUS": "MySQL深度-Binlog状态",
"MYSQL_DEPTH_BINLOG_EXPIRE_DAYS": "MySQL深度-Binlog过期天数",
"MYSQL_DEPTH_FRAGMENTED_TABLES": "MySQL深度-碎片表数",
"MYSQL_DEPTH_CONN_ERRORS": "MySQL深度-连接错误数",
"MYSQL_DEPTH_INNODB_BP_SIZE": "MySQL深度-InnoDB缓冲池大小",
"MYSQL_DEPTH_TRX_ACTIVE": "MySQL深度-活跃事务数",
"MYSQL_DEPTH_LOCK_WAITS": "MySQL深度-锁等待数",
"MYSQL_DEPTH_DATABASE_SIZE": "MySQL深度-数据库总大小",
"MYSQL_DEPTH_TABLE_COUNT": "MySQL深度-表总数",
"MYSQL_DEPTH_REPLICATION_STATUS": "MySQL深度-复制状态",
"MYSQL_DEPTH_SLAVE_IO_RUNNING": "MySQL深度-从库IO线程",
"MYSQL_DEPTH_SLAVE_SQL_RUNNING": "MySQL深度-从库SQL线程",
"MYSQL_DEPTH_SLAVE_DELAY": "MySQL深度-主从延迟",
"MYSQL_DEPTH_ACTIVE_PROCESSLIST": "MySQL深度-进程列表",
"MYSQL_DEPTH_TOP_TABLES": "MySQL深度-大表TOP5",
"MYSQL_DEPTH_TEMP_TABLE_RATE": "MySQL深度-临时表使用率",
"MYSQL_DEPTH_TABLES_WITHOUT_INDEX": "MySQL深度-缺索引表数",
# ---- 25 Redis深度检测 ----
"REDIS_DEPTH_VERSION": "Redis深度-版本",
"REDIS_DEPTH_UPTIME_DAYS": "Redis深度-运行天数",
"REDIS_DEPTH_KEY_COUNT": "Redis深度-键数量",
"REDIS_DEPTH_USED_MEMORY": "Redis深度-内存使用",
"REDIS_DEPTH_MEM_FRAGMENTATION": "Redis深度-内存碎片率",
"REDIS_DEPTH_CLIENT_COUNT": "Redis深度-客户端数",
"REDIS_DEPTH_OPS_PER_SEC": "Redis深度-每秒操作数",
"REDIS_DEPTH_RDB_PERSISTENCE": "Redis深度-RDB持久化",
"REDIS_DEPTH_AOF_PERSISTENCE": "Redis深度-AOF持久化",
"REDIS_DEPTH_REPLICATION_STATUS": "Redis深度-复制状态",
"REDIS_DEPTH_ROLE": "Redis深度-角色",
"REDIS_DEPTH_SLAVES": "Redis深度-从库数",
"REDIS_DEPTH_CACHE_HIT_RATE": "Redis深度-缓存命中率",
"REDIS_DEPTH_REJECTED_CONNECTIONS": "Redis深度-拒绝连接数",
"REDIS_DEPTH_SLOWLOG_COUNT": "Redis深度-慢日志数",
"REDIS_DEPTH_BLOCKED_CLIENTS": "Redis深度-阻塞客户端数",
"REDIS_DEPTH_KEYSPACE_KEYS": "Redis深度-键空间键数",
"REDIS_DEPTH_KEYSPACE_EXPIRES": "Redis深度-过期键数",
"REDIS_DEPTH_CONFIG_MAXCLIENTS": "Redis深度-最大客户端配置",
"REDIS_DEPTH_CONFIG_TIMEOUT": "Redis深度-超时配置",
"REDIS_DEPTH_CLUSTER_STATUS": "Redis深度-集群状态",
"REDIS_DEPTH_RDB_AGE_MIN": "Redis深度-RDB年龄(分钟)",
# ---- 24 Redis基础 ----
"REDIS_CONTAINER_STATUS": "Redis容器状态",
"REDIS_CONTAINER_LEVEL": "Redis容器等级",
......@@ -596,6 +651,46 @@ DISPLAY_NAMES: Dict[str, str] = {
"HTTPS_CERT_STATUS": "HTTPS证书状态",
"SYSTEM_UPTIME_DAYS": "系统运行天数",
# ---- 46 安全合规深度检测 ----
"SECURITY_DEEP_MYSQL_ROOT_HOST": "MySQL Root 访问范围",
"SECURITY_DEEP_MYSQL_ROOT_HOST_LEVEL": "MySQL Root 访问等级",
"SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS": "MySQL 空密码用户数",
"SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS_LEVEL": "MySQL 空密码等级",
"SECURITY_DEEP_REDIS_PASSWORD": "Redis 密码状态",
"SECURITY_DEEP_REDIS_PASSWORD_LEVEL": "Redis 密码等级",
"SECURITY_DEEP_EMQX_DEFAULT_PWD": "EMQX 默认密码状态",
"SECURITY_DEEP_EMQX_DEFAULT_PWD_LEVEL": "EMQX 默认密码等级",
"SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT": "Linux 空密码账户数",
"SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT_LEVEL": "Linux 空密码等级",
"SECURITY_DEEP_SUID_COUNT": "SUID 文件总数",
"SECURITY_DEEP_SUSPICIOUS_SUID_COUNT": "可疑 SUID 文件数",
"SECURITY_DEEP_SUSPICIOUS_SUID_COUNT_LEVEL": "SUID 安全等级",
"SECURITY_DEEP_SUSPICIOUS_CRON_COUNT": "可疑定时任务数",
"SECURITY_DEEP_SUSPICIOUS_CRON_COUNT_LEVEL": "定时任务安全等级",
"SECURITY_DEEP_SSH_BRUTEFORCE_IPS": "SSH 暴力破解 IP 数",
"SECURITY_DEEP_SSH_BRUTEFORCE_IPS_LEVEL": "SSH 暴力破解等级",
"SECURITY_DEEP_UNEXPECTED_PORTS": "异常端口列表",
"SECURITY_DEEP_UNEXPECTED_PORTS_COUNT": "异常端口数",
"SECURITY_DEEP_UNEXPECTED_PORTS_COUNT_LEVEL": "端口安全等级",
# ---- 39 中间件连接检测 ----
"MW_CONN_REDIS_STATUS": "Redis 连接状态",
"MW_CONN_REDIS_STATUS_LEVEL": "Redis 连接等级",
"MW_CONN_REDIS_VERSION": "Redis 版本",
"MW_CONN_REDIS_MEMORY": "Redis 内存使用",
"MW_CONN_REDIS_CLIENTS": "Redis 连接数",
"MW_CONN_MYSQL_STATUS": "MySQL 连接状态",
"MW_CONN_MYSQL_STATUS_LEVEL": "MySQL 连接等级",
"MW_CONN_MYSQL_VERSION": "MySQL 版本",
"MW_CONN_EMQX_STATUS": "EMQX 连接状态",
"MW_CONN_EMQX_STATUS_LEVEL": "EMQX 连接等级",
"MW_CONN_EMQX_MQTT_PORT": "EMQX MQTT 端口",
"MW_CONN_EMQX_DASHBOARD": "EMQX Dashboard 端口",
"MW_CONN_EMQX_API_STATUS": "EMQX API 状态",
"MW_CONN_FASTDFS_STATUS": "FastDFS 连接状态",
"MW_CONN_FASTDFS_STATUS_LEVEL": "FastDFS 连接等级",
"MW_CONN_FASTDFS_NETWORK": "FastDFS 网络模式",
# ---- 容器进程检测(动态KEY) ----
"PROCESSES_mysql": "MySQL进程数",
"PROCESSES_redis": "Redis进程数",
......
......@@ -34,6 +34,12 @@ _VALUE_STATUS_MAP = {
"正常": NORMAL, "OK": NORMAL, "运行中": NORMAL, "是": NORMAL,
"警告": WARNING, "已停止": WARNING,
"严重": CRITICAL, "ERROR": CRITICAL, "异常": CRITICAL, "未运行": CRITICAL,
# 中间件连接 / 安全合规深度检测的异常状态词
"端口未监听": CRITICAL, "MQTT 端口未监听": CRITICAL,
"连接失败": CRITICAL, "进程异常": CRITICAL,
"未检测到容器": CRITICAL, "容器未运行": CRITICAL,
"无密码": CRITICAL, "弱密码": CRITICAL, "使用默认密码": CRITICAL,
"查询失败": WARNING, "无法读取": WARNING,
}
# 无效值/未安装占位符(这些值表示服务未安装或检测失败,不作为正常检测项)
......
......@@ -56,6 +56,30 @@ THRESHOLDS: Dict[str, str] = {
"REDIS_MEMORY_USAGE": ">80%",
"REDIS_BLOCKED_CLIENTS": ">10",
"REDIS_SLAVES": "<1",
# ---- MySQL深度 ----
"MYSQL_DEPTH_CONNECTION_USAGE": ">80%",
"MYSQL_DEPTH_SLOW_QUERY_COUNT": ">100",
"MYSQL_DEPTH_BUFFER_POOL_HIT_RATE": "<95%",
"MYSQL_DEPTH_SLAVE_DELAY": ">10s",
"MYSQL_DEPTH_DEADLOCKS": ">0",
"MYSQL_DEPTH_LOCK_WAITS": ">0",
"MYSQL_DEPTH_FRAGMENTED_TABLES": ">10",
# ---- Redis深度 ----
"REDIS_DEPTH_MEM_FRAGMENTATION": ">5",
"REDIS_DEPTH_CACHE_HIT_RATE": "<90%",
"REDIS_DEPTH_BLOCKED_CLIENTS": ">10",
"REDIS_DEPTH_REJECTED_CONNECTIONS": ">0",
"REDIS_DEPTH_SLOWLOG_COUNT": ">10",
# ---- 安全合规深度检测 ----
"SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS": ">0",
"SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT": ">0",
"SECURITY_DEEP_SUSPICIOUS_SUID_COUNT": ">0",
"SECURITY_DEEP_SUSPICIOUS_CRON_COUNT": ">0",
"SECURITY_DEEP_SSH_BRUTEFORCE_IPS": ">0",
"SECURITY_DEEP_UNEXPECTED_PORTS_COUNT": ">0",
}
......
{% extends "service_manage/base.html" %}
{% block title %}服务授权{% endblock %}
{% block breadcrumb %}服务授权{% endblock %}
{% block extra_css %}
<style>
.steps-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin-bottom: 24px; }
.step-card { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid var(--gray-200); position: relative; }
.step-number { position: absolute; top: -12px; left: 20px; background: var(--primary); color: #fff; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; }
.step-title { font-size: 16px; font-weight: 700; color: var(--gray-900); margin-bottom: 16px; padding-top: 8px; }
.step-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
.btn-action { display: inline-flex; align-items: center; gap: 6px; padding: 10px 16px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; transition: all 0.2s; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-hover); }
.btn-outline { background: #fff; color: var(--gray-700); border: 1px solid var(--gray-300); }
.btn-outline:hover { background: var(--gray-50); border-color: var(--gray-400); }
.step-desc { font-size: 13px; color: var(--gray-500); line-height: 1.6; }
.form-group { margin-bottom: 16px; }
.form-label { display: block; font-size: 14px; font-weight: 600; color: var(--gray-700); margin-bottom: 6px; }
.form-label .required { color: var(--red); margin-left: 2px; }
.form-input { width: 100%; padding: 10px 12px; border: 1px solid var(--gray-300); border-radius: 8px; font-size: 14px; color: var(--gray-900); background: #fff; transition: border-color 0.2s, box-shadow 0.2s; }
.form-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-light); }
.form-input::placeholder { color: var(--gray-400); }
textarea.form-input { min-height: 80px; resize: vertical; }
.btn-save { display: inline-flex; align-items: center; gap: 6px; background: var(--primary); color: #fff; border: none; padding: 10px 24px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; }
.btn-save:hover { background: var(--primary-hover); }
.upload-area { border: 2px dashed var(--gray-300); border-radius: 8px; padding: 20px; text-align: center; cursor: pointer; transition: border-color 0.2s, background 0.2s; }
.upload-area:hover { border-color: var(--primary); background: var(--primary-light); }
.upload-icon { font-size: 24px; margin-bottom: 8px; }
.upload-text { font-size: 14px; color: var(--gray-500); }
.upload-hint { font-size: 12px; color: var(--gray-400); margin-top: 4px; }
@media screen and (max-width: 768px) { .steps-container { grid-template-columns: 1fr; } }
</style>
{% endblock %}
{% block content %}
<div class="section-head"><h2 class="section-title">🔑 服务授权</h2></div>
<div class="steps-container">
<div class="step-card">
<div class="step-number">1</div>
<div class="step-title">申请服务授权</div>
<div class="step-actions">
<button class="btn-action btn-outline" onclick="handleDownload('activation')">📥 下载激活文件</button>
<button class="btn-action btn-primary" onclick="document.getElementById('auth-file').click()">📤 上传授权文件</button>
<input type="file" id="auth-file" style="display:none" accept=".lic,.key,.dat" onchange="handleAuthUpload(this)">
</div>
<div class="upload-area" onclick="document.getElementById('auth-file').click()">
<div class="upload-icon">📁</div>
<div class="upload-text">点击或拖拽上传授权文件</div>
<div class="upload-hint">支持 .lic / .key / .dat 格式</div>
</div>
<div class="step-desc" style="margin-top: 12px;">下载激活文件后发送给厂商获取授权,收到授权文件后上传即可完成授权申请。</div>
</div>
<div class="step-card">
<div class="step-number">2</div>
<div class="step-title">填写项目信息</div>
<form id="project-form" onsubmit="handleSaveProject(event)">
<div class="form-group"><label class="form-label">项目销售 <span class="required">*</span></label><input type="text" class="form-input" placeholder="请输入项目的销售" required></div>
<div class="form-group"><label class="form-label">部署人员 <span class="required">*</span></label><input type="text" class="form-input" placeholder="请输入项目部署或更新人员" required></div>
<div class="form-group"><label class="form-label">下单时间 <span class="required">*</span></label><input type="datetime-local" class="form-input" required></div>
<div class="form-group"><label class="form-label">服务器密码 <span class="required">*</span></label><input type="password" class="form-input" placeholder="请输入服务器密码" required></div>
<div class="form-group"><label class="form-label">项目概述 <span class="required">*</span></label><textarea class="form-input" placeholder="请输入项目概述" required></textarea></div>
<button type="submit" class="btn-save">💾 保存</button>
</form>
</div>
<div class="step-card">
<div class="step-number">3</div>
<div class="step-title">填写并提交部署视图</div>
<div class="step-actions"><button class="btn-action btn-outline" onclick="handleDownload('deploy-view')">📥 下载部署视图</button></div>
<div class="upload-area" onclick="document.getElementById('deploy-file').click()">
<div class="upload-icon">📄</div>
<div class="upload-text">点击上传已填写的部署视图</div>
<div class="upload-hint">支持 .xlsx / .xls / .pdf 格式</div>
</div>
<input type="file" id="deploy-file" style="display:none" accept=".xlsx,.xls,.pdf" onchange="handleDeployUpload(this)">
<div class="step-desc" style="margin-top: 12px;">下载部署视图模板,填写后上传提交。</div>
</div>
<div class="step-card">
<div class="step-number">4</div>
<div class="step-title">填写并提交自检文档</div>
<div class="step-actions"><button class="btn-action btn-outline" onclick="handleDownload('self-check')">📥 下载自检文档</button></div>
<div class="upload-area" onclick="document.getElementById('check-file').click()">
<div class="upload-icon">📋</div>
<div class="upload-text">点击上传已填写的自检文档</div>
<div class="upload-hint">支持 .docx / .doc / .pdf 格式</div>
</div>
<input type="file" id="check-file" style="display:none" accept=".docx,.doc,.pdf" onchange="handleCheckUpload(this)">
<div class="step-desc" style="margin-top: 12px;">下载自检文档模板,填写后上传提交。</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function handleDownload(type) { alert("下载功能开发中,敬请期待"); }
function handleAuthUpload(input) { if (input.files.length > 0) alert("授权文件上传功能开发中,敬请期待"); }
function handleDeployUpload(input) { if (input.files.length > 0) alert("部署视图上传功能开发中,敬请期待"); }
function handleCheckUpload(input) { if (input.files.length > 0) alert("自检文档上传功能开发中,敬请期待"); }
function handleSaveProject(e) { e.preventDefault(); alert("项目信息保存功能开发中,敬请期待"); }
</script>
{% endblock %}
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5">
<title>{% block title %}服务管理{% endblock %} - 运行维护平台</title>
<style>
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--primary-light: #dbeafe;
--green: #16a34a;
--green-light: #dcfce7;
--yellow: #d97706;
--yellow-light: #fef3c7;
--red: #dc2626;
--red-light: #fee2e2;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Microsoft YaHei", sans-serif;
background: #f3f4f6;
min-height: 100vh;
}
/* ===== 侧边栏 ===== */
.sidebar {
position: fixed;
left: 0;
top: 0;
width: 200px;
height: 100vh;
background: linear-gradient(180deg, #1e293b 0%, #334155 100%);
z-index: 100;
transition: transform 0.3s ease;
}
.sidebar-brand {
color: #fff;
font-size: 18px;
font-weight: 700;
padding: 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 8px;
}
.sidebar-nav {
padding: 16px 0;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 20px;
color: #94a3b8;
text-decoration: none;
font-size: 14px;
transition: all 0.2s;
}
.nav-item:hover {
background: rgba(255,255,255,0.05);
color: #fff;
}
.nav-item.active {
background: var(--primary);
color: #fff;
}
.nav-icon {
font-size: 16px;
width: 20px;
text-align: center;
}
/* ===== 顶部条 ===== */
.topbar {
position: fixed;
left: 200px;
top: 0;
right: 0;
height: 56px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
z-index: 99;
}
.topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.hamburger {
display: none;
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.35);
color: #fff;
font-size: 18px;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
}
.topbar-home {
background: rgba(255,255,255,0.2);
color: #fff;
border: 1px solid rgba(255,255,255,0.35);
padding: 6px 14px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
}
.topbar-home:hover {
background: rgba(255,255,255,0.3);
}
.topbar-breadcrumb {
color: rgba(255,255,255,0.7);
font-size: 14px;
}
.topbar-breadcrumb a {
color: rgba(255,255,255,0.7);
text-decoration: none;
}
.topbar-breadcrumb a:hover {
color: #fff;
}
.topbar-breadcrumb .current {
color: #fff;
}
.topbar-user {
color: #fff;
display: flex;
align-items: center;
gap: 14px;
font-size: 14px;
}
.btn-logout {
background: rgba(255,255,255,0.15);
color: #fff;
border: 1px solid rgba(255,255,255,0.3);
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
}
.btn-logout:hover {
background: rgba(255,255,255,0.25);
}
/* ===== 主内容区 ===== */
.main-content {
margin-left: 200px;
padding: 84px 24px 28px;
min-height: 100vh;
}
/* ===== 通用组件 ===== */
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.section-title {
font-size: 18px;
font-weight: 700;
color: var(--gray-900);
display: flex;
align-items: center;
gap: 8px;
}
.footer {
text-align: center;
padding: 20px;
color: var(--gray-500);
font-size: 12px;
}
/* ===== 移动端响应式 ===== */
@media screen and (max-width: 768px) {
.sidebar {
transform: translateX(-200px);
}
.sidebar.open {
transform: translateX(0);
}
.topbar {
left: 0;
}
.main-content {
margin-left: 0;
padding: 84px 14px 28px;
}
.hamburger {
display: block;
}
.topbar-home {
display: none;
}
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- 侧边栏 -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-brand">🛠️ 服务管理</div>
<nav class="sidebar-nav">
<a href="/service-manage/authorization" class="nav-item {{ 'active' if active_menu == 'authorization' }}">
<span class="nav-icon">🔑</span>
<span>服务授权</span>
</a>
<a href="/service-manage/upgrade" class="nav-item {{ 'active' if active_menu == 'upgrade' }}">
<span class="nav-icon">⬆️</span>
<span>服务升级</span>
</a>
<a href="/service-manage/info" class="nav-item {{ 'active' if active_menu == 'info' }}">
<span class="nav-icon">ℹ️</span>
<span>服务信息</span>
</a>
</nav>
</aside>
<!-- 顶部条 -->
<header class="topbar">
<div class="topbar-left">
<button class="hamburger" id="hamburger" onclick="toggleSidebar()"></button>
<a href="/" class="topbar-home">🏠 返回首页</a>
<span class="topbar-breadcrumb">
<a href="/service-manage">服务管理</a>
<span> / </span>
<span class="current">{% block breadcrumb %}{% endblock %}</span>
</span>
</div>
<div class="topbar-user">
<span>{{ user.username }}(管理员)</span>
<button class="btn-logout" onclick="handleLogout()">🚪 退出</button>
</div>
</header>
<!-- 主内容区 -->
<main class="main-content">
{% block content %}{% endblock %}
</main>
<div class="footer">运行维护平台 · 服务管理模块</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
}
// 点击遮罩关闭侧边栏
document.addEventListener('click', function(e) {
const sidebar = document.getElementById('sidebar');
const hamburger = document.getElementById('hamburger');
if (sidebar.classList.contains('open') &&
!sidebar.contains(e.target) &&
!hamburger.contains(e.target)) {
sidebar.classList.remove('open');
}
});
async function handleLogout() {
try {
await fetch('/logout', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
credentials: 'include'
});
} catch (e) {}
window.location.href = '/login';
}
</script>
{% block extra_js %}{% endblock %}
</body>
</html>
{% extends "service_manage/base.html" %}
{% block title %}服务信息{% endblock %}
{% block breadcrumb %}服务信息{% endblock %}
{% block extra_css %}
<style>
.filter-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; }
.filter-label { font-size: 14px; font-weight: 600; color: var(--gray-700); }
.filter-select { padding: 8px 12px; border: 1px solid var(--gray-300); border-radius: 8px; font-size: 14px; color: var(--gray-900); background: #fff; min-width: 160px; cursor: pointer; }
.filter-select:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-light); }
.info-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }
.info-card { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid var(--gray-200); }
.info-card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.info-card-title { font-size: 16px; font-weight: 700; color: var(--gray-900); display: flex; align-items: center; gap: 8px; }
.info-card-value { font-size: 36px; font-weight: 700; margin-bottom: 8px; }
.info-card-unit { font-size: 14px; font-weight: 400; color: var(--gray-500); }
.progress-bar { width: 100%; height: 8px; background: var(--gray-200); border-radius: 4px; overflow: hidden; margin-top: 8px; }
.progress-fill { height: 100%; border-radius: 4px; transition: width 0.6s ease; }
.progress-green { background: var(--green); }
.progress-yellow { background: var(--yellow); }
.progress-red { background: var(--red); }
.info-card-detail { font-size: 13px; color: var(--gray-500); margin-top: 8px; }
.status-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; }
.badge-normal { background: var(--green-light); color: var(--green); }
.badge-warning { background: var(--yellow-light); color: var(--yellow); }
.badge-danger { background: var(--red-light); color: var(--red); }
@media screen and (max-width: 768px) { .info-cards { grid-template-columns: 1fr; } }
</style>
{% endblock %}
{% block content %}
<div class="section-head"><h2 class="section-title">ℹ️ 服务信息</h2></div>
<div class="filter-bar">
<label class="filter-label">监控类型:</label>
<select class="filter-select" onchange="handleFilter(this.value)">
<option value="system">系统</option>
<option value="docker">Docker容器</option>
<option value="nginx">Nginx</option>
<option value="mysql">MySQL</option>
<option value="redis">Redis</option>
</select>
</div>
<div class="info-cards">
<div class="info-card">
<div class="info-card-header">
<div class="info-card-title">🖥️ CPU 使用率</div>
<span class="status-badge badge-normal">正常</span>
</div>
<div class="info-card-value" style="color: var(--green);">32.5<span class="info-card-unit">%</span></div>
<div class="progress-bar"><div class="progress-fill progress-green" style="width: 32.5%;"></div></div>
<div class="info-card-detail">核心数: 8 | 负载: 2.1, 1.8, 1.6</div>
</div>
<div class="info-card">
<div class="info-card-header">
<div class="info-card-title">💾 内存使用率</div>
<span class="status-badge badge-warning">注意</span>
</div>
<div class="info-card-value" style="color: var(--yellow);">68.3<span class="info-card-unit">%</span></div>
<div class="progress-bar"><div class="progress-fill progress-yellow" style="width: 68.3%;"></div></div>
<div class="info-card-detail">已用: 10.9 GB / 总计: 16 GB</div>
</div>
<div class="info-card">
<div class="info-card-header">
<div class="info-card-title">💿 硬盘使用率</div>
<span class="status-badge badge-normal">正常</span>
</div>
<div class="info-card-value" style="color: var(--green);">45.2<span class="info-card-unit">%</span></div>
<div class="progress-bar"><div class="progress-fill progress-green" style="width: 45.2%;"></div></div>
<div class="info-card-detail">已用: 90.4 GB / 总计: 200 GB</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function handleFilter(type) {
alert("筛选功能开发中,敬请期待 - 类型: " + type);
}
</script>
{% endblock %}
\ No newline at end of file
此差异已折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论