提交 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() {
done
}
# 检测容器进程详情
# 检测容器进程详情(遍历所有已配置容器)
check_container_processes() {
for key in "mysql" "redis" "emqx" "java"; do
local container_name="${CONTAINERS[$key]}"
for key in "${!CONTAINERS[@]}"; do
local container_name
container_name=$(resolve_container "$key" 2>/dev/null)
if [ -z "$container_name" ]; then
continue
fi
if ! docker ps --format "{{.Names}}" | grep -q "^${container_name}$"; then
continue
fi
# 获取容器内进程数
local process_count
process_count=$(docker exec "$container_name" ps aux 2>/dev/null | wc -l)
process_count=$((process_count - 1))
if [ -n "$process_count" ] && [ "$process_count" -gt 0 ]; then
output_result "PROCESSES_${key}" "$process_count"
output_result "PROCESSES_${container_name}" "$process_count"
fi
done
......
......@@ -71,6 +71,14 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "11_scheduled_tasks.sh"),
CheckModule("12_port_check", "端口检测", "system",
{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 类 ----
CheckModule("20_docker_basic", "Docker基础", "service",
{SUITE_QUICK, SUITE_FULL}, "20_docker_basic.sh"),
......@@ -84,6 +92,24 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "24_redis_basic.sh"),
CheckModule("25_redis_depth", "Redis深度检测", "service",
{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",
{SUITE_FULL}, "37_ustorage_check.sh"),
CheckModule("38_utracker_check", "UTracker容器监测", "service",
......
......@@ -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-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; }
......@@ -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; }
.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 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); }
/* 汇总卡片 */
......@@ -60,12 +60,14 @@
.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: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-title { font-weight:700; color:var(--gray-900); flex:1; }
.module-mini { display:flex; gap:8px; font-size:13px; }
.module-body { display:none; }
.module-block.open .module-body { display:block; }
.module-body { max-height:0; overflow:hidden; transition:max-height .35s ease; }
.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-name { color:var(--gray-700); }
.item-value { color:var(--gray-900); font-weight:600; word-break:break-all; }
......@@ -73,6 +75,26 @@
.item-status { font-weight:600; }
.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; }
@media screen and (max-width:768px) {
......@@ -82,6 +104,8 @@
.abnormal-row { grid-template-columns:24px 1fr auto; gap:6px; }
.abnormal-row .ab-threshold, .abnormal-row .ab-module { display:none; }
.item-row { grid-template-columns:1fr 1fr; gap:6px; }
.process-table { font-size:11px; }
.process-table .cmd-cell { max-width:150px; }
}
</style>
</head>
......@@ -155,27 +179,64 @@
{% endif %}
<!-- 分模块折叠明细 -->
<div class="section-title">📋 检测明细</div>
<div class="section-title">📋 检测明细(点击展开)</div>
{% for m in report.modules %}
{% set ms = m.summary or {} %}
<div class="module-block{{ ' open' if (ms['警告'] or ms['严重']) else '' }}">
<div class="module-header" onclick="this.parentElement.classList.toggle('open')">
{% set has_abnormal = (ms.get('警告', 0) or ms.get('严重', 0)) %}
<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-title">{{ m.name }}({{ m['items'] | length }})</span>
<span class="module-title">{{ m.name }}</span>
<span class="module-mini">
{% if ms['正常'] %}<span class="sm-ok stt-正常">✓{{ ms['正常'] }}</span>{% endif %}
{% if ms['警告'] %}<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.get('警告') %}<span class="stt-警告">⚠{{ ms['警告'] }}</span>{% endif %}
{% if ms.get('严重') %}<span class="stt-严重">✗{{ ms['严重'] }}</span>{% endif %}
</span>
</div>
<div class="module-body">
{% 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">
<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>
{% endif %}
<span class="item-threshold">{{ it.threshold or '-' }}</span>
<span class="item-status stt-{{ it.status }}">{{ it.status }}</span>
</div>
{% endif %}
{% endfor %}
</div>
</div>
......@@ -189,6 +250,18 @@
document.addEventListener('click', (e) => {
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) {
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();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论