hono-status-monitor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,454 @@
1
+ // =============================================================================
2
+ // HONO STATUS MONITOR - DASHBOARD
3
+ // Real-time monitoring dashboard HTML generator
4
+ // =============================================================================
5
+ /**
6
+ * Generate the status dashboard HTML
7
+ */
8
+ export function generateDashboard({ hostname, uptime, socketPath, title }) {
9
+ return `<!DOCTYPE html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <title>${title}</title>
15
+ <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
16
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
17
+ <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
18
+ <style>
19
+ :root {
20
+ --bg: #fff;
21
+ --bg-secondary: #f8f9fa;
22
+ --bg-card: #fff;
23
+ --border: #e5e5e5;
24
+ --text: #111;
25
+ --text-secondary: #666;
26
+ --text-muted: #999;
27
+ --accent: #3b82f6;
28
+ --success: #10b981;
29
+ --warning: #f59e0b;
30
+ --danger: #ef4444;
31
+ }
32
+
33
+ .dark {
34
+ --bg: #0f0f0f;
35
+ --bg-secondary: #1a1a1a;
36
+ --bg-card: #1a1a1a;
37
+ --border: #2a2a2a;
38
+ --text: #fafafa;
39
+ --text-secondary: #a0a0a0;
40
+ --text-muted: #666;
41
+ }
42
+
43
+ * { margin: 0; padding: 0; box-sizing: border-box; }
44
+
45
+ body {
46
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
47
+ background: var(--bg-secondary);
48
+ color: var(--text);
49
+ min-height: 100vh;
50
+ transition: all 0.3s;
51
+ }
52
+
53
+ .container {
54
+ max-width: 800px;
55
+ margin: 0 auto;
56
+ padding: 20px;
57
+ background: var(--bg);
58
+ min-height: 100vh;
59
+ border-left: 1px solid var(--border);
60
+ border-right: 1px solid var(--border);
61
+ }
62
+
63
+ header {
64
+ display: flex;
65
+ justify-content: space-between;
66
+ align-items: center;
67
+ margin-bottom: 16px;
68
+ padding-bottom: 16px;
69
+ border-bottom: 1px solid var(--border);
70
+ }
71
+
72
+ .title-section h1 { font-size: 18px; font-weight: 600; }
73
+ .title-section .subtitle { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
74
+
75
+ .header-controls { display: flex; align-items: center; gap: 12px; }
76
+
77
+ .theme-toggle {
78
+ width: 36px; height: 36px;
79
+ border: 1px solid var(--border);
80
+ background: var(--bg-card);
81
+ border-radius: 8px;
82
+ cursor: pointer;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ font-size: 16px;
87
+ }
88
+
89
+ .status-badge {
90
+ display: flex; align-items: center; gap: 6px;
91
+ padding: 6px 12px; border-radius: 16px;
92
+ font-size: 11px; font-weight: 500;
93
+ }
94
+ .status-badge.connected { background: #dcfce7; color: #166534; }
95
+ .status-badge.disconnected { background: #fee2e2; color: #991b1b; }
96
+ .dark .status-badge.connected { background: #14532d; color: #86efac; }
97
+ .dark .status-badge.disconnected { background: #7f1d1d; color: #fca5a5; }
98
+
99
+ /* Stats Bar */
100
+ .stats-bar {
101
+ display: grid;
102
+ grid-template-columns: repeat(4, 1fr);
103
+ gap: 8px;
104
+ margin-bottom: 16px;
105
+ }
106
+ .stat-box {
107
+ padding: 12px;
108
+ background: var(--bg-secondary);
109
+ border-radius: 8px;
110
+ text-align: center;
111
+ }
112
+ .stat-box .label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
113
+ .stat-box .value { font-size: 18px; font-weight: 600; margin-top: 4px; }
114
+
115
+ /* Percentiles */
116
+ .percentiles {
117
+ display: grid;
118
+ grid-template-columns: repeat(4, 1fr);
119
+ gap: 8px;
120
+ margin-bottom: 16px;
121
+ padding: 12px;
122
+ background: var(--bg-secondary);
123
+ border-radius: 8px;
124
+ }
125
+ .percentile-item { text-align: center; }
126
+ .percentile-item .label { font-size: 10px; color: var(--text-muted); }
127
+ .percentile-item .value { font-size: 16px; font-weight: 600; color: var(--accent); }
128
+
129
+ /* Metric Rows */
130
+ .metric-row {
131
+ display: flex; align-items: center;
132
+ padding: 12px 0; border-bottom: 1px solid var(--border);
133
+ }
134
+ .metric-info { width: 140px; flex-shrink: 0; }
135
+ .metric-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.3px; }
136
+ .metric-value { font-size: 28px; font-weight: 300; line-height: 1.1; }
137
+ .metric-unit { font-size: 14px; color: var(--text-muted); }
138
+ .metric-alert { color: var(--danger) !important; }
139
+ .chart-container { flex: 1; height: 50px; margin-left: 16px; }
140
+
141
+ /* Section Titles */
142
+ .section-title {
143
+ font-size: 11px; font-weight: 600; color: var(--text-muted);
144
+ text-transform: uppercase; letter-spacing: 0.5px;
145
+ margin: 20px 0 12px; padding-top: 12px;
146
+ border-top: 1px solid var(--border);
147
+ }
148
+
149
+ /* Route Tables */
150
+ .routes-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
151
+ .route-section { background: var(--bg-secondary); border-radius: 8px; padding: 12px; }
152
+ .route-section h3 { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; margin-bottom: 8px; }
153
+ .route-item { display: flex; justify-content: space-between; font-size: 12px; padding: 6px 0; border-bottom: 1px solid var(--border); }
154
+ .route-item:last-child { border-bottom: none; }
155
+ .route-path { font-family: monospace; color: var(--text-secondary); max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
156
+ .route-stat { font-weight: 500; }
157
+ .route-stat.slow { color: var(--warning); }
158
+ .route-stat.error { color: var(--danger); }
159
+
160
+ /* Status Codes */
161
+ .status-codes { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
162
+ .status-code-box { text-align: center; padding: 10px; background: var(--bg-secondary); border-radius: 6px; }
163
+ .status-code-box .code { font-size: 10px; color: var(--text-muted); }
164
+ .status-code-box .count { font-size: 18px; font-weight: 600; margin-top: 2px; }
165
+ .s2xx { color: var(--success); }
166
+ .s3xx { color: var(--accent); }
167
+ .s4xx { color: var(--warning); }
168
+ .s5xx { color: var(--danger); }
169
+
170
+ /* Errors Panel */
171
+ .errors-panel { background: var(--bg-secondary); border-radius: 8px; padding: 12px; }
172
+ .error-item { font-size: 12px; padding: 8px; background: var(--bg-card); border-radius: 4px; margin-top: 6px; border-left: 3px solid var(--danger); }
173
+ .error-item:first-of-type { margin-top: 0; }
174
+ .error-time { font-size: 10px; color: var(--text-muted); }
175
+ .error-path { font-family: monospace; color: var(--danger); }
176
+
177
+ /* Database & Health */
178
+ .health-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
179
+ .health-item { padding: 12px; background: var(--bg-secondary); border-radius: 8px; text-align: center; }
180
+ .health-item .label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; }
181
+ .health-item .value { font-size: 16px; font-weight: 600; margin-top: 4px; }
182
+ .health-item .status { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; margin-top: 4px; }
183
+ .health-item .status.ok { background: #dcfce7; color: #166534; }
184
+ .health-item .status.error { background: #fee2e2; color: #991b1b; }
185
+ .dark .health-item .status.ok { background: #14532d; color: #86efac; }
186
+ .dark .health-item .status.error { background: #7f1d1d; color: #fca5a5; }
187
+
188
+ /* Process Info */
189
+ .process-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
190
+ .process-item { padding: 10px; background: var(--bg-secondary); border-radius: 6px; }
191
+ .process-item .label { font-size: 9px; color: var(--text-muted); text-transform: uppercase; }
192
+ .process-item .value { font-size: 13px; font-weight: 500; margin-top: 2px; }
193
+
194
+ @media (max-width: 640px) {
195
+ .container { padding: 12px; }
196
+ .stats-bar, .percentiles { grid-template-columns: repeat(2, 1fr); }
197
+ .routes-grid { grid-template-columns: 1fr; }
198
+ .status-codes { grid-template-columns: repeat(3, 1fr); }
199
+ .health-grid, .process-grid { grid-template-columns: repeat(2, 1fr); }
200
+ }
201
+ </style>
202
+ </head>
203
+ <body>
204
+ <div class="container">
205
+ <header>
206
+ <div class="title-section">
207
+ <h1>${title}</h1>
208
+ <div class="subtitle">${hostname}</div>
209
+ </div>
210
+ <div class="header-controls">
211
+ <button class="theme-toggle" onclick="toggleTheme()" title="Toggle dark mode">🌓</button>
212
+ <div class="status-badge disconnected" id="connBadge">
213
+ <span id="connText">Connecting</span>
214
+ </div>
215
+ </div>
216
+ </header>
217
+
218
+ <div class="stats-bar">
219
+ <div class="stat-box"><div class="label">Uptime</div><div class="value" id="uptime">${uptime}</div></div>
220
+ <div class="stat-box"><div class="label">Requests</div><div class="value" id="totalReq">0</div></div>
221
+ <div class="stat-box"><div class="label">Active</div><div class="value" id="activeConn">0</div></div>
222
+ <div class="stat-box"><div class="label">Error Rate</div><div class="value" id="errorRate">0%</div></div>
223
+ </div>
224
+
225
+ <div class="percentiles">
226
+ <div class="percentile-item"><div class="label">Avg</div><div class="value" id="pAvg">0ms</div></div>
227
+ <div class="percentile-item"><div class="label">P50</div><div class="value" id="p50">0ms</div></div>
228
+ <div class="percentile-item"><div class="label">P95</div><div class="value" id="p95">0ms</div></div>
229
+ <div class="percentile-item"><div class="label">P99</div><div class="value" id="p99">0ms</div></div>
230
+ </div>
231
+
232
+ <div class="metric-row">
233
+ <div class="metric-info"><div class="metric-label">CPU</div><div class="metric-value"><span id="cpuVal">0</span><span class="metric-unit">%</span></div></div>
234
+ <div class="chart-container"><canvas id="cpuChart"></canvas></div>
235
+ </div>
236
+ <div class="metric-row">
237
+ <div class="metric-info"><div class="metric-label">Memory</div><div class="metric-value"><span id="memVal">0</span><span class="metric-unit">MB</span></div></div>
238
+ <div class="chart-container"><canvas id="memChart"></canvas></div>
239
+ </div>
240
+ <div class="metric-row">
241
+ <div class="metric-info"><div class="metric-label">Heap</div><div class="metric-value"><span id="heapVal">0</span><span class="metric-unit">MB</span></div></div>
242
+ <div class="chart-container"><canvas id="heapChart"></canvas></div>
243
+ </div>
244
+ <div class="metric-row">
245
+ <div class="metric-info"><div class="metric-label">Load</div><div class="metric-value" id="loadVal">0.00</div></div>
246
+ <div class="chart-container"><canvas id="loadChart"></canvas></div>
247
+ </div>
248
+ <div class="metric-row">
249
+ <div class="metric-info"><div class="metric-label">Response</div><div class="metric-value"><span id="rtVal">0</span><span class="metric-unit">ms</span></div></div>
250
+ <div class="chart-container"><canvas id="rtChart"></canvas></div>
251
+ </div>
252
+ <div class="metric-row">
253
+ <div class="metric-info"><div class="metric-label">RPS</div><div class="metric-value" id="rpsVal">0</div></div>
254
+ <div class="chart-container"><canvas id="rpsChart"></canvas></div>
255
+ </div>
256
+ <div class="metric-row">
257
+ <div class="metric-info"><div class="metric-label">Event Loop</div><div class="metric-value"><span id="lagVal">0</span><span class="metric-unit">ms</span></div></div>
258
+ <div class="chart-container"><canvas id="lagChart"></canvas></div>
259
+ </div>
260
+
261
+ <div class="section-title">Route Analytics</div>
262
+ <div class="routes-grid">
263
+ <div class="route-section">
264
+ <h3>🔥 Top Routes</h3>
265
+ <div id="topRoutes"><div class="route-item"><span class="route-path">No data yet</span></div></div>
266
+ </div>
267
+ <div class="route-section">
268
+ <h3>🐢 Slowest Routes</h3>
269
+ <div id="slowRoutes"><div class="route-item"><span class="route-path">No data yet</span></div></div>
270
+ </div>
271
+ </div>
272
+
273
+ <div class="section-title">HTTP Status Codes</div>
274
+ <div class="status-codes">
275
+ <div class="status-code-box"><div class="code">2xx</div><div class="count s2xx" id="s2xx">0</div></div>
276
+ <div class="status-code-box"><div class="code">3xx</div><div class="count s3xx" id="s3xx">0</div></div>
277
+ <div class="status-code-box"><div class="code">4xx</div><div class="count s4xx" id="s4xx">0</div></div>
278
+ <div class="status-code-box"><div class="code">5xx</div><div class="count s5xx" id="s5xx">0</div></div>
279
+ <div class="status-code-box"><div class="code">Rate Limited</div><div class="count" id="rateLimited">0</div></div>
280
+ </div>
281
+
282
+ <div class="section-title">Recent Errors</div>
283
+ <div class="errors-panel" id="errorsPanel">
284
+ <div style="color: var(--text-muted); font-size: 12px;">No errors recorded</div>
285
+ </div>
286
+
287
+ <div class="section-title">Health Checks</div>
288
+ <div class="health-grid">
289
+ <div class="health-item">
290
+ <div class="label">Database</div>
291
+ <div class="value" id="dbLatency">-</div>
292
+ <div class="status" id="dbStatus">-</div>
293
+ </div>
294
+ <div class="health-item">
295
+ <div class="label">Heap Total</div>
296
+ <div class="value"><span id="heapTotal">0</span>MB</div>
297
+ </div>
298
+ <div class="health-item">
299
+ <div class="label">Heap Growth</div>
300
+ <div class="value"><span id="heapGrowth">0</span>MB/s</div>
301
+ </div>
302
+ </div>
303
+
304
+ <div class="section-title">Process Info</div>
305
+ <div class="process-grid">
306
+ <div class="process-item"><div class="label">Node</div><div class="value" id="nodeVer">-</div></div>
307
+ <div class="process-item"><div class="label">Platform</div><div class="value" id="platform">-</div></div>
308
+ <div class="process-item"><div class="label">PID</div><div class="value" id="pid">-</div></div>
309
+ <div class="process-item"><div class="label">CPUs</div><div class="value" id="cpuCount">-</div></div>
310
+ </div>
311
+ </div>
312
+
313
+ <script>
314
+ (function() {
315
+ var isDark = localStorage.getItem('statusDark') === 'true';
316
+ if (isDark) document.body.classList.add('dark');
317
+
318
+ window.toggleTheme = function() {
319
+ document.body.classList.toggle('dark');
320
+ localStorage.setItem('statusDark', document.body.classList.contains('dark'));
321
+ };
322
+
323
+ var chartColor = isDark ? '#666' : '#333';
324
+ var gridColor = isDark ? '#2a2a2a' : '#f0f0f0';
325
+
326
+ var chartConfig = {
327
+ responsive: true, maintainAspectRatio: false, animation: false,
328
+ plugins: { legend: { display: false } },
329
+ scales: {
330
+ x: { type: 'time', time: { unit: 'second' }, grid: { display: false }, ticks: { display: false } },
331
+ y: { beginAtZero: true, grid: { color: gridColor, drawBorder: false }, ticks: { font: { size: 9 }, color: '#999', maxTicksLimit: 3 } }
332
+ },
333
+ elements: { point: { radius: 0 }, line: { tension: 0.2, borderWidth: 1.5 } }
334
+ };
335
+
336
+ function createChart(id, color) {
337
+ var ctx = document.getElementById(id).getContext('2d');
338
+ var config = JSON.parse(JSON.stringify(chartConfig));
339
+ return new Chart(ctx, { type: 'line', data: { datasets: [{ data: [], borderColor: color, fill: false }] }, options: config });
340
+ }
341
+
342
+ var charts = {
343
+ cpu: createChart('cpuChart', '#3b82f6'),
344
+ mem: createChart('memChart', '#8b5cf6'),
345
+ heap: createChart('heapChart', '#a855f7'),
346
+ load: createChart('loadChart', '#f59e0b'),
347
+ rt: createChart('rtChart', '#10b981'),
348
+ rps: createChart('rpsChart', '#ec4899'),
349
+ lag: createChart('lagChart', '#ef4444')
350
+ };
351
+
352
+ function updateChart(chart, points) {
353
+ chart.data.datasets[0].data = points.map(function(p) { return { x: new Date(p.timestamp), y: p.value }; });
354
+ chart.update('none');
355
+ }
356
+
357
+ function formatUptime(s) {
358
+ var d=Math.floor(s/86400), h=Math.floor((s%86400)/3600), m=Math.floor((s%3600)/60), parts=[];
359
+ if(d)parts.push(d+'d'); if(h)parts.push(h+'h'); if(m)parts.push(m+'m'); parts.push((s%60)+'s');
360
+ return parts.join(' ');
361
+ }
362
+
363
+ function sumCodes(codes, prefix) { var sum=0; for(var c in codes) if(c.startsWith(prefix)) sum+=codes[c]; return sum; }
364
+
365
+ function renderRoutes(containerId, routes, statKey, isSlow) {
366
+ var container = document.getElementById(containerId);
367
+ if (!routes || routes.length === 0) { container.innerHTML = '<div class="route-item"><span class="route-path">No data yet</span></div>'; return; }
368
+ container.innerHTML = routes.slice(0,5).map(function(r) {
369
+ var val = statKey === 'avgTime' ? r.avgTime.toFixed(1) + 'ms' : (statKey === 'errors' ? r.errors : r.count);
370
+ var cls = isSlow && r.avgTime > 100 ? 'slow' : (statKey === 'errors' ? 'error' : '');
371
+ return '<div class="route-item"><span class="route-path">' + r.method + ' ' + r.path + '</span><span class="route-stat ' + cls + '">' + val + '</span></div>';
372
+ }).join('');
373
+ }
374
+
375
+ function renderErrors(errors) {
376
+ var panel = document.getElementById('errorsPanel');
377
+ if (!errors || errors.length === 0) { panel.innerHTML = '<div style="color:var(--text-muted);font-size:12px;">No errors recorded</div>'; return; }
378
+ panel.innerHTML = errors.slice(0,5).map(function(e) {
379
+ return '<div class="error-item"><div class="error-time">' + new Date(e.timestamp).toLocaleTimeString() + '</div><div class="error-path">' + e.method + ' ' + e.path + ' → ' + e.status + '</div></div>';
380
+ }).join('');
381
+ }
382
+
383
+ function applyAlertColors(alerts) {
384
+ document.getElementById('cpuVal').style.color = alerts.cpu ? 'var(--danger)' : '';
385
+ document.getElementById('rtVal').style.color = alerts.responseTime ? 'var(--danger)' : '';
386
+ document.getElementById('lagVal').style.color = alerts.eventLoopLag ? 'var(--danger)' : '';
387
+ document.getElementById('errorRate').style.color = alerts.errorRate ? 'var(--danger)' : '';
388
+ }
389
+
390
+ var connBadge = document.getElementById('connBadge'), connText = document.getElementById('connText');
391
+
392
+ var socket = io(window.location.origin, { path: '${socketPath}', transports: ['websocket', 'polling'] });
393
+
394
+ socket.on('connect', function() { connBadge.className = 'status-badge connected'; connText.textContent = 'Live'; });
395
+ socket.on('disconnect', function() { connBadge.className = 'status-badge disconnected'; connText.textContent = 'Offline'; });
396
+
397
+ socket.on('metrics', function(data) {
398
+ var s = data.snapshot, c = data.charts;
399
+
400
+ document.getElementById('cpuVal').textContent = s.cpu.toFixed(1);
401
+ document.getElementById('memVal').textContent = s.memoryMB.toFixed(0);
402
+ document.getElementById('heapVal').textContent = s.heapUsedMB.toFixed(1);
403
+ document.getElementById('loadVal').textContent = s.loadAvg.toFixed(2);
404
+ document.getElementById('rtVal').textContent = s.responseTime.toFixed(1);
405
+ document.getElementById('rpsVal').textContent = s.rps.toFixed(1);
406
+ document.getElementById('lagVal').textContent = s.eventLoopLag.toFixed(1);
407
+
408
+ document.getElementById('uptime').textContent = formatUptime(s.processUptime);
409
+ document.getElementById('totalReq').textContent = s.totalRequests.toLocaleString();
410
+ document.getElementById('activeConn').textContent = s.activeConnections;
411
+ document.getElementById('errorRate').textContent = s.errorRate.toFixed(1) + '%';
412
+
413
+ document.getElementById('pAvg').textContent = s.percentiles.avg.toFixed(1) + 'ms';
414
+ document.getElementById('p50').textContent = s.percentiles.p50.toFixed(1) + 'ms';
415
+ document.getElementById('p95').textContent = s.percentiles.p95.toFixed(1) + 'ms';
416
+ document.getElementById('p99').textContent = s.percentiles.p99.toFixed(1) + 'ms';
417
+
418
+ if (c.cpu) updateChart(charts.cpu, c.cpu);
419
+ if (c.memory) updateChart(charts.mem, c.memory);
420
+ if (c.heap) updateChart(charts.heap, c.heap);
421
+ if (c.loadAvg) updateChart(charts.load, c.loadAvg);
422
+ if (c.responseTime) updateChart(charts.rt, c.responseTime);
423
+ if (c.rps) updateChart(charts.rps, c.rps);
424
+ if (c.eventLoopLag) updateChart(charts.lag, c.eventLoopLag);
425
+
426
+ renderRoutes('topRoutes', s.topRoutes, 'count', false);
427
+ renderRoutes('slowRoutes', s.slowestRoutes, 'avgTime', true);
428
+
429
+ document.getElementById('s2xx').textContent = sumCodes(s.statusCodes, '2');
430
+ document.getElementById('s3xx').textContent = sumCodes(s.statusCodes, '3');
431
+ document.getElementById('s4xx').textContent = sumCodes(s.statusCodes, '4');
432
+ document.getElementById('s5xx').textContent = sumCodes(s.statusCodes, '5');
433
+ document.getElementById('rateLimited').textContent = s.rateLimitStats.blocked;
434
+
435
+ renderErrors(s.recentErrors);
436
+ applyAlertColors(s.alerts);
437
+
438
+ document.getElementById('dbLatency').textContent = s.database.latencyMs.toFixed(1) + 'ms';
439
+ document.getElementById('dbStatus').textContent = s.database.connected ? 'Connected' : 'Disconnected';
440
+ document.getElementById('dbStatus').className = 'status ' + (s.database.connected ? 'ok' : 'error');
441
+ document.getElementById('heapTotal').textContent = s.heapTotalMB.toFixed(0);
442
+ document.getElementById('heapGrowth').textContent = s.gc.heapGrowthRate.toFixed(2);
443
+
444
+ document.getElementById('nodeVer').textContent = s.nodeVersion;
445
+ document.getElementById('platform').textContent = s.platform.split(' ')[0];
446
+ document.getElementById('pid').textContent = s.pid;
447
+ document.getElementById('cpuCount').textContent = s.cpuCount;
448
+ });
449
+ })();
450
+ </script>
451
+ </body>
452
+ </html>`;
453
+ }
454
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,kCAAkC;AAClC,gDAAgD;AAChD,gFAAgF;AAIhF;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAkB;IACrF,OAAO;;;;;aAKE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAiMI,KAAK;wCACa,QAAQ;;;;;;;;;;;kGAWkD,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DA6KzC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4DjE,CAAC;AACT,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { Hono } from 'hono';
2
+ import type { Server as HttpServer } from 'http';
3
+ import type { StatusMonitorConfig } from './types.js';
4
+ export * from './types.js';
5
+ export { createMonitor, type Monitor } from './monitor.js';
6
+ export { createMiddleware } from './middleware.js';
7
+ export { generateDashboard } from './dashboard.js';
8
+ /**
9
+ * Create a complete status monitor with routes, middleware, and WebSocket
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { Hono } from 'hono';
14
+ * import { serve } from '@hono/node-server';
15
+ * import { statusMonitor } from 'hono-status-monitor';
16
+ *
17
+ * const app = new Hono();
18
+ * const monitor = statusMonitor();
19
+ *
20
+ * // Add middleware to track all requests
21
+ * app.use('*', monitor.middleware);
22
+ *
23
+ * // Mount status routes
24
+ * app.route('/status', monitor.routes);
25
+ *
26
+ * // Start server and initialize WebSocket
27
+ * const server = serve({ fetch: app.fetch, port: 3000 });
28
+ * monitor.initSocket(server);
29
+ * ```
30
+ */
31
+ export declare function statusMonitor(config?: StatusMonitorConfig): {
32
+ /** Hono middleware for tracking all requests */
33
+ middleware: (c: any, next: () => Promise<void>) => Promise<void>;
34
+ /** Pre-configured Hono routes for dashboard and API */
35
+ routes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
36
+ /** Initialize Socket.io on the HTTP server for real-time updates */
37
+ initSocket: (server: HttpServer) => import("socket.io").Server<import("socket.io").DefaultEventsMap, import("socket.io").DefaultEventsMap, import("socket.io").DefaultEventsMap, any>;
38
+ /** Track rate limit events for the dashboard */
39
+ trackRateLimit: (blocked: boolean) => void;
40
+ /** Get current metrics snapshot */
41
+ getMetrics: () => Promise<import("./types.js").MetricsSnapshot>;
42
+ /** Get chart data for all metrics */
43
+ getCharts: () => import("./types.js").ChartData;
44
+ /** Stop metrics collection */
45
+ stop: () => void;
46
+ /** Access to the underlying monitor instance */
47
+ monitor: {
48
+ config: Required<StatusMonitorConfig>;
49
+ trackRequest: (path: string, method: string) => void;
50
+ trackRequestComplete: (path: string, method: string, durationMs: number, statusCode: number) => void;
51
+ trackRateLimitEvent: (blocked: boolean) => void;
52
+ getMetricsSnapshot: (dbStats?: import("./types.js").DatabaseStats) => Promise<import("./types.js").MetricsSnapshot>;
53
+ getChartData: () => import("./types.js").ChartData;
54
+ start: () => void;
55
+ stop: () => void;
56
+ initSocket: (server: HttpServer) => import("socket.io").Server;
57
+ formatUptime: (seconds: number) => string;
58
+ readonly io: import("socket.io").Server<import("socket.io").DefaultEventsMap, import("socket.io").DefaultEventsMap, import("socket.io").DefaultEventsMap, any> | null;
59
+ };
60
+ };
61
+ export default statusMonitor;
62
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAIjD,OAAO,KAAK,EAAE,mBAAmB,EAAqB,MAAM,YAAY,CAAC;AAGzE,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,mBAAwB;IAkCtD,gDAAgD;;IAEhD,uDAAuD;;IAEvD,oEAAoE;yBAC/C,UAAU;IAC/B,gDAAgD;8BACtB,OAAO;IACjC,mCAAmC;;IAEnC,qCAAqC;;IAErC,8BAA8B;;IAE9B,gDAAgD;;;;;;;;;;;;;;EAGvD;AAGD,eAAe,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,85 @@
1
+ // =============================================================================
2
+ // HONO STATUS MONITOR
3
+ // Real-time server monitoring dashboard for Hono.js with WebSocket updates
4
+ // =============================================================================
5
+ import { Hono } from 'hono';
6
+ import { createMonitor } from './monitor.js';
7
+ import { createMiddleware } from './middleware.js';
8
+ import { generateDashboard } from './dashboard.js';
9
+ // Re-export types
10
+ export * from './types.js';
11
+ export { createMonitor } from './monitor.js';
12
+ export { createMiddleware } from './middleware.js';
13
+ export { generateDashboard } from './dashboard.js';
14
+ /**
15
+ * Create a complete status monitor with routes, middleware, and WebSocket
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { Hono } from 'hono';
20
+ * import { serve } from '@hono/node-server';
21
+ * import { statusMonitor } from 'hono-status-monitor';
22
+ *
23
+ * const app = new Hono();
24
+ * const monitor = statusMonitor();
25
+ *
26
+ * // Add middleware to track all requests
27
+ * app.use('*', monitor.middleware);
28
+ *
29
+ * // Mount status routes
30
+ * app.route('/status', monitor.routes);
31
+ *
32
+ * // Start server and initialize WebSocket
33
+ * const server = serve({ fetch: app.fetch, port: 3000 });
34
+ * monitor.initSocket(server);
35
+ * ```
36
+ */
37
+ export function statusMonitor(config = {}) {
38
+ // Create monitor instance
39
+ const monitor = createMonitor(config);
40
+ // Create middleware
41
+ const middleware = createMiddleware(monitor);
42
+ // Create Hono routes
43
+ const routes = new Hono();
44
+ // Dashboard page
45
+ routes.get('/', async (c) => {
46
+ const snapshot = await monitor.getMetricsSnapshot();
47
+ const html = generateDashboard({
48
+ hostname: snapshot.hostname,
49
+ uptime: monitor.formatUptime(snapshot.uptime),
50
+ socketPath: monitor.config.socketPath,
51
+ title: monitor.config.title
52
+ });
53
+ return c.html(html);
54
+ });
55
+ // JSON API endpoint
56
+ routes.get('/api/metrics', async (c) => {
57
+ return c.json({
58
+ snapshot: await monitor.getMetricsSnapshot(),
59
+ charts: monitor.getChartData()
60
+ });
61
+ });
62
+ // Start metrics collection
63
+ monitor.start();
64
+ return {
65
+ /** Hono middleware for tracking all requests */
66
+ middleware,
67
+ /** Pre-configured Hono routes for dashboard and API */
68
+ routes,
69
+ /** Initialize Socket.io on the HTTP server for real-time updates */
70
+ initSocket: (server) => monitor.initSocket(server),
71
+ /** Track rate limit events for the dashboard */
72
+ trackRateLimit: (blocked) => monitor.trackRateLimitEvent(blocked),
73
+ /** Get current metrics snapshot */
74
+ getMetrics: () => monitor.getMetricsSnapshot(),
75
+ /** Get chart data for all metrics */
76
+ getCharts: () => monitor.getChartData(),
77
+ /** Stop metrics collection */
78
+ stop: () => monitor.stop(),
79
+ /** Access to the underlying monitor instance */
80
+ monitor
81
+ };
82
+ }
83
+ // Default export
84
+ export default statusMonitor;
85
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,sBAAsB;AACtB,2EAA2E;AAC3E,gFAAgF;AAEhF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,aAAa,EAAgB,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGnD,kBAAkB;AAClB,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAgB,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,aAAa,CAAC,SAA8B,EAAE;IAC1D,0BAA0B;IAC1B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEtC,oBAAoB;IACpB,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE7C,qBAAqB;IACrB,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;IAE1B,iBAAiB;IACjB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACxB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,kBAAkB,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,iBAAiB,CAAC;YAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC7C,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU;YACrC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK;SAC9B,CAAC,CAAC;QACH,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,oBAAoB;IACpB,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACnC,OAAO,CAAC,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,MAAM,OAAO,CAAC,kBAAkB,EAAE;YAC5C,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE;SACjC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,2BAA2B;IAC3B,OAAO,CAAC,KAAK,EAAE,CAAC;IAEhB,OAAO;QACH,gDAAgD;QAChD,UAAU;QACV,uDAAuD;QACvD,MAAM;QACN,oEAAoE;QACpE,UAAU,EAAE,CAAC,MAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;QAC9D,gDAAgD;QAChD,cAAc,EAAE,CAAC,OAAgB,EAAE,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC;QAC1E,mCAAmC;QACnC,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE;QAC9C,qCAAqC;QACrC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE;QACvC,8BAA8B;QAC9B,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE;QAC1B,gDAAgD;QAChD,OAAO;KACV,CAAC;AACN,CAAC;AAED,iBAAiB;AACjB,eAAe,aAAa,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Monitor } from './monitor.js';
2
+ /**
3
+ * Create Hono middleware for tracking requests
4
+ */
5
+ export declare function createMiddleware(monitor: Monitor): (c: any, next: () => Promise<void>) => Promise<void>;
6
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,IAG/B,GAAG,GAAG,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC,mBAyBlD"}