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
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// ─── CLI Renderer ────────────────────────────────────────────────
|
|
2
|
+
// Beautiful terminal output for SystemLens
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import Table from 'cli-table3';
|
|
5
|
+
import { progressBar } from '../utils/helpers.js';
|
|
6
|
+
|
|
7
|
+
export class CliRenderer {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.width = process.stdout.columns || 80;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render the full system report
|
|
14
|
+
* @param {Object} report - { analysis, explanation, suggestions, snapshot }
|
|
15
|
+
*/
|
|
16
|
+
render(report) {
|
|
17
|
+
const { analysis, explanation, suggestions, snapshot, aiInsights } = report;
|
|
18
|
+
|
|
19
|
+
console.clear();
|
|
20
|
+
this._renderHeader();
|
|
21
|
+
this._renderHealthBanner(explanation.health);
|
|
22
|
+
this._renderHeadline(explanation.headline);
|
|
23
|
+
this._renderSystemSummary(analysis.cpu, analysis.memory, snapshot);
|
|
24
|
+
this._renderExplanation(explanation.explanation);
|
|
25
|
+
this._renderCauseEffect(explanation.causeAndEffect);
|
|
26
|
+
this._renderTopProcesses(analysis.rawProcesses?.topByCpu || []);
|
|
27
|
+
this._renderDevInsights(explanation.devInsights);
|
|
28
|
+
this._renderPatterns(explanation.patternNarrative);
|
|
29
|
+
this._renderAIInsights(aiInsights);
|
|
30
|
+
this._renderSuggestions(suggestions);
|
|
31
|
+
this._renderFooter(analysis.patterns);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Header ──────────────────────────────────────────────
|
|
35
|
+
_renderHeader() {
|
|
36
|
+
const title = chalk.bold.cyan(`
|
|
37
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
38
|
+
║ 🔬 S Y S T E M L E N S ║
|
|
39
|
+
║ Intelligent System Explainer ║
|
|
40
|
+
║ CreatedBYNJ5.0 ║
|
|
41
|
+
╚═══════════════════════════════════════════════════════════╝`);
|
|
42
|
+
console.log(title);
|
|
43
|
+
console.log();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Health Banner ───────────────────────────────────────
|
|
47
|
+
_renderHealthBanner(health) {
|
|
48
|
+
const colors = {
|
|
49
|
+
excellent: chalk.bgGreen.black.bold,
|
|
50
|
+
good: chalk.bgYellow.black.bold,
|
|
51
|
+
stressed: chalk.bgHex('#FF8C00').black.bold,
|
|
52
|
+
critical: chalk.bgRed.white.bold,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const colorFn = colors[health.status] || chalk.bgGray.white.bold;
|
|
56
|
+
const banner = colorFn(` ${health.emoji} SYSTEM STATUS: ${health.label.toUpperCase()} `);
|
|
57
|
+
console.log(` ${banner}`);
|
|
58
|
+
console.log(chalk.dim(` ${health.description}`));
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Headline ────────────────────────────────────────────
|
|
63
|
+
_renderHeadline(headline) {
|
|
64
|
+
console.log(chalk.bold.white(` ${headline}`));
|
|
65
|
+
console.log();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── System Summary ──────────────────────────────────────
|
|
69
|
+
_renderSystemSummary(cpu, memory, snapshot) {
|
|
70
|
+
console.log(chalk.bold.underline.cyan(' 📊 System Summary'));
|
|
71
|
+
console.log();
|
|
72
|
+
|
|
73
|
+
// CPU bar
|
|
74
|
+
const cpuColor = cpu.overall >= 80 ? chalk.red : cpu.overall >= 60 ? chalk.yellow : chalk.green;
|
|
75
|
+
const cpuBar = this._coloredBar(cpu.overall);
|
|
76
|
+
console.log(` CPU ${cpuBar} ${cpuColor.bold(`${cpu.overall}%`)}`);
|
|
77
|
+
console.log(chalk.dim(` User: ${cpu.details.user}% │ System: ${cpu.details.system}% │ Idle: ${cpu.details.idle}%`));
|
|
78
|
+
|
|
79
|
+
// Memory bar
|
|
80
|
+
const memPct = memory.usedPercent;
|
|
81
|
+
const memColor = memPct >= 80 ? chalk.red : memPct >= 60 ? chalk.yellow : chalk.green;
|
|
82
|
+
const memBar = this._coloredBar(memPct);
|
|
83
|
+
console.log(` Memory ${memBar} ${memColor.bold(`${memPct}%`)}`);
|
|
84
|
+
console.log(chalk.dim(` Used: ${memory.details.formatted.used} │ Free: ${memory.details.formatted.free} │ Total: ${memory.details.formatted.total}`));
|
|
85
|
+
|
|
86
|
+
// Disk (if available)
|
|
87
|
+
if (snapshot?.disk?.filesystems?.length > 0) {
|
|
88
|
+
const rootFs = snapshot.disk.filesystems.find(f => f.mount === '/') || snapshot.disk.filesystems[0];
|
|
89
|
+
if (rootFs) {
|
|
90
|
+
const diskBar = this._coloredBar(rootFs.usedPercent);
|
|
91
|
+
const diskColor = rootFs.usedPercent >= 90 ? chalk.red : rootFs.usedPercent >= 75 ? chalk.yellow : chalk.green;
|
|
92
|
+
console.log(` Disk ${diskBar} ${diskColor.bold(`${rootFs.usedPercent}%`)}`);
|
|
93
|
+
console.log(chalk.dim(` Used: ${rootFs.formatted.used} │ Free: ${rootFs.formatted.available} │ Total: ${rootFs.formatted.size} [${rootFs.mount}]`));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Temperature
|
|
98
|
+
if (cpu.details.temperature) {
|
|
99
|
+
const tempColor = cpu.details.temperature > 80 ? chalk.red : cpu.details.temperature > 60 ? chalk.yellow : chalk.green;
|
|
100
|
+
console.log(` Temp ${tempColor.bold(`${cpu.details.temperature}°C`)}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Main Explanation ────────────────────────────────────
|
|
107
|
+
_renderExplanation(explanation) {
|
|
108
|
+
if (!explanation) return;
|
|
109
|
+
|
|
110
|
+
console.log(chalk.bold.underline.cyan(' 💬 What\'s Happening'));
|
|
111
|
+
console.log();
|
|
112
|
+
|
|
113
|
+
const paragraphs = explanation.split('\n\n');
|
|
114
|
+
for (const para of paragraphs) {
|
|
115
|
+
// Word-wrap each paragraph
|
|
116
|
+
const wrapped = this._wordWrap(para, this.width - 6);
|
|
117
|
+
for (const line of wrapped) {
|
|
118
|
+
console.log(chalk.white(` ${line}`));
|
|
119
|
+
}
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── Cause & Effect ──────────────────────────────────────
|
|
125
|
+
_renderCauseEffect(chains) {
|
|
126
|
+
if (!chains || chains.length === 0) return;
|
|
127
|
+
|
|
128
|
+
console.log(chalk.bold.underline.cyan(' 🔗 Cause & Effect'));
|
|
129
|
+
console.log();
|
|
130
|
+
|
|
131
|
+
for (const chain of chains) {
|
|
132
|
+
console.log(chalk.bold(` ${chain.emoji} Cause: `) + chalk.white(chain.cause));
|
|
133
|
+
console.log(chalk.bold(` Effect: `) + chalk.yellow(chain.effect));
|
|
134
|
+
console.log(chalk.bold(` Consequence: `) + chalk.red(chain.consequence));
|
|
135
|
+
console.log();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Top Processes Table ─────────────────────────────────
|
|
140
|
+
_renderTopProcesses(processes) {
|
|
141
|
+
if (!processes || processes.length === 0) return;
|
|
142
|
+
|
|
143
|
+
// Filter out system & background processes — only show apps the user can actually close
|
|
144
|
+
const userProcesses = processes.filter(p => {
|
|
145
|
+
const cat = p.classification?.category;
|
|
146
|
+
return cat !== 'SYSTEM' && cat !== 'BACKGROUND';
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (userProcesses.length === 0) {
|
|
150
|
+
console.log(chalk.bold.underline.cyan(' 🔍 Apps Using Resources'));
|
|
151
|
+
console.log();
|
|
152
|
+
console.log(chalk.green(' All resource usage is from system processes — nothing for you to close.'));
|
|
153
|
+
console.log();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(chalk.bold.underline.cyan(' 🔍 Apps Using Resources'));
|
|
158
|
+
console.log(chalk.dim(' (System processes hidden — only showing apps you can close)'));
|
|
159
|
+
console.log();
|
|
160
|
+
|
|
161
|
+
const table = new Table({
|
|
162
|
+
head: [
|
|
163
|
+
chalk.cyan('PID'),
|
|
164
|
+
chalk.cyan('Name'),
|
|
165
|
+
chalk.cyan('Category'),
|
|
166
|
+
chalk.cyan('CPU %'),
|
|
167
|
+
chalk.cyan('Memory %'),
|
|
168
|
+
chalk.cyan('Memory'),
|
|
169
|
+
],
|
|
170
|
+
style: { head: [], border: ['dim'] },
|
|
171
|
+
colWidths: [8, 20, 16, 8, 10, 10],
|
|
172
|
+
chars: {
|
|
173
|
+
'top': '─', 'top-mid': '┬', 'top-left': ' ┌', 'top-right': '┐',
|
|
174
|
+
'bottom': '─', 'bottom-mid': '┴', 'bottom-left': ' └', 'bottom-right': '┘',
|
|
175
|
+
'left': ' │', 'left-mid': ' ├', 'mid': '─', 'mid-mid': '┼',
|
|
176
|
+
'right': '│', 'right-mid': '┤', 'middle': '│'
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
for (const proc of userProcesses.slice(0, 8)) {
|
|
181
|
+
const cpuColor = proc.cpu >= 30 ? chalk.red : proc.cpu >= 15 ? chalk.yellow : chalk.white;
|
|
182
|
+
const memColor = proc.mem >= 20 ? chalk.red : proc.mem >= 10 ? chalk.yellow : chalk.white;
|
|
183
|
+
|
|
184
|
+
table.push([
|
|
185
|
+
chalk.dim(proc.pid.toString()),
|
|
186
|
+
chalk.bold.white(proc.name.substring(0, 18)),
|
|
187
|
+
(proc.classification?.label || '❓ Other').substring(0, 14),
|
|
188
|
+
cpuColor(proc.cpu.toString()),
|
|
189
|
+
memColor(proc.mem.toString()),
|
|
190
|
+
chalk.dim(proc.memRssFormatted || ''),
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(table.toString());
|
|
195
|
+
console.log();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── Dev Insights ────────────────────────────────────────
|
|
199
|
+
_renderDevInsights(devInsights) {
|
|
200
|
+
if (!devInsights || devInsights.length === 0) return;
|
|
201
|
+
|
|
202
|
+
console.log(chalk.bold.underline.magenta(' 🛠️ Developer Insights'));
|
|
203
|
+
console.log();
|
|
204
|
+
|
|
205
|
+
for (const insight of devInsights) {
|
|
206
|
+
const prefix = insight.actionable ? chalk.yellow('!') : chalk.green('✓');
|
|
207
|
+
console.log(` ${insight.emoji} ${prefix} ${chalk.white(insight.insight)}`);
|
|
208
|
+
}
|
|
209
|
+
console.log();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ─── Pattern Narratives ──────────────────────────────────
|
|
213
|
+
_renderPatterns(patterns) {
|
|
214
|
+
if (!patterns || patterns.length === 0) return;
|
|
215
|
+
|
|
216
|
+
console.log(chalk.bold.underline.cyan(' 📈 Trends & Patterns'));
|
|
217
|
+
console.log();
|
|
218
|
+
|
|
219
|
+
for (const narrative of patterns) {
|
|
220
|
+
console.log(chalk.white(` ${narrative}`));
|
|
221
|
+
}
|
|
222
|
+
console.log();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── AI Insights ─────────────────────────────────────────
|
|
226
|
+
_renderAIInsights(aiInsights) {
|
|
227
|
+
if (!aiInsights) return;
|
|
228
|
+
|
|
229
|
+
console.log(chalk.bold.underline.hex('#8B5CF6')(' 🤖 AI Deep Insights'));
|
|
230
|
+
console.log();
|
|
231
|
+
|
|
232
|
+
if (aiInsights.deepInsight) {
|
|
233
|
+
console.log(chalk.hex('#A78BFA')(` 💡 ${aiInsights.deepInsight}`));
|
|
234
|
+
}
|
|
235
|
+
if (aiInsights.prediction) {
|
|
236
|
+
console.log(chalk.hex('#818CF8')(` 🔮 ${aiInsights.prediction}`));
|
|
237
|
+
}
|
|
238
|
+
if (aiInsights.proTip) {
|
|
239
|
+
console.log(chalk.hex('#6366F1')(` ⚡ ${aiInsights.proTip}`));
|
|
240
|
+
}
|
|
241
|
+
console.log();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─── Suggestions ─────────────────────────────────────────
|
|
245
|
+
_renderSuggestions(suggestions) {
|
|
246
|
+
if (!suggestions || suggestions.length === 0) {
|
|
247
|
+
console.log(chalk.bold.underline.green(' ✅ Suggested Actions'));
|
|
248
|
+
console.log();
|
|
249
|
+
console.log(chalk.green(' No action needed — system is running well!'));
|
|
250
|
+
console.log();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(chalk.bold.underline.green(' ✅ Suggested Actions'));
|
|
255
|
+
console.log();
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
258
|
+
const s = suggestions[i];
|
|
259
|
+
const impactColors = {
|
|
260
|
+
critical: chalk.red.bold,
|
|
261
|
+
high: chalk.yellow.bold,
|
|
262
|
+
medium: chalk.cyan,
|
|
263
|
+
low: chalk.dim,
|
|
264
|
+
};
|
|
265
|
+
const impactFn = impactColors[s.impact] || chalk.white;
|
|
266
|
+
|
|
267
|
+
console.log(` ${s.emoji} ${chalk.bold.white(`${i + 1}. ${s.action}`)}`);
|
|
268
|
+
console.log(chalk.dim(` ${s.reason}`));
|
|
269
|
+
console.log(` Impact: ${impactFn(s.impact.toUpperCase())} │ Category: ${chalk.cyan(s.category)}`);
|
|
270
|
+
console.log();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Footer ──────────────────────────────────────────────
|
|
275
|
+
_renderFooter(patterns) {
|
|
276
|
+
const time = new Date().toLocaleTimeString();
|
|
277
|
+
const dataPoints = patterns?.cpu?.recentAvg !== undefined
|
|
278
|
+
? chalk.dim(` │ Avg CPU: ${patterns.cpu.recentAvg}% │ Trend: ${patterns.cpu.trend}`)
|
|
279
|
+
: '';
|
|
280
|
+
|
|
281
|
+
console.log(chalk.dim(` ─────────────────────────────────────────────────────────────`));
|
|
282
|
+
console.log(chalk.dim(` 🕐 Last updated: ${time}${dataPoints}`));
|
|
283
|
+
console.log(chalk.dim(` Press Ctrl+C to exit │ Refreshes every 3 seconds │ CreatedBYNJ5.0`));
|
|
284
|
+
console.log();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─── Helpers ─────────────────────────────────────────────
|
|
288
|
+
_coloredBar(value, width = 25) {
|
|
289
|
+
const filled = Math.round((value / 100) * width);
|
|
290
|
+
const empty = width - filled;
|
|
291
|
+
|
|
292
|
+
let barColor;
|
|
293
|
+
if (value >= 80) barColor = chalk.red;
|
|
294
|
+
else if (value >= 60) barColor = chalk.yellow;
|
|
295
|
+
else if (value >= 40) barColor = chalk.cyan;
|
|
296
|
+
else barColor = chalk.green;
|
|
297
|
+
|
|
298
|
+
return barColor('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
_wordWrap(text, maxWidth) {
|
|
302
|
+
const words = text.split(' ');
|
|
303
|
+
const lines = [];
|
|
304
|
+
let currentLine = '';
|
|
305
|
+
|
|
306
|
+
for (const word of words) {
|
|
307
|
+
if ((currentLine + ' ' + word).trim().length > maxWidth) {
|
|
308
|
+
lines.push(currentLine.trim());
|
|
309
|
+
currentLine = word;
|
|
310
|
+
} else {
|
|
311
|
+
currentLine += ' ' + word;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (currentLine.trim()) lines.push(currentLine.trim());
|
|
315
|
+
|
|
316
|
+
return lines;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ─── SystemLens Constants ────────────────────────────────────────
|
|
2
|
+
// Thresholds, categories, and configuration for the analysis engine
|
|
3
|
+
|
|
4
|
+
export const THRESHOLDS = {
|
|
5
|
+
CPU_HIGH: 80,
|
|
6
|
+
CPU_WARNING: 60,
|
|
7
|
+
CPU_MODERATE: 40,
|
|
8
|
+
MEMORY_HIGH: 80,
|
|
9
|
+
MEMORY_WARNING: 60,
|
|
10
|
+
MEMORY_MODERATE: 40,
|
|
11
|
+
DISK_HIGH: 90,
|
|
12
|
+
DISK_WARNING: 75,
|
|
13
|
+
PROCESS_CPU_HIGH: 30,
|
|
14
|
+
PROCESS_CPU_WARNING: 15,
|
|
15
|
+
PROCESS_MEM_HIGH: 20,
|
|
16
|
+
PROCESS_MEM_WARNING: 10,
|
|
17
|
+
SPIKE_THRESHOLD: 20, // % jump considered a spike
|
|
18
|
+
SUSTAINED_DURATION: 60000, // ms — 60s of high usage = sustained
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const PROCESS_CATEGORIES = {
|
|
22
|
+
BROWSER: {
|
|
23
|
+
label: '🌐 Browser',
|
|
24
|
+
patterns: ['chrome', 'chromium', 'firefox', 'brave', 'safari', 'edge', 'opera', 'vivaldi'],
|
|
25
|
+
color: '#4285F4',
|
|
26
|
+
},
|
|
27
|
+
DEV_TOOLS: {
|
|
28
|
+
label: '🛠️ Development',
|
|
29
|
+
patterns: ['node', 'npm', 'npx', 'yarn', 'pnpm', 'code', 'electron', 'webpack', 'vite', 'tsc', 'tsx', 'esbuild', 'turbo', 'jest', 'vitest', 'cargo', 'rustc', 'go', 'python', 'python3', 'pip', 'java', 'javac', 'gradle', 'maven', 'docker', 'git'],
|
|
30
|
+
color: '#F7DF1E',
|
|
31
|
+
},
|
|
32
|
+
EDITORS: {
|
|
33
|
+
label: '📝 Editors/IDE',
|
|
34
|
+
patterns: ['code', 'cursor', 'windsurf', 'sublime', 'atom', 'vim', 'nvim', 'emacs', 'idea', 'webstorm', 'pycharm', 'android-studio'],
|
|
35
|
+
color: '#007ACC',
|
|
36
|
+
},
|
|
37
|
+
MEDIA: {
|
|
38
|
+
label: '🎵 Media',
|
|
39
|
+
patterns: ['spotify', 'vlc', 'mpv', 'ffmpeg', 'obs', 'audacity', 'gimp', 'blender', 'kdenlive'],
|
|
40
|
+
color: '#1DB954',
|
|
41
|
+
},
|
|
42
|
+
COMMUNICATION: {
|
|
43
|
+
label: '💬 Communication',
|
|
44
|
+
patterns: ['slack', 'discord', 'teams', 'zoom', 'telegram', 'signal', 'skype', 'thunderbird'],
|
|
45
|
+
color: '#7289DA',
|
|
46
|
+
},
|
|
47
|
+
SYSTEM: {
|
|
48
|
+
label: '⚙️ System',
|
|
49
|
+
patterns: ['systemd', 'init', 'kworker', 'ksoftirqd', 'kswapd', 'irq', 'migration', 'watchdog', 'dbus', 'polkit', 'NetworkManager', 'snapd', 'thermald', 'upowerd', 'udisks', 'accounts-daemon', 'gdm', 'lightdm', 'sddm', 'Xorg', 'Xwayland', 'gnome-shell', 'plasmashell', 'pipewire', 'pulseaudio', 'wireplumber', 'krunner', 'kactivitymanagerd', 'kwin_wayland', 'kwin_x11', 'kwin', 'kglobalaccel', 'kded', 'ksmserver', 'kscreen', 'ksystemstats', 'powerdevil', 'polkit-kde', 'xdg-desktop-portal', 'xdg-document-portal', 'xdg-permission-store', 'gsd-', 'gnome-keyring', 'at-spi', 'ibus', 'fcitx', 'gvfs', 'nautilus', 'dolphin', 'evolution-', 'goa-', 'mutter', 'marco', 'xfwm', 'xfce4-', 'cinnamon', 'budgie', 'exe'],
|
|
50
|
+
color: '#6C757D',
|
|
51
|
+
},
|
|
52
|
+
BACKGROUND: {
|
|
53
|
+
label: '🔄 Background',
|
|
54
|
+
patterns: ['cron', 'atd', 'rsyslog', 'journald', 'cupsd', 'avahi', 'colord', 'packagekitd', 'fwupd', 'tracker', 'baloo', 'zeitgeist', 'gvfsd', 'dconf', 'at-spi-bus', 'xdg-', 'evolution-data', 'gnome-calendar', 'update-notifier', 'unattended-upgrade', 'packagekit'],
|
|
55
|
+
color: '#ADB5BD',
|
|
56
|
+
},
|
|
57
|
+
UNKNOWN: {
|
|
58
|
+
label: '❓ Other',
|
|
59
|
+
patterns: [],
|
|
60
|
+
color: '#CED4DA',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const SEVERITY = {
|
|
65
|
+
CRITICAL: { label: 'CRITICAL', emoji: '🔴', color: 'red' },
|
|
66
|
+
WARNING: { label: 'WARNING', emoji: '🟡', color: 'yellow' },
|
|
67
|
+
INFO: { label: 'INFO', emoji: '🟢', color: 'green' },
|
|
68
|
+
OK: { label: 'OK', emoji: '✅', color: 'green' },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const MONITORING_INTERVAL = 3000; // ms between data collection cycles
|
|
72
|
+
export const HISTORY_MAX_ENTRIES = 200; // max snapshots in history
|
|
73
|
+
export const TOP_PROCESSES_COUNT = 10; // number of top processes to analyze
|
|
74
|
+
|
|
75
|
+
export const HEALTH_LABELS = {
|
|
76
|
+
EXCELLENT: { emoji: '🟢', label: 'Excellent', description: 'System is running smoothly' },
|
|
77
|
+
GOOD: { emoji: '🟡', label: 'Good', description: 'Minor load detected' },
|
|
78
|
+
STRESSED: { emoji: '🟠', label: 'Stressed', description: 'System under notable pressure' },
|
|
79
|
+
CRITICAL: { emoji: '🔴', label: 'Critical', description: 'System is struggling — action needed' },
|
|
80
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// ─── SystemLens Helpers ──────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format bytes into human-readable string
|
|
5
|
+
*/
|
|
6
|
+
export function formatBytes(bytes, decimals = 1) {
|
|
7
|
+
if (bytes === 0) return '0 B';
|
|
8
|
+
const k = 1024;
|
|
9
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
10
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
11
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format uptime seconds into human-readable duration
|
|
16
|
+
*/
|
|
17
|
+
export function formatUptime(seconds) {
|
|
18
|
+
const days = Math.floor(seconds / 86400);
|
|
19
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
20
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
21
|
+
|
|
22
|
+
const parts = [];
|
|
23
|
+
if (days > 0) parts.push(`${days}d`);
|
|
24
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
25
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
26
|
+
|
|
27
|
+
return parts.length > 0 ? parts.join(' ') : '< 1m';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Format percentage with color coding level
|
|
32
|
+
*/
|
|
33
|
+
export function classifyPercentage(value) {
|
|
34
|
+
if (value >= 80) return 'critical';
|
|
35
|
+
if (value >= 60) return 'warning';
|
|
36
|
+
if (value >= 40) return 'moderate';
|
|
37
|
+
return 'normal';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Calculate percentage
|
|
42
|
+
*/
|
|
43
|
+
export function percentage(part, total) {
|
|
44
|
+
if (total === 0) return 0;
|
|
45
|
+
return parseFloat(((part / total) * 100).toFixed(1));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Truncate string to max length with ellipsis
|
|
50
|
+
*/
|
|
51
|
+
export function truncate(str, maxLength = 30) {
|
|
52
|
+
if (!str) return '';
|
|
53
|
+
return str.length > maxLength ? str.substring(0, maxLength - 1) + '…' : str;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get current timestamp in ISO format
|
|
58
|
+
*/
|
|
59
|
+
export function timestamp() {
|
|
60
|
+
return new Date().toISOString();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sleep for ms milliseconds
|
|
65
|
+
*/
|
|
66
|
+
export function sleep(ms) {
|
|
67
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generate a progress bar string
|
|
72
|
+
*/
|
|
73
|
+
export function progressBar(value, maxValue = 100, width = 20) {
|
|
74
|
+
const ratio = Math.min(value / maxValue, 1);
|
|
75
|
+
const filled = Math.round(ratio * width);
|
|
76
|
+
const empty = width - filled;
|
|
77
|
+
|
|
78
|
+
let bar;
|
|
79
|
+
if (ratio >= 0.8) {
|
|
80
|
+
bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
81
|
+
} else if (ratio >= 0.6) {
|
|
82
|
+
bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
83
|
+
} else {
|
|
84
|
+
bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return bar;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Round to n decimal places
|
|
92
|
+
*/
|
|
93
|
+
export function round(value, decimals = 1) {
|
|
94
|
+
return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Deep clone an object (simple, non-circular)
|
|
99
|
+
*/
|
|
100
|
+
export function deepClone(obj) {
|
|
101
|
+
return JSON.parse(JSON.stringify(obj));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Calculate average of an array
|
|
106
|
+
*/
|
|
107
|
+
export function average(arr) {
|
|
108
|
+
if (!arr || arr.length === 0) return 0;
|
|
109
|
+
return arr.reduce((sum, val) => sum + val, 0) / arr.length;
|
|
110
|
+
}
|