lightman-agent 1.0.4 → 1.0.6
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/agent.config.template.json +30 -30
- package/package.json +52 -52
- package/public/assets/index-CcBNCz6h.css +1 -1
- package/public/assets/index-D9QHMG8k.js +1 -0
- package/public/assets/index-H-8HDl46.js +1 -1
- package/public/assets/index-YodeiCia.css +1 -0
- package/public/assets/index-legacy-DWtNM8y7.js +41 -0
- package/public/assets/museum-map-CwVDA2z1.svg +4182 -0
- package/public/assets/polyfills-legacy-DyVYWHbW.js +4 -0
- package/public/index.html +7 -2
- package/public/templates/custom08/elements/back-button.svg +20 -0
- package/public/templates/custom08/elements/base-map-background.svg +37 -0
- package/public/templates/custom08/elements/base-map.svg +1191 -0
- package/public/templates/custom08/elements/gallery-1-2-3-info-panel.svg +236 -0
- package/public/templates/custom08/elements/gallery-4-5-6-7-info-panel.svg +266 -0
- package/public/templates/custom08/elements/gallery-8-9-info-panel.svg +274 -0
- package/public/templates/custom08/elements/gallery-labels/_nav-map-styles.css +554 -0
- package/public/templates/custom08/elements/gallery-labels/_styles.css +556 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-1.svg +35 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-2.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-3.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-4.svg +37 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-5.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-6.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-7.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-8.svg +37 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-9.svg +34 -0
- package/public/templates/custom08/elements/hand-hint.png +0 -0
- package/public/templates/custom08/elements/idle-screen-bg.svg +5 -0
- package/public/templates/custom08/elements/idle-screen-map.svg +627 -0
- package/public/templates/custom08/elements/idle-screen-text.svg +350 -0
- package/public/templates/custom08/elements/key-map-1.svg +986 -0
- package/public/templates/custom08/elements/key-map-2.svg +1018 -0
- package/public/templates/custom08/elements/key-map-3.svg +1019 -0
- package/public/templates/custom08/elements/key-map-combined.svg +1001 -0
- package/public/templates/custom08/elements/map-highlight-marker.svg +11 -0
- package/public/templates/custom08/elements/map-pin-marker.svg +15 -0
- package/public/templates/custom08/elements/map-teardrop-star-marker.svg +13 -0
- package/public/templates/custom08/elements/nav-circle-galleries-1-3.svg +21 -0
- package/public/templates/custom08/elements/nav-circle-galleries-4-7.svg +24 -0
- package/public/templates/custom08/elements/nav-circle-galleries-8-9.svg +20 -0
- package/public/templates/custom08/elements/section1-map.svg +1435 -0
- package/public/templates/custom08/elements/section2-map.svg +1724 -0
- package/public/templates/custom08/elements/section3-map.svg +1295 -0
- package/public/templates/custom08/fonts/CabinetGrotesk-Variable.ttf +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.12_PM.png +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.56_PM.png +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.24.24_PM.png +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.31.58_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.11_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.36_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.48_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.59_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.15_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.27_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.34_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.42_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.50_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.58_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.04_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.11_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.20_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.57_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.03_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.16_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.23_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/prologue-highlight.png +0 -0
- package/scripts/guardian.ps1 +75 -75
- package/scripts/install-linux.sh +134 -134
- package/scripts/install-rpi.sh +117 -117
- package/scripts/install-windows.ps1 +513 -512
- package/scripts/launch-kiosk.vbs +101 -101
- package/scripts/lightman-agent.logrotate +12 -12
- package/scripts/lightman-agent.service +38 -38
- package/scripts/lightman-shell.bat +107 -107
- package/scripts/reinstall-windows.ps1 +26 -26
- package/scripts/restore-desktop.ps1 +32 -32
- package/scripts/setup.ps1 +116 -116
- package/scripts/setup.sh +115 -115
- package/scripts/sync-display.mjs +20 -0
- package/scripts/uninstall-linux.sh +50 -50
- package/scripts/uninstall-windows.ps1 +54 -54
- package/src/commands/display.ts +177 -177
- package/src/commands/kiosk.ts +113 -113
- package/src/commands/maintenance.ts +106 -106
- package/src/commands/network.ts +129 -129
- package/src/commands/power.ts +163 -163
- package/src/commands/rpi.ts +45 -45
- package/src/commands/screenshot.ts +166 -166
- package/src/commands/serial.ts +17 -17
- package/src/commands/update.ts +124 -124
- package/src/index.ts +652 -652
- package/src/lib/config.ts +69 -69
- package/src/lib/identity.ts +40 -40
- package/src/lib/logger.ts +137 -137
- package/src/lib/platform.ts +10 -10
- package/src/lib/rpi.ts +180 -180
- package/src/lib/screens.ts +128 -128
- package/src/lib/types.ts +176 -176
- package/src/services/commands.ts +107 -107
- package/src/services/health.ts +161 -161
- package/src/services/kiosk.ts +384 -384
- package/src/services/localEvents.ts +60 -60
- package/src/services/logForwarder.ts +72 -72
- package/src/services/multiScreenKiosk.ts +324 -324
- package/src/services/oscBridge.ts +186 -186
- package/src/services/powerScheduler.ts +260 -260
- package/src/services/provisioning.ts +120 -120
- package/src/services/serialBridge.ts +230 -230
- package/src/services/serviceLauncher.ts +183 -183
- package/src/services/staticServer.ts +226 -226
- package/src/services/updater.ts +249 -249
- package/src/services/watchdog.ts +310 -310
- package/src/services/websocket.ts +152 -152
- package/tsconfig.json +28 -28
package/src/services/health.ts
CHANGED
|
@@ -1,161 +1,161 @@
|
|
|
1
|
-
import si from 'systeminformation';
|
|
2
|
-
import net from 'net';
|
|
3
|
-
import { URL } from 'url';
|
|
4
|
-
import type { HealthReport, WsMessage } from '../lib/types.js';
|
|
5
|
-
import type { WsClient } from './websocket.js';
|
|
6
|
-
import type { Logger } from '../lib/logger.js';
|
|
7
|
-
import { readFileSync } from 'fs';
|
|
8
|
-
import { resolve } from 'path';
|
|
9
|
-
import { isRaspberryPi, getGpuTemp, getThrottled, isSdCardReadOnly } from '../lib/rpi.js';
|
|
10
|
-
|
|
11
|
-
export class HealthMonitor {
|
|
12
|
-
private wsClient: WsClient;
|
|
13
|
-
private logger: Logger;
|
|
14
|
-
private intervalMs: number;
|
|
15
|
-
private timer: NodeJS.Timeout | null = null;
|
|
16
|
-
private agentVersion: string;
|
|
17
|
-
private serverUrl: string;
|
|
18
|
-
|
|
19
|
-
constructor(wsClient: WsClient, logger: Logger, intervalMs: number, serverUrl?: string) {
|
|
20
|
-
this.wsClient = wsClient;
|
|
21
|
-
this.logger = logger;
|
|
22
|
-
this.intervalMs = intervalMs;
|
|
23
|
-
this.serverUrl = serverUrl || '';
|
|
24
|
-
|
|
25
|
-
// Read version from package.json
|
|
26
|
-
try {
|
|
27
|
-
const pkg = JSON.parse(
|
|
28
|
-
readFileSync(resolve(process.cwd(), 'package.json'), 'utf-8')
|
|
29
|
-
);
|
|
30
|
-
this.agentVersion = pkg.version || '0.0.0';
|
|
31
|
-
} catch {
|
|
32
|
-
this.agentVersion = '0.0.0';
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
start(): void {
|
|
37
|
-
this.logger.info(`Health monitor started (interval: ${this.intervalMs}ms)`);
|
|
38
|
-
|
|
39
|
-
// Send initial report after a short delay
|
|
40
|
-
setTimeout(() => {
|
|
41
|
-
this.collectAndSend();
|
|
42
|
-
}, 5_000);
|
|
43
|
-
|
|
44
|
-
this.timer = setInterval(() => {
|
|
45
|
-
this.collectAndSend();
|
|
46
|
-
}, this.intervalMs);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
stop(): void {
|
|
50
|
-
if (this.timer) {
|
|
51
|
-
clearInterval(this.timer);
|
|
52
|
-
this.timer = null;
|
|
53
|
-
}
|
|
54
|
-
this.logger.info('Health monitor stopped');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async collect(): Promise<HealthReport> {
|
|
58
|
-
const [cpu, mem, disk, temp] = await Promise.all([
|
|
59
|
-
si.currentLoad(),
|
|
60
|
-
si.mem(),
|
|
61
|
-
si.fsSize(),
|
|
62
|
-
si.cpuTemperature(),
|
|
63
|
-
]);
|
|
64
|
-
|
|
65
|
-
// Use the first/main disk
|
|
66
|
-
const mainDisk = disk[0] || { size: 0, used: 0, use: 0 };
|
|
67
|
-
|
|
68
|
-
const report: HealthReport = {
|
|
69
|
-
cpuUsage: Math.round(cpu.currentLoad * 100) / 100,
|
|
70
|
-
memTotal: mem.total,
|
|
71
|
-
memUsed: mem.used,
|
|
72
|
-
memPercent: mem.total > 0 ? Math.round((mem.used / mem.total) * 10000) / 100 : 0,
|
|
73
|
-
diskTotal: mainDisk.size,
|
|
74
|
-
diskUsed: mainDisk.used,
|
|
75
|
-
diskPercent: Math.round(mainDisk.use * 100) / 100,
|
|
76
|
-
cpuTemp: temp.main !== null ? Math.round(temp.main * 10) / 10 : null,
|
|
77
|
-
uptime: Math.round(process.uptime()),
|
|
78
|
-
agentVersion: this.agentVersion,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Add RPi-specific fields when running on Raspberry Pi
|
|
82
|
-
if (isRaspberryPi()) {
|
|
83
|
-
report.gpuTemp = getGpuTemp();
|
|
84
|
-
report.throttled = getThrottled();
|
|
85
|
-
report.sdCardReadOnly = isSdCardReadOnly();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Add network info
|
|
89
|
-
try {
|
|
90
|
-
const ifaces = await si.networkInterfaces();
|
|
91
|
-
const ifaceList = Array.isArray(ifaces) ? ifaces : [ifaces];
|
|
92
|
-
const primary = ifaceList.find((i) => !i.internal && i.ip4) || null;
|
|
93
|
-
|
|
94
|
-
if (primary) {
|
|
95
|
-
let serverLatencyMs: number | null = null;
|
|
96
|
-
if (this.serverUrl) {
|
|
97
|
-
try {
|
|
98
|
-
const url = new URL(this.serverUrl);
|
|
99
|
-
const host = url.hostname;
|
|
100
|
-
const port = parseInt(url.port, 10) || 3001;
|
|
101
|
-
const start = Date.now();
|
|
102
|
-
const reachable = await this.tcpPing(host, port, 5000);
|
|
103
|
-
serverLatencyMs = reachable ? Date.now() - start : null;
|
|
104
|
-
} catch {
|
|
105
|
-
// Ignore ping errors in health collection
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
report.network = {
|
|
110
|
-
interface: primary.iface,
|
|
111
|
-
ip: primary.ip4,
|
|
112
|
-
mac: primary.mac,
|
|
113
|
-
serverLatencyMs,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
} catch {
|
|
117
|
-
// Network info is optional, don't fail health collection
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return report;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private tcpPing(host: string, port: number, timeoutMs: number): Promise<boolean> {
|
|
124
|
-
return new Promise((resolve) => {
|
|
125
|
-
const socket = new net.Socket();
|
|
126
|
-
let resolved = false;
|
|
127
|
-
|
|
128
|
-
const done = (result: boolean) => {
|
|
129
|
-
if (resolved) return;
|
|
130
|
-
resolved = true;
|
|
131
|
-
socket.destroy();
|
|
132
|
-
resolve(result);
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
socket.setTimeout(timeoutMs);
|
|
136
|
-
socket.on('connect', () => done(true));
|
|
137
|
-
socket.on('timeout', () => done(false));
|
|
138
|
-
socket.on('error', () => done(false));
|
|
139
|
-
socket.connect(port, host);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private async collectAndSend(): Promise<void> {
|
|
144
|
-
try {
|
|
145
|
-
const report = await this.collect();
|
|
146
|
-
const msg: WsMessage = {
|
|
147
|
-
type: 'agent:health',
|
|
148
|
-
payload: report as unknown as Record<string, unknown>,
|
|
149
|
-
timestamp: Date.now(),
|
|
150
|
-
};
|
|
151
|
-
this.wsClient.send(msg);
|
|
152
|
-
this.logger.debug('Health report sent', {
|
|
153
|
-
cpu: report.cpuUsage,
|
|
154
|
-
mem: report.memPercent,
|
|
155
|
-
disk: report.diskPercent,
|
|
156
|
-
});
|
|
157
|
-
} catch (err) {
|
|
158
|
-
this.logger.error('Failed to collect health data:', err);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
1
|
+
import si from 'systeminformation';
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import { URL } from 'url';
|
|
4
|
+
import type { HealthReport, WsMessage } from '../lib/types.js';
|
|
5
|
+
import type { WsClient } from './websocket.js';
|
|
6
|
+
import type { Logger } from '../lib/logger.js';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
import { isRaspberryPi, getGpuTemp, getThrottled, isSdCardReadOnly } from '../lib/rpi.js';
|
|
10
|
+
|
|
11
|
+
export class HealthMonitor {
|
|
12
|
+
private wsClient: WsClient;
|
|
13
|
+
private logger: Logger;
|
|
14
|
+
private intervalMs: number;
|
|
15
|
+
private timer: NodeJS.Timeout | null = null;
|
|
16
|
+
private agentVersion: string;
|
|
17
|
+
private serverUrl: string;
|
|
18
|
+
|
|
19
|
+
constructor(wsClient: WsClient, logger: Logger, intervalMs: number, serverUrl?: string) {
|
|
20
|
+
this.wsClient = wsClient;
|
|
21
|
+
this.logger = logger;
|
|
22
|
+
this.intervalMs = intervalMs;
|
|
23
|
+
this.serverUrl = serverUrl || '';
|
|
24
|
+
|
|
25
|
+
// Read version from package.json
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(
|
|
28
|
+
readFileSync(resolve(process.cwd(), 'package.json'), 'utf-8')
|
|
29
|
+
);
|
|
30
|
+
this.agentVersion = pkg.version || '0.0.0';
|
|
31
|
+
} catch {
|
|
32
|
+
this.agentVersion = '0.0.0';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
start(): void {
|
|
37
|
+
this.logger.info(`Health monitor started (interval: ${this.intervalMs}ms)`);
|
|
38
|
+
|
|
39
|
+
// Send initial report after a short delay
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
this.collectAndSend();
|
|
42
|
+
}, 5_000);
|
|
43
|
+
|
|
44
|
+
this.timer = setInterval(() => {
|
|
45
|
+
this.collectAndSend();
|
|
46
|
+
}, this.intervalMs);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
stop(): void {
|
|
50
|
+
if (this.timer) {
|
|
51
|
+
clearInterval(this.timer);
|
|
52
|
+
this.timer = null;
|
|
53
|
+
}
|
|
54
|
+
this.logger.info('Health monitor stopped');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async collect(): Promise<HealthReport> {
|
|
58
|
+
const [cpu, mem, disk, temp] = await Promise.all([
|
|
59
|
+
si.currentLoad(),
|
|
60
|
+
si.mem(),
|
|
61
|
+
si.fsSize(),
|
|
62
|
+
si.cpuTemperature(),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
// Use the first/main disk
|
|
66
|
+
const mainDisk = disk[0] || { size: 0, used: 0, use: 0 };
|
|
67
|
+
|
|
68
|
+
const report: HealthReport = {
|
|
69
|
+
cpuUsage: Math.round(cpu.currentLoad * 100) / 100,
|
|
70
|
+
memTotal: mem.total,
|
|
71
|
+
memUsed: mem.used,
|
|
72
|
+
memPercent: mem.total > 0 ? Math.round((mem.used / mem.total) * 10000) / 100 : 0,
|
|
73
|
+
diskTotal: mainDisk.size,
|
|
74
|
+
diskUsed: mainDisk.used,
|
|
75
|
+
diskPercent: Math.round(mainDisk.use * 100) / 100,
|
|
76
|
+
cpuTemp: temp.main !== null ? Math.round(temp.main * 10) / 10 : null,
|
|
77
|
+
uptime: Math.round(process.uptime()),
|
|
78
|
+
agentVersion: this.agentVersion,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Add RPi-specific fields when running on Raspberry Pi
|
|
82
|
+
if (isRaspberryPi()) {
|
|
83
|
+
report.gpuTemp = getGpuTemp();
|
|
84
|
+
report.throttled = getThrottled();
|
|
85
|
+
report.sdCardReadOnly = isSdCardReadOnly();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add network info
|
|
89
|
+
try {
|
|
90
|
+
const ifaces = await si.networkInterfaces();
|
|
91
|
+
const ifaceList = Array.isArray(ifaces) ? ifaces : [ifaces];
|
|
92
|
+
const primary = ifaceList.find((i) => !i.internal && i.ip4) || null;
|
|
93
|
+
|
|
94
|
+
if (primary) {
|
|
95
|
+
let serverLatencyMs: number | null = null;
|
|
96
|
+
if (this.serverUrl) {
|
|
97
|
+
try {
|
|
98
|
+
const url = new URL(this.serverUrl);
|
|
99
|
+
const host = url.hostname;
|
|
100
|
+
const port = parseInt(url.port, 10) || 3001;
|
|
101
|
+
const start = Date.now();
|
|
102
|
+
const reachable = await this.tcpPing(host, port, 5000);
|
|
103
|
+
serverLatencyMs = reachable ? Date.now() - start : null;
|
|
104
|
+
} catch {
|
|
105
|
+
// Ignore ping errors in health collection
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
report.network = {
|
|
110
|
+
interface: primary.iface,
|
|
111
|
+
ip: primary.ip4,
|
|
112
|
+
mac: primary.mac,
|
|
113
|
+
serverLatencyMs,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// Network info is optional, don't fail health collection
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return report;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private tcpPing(host: string, port: number, timeoutMs: number): Promise<boolean> {
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
const socket = new net.Socket();
|
|
126
|
+
let resolved = false;
|
|
127
|
+
|
|
128
|
+
const done = (result: boolean) => {
|
|
129
|
+
if (resolved) return;
|
|
130
|
+
resolved = true;
|
|
131
|
+
socket.destroy();
|
|
132
|
+
resolve(result);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
socket.setTimeout(timeoutMs);
|
|
136
|
+
socket.on('connect', () => done(true));
|
|
137
|
+
socket.on('timeout', () => done(false));
|
|
138
|
+
socket.on('error', () => done(false));
|
|
139
|
+
socket.connect(port, host);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async collectAndSend(): Promise<void> {
|
|
144
|
+
try {
|
|
145
|
+
const report = await this.collect();
|
|
146
|
+
const msg: WsMessage = {
|
|
147
|
+
type: 'agent:health',
|
|
148
|
+
payload: report as unknown as Record<string, unknown>,
|
|
149
|
+
timestamp: Date.now(),
|
|
150
|
+
};
|
|
151
|
+
this.wsClient.send(msg);
|
|
152
|
+
this.logger.debug('Health report sent', {
|
|
153
|
+
cpu: report.cpuUsage,
|
|
154
|
+
mem: report.memPercent,
|
|
155
|
+
disk: report.diskPercent,
|
|
156
|
+
});
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this.logger.error('Failed to collect health data:', err);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|