Commit d27a237f authored by vicotor's avatar vicotor

update html style

parent edd362af
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node Monitor</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 30px;
}
h1 {
font-size: 28px;
margin-bottom: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.last-updated {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
gap: 8px;
}
.last-updated::before {
content: '🕐';
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-card strong {
display: block;
font-size: 24px;
margin: 8px 0 4px 0;
}
.stat-card span {
font-size: 12px;
opacity: 0.9;
}
.table-wrapper {
overflow-x: auto;
border-radius: 8px;
}
table {
border-collapse: collapse;
width: 100%;
}
thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
position: sticky;
top: 0;
z-index: 10;
}
th {
padding: 15px 12px;
text-align: left;
font-weight: 600;
font-size: 13px;
letter-spacing: 0.5px;
}
td {
padding: 12px;
border-bottom: 1px solid #e8e8e8;
font-size: 14px;
}
tbody tr {
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
tbody tr:hover {
background-color: #f8f9ff;
box-shadow: inset 0 0 0 1px rgba(102, 126, 234, 0.1);
}
/* 状态指示器 */
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 20px;
font-weight: 600;
font-size: 13px;
}
.status-online {
background-color: #d4edda;
color: #155724;
}
.status-online::before {
content: '●';
color: #28a745;
}
.status-offline {
background-color: #f8d7da;
color: #721c24;
}
.status-offline::before {
content: '●';
color: #dc3545;
}
/* 离线行背景 */
tr.offline {
background-color: #fff5f5;
}
tr.offline:hover {
background-color: #ffe8e8;
}
/* 进度条 */
.progress-bar {
display: flex;
align-items: center;
gap: 8px;
}
.progress {
flex: 1;
height: 6px;
background-color: #e0e0e0;
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-text {
font-weight: 600;
font-size: 13px;
min-width: 45px;
text-align: right;
}
/* 高风险指示 */
.high {
color: #dc3545;
font-weight: 700;
padding: 2px 6px;
background-color: #ffe0e0;
border-radius: 4px;
}
/* 磁盘信息 */
.disk-item {
margin-bottom: 8px;
font-size: 13px;
}
.disk-item:last-child {
margin-bottom: 0;
}
.disk-item strong {
color: #667eea;
margin-right: 4px;
}
/* IP地址样式 */
.ip-badge {
font-family: 'Monaco', 'Courier New', monospace;
background-color: #f5f5f5;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
/* N/A 样式 */
.na {
color: #999;
font-style: italic;
}
/* 响应式设计 */
@media (max-width: 768px) {
.container {
padding: 15px;
}
h1 {
font-size: 22px;
}
.header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
th, td {
padding: 10px 8px;
font-size: 12px;
}
.stat-card {
padding: 12px;
}
table {
font-size: 12px;
}
}
/* 滚动条样式 */
.table-wrapper::-webkit-scrollbar {
height: 8px;
}
.table-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5568d3 0%, #653b91 100%);
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 Node Monitor Dashboard</h1>
<div class="last-updated">最后更新: <strong>2025-12-06 12:20:57</strong></div>
</div>
<div class="stats">
<div class="stat-card">
<span>在线节点</span>
<strong>11</strong>
</div>
<div class="stat-card">
<span>离线节点</span>
<strong>5</strong>
</div>
<div class="stat-card">
<span>总节点数</span>
<strong>16</strong>
</div>
<div class="stat-card">
<span>在线率</span>
<strong>68.75%</strong>
</div>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>节点 ID</th>
<th>状态</th>
<th>区块高度</th>
<th>CPU 使用率</th>
<th>内存使用率</th>
<th>内存 (可用/总计)</th>
<th>磁盘信息</th>
<th>私有 IP</th>
<th>公网 IP</th>
<th>最后报告</th>
</tr>
</thead>
<tbody>
<tr class="online">
<td><strong>debugcmp</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td class="na">N/A</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 0.38%"></div></div>
<div class="progress-text">0.38%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 41.40%"></div></div>
<div class="progress-text">41.40%</div>
</div>
</td>
<td>8.83 GB / 15.24 GB</td>
<td><div class='disk-item'><strong>/:</strong> 79.22% (51.92 GB / 249.93 GB)</div><div class='disk-item'><strong>/node:</strong> 79.22% (51.92 GB / 249.93 GB)</div></td>
<td><span class="ip-badge">172.31.29.199</span></td>
<td><span class="ip-badge">15.206.56.79</span></td>
<td>2025-12-06 12:20:52</td>
</tr>
<tr class="online">
<td><strong>local</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td class="na">N/A</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 1.00%"></div></div>
<div class="progress-text">1.00%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 57.42%"></div></div>
<div class="progress-text">57.42%</div>
</div>
</td>
<td>0.14 GB / 0.41 GB</td>
<td><div class='disk-item'><strong>/:</strong> 75.05% (4.79 GB / 19.20 GB)</div></td>
<td><span class="ip-badge">172.26.5.41</span></td>
<td><span class="ip-badge">18.180.123.163</span></td>
<td>2025-12-06 12:20:57</td>
</tr>
<tr class="offline">
<td><strong>mars-app-1</strong></td>
<td><span class="status-badge status-offline">Offline</span></td>
<td class="na">N/A</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 2.04%"></div></div>
<div class="progress-text">2.04%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 15.77%"></div></div>
<div class="progress-text">15.77%</div>
</div>
</td>
<td>12.64 GB / 15.42 GB</td>
<td><div class='disk-item'><strong>/:</strong> 26.38% (34.88 GB / 47.39 GB)</div><div class='disk-item'><strong>/node:</strong> 4.83% (70.59 GB / 78.19 GB)</div></td>
<td><span class="ip-badge">172.31.33.105</span></td>
<td><span class="ip-badge">13.250.60.115</span></td>
<td>2025-11-27 05:22:31</td>
</tr>
<tr class="online">
<td><strong>mars1</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td>6820928</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 1.01%"></div></div>
<div class="progress-text">1.01%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 66.85%"></div></div>
<div class="progress-text">66.85%</div>
</div>
</td>
<td>8.13 GB / 15.25 GB</td>
<td><div class='disk-item'><strong>/:</strong> 60.36% (30.30 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> 75.31% (69.56 GB / 294.73 GB)</div></td>
<td><span class="ip-badge">172.31.33.58</span></td>
<td><span class="ip-badge">13.250.29.51</span></td>
<td>2025-12-06 12:20:57</td>
</tr>
<tr class="online">
<td><strong>mars10</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td>6820926</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 21.04%"></div></div>
<div class="progress-text">21.04%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 61.56%"></div></div>
<div class="progress-text">61.56%</div>
</div>
</td>
<td>7.33 GB / 15.14 GB</td>
<td><div class='disk-item'><strong>/:</strong> 13.86% (65.84 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> 89.93% (28.21 GB / 294.23 GB)</div></td>
<td><span class="ip-badge">172.31.36.91</span></td>
<td><span class="ip-badge">13.229.100.43</span></td>
<td>2025-12-06 12:20:55</td>
</tr>
<tr class="offline">
<td><strong>mars11</strong></td>
<td><span class="status-badge status-offline">Offline</span></td>
<td>6295014</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 25.94%"></div></div>
<div class="progress-text">25.94%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 76.24%"></div></div>
<div class="progress-text">76.24%</div>
</div>
</td>
<td>4.23 GB / 7.55 GB</td>
<td><div class='disk-item'><strong>/:</strong> 17.65% (62.95 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> <span class='high'>92.49%</span> (21.04 GB / 294.23 GB)</div></td>
<td><span class="ip-badge">172.31.35.205</span></td>
<td><span class="ip-badge">54.255.227.243</span></td>
<td>2025-11-27 05:33:14</td>
</tr>
<tr class="offline">
<td><strong>mars12</strong></td>
<td><span class="status-badge status-offline">Offline</span></td>
<td>6136745</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 35.52%"></div></div>
<div class="progress-text">35.52%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 70.32%"></div></div>
<div class="progress-text">70.32%</div>
</div>
</td>
<td>3.86 GB / 7.55 GB</td>
<td><div class='disk-item'><strong>/:</strong> 12.34% (67.01 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> 89.28% (30.03 GB / 294.23 GB)</div></td>
<td><span class="ip-badge">172.31.39.246</span></td>
<td><span class="ip-badge">13.212.5.38</span></td>
<td>2025-11-24 10:32:22</td>
</tr>
<tr class="offline">
<td><strong>mars13</strong></td>
<td><span class="status-badge status-offline">Offline</span></td>
<td>6136746</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 25.37%"></div></div>
<div class="progress-text">25.37%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 57.74%"></div></div>
<div class="progress-text">57.74%</div>
</div>
</td>
<td>4.28 GB / 7.55 GB</td>
<td><div class='disk-item'><strong>/:</strong> 13.39% (66.20 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> 86.62% (37.48 GB / 294.23 GB)</div></td>
<td><span class="ip-badge">172.31.43.169</span></td>
<td><span class="ip-badge">175.41.176.136</span></td>
<td>2025-11-24 10:32:23</td>
</tr>
<tr class="offline">
<td><strong>mars14</strong></td>
<td><span class="status-badge status-offline">Offline</span></td>
<td>6136746</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 25.56%"></div></div>
<div class="progress-text">25.56%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 56.25%"></div></div>
<div class="progress-text">56.25%</div>
</div>
</td>
<td>4.55 GB / 7.55 GB</td>
<td><div class='disk-item'><strong>/:</strong> 13.41% (66.18 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> 86.40% (38.11 GB / 294.23 GB)</div></td>
<td><span class="ip-badge">172.31.33.219</span></td>
<td><span class="ip-badge">52.221.230.59</span></td>
<td>2025-11-24 10:32:24</td>
</tr>
<tr class="online">
<td><strong>mars4</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td>6820925</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 25.56%"></div></div>
<div class="progress-text">25.56%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 64.63%"></div></div>
<div class="progress-text">64.63%</div>
</div>
</td>
<td>4.04 GB / 7.55 GB</td>
<td><div class='disk-item'><strong>/:</strong> 29.37% (53.98 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> <span class='high'>91.42%</span> (24.17 GB / 294.73 GB)</div></td>
<td><span class="ip-badge">172.31.38.129</span></td>
<td><span class="ip-badge">13.213.19.33</span></td>
<td>2025-12-06 12:20:54</td>
</tr>
<tr class="online">
<td><strong>mars5</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td class="na">N/A</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 3.84%"></div></div>
<div class="progress-text">3.84%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 72.09%"></div></div>
<div class="progress-text">72.09%</div>
</div>
</td>
<td>1.50 GB / 7.50 GB</td>
<td><div class='disk-item'><strong>/:</strong> 66.34% (25.73 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> 70.73% (54.82 GB / 196.30 GB)</div></td>
<td><span class="ip-badge">172.31.45.123</span></td>
<td><span class="ip-badge">13.212.186.205</span></td>
<td>2025-12-06 12:20:56</td>
</tr>
<tr class="online">
<td><strong>mars6</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td>6820927</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 14.30%"></div></div>
<div class="progress-text">14.30%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 76.53%"></div></div>
<div class="progress-text">76.53%</div>
</div>
</td>
<td>3.25 GB / 15.25 GB</td>
<td><div class='disk-item'><strong>/:</strong> 52.72% (36.14 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> 77.80% (83.49 GB / 393.15 GB)</div></td>
<td><span class="ip-badge">172.31.44.58</span></td>
<td><span class="ip-badge">13.250.53.191</span></td>
<td>2025-12-06 12:20:57</td>
</tr>
<tr class="online">
<td><strong>mars7</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td>6820924</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 18.87%"></div></div>
<div class="progress-text">18.87%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 50.15%"></div></div>
<div class="progress-text">50.15%</div>
</div>
</td>
<td>9.87 GB / 15.25 GB</td>
<td><div class='disk-item'><strong>/:</strong> 13.22% (66.33 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> <span class='high'>96.61%</span> (9.56 GB / 294.73 GB)</div></td>
<td><span class="ip-badge">172.31.45.18</span></td>
<td><span class="ip-badge">13.213.85.206</span></td>
<td>2025-12-06 12:20:55</td>
</tr>
<tr class="online">
<td><strong>mars8</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td>6820927</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 17.13%"></div></div>
<div class="progress-text">17.13%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 64.48%"></div></div>
<div class="progress-text">64.48%</div>
</div>
</td>
<td>8.41 GB / 15.25 GB</td>
<td><div class='disk-item'><strong>/:</strong> 89.48% (8.04 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> <span class='high'>96.39%</span> (10.10 GB / 294.23 GB)</div></td>
<td><span class="ip-badge">172.31.43.155</span></td>
<td><span class="ip-badge">54.255.194.135</span></td>
<td>2025-12-06 12:20:56</td>
</tr>
<tr class="online">
<td><strong>mars9</strong></td>
<td><span class="status-badge status-online">Online</span></td>
<td>6820924</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 12.77%"></div></div>
<div class="progress-text">12.77%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: 67.10%"></div></div>
<div class="progress-text">67.10%</div>
</div>
</td>
<td>8.10 GB / 15.25 GB</td>
<td><div class='disk-item'><strong>/:</strong> 20.50% (60.76 GB / 76.45 GB)</div><div class='disk-item'><strong>/node:</strong> <span class='high'>96.43%</span> (10.00 GB / 294.23 GB)</div></td>
<td><span class="ip-badge">172.31.36.191</span></td>
<td><span class="ip-badge">13.212.195.204</span></td>
<td>2025-12-06 12:20:53</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
\ No newline at end of file
...@@ -65,50 +65,352 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) { ...@@ -65,50 +65,352 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) {
tmpl := ` tmpl := `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node Monitor</title> <title>Node Monitor</title>
<style> <style>
table { border-collapse: collapse; width: 100%; } * {
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } margin: 0;
th { background-color: #f2f2f2; } padding: 0;
.offline { background-color: #ffebee; } box-sizing: border-box;
.online { background-color: #e8f5e8; } }
.disk-item { margin-bottom: 5px; }
.high { color: #c62828; font-weight: 700; } /* red bold for >90% */ body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 30px;
}
h1 {
font-size: 28px;
margin-bottom: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.last-updated {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
gap: 8px;
}
.last-updated::before {
content: '🕐';
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-card strong {
display: block;
font-size: 24px;
margin: 8px 0 4px 0;
}
.stat-card span {
font-size: 12px;
opacity: 0.9;
}
.table-wrapper {
overflow-x: auto;
border-radius: 8px;
}
table {
border-collapse: collapse;
width: 100%;
}
thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
position: sticky;
top: 0;
z-index: 10;
}
th {
padding: 15px 12px;
text-align: left;
font-weight: 600;
font-size: 13px;
letter-spacing: 0.5px;
}
td {
padding: 12px;
border-bottom: 1px solid #e8e8e8;
font-size: 14px;
}
tbody tr {
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
tbody tr:hover {
background-color: #f8f9ff;
box-shadow: inset 0 0 0 1px rgba(102, 126, 234, 0.1);
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 20px;
font-weight: 600;
font-size: 13px;
}
.status-online {
background-color: #d4edda;
color: #155724;
}
.status-online::before {
content: '●';
color: #28a745;
}
.status-offline {
background-color: #f8d7da;
color: #721c24;
}
.status-offline::before {
content: '●';
color: #dc3545;
}
tr.offline {
background-color: #fff5f5;
}
tr.offline:hover {
background-color: #ffe8e8;
}
.progress-bar {
display: flex;
align-items: center;
gap: 8px;
}
.progress {
flex: 1;
height: 6px;
background-color: #e0e0e0;
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-text {
font-weight: 600;
font-size: 13px;
min-width: 45px;
text-align: right;
}
.high {
color: #dc3545;
font-weight: 700;
padding: 2px 6px;
background-color: #ffe0e0;
border-radius: 4px;
}
.disk-item {
margin-bottom: 8px;
font-size: 13px;
}
.disk-item:last-child {
margin-bottom: 0;
}
.disk-item strong {
color: #667eea;
margin-right: 4px;
}
.ip-badge {
font-family: 'Monaco', 'Courier New', monospace;
background-color: #f5f5f5;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.na {
color: #999;
font-style: italic;
}
@media (max-width: 768px) {
.container {
padding: 15px;
}
h1 {
font-size: 22px;
}
.header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
th, td {
padding: 10px 8px;
font-size: 12px;
}
.stat-card {
padding: 12px;
}
table {
font-size: 12px;
}
}
.table-wrapper::-webkit-scrollbar {
height: 8px;
}
.table-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 4px;
}
.table-wrapper::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5568d3 0%, #653b91 100%);
}
</style> </style>
</head> </head>
<body> <body>
<h1>Node Monitor Dashboard</h1> <div class="container">
<p>Last updated: {{.UpdateTime}}</p> <div class="header">
<table> <h1>🚀 Node Monitor Dashboard</h1>
<tr> <div class="last-updated">最后更新: <strong>{{.UpdateTime}}</strong></div>
<th>Node ID</th> </div>
<th>Status</th>
<th>Height</th> <div class="stats">
<th>CPU Usage (%)</th> <div class="stat-card">
<th>Memory Usage (%)</th> <span>在线节点</span>
<th>Memory (Free/Total)</th> <strong>{{.OnlineCount}}</strong>
<th>Disk Information</th> </div>
<th>Private IP</th> <div class="stat-card">
<th>Public IP</th> <span>离线节点</span>
<th>Last Report</th> <strong>{{.OfflineCount}}</strong>
</tr> </div>
{{range .Nodes}} <div class="stat-card">
<tr class="{{.StatusClass}}"> <span>总节点数</span>
<td>{{.NodeID}}</td> <strong>{{.TotalCount}}</strong>
<td>{{.Status}}</td> </div>
<td>{{.Height}}</td> <div class="stat-card">
<td>{{printf "%.2f" .CPUUsage}}</td> <span>在线率</span>
<td><span class="{{.MemoryUsageClass}}">{{.MemoryUsageFormatted}}</span></td> <strong>{{.OnlineRate}}</strong>
<td>{{.MemoryInfo}}</td> </div>
<td>{{.AllDisksInfo}}</td> </div>
<td>{{.PrivateIP}}</td>
<td>{{.PublicIP}}</td> <div class="table-wrapper">
<td>{{.LastReport}}</td> <table>
</tr> <thead>
{{end}} <tr>
</table> <th>节点 ID</th>
<th>状态</th>
<th>区块高度</th>
<th>CPU 使用率</th>
<th>内存使用率</th>
<th>内存 (可用/总计)</th>
<th>磁盘信息</th>
<th>私有 IP</th>
<th>公网 IP</th>
<th>最后报告</th>
</tr>
</thead>
<tbody>
{{range .Nodes}}
<tr class="{{.StatusClass}}">
<td><strong>{{.NodeID}}</strong></td>
<td><span class="status-badge status-{{.StatusClass}}">{{.Status}}</span></td>
<td>{{.HeightDisplay}}</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: {{printf "%.2f" .CPUUsage}}%"></div></div>
<div class="progress-text">{{printf "%.2f" .CPUUsage}}%</div>
</div>
</td>
<td>
<div class="progress-bar">
<div class="progress"><div class="progress-fill" style="width: {{printf "%.2f" .MemoryUsage}}%"></div></div>
<div class="progress-text {{.MemoryUsageClass}}">{{.MemoryUsageFormatted}}</div>
</div>
</td>
<td>{{.MemoryInfo}}</td>
<td>{{.AllDisksInfo}}</td>
<td><span class="ip-badge">{{.PrivateIP}}</span></td>
<td><span class="ip-badge">{{.PublicIP}}</span></td>
<td>{{.LastReport}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</body> </body>
</html> </html>
` `
...@@ -122,16 +424,28 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) { ...@@ -122,16 +424,28 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) {
LastReport string LastReport string
MemoryUsageClass string MemoryUsageClass string
MemoryUsageFormatted string MemoryUsageFormatted string
HeightDisplay template.HTML
} }
nodeViews := make([]NodeView, len(nodes)) nodeViews := make([]NodeView, len(nodes))
onlineCount := 0
offlineCount := 0
for i, node := range nodes { for i, node := range nodes {
isOnline := time.Since(node.LastHeartbeat) < 30*time.Second isOnline := time.Since(node.LastHeartbeat) < 30*time.Second
status := "Online" status := "在线"
statusClass := "online" statusClass := "online"
if !isOnline { if !isOnline {
status = "Offline" status = "离线"
statusClass = "offline" statusClass = "offline"
offlineCount++
} else {
onlineCount++
}
// Format height display
heightDisplay := node.Height
if node.Height == "" || node.Height == "0" {
heightDisplay = `<span class="na">N/A</span>`
} }
// Format all disk information and highlight usage > 90% // Format all disk information and highlight usage > 90%
...@@ -171,15 +485,30 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) { ...@@ -171,15 +485,30 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) {
LastReport: node.Timestamp.Format("2006-01-02 15:04:05"), LastReport: node.Timestamp.Format("2006-01-02 15:04:05"),
MemoryUsageClass: memUsageClass, MemoryUsageClass: memUsageClass,
MemoryUsageFormatted: memUsageFormatted, MemoryUsageFormatted: memUsageFormatted,
HeightDisplay: template.HTML(heightDisplay),
} }
} }
totalCount := len(nodes)
onlineRate := "0%"
if totalCount > 0 {
onlineRate = fmt.Sprintf("%.1f%%", float64(onlineCount)/float64(totalCount)*100)
}
data := struct { data := struct {
Nodes []NodeView Nodes []NodeView
UpdateTime string UpdateTime string
OnlineCount int
OfflineCount int
TotalCount int
OnlineRate string
}{ }{
Nodes: nodeViews, Nodes: nodeViews,
UpdateTime: time.Now().Format("2006-01-02 15:04:05"), UpdateTime: time.Now().Format("2006-01-02 15:04:05"),
OnlineCount: onlineCount,
OfflineCount: offlineCount,
TotalCount: totalCount,
OnlineRate: onlineRate,
} }
t, err := template.New("nodes").Parse(tmpl) t, err := template.New("nodes").Parse(tmpl)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment