@ceon-oy/monitor-sdk 1.2.0 → 1.3.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/dist/index.d.mts +62 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +242 -17
- package/dist/index.mjs +242 -17
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -53,7 +53,7 @@ interface MonitorClientConfig {
|
|
|
53
53
|
allowInsecureHttp?: boolean;
|
|
54
54
|
/** Custom npm registry URL for fetching latest versions (default: https://registry.npmjs.org) */
|
|
55
55
|
npmRegistryUrl?: string;
|
|
56
|
-
/** Enable SDK-based health check polling (default:
|
|
56
|
+
/** Enable SDK-based health check polling (default: true) */
|
|
57
57
|
healthCheckEnabled?: boolean;
|
|
58
58
|
/** Interval to fetch health endpoints from server in ms (default: 60000) */
|
|
59
59
|
healthCheckFetchIntervalMs?: number;
|
|
@@ -225,6 +225,12 @@ declare class MonitorClient {
|
|
|
225
225
|
private healthCheckFetchTimer;
|
|
226
226
|
private healthCheckTimers;
|
|
227
227
|
private healthCheckResultsQueue;
|
|
228
|
+
private healthCheckFlushTimer;
|
|
229
|
+
private sdkErrorQueue;
|
|
230
|
+
private sdkErrorFlushTimer;
|
|
231
|
+
private sdkErrorsInCurrentWindow;
|
|
232
|
+
private sdkErrorWindowResetTimer;
|
|
233
|
+
private metricsCollectionTimer;
|
|
228
234
|
constructor(config: MonitorClientConfig);
|
|
229
235
|
/**
|
|
230
236
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -252,6 +258,15 @@ declare class MonitorClient {
|
|
|
252
258
|
flush(): Promise<void>;
|
|
253
259
|
private getErrorKey;
|
|
254
260
|
close(): Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* Queue an SDK error to be reported to the server's system errors page.
|
|
263
|
+
* Fire-and-forget — never throws. If reporting itself fails, logs to console only.
|
|
264
|
+
* Throttled to max 20 errors per minute to prevent flooding.
|
|
265
|
+
*/
|
|
266
|
+
private reportError;
|
|
267
|
+
private startSdkErrorFlushTimer;
|
|
268
|
+
private stopSdkErrorFlushTimer;
|
|
269
|
+
private flushSdkErrors;
|
|
255
270
|
private stopAuditIntervalTimer;
|
|
256
271
|
/**
|
|
257
272
|
* Fetch project settings from the monitoring server.
|
|
@@ -262,6 +277,12 @@ declare class MonitorClient {
|
|
|
262
277
|
vulnerabilityScanIntervalHours: number;
|
|
263
278
|
scanRequestedAt: string | null;
|
|
264
279
|
techScanRequestedAt: string | null;
|
|
280
|
+
metricsEnabled?: boolean;
|
|
281
|
+
metricsIntervalSeconds?: number;
|
|
282
|
+
metricsDiskPaths?: string[];
|
|
283
|
+
metricsCpuThreshold?: number;
|
|
284
|
+
metricsRamThreshold?: number;
|
|
285
|
+
metricsDiskThreshold?: number;
|
|
265
286
|
} | null>;
|
|
266
287
|
/**
|
|
267
288
|
* Setup automatic vulnerability scanning based on server-configured interval.
|
|
@@ -464,6 +485,46 @@ declare class MonitorClient {
|
|
|
464
485
|
* Stop all health check timers
|
|
465
486
|
*/
|
|
466
487
|
private stopHealthCheckTimers;
|
|
488
|
+
/**
|
|
489
|
+
* Start system metrics collection based on server-configured settings.
|
|
490
|
+
* Only runs if metricsEnabled is true in project settings.
|
|
491
|
+
* Collects CPU, RAM, and disk usage using built-in Node.js modules.
|
|
492
|
+
*/
|
|
493
|
+
setupSystemMetricsCollection(): Promise<void>;
|
|
494
|
+
private stopMetricsCollection;
|
|
495
|
+
/**
|
|
496
|
+
* Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
|
|
497
|
+
* and submit to the monitoring server.
|
|
498
|
+
*
|
|
499
|
+
* Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
|
|
500
|
+
* Zero dependencies — all built-in Node.js 18.15+
|
|
501
|
+
*/
|
|
502
|
+
private collectAndSubmitMetrics;
|
|
503
|
+
/**
|
|
504
|
+
* Measure CPU utilization by sampling cpus() twice with a 500ms gap.
|
|
505
|
+
* Returns a percentage (0–100).
|
|
506
|
+
*/
|
|
507
|
+
private measureCpuPercent;
|
|
508
|
+
/**
|
|
509
|
+
* Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
|
|
510
|
+
*/
|
|
511
|
+
private collectDiskMetrics;
|
|
512
|
+
/**
|
|
513
|
+
* Submit a system metric snapshot to the monitoring server.
|
|
514
|
+
*/
|
|
515
|
+
submitSystemMetric(metric: {
|
|
516
|
+
hostname: string;
|
|
517
|
+
cpuPercent: number;
|
|
518
|
+
memoryTotal: number;
|
|
519
|
+
memoryUsed: number;
|
|
520
|
+
memoryPercent: number;
|
|
521
|
+
disks: Array<{
|
|
522
|
+
path: string;
|
|
523
|
+
total: number;
|
|
524
|
+
used: number;
|
|
525
|
+
percent: number;
|
|
526
|
+
}>;
|
|
527
|
+
}): Promise<void>;
|
|
467
528
|
}
|
|
468
529
|
|
|
469
530
|
export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, type HealthStatus, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SdkHealthEndpoint, type SdkHealthEndpointsResponse, type SdkHealthResult, type SdkHealthResultsResponse, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
|
package/dist/index.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ interface MonitorClientConfig {
|
|
|
53
53
|
allowInsecureHttp?: boolean;
|
|
54
54
|
/** Custom npm registry URL for fetching latest versions (default: https://registry.npmjs.org) */
|
|
55
55
|
npmRegistryUrl?: string;
|
|
56
|
-
/** Enable SDK-based health check polling (default:
|
|
56
|
+
/** Enable SDK-based health check polling (default: true) */
|
|
57
57
|
healthCheckEnabled?: boolean;
|
|
58
58
|
/** Interval to fetch health endpoints from server in ms (default: 60000) */
|
|
59
59
|
healthCheckFetchIntervalMs?: number;
|
|
@@ -225,6 +225,12 @@ declare class MonitorClient {
|
|
|
225
225
|
private healthCheckFetchTimer;
|
|
226
226
|
private healthCheckTimers;
|
|
227
227
|
private healthCheckResultsQueue;
|
|
228
|
+
private healthCheckFlushTimer;
|
|
229
|
+
private sdkErrorQueue;
|
|
230
|
+
private sdkErrorFlushTimer;
|
|
231
|
+
private sdkErrorsInCurrentWindow;
|
|
232
|
+
private sdkErrorWindowResetTimer;
|
|
233
|
+
private metricsCollectionTimer;
|
|
228
234
|
constructor(config: MonitorClientConfig);
|
|
229
235
|
/**
|
|
230
236
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -252,6 +258,15 @@ declare class MonitorClient {
|
|
|
252
258
|
flush(): Promise<void>;
|
|
253
259
|
private getErrorKey;
|
|
254
260
|
close(): Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* Queue an SDK error to be reported to the server's system errors page.
|
|
263
|
+
* Fire-and-forget — never throws. If reporting itself fails, logs to console only.
|
|
264
|
+
* Throttled to max 20 errors per minute to prevent flooding.
|
|
265
|
+
*/
|
|
266
|
+
private reportError;
|
|
267
|
+
private startSdkErrorFlushTimer;
|
|
268
|
+
private stopSdkErrorFlushTimer;
|
|
269
|
+
private flushSdkErrors;
|
|
255
270
|
private stopAuditIntervalTimer;
|
|
256
271
|
/**
|
|
257
272
|
* Fetch project settings from the monitoring server.
|
|
@@ -262,6 +277,12 @@ declare class MonitorClient {
|
|
|
262
277
|
vulnerabilityScanIntervalHours: number;
|
|
263
278
|
scanRequestedAt: string | null;
|
|
264
279
|
techScanRequestedAt: string | null;
|
|
280
|
+
metricsEnabled?: boolean;
|
|
281
|
+
metricsIntervalSeconds?: number;
|
|
282
|
+
metricsDiskPaths?: string[];
|
|
283
|
+
metricsCpuThreshold?: number;
|
|
284
|
+
metricsRamThreshold?: number;
|
|
285
|
+
metricsDiskThreshold?: number;
|
|
265
286
|
} | null>;
|
|
266
287
|
/**
|
|
267
288
|
* Setup automatic vulnerability scanning based on server-configured interval.
|
|
@@ -464,6 +485,46 @@ declare class MonitorClient {
|
|
|
464
485
|
* Stop all health check timers
|
|
465
486
|
*/
|
|
466
487
|
private stopHealthCheckTimers;
|
|
488
|
+
/**
|
|
489
|
+
* Start system metrics collection based on server-configured settings.
|
|
490
|
+
* Only runs if metricsEnabled is true in project settings.
|
|
491
|
+
* Collects CPU, RAM, and disk usage using built-in Node.js modules.
|
|
492
|
+
*/
|
|
493
|
+
setupSystemMetricsCollection(): Promise<void>;
|
|
494
|
+
private stopMetricsCollection;
|
|
495
|
+
/**
|
|
496
|
+
* Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
|
|
497
|
+
* and submit to the monitoring server.
|
|
498
|
+
*
|
|
499
|
+
* Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
|
|
500
|
+
* Zero dependencies — all built-in Node.js 18.15+
|
|
501
|
+
*/
|
|
502
|
+
private collectAndSubmitMetrics;
|
|
503
|
+
/**
|
|
504
|
+
* Measure CPU utilization by sampling cpus() twice with a 500ms gap.
|
|
505
|
+
* Returns a percentage (0–100).
|
|
506
|
+
*/
|
|
507
|
+
private measureCpuPercent;
|
|
508
|
+
/**
|
|
509
|
+
* Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
|
|
510
|
+
*/
|
|
511
|
+
private collectDiskMetrics;
|
|
512
|
+
/**
|
|
513
|
+
* Submit a system metric snapshot to the monitoring server.
|
|
514
|
+
*/
|
|
515
|
+
submitSystemMetric(metric: {
|
|
516
|
+
hostname: string;
|
|
517
|
+
cpuPercent: number;
|
|
518
|
+
memoryTotal: number;
|
|
519
|
+
memoryUsed: number;
|
|
520
|
+
memoryPercent: number;
|
|
521
|
+
disks: Array<{
|
|
522
|
+
path: string;
|
|
523
|
+
total: number;
|
|
524
|
+
used: number;
|
|
525
|
+
percent: number;
|
|
526
|
+
}>;
|
|
527
|
+
}): Promise<void>;
|
|
467
528
|
}
|
|
468
529
|
|
|
469
530
|
export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, type HealthStatus, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SdkHealthEndpoint, type SdkHealthEndpointsResponse, type SdkHealthResult, type SdkHealthResultsResponse, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
|
package/dist/index.js
CHANGED
|
@@ -73,8 +73,10 @@ var CONFIG_LIMITS = {
|
|
|
73
73
|
// 60 seconds (default)
|
|
74
74
|
HEALTH_CHECK_MIN_INTERVAL_MS: 1e4,
|
|
75
75
|
// 10 seconds (min endpoint interval)
|
|
76
|
-
HEALTH_CHECK_MAX_BATCH_SIZE: 50
|
|
76
|
+
HEALTH_CHECK_MAX_BATCH_SIZE: 50,
|
|
77
77
|
// Max results per batch
|
|
78
|
+
HEALTH_CHECK_FLUSH_INTERVAL_MS: 3e4
|
|
79
|
+
// Flush health results every 30 seconds
|
|
78
80
|
};
|
|
79
81
|
var MonitorClient = class {
|
|
80
82
|
constructor(config) {
|
|
@@ -91,6 +93,14 @@ var MonitorClient = class {
|
|
|
91
93
|
this.healthCheckFetchTimer = null;
|
|
92
94
|
this.healthCheckTimers = /* @__PURE__ */ new Map();
|
|
93
95
|
this.healthCheckResultsQueue = [];
|
|
96
|
+
this.healthCheckFlushTimer = null;
|
|
97
|
+
// SDK error reporting queue (errors visible on admin system errors page)
|
|
98
|
+
this.sdkErrorQueue = [];
|
|
99
|
+
this.sdkErrorFlushTimer = null;
|
|
100
|
+
this.sdkErrorsInCurrentWindow = 0;
|
|
101
|
+
this.sdkErrorWindowResetTimer = null;
|
|
102
|
+
// System metrics collection (on-premise only, opt-in)
|
|
103
|
+
this.metricsCollectionTimer = null;
|
|
94
104
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
95
105
|
throw new Error("[MonitorClient] API key is required");
|
|
96
106
|
}
|
|
@@ -150,24 +160,28 @@ var MonitorClient = class {
|
|
|
150
160
|
this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
|
|
151
161
|
this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
|
|
152
162
|
this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
|
|
153
|
-
this.healthCheckEnabled = config.healthCheckEnabled
|
|
163
|
+
this.healthCheckEnabled = config.healthCheckEnabled ?? true;
|
|
154
164
|
this.healthCheckFetchIntervalMs = config.healthCheckFetchIntervalMs || CONFIG_LIMITS.HEALTH_CHECK_FETCH_INTERVAL_MS;
|
|
155
165
|
this.startFlushTimer();
|
|
156
166
|
if (this.trackDependencies) {
|
|
157
167
|
this.syncDependencies().catch((err) => {
|
|
158
|
-
|
|
168
|
+
this.reportError("DEPENDENCY_SYNC", "Failed to sync dependencies on startup", err);
|
|
159
169
|
});
|
|
160
170
|
}
|
|
161
171
|
if (this.autoAudit) {
|
|
162
172
|
this.setupAutoAudit().catch((err) => {
|
|
163
|
-
|
|
173
|
+
this.reportError("AUDIT_SETUP", "Failed to setup auto audit", err);
|
|
164
174
|
});
|
|
165
175
|
}
|
|
166
176
|
if (this.healthCheckEnabled) {
|
|
167
177
|
this.setupHealthCheckPolling().catch((err) => {
|
|
168
|
-
|
|
178
|
+
this.reportError("HEALTH_POLLING_SETUP", "Failed to setup health check polling", err);
|
|
169
179
|
});
|
|
170
180
|
}
|
|
181
|
+
this.startSdkErrorFlushTimer();
|
|
182
|
+
this.setupSystemMetricsCollection().catch((err) => {
|
|
183
|
+
this.reportError("SYSTEM_METRICS_SETUP", "Failed to setup system metrics collection", err);
|
|
184
|
+
});
|
|
171
185
|
}
|
|
172
186
|
/**
|
|
173
187
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -317,8 +331,66 @@ var MonitorClient = class {
|
|
|
317
331
|
this.stopFlushTimer();
|
|
318
332
|
this.stopAuditIntervalTimer();
|
|
319
333
|
this.stopHealthCheckTimers();
|
|
334
|
+
this.stopSdkErrorFlushTimer();
|
|
335
|
+
this.stopMetricsCollection();
|
|
320
336
|
await this.flush();
|
|
321
337
|
await this.flushHealthResults();
|
|
338
|
+
await this.flushSdkErrors();
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Queue an SDK error to be reported to the server's system errors page.
|
|
342
|
+
* Fire-and-forget — never throws. If reporting itself fails, logs to console only.
|
|
343
|
+
* Throttled to max 20 errors per minute to prevent flooding.
|
|
344
|
+
*/
|
|
345
|
+
reportError(category, message, err) {
|
|
346
|
+
if (this.sdkErrorsInCurrentWindow >= 20) return;
|
|
347
|
+
this.sdkErrorsInCurrentWindow++;
|
|
348
|
+
const errorMessage = err instanceof Error ? `${message}: ${err.message}` : message;
|
|
349
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
350
|
+
this.sdkErrorQueue.push({ category, message: errorMessage, stack });
|
|
351
|
+
if (this.sdkErrorQueue.length >= 20) {
|
|
352
|
+
this.flushSdkErrors().catch(() => {
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
startSdkErrorFlushTimer() {
|
|
357
|
+
this.sdkErrorFlushTimer = setInterval(() => {
|
|
358
|
+
this.flushSdkErrors().catch(() => {
|
|
359
|
+
});
|
|
360
|
+
}, 3e4);
|
|
361
|
+
this.sdkErrorWindowResetTimer = setInterval(() => {
|
|
362
|
+
this.sdkErrorsInCurrentWindow = 0;
|
|
363
|
+
}, 6e4);
|
|
364
|
+
}
|
|
365
|
+
stopSdkErrorFlushTimer() {
|
|
366
|
+
if (this.sdkErrorFlushTimer) {
|
|
367
|
+
clearInterval(this.sdkErrorFlushTimer);
|
|
368
|
+
this.sdkErrorFlushTimer = null;
|
|
369
|
+
}
|
|
370
|
+
if (this.sdkErrorWindowResetTimer) {
|
|
371
|
+
clearInterval(this.sdkErrorWindowResetTimer);
|
|
372
|
+
this.sdkErrorWindowResetTimer = null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async flushSdkErrors() {
|
|
376
|
+
if (this.sdkErrorQueue.length === 0) return;
|
|
377
|
+
const errors = [...this.sdkErrorQueue];
|
|
378
|
+
this.sdkErrorQueue = [];
|
|
379
|
+
try {
|
|
380
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/sdk-errors`, {
|
|
381
|
+
method: "POST",
|
|
382
|
+
headers: {
|
|
383
|
+
"Content-Type": "application/json",
|
|
384
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
385
|
+
},
|
|
386
|
+
body: JSON.stringify({ errors })
|
|
387
|
+
});
|
|
388
|
+
if (!response.ok) {
|
|
389
|
+
console.warn(`[MonitorClient] Failed to report SDK errors: HTTP ${response.status}`);
|
|
390
|
+
}
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.warn("[MonitorClient] Failed to flush SDK errors to server:", err instanceof Error ? err.message : String(err));
|
|
393
|
+
}
|
|
322
394
|
}
|
|
323
395
|
stopAuditIntervalTimer() {
|
|
324
396
|
if (this.auditIntervalTimer) {
|
|
@@ -380,14 +452,14 @@ var MonitorClient = class {
|
|
|
380
452
|
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
381
453
|
this.auditIntervalTimer = setInterval(() => {
|
|
382
454
|
this.runScanAndTrackTime().catch((err) => {
|
|
383
|
-
|
|
455
|
+
this.reportError("AUDIT_SCAN", "Auto audit scan failed", err);
|
|
384
456
|
});
|
|
385
457
|
}, intervalMs);
|
|
386
458
|
}
|
|
387
459
|
console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
|
|
388
460
|
this.settingsPollingTimer = setInterval(() => {
|
|
389
461
|
this.checkForScanRequest().catch((err) => {
|
|
390
|
-
|
|
462
|
+
this.reportError("SETTINGS_POLL", "Scan request check failed", err);
|
|
391
463
|
});
|
|
392
464
|
}, CONFIG_LIMITS.SETTINGS_POLL_INTERVAL_MS);
|
|
393
465
|
}
|
|
@@ -408,7 +480,7 @@ var MonitorClient = class {
|
|
|
408
480
|
const duration = Date.now() - startTime;
|
|
409
481
|
console.log(`[MonitorClient] Vulnerability scan completed in ${duration}ms`);
|
|
410
482
|
} catch (err) {
|
|
411
|
-
|
|
483
|
+
this.reportError("VULNERABILITY_SCAN", "Vulnerability scan failed", err);
|
|
412
484
|
}
|
|
413
485
|
}
|
|
414
486
|
/**
|
|
@@ -435,7 +507,7 @@ var MonitorClient = class {
|
|
|
435
507
|
}
|
|
436
508
|
}
|
|
437
509
|
} catch (err) {
|
|
438
|
-
|
|
510
|
+
this.reportError("SETTINGS_POLL", "Failed to check for scan request", err);
|
|
439
511
|
}
|
|
440
512
|
}
|
|
441
513
|
enqueue(payload) {
|
|
@@ -522,7 +594,7 @@ var MonitorClient = class {
|
|
|
522
594
|
);
|
|
523
595
|
console.log("[MonitorClient] Technology sync completed successfully");
|
|
524
596
|
} catch (err) {
|
|
525
|
-
|
|
597
|
+
this.reportError("DEPENDENCY_SYNC", "Technology sync failed", err);
|
|
526
598
|
}
|
|
527
599
|
}
|
|
528
600
|
async performDependencySync(signal) {
|
|
@@ -1026,7 +1098,7 @@ var MonitorClient = class {
|
|
|
1026
1098
|
const result = await response.json();
|
|
1027
1099
|
return result.data;
|
|
1028
1100
|
} catch (err) {
|
|
1029
|
-
|
|
1101
|
+
this.reportError("VULNERABILITY_SCAN", "Failed to audit dependencies", err);
|
|
1030
1102
|
return null;
|
|
1031
1103
|
}
|
|
1032
1104
|
}
|
|
@@ -1056,7 +1128,7 @@ var MonitorClient = class {
|
|
|
1056
1128
|
}
|
|
1057
1129
|
return result;
|
|
1058
1130
|
} catch (err) {
|
|
1059
|
-
|
|
1131
|
+
this.reportError("VULNERABILITY_SCAN", "Multi-path audit failed", err);
|
|
1060
1132
|
return null;
|
|
1061
1133
|
}
|
|
1062
1134
|
}
|
|
@@ -1318,9 +1390,14 @@ var MonitorClient = class {
|
|
|
1318
1390
|
await this.fetchAndScheduleHealthChecks();
|
|
1319
1391
|
this.healthCheckFetchTimer = setInterval(() => {
|
|
1320
1392
|
this.fetchAndScheduleHealthChecks().catch((err) => {
|
|
1321
|
-
|
|
1393
|
+
this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch and schedule health checks", err);
|
|
1322
1394
|
});
|
|
1323
1395
|
}, this.healthCheckFetchIntervalMs);
|
|
1396
|
+
this.healthCheckFlushTimer = setInterval(() => {
|
|
1397
|
+
this.flushHealthResults().catch((err) => {
|
|
1398
|
+
this.reportError("HEALTH_RESULTS_FLUSH", "Failed to flush health results", err);
|
|
1399
|
+
});
|
|
1400
|
+
}, CONFIG_LIMITS.HEALTH_CHECK_FLUSH_INTERVAL_MS);
|
|
1324
1401
|
}
|
|
1325
1402
|
/**
|
|
1326
1403
|
* Fetch health endpoints and schedule individual checks
|
|
@@ -1339,7 +1416,7 @@ var MonitorClient = class {
|
|
|
1339
1416
|
this.scheduleHealthCheck(endpoint);
|
|
1340
1417
|
}
|
|
1341
1418
|
} catch (err) {
|
|
1342
|
-
|
|
1419
|
+
this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch health endpoints from server", err);
|
|
1343
1420
|
}
|
|
1344
1421
|
}
|
|
1345
1422
|
/**
|
|
@@ -1348,11 +1425,11 @@ var MonitorClient = class {
|
|
|
1348
1425
|
scheduleHealthCheck(endpoint) {
|
|
1349
1426
|
const intervalMs = Math.max(CONFIG_LIMITS.HEALTH_CHECK_MIN_INTERVAL_MS, endpoint.intervalMs);
|
|
1350
1427
|
this.runHealthCheck(endpoint).catch((err) => {
|
|
1351
|
-
|
|
1428
|
+
this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
|
|
1352
1429
|
});
|
|
1353
1430
|
const timer = setInterval(() => {
|
|
1354
1431
|
this.runHealthCheck(endpoint).catch((err) => {
|
|
1355
|
-
|
|
1432
|
+
this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
|
|
1356
1433
|
});
|
|
1357
1434
|
}, intervalMs);
|
|
1358
1435
|
this.healthCheckTimers.set(endpoint.id, timer);
|
|
@@ -1378,7 +1455,7 @@ var MonitorClient = class {
|
|
|
1378
1455
|
const response = await this.submitHealthResults(results);
|
|
1379
1456
|
console.log(`[MonitorClient] Submitted ${response.processed}/${response.total} health check results`);
|
|
1380
1457
|
} catch (err) {
|
|
1381
|
-
|
|
1458
|
+
this.reportError("HEALTH_RESULTS_FLUSH", "Failed to submit health check results to server", err);
|
|
1382
1459
|
if (this.healthCheckResultsQueue.length + results.length <= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
|
|
1383
1460
|
this.healthCheckResultsQueue.unshift(...results);
|
|
1384
1461
|
}
|
|
@@ -1392,11 +1469,159 @@ var MonitorClient = class {
|
|
|
1392
1469
|
clearInterval(this.healthCheckFetchTimer);
|
|
1393
1470
|
this.healthCheckFetchTimer = null;
|
|
1394
1471
|
}
|
|
1472
|
+
if (this.healthCheckFlushTimer) {
|
|
1473
|
+
clearInterval(this.healthCheckFlushTimer);
|
|
1474
|
+
this.healthCheckFlushTimer = null;
|
|
1475
|
+
}
|
|
1395
1476
|
for (const [endpointId, timer] of this.healthCheckTimers.entries()) {
|
|
1396
1477
|
clearInterval(timer);
|
|
1397
1478
|
this.healthCheckTimers.delete(endpointId);
|
|
1398
1479
|
}
|
|
1399
1480
|
}
|
|
1481
|
+
// ---- System Metrics (on-premise only) ----
|
|
1482
|
+
/**
|
|
1483
|
+
* Start system metrics collection based on server-configured settings.
|
|
1484
|
+
* Only runs if metricsEnabled is true in project settings.
|
|
1485
|
+
* Collects CPU, RAM, and disk usage using built-in Node.js modules.
|
|
1486
|
+
*/
|
|
1487
|
+
async setupSystemMetricsCollection() {
|
|
1488
|
+
const settings = await this.fetchProjectSettings();
|
|
1489
|
+
if (!settings?.metricsEnabled) {
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
const intervalMs = Math.max(3e4, (settings.metricsIntervalSeconds ?? 60) * 1e3);
|
|
1493
|
+
const diskPaths = settings.metricsDiskPaths ?? ["/"];
|
|
1494
|
+
console.log(`[MonitorClient] System metrics collection enabled (every ${intervalMs / 1e3}s, paths: ${diskPaths.join(", ")})`);
|
|
1495
|
+
this.collectAndSubmitMetrics(diskPaths).catch((err) => {
|
|
1496
|
+
this.reportError("SYSTEM_METRICS", "Initial system metrics collection failed", err);
|
|
1497
|
+
});
|
|
1498
|
+
this.metricsCollectionTimer = setInterval(() => {
|
|
1499
|
+
this.collectAndSubmitMetrics(diskPaths).catch((err) => {
|
|
1500
|
+
this.reportError("SYSTEM_METRICS", "Scheduled system metrics collection failed", err);
|
|
1501
|
+
});
|
|
1502
|
+
}, intervalMs);
|
|
1503
|
+
}
|
|
1504
|
+
stopMetricsCollection() {
|
|
1505
|
+
if (this.metricsCollectionTimer) {
|
|
1506
|
+
clearInterval(this.metricsCollectionTimer);
|
|
1507
|
+
this.metricsCollectionTimer = null;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
|
|
1512
|
+
* and submit to the monitoring server.
|
|
1513
|
+
*
|
|
1514
|
+
* Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
|
|
1515
|
+
* Zero dependencies — all built-in Node.js 18.15+
|
|
1516
|
+
*/
|
|
1517
|
+
async collectAndSubmitMetrics(diskPaths) {
|
|
1518
|
+
if (typeof window !== "undefined" || typeof document !== "undefined") {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
let os;
|
|
1522
|
+
let fs;
|
|
1523
|
+
try {
|
|
1524
|
+
const osModule = await import(
|
|
1525
|
+
/* webpackIgnore: true */
|
|
1526
|
+
"os"
|
|
1527
|
+
);
|
|
1528
|
+
const fsModule = await import(
|
|
1529
|
+
/* webpackIgnore: true */
|
|
1530
|
+
"fs/promises"
|
|
1531
|
+
);
|
|
1532
|
+
os = osModule;
|
|
1533
|
+
fs = fsModule;
|
|
1534
|
+
} catch {
|
|
1535
|
+
this.reportError("SYSTEM_METRICS", "System metrics require Node.js (not available in bundled/browser environments)");
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
try {
|
|
1539
|
+
const hostname = os.hostname();
|
|
1540
|
+
const cpuPercent = await this.measureCpuPercent(os);
|
|
1541
|
+
const memoryTotal = os.totalmem();
|
|
1542
|
+
const memoryFree = os.freemem();
|
|
1543
|
+
const memoryUsed = memoryTotal - memoryFree;
|
|
1544
|
+
const memoryPercent = memoryTotal > 0 ? memoryUsed / memoryTotal * 100 : 0;
|
|
1545
|
+
const disks = await this.collectDiskMetrics(diskPaths, fs);
|
|
1546
|
+
await this.submitSystemMetric({
|
|
1547
|
+
hostname,
|
|
1548
|
+
cpuPercent,
|
|
1549
|
+
memoryTotal,
|
|
1550
|
+
memoryUsed,
|
|
1551
|
+
memoryPercent,
|
|
1552
|
+
disks
|
|
1553
|
+
});
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
this.reportError("SYSTEM_METRICS", "Failed to collect system metrics", err);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Measure CPU utilization by sampling cpus() twice with a 500ms gap.
|
|
1560
|
+
* Returns a percentage (0–100).
|
|
1561
|
+
*/
|
|
1562
|
+
async measureCpuPercent(os) {
|
|
1563
|
+
const sample1 = os.cpus();
|
|
1564
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1565
|
+
const sample2 = os.cpus();
|
|
1566
|
+
let totalIdle = 0;
|
|
1567
|
+
let totalTick = 0;
|
|
1568
|
+
for (let i = 0; i < sample2.length; i++) {
|
|
1569
|
+
const prev = sample1[i];
|
|
1570
|
+
const curr = sample2[i];
|
|
1571
|
+
const prevTotal = Object.values(prev.times).reduce((a, b) => a + b, 0);
|
|
1572
|
+
const currTotal = Object.values(curr.times).reduce((a, b) => a + b, 0);
|
|
1573
|
+
totalTick += currTotal - prevTotal;
|
|
1574
|
+
totalIdle += curr.times.idle - prev.times.idle;
|
|
1575
|
+
}
|
|
1576
|
+
const idlePercent = totalTick > 0 ? totalIdle / totalTick * 100 : 0;
|
|
1577
|
+
return Math.max(0, Math.min(100, 100 - idlePercent));
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
|
|
1581
|
+
*/
|
|
1582
|
+
async collectDiskMetrics(paths, fs) {
|
|
1583
|
+
const results = [];
|
|
1584
|
+
for (const diskPath of paths) {
|
|
1585
|
+
try {
|
|
1586
|
+
const stat = await fs.statfs(diskPath);
|
|
1587
|
+
const total = stat.bsize * stat.blocks;
|
|
1588
|
+
const free = stat.bsize * stat.bavail;
|
|
1589
|
+
const used = total - free;
|
|
1590
|
+
const percent = total > 0 ? used / total * 100 : 0;
|
|
1591
|
+
results.push({
|
|
1592
|
+
path: diskPath,
|
|
1593
|
+
total: Math.round(total),
|
|
1594
|
+
used: Math.round(used),
|
|
1595
|
+
percent: Math.round(percent * 10) / 10
|
|
1596
|
+
// 1 decimal place
|
|
1597
|
+
});
|
|
1598
|
+
} catch {
|
|
1599
|
+
this.reportError("SYSTEM_METRICS", `Failed to get disk stats for path: ${diskPath}`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return results;
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Submit a system metric snapshot to the monitoring server.
|
|
1606
|
+
*/
|
|
1607
|
+
async submitSystemMetric(metric) {
|
|
1608
|
+
try {
|
|
1609
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/system-metrics`, {
|
|
1610
|
+
method: "POST",
|
|
1611
|
+
headers: {
|
|
1612
|
+
"Content-Type": "application/json",
|
|
1613
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1614
|
+
},
|
|
1615
|
+
body: JSON.stringify(metric)
|
|
1616
|
+
});
|
|
1617
|
+
if (!response.ok) {
|
|
1618
|
+
const errorText = await response.text().catch(() => "");
|
|
1619
|
+
throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
|
|
1620
|
+
}
|
|
1621
|
+
} catch (err) {
|
|
1622
|
+
this.reportError("SYSTEM_METRICS", "Failed to submit system metric to server", err);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1400
1625
|
};
|
|
1401
1626
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1402
1627
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -37,8 +37,10 @@ var CONFIG_LIMITS = {
|
|
|
37
37
|
// 60 seconds (default)
|
|
38
38
|
HEALTH_CHECK_MIN_INTERVAL_MS: 1e4,
|
|
39
39
|
// 10 seconds (min endpoint interval)
|
|
40
|
-
HEALTH_CHECK_MAX_BATCH_SIZE: 50
|
|
40
|
+
HEALTH_CHECK_MAX_BATCH_SIZE: 50,
|
|
41
41
|
// Max results per batch
|
|
42
|
+
HEALTH_CHECK_FLUSH_INTERVAL_MS: 3e4
|
|
43
|
+
// Flush health results every 30 seconds
|
|
42
44
|
};
|
|
43
45
|
var MonitorClient = class {
|
|
44
46
|
constructor(config) {
|
|
@@ -55,6 +57,14 @@ var MonitorClient = class {
|
|
|
55
57
|
this.healthCheckFetchTimer = null;
|
|
56
58
|
this.healthCheckTimers = /* @__PURE__ */ new Map();
|
|
57
59
|
this.healthCheckResultsQueue = [];
|
|
60
|
+
this.healthCheckFlushTimer = null;
|
|
61
|
+
// SDK error reporting queue (errors visible on admin system errors page)
|
|
62
|
+
this.sdkErrorQueue = [];
|
|
63
|
+
this.sdkErrorFlushTimer = null;
|
|
64
|
+
this.sdkErrorsInCurrentWindow = 0;
|
|
65
|
+
this.sdkErrorWindowResetTimer = null;
|
|
66
|
+
// System metrics collection (on-premise only, opt-in)
|
|
67
|
+
this.metricsCollectionTimer = null;
|
|
58
68
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
59
69
|
throw new Error("[MonitorClient] API key is required");
|
|
60
70
|
}
|
|
@@ -114,24 +124,28 @@ var MonitorClient = class {
|
|
|
114
124
|
this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
|
|
115
125
|
this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
|
|
116
126
|
this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
|
|
117
|
-
this.healthCheckEnabled = config.healthCheckEnabled
|
|
127
|
+
this.healthCheckEnabled = config.healthCheckEnabled ?? true;
|
|
118
128
|
this.healthCheckFetchIntervalMs = config.healthCheckFetchIntervalMs || CONFIG_LIMITS.HEALTH_CHECK_FETCH_INTERVAL_MS;
|
|
119
129
|
this.startFlushTimer();
|
|
120
130
|
if (this.trackDependencies) {
|
|
121
131
|
this.syncDependencies().catch((err) => {
|
|
122
|
-
|
|
132
|
+
this.reportError("DEPENDENCY_SYNC", "Failed to sync dependencies on startup", err);
|
|
123
133
|
});
|
|
124
134
|
}
|
|
125
135
|
if (this.autoAudit) {
|
|
126
136
|
this.setupAutoAudit().catch((err) => {
|
|
127
|
-
|
|
137
|
+
this.reportError("AUDIT_SETUP", "Failed to setup auto audit", err);
|
|
128
138
|
});
|
|
129
139
|
}
|
|
130
140
|
if (this.healthCheckEnabled) {
|
|
131
141
|
this.setupHealthCheckPolling().catch((err) => {
|
|
132
|
-
|
|
142
|
+
this.reportError("HEALTH_POLLING_SETUP", "Failed to setup health check polling", err);
|
|
133
143
|
});
|
|
134
144
|
}
|
|
145
|
+
this.startSdkErrorFlushTimer();
|
|
146
|
+
this.setupSystemMetricsCollection().catch((err) => {
|
|
147
|
+
this.reportError("SYSTEM_METRICS_SETUP", "Failed to setup system metrics collection", err);
|
|
148
|
+
});
|
|
135
149
|
}
|
|
136
150
|
/**
|
|
137
151
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -281,8 +295,66 @@ var MonitorClient = class {
|
|
|
281
295
|
this.stopFlushTimer();
|
|
282
296
|
this.stopAuditIntervalTimer();
|
|
283
297
|
this.stopHealthCheckTimers();
|
|
298
|
+
this.stopSdkErrorFlushTimer();
|
|
299
|
+
this.stopMetricsCollection();
|
|
284
300
|
await this.flush();
|
|
285
301
|
await this.flushHealthResults();
|
|
302
|
+
await this.flushSdkErrors();
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Queue an SDK error to be reported to the server's system errors page.
|
|
306
|
+
* Fire-and-forget — never throws. If reporting itself fails, logs to console only.
|
|
307
|
+
* Throttled to max 20 errors per minute to prevent flooding.
|
|
308
|
+
*/
|
|
309
|
+
reportError(category, message, err) {
|
|
310
|
+
if (this.sdkErrorsInCurrentWindow >= 20) return;
|
|
311
|
+
this.sdkErrorsInCurrentWindow++;
|
|
312
|
+
const errorMessage = err instanceof Error ? `${message}: ${err.message}` : message;
|
|
313
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
314
|
+
this.sdkErrorQueue.push({ category, message: errorMessage, stack });
|
|
315
|
+
if (this.sdkErrorQueue.length >= 20) {
|
|
316
|
+
this.flushSdkErrors().catch(() => {
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
startSdkErrorFlushTimer() {
|
|
321
|
+
this.sdkErrorFlushTimer = setInterval(() => {
|
|
322
|
+
this.flushSdkErrors().catch(() => {
|
|
323
|
+
});
|
|
324
|
+
}, 3e4);
|
|
325
|
+
this.sdkErrorWindowResetTimer = setInterval(() => {
|
|
326
|
+
this.sdkErrorsInCurrentWindow = 0;
|
|
327
|
+
}, 6e4);
|
|
328
|
+
}
|
|
329
|
+
stopSdkErrorFlushTimer() {
|
|
330
|
+
if (this.sdkErrorFlushTimer) {
|
|
331
|
+
clearInterval(this.sdkErrorFlushTimer);
|
|
332
|
+
this.sdkErrorFlushTimer = null;
|
|
333
|
+
}
|
|
334
|
+
if (this.sdkErrorWindowResetTimer) {
|
|
335
|
+
clearInterval(this.sdkErrorWindowResetTimer);
|
|
336
|
+
this.sdkErrorWindowResetTimer = null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async flushSdkErrors() {
|
|
340
|
+
if (this.sdkErrorQueue.length === 0) return;
|
|
341
|
+
const errors = [...this.sdkErrorQueue];
|
|
342
|
+
this.sdkErrorQueue = [];
|
|
343
|
+
try {
|
|
344
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/sdk-errors`, {
|
|
345
|
+
method: "POST",
|
|
346
|
+
headers: {
|
|
347
|
+
"Content-Type": "application/json",
|
|
348
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
349
|
+
},
|
|
350
|
+
body: JSON.stringify({ errors })
|
|
351
|
+
});
|
|
352
|
+
if (!response.ok) {
|
|
353
|
+
console.warn(`[MonitorClient] Failed to report SDK errors: HTTP ${response.status}`);
|
|
354
|
+
}
|
|
355
|
+
} catch (err) {
|
|
356
|
+
console.warn("[MonitorClient] Failed to flush SDK errors to server:", err instanceof Error ? err.message : String(err));
|
|
357
|
+
}
|
|
286
358
|
}
|
|
287
359
|
stopAuditIntervalTimer() {
|
|
288
360
|
if (this.auditIntervalTimer) {
|
|
@@ -344,14 +416,14 @@ var MonitorClient = class {
|
|
|
344
416
|
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
345
417
|
this.auditIntervalTimer = setInterval(() => {
|
|
346
418
|
this.runScanAndTrackTime().catch((err) => {
|
|
347
|
-
|
|
419
|
+
this.reportError("AUDIT_SCAN", "Auto audit scan failed", err);
|
|
348
420
|
});
|
|
349
421
|
}, intervalMs);
|
|
350
422
|
}
|
|
351
423
|
console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
|
|
352
424
|
this.settingsPollingTimer = setInterval(() => {
|
|
353
425
|
this.checkForScanRequest().catch((err) => {
|
|
354
|
-
|
|
426
|
+
this.reportError("SETTINGS_POLL", "Scan request check failed", err);
|
|
355
427
|
});
|
|
356
428
|
}, CONFIG_LIMITS.SETTINGS_POLL_INTERVAL_MS);
|
|
357
429
|
}
|
|
@@ -372,7 +444,7 @@ var MonitorClient = class {
|
|
|
372
444
|
const duration = Date.now() - startTime;
|
|
373
445
|
console.log(`[MonitorClient] Vulnerability scan completed in ${duration}ms`);
|
|
374
446
|
} catch (err) {
|
|
375
|
-
|
|
447
|
+
this.reportError("VULNERABILITY_SCAN", "Vulnerability scan failed", err);
|
|
376
448
|
}
|
|
377
449
|
}
|
|
378
450
|
/**
|
|
@@ -399,7 +471,7 @@ var MonitorClient = class {
|
|
|
399
471
|
}
|
|
400
472
|
}
|
|
401
473
|
} catch (err) {
|
|
402
|
-
|
|
474
|
+
this.reportError("SETTINGS_POLL", "Failed to check for scan request", err);
|
|
403
475
|
}
|
|
404
476
|
}
|
|
405
477
|
enqueue(payload) {
|
|
@@ -486,7 +558,7 @@ var MonitorClient = class {
|
|
|
486
558
|
);
|
|
487
559
|
console.log("[MonitorClient] Technology sync completed successfully");
|
|
488
560
|
} catch (err) {
|
|
489
|
-
|
|
561
|
+
this.reportError("DEPENDENCY_SYNC", "Technology sync failed", err);
|
|
490
562
|
}
|
|
491
563
|
}
|
|
492
564
|
async performDependencySync(signal) {
|
|
@@ -990,7 +1062,7 @@ var MonitorClient = class {
|
|
|
990
1062
|
const result = await response.json();
|
|
991
1063
|
return result.data;
|
|
992
1064
|
} catch (err) {
|
|
993
|
-
|
|
1065
|
+
this.reportError("VULNERABILITY_SCAN", "Failed to audit dependencies", err);
|
|
994
1066
|
return null;
|
|
995
1067
|
}
|
|
996
1068
|
}
|
|
@@ -1020,7 +1092,7 @@ var MonitorClient = class {
|
|
|
1020
1092
|
}
|
|
1021
1093
|
return result;
|
|
1022
1094
|
} catch (err) {
|
|
1023
|
-
|
|
1095
|
+
this.reportError("VULNERABILITY_SCAN", "Multi-path audit failed", err);
|
|
1024
1096
|
return null;
|
|
1025
1097
|
}
|
|
1026
1098
|
}
|
|
@@ -1282,9 +1354,14 @@ var MonitorClient = class {
|
|
|
1282
1354
|
await this.fetchAndScheduleHealthChecks();
|
|
1283
1355
|
this.healthCheckFetchTimer = setInterval(() => {
|
|
1284
1356
|
this.fetchAndScheduleHealthChecks().catch((err) => {
|
|
1285
|
-
|
|
1357
|
+
this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch and schedule health checks", err);
|
|
1286
1358
|
});
|
|
1287
1359
|
}, this.healthCheckFetchIntervalMs);
|
|
1360
|
+
this.healthCheckFlushTimer = setInterval(() => {
|
|
1361
|
+
this.flushHealthResults().catch((err) => {
|
|
1362
|
+
this.reportError("HEALTH_RESULTS_FLUSH", "Failed to flush health results", err);
|
|
1363
|
+
});
|
|
1364
|
+
}, CONFIG_LIMITS.HEALTH_CHECK_FLUSH_INTERVAL_MS);
|
|
1288
1365
|
}
|
|
1289
1366
|
/**
|
|
1290
1367
|
* Fetch health endpoints and schedule individual checks
|
|
@@ -1303,7 +1380,7 @@ var MonitorClient = class {
|
|
|
1303
1380
|
this.scheduleHealthCheck(endpoint);
|
|
1304
1381
|
}
|
|
1305
1382
|
} catch (err) {
|
|
1306
|
-
|
|
1383
|
+
this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch health endpoints from server", err);
|
|
1307
1384
|
}
|
|
1308
1385
|
}
|
|
1309
1386
|
/**
|
|
@@ -1312,11 +1389,11 @@ var MonitorClient = class {
|
|
|
1312
1389
|
scheduleHealthCheck(endpoint) {
|
|
1313
1390
|
const intervalMs = Math.max(CONFIG_LIMITS.HEALTH_CHECK_MIN_INTERVAL_MS, endpoint.intervalMs);
|
|
1314
1391
|
this.runHealthCheck(endpoint).catch((err) => {
|
|
1315
|
-
|
|
1392
|
+
this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
|
|
1316
1393
|
});
|
|
1317
1394
|
const timer = setInterval(() => {
|
|
1318
1395
|
this.runHealthCheck(endpoint).catch((err) => {
|
|
1319
|
-
|
|
1396
|
+
this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
|
|
1320
1397
|
});
|
|
1321
1398
|
}, intervalMs);
|
|
1322
1399
|
this.healthCheckTimers.set(endpoint.id, timer);
|
|
@@ -1342,7 +1419,7 @@ var MonitorClient = class {
|
|
|
1342
1419
|
const response = await this.submitHealthResults(results);
|
|
1343
1420
|
console.log(`[MonitorClient] Submitted ${response.processed}/${response.total} health check results`);
|
|
1344
1421
|
} catch (err) {
|
|
1345
|
-
|
|
1422
|
+
this.reportError("HEALTH_RESULTS_FLUSH", "Failed to submit health check results to server", err);
|
|
1346
1423
|
if (this.healthCheckResultsQueue.length + results.length <= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
|
|
1347
1424
|
this.healthCheckResultsQueue.unshift(...results);
|
|
1348
1425
|
}
|
|
@@ -1356,11 +1433,159 @@ var MonitorClient = class {
|
|
|
1356
1433
|
clearInterval(this.healthCheckFetchTimer);
|
|
1357
1434
|
this.healthCheckFetchTimer = null;
|
|
1358
1435
|
}
|
|
1436
|
+
if (this.healthCheckFlushTimer) {
|
|
1437
|
+
clearInterval(this.healthCheckFlushTimer);
|
|
1438
|
+
this.healthCheckFlushTimer = null;
|
|
1439
|
+
}
|
|
1359
1440
|
for (const [endpointId, timer] of this.healthCheckTimers.entries()) {
|
|
1360
1441
|
clearInterval(timer);
|
|
1361
1442
|
this.healthCheckTimers.delete(endpointId);
|
|
1362
1443
|
}
|
|
1363
1444
|
}
|
|
1445
|
+
// ---- System Metrics (on-premise only) ----
|
|
1446
|
+
/**
|
|
1447
|
+
* Start system metrics collection based on server-configured settings.
|
|
1448
|
+
* Only runs if metricsEnabled is true in project settings.
|
|
1449
|
+
* Collects CPU, RAM, and disk usage using built-in Node.js modules.
|
|
1450
|
+
*/
|
|
1451
|
+
async setupSystemMetricsCollection() {
|
|
1452
|
+
const settings = await this.fetchProjectSettings();
|
|
1453
|
+
if (!settings?.metricsEnabled) {
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
const intervalMs = Math.max(3e4, (settings.metricsIntervalSeconds ?? 60) * 1e3);
|
|
1457
|
+
const diskPaths = settings.metricsDiskPaths ?? ["/"];
|
|
1458
|
+
console.log(`[MonitorClient] System metrics collection enabled (every ${intervalMs / 1e3}s, paths: ${diskPaths.join(", ")})`);
|
|
1459
|
+
this.collectAndSubmitMetrics(diskPaths).catch((err) => {
|
|
1460
|
+
this.reportError("SYSTEM_METRICS", "Initial system metrics collection failed", err);
|
|
1461
|
+
});
|
|
1462
|
+
this.metricsCollectionTimer = setInterval(() => {
|
|
1463
|
+
this.collectAndSubmitMetrics(diskPaths).catch((err) => {
|
|
1464
|
+
this.reportError("SYSTEM_METRICS", "Scheduled system metrics collection failed", err);
|
|
1465
|
+
});
|
|
1466
|
+
}, intervalMs);
|
|
1467
|
+
}
|
|
1468
|
+
stopMetricsCollection() {
|
|
1469
|
+
if (this.metricsCollectionTimer) {
|
|
1470
|
+
clearInterval(this.metricsCollectionTimer);
|
|
1471
|
+
this.metricsCollectionTimer = null;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
|
|
1476
|
+
* and submit to the monitoring server.
|
|
1477
|
+
*
|
|
1478
|
+
* Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
|
|
1479
|
+
* Zero dependencies — all built-in Node.js 18.15+
|
|
1480
|
+
*/
|
|
1481
|
+
async collectAndSubmitMetrics(diskPaths) {
|
|
1482
|
+
if (typeof window !== "undefined" || typeof document !== "undefined") {
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
let os;
|
|
1486
|
+
let fs;
|
|
1487
|
+
try {
|
|
1488
|
+
const osModule = await import(
|
|
1489
|
+
/* webpackIgnore: true */
|
|
1490
|
+
"os"
|
|
1491
|
+
);
|
|
1492
|
+
const fsModule = await import(
|
|
1493
|
+
/* webpackIgnore: true */
|
|
1494
|
+
"fs/promises"
|
|
1495
|
+
);
|
|
1496
|
+
os = osModule;
|
|
1497
|
+
fs = fsModule;
|
|
1498
|
+
} catch {
|
|
1499
|
+
this.reportError("SYSTEM_METRICS", "System metrics require Node.js (not available in bundled/browser environments)");
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
try {
|
|
1503
|
+
const hostname = os.hostname();
|
|
1504
|
+
const cpuPercent = await this.measureCpuPercent(os);
|
|
1505
|
+
const memoryTotal = os.totalmem();
|
|
1506
|
+
const memoryFree = os.freemem();
|
|
1507
|
+
const memoryUsed = memoryTotal - memoryFree;
|
|
1508
|
+
const memoryPercent = memoryTotal > 0 ? memoryUsed / memoryTotal * 100 : 0;
|
|
1509
|
+
const disks = await this.collectDiskMetrics(diskPaths, fs);
|
|
1510
|
+
await this.submitSystemMetric({
|
|
1511
|
+
hostname,
|
|
1512
|
+
cpuPercent,
|
|
1513
|
+
memoryTotal,
|
|
1514
|
+
memoryUsed,
|
|
1515
|
+
memoryPercent,
|
|
1516
|
+
disks
|
|
1517
|
+
});
|
|
1518
|
+
} catch (err) {
|
|
1519
|
+
this.reportError("SYSTEM_METRICS", "Failed to collect system metrics", err);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Measure CPU utilization by sampling cpus() twice with a 500ms gap.
|
|
1524
|
+
* Returns a percentage (0–100).
|
|
1525
|
+
*/
|
|
1526
|
+
async measureCpuPercent(os) {
|
|
1527
|
+
const sample1 = os.cpus();
|
|
1528
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1529
|
+
const sample2 = os.cpus();
|
|
1530
|
+
let totalIdle = 0;
|
|
1531
|
+
let totalTick = 0;
|
|
1532
|
+
for (let i = 0; i < sample2.length; i++) {
|
|
1533
|
+
const prev = sample1[i];
|
|
1534
|
+
const curr = sample2[i];
|
|
1535
|
+
const prevTotal = Object.values(prev.times).reduce((a, b) => a + b, 0);
|
|
1536
|
+
const currTotal = Object.values(curr.times).reduce((a, b) => a + b, 0);
|
|
1537
|
+
totalTick += currTotal - prevTotal;
|
|
1538
|
+
totalIdle += curr.times.idle - prev.times.idle;
|
|
1539
|
+
}
|
|
1540
|
+
const idlePercent = totalTick > 0 ? totalIdle / totalTick * 100 : 0;
|
|
1541
|
+
return Math.max(0, Math.min(100, 100 - idlePercent));
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
|
|
1545
|
+
*/
|
|
1546
|
+
async collectDiskMetrics(paths, fs) {
|
|
1547
|
+
const results = [];
|
|
1548
|
+
for (const diskPath of paths) {
|
|
1549
|
+
try {
|
|
1550
|
+
const stat = await fs.statfs(diskPath);
|
|
1551
|
+
const total = stat.bsize * stat.blocks;
|
|
1552
|
+
const free = stat.bsize * stat.bavail;
|
|
1553
|
+
const used = total - free;
|
|
1554
|
+
const percent = total > 0 ? used / total * 100 : 0;
|
|
1555
|
+
results.push({
|
|
1556
|
+
path: diskPath,
|
|
1557
|
+
total: Math.round(total),
|
|
1558
|
+
used: Math.round(used),
|
|
1559
|
+
percent: Math.round(percent * 10) / 10
|
|
1560
|
+
// 1 decimal place
|
|
1561
|
+
});
|
|
1562
|
+
} catch {
|
|
1563
|
+
this.reportError("SYSTEM_METRICS", `Failed to get disk stats for path: ${diskPath}`);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return results;
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Submit a system metric snapshot to the monitoring server.
|
|
1570
|
+
*/
|
|
1571
|
+
async submitSystemMetric(metric) {
|
|
1572
|
+
try {
|
|
1573
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/system-metrics`, {
|
|
1574
|
+
method: "POST",
|
|
1575
|
+
headers: {
|
|
1576
|
+
"Content-Type": "application/json",
|
|
1577
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1578
|
+
},
|
|
1579
|
+
body: JSON.stringify(metric)
|
|
1580
|
+
});
|
|
1581
|
+
if (!response.ok) {
|
|
1582
|
+
const errorText = await response.text().catch(() => "");
|
|
1583
|
+
throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
|
|
1584
|
+
}
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
this.reportError("SYSTEM_METRICS", "Failed to submit system metric to server", err);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1364
1589
|
};
|
|
1365
1590
|
export {
|
|
1366
1591
|
MonitorClient
|
package/package.json
CHANGED