version 1.1.8

This commit is contained in:
cppla
2026-07-01 11:55:45 +08:00
parent f24a70b9d7
commit c58eaf6ff5
16 changed files with 1985 additions and 853 deletions
+115 -1
View File
@@ -230,7 +230,7 @@ body.light .gauge-half .needle{background:linear-gradient(var(--text),var(--text
font-variant-numeric:tabular-nums;
}
.cards .card{border:1px solid var(--border);border-radius:12px;padding:.75rem .85rem;background:linear-gradient(145deg,var(--bg),var(--bg-alt));display:flex;flex-direction:column;gap:.45rem;position:relative;}
.cards .card.offline{opacity:.6;}
.cards .card.offline{filter:saturate(.92);}
.cards .card.high-load{border-color:rgba(239,68,68,.6);background:linear-gradient(180deg, rgba(239,68,68,.22), rgba(239,68,68,.12)), var(--bg-alt);box-shadow:0 0 0 1px rgba(239,68,68,.48),0 6px 18px -6px rgba(239,68,68,.28);}
table.data tbody tr.high-load{background:rgba(239,68,68,.18) !important;}
table.data tbody tr.high-load:hover{background:rgba(239,68,68,.26) !important;}
@@ -316,3 +316,117 @@ table.data tbody tr[class*="os-"]:hover{background:linear-gradient(180deg, color
.brand:hover .logo-mark{transform:translateY(-2px) scale(1.05)}
.brand:hover .logo-text{color:var(--text)}
@media (max-width:640px){.brand .logo-text{font-size:15px}.brand .logo-mark{width:30px;height:30px}}
/* 运维控制台增强 */
.ops-overview{display:grid;grid-template-columns:repeat(5,minmax(140px,1fr));gap:.75rem}
.overview-card{border:1px solid var(--border);background:var(--bg-alt);border-radius:8px;padding:.8rem .9rem;display:flex;flex-direction:column;gap:.25rem;min-height:82px}
.overview-card .label{font-size:12px;color:var(--text-dim)}
.overview-card .value{font-size:24px;line-height:1;font-weight:700;font-variant-numeric:tabular-nums}
.overview-card .hint{font-size:12px;color:var(--text-dim);line-height:1.3}
.overview-card.ok{border-color:rgba(16,185,129,.35)}
.overview-card.warn{border-color:rgba(245,158,11,.45)}
.overview-card.err{border-color:rgba(239,68,68,.5)}
.ops-toolbar{display:flex;align-items:end;gap:.75rem;flex-wrap:wrap;border:1px solid var(--border);background:var(--bg-alt);border-radius:8px;padding:.75rem}
.search-field,.select-field{display:flex;flex-direction:column;gap:.3rem;color:var(--text-dim);font-size:12px}
.search-field input,.select-field select,.config-form input,.config-form textarea{border:1px solid var(--border);background:var(--bg);color:var(--text);border-radius:6px;padding:0 .65rem;outline:none;transition:var(--trans)}
.search-field input,.select-field select,.config-form input{height:36px}
.config-form textarea{min-height:92px;resize:vertical;padding:.55rem .65rem;line-height:1.45;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.search-field input:focus,.select-field select:focus,.config-form input:focus,.config-form textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 18%,transparent)}
.search-field input{min-width:280px}
.segmented{display:inline-flex;border:1px solid var(--border);border-radius:7px;overflow:hidden;background:var(--bg)}
.segmented button{height:36px;border:0;border-right:1px solid var(--border);background:transparent;color:var(--text-dim);padding:0 .8rem;cursor:pointer}
.segmented button:last-child{border-right:0}
.segmented button.active,.segmented button:hover{background:var(--accent);color:#fff}
.icon-text,.primary-btn,.danger-btn{height:36px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);padding:0 .75rem;cursor:pointer;transition:var(--trans);font-weight:600}
.icon-text:hover{border-color:var(--accent);color:var(--accent)}
.primary-btn{background:var(--accent);border-color:var(--accent);color:#fff}
.primary-btn:hover{filter:brightness(1.08)}
.danger-btn{background:rgba(239,68,68,.14);border-color:rgba(239,68,68,.38);color:var(--danger)}
.danger-btn:hover{background:var(--danger);color:#fff}
th[data-sort]{cursor:pointer;user-select:none}
th[data-sort]:after{content:"";display:inline-block;margin-left:6px;border:4px solid transparent;border-top-color:color-mix(in srgb,var(--text-dim) 70%,transparent);transform:translateY(2px);opacity:.45}
th[data-sort].sorted-asc:after{border-top-color:transparent;border-bottom-color:var(--accent);transform:translateY(-2px)}
th[data-sort].sorted-desc:after{border-top-color:var(--accent);opacity:1}
.section-head h2{margin:0;font-size:18px}
.section-head p{margin:.25rem 0 0}
.empty-state{border:1px dashed var(--border);border-radius:8px;background:var(--bg-alt);padding:1.25rem;color:var(--text-dim);text-align:center}
/* 右侧详情抽屉 */
.drawer-backdrop{align-items:stretch;justify-content:flex-end;padding:0;background:rgba(0,0,0,.48)}
.detail-drawer{height:100vh;max-height:100vh;width:min(620px,100vw);max-width:min(620px,100vw);border-radius:0;border-top:0;border-right:0;border-bottom:0;padding:1.1rem;overflow:auto;animation:slideIn .24s ease}
.modal-title{padding-right:2.2rem}
.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.55rem}
.detail-section{border:1px solid var(--border);border-radius:8px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));padding:.75rem;display:flex;flex-direction:column;gap:.65rem}
.detail-section h4{margin:0;font-size:13px;color:var(--text-dim);font-weight:700;letter-spacing:.4px}
.detail-section .kv{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:flex-start;gap:.6rem;min-width:0}
.detail-section .kv span{min-width:0;white-space:normal}
.detail-section .kv>span:last-child{text-align:right;overflow-wrap:anywhere;word-break:break-word}
.detail-section .mono{display:block;white-space:normal;overflow-wrap:anywhere;word-break:break-word;line-height:1.45}
.detail-inline{display:inline-flex;align-items:center;justify-content:flex-end;gap:.45rem;white-space:nowrap!important}
.detail-inline .mono{display:inline!important;white-space:nowrap!important;line-height:1}
.detail-values{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.15rem .55rem}
.detail-values span{white-space:nowrap!important}
.resource-section{gap:.5rem}
.resource-meter{display:flex;flex-direction:column;gap:.26rem;min-width:0}
.resource-meter-head,.resource-mini{display:flex;align-items:center;justify-content:space-between;gap:.55rem;font-size:12px;min-width:0}
.resource-meter-head span,.resource-mini span{color:var(--text-dim);white-space:nowrap}
.resource-meter-head strong,.resource-mini strong{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;font-weight:700;text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.resource-track{height:7px;border-radius:999px;background:color-mix(in srgb,var(--text-dim) 16%,transparent);overflow:hidden;border:1px solid color-mix(in srgb,var(--border) 75%,transparent)}
.resource-track i{display:block;height:100%;width:var(--p);border-radius:inherit;background:var(--accent);transition:width .5s ease}
.resource-meter[data-kind=mem] .resource-track i{background:var(--ok)}
.resource-meter[data-kind=swap] .resource-track i{background:#14b8a6}
.resource-meter[data-kind=hdd] .resource-track i{background:#06b6d4}
.resource-meter[data-level=warn] .resource-track i{background:var(--warn)}
.resource-meter[data-level=bad] .resource-track i{background:var(--danger)}
.resource-mini{border-top:1px solid var(--border);padding-top:.45rem}
.chart-section{gap:.55rem}
.chart-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;flex-wrap:wrap}
.chart-head h4{margin:0}
.chart-legend{display:inline-flex;align-items:center;justify-content:flex-end;gap:.7rem;flex-wrap:wrap;font-size:11px;color:var(--text-dim)}
.chart-legend span{display:inline-flex;align-items:center;gap:.28rem;white-space:nowrap}
.chart-legend i{display:inline-block;width:10px;height:3px;border-radius:999px;box-shadow:0 0 0 1px rgba(0,0,0,.16)}
.detail-chart{width:100%;border:1px solid var(--border);border-radius:8px;background:var(--bg)}
@keyframes slideIn{from{transform:translateX(22px);opacity:.7}to{transform:translateX(0);opacity:1}}
/* 管理配置 */
.config-grid{display:grid;grid-template-columns:minmax(320px,.95fr) minmax(320px,1.05fr);gap:.9rem;align-items:start}
.admin-card{border:1px solid var(--border);border-radius:8px;background:var(--bg-alt);padding:1rem;display:flex;flex-direction:column;gap:.85rem}
.section-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}
.section-actions{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;justify-content:flex-end}
.admin-status{border:1px solid var(--border);border-radius:8px;background:var(--bg);padding:.75rem;font-size:13px;color:var(--text-dim)}
.admin-status.ok{border-color:rgba(16,185,129,.35);color:var(--ok)}
.admin-status.err{border-color:rgba(239,68,68,.45);color:var(--danger)}
.admin-login,.config-actions,.form-actions{display:flex;align-items:end;gap:.65rem;flex-wrap:wrap}
.config-tabs{align-self:flex-start;max-width:100%}
.config-list{display:flex;flex-direction:column;gap:.5rem}
.config-row{display:grid;grid-template-columns:1fr auto;gap:.75rem;align-items:center;border:1px solid var(--border);border-radius:8px;background:var(--bg);padding:.7rem;cursor:pointer}
.config-row:hover,.config-row.active{border-color:var(--accent)}
.config-row .name{font-weight:700}
.config-row .meta{font-size:12px;color:var(--text-dim);margin-top:.2rem;word-break:break-all}
.config-form{display:flex;flex-direction:column;gap:.75rem}
.config-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem}
.config-form label{display:flex;flex-direction:column;gap:.3rem;color:var(--text-dim);font-size:12px}
.config-form label.wide{grid-column:1/-1}
.config-form .check-row{flex-direction:row;align-items:center;gap:.5rem}
.config-form .check-row input{height:auto}
.config-form .form-actions{grid-column:1/-1}
.config-form input[name=password],.config-form textarea[name=rule]{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.config-form button:disabled{opacity:.5;cursor:not-allowed}
@media (max-width:980px){
.ops-overview{grid-template-columns:repeat(2,minmax(0,1fr))}
.config-grid{grid-template-columns:1fr}
.search-field input{min-width:220px}
}
@media (max-width:700px){
.ops-overview{display:none}
}
@media (max-width:640px){
.ops-toolbar{align-items:stretch}
.search-field,.select-field,.search-field input,.select-field select,.segmented,.icon-text,.primary-btn,.danger-btn{width:100%}
.segmented button{flex:1;padding:0 .35rem}
.section-head{flex-direction:column}
.section-actions{width:100%;justify-content:stretch}
.detail-grid,.config-fields{grid-template-columns:1fr}
.detail-drawer{width:100vw}
}
+6 -10
View File
@@ -1,11 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<defs>
<linearGradient id="g" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#3b82f6" />
<stop offset="1" stop-color="#2563eb" />
</linearGradient>
</defs>
<rect x="8" y="12" width="48" height="40" rx="12" fill="url(#g)" />
<path stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" d="M22 38v-8l6 4 8-10 6 6v8" />
<circle cx="22" cy="22" r="4" fill="#fff" />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 86" fill="none">
<path d="M4 46H28L38 34L55 82L65 4L74 47H88M100 47H110M122 47H126"
stroke="#111827"
stroke-width="8"
stroke-linecap="round"
stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 266 B

+107 -22
View File
@@ -6,17 +6,14 @@
<meta name="description" content="云监控,ServerStatus中文版,ServerStatus,ServerStatus cppla" />
<title>云监控</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="alternate icon" href="favicon.ico" />
<link rel="stylesheet" href="css/app.css" />
<link rel="stylesheet" href="css/app.css?v=20260630-7" />
</head>
<body>
<header class="topbar">
<div class="brand" title="云监控">
<span class="logo-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M7.5 17a4.5 4.5 0 0 1-.9-8.92A6 6 0 0 1 18.4 9.6 4 4 0 0 1 18 17H7.5Z" />
<rect x="9" y="11" width="6" height="4" rx="1" />
<path d="M11 15v2.5a.5.5 0 0 0 .5.5h1" />
<svg viewBox="0 0 128 86" width="24" height="18" fill="none" stroke="currentColor" stroke-width="8" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 46H28L38 34L55 82L65 4L74 47H88M100 47H110M122 47H126" />
</svg>
</span>
<span class="logo-text"><span class="logo-accent"></span>监控</span>
@@ -25,6 +22,7 @@
<button data-tab="servers" class="active">主机</button>
<button data-tab="monitors">服务</button>
<button data-tab="ssl">证书</button>
<button data-tab="config">配置</button>
</nav>
<div class="actions">
<button id="themeToggle" title="切换主题 (当前: 自动或手动)" aria-label="切换主题">🌓</button>
@@ -35,24 +33,59 @@
<main class="wrapper">
<div id="notice" class="notice info">初始化中...</div>
<section class="ops-overview" id="overviewCards" aria-label="运行概览"></section>
<section class="ops-toolbar" id="serversToolbar" aria-label="主机筛选">
<label class="search-field">
<span>搜索</span>
<input id="serverSearch" type="search" autocomplete="off" placeholder="节点 / 地区 / 系统 / 主机名" />
</label>
<div class="segmented" id="statusFilter" role="group" aria-label="状态筛选">
<button data-filter="all" class="active">全部</button>
<button data-filter="online">在线</button>
<button data-filter="offline">离线</button>
<button data-filter="alert" title="只显示在线但资源、丢包或流量异常的节点">异常</button>
</div>
<label class="select-field">
<span>系统</span>
<select id="osFilter">
<option value="all">全部系统</option>
</select>
</label>
<label class="select-field">
<span>排序</span>
<select id="sortSelect">
<option value="name">节点</option>
<option value="status">在线状态</option>
<option value="load">负载</option>
<option value="cpu">CPU</option>
<option value="memory">内存</option>
<option value="hdd">硬盘</option>
<option value="traffic">月流量</option>
<option value="loss">丢包</option>
</select>
</label>
<button id="sortDirection" class="icon-text" title="切换排序方向">降序</button>
</section>
<section id="panel-servers" class="panel active" aria-labelledby="主机">
<div class="table-wrap">
<table class="data" id="serversTable">
<thead>
<tr>
<th>协议</th>
<th>月流量 ↓|↑</th>
<th>节点</th>
<th>虚拟化</th>
<th>位置</th>
<th data-sort="status">协议</th>
<th data-sort="traffic">月流量 ↓|↑</th>
<th data-sort="name">节点</th>
<th data-sort="type">虚拟化</th>
<th data-sort="location">位置</th>
<th>在线</th>
<th>负载</th>
<th data-sort="load">负载</th>
<th>当前网络 ↓|↑</th>
<th>总流量 ↓|↑</th>
<th>CPU</th>
<th>内存</th>
<th>硬盘</th>
<th style="text-align:center;">联通|电信|移动</th>
<th data-sort="cpu">CPU</th>
<th data-sort="memory">内存</th>
<th data-sort="hdd">硬盘</th>
<th data-sort="loss" style="text-align:center;">联通|电信|移动</th>
</tr>
</thead>
<tbody id="serversBody"></tbody>
@@ -99,21 +132,73 @@
<!-- 移动端卡片布局 (证书) -->
<div id="sslCards" class="cards" style="display:none;"></div>
</section>
<section id="panel-config" class="panel" aria-labelledby="配置">
<div class="config-grid">
<section class="admin-card">
<div class="section-head">
<div>
<h2>配置管理</h2>
<p class="muted">可编辑节点、服务监测、证书与告警规则,保存后自动重载。</p>
</div>
<div class="section-actions">
<button id="adminReload" class="icon-text">重载配置</button>
<button id="adminRestart" class="danger-btn">重启服务</button>
</div>
</div>
<div id="adminStatus" class="admin-status">正在检测管理 API...</div>
<form id="adminTokenForm" class="admin-login">
<label class="search-field">
<span>管理令牌</span>
<input id="adminToken" type="password" autocomplete="current-password" placeholder="ADMIN_TOKEN" />
</label>
<button type="submit" class="primary-btn">连接</button>
</form>
<div class="segmented config-tabs" id="configTypeTabs" role="group" aria-label="配置类型">
<button type="button" data-type="servers" class="active">节点</button>
<button type="button" data-type="monitors">监测</button>
<button type="button" data-type="sslcerts">证书</button>
<button type="button" data-type="watchdog">告警</button>
</div>
<div class="config-actions">
<button id="addConfigItemBtn" class="primary-btn">新增节点</button>
<button id="refreshConfigBtn" class="icon-text">刷新配置</button>
</div>
<div id="configItemList" class="config-list"></div>
</section>
<section class="admin-card">
<div class="section-head">
<div>
<h2 id="configEditorTitle">新增节点</h2>
<p class="muted" id="configEditorHint">保存后会写入 config.json,并向 sergate 发送 SIGHUP 重载。</p>
</div>
</div>
<form id="configForm" class="config-form">
<div id="configFields" class="config-fields"></div>
<div class="form-actions">
<button type="submit" class="primary-btn">保存配置</button>
<button type="button" id="deleteConfigItemBtn" class="danger-btn">删除配置</button>
<button type="button" id="resetConfigFormBtn" class="icon-text">清空</button>
</div>
</form>
</section>
</div>
</section>
</main>
<!-- 详情弹窗 -->
<div id="detailModal" class="modal-backdrop" style="display:none;">
<div class="modal-box" role="dialog" aria-modal="true" aria-labelledby="detailTitle">
<!-- 详情抽屉 -->
<div id="detailModal" class="modal-backdrop drawer-backdrop" style="display:none;">
<aside class="modal-box detail-drawer" role="dialog" aria-modal="true" aria-labelledby="detailTitle">
<button class="modal-close" id="detailClose" aria-label="关闭">×</button>
<h3 id="detailTitle" class="modal-title">节点详情</h3>
<div id="detailContent" class="modal-content">加载中...</div>
</div>
</aside>
</div>
<footer class="footer">
<a href="https://github.com/cppla/ServerStatus" target="_blank" rel="noopener">ServerStatus中文版</a>
</footer>
<script src="js/app.js" defer></script>
<script src="js/app.js?v=20260630-7" defer></script>
</body>
</html>
</html>
+761 -613
View File
File diff suppressed because it is too large Load Diff