systemlens 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/.env.example +5 -0
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/bin/systemlens.js +127 -0
- package/package.json +73 -0
- package/server.js +106 -0
- package/src/analyzers/cpu.analyzer.js +124 -0
- package/src/analyzers/memory.analyzer.js +107 -0
- package/src/analyzers/process.analyzer.js +136 -0
- package/src/analyzers/spike.detector.js +135 -0
- package/src/classifiers/process.classifier.js +127 -0
- package/src/collectors/cpu.collector.js +48 -0
- package/src/collectors/disk.collector.js +41 -0
- package/src/collectors/memory.collector.js +41 -0
- package/src/collectors/process.collector.js +65 -0
- package/src/engines/ai.engine.js +105 -0
- package/src/engines/explanation.engine.js +348 -0
- package/src/engines/suggestion.engine.js +242 -0
- package/src/history/history.tracker.js +114 -0
- package/src/index.js +130 -0
- package/src/monitor/realtime.monitor.js +145 -0
- package/src/renderer/cli.renderer.js +318 -0
- package/src/utils/constants.js +80 -0
- package/src/utils/helpers.js +110 -0
- package/web/app.js +352 -0
- package/web/index.html +209 -0
- package/web/style.css +886 -0
package/web/app.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// ─── SystemLens Web Dashboard ────────────────────────────────────
|
|
2
|
+
// Real-time WebSocket client that renders system analysis beautifully
|
|
3
|
+
|
|
4
|
+
(() => {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
// ─── State ─────────────────────────────────────────────
|
|
8
|
+
let ws = null;
|
|
9
|
+
let reconnectTimer = null;
|
|
10
|
+
let cpuHistory = [];
|
|
11
|
+
let memHistory = [];
|
|
12
|
+
let currentTab = 'cpu';
|
|
13
|
+
let latestData = null;
|
|
14
|
+
const MAX_SPARKLINE = 30;
|
|
15
|
+
|
|
16
|
+
// ─── DOM References ────────────────────────────────────
|
|
17
|
+
const $ = (id) => document.getElementById(id);
|
|
18
|
+
|
|
19
|
+
// ─── WebSocket Connection ──────────────────────────────
|
|
20
|
+
function connect() {
|
|
21
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
22
|
+
ws = new WebSocket(`${protocol}//${location.host}`);
|
|
23
|
+
|
|
24
|
+
ws.onopen = () => {
|
|
25
|
+
updateConnectionStatus('connected');
|
|
26
|
+
if (reconnectTimer) {
|
|
27
|
+
clearTimeout(reconnectTimer);
|
|
28
|
+
reconnectTimer = null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
ws.onmessage = (event) => {
|
|
33
|
+
try {
|
|
34
|
+
const data = JSON.parse(event.data);
|
|
35
|
+
latestData = data;
|
|
36
|
+
render(data);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error('Parse error:', e);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
ws.onclose = () => {
|
|
43
|
+
updateConnectionStatus('disconnected');
|
|
44
|
+
reconnectTimer = setTimeout(connect, 3000);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
ws.onerror = () => {
|
|
48
|
+
updateConnectionStatus('disconnected');
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function updateConnectionStatus(status) {
|
|
53
|
+
const dot = document.querySelector('.status-dot');
|
|
54
|
+
const text = document.querySelector('.status-text');
|
|
55
|
+
|
|
56
|
+
dot.className = `status-dot ${status}`;
|
|
57
|
+
text.textContent = status === 'connected' ? 'Live' :
|
|
58
|
+
status === 'disconnected' ? 'Reconnecting...' : 'Connecting...';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Main Render Function ─────────────────────────────
|
|
62
|
+
function render(data) {
|
|
63
|
+
const { analysis, explanation, suggestions, snapshot } = data;
|
|
64
|
+
|
|
65
|
+
updateTimestamp();
|
|
66
|
+
renderHealth(explanation.health, explanation.headline);
|
|
67
|
+
renderMetrics(analysis, snapshot);
|
|
68
|
+
renderExplanation(explanation.explanation);
|
|
69
|
+
renderCauseEffect(explanation.causeAndEffect);
|
|
70
|
+
renderProcesses(analysis.rawProcesses);
|
|
71
|
+
renderDevInsights(explanation.devInsights);
|
|
72
|
+
renderPatterns(explanation.patternNarrative);
|
|
73
|
+
renderSuggestions(suggestions);
|
|
74
|
+
updateSparklines(analysis);
|
|
75
|
+
updateDataPoints();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Timestamp ─────────────────────────────────────────
|
|
79
|
+
function updateTimestamp() {
|
|
80
|
+
$('last-update').textContent = new Date().toLocaleTimeString();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Health Banner ─────────────────────────────────────
|
|
84
|
+
function renderHealth(health, headline) {
|
|
85
|
+
const banner = $('health-banner');
|
|
86
|
+
banner.className = `health-banner ${health.status}`;
|
|
87
|
+
$('health-emoji').textContent = health.emoji;
|
|
88
|
+
$('health-label').textContent = health.label;
|
|
89
|
+
$('health-desc').textContent = health.description;
|
|
90
|
+
$('health-headline').textContent = headline;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Metrics Cards ─────────────────────────────────────
|
|
94
|
+
function renderMetrics(analysis, snapshot) {
|
|
95
|
+
const { cpu, memory, process: pa } = analysis;
|
|
96
|
+
|
|
97
|
+
// CPU
|
|
98
|
+
updateMetricCard('cpu', cpu.overall, '%', {
|
|
99
|
+
details: `User: ${cpu.details.user}% │ System: ${cpu.details.system}% │ Idle: ${cpu.details.idle}%`,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Memory
|
|
103
|
+
updateMetricCard('memory', memory.usedPercent, '%', {
|
|
104
|
+
details: `Used: ${memory.details.formatted.used} │ Free: ${memory.details.formatted.free} │ Total: ${memory.details.formatted.total}`,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Disk
|
|
108
|
+
if (snapshot?.disk?.filesystems?.length > 0) {
|
|
109
|
+
const rootFs = snapshot.disk.filesystems.find(f => f.mount === '/') || snapshot.disk.filesystems[0];
|
|
110
|
+
if (rootFs) {
|
|
111
|
+
updateMetricCard('disk', rootFs.usedPercent, '%', {
|
|
112
|
+
details: `Used: ${rootFs.formatted.used} │ Free: ${rootFs.formatted.available} │ Total: ${rootFs.formatted.size}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Processes
|
|
118
|
+
const procCard = $('process-value');
|
|
119
|
+
procCard.textContent = snapshot?.processes?.total || '--';
|
|
120
|
+
procCard.className = 'metric-value';
|
|
121
|
+
$('process-details').querySelector('span').textContent =
|
|
122
|
+
`Running: ${snapshot?.processes?.running || '--'} │ Sleeping: ${snapshot?.processes?.sleeping || '--'}`;
|
|
123
|
+
|
|
124
|
+
// Hog tags
|
|
125
|
+
const cpuHogsTag = $('cpu-hogs-tag');
|
|
126
|
+
const memHogsTag = $('mem-hogs-tag');
|
|
127
|
+
cpuHogsTag.textContent = `${pa.summary.cpuHogCount} CPU Hogs`;
|
|
128
|
+
cpuHogsTag.className = pa.summary.cpuHogCount > 0 ? 'sub-tag alert' : 'sub-tag';
|
|
129
|
+
memHogsTag.textContent = `${pa.summary.memHogCount} Mem Hogs`;
|
|
130
|
+
memHogsTag.className = pa.summary.memHogCount > 0 ? 'sub-tag alert' : 'sub-tag';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function updateMetricCard(id, value, suffix, opts) {
|
|
134
|
+
const valueEl = $(`${id}-value`);
|
|
135
|
+
const barEl = $(`${id}-bar`);
|
|
136
|
+
const detailsEl = $(`${id}-details`);
|
|
137
|
+
|
|
138
|
+
// Update value
|
|
139
|
+
valueEl.textContent = `${value}${suffix}`;
|
|
140
|
+
|
|
141
|
+
// Color class
|
|
142
|
+
let colorClass = 'green';
|
|
143
|
+
if (value >= 80) colorClass = 'red';
|
|
144
|
+
else if (value >= 60) colorClass = 'yellow';
|
|
145
|
+
else if (value >= 40) colorClass = 'orange';
|
|
146
|
+
valueEl.className = `metric-value ${colorClass}`;
|
|
147
|
+
|
|
148
|
+
// Bar
|
|
149
|
+
barEl.style.width = `${value}%`;
|
|
150
|
+
barEl.className = `metric-bar${value >= 80 ? ' critical' : value >= 60 ? ' warning' : ''}`;
|
|
151
|
+
|
|
152
|
+
// Details
|
|
153
|
+
if (opts.details && detailsEl) {
|
|
154
|
+
detailsEl.querySelector('span').textContent = opts.details;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── Sparklines ────────────────────────────────────────
|
|
159
|
+
function updateSparklines(analysis) {
|
|
160
|
+
cpuHistory.push(analysis.cpu.overall);
|
|
161
|
+
memHistory.push(analysis.memory.usedPercent);
|
|
162
|
+
|
|
163
|
+
if (cpuHistory.length > MAX_SPARKLINE) cpuHistory.shift();
|
|
164
|
+
if (memHistory.length > MAX_SPARKLINE) memHistory.shift();
|
|
165
|
+
|
|
166
|
+
renderSparkline('cpu-sparkline', cpuHistory);
|
|
167
|
+
renderSparkline('memory-sparkline', memHistory);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function renderSparkline(containerId, data) {
|
|
171
|
+
const container = $(containerId);
|
|
172
|
+
if (!container) return;
|
|
173
|
+
|
|
174
|
+
const max = Math.max(...data, 1);
|
|
175
|
+
|
|
176
|
+
container.innerHTML = data.map(v => {
|
|
177
|
+
const height = Math.max(2, (v / 100) * 28);
|
|
178
|
+
const color = v >= 80 ? 'var(--red)' : v >= 60 ? 'var(--yellow)' : v >= 40 ? 'var(--cyan)' : 'var(--green)';
|
|
179
|
+
return `<div class="sparkline-bar" style="height:${height}px; background:${color};"></div>`;
|
|
180
|
+
}).join('');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ─── Explanation ───────────────────────────────────────
|
|
184
|
+
function renderExplanation(text) {
|
|
185
|
+
if (!text) return;
|
|
186
|
+
const body = $('explanation-body');
|
|
187
|
+
const paragraphs = text.split('\n\n');
|
|
188
|
+
body.innerHTML = paragraphs.map(p => `<p>${escapeHtml(p)}</p>`).join('');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── Cause & Effect ───────────────────────────────────
|
|
192
|
+
function renderCauseEffect(chains) {
|
|
193
|
+
const panel = $('cause-panel');
|
|
194
|
+
const body = $('cause-body');
|
|
195
|
+
|
|
196
|
+
if (!chains || chains.length === 0) {
|
|
197
|
+
panel.style.display = 'none';
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
panel.style.display = '';
|
|
202
|
+
body.innerHTML = chains.map(chain => `
|
|
203
|
+
<div class="cause-chain">
|
|
204
|
+
<div class="cause-label cause">${chain.emoji} Cause</div>
|
|
205
|
+
<div class="cause-text">${escapeHtml(chain.cause)}</div>
|
|
206
|
+
<div class="cause-label effect">→ Effect</div>
|
|
207
|
+
<div class="cause-text">${escapeHtml(chain.effect)}</div>
|
|
208
|
+
<div class="cause-label consequence">→ Consequence</div>
|
|
209
|
+
<div class="cause-text">${escapeHtml(chain.consequence)}</div>
|
|
210
|
+
</div>
|
|
211
|
+
`).join('');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Processes Table ──────────────────────────────────
|
|
215
|
+
function renderProcesses(rawProcesses) {
|
|
216
|
+
if (!rawProcesses) return;
|
|
217
|
+
|
|
218
|
+
const procs = currentTab === 'cpu' ? rawProcesses.topByCpu : rawProcesses.topByMemory;
|
|
219
|
+
const table = $('process-table');
|
|
220
|
+
|
|
221
|
+
let html = `
|
|
222
|
+
<div class="process-row header">
|
|
223
|
+
<span>PID</span>
|
|
224
|
+
<span>Name</span>
|
|
225
|
+
<span>Category</span>
|
|
226
|
+
<span>CPU%</span>
|
|
227
|
+
<span>MEM%</span>
|
|
228
|
+
<span>Memory</span>
|
|
229
|
+
</div>
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
for (const proc of (procs || []).slice(0, 10)) {
|
|
233
|
+
const cpuColor = proc.cpu >= 30 ? 'color-red' : proc.cpu >= 15 ? 'color-yellow' : '';
|
|
234
|
+
const memColor = proc.mem >= 20 ? 'color-red' : proc.mem >= 10 ? 'color-yellow' : '';
|
|
235
|
+
|
|
236
|
+
html += `
|
|
237
|
+
<div class="process-row">
|
|
238
|
+
<span class="process-pid">${proc.pid}</span>
|
|
239
|
+
<span class="process-name" title="${escapeHtml(proc.command || proc.name)}">${escapeHtml(proc.name)}</span>
|
|
240
|
+
<span class="process-category">${proc.classification?.label || '❓ Other'}</span>
|
|
241
|
+
<span class="process-cpu ${cpuColor}">${proc.cpu}%</span>
|
|
242
|
+
<span class="process-mem ${memColor}">${proc.mem}%</span>
|
|
243
|
+
<span class="process-mem-size">${proc.memRssFormatted || ''}</span>
|
|
244
|
+
</div>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
table.innerHTML = html;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ─── Dev Insights ─────────────────────────────────────
|
|
252
|
+
function renderDevInsights(insights) {
|
|
253
|
+
const panel = $('dev-panel');
|
|
254
|
+
const body = $('dev-body');
|
|
255
|
+
|
|
256
|
+
if (!insights || insights.length === 0) {
|
|
257
|
+
panel.style.display = 'none';
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
panel.style.display = '';
|
|
262
|
+
body.innerHTML = insights.map(i => `
|
|
263
|
+
<div class="dev-insight">
|
|
264
|
+
<span class="dev-insight-emoji">${i.emoji}</span>
|
|
265
|
+
<span class="dev-insight-text">${escapeHtml(i.insight)}</span>
|
|
266
|
+
</div>
|
|
267
|
+
`).join('');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ─── Patterns ─────────────────────────────────────────
|
|
271
|
+
function renderPatterns(patterns) {
|
|
272
|
+
const panel = $('patterns-panel');
|
|
273
|
+
const body = $('patterns-body');
|
|
274
|
+
|
|
275
|
+
if (!patterns || patterns.length === 0) {
|
|
276
|
+
panel.style.display = 'none';
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
panel.style.display = '';
|
|
281
|
+
body.innerHTML = patterns.map(p => `
|
|
282
|
+
<div class="pattern-item">${escapeHtml(p)}</div>
|
|
283
|
+
`).join('');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ─── Suggestions ──────────────────────────────────────
|
|
287
|
+
function renderSuggestions(suggestions) {
|
|
288
|
+
const body = $('suggestions-body');
|
|
289
|
+
|
|
290
|
+
if (!suggestions || suggestions.length === 0) {
|
|
291
|
+
body.innerHTML = `
|
|
292
|
+
<div class="no-suggestions">
|
|
293
|
+
<span class="no-suggestions-emoji">✨</span>
|
|
294
|
+
No action needed — system is running well!
|
|
295
|
+
</div>
|
|
296
|
+
`;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
body.innerHTML = suggestions.map((s, i) => `
|
|
301
|
+
<div class="suggestion-item">
|
|
302
|
+
<div class="suggestion-header">
|
|
303
|
+
<span class="suggestion-emoji">${s.emoji}</span>
|
|
304
|
+
<span class="suggestion-action">${i + 1}. ${escapeHtml(s.action)}</span>
|
|
305
|
+
<span class="suggestion-impact ${s.impact}">${s.impact}</span>
|
|
306
|
+
</div>
|
|
307
|
+
<div class="suggestion-reason">${escapeHtml(s.reason)}</div>
|
|
308
|
+
<div class="suggestion-meta">
|
|
309
|
+
<span>Category: ${escapeHtml(s.category)}</span>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
`).join('');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ─── Data Points Counter ──────────────────────────────
|
|
316
|
+
function updateDataPoints() {
|
|
317
|
+
$('data-points').textContent = `${cpuHistory.length} data points`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ─── Tab Switching ────────────────────────────────────
|
|
321
|
+
function initTabs() {
|
|
322
|
+
const tabs = document.querySelectorAll('#process-tabs .tab');
|
|
323
|
+
tabs.forEach(tab => {
|
|
324
|
+
tab.addEventListener('click', () => {
|
|
325
|
+
tabs.forEach(t => t.classList.remove('active'));
|
|
326
|
+
tab.classList.add('active');
|
|
327
|
+
currentTab = tab.dataset.tab;
|
|
328
|
+
if (latestData) renderProcesses(latestData.analysis.rawProcesses);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ─── Utility ──────────────────────────────────────────
|
|
334
|
+
function escapeHtml(text) {
|
|
335
|
+
if (!text) return '';
|
|
336
|
+
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
337
|
+
return String(text).replace(/[&<>"']/g, m => map[m]);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ─── Init ─────────────────────────────────────────────
|
|
341
|
+
function init() {
|
|
342
|
+
initTabs();
|
|
343
|
+
connect();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Start when DOM is ready
|
|
347
|
+
if (document.readyState === 'loading') {
|
|
348
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
349
|
+
} else {
|
|
350
|
+
init();
|
|
351
|
+
}
|
|
352
|
+
})();
|
package/web/index.html
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>SystemLens — Intelligent System Explainer</title>
|
|
8
|
+
<meta name="description"
|
|
9
|
+
content="An intelligent system explainer that converts raw metrics into human-readable insights. Understand why your system is slow, not just what the numbers are.">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
|
+
<link
|
|
12
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap"
|
|
13
|
+
rel="stylesheet">
|
|
14
|
+
<link rel="stylesheet" href="style.css">
|
|
15
|
+
</head>
|
|
16
|
+
|
|
17
|
+
<body>
|
|
18
|
+
<!-- ─── Background Glow Effects ─── -->
|
|
19
|
+
<div class="bg-glow bg-glow-1"></div>
|
|
20
|
+
<div class="bg-glow bg-glow-2"></div>
|
|
21
|
+
<div class="bg-glow bg-glow-3"></div>
|
|
22
|
+
|
|
23
|
+
<!-- ─── Header ─── -->
|
|
24
|
+
<header class="header" id="header">
|
|
25
|
+
<div class="header-left">
|
|
26
|
+
<div class="logo">
|
|
27
|
+
<span class="logo-icon">🔬</span>
|
|
28
|
+
<h1 class="logo-text">System<span class="logo-accent">Lens</span></h1>
|
|
29
|
+
</div>
|
|
30
|
+
<span class="header-tagline">Intelligent System Explainer — CreatedBYNJ5.0</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="header-right">
|
|
33
|
+
<div class="connection-status" id="connection-status">
|
|
34
|
+
<span class="status-dot"></span>
|
|
35
|
+
<span class="status-text">Connecting...</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="last-update" id="last-update">--:--:--</div>
|
|
38
|
+
</div>
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
<!-- ─── Health Banner ─── -->
|
|
42
|
+
<section class="health-banner" id="health-banner">
|
|
43
|
+
<div class="health-content">
|
|
44
|
+
<span class="health-emoji" id="health-emoji">⏳</span>
|
|
45
|
+
<div class="health-text">
|
|
46
|
+
<h2 class="health-label" id="health-label">Analyzing System...</h2>
|
|
47
|
+
<p class="health-desc" id="health-desc">Collecting initial data. Please wait a moment.</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="health-headline" id="health-headline"></div>
|
|
51
|
+
</section>
|
|
52
|
+
|
|
53
|
+
<!-- ─── Main Grid ─── -->
|
|
54
|
+
<main class="dashboard">
|
|
55
|
+
<!-- ─── Metrics Cards ─── -->
|
|
56
|
+
<section class="metrics-row" id="metrics-section">
|
|
57
|
+
<!-- CPU Card -->
|
|
58
|
+
<div class="metric-card" id="cpu-card">
|
|
59
|
+
<div class="metric-header">
|
|
60
|
+
<span class="metric-icon">⚡</span>
|
|
61
|
+
<span class="metric-title">CPU Usage</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="metric-value" id="cpu-value">--%</div>
|
|
64
|
+
<div class="metric-bar-container">
|
|
65
|
+
<div class="metric-bar" id="cpu-bar" style="width: 0%"></div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="metric-details" id="cpu-details">
|
|
68
|
+
<span>User: --% │ System: --% │ Idle: --%</span>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="metric-sparkline" id="cpu-sparkline"></div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- Memory Card -->
|
|
74
|
+
<div class="metric-card" id="memory-card">
|
|
75
|
+
<div class="metric-header">
|
|
76
|
+
<span class="metric-icon">🧠</span>
|
|
77
|
+
<span class="metric-title">Memory</span>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="metric-value" id="memory-value">--%</div>
|
|
80
|
+
<div class="metric-bar-container">
|
|
81
|
+
<div class="metric-bar" id="memory-bar" style="width: 0%"></div>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="metric-details" id="memory-details">
|
|
84
|
+
<span>Used: -- │ Free: -- │ Total: --</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="metric-sparkline" id="memory-sparkline"></div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Disk Card -->
|
|
90
|
+
<div class="metric-card" id="disk-card">
|
|
91
|
+
<div class="metric-header">
|
|
92
|
+
<span class="metric-icon">💾</span>
|
|
93
|
+
<span class="metric-title">Disk</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="metric-value" id="disk-value">--%</div>
|
|
96
|
+
<div class="metric-bar-container">
|
|
97
|
+
<div class="metric-bar" id="disk-bar" style="width: 0%"></div>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="metric-details" id="disk-details">
|
|
100
|
+
<span>Used: -- │ Free: -- │ Total: --</span>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Processes Card -->
|
|
105
|
+
<div class="metric-card" id="process-card">
|
|
106
|
+
<div class="metric-header">
|
|
107
|
+
<span class="metric-icon">📊</span>
|
|
108
|
+
<span class="metric-title">Processes</span>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="metric-value" id="process-value">--</div>
|
|
111
|
+
<div class="metric-details" id="process-details">
|
|
112
|
+
<span>Running: -- │ Sleeping: --</span>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="metric-sub" id="process-sub">
|
|
115
|
+
<span class="sub-tag" id="cpu-hogs-tag">0 CPU Hogs</span>
|
|
116
|
+
<span class="sub-tag" id="mem-hogs-tag">0 Mem Hogs</span>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</section>
|
|
120
|
+
|
|
121
|
+
<!-- ─── Two-Column Layout ─── -->
|
|
122
|
+
<div class="two-columns">
|
|
123
|
+
<!-- ─── Left Column: Explanation ─── -->
|
|
124
|
+
<div class="column-left">
|
|
125
|
+
<!-- Explanation Panel -->
|
|
126
|
+
<section class="panel explanation-panel" id="explanation-panel">
|
|
127
|
+
<div class="panel-header">
|
|
128
|
+
<span class="panel-icon">💬</span>
|
|
129
|
+
<h3 class="panel-title">What's Happening</h3>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="panel-body" id="explanation-body">
|
|
132
|
+
<p class="placeholder-text">Waiting for analysis data...</p>
|
|
133
|
+
</div>
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<!-- Cause & Effect Panel -->
|
|
137
|
+
<section class="panel cause-panel" id="cause-panel" style="display: none;">
|
|
138
|
+
<div class="panel-header">
|
|
139
|
+
<span class="panel-icon">🔗</span>
|
|
140
|
+
<h3 class="panel-title">Cause & Effect</h3>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="panel-body" id="cause-body"></div>
|
|
143
|
+
</section>
|
|
144
|
+
|
|
145
|
+
<!-- Patterns Panel -->
|
|
146
|
+
<section class="panel patterns-panel" id="patterns-panel" style="display: none;">
|
|
147
|
+
<div class="panel-header">
|
|
148
|
+
<span class="panel-icon">📈</span>
|
|
149
|
+
<h3 class="panel-title">Trends & Patterns</h3>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="panel-body" id="patterns-body"></div>
|
|
152
|
+
</section>
|
|
153
|
+
|
|
154
|
+
<!-- Dev Insights Panel -->
|
|
155
|
+
<section class="panel dev-panel" id="dev-panel" style="display: none;">
|
|
156
|
+
<div class="panel-header">
|
|
157
|
+
<span class="panel-icon">🛠️</span>
|
|
158
|
+
<h3 class="panel-title">Developer Insights</h3>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="panel-body" id="dev-body"></div>
|
|
161
|
+
</section>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<!-- ─── Right Column: Processes + Suggestions ─── -->
|
|
165
|
+
<div class="column-right">
|
|
166
|
+
<!-- Processes Table -->
|
|
167
|
+
<section class="panel processes-panel" id="processes-panel">
|
|
168
|
+
<div class="panel-header">
|
|
169
|
+
<span class="panel-icon">🔍</span>
|
|
170
|
+
<h3 class="panel-title">Top Processes</h3>
|
|
171
|
+
<div class="panel-tabs" id="process-tabs">
|
|
172
|
+
<button class="tab active" data-tab="cpu">By CPU</button>
|
|
173
|
+
<button class="tab" data-tab="memory">By Memory</button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="panel-body">
|
|
177
|
+
<div class="process-table" id="process-table">
|
|
178
|
+
<div class="table-placeholder">Loading processes...</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</section>
|
|
182
|
+
|
|
183
|
+
<!-- Suggestions Panel -->
|
|
184
|
+
<section class="panel suggestions-panel" id="suggestions-panel">
|
|
185
|
+
<div class="panel-header">
|
|
186
|
+
<span class="panel-icon">✅</span>
|
|
187
|
+
<h3 class="panel-title">Suggested Actions</h3>
|
|
188
|
+
</div>
|
|
189
|
+
<div class="panel-body" id="suggestions-body">
|
|
190
|
+
<p class="placeholder-text">Waiting for analysis data...</p>
|
|
191
|
+
</div>
|
|
192
|
+
</section>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</main>
|
|
196
|
+
|
|
197
|
+
<!-- ─── Footer ─── -->
|
|
198
|
+
<footer class="footer">
|
|
199
|
+
<span>🔬 SystemLens — CreatedBYNJ5.0</span>
|
|
200
|
+
<span class="footer-sep">│</span>
|
|
201
|
+
<span>Refreshes every 3s</span>
|
|
202
|
+
<span class="footer-sep">│</span>
|
|
203
|
+
<span id="data-points">0 data points</span>
|
|
204
|
+
</footer>
|
|
205
|
+
|
|
206
|
+
<script src="app.js"></script>
|
|
207
|
+
</body>
|
|
208
|
+
|
|
209
|
+
</html>
|