pms_md 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.
- package/README.md +93 -0
- package/node-monitor/ARCHITECTURE.md +341 -0
- package/node-monitor/CHANGELOG.md +105 -0
- package/node-monitor/CONTRIBUTING.md +96 -0
- package/node-monitor/DESIGN_IMPROVEMENTS.md +286 -0
- package/node-monitor/FILTER_BUTTONS_FIX.md +303 -0
- package/node-monitor/GETTING_STARTED.md +416 -0
- package/node-monitor/INSTALLATION.md +470 -0
- package/node-monitor/LICENSE +22 -0
- package/node-monitor/PUBLISHING_GUIDE.md +331 -0
- package/node-monitor/QUICK_REFERENCE.md +252 -0
- package/node-monitor/README.md +458 -0
- package/node-monitor/READY_TO_PUBLISH.md +272 -0
- package/node-monitor/SETUP_GUIDE.md +479 -0
- package/node-monitor/examples/EMAIL_SETUP_GUIDE.md +282 -0
- package/node-monitor/examples/ERROR_LOGGING_GUIDE.md +405 -0
- package/node-monitor/examples/GET_APP_PASSWORD.md +145 -0
- package/node-monitor/examples/LOG_FILES_REFERENCE.md +336 -0
- package/node-monitor/examples/QUICK_START_EMAIL.md +126 -0
- package/node-monitor/examples/express-app.js +499 -0
- package/node-monitor/examples/package-lock.json +1295 -0
- package/node-monitor/examples/package.json +18 -0
- package/node-monitor/examples/public/css/style.css +718 -0
- package/node-monitor/examples/public/js/dashboard.js +207 -0
- package/node-monitor/examples/public/js/health.js +114 -0
- package/node-monitor/examples/public/js/main.js +89 -0
- package/node-monitor/examples/public/js/metrics.js +225 -0
- package/node-monitor/examples/public/js/theme.js +138 -0
- package/node-monitor/examples/views/dashboard.ejs +20 -0
- package/node-monitor/examples/views/error-logs.ejs +1129 -0
- package/node-monitor/examples/views/health.ejs +21 -0
- package/node-monitor/examples/views/home.ejs +341 -0
- package/node-monitor/examples/views/layout.ejs +50 -0
- package/node-monitor/examples/views/metrics.ejs +16 -0
- package/node-monitor/examples/views/partials/footer.ejs +16 -0
- package/node-monitor/examples/views/partials/header.ejs +35 -0
- package/node-monitor/examples/views/partials/nav.ejs +23 -0
- package/node-monitor/examples/views/status.ejs +390 -0
- package/node-monitor/package-lock.json +4300 -0
- package/node-monitor/package.json +76 -0
- package/node-monitor/pre-publish-check.js +200 -0
- package/node-monitor/src/config/monitoringConfig.js +255 -0
- package/node-monitor/src/index.js +300 -0
- package/node-monitor/src/logger/errorLogger.js +297 -0
- package/node-monitor/src/monitors/apiErrorMonitor.js +156 -0
- package/node-monitor/src/monitors/dbConnectionMonitor.js +389 -0
- package/node-monitor/src/monitors/serverHealthMonitor.js +320 -0
- package/node-monitor/src/monitors/systemResourceMonitor.js +357 -0
- package/node-monitor/src/notifiers/emailNotifier.js +248 -0
- package/node-monitor/src/notifiers/notificationManager.js +96 -0
- package/node-monitor/src/notifiers/slackNotifier.js +209 -0
- package/node-monitor/src/views/dashboard.html +530 -0
- package/node-monitor/src/views/health.html +399 -0
- package/node-monitor/src/views/metrics.html +406 -0
- package/package.json +22 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// ===================================
|
|
2
|
+
// DASHBOARD FUNCTIONS
|
|
3
|
+
// ===================================
|
|
4
|
+
|
|
5
|
+
async function loadDashboard() {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch('/monitor/status?format=json');
|
|
8
|
+
const data = await response.json();
|
|
9
|
+
renderDashboard(data);
|
|
10
|
+
updateTimestamp();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
showError('dashboardContent', error.message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renderDashboard(data) {
|
|
17
|
+
const isHealthy = data.health?.isHealthy ?? true;
|
|
18
|
+
|
|
19
|
+
// Update status badge
|
|
20
|
+
const statusBadge = document.getElementById('overallStatus');
|
|
21
|
+
const statusText = document.getElementById('statusText');
|
|
22
|
+
|
|
23
|
+
if (statusBadge && statusText) {
|
|
24
|
+
statusBadge.className = `status-badge ${isHealthy ? 'healthy' : 'unhealthy'}`;
|
|
25
|
+
statusText.textContent = isHealthy ? 'Healthy' : 'Unhealthy';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Render content
|
|
29
|
+
const content = `
|
|
30
|
+
<div class="grid">
|
|
31
|
+
${renderApplicationCard(data)}
|
|
32
|
+
${renderHealthCard(data.health)}
|
|
33
|
+
${renderSystemCard(data.system)}
|
|
34
|
+
${renderErrorCard(data.errors)}
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
document.getElementById('dashboardContent').innerHTML = content;
|
|
39
|
+
|
|
40
|
+
// Render stats summary
|
|
41
|
+
renderStatsSummary(data);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderApplicationCard(data) {
|
|
45
|
+
const uptime = data.health?.uptime || 0;
|
|
46
|
+
|
|
47
|
+
return `
|
|
48
|
+
<div class="card">
|
|
49
|
+
<div class="card-header">
|
|
50
|
+
<span class="card-icon">🚀</span>
|
|
51
|
+
<h2 class="card-title">Application</h2>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="metric">
|
|
54
|
+
<span class="metric-label">Status</span>
|
|
55
|
+
<span class="metric-value good">${data.isRunning ? 'Running' : 'Stopped'}</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="metric">
|
|
58
|
+
<span class="metric-label">Uptime</span>
|
|
59
|
+
<span class="metric-value">${formatUptime(uptime)}</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="metric">
|
|
62
|
+
<span class="metric-label">Environment</span>
|
|
63
|
+
<span class="metric-value">${data.environment || 'development'}</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="metric">
|
|
66
|
+
<span class="metric-label">Monitoring</span>
|
|
67
|
+
<span class="metric-value good">Active</span>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function renderHealthCard(health) {
|
|
74
|
+
if (!health) return '';
|
|
75
|
+
|
|
76
|
+
return `
|
|
77
|
+
<div class="card">
|
|
78
|
+
<div class="card-header">
|
|
79
|
+
<span class="card-icon">💚</span>
|
|
80
|
+
<h2 class="card-title">Health Checks</h2>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="metric">
|
|
83
|
+
<span class="metric-label">Overall Status</span>
|
|
84
|
+
<span class="metric-value ${health.isHealthy ? 'good' : 'danger'}">
|
|
85
|
+
${health.isHealthy ? '✓ Healthy' : '✗ Unhealthy'}
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="metric">
|
|
89
|
+
<span class="metric-label">Registered Checks</span>
|
|
90
|
+
<span class="metric-value">${health.registeredChecks || 0}</span>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="metric">
|
|
93
|
+
<span class="metric-label">Consecutive Failures</span>
|
|
94
|
+
<span class="metric-value ${health.consecutiveFailures > 0 ? 'warning' : 'good'}">
|
|
95
|
+
${health.consecutiveFailures || 0}
|
|
96
|
+
</span>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderSystemCard(system) {
|
|
103
|
+
if (!system) return '';
|
|
104
|
+
|
|
105
|
+
const cpuPercent = system.cpu?.system || 0;
|
|
106
|
+
const memoryPercent = system.memory?.system?.usagePercent || 0;
|
|
107
|
+
|
|
108
|
+
return `
|
|
109
|
+
<div class="card">
|
|
110
|
+
<div class="card-header">
|
|
111
|
+
<span class="card-icon">💻</span>
|
|
112
|
+
<h2 class="card-title">System Resources</h2>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="metric">
|
|
115
|
+
<span class="metric-label">CPU Usage</span>
|
|
116
|
+
<span class="metric-value ${getCpuClass(cpuPercent)}">${cpuPercent.toFixed(1)}%</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="progress-bar">
|
|
119
|
+
<div class="progress-fill ${getCpuClass(cpuPercent)}" style="width: ${cpuPercent}%"></div>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="metric" style="margin-top: 15px;">
|
|
122
|
+
<span class="metric-label">Memory Usage</span>
|
|
123
|
+
<span class="metric-value ${getMemoryClass(memoryPercent)}">${memoryPercent.toFixed(1)}%</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="progress-bar">
|
|
126
|
+
<div class="progress-fill ${getMemoryClass(memoryPercent)}" style="width: ${memoryPercent}%"></div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="metric" style="margin-top: 15px;">
|
|
129
|
+
<span class="metric-label">Heap Used</span>
|
|
130
|
+
<span class="metric-value">${formatBytes(system.memory?.heapUsed || 0)}</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderErrorCard(errors) {
|
|
137
|
+
if (!errors) return '';
|
|
138
|
+
|
|
139
|
+
return `
|
|
140
|
+
<div class="card">
|
|
141
|
+
<div class="card-header">
|
|
142
|
+
<span class="card-icon">⚠️</span>
|
|
143
|
+
<h2 class="card-title">Error Tracking</h2>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="metric">
|
|
146
|
+
<span class="metric-label">Current Error Rate</span>
|
|
147
|
+
<span class="metric-value ${errors.currentErrorRate > 10 ? 'danger' : 'good'}">
|
|
148
|
+
${errors.currentErrorRate || 0} /min
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="metric">
|
|
152
|
+
<span class="metric-label">Threshold</span>
|
|
153
|
+
<span class="metric-value">${errors.threshold || 0} /min</span>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="metric">
|
|
156
|
+
<span class="metric-label">API Error Rate</span>
|
|
157
|
+
<span class="metric-value">${errors.apiErrorRate || 0} /min</span>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderStatsSummary(data) {
|
|
164
|
+
const summaryEl = document.getElementById('statsSummary');
|
|
165
|
+
if (!summaryEl) return;
|
|
166
|
+
|
|
167
|
+
const uptime = data.health?.uptime || 0;
|
|
168
|
+
const cpuPercent = data.system?.cpu?.system || 0;
|
|
169
|
+
const memoryPercent = data.system?.memory?.system?.usagePercent || 0;
|
|
170
|
+
|
|
171
|
+
summaryEl.innerHTML = `
|
|
172
|
+
<div class="stats-grid">
|
|
173
|
+
<div class="card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
|
174
|
+
<div style="text-align: center;">
|
|
175
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Uptime</div>
|
|
176
|
+
<div style="font-size: 2em; font-weight: bold; margin: 10px 0;">${formatUptime(uptime)}</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="card" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white;">
|
|
180
|
+
<div style="text-align: center;">
|
|
181
|
+
<div style="font-size: 0.9em; opacity: 0.9;">CPU Usage</div>
|
|
182
|
+
<div style="font-size: 2em; font-weight: bold; margin: 10px 0;">${cpuPercent.toFixed(1)}%</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="card" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white;">
|
|
186
|
+
<div style="text-align: center;">
|
|
187
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Memory Usage</div>
|
|
188
|
+
<div style="font-size: 2em; font-weight: bold; margin: 10px 0;">${memoryPercent.toFixed(1)}%</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="card" style="background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white;">
|
|
192
|
+
<div style="text-align: center;">
|
|
193
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Error Rate</div>
|
|
194
|
+
<div style="font-size: 2em; font-weight: bold; margin: 10px 0;">${data.errors?.currentErrorRate || 0}/min</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Load dashboard on page load
|
|
202
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
203
|
+
loadDashboard();
|
|
204
|
+
// Auto-refresh every 30 seconds
|
|
205
|
+
setInterval(loadDashboard, 30000);
|
|
206
|
+
});
|
|
207
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ===================================
|
|
2
|
+
// HEALTH CHECK FUNCTIONS
|
|
3
|
+
// ===================================
|
|
4
|
+
|
|
5
|
+
async function loadHealth() {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch('/health?format=json');
|
|
8
|
+
const data = await response.json();
|
|
9
|
+
renderHealth(data);
|
|
10
|
+
updateTimestamp();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
showError('healthContent', error.message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renderHealth(data) {
|
|
17
|
+
const isHealthy = data.status === 'healthy';
|
|
18
|
+
|
|
19
|
+
// Update status indicator
|
|
20
|
+
const indicator = document.getElementById('statusIndicator');
|
|
21
|
+
const statusText = document.getElementById('statusText');
|
|
22
|
+
|
|
23
|
+
if (indicator && statusText) {
|
|
24
|
+
if (isHealthy) {
|
|
25
|
+
indicator.className = 'status-indicator healthy';
|
|
26
|
+
statusText.textContent = 'All Systems Operational';
|
|
27
|
+
} else {
|
|
28
|
+
indicator.className = 'status-indicator unhealthy';
|
|
29
|
+
statusText.textContent = 'System Issues Detected';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Render content
|
|
34
|
+
const content = `
|
|
35
|
+
<div class="card">
|
|
36
|
+
<h2 style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px;">
|
|
37
|
+
📊 Overview
|
|
38
|
+
</h2>
|
|
39
|
+
<div class="stats-grid">
|
|
40
|
+
<div class="card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
|
41
|
+
<div style="text-align: center;">
|
|
42
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Status</div>
|
|
43
|
+
<div style="font-size: 3em; margin: 10px 0;">${isHealthy ? '✓' : '✗'}</div>
|
|
44
|
+
<div style="font-size: 1.1em; font-weight: bold;">${data.status}</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="card" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white;">
|
|
48
|
+
<div style="text-align: center;">
|
|
49
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Uptime</div>
|
|
50
|
+
<div style="font-size: 2.5em; font-weight: bold; margin: 10px 0;">${formatUptime(data.uptime)}</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="card" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white;">
|
|
54
|
+
<div style="text-align: center;">
|
|
55
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Checks</div>
|
|
56
|
+
<div style="font-size: 2.5em; font-weight: bold; margin: 10px 0;">${Object.keys(data.checks || {}).length}</div>
|
|
57
|
+
<div style="font-size: 0.9em;">registered</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
${renderChecks(data.checks)}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
document.getElementById('healthContent').innerHTML = content;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function renderChecks(checks) {
|
|
70
|
+
if (!checks || Object.keys(checks).length === 0) {
|
|
71
|
+
return `
|
|
72
|
+
<div class="card">
|
|
73
|
+
<h2 style="display: flex; align-items: center; gap: 10px;">
|
|
74
|
+
✅ Health Checks
|
|
75
|
+
</h2>
|
|
76
|
+
<p style="text-align: center; color: #666; padding: 20px;">
|
|
77
|
+
No custom health checks registered
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const checkItems = Object.entries(checks).map(([name, check]) => {
|
|
84
|
+
const isPassing = check.status === 'pass';
|
|
85
|
+
return `
|
|
86
|
+
<div class="card" style="border-left: 5px solid ${isPassing ? '#10b981' : '#ef4444'};">
|
|
87
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
88
|
+
<div>
|
|
89
|
+
<h3 style="color: #333; margin-bottom: 5px;">${formatCheckName(name)}</h3>
|
|
90
|
+
<p style="color: #666; font-size: 0.9em;">Health check</p>
|
|
91
|
+
</div>
|
|
92
|
+
<div style="padding: 8px 20px; border-radius: 25px; font-weight: bold; background: ${isPassing ? '#d1fae5' : '#fee2e2'}; color: ${isPassing ? '#065f46' : '#991b1b'};">
|
|
93
|
+
${isPassing ? '✓ Pass' : '✗ Fail'}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
`;
|
|
98
|
+
}).join('');
|
|
99
|
+
|
|
100
|
+
return `
|
|
101
|
+
<div>
|
|
102
|
+
<h2 style="color: white; margin: 30px 0 20px 0; font-size: 1.5em;">✅ Health Checks</h2>
|
|
103
|
+
${checkItems}
|
|
104
|
+
</div>
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Load health on page load
|
|
109
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
110
|
+
loadHealth();
|
|
111
|
+
// Auto-refresh every 10 seconds
|
|
112
|
+
setInterval(loadHealth, 10000);
|
|
113
|
+
});
|
|
114
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ===================================
|
|
2
|
+
// UTILITY FUNCTIONS
|
|
3
|
+
// ===================================
|
|
4
|
+
|
|
5
|
+
function updateTimestamp() {
|
|
6
|
+
const now = new Date().toLocaleString();
|
|
7
|
+
const timestampEl = document.getElementById('lastUpdate');
|
|
8
|
+
if (timestampEl) {
|
|
9
|
+
timestampEl.textContent = `Last updated: ${now}`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function formatBytes(bytes) {
|
|
14
|
+
if (bytes === 0) return '0 B';
|
|
15
|
+
const k = 1024;
|
|
16
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
17
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
18
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function formatUptime(seconds) {
|
|
22
|
+
if (!seconds) return '0s';
|
|
23
|
+
|
|
24
|
+
const days = Math.floor(seconds / 86400);
|
|
25
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
26
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
27
|
+
const secs = Math.floor(seconds % 60);
|
|
28
|
+
|
|
29
|
+
const parts = [];
|
|
30
|
+
if (days > 0) parts.push(`${days}d`);
|
|
31
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
32
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
33
|
+
if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
|
|
34
|
+
|
|
35
|
+
return parts.join(' ');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getCpuClass(percent) {
|
|
39
|
+
if (percent > 80) return 'danger';
|
|
40
|
+
if (percent > 60) return 'warning';
|
|
41
|
+
return 'good';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getMemoryClass(percent) {
|
|
45
|
+
if (percent > 90) return 'danger';
|
|
46
|
+
if (percent > 75) return 'warning';
|
|
47
|
+
return 'good';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatCheckName(name) {
|
|
51
|
+
return name
|
|
52
|
+
.split('-')
|
|
53
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
54
|
+
.join(' ');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ===================================
|
|
58
|
+
// ERROR HANDLING
|
|
59
|
+
// ===================================
|
|
60
|
+
|
|
61
|
+
function showError(containerId, message) {
|
|
62
|
+
const container = document.getElementById(containerId);
|
|
63
|
+
if (container) {
|
|
64
|
+
container.innerHTML = `
|
|
65
|
+
<div class="card" style="border-left: 5px solid #ef4444;">
|
|
66
|
+
<h3 style="color: #ef4444; display: flex; align-items: center; gap: 10px;">
|
|
67
|
+
<span>❌</span>
|
|
68
|
+
<span>Error Loading Data</span>
|
|
69
|
+
</h3>
|
|
70
|
+
<p style="color: #666; margin-top: 10px;">${escapeHtml(message)}</p>
|
|
71
|
+
<button class="refresh-btn" onclick="location.reload()" style="margin-top: 20px;">
|
|
72
|
+
🔄 Reload Page
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function escapeHtml(text) {
|
|
80
|
+
const div = document.createElement('div');
|
|
81
|
+
div.textContent = text;
|
|
82
|
+
return div.innerHTML;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Initialize timestamp on page load
|
|
86
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
87
|
+
updateTimestamp();
|
|
88
|
+
});
|
|
89
|
+
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// ===================================
|
|
2
|
+
// METRICS FUNCTIONS
|
|
3
|
+
// ===================================
|
|
4
|
+
|
|
5
|
+
let cpuChart = null;
|
|
6
|
+
let memoryChart = null;
|
|
7
|
+
|
|
8
|
+
async function loadMetrics() {
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch('/monitor/metrics?format=json');
|
|
11
|
+
const data = await response.json();
|
|
12
|
+
renderMetrics(data);
|
|
13
|
+
updateTimestamp();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
showError('statsContent', error.message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderMetrics(data) {
|
|
20
|
+
renderStats(data);
|
|
21
|
+
renderCharts(data);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderStats(data) {
|
|
25
|
+
// Get the latest values from the history arrays
|
|
26
|
+
const cpuHistory = data.history?.cpu || [];
|
|
27
|
+
const memoryHistory = data.history?.memory || [];
|
|
28
|
+
|
|
29
|
+
const latestCpu = cpuHistory.length > 0 ? cpuHistory[cpuHistory.length - 1].value : { process: 0, system: 0 };
|
|
30
|
+
const latestMemory = memoryHistory.length > 0 ? memoryHistory[memoryHistory.length - 1].value : { system: {}, process: {} };
|
|
31
|
+
|
|
32
|
+
const html = `
|
|
33
|
+
<div class="stats-grid">
|
|
34
|
+
<div class="card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
|
35
|
+
<div style="text-align: center;">
|
|
36
|
+
<div style="font-size: 0.9em; opacity: 0.9;">CPU Usage (Process)</div>
|
|
37
|
+
<div style="font-size: 2.5em; font-weight: bold; margin: 10px 0;">${(latestCpu.process || 0).toFixed(1)}%</div>
|
|
38
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Current load</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="card" style="background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); color: white;">
|
|
42
|
+
<div style="text-align: center;">
|
|
43
|
+
<div style="font-size: 0.9em; opacity: 0.9;">CPU Usage (System)</div>
|
|
44
|
+
<div style="font-size: 2.5em; font-weight: bold; margin: 10px 0;">${(latestCpu.system || 0).toFixed(1)}%</div>
|
|
45
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Overall system</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="card" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white;">
|
|
49
|
+
<div style="text-align: center;">
|
|
50
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Memory Usage (System)</div>
|
|
51
|
+
<div style="font-size: 2.5em; font-weight: bold; margin: 10px 0;">${(latestMemory.system?.usagePercent || 0).toFixed(1)}%</div>
|
|
52
|
+
<div style="font-size: 0.9em; opacity: 0.9;">${latestMemory.system?.used || 'N/A'} used</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="card" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white;">
|
|
56
|
+
<div style="text-align: center;">
|
|
57
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Data Points</div>
|
|
58
|
+
<div style="font-size: 2.5em; font-weight: bold; margin: 10px 0;">${cpuHistory.length || 0}</div>
|
|
59
|
+
<div style="font-size: 0.9em; opacity: 0.9;">Collected samples</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
document.getElementById('statsContent').innerHTML = html;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderCharts(data) {
|
|
69
|
+
const cpuHistory = data.history?.cpu || [];
|
|
70
|
+
const memoryHistory = data.history?.memory || [];
|
|
71
|
+
|
|
72
|
+
if (cpuHistory.length === 0 && memoryHistory.length === 0) {
|
|
73
|
+
document.getElementById('chartsContent').innerHTML = `
|
|
74
|
+
<div class="card">
|
|
75
|
+
<p style="text-align: center; color: #666;">No metrics history available yet. Data will appear as the system collects metrics.</p>
|
|
76
|
+
</div>
|
|
77
|
+
`;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Use CPU history for labels (both should have same length)
|
|
82
|
+
const labels = cpuHistory.map((_, index) => `${index + 1}`);
|
|
83
|
+
const cpuProcessData = cpuHistory.map(m => m.value?.process || 0);
|
|
84
|
+
const cpuSystemData = cpuHistory.map(m => m.value?.system || 0);
|
|
85
|
+
const memoryData = memoryHistory.map(m => m.value?.system?.usagePercent || 0);
|
|
86
|
+
|
|
87
|
+
document.getElementById('chartsContent').innerHTML = `
|
|
88
|
+
<div class="card">
|
|
89
|
+
<h2 style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px;">
|
|
90
|
+
💻 CPU Usage Over Time
|
|
91
|
+
</h2>
|
|
92
|
+
<div style="position: relative; height: 300px;">
|
|
93
|
+
<canvas id="cpuChart"></canvas>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="card">
|
|
98
|
+
<h2 style="display: flex; align-items: center; gap: 10px; margin-bottom: 20px;">
|
|
99
|
+
🧠 Memory Usage Over Time
|
|
100
|
+
</h2>
|
|
101
|
+
<div style="position: relative; height: 300px;">
|
|
102
|
+
<canvas id="memoryChart"></canvas>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
// Destroy existing charts
|
|
108
|
+
if (cpuChart) cpuChart.destroy();
|
|
109
|
+
if (memoryChart) memoryChart.destroy();
|
|
110
|
+
|
|
111
|
+
// Create CPU chart
|
|
112
|
+
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
|
113
|
+
cpuChart = new Chart(cpuCtx, {
|
|
114
|
+
type: 'line',
|
|
115
|
+
data: {
|
|
116
|
+
labels: labels,
|
|
117
|
+
datasets: [
|
|
118
|
+
{
|
|
119
|
+
label: 'Process CPU %',
|
|
120
|
+
data: cpuProcessData,
|
|
121
|
+
borderColor: '#667eea',
|
|
122
|
+
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
|
123
|
+
tension: 0.4,
|
|
124
|
+
fill: true,
|
|
125
|
+
borderWidth: 2
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: 'System CPU %',
|
|
129
|
+
data: cpuSystemData,
|
|
130
|
+
borderColor: '#764ba2',
|
|
131
|
+
backgroundColor: 'rgba(118, 75, 162, 0.1)',
|
|
132
|
+
tension: 0.4,
|
|
133
|
+
fill: true,
|
|
134
|
+
borderWidth: 2
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
options: {
|
|
139
|
+
responsive: true,
|
|
140
|
+
maintainAspectRatio: false,
|
|
141
|
+
plugins: {
|
|
142
|
+
legend: {
|
|
143
|
+
display: true,
|
|
144
|
+
position: 'top'
|
|
145
|
+
},
|
|
146
|
+
tooltip: {
|
|
147
|
+
mode: 'index',
|
|
148
|
+
intersect: false
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
scales: {
|
|
152
|
+
y: {
|
|
153
|
+
beginAtZero: true,
|
|
154
|
+
max: 100,
|
|
155
|
+
ticks: {
|
|
156
|
+
callback: function(value) {
|
|
157
|
+
return value + '%';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
interaction: {
|
|
163
|
+
mode: 'nearest',
|
|
164
|
+
axis: 'x',
|
|
165
|
+
intersect: false
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Create Memory chart
|
|
171
|
+
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
|
172
|
+
memoryChart = new Chart(memoryCtx, {
|
|
173
|
+
type: 'line',
|
|
174
|
+
data: {
|
|
175
|
+
labels: labels,
|
|
176
|
+
datasets: [{
|
|
177
|
+
label: 'Memory Usage %',
|
|
178
|
+
data: memoryData,
|
|
179
|
+
borderColor: '#10b981',
|
|
180
|
+
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
181
|
+
tension: 0.4,
|
|
182
|
+
fill: true,
|
|
183
|
+
borderWidth: 2
|
|
184
|
+
}]
|
|
185
|
+
},
|
|
186
|
+
options: {
|
|
187
|
+
responsive: true,
|
|
188
|
+
maintainAspectRatio: false,
|
|
189
|
+
plugins: {
|
|
190
|
+
legend: {
|
|
191
|
+
display: true,
|
|
192
|
+
position: 'top'
|
|
193
|
+
},
|
|
194
|
+
tooltip: {
|
|
195
|
+
mode: 'index',
|
|
196
|
+
intersect: false
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
scales: {
|
|
200
|
+
y: {
|
|
201
|
+
beginAtZero: true,
|
|
202
|
+
max: 100,
|
|
203
|
+
ticks: {
|
|
204
|
+
callback: function(value) {
|
|
205
|
+
return value + '%';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
interaction: {
|
|
211
|
+
mode: 'nearest',
|
|
212
|
+
axis: 'x',
|
|
213
|
+
intersect: false
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Load metrics on page load
|
|
220
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
221
|
+
loadMetrics();
|
|
222
|
+
// Auto-refresh every 30 seconds
|
|
223
|
+
setInterval(loadMetrics, 30000);
|
|
224
|
+
});
|
|
225
|
+
|