Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nodemonitor
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
nodemonitor
Commits
d27a237f
Commit
d27a237f
authored
Dec 06, 2025
by
vicotor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update html style
parent
edd362af
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
1051 additions
and
44 deletions
+1051
-44
demo.html
demo.html
+678
-0
main.go
server/main.go
+373
-44
No files found.
demo.html
0 → 100644
View file @
d27a237f
<!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
,
1
fr
));
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
server/main.go
View file @
d27a237f
...
...
@@ -65,50 +65,352 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) {
tmpl
:=
`
<!DOCTYPE html>
<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>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.offline { background-color: #ffebee; }
.online { background-color: #e8f5e8; }
.disk-item { margin-bottom: 5px; }
.high { color: #c62828; font-weight: 700; } /* red bold for >90% */
* {
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-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>
</head>
<body>
<h1>Node Monitor Dashboard</h1>
<p>Last updated: {{.UpdateTime}}</p>
<table>
<tr>
<th>Node ID</th>
<th>Status</th>
<th>Height</th>
<th>CPU Usage (%)</th>
<th>Memory Usage (%)</th>
<th>Memory (Free/Total)</th>
<th>Disk Information</th>
<th>Private IP</th>
<th>Public IP</th>
<th>Last Report</th>
</tr>
{{range .Nodes}}
<tr class="{{.StatusClass}}">
<td>{{.NodeID}}</td>
<td>{{.Status}}</td>
<td>{{.Height}}</td>
<td>{{printf "%.2f" .CPUUsage}}</td>
<td><span class="{{.MemoryUsageClass}}">{{.MemoryUsageFormatted}}</span></td>
<td>{{.MemoryInfo}}</td>
<td>{{.AllDisksInfo}}</td>
<td>{{.PrivateIP}}</td>
<td>{{.PublicIP}}</td>
<td>{{.LastReport}}</td>
</tr>
{{end}}
</table>
<div class="container">
<div class="header">
<h1>🚀 Node Monitor Dashboard</h1>
<div class="last-updated">最后更新: <strong>{{.UpdateTime}}</strong></div>
</div>
<div class="stats">
<div class="stat-card">
<span>在线节点</span>
<strong>{{.OnlineCount}}</strong>
</div>
<div class="stat-card">
<span>离线节点</span>
<strong>{{.OfflineCount}}</strong>
</div>
<div class="stat-card">
<span>总节点数</span>
<strong>{{.TotalCount}}</strong>
</div>
<div class="stat-card">
<span>在线率</span>
<strong>{{.OnlineRate}}</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>
{{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>
</html>
`
...
...
@@ -122,16 +424,28 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) {
LastReport
string
MemoryUsageClass
string
MemoryUsageFormatted
string
HeightDisplay
template
.
HTML
}
nodeViews
:=
make
([]
NodeView
,
len
(
nodes
))
onlineCount
:=
0
offlineCount
:=
0
for
i
,
node
:=
range
nodes
{
isOnline
:=
time
.
Since
(
node
.
LastHeartbeat
)
<
30
*
time
.
Second
status
:=
"
Online
"
status
:=
"
在线
"
statusClass
:=
"online"
if
!
isOnline
{
status
=
"
Offline
"
status
=
"
离线
"
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%
...
...
@@ -171,15 +485,30 @@ func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) {
LastReport
:
node
.
Timestamp
.
Format
(
"2006-01-02 15:04:05"
),
MemoryUsageClass
:
memUsageClass
,
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
{
Nodes
[]
NodeView
UpdateTime
string
Nodes
[]
NodeView
UpdateTime
string
OnlineCount
int
OfflineCount
int
TotalCount
int
OnlineRate
string
}{
Nodes
:
nodeViews
,
UpdateTime
:
time
.
Now
()
.
Format
(
"2006-01-02 15:04:05"
),
Nodes
:
nodeViews
,
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
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment