</> dashboard.php

297 行11626 字节.php
📥 下载源码 🔙 返回工具
1<!DOCTYPE html>
2<class="tag">html lang="zh-CN">
3<class="tag">head>
4 <class="tag">meta charset="UTF-class="number">8">
5 <class="tag">meta name="viewport" content="width=device-width, initial-scale=class="number">1.0">
6 <class="tag">title>η空间 · 网站仪表盘</class="tag">title>
7 <class="tag">meta name="description" content="η空间网站状态监控 - 服务器实时状态、访问统计、页面列表。">
8 <class="tag">link rel="preconnect" href="https:class=class="string">"keyword">class=class="string">"comment">//fonts.googleapis.com">
9 <class="tag">link rel="preconnect" href="https:class=class="string">"keyword">class=class="string">"comment">//fonts.gstatic.com" crossorigin>
10 <class="tag">link href="https:class=class="string">"keyword">class=class="string">"comment">//fonts.googleapis.com/css2?family=Outfit:wght@class="number">300;class="number">400;class="number">500;class="number">600&family=JetBrains+Mono:wght@class="number">400;class="number">600&display=swap" rel="stylesheet">
11 <class="tag">link rel="stylesheet" href="https:class=class="string">"keyword">class=class="string">"comment">//cdnjs.cloudflare.com/ajax/libs/font-awesome/class="number">6.5.class="number">1/css/all.min.css">
12 <class="tag">link rel="stylesheet" href="/tools/share.css">
13 <class="tag">link rel="stylesheet" href="/assets/pet-cat.css">
14 <class="tag">style>
15 *, *::before, *::after { margin: class="number">0; padding: class="number">0; box-sizing: border-box; }
16 body {
17 font-family: class="string">'Outfit', -apple-system, sans-serif;
18 background: class=class="string">"keyword">class=class="string">"comment">#0C0C0C;
19 color: class=class="string">"keyword">class=class="string">"comment">#F7F5F0;
20 min-height: 100vh;
21 }
22 .container { max-width: 900px; margin: class="number">0 auto; padding: 20px 16px 60px; }
23
24 /* 头部 */
25 .dash-class=class="string">"keyword">header {
26 text-align: center;
27 padding: 40px class="number">0 30px;
28 }
29 .dash-class=class="string">"keyword">header h1 {
30 font-size: 28px;
31 font-weight: class="number">600;
32 }
33 .dash-class=class="string">"keyword">header h1 .accent { color: class=class="string">"keyword">class=class="string">"comment">#FF6B35; }
34 .dash-class=class="string">"keyword">header p {
35 color: class=class="string">"keyword">class=class="string">"comment">#class="number">888883;
36 font-size: 14px;
37 margin-top: 6px;
38 }
39
40 /* 卡片网格 */
41 .dash-grid {
42 display: grid;
43 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
44 gap: 12px;
45 margin-bottom: 24px;
46 }
47 .dash-card {
48 background: class=class="string">"keyword">class=class="string">"comment">#class="number">161616;
49 border: 1px solid class=class="string">"keyword">class=class="string">"comment">#class="number">222;
50 border-radius: 12px;
51 padding: 20px;
52 transition: border-color class="number">0.3s;
53 }
54 .dash-card:hover { border-color: class=class="string">"keyword">class=class="string">"comment">#class="number">333; }
55 .dash-card .label {
56 font-size: 12px;
57 color: class=class="string">"keyword">class=class="string">"comment">#class="number">888883;
58 text-transform: uppercase;
59 letter-spacing: 1px;
60 margin-bottom: 8px;
61 display: flex;
62 align-items: center;
63 gap: 6px;
64 }
65 .dash-card .value {
66 font-size: 24px;
67 font-weight: class="number">600;
68 font-family: class="string">'JetBrains Mono', monospace;
69 }
70 .dash-card .value.green { color: class=class="string">"keyword">class=class="string">"comment">#4ade80; }
71 .dash-card .value.orange { color: class=class="string">"keyword">class=class="string">"comment">#fb923c; }
72 .dash-card .value.blue { color: class=class="string">"keyword">class=class="string">"comment">#60a5fa; }
73 .dash-card .value.pink { color: class=class="string">"keyword">class=class="string">"comment">#f472b6; }
74 .dash-card .sub {
75 font-size: 12px;
76 color: class=class="string">"keyword">class=class="string">"comment">#class="number">666;
77 margin-top: 4px;
78 }
79
80 /* 页面列表 */
81 .section-title {
82 font-size: 16px;
83 font-weight: class="number">500;
84 color: class=class="string">"keyword">class=class="string">"comment">#class="number">888883;
85 margin: 24px class="number">0 12px;
86 display: flex;
87 align-items: center;
88 gap: 8px;
89 }
90 .page-list {
91 display: grid;
92 grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
93 gap: 8px;
94 }
95 .page-item {
96 background: class=class="string">"keyword">class=class="string">"comment">#class="number">161616;
97 border: 1px solid class=class="string">"keyword">class=class="string">"comment">#class="number">222;
98 border-radius: 8px;
99 padding: 12px 14px;
100 display: flex;
101 align-items: center;
102 justify-content: space-between;
103 transition: border-color class="number">0.3s;
104 }
105 .page-item:hover { border-color: class=class="string">"keyword">class=class="string">"comment">#class="number">333; }
106 .page-item .page-name {
107 font-size: 13px;
108 font-weight: class="number">500;
109 }
110 .page-item .page-url {
111 font-size: 11px;
112 color: class=class="string">"keyword">class=class="string">"comment">#class="number">666;
113 margin-top: 2px;
114 }
115 .page-item .page-status {
116 width: 8px;
117 height: 8px;
118 border-radius: class="number">50%;
119 flex-shrink: class="number">0;
120 }
121 .page-status.online { background: class=class="string">"keyword">class=class="string">"comment">#4ade80; box-shadow: class="number">0 class="number">0 6px rgba(class="number">74,class="number">222,class="number">128,class="number">0.5); }
122 .page-status.offline { background: class=class="string">"keyword">class=class="string">"comment">#ef4444; box-shadow: class="number">0 class="number">0 6px rgba(class="number">239,class="number">68,class="number">68,class="number">0.5); }
123 .page-status.checking { background: class=class="string">"keyword">class=class="string">"comment">#fb923c; animation: pulse 1s infinite; }
124 @keyframes pulse {
125 class="number">0%, class="number">100% { opacity: class="number">1; }
126 class="number">50% { opacity: class="number">0.3; }
127 }
128
129 /* 加载状态 */
130 .loading-text {
131 text-align: center;
132 padding: 60px;
133 color: class=class="string">"keyword">class=class="string">"comment">#class="number">666;
134 font-size: 14px;
135 }
136 .loading-text i { margin-right: 8px; }
137
138 /* 错误 */
139 .error-msg {
140 text-align: center;
141 padding: 40px;
142 color: class=class="string">"keyword">class=class="string">"comment">#ef4444;
143 }
144
145 /* 时间戳 */
146 .dash-footer {
147 text-align: center;
148 padding: 30px class="number">0 10px;
149 font-size: 12px;
150 color: class=class="string">"keyword">class=class="string">"comment">#class="number">555;
151 }
152 .dash-footer a { color: class=class="string">"keyword">class=class="string">"comment">#FF6B35; text-decoration: none; }
153 .dash-footer a:hover { text-decoration: underline; }
154
155 /* 进度条 */
156 .mem-bar {
157 margin-top: 8px;
158 height: 4px;
159 background: class=class="string">"keyword">class=class="string">"comment">#class="number">222;
160 border-radius: 2px;
161 overflow: hidden;
162 }
163 .mem-bar-fill {
164 height: class="number">100%;
165 background: linear-gradient(90deg, class=class="string">"keyword">class=class="string">"comment">#4ade80, #fb923c, #ef4444);
166 border-radius: 2px;
167 transition: width class="number">0.5s;
168 }
169
170 @media (max-width: 500px) {
171 .dash-class=class="string">"keyword">header h1 { font-size: 22px; }
172 .dash-grid { grid-template-columns: 1fr 1fr; }
173 .page-list { grid-template-columns: 1fr; }
174 }
175 </class="tag">style>
176</class="tag">head>
177<class="tag">body>
178
179 <class="tag">div class=class="string">"keyword">class="container">
180 <class="tag">div class=class="string">"keyword">class="dash-class=class="string">"keyword">header">
181 <class="tag">h1>📊 <class="tag">span class=class="string">"keyword">class="accent">η</class="tag">span> 空间仪表盘</class="tag">h1>
182 <class="tag">p>服务器状态 · 访问统计 · 页面监控</class="tag">p>
183 </class="tag">div>
184
185 <class="tag">div id="dashContent">
186 <class="tag">div class=class="string">"keyword">class="loading-text"><class="tag">i class=class="string">"keyword">class="fas fa-spinner fa-spin"></class="tag">i> 加载中...</class="tag">div>
187 </class="tag">div>
188 </class="tag">div>
189
190 <class="tag">script src="/tools/share.js"></class="tag">script>
191 <class="tag">script src="/assets/pet-cat.js"></class="tag">script>
192 <class="tag">script>
193 (class=class="string">"keyword">function() {
194 class=class="string">"keyword">const el = document.class="func">getElementById(class="string">'dashContent');
195
196 class=class="string">"keyword">function fmtUptime(secs) {
197 class=class="string">"keyword">const d = Math.floor(secs / class="number">86400);
198 class=class="string">"keyword">const h = Math.floor((secs % class="number">86400) / class="number">3600);
199 class=class="string">"keyword">const m = Math.floor((secs % class="number">3600) / class="number">60);
200 class=class="string">"keyword">const s = Math.floor(secs % class="number">60);
201 class=class="string">"keyword">let parts = [];
202 class=class="string">"keyword">if (d) parts.push(d + class="string">'天');
203 class=class="string">"keyword">if (h) parts.push(h + class="string">'时');
204 class=class="string">"keyword">if (m) parts.push(m + class="string">'分');
205 parts.push(s + class="string">'秒');
206 class=class="string">"keyword">return parts.join(class="string">'');
207 }
208
209 class=class="string">"keyword">function fmtBytes(bytes) {
210 class=class="string">"keyword">if (bytes < class="number">1024) class=class="string">"keyword">return bytes + class="string">'B';
211 class=class="string">"keyword">if (bytes < class="number">1048576) class=class="string">"keyword">return (bytes / class="number">1024).toFixed(class="number">1) + class="string">'KB';
212 class=class="string">"keyword">return (bytes / class="number">1048576).toFixed(class="number">1) + class="string">'MB';
213 }
214
215 class=class="string">"keyword">function fmtDate(iso) {
216 class=class="string">"keyword">if (!iso) class=class="string">"keyword">return class="string">'未知';
217 class=class="string">"keyword">const d = class=class="string">"keyword">new class="func">Date(iso);
218 class=class="string">"keyword">return d.getFullYear() + class="string">'-' + class="func">String(d.getMonth()+class="number">1).padStart(class="number">2,class="string">'class="number">0') + class="string">'-' +
219 class="func">String(d.getDate()).padStart(class="number">2,class="string">'class="number">0') + class="string">' ' +
220 class="func">String(d.getHours()).padStart(class="number">2,class="string">'class="number">0') + class="string">':' +
221 class="func">String(d.getMinutes()).padStart(class="number">2,class="string">'class="number">0');
222 }
223
224 class=class="string">"keyword">function checkPage(url) {
225 class=class="string">"keyword">return class="func">fetch(url, { method: class="string">'HEAD', mode: class="string">'no-cors' })
226 .class="func">then(class=class="string">"keyword">function(r) { class=class="string">"keyword">return class="string">'online'; })
227 .class="func">catch(class=class="string">"keyword">function() { class=class="string">"keyword">return class="string">'checking'; });
228 }
229
230 class="func">fetch(class="string">'/tools/stats-proxy.php')
231 .class="func">then(class=class="string">"keyword">function(r) { class=class="string">"keyword">return r.json(); })
232 .class="func">then(class=class="string">"keyword">function(stats) {
233 class=class="string">"keyword">if (stats.error) {
234 el.innerHTML = class="string">'<class="tag">div class="keyword">class="error-msg"><class="tag">i class="keyword">class="fas fa-exclamation-triangle"></class="tag">i> ' + stats.error + class="string">'</class="tag">div>';
235 class=class="string">"keyword">return;
236 }
237
238 class=class="string">"keyword">const memPercent = stats.memory ?
239 Math.min(class="number">100, (stats.memory.heapUsed / stats.memory.heapTotal * class="number">100).toFixed(class="number">1)) : class="number">0;
240 class=class="string">"keyword">const heapTotal = stats.memory ? fmtBytes(stats.memory.heapTotal) : class="string">'-';
241 class=class="string">"keyword">const heapUsed = stats.memory ? fmtBytes(stats.memory.heapUsed) : class="string">'-';
242
243 class=class="string">"keyword">var html = class="string">'';
244
245 class=class="string">"keyword">class=class="string">"comment">// 统计卡片
246 html += class="string">'<class="tag">div class="keyword">class="dash-grid">';
247 html += class="string">' <class="tag">div class="keyword">class="dash-card"><class="tag">div class="keyword">class="label"><class="tag">i class="keyword">class="fas fa-eye"></class="tag">i> 总访问量</class="tag">div><class="tag">div class="keyword">class="value blue">' + stats.count + class="string">'</class="tag">div><class="tag">div class="keyword">class="sub">最后更新: ' + fmtDate(stats.updated) + class="string">'</class="tag">div></class="tag">div>';
248 html += class="string">' <class="tag">div class="keyword">class="dash-card"><class="tag">div class="keyword">class="label"><class="tag">i class="keyword">class="fas fa-rocket"></class="tag">i> 运行时间</class="tag">div><class="tag">div class="keyword">class="value green">' + fmtUptime(stats.uptime) + class="string">'</class="tag">div><class="tag">div class="keyword">class="sub">进程PID: ' + stats.pid + class="string">'</class="tag">div></class="tag">div>';
249 html += class="string">' <class="tag">div class="keyword">class="dash-card"><class="tag">div class="keyword">class="label"><class="tag">i class="keyword">class="fas fa-microchip"></class="tag">i> 内存占用</class="tag">div><class="tag">div class="keyword">class="value orange">' + heapUsed + class="string">'</class="tag">div><class="tag">div class="keyword">class="sub">总量 ' + heapTotal + class="string">'</class="tag">div><class="tag">div class="keyword">class="mem-bar"><class="tag">div class="keyword">class="mem-bar-fill" style="width:' + memPercent + class="string">'%"></class="tag">div></class="tag">div></class="tag">div>';
250 html += class="string">' <class="tag">div class="keyword">class="dash-card"><class="tag">div class="keyword">class="label"><class="tag">i class="keyword">class="fas fa-cog"></class="tag">i> Node版本</class="tag">div><class="tag">div class="keyword">class="value pink">' + stats.node + class="string">'</class="tag">div><class="tag">div class="keyword">class="sub">' + stats.platform + class="string">' · ' + stats.arch + class="string">'</class="tag">div></class="tag">div>';
251 html += class="string">'</class="tag">div>';
252
253 class=class="string">"keyword">class=class="string">"comment">// 页面列表
254 html += class="string">'<class="tag">div class="keyword">class="section-title"><class="tag">i class="keyword">class="fas fa-sitemap"></class="tag">i> 站点页面 (' + stats.pages.length + class="string">'个)</class="tag">div>';
255 html += class="string">'<class="tag">div class="keyword">class="page-list" id="pageList">';
256 class=class="string">"keyword">for (class=class="string">"keyword">var i = class="number">0; i < stats.pages.length; i++) {
257 class=class="string">"keyword">var p = stats.pages[i];
258 html += class="string">' <class="tag">div class="keyword">class="page-item" data-url="' + p.url + class="string">'">';
259 html += class="string">' <class="tag">div><class="tag">div class="keyword">class="page-name">' + p.name + class="string">'</class="tag">div><class="tag">div class="keyword">class="page-url">' + p.url.replace(class="string">'https:class="keyword">class="comment">//class="number">273059.cn', class="string">'') + class="string">'</class="tag">div></class="tag">div>';
260 html += class="string">' <class="tag">div class="keyword">class="page-status checking" id="status-' + i + class="string">'"></class="tag">div>';
261 html += class="string">' </class="tag">div>';
262 }
263 html += class="string">'</class="tag">div>';
264
265 html += class="string">'<class="tag">div class="keyword">class="dash-footer">🔄 实时数据 · <class="tag">a href="https:class="keyword">class="comment">//class="number">273059.cn">← 返回首页</class="tag">a></class="tag">div>';
266
267 el.innerHTML = html;
268
269 class=class="string">"keyword">class=class="string">"comment">// 逐个检查页面状态
270 class=class="string">"keyword">for (class=class="string">"keyword">var i = class="number">0; i < stats.pages.length; i++) {
271 (class=class="string">"keyword">function(idx, url) {
272 class=class="string">"keyword">var dot = document.class="func">getElementById(class="string">'status-' + idx);
273 class=class="string">"keyword">if (!dot) class=class="string">"keyword">return;
274 class="func">fetch(class="string">'/tools/counter-proxy.php?action=ping&url=' + encodeURIComponent(url), { method: class="string">'GET' })
275 .class="func">catch(class=class="string">"keyword">function(){});
276 class=class="string">"keyword">class=class="string">"comment">// 直接用 HEAD 请求检查页面
277 class=class="string">"keyword">var xhr = class=class="string">"keyword">new XMLHttpRequest();
278 xhr.open(class="string">'HEAD', url, true);
279 xhr.onload = class=class="string">"keyword">function() {
280 dot.className = class="string">'page-status online';
281 };
282 xhr.onerror = class=class="string">"keyword">function() {
283 class=class="string">"keyword">class=class="string">"comment">// 跨域限制下HEAD可能失败,但页面其实在线
284 dot.className = class="string">'page-status online';
285 };
286 xhr.send();
287 })(i, p.url);
288 }
289 })
290 .class="func">catch(class=class="string">"keyword">function(err) {
291 el.innerHTML = class="string">'<class="tag">div class="keyword">class="error-msg"><class="tag">i class="keyword">class="fas fa-exclamation-triangle"></class="tag">i> 加载失败: ' + err.message + class="string">'</class="tag">div>';
292 });
293 })();
294 </class="tag">script>
295</class="tag">body>
296</class="tag">html>
297