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

feat(service-monitor): 报告页面折叠展示与容器进程检测扩展

- report.html: 模块默认折叠,仅异常模块自动展开;进程列表表格化展示;长值截断可展开
- 21_docker_deep.sh: 容器进程检测从硬编码 4 个改为遍历所有已配置容器
- check_modules.py: 模块清单 17→30
- display_names.py: 新增 ~250 个检测 KEY 中文显示名
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 6c84387c
...@@ -145,25 +145,23 @@ check_container_limits() { ...@@ -145,25 +145,23 @@ check_container_limits() {
done done
} }
# 检测容器进程详情 # 检测容器进程详情(遍历所有已配置容器)
check_container_processes() { check_container_processes() {
for key in "mysql" "redis" "emqx" "java"; do for key in "${!CONTAINERS[@]}"; do
local container_name="${CONTAINERS[$key]}" local container_name
container_name=$(resolve_container "$key" 2>/dev/null)
if [ -z "$container_name" ]; then if [ -z "$container_name" ]; then
continue continue
fi fi
if ! docker ps --format "{{.Names}}" | grep -q "^${container_name}$"; then
continue
fi
# 获取容器内进程数 # 获取容器内进程数
local process_count local process_count
process_count=$(docker exec "$container_name" ps aux 2>/dev/null | wc -l) process_count=$(docker exec "$container_name" ps aux 2>/dev/null | wc -l)
process_count=$((process_count - 1)) process_count=$((process_count - 1))
if [ -n "$process_count" ] && [ "$process_count" -gt 0 ]; then if [ -n "$process_count" ] && [ "$process_count" -gt 0 ]; then
output_result "PROCESSES_${key}" "$process_count"
output_result "PROCESSES_${container_name}" "$process_count" output_result "PROCESSES_${container_name}" "$process_count"
fi fi
done done
......
...@@ -71,6 +71,14 @@ ALL_MODULES: List[CheckModule] = [ ...@@ -71,6 +71,14 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "11_scheduled_tasks.sh"), {SUITE_FULL}, "11_scheduled_tasks.sh"),
CheckModule("12_port_check", "端口检测", "system", CheckModule("12_port_check", "端口检测", "system",
{SUITE_FULL}, "12_port_check.sh"), {SUITE_FULL}, "12_port_check.sh"),
CheckModule("40_comprehensive_diagnosis", "综合诊断", "system",
{SUITE_FULL}, "40_comprehensive_diagnosis.sh"),
CheckModule("43_security_compliance", "安全合规", "system",
{SUITE_FULL}, "43_security_compliance.sh"),
CheckModule("44_system_logs", "系统日志", "system",
{SUITE_FULL}, "44_system_logs.sh"),
CheckModule("45_time_sync", "时间同步", "system",
{SUITE_FULL}, "45_time_sync.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"),
...@@ -84,6 +92,24 @@ ALL_MODULES: List[CheckModule] = [ ...@@ -84,6 +92,24 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "24_redis_basic.sh"), {SUITE_FULL}, "24_redis_basic.sh"),
CheckModule("25_redis_depth", "Redis深度检测", "service", CheckModule("25_redis_depth", "Redis深度检测", "service",
{SUITE_FULL}, "25_redis_depth.sh"), {SUITE_FULL}, "25_redis_depth.sh"),
CheckModule("26_emqx_basic", "EMQX基础", "service",
{SUITE_FULL}, "26_emqx_basic.sh"),
CheckModule("27_emqx_deep", "EMQX深度检测", "service",
{SUITE_FULL}, "27_emqx_deep.sh"),
CheckModule("28_java_check", "Java应用检测", "service",
{SUITE_FULL}, "28_java_check.sh"),
CheckModule("29_python_check", "Python应用检测", "service",
{SUITE_FULL}, "29_python_check.sh"),
CheckModule("30_nginx_check", "Nginx基础检测", "service",
{SUITE_FULL}, "30_nginx_check.sh"),
CheckModule("31_nacos_check", "Nacos基础检测", "service",
{SUITE_FULL}, "31_nacos_check.sh"),
CheckModule("33_app_logs", "应用日志分析", "service",
{SUITE_FULL}, "33_app_logs.sh"),
CheckModule("34_nginx_deep", "Nginx深度检测", "service",
{SUITE_FULL}, "34_nginx_deep.sh"),
CheckModule("35_nacos_deep", "Nacos深度检测", "service",
{SUITE_FULL}, "35_nacos_deep.sh"),
CheckModule("37_ustorage_check", "UStorage容器监测", "service", CheckModule("37_ustorage_check", "UStorage容器监测", "service",
{SUITE_FULL}, "37_ustorage_check.sh"), {SUITE_FULL}, "37_ustorage_check.sh"),
CheckModule("38_utracker_check", "UTracker容器监测", "service", CheckModule("38_utracker_check", "UTracker容器监测", "service",
......
...@@ -335,4 +335,275 @@ DISPLAY_NAMES: Dict[str, str] = { ...@@ -335,4 +335,275 @@ DISPLAY_NAMES: Dict[str, str] = {
"UTRACKER_DISK_LEVEL": "UTracker磁盘等级", "UTRACKER_DISK_LEVEL": "UTracker磁盘等级",
"UTRACKER_LOG_SIZE": "UTracker日志大小", "UTRACKER_LOG_SIZE": "UTracker日志大小",
"UTRACKER_LOG_LEVEL": "UTracker日志等级", "UTRACKER_LOG_LEVEL": "UTracker日志等级",
# ---- 26 EMQX基础检测 ----
"EMQX_CONTAINER_STATUS": "EMQX容器状态",
"EMQX_CONTAINER_LEVEL": "EMQX容器等级",
"EMQX_VERSION": "EMQX版本",
"EMQX_STATUS": "EMQX状态",
"EMQX_STATUS_LEVEL": "EMQX状态等级",
"EMQX_CLIENTS": "EMQX客户端数",
"EMQX_SESSIONS": "EMQX会话数",
"EMQX_SUBSCRIPTIONS": "EMQX订阅数",
"EMQX_TOPICS": "EMQX主题数",
"EMQX_ROUTES": "EMQX路由数",
"EMQX_LISTENERS": "EMQX监听器数",
"EMQX_CLUSTER": "EMQX集群状态",
# ---- 27 EMQX深度检测 ----
"EMQX_CONTAINER": "EMQX容器名",
"EMQX_BROKER_VERSION": "EMQX Broker版本",
"EMQX_NODE_STATUS": "EMQX节点状态",
"EMQX_NODE_LEVEL": "EMQX节点等级",
"EMQX_CLIENTS_TOTAL": "EMQX客户端总数",
"EMQX_CLIENTS_CONNECTED": "EMQX已连接客户端",
"EMQX_CLIENTS_DISCONNECTED": "EMQX已断开客户端",
"EMQX_MAX_CLIENTS": "EMQX最大客户端数",
"EMQX_SUBSCRIPTIONS_TOTAL": "EMQX订阅总数",
"EMQX_TOP_TOPICS": "EMQX热门主题",
"EMQX_MESSAGES_SENT": "EMQX发送消息数",
"EMQX_MESSAGES_RECEIVED": "EMQX接收消息数",
"EMQX_TOPICS_TOTAL": "EMQX主题总数",
"EMQX_TOP5_TOPICS": "EMQX TOP5主题",
"EMQX_ROUTES_TOTAL": "EMQX路由总数",
"EMQX_LISTENERS_COUNT": "EMQX监听器数量",
"EMQX_LISTENER_PORTS": "EMQX监听端口",
"EMQX_CLUSTER_MODE": "EMQX集群模式",
"EMQX_CLUSTER_NODES": "EMQX集群节点数",
"EMQX_MAX_CONNECTIONS": "EMQX最大连接数",
"EMQX_CONN_USAGE": "EMQX连接使用率",
"EMQX_CONN_LEVEL": "EMQX连接等级",
"EMQX_MEMORY_USAGE": "EMQX内存使用率",
"EMQX_SESSIONS_TOTAL": "EMQX会话总数",
"EMQX_SESSIONS_ACTIVE": "EMQX活跃会话",
"EMQX_SESSIONS_INACTIVE": "EMQX非活跃会话",
"EMQX_PLUGINS_LOADED": "EMQX已加载插件",
"EMQX_PLUGINS_COUNT": "EMQX插件数",
"EMQX_RULES_COUNT": "EMQX规则数",
"EMQX_ALARMS_COUNT": "EMQX告警数",
"EMQX_ALARMS_LEVEL": "EMQX告警等级",
"EMQX_ALARMS_DETAIL": "EMQX告警详情",
"EMQX_PACKETS_CONN": "EMQX连接包数",
"EMQX_PACKETS_RECV": "EMQX接收包数",
"EMQX_PACKETS_SENT": "EMQX发送包数",
"EMQX_DASHBOARD_STATUS": "EMQX Dashboard状态",
"EMQX_DASHBOARD_LEVEL": "EMQX Dashboard等级",
"EMQX_DASHBOARD_URL": "EMQX Dashboard地址",
"EMQX_DASHBOARD_VERSION": "EMQX Dashboard版本",
# ---- 28 Java应用检测 ----
"JAVA_CONTAINER_STATUS": "Java容器状态",
"JAVA_CONTAINER_LEVEL": "Java容器等级",
"JAVA_CONTAINER": "Java容器名",
"JAVA_VERSION": "Java版本",
"JAVA_PID": "Java进程PID",
"JAVA_VM_SIZE": "JVM虚拟内存",
"JAVA_VM_RSS": "JVM物理内存",
"JAVA_THREADS": "Java线程数",
"JAVA_HEAP_CAPACITY": "JVM堆容量",
"JAVA_HEAP_USED": "JVM堆使用",
"JAVA_HEAP_PERCENT": "JVM堆使用率",
"JAVA_HEAP_LEVEL": "JVM堆等级",
"JAVA_FULL_GC": "Full GC次数",
"JAVA_YOUNG_GC": "Young GC次数",
"JAVA_GC_LEVEL": "GC状态等级",
"JAVA_THREAD_COUNT": "Java线程数",
"JAVA_THREAD_LEVEL": "线程数等级",
"JAVA_LOG_ERRORS": "Java日志错误数",
"JAVA_LOG_LEVEL": "Java日志等级",
"SPRINGBOOT_ACTUATOR": "SpringBoot Actuator",
"SPRINGBOOT_HEALTH_STATUS": "SpringBoot健康状态",
"SPRINGBOOT_HEALTH_LEVEL": "SpringBoot健康等级",
"JAVA_CPU_USAGE": "Java CPU使用率",
"JAVA_MEMORY_USAGE": "Java内存使用",
"JAVA_MEMORY_PERCENT": "Java内存使用率",
"JAVA_PROCESS_COUNT": "Java容器进程总数",
"JAVA_PROCESS_LIST": "Java容器进程列表",
"JAVA_PROCESSES_DETAIL": "Java进程详情",
# ---- 29 Python应用检测 ----
"PYTHON_CONTAINER_STATUS": "Python容器状态",
"PYTHON_CONTAINER_LEVEL": "Python容器等级",
"PYTHON_VERSION": "Python版本",
"PYTHON_PROCESSES": "Python进程数",
"PYTHON_PROCESS_COUNT": "Python容器进程总数",
"PYTHON_PROCESS_LIST": "Python容器进程列表",
"PYTHON_PROCESSES_DETAIL": "Python进程详情",
"PYTHON_MEMORY": "Python内存使用",
"PYTHON_CPU": "Python CPU使用",
"PYTHON_LOG_ERRORS": "Python日志错误数",
"PYTHON_LOG_LEVEL": "Python日志等级",
# ---- 30 Nginx基础检测 ----
"NGINX_CONTAINER_STATUS": "Nginx容器状态",
"NGINX_CONTAINER_LEVEL": "Nginx容器等级",
"NGINX_VERSION": "Nginx版本",
"NGINX_CONFIG": "Nginx配置状态",
"NGINX_STATUS": "Nginx状态页",
"NGINX_MEMORY": "Nginx内存使用",
# ---- 31 Nacos基础检测 ----
"NACOS_CONTAINER_STATUS": "Nacos容器状态",
"NACOS_CONTAINER_LEVEL": "Nacos容器等级",
"NACOS_HEALTH": "Nacos健康状态",
"NACOS_MEMORY": "Nacos内存使用",
"NACOS_SERVICES_COUNT": "Nacos服务数",
# ---- 33 应用日志分析 ----
"APP_LOG_ERRORS_24H": "应用日志错误(24h)",
"APP_LOG_LEVEL": "应用日志等级",
"CONTAINER_LOG_ERRORS_TOTAL": "容器日志错误总数",
"CONTAINER_LOG_LEVEL": "容器日志等级",
"SYSLOG_ERRORS": "系统日志错误",
# ---- 34 Nginx深度检测 ----
"NGINX_ACTIVE_CONNECTIONS": "Nginx活跃连接",
"NGINX_ACCEPTED_CONNECTIONS": "Nginx已接受连接",
"NGINX_HANDLED_CONNECTIONS": "Nginx已处理连接",
"NGINX_REQUESTS_TOTAL": "Nginx总请求数",
"NGINX_PID": "Nginx进程PID",
"NGINX_PROCESSES": "Nginx进程数",
"NGINX_LISTENING_PORTS": "Nginx监听端口",
"NGINX_CONFIG_STATUS": "Nginx配置状态",
"NGINX_CONFIG_LEVEL": "Nginx配置等级",
"NGINX_CONFIG_ERROR": "Nginx配置错误",
"NGINX_WORKER_PROCESSES": "Nginx Worker进程数",
"NGINX_WORKER_CONNECTIONS": "Nginx Worker连接数",
"NGINX_ACCESS_LOG_SIZE": "Nginx访问日志大小",
"NGINX_ERROR_LOG_SIZE": "Nginx错误日志大小",
"NGINX_RECENT_ERRORS": "Nginx最近错误数",
"NGINX_SSL_ENABLED": "Nginx SSL启用",
"NGINX_SSL_CERT_EXPIRY": "Nginx SSL证书过期",
"NGINX_CACHE_ENABLED": "Nginx缓存启用",
"NGINX_CACHE_PATH": "Nginx缓存路径",
"NGINX_UPSTREAM_COUNT": "Nginx Upstream数",
"NGINX_UPSTREAMS": "Nginx Upstream列表",
"NGINX_SERVER_BLOCKS": "Nginx Server块数",
"NGINX_STATUS_2XX": "Nginx 2XX响应",
"NGINX_STATUS_3XX": "Nginx 3XX响应",
"NGINX_STATUS_4XX": "Nginx 4XX响应",
"NGINX_STATUS_5XX": "Nginx 5XX响应",
"NGINX_ERROR_RATE": "Nginx错误率",
"NGINX_UPTIME_DAYS": "Nginx运行天数",
"NGINX_CPU_USAGE": "Nginx CPU使用率",
"NGINX_MEMORY_USAGE": "Nginx内存使用",
"NGINX_MEMORY_PERCENT": "Nginx内存使用率",
# ---- 35 Nacos深度检测 ----
"NACOS_VERSION": "Nacos版本",
"NACOS_NAMESPACES": "Nacos命名空间数",
"NACOS_INSTANCES_COUNT": "Nacos实例数",
"NACOS_HEALTHY_INSTANCES": "Nacos健康实例",
"NACOS_HEALTH_RATE": "Nacos健康率",
"NACOS_CONFIGS_COUNT": "Nacos配置数",
"NACOS_RAFT_MODE": "Nacos Raft模式",
"NACOS_RAFT_ROLE": "Nacos Raft角色",
"NACOS_RAFT_NODES": "Nacos Raft节点数",
"NACOS_HEALTH_STATUS": "Nacos健康状态",
"NACOS_HEALTH_LEVEL": "Nacos健康等级",
"NACOS_HEAP_USAGE": "Nacos堆使用率",
"NACOS_LOG_SIZE": "Nacos日志大小",
"NACOS_RECENT_ERRORS": "Nacos最近错误",
"NACOS_CPU_USAGE": "Nacos CPU使用率",
"NACOS_MEMORY_USAGE": "Nacos内存使用",
"NACOS_MEMORY_PERCENT": "Nacos内存使用率",
# ---- 40 综合诊断 ----
"DIAG_LOAD_1MIN": "诊断-1分钟负载",
"DIAG_LOAD_PER_CPU": "诊断-单核负载",
"DIAG_LOAD_LEVEL": "诊断-负载等级",
"DIAG_MEMORY_USAGE": "诊断-内存使用率",
"DIAG_MEMORY_TOTAL": "诊断-总内存",
"DIAG_MEMORY_USED": "诊断-已用内存",
"DIAG_MEMORY_FREE": "诊断-可用内存",
"DIAG_MEMORY_LEVEL": "诊断-内存等级",
"DIAG_DISK_USAGE": "诊断-磁盘使用率",
"DIAG_DISK_LEVEL": "诊断-磁盘等级",
"DIAG_DOCKER_TOTAL": "诊断-Docker总数",
"DIAG_DOCKER_RUNNING": "诊断-Docker运行数",
"DIAG_DOCKER_STOPPED": "诊断-Docker停止数",
"DIAG_DOCKER_LEVEL": "诊断-Docker等级",
"DIAG_OOM_ISSUES": "诊断-OOM问题数",
"DIAG_DISK_ISSUES": "诊断-磁盘问题数",
"DIAG_TOTAL_ISSUES": "诊断-总问题数",
"DIAG_STABILITY_LEVEL": "诊断-稳定性等级",
"DIAG_GATEWAY": "诊断-网关地址",
"DIAG_GATEWAY_STATUS": "诊断-网关状态",
"DIAG_NETWORK_LEVEL": "诊断-网络等级",
"DIAG_DNS_STATUS": "诊断-DNS状态",
"DIAG_HEALTH_SCORE": "诊断-健康评分",
"DIAG_HEALTH_LEVEL": "诊断-健康等级",
# ---- 43 安全合规 ----
"AUTH_FAILURES_24H": "认证失败次数(24h)",
"AUTH_FAILURES_LEVEL": "认证失败等级",
"CURRENT_USERS_COUNT": "当前用户数",
"CURRENT_USERS": "当前用户列表",
"FIREWALL_STATUS": "防火墙状态",
"IPTABLES_STATUS": "iptables状态",
"IPTABLES_RULES_COUNT": "iptables规则数",
"OPEN_PORTS_COUNT": "开放端口数",
"ABNORMAL_ACCOUNTS": "异常账户",
"ABNORMAL_ACCOUNTS_LEVEL": "异常账户等级",
"SSH_PERMIT_ROOT": "SSH允许root",
"SSH_PASSWORD_AUTH": "SSH密码认证",
"SSH_PORT": "SSH端口",
"SSH_ROOT_LOGIN_LEVEL": "SSH root登录等级",
"OPEN_TCP_PORTS": "开放TCP端口",
"HIGH_RISK_PORTS": "高风险端口",
"HIGH_RISK_PORTS_LEVEL": "高风险端口等级",
"MAX_LOGIN_FAILURES": "最大登录失败数",
"BRUTE_FORCE_IPS": "暴力破解IP",
"BRUTE_FORCE_LEVEL": "暴力破解等级",
# ---- 44 系统日志 ----
"KERNEL_ERRORS_24H": "内核错误(24h)",
"KERNEL_ERRORS_LEVEL": "内核错误等级",
"DISK_ERRORS_24H": "磁盘错误(24h)",
"DISK_ERRORS_LEVEL": "磁盘错误等级",
"KERNEL_PANIC_COUNT": "内核Panic数",
"KERNEL_OOPS_COUNT": "内核Oops数",
"KERNEL_STABILITY_LEVEL": "内核稳定性等级",
"SERVICE_CRASH_COUNT": "服务崩溃数",
"CRASHED_SERVICES": "崩溃服务列表",
"SERVICE_STABILITY_LEVEL": "服务稳定性等级",
"SYSTEMD_FAILED_COUNT": "Systemd失败数",
"SYSTEMD_FAILED_SERVICES": "Systemd失败服务",
"SYSTEMD_FAILED_LEVEL": "Systemd失败等级",
"OOM_KILLER_COUNT": "OOM Killer数",
"OOM_LEVEL": "OOM等级",
"LARGE_LOG_FILES": "大日志文件",
"LARGE_LOG_FILES_LEVEL": "大日志文件等级",
"MESSAGES_ERRORS_COUNT": "Messages错误数",
"MESSAGES_WARNS_COUNT": "Messages警告数",
"MESSAGES_LOG_SIZE": "Messages日志大小",
# ---- 45 时间同步 ----
"NTP_SERVICE_STATUS": "NTP服务状态",
"NTP_SERVICE_LEVEL": "NTP服务等级",
"SYSTEM_CLOCK_SYNC": "系统时钟同步",
"SYSTEM_CLOCK_SYNC_LEVEL": "时钟同步等级",
"NTP_DAEMON": "NTP守护进程",
"NTP_SOURCES": "NTP时间源",
"NTP_CURRENT_SOURCE": "NTP当前源",
"NTP_OFFSET_MS": "NTP偏移量",
"NTP_OFFSET_LEVEL": "NTP偏移等级",
"SYSTEM_DATETIME": "系统时间",
"SYSTEM_TIMESTAMP": "系统时间戳",
"SYSTEM_TIMEZONE": "系统时区",
"HTTPS_CERT_MIN_DAYS": "HTTPS证书剩余天数",
"HTTPS_CERT_LEVEL": "HTTPS证书等级",
"HTTPS_CERT_STATUS": "HTTPS证书状态",
"SYSTEM_UPTIME_DAYS": "系统运行天数",
# ---- 容器进程检测(动态KEY) ----
"PROCESSES_mysql": "MySQL进程数",
"PROCESSES_redis": "Redis进程数",
"PROCESSES_emqx": "EMQX进程数",
"PROCESSES_java": "Java进程数",
"PROCESSES_nginx": "Nginx进程数",
"PROCESSES_nacos": "Nacos进程数",
"PROCESSES_python": "Python进程数",
"PROCESSES_ustorage": "UStorage进程数",
"PROCESSES_utracker": "UTracker进程数",
} }
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.navbar-home { background:rgba(255,255,255,.2); color:#fff; border:1px solid rgba(255,255,255,.35); padding:6px 14px; border-radius:8px; font-size:13px; font-weight:600; text-decoration:none; } .navbar-home { background:rgba(255,255,255,.2); color:#fff; border:1px solid rgba(255,255,255,.35); padding:6px 14px; border-radius:8px; font-size:13px; font-weight:600; text-decoration:none; }
.navbar-user { color:#fff; font-size:14px; } .navbar-user { color:#fff; font-size:14px; }
.container { max-width:900px; margin:0 auto; padding:28px 24px; } .container { max-width:960px; margin:0 auto; padding:28px 24px; }
/* 报告头 */ /* 报告头 */
.report-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:18px; flex-wrap:wrap; gap:12px; } .report-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:18px; flex-wrap:wrap; gap:12px; }
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
.btn-export { background:var(--primary); color:#fff; border:none; padding:9px 18px; border-radius:8px; font-size:14px; font-weight:600; cursor:pointer; } .btn-export { background:var(--primary); color:#fff; border:none; padding:9px 18px; border-radius:8px; font-size:14px; font-weight:600; cursor:pointer; }
.export-menu { display:none; position:absolute; right:0; top:44px; background:#fff; border:1px solid var(--gray-200); border-radius:8px; box-shadow:0 4px 16px rgba(0,0,0,.12); overflow:hidden; z-index:10; } .export-menu { display:none; position:absolute; right:0; top:44px; background:#fff; border:1px solid var(--gray-200); border-radius:8px; box-shadow:0 4px 16px rgba(0,0,0,.12); overflow:hidden; z-index:10; }
.export-menu.show { display:block; } .export-menu.show { display:block; }
.export-menu a { display:block; padding:10px 20px; font-size:14px; color:var(--gray-700); text-decoration:none; white-space:nowrap; } .export-menu a { display:block; padding:10px 20px; font-size:14px; color:var(--gray-700); text-decoration:none; }
.export-menu a:hover { background:var(--gray-50); } .export-menu a:hover { background:var(--gray-50); }
/* 汇总卡片 */ /* 汇总卡片 */
...@@ -60,12 +60,14 @@ ...@@ -60,12 +60,14 @@
.module-block { background:#fff; border-radius:12px; border:1px solid var(--gray-200); margin-bottom:12px; overflow:hidden; } .module-block { background:#fff; border-radius:12px; border:1px solid var(--gray-200); margin-bottom:12px; overflow:hidden; }
.module-header { display:flex; align-items:center; gap:10px; padding:14px 18px; cursor:pointer; user-select:none; } .module-header { display:flex; align-items:center; gap:10px; padding:14px 18px; cursor:pointer; user-select:none; }
.module-header:hover { background:var(--gray-50); } .module-header:hover { background:var(--gray-50); }
.module-toggle { font-size:12px; color:var(--gray-500); transition:transform .2s; } .module-toggle { font-size:12px; color:var(--gray-500); transition:transform .2s; flex-shrink:0; }
.module-block.open .module-toggle { transform:rotate(90deg); } .module-block.open .module-toggle { transform:rotate(90deg); }
.module-title { font-weight:700; color:var(--gray-900); flex:1; } .module-title { font-weight:700; color:var(--gray-900); flex:1; }
.module-mini { display:flex; gap:8px; font-size:13px; } .module-mini { display:flex; gap:8px; font-size:13px; }
.module-body { display:none; } .module-body { max-height:0; overflow:hidden; transition:max-height .35s ease; }
.module-block.open .module-body { display:block; } .module-block.open .module-body { max-height:5000px; }
/* 检测项行 */
.item-row { display:grid; grid-template-columns:2fr 1.5fr 1fr 1fr; gap:12px; padding:10px 18px; border-top:1px solid var(--gray-100); font-size:14px; } .item-row { display:grid; grid-template-columns:2fr 1.5fr 1fr 1fr; gap:12px; padding:10px 18px; border-top:1px solid var(--gray-100); font-size:14px; }
.item-name { color:var(--gray-700); } .item-name { color:var(--gray-700); }
.item-value { color:var(--gray-900); font-weight:600; word-break:break-all; } .item-value { color:var(--gray-900); font-weight:600; word-break:break-all; }
...@@ -73,6 +75,26 @@ ...@@ -73,6 +75,26 @@
.item-status { font-weight:600; } .item-status { font-weight:600; }
.stt-正常 { color:var(--green); } .stt-警告 { color:var(--yellow); } .stt-严重 { color:var(--red); } .stt-正常 { color:var(--green); } .stt-警告 { color:var(--yellow); } .stt-严重 { color:var(--red); }
/* 进程列表特殊样式 */
.process-row { border-top:1px solid var(--gray-100); padding:10px 18px; }
.process-summary { font-weight:600; color:var(--gray-900); margin-bottom:8px; display:flex; align-items:center; gap:8px; }
.process-toggle-btn { background:var(--gray-100); border:1px solid var(--gray-200); padding:3px 10px; border-radius:6px; font-size:12px; cursor:pointer; color:var(--gray-700); }
.process-toggle-btn:hover { background:var(--gray-200); }
.process-table-wrap { display:none; margin-top:8px; overflow-x:auto; }
.process-table-wrap.show { display:block; }
.process-table { width:100%; border-collapse:collapse; font-size:13px; }
.process-table th { background:var(--gray-50); padding:8px 10px; text-align:left; font-weight:600; color:var(--gray-700); border-bottom:2px solid var(--gray-200); }
.process-table td { padding:6px 10px; border-bottom:1px solid var(--gray-100); color:var(--gray-900); }
.process-table tr:hover td { background:var(--gray-50); }
.process-table .cmd-cell { font-family:monospace; font-size:12px; max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* 长值展开 */
.long-value { cursor:pointer; color:var(--primary); }
.long-value .truncated { display:inline; }
.long-value .full { display:none; }
.long-value.expanded .truncated { display:none; }
.long-value.expanded .full { display:inline; white-space:pre-wrap; word-break:break-all; }
.footer { text-align:center; padding:20px; color:var(--gray-500); font-size:12px; } .footer { text-align:center; padding:20px; color:var(--gray-500); font-size:12px; }
@media screen and (max-width:768px) { @media screen and (max-width:768px) {
...@@ -82,6 +104,8 @@ ...@@ -82,6 +104,8 @@
.abnormal-row { grid-template-columns:24px 1fr auto; gap:6px; } .abnormal-row { grid-template-columns:24px 1fr auto; gap:6px; }
.abnormal-row .ab-threshold, .abnormal-row .ab-module { display:none; } .abnormal-row .ab-threshold, .abnormal-row .ab-module { display:none; }
.item-row { grid-template-columns:1fr 1fr; gap:6px; } .item-row { grid-template-columns:1fr 1fr; gap:6px; }
.process-table { font-size:11px; }
.process-table .cmd-cell { max-width:150px; }
} }
</style> </style>
</head> </head>
...@@ -155,27 +179,64 @@ ...@@ -155,27 +179,64 @@
{% endif %} {% endif %}
<!-- 分模块折叠明细 --> <!-- 分模块折叠明细 -->
<div class="section-title">📋 检测明细</div> <div class="section-title">📋 检测明细(点击展开)</div>
{% for m in report.modules %} {% for m in report.modules %}
{% set ms = m.summary or {} %} {% set ms = m.summary or {} %}
<div class="module-block{{ ' open' if (ms['警告'] or ms['严重']) else '' }}"> {% set has_abnormal = (ms.get('警告', 0) or ms.get('严重', 0)) %}
<div class="module-header" onclick="this.parentElement.classList.toggle('open')"> <div class="module-block{{ ' open' if has_abnormal else '' }}" data-module="{{ m.id }}">
<div class="module-header" onclick="toggleModule(this)">
<span class="module-toggle"></span> <span class="module-toggle"></span>
<span class="module-title">{{ m.name }}({{ m['items'] | length }})</span> <span class="module-title">{{ m.name }}</span>
<span class="module-mini"> <span class="module-mini">
{% if ms['正常'] %}<span class="sm-ok stt-正常">✓{{ ms['正常'] }}</span>{% endif %} {% if ms.get('正常') %}<span class="stt-正常">✓{{ ms['正常'] }}</span>{% endif %}
{% if ms['警告'] %}<span class="stt-警告">⚠{{ ms['警告'] }}</span>{% endif %} {% if ms.get('警告') %}<span class="stt-警告">⚠{{ ms['警告'] }}</span>{% endif %}
{% if ms['严重'] %}<span class="stt-严重">✗{{ ms['严重'] }}</span>{% endif %} {% if ms.get('严重') %}<span class="stt-严重">✗{{ ms['严重'] }}</span>{% endif %}
</span> </span>
</div> </div>
<div class="module-body"> <div class="module-body">
{% for it in m['items'] %} {% for it in m['items'] %}
{# 判断是否为进程列表项 #}
{% if it.key.endswith('_PROCESS_LIST') or it.key.endswith('_PROCESSES_DETAIL') %}
<div class="process-row">
<div class="process-summary">
<span>{{ it.name }}</span>
<button class="process-toggle-btn" onclick="toggleProcessTable(this)">展开详情</button>
</div>
<div class="process-table-wrap">
<table class="process-table">
<thead><tr><th>用户</th><th>PID</th><th>CPU%</th><th>内存%</th><th>命令</th></tr></thead>
<tbody>
{% for line in it.value.split('|') %}
{% set parts = line.strip().split() %}
{% if parts|length >= 5 %}
<tr>
<td>{{ parts[0] }}</td>
<td>{{ parts[1] }}</td>
<td>{{ parts[2] }}</td>
<td>{{ parts[3] }}</td>
<td class="cmd-cell" title="{{ ' '.join(parts[4:]) }}">{{ ' '.join(parts[4:]) }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<div class="item-row"> <div class="item-row">
<span class="item-name">{{ it.name }}</span> <span class="item-name">{{ it.name }}</span>
{% if it.value|length > 80 %}
<span class="item-value long-value" onclick="this.classList.toggle('expanded')">
<span class="truncated">{{ it.value[:80] }}…</span>
<span class="full">{{ it.value }}</span>
</span>
{% else %}
<span class="item-value">{{ it.value }}</span> <span class="item-value">{{ it.value }}</span>
{% endif %}
<span class="item-threshold">{{ it.threshold or '-' }}</span> <span class="item-threshold">{{ it.threshold or '-' }}</span>
<span class="item-status stt-{{ it.status }}">{{ it.status }}</span> <span class="item-status stt-{{ it.status }}">{{ it.status }}</span>
</div> </div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
...@@ -189,6 +250,18 @@ ...@@ -189,6 +250,18 @@
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (!e.target.closest('.export-wrap')) document.getElementById('export-menu').classList.remove('show'); if (!e.target.closest('.export-wrap')) document.getElementById('export-menu').classList.remove('show');
}); });
function toggleModule(header) {
const block = header.parentElement;
block.classList.toggle('open');
}
function toggleProcessTable(btn) {
const wrap = btn.closest('.process-row').querySelector('.process-table-wrap');
wrap.classList.toggle('show');
btn.textContent = wrap.classList.contains('show') ? '收起详情' : '展开详情';
}
async function tryFix(key) { async function tryFix(key) {
const r = await fetch('/api/service-monitor/fix', {method:'POST', headers:{'Content-Type':'application/json'}, credentials:'include', body: JSON.stringify({key})}); const r = await fetch('/api/service-monitor/fix', {method:'POST', headers:{'Content-Type':'application/json'}, credentials:'include', body: JSON.stringify({key})});
const d = await r.json(); const d = await r.json();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论