iobroker.beszel 0.1.2
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/.github/auto-merge.yml +2 -0
- package/.github/dependabot.yml +12 -0
- package/.github/workflows/automerge-dependabot.yml +32 -0
- package/.github/workflows/test-and-release.yml +62 -0
- package/.vscode/settings.json +12 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +91 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/admin/beszel.svg +9 -0
- package/admin/i18n/de/translations.json +43 -0
- package/admin/i18n/en/translations.json +43 -0
- package/admin/i18n/es/translations.json +43 -0
- package/admin/i18n/fr/translations.json +43 -0
- package/admin/i18n/it/translations.json +43 -0
- package/admin/i18n/nl/translations.json +43 -0
- package/admin/i18n/pl/translations.json +43 -0
- package/admin/i18n/pt/translations.json +43 -0
- package/admin/i18n/ru/translations.json +43 -0
- package/admin/i18n/uk/translations.json +43 -0
- package/admin/i18n/zh-cn/translations.json +43 -0
- package/admin/jsonConfig.json +240 -0
- package/build/lib/beszel-client.d.ts +39 -0
- package/build/lib/beszel-client.d.ts.map +1 -0
- package/build/lib/beszel-client.js +199 -0
- package/build/lib/state-manager.d.ts +47 -0
- package/build/lib/state-manager.d.ts.map +1 -0
- package/build/lib/state-manager.js +738 -0
- package/build/lib/types.d.ts +174 -0
- package/build/lib/types.d.ts.map +1 -0
- package/build/lib/types.js +2 -0
- package/build/main.d.ts +2 -0
- package/build/main.d.ts.map +1 -0
- package/build/main.js +191 -0
- package/eslint.config.mjs +36 -0
- package/io-package.json +162 -0
- package/package.json +61 -0
- package/scripts/version.js +28 -0
- package/src/lib/beszel-client.ts +216 -0
- package/src/lib/state-manager.ts +1050 -0
- package/src/lib/types.ts +192 -0
- package/src/main.ts +199 -0
- package/test/testPackageFiles.ts +5 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +24 -0
- package/tsconfig.test.json +9 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StateManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Manages creation and updates of ioBroker states for Beszel systems.
|
|
6
|
+
*/
|
|
7
|
+
class StateManager {
|
|
8
|
+
adapter;
|
|
9
|
+
constructor(adapter) {
|
|
10
|
+
this.adapter = adapter;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Sanitize a name to a valid ioBroker state ID segment.
|
|
14
|
+
* Lowercase, replace non-alphanumeric with _, max 50 chars, trim underscores.
|
|
15
|
+
*
|
|
16
|
+
* @param name
|
|
17
|
+
*/
|
|
18
|
+
sanitize(name) {
|
|
19
|
+
return name
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
22
|
+
.replace(/^_+|_+$/g, "")
|
|
23
|
+
.slice(0, 50);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Update all states for a single system.
|
|
27
|
+
*
|
|
28
|
+
* @param system
|
|
29
|
+
* @param stats
|
|
30
|
+
* @param containers
|
|
31
|
+
* @param config
|
|
32
|
+
*/
|
|
33
|
+
async updateSystem(system, stats, containers, config) {
|
|
34
|
+
const sysId = `systems.${this.sanitize(system.name)}`;
|
|
35
|
+
// Create device object
|
|
36
|
+
await this.adapter.setObjectNotExistsAsync(sysId, {
|
|
37
|
+
type: "device",
|
|
38
|
+
common: { name: system.name },
|
|
39
|
+
native: { id: system.id, host: system.host },
|
|
40
|
+
});
|
|
41
|
+
// Always: online + status
|
|
42
|
+
const isUp = system.status === "up";
|
|
43
|
+
await this.createAndSetState(`${sysId}.online`, {
|
|
44
|
+
name: "Online",
|
|
45
|
+
type: "boolean",
|
|
46
|
+
role: "indicator.reachable",
|
|
47
|
+
read: true,
|
|
48
|
+
write: false,
|
|
49
|
+
}, isUp);
|
|
50
|
+
await this.createAndSetState(`${sysId}.status`, {
|
|
51
|
+
name: "Status",
|
|
52
|
+
type: "string",
|
|
53
|
+
role: "text",
|
|
54
|
+
read: true,
|
|
55
|
+
write: false,
|
|
56
|
+
}, system.status);
|
|
57
|
+
// Uptime
|
|
58
|
+
if (config.metrics_uptime) {
|
|
59
|
+
const uptime = system.info.u ?? null;
|
|
60
|
+
await this.createAndSetState(`${sysId}.uptime`, {
|
|
61
|
+
name: "Uptime",
|
|
62
|
+
type: "number",
|
|
63
|
+
role: "value",
|
|
64
|
+
unit: "s",
|
|
65
|
+
read: true,
|
|
66
|
+
write: false,
|
|
67
|
+
}, uptime);
|
|
68
|
+
await this.createAndSetState(`${sysId}.uptime_text`, {
|
|
69
|
+
name: "Uptime (formatted)",
|
|
70
|
+
type: "string",
|
|
71
|
+
role: "text",
|
|
72
|
+
read: true,
|
|
73
|
+
write: false,
|
|
74
|
+
}, uptime !== null ? this.formatUptime(uptime) : null);
|
|
75
|
+
}
|
|
76
|
+
// Agent version
|
|
77
|
+
if (config.metrics_agentVersion) {
|
|
78
|
+
await this.createAndSetState(`${sysId}.agent_version`, {
|
|
79
|
+
name: "Agent Version",
|
|
80
|
+
type: "string",
|
|
81
|
+
role: "text",
|
|
82
|
+
read: true,
|
|
83
|
+
write: false,
|
|
84
|
+
}, system.info.v ?? null);
|
|
85
|
+
}
|
|
86
|
+
// Systemd services
|
|
87
|
+
if (config.metrics_services) {
|
|
88
|
+
const sv = system.info.sv;
|
|
89
|
+
await this.createAndSetState(`${sysId}.services_total`, {
|
|
90
|
+
name: "Services Total",
|
|
91
|
+
type: "number",
|
|
92
|
+
role: "value",
|
|
93
|
+
read: true,
|
|
94
|
+
write: false,
|
|
95
|
+
}, sv?.[0] ?? null);
|
|
96
|
+
await this.createAndSetState(`${sysId}.services_failed`, {
|
|
97
|
+
name: "Services Failed",
|
|
98
|
+
type: "number",
|
|
99
|
+
role: "value.warning",
|
|
100
|
+
read: true,
|
|
101
|
+
write: false,
|
|
102
|
+
}, sv?.[1] ?? null);
|
|
103
|
+
}
|
|
104
|
+
// Stats-based metrics (only if stats available)
|
|
105
|
+
if (stats) {
|
|
106
|
+
await this.updateStatsStates(sysId, system, stats, config);
|
|
107
|
+
}
|
|
108
|
+
// Load avg fallback to system.info.la if no stats
|
|
109
|
+
if (config.metrics_loadAvg && !stats && system.info.la) {
|
|
110
|
+
const la = system.info.la;
|
|
111
|
+
await this.createAndSetState(`${sysId}.load_avg_1m`, {
|
|
112
|
+
name: "Load Average 1m",
|
|
113
|
+
type: "number",
|
|
114
|
+
role: "value",
|
|
115
|
+
read: true,
|
|
116
|
+
write: false,
|
|
117
|
+
}, la[0]);
|
|
118
|
+
await this.createAndSetState(`${sysId}.load_avg_5m`, {
|
|
119
|
+
name: "Load Average 5m",
|
|
120
|
+
type: "number",
|
|
121
|
+
role: "value",
|
|
122
|
+
read: true,
|
|
123
|
+
write: false,
|
|
124
|
+
}, la[1]);
|
|
125
|
+
await this.createAndSetState(`${sysId}.load_avg_15m`, {
|
|
126
|
+
name: "Load Average 15m",
|
|
127
|
+
type: "number",
|
|
128
|
+
role: "value",
|
|
129
|
+
read: true,
|
|
130
|
+
write: false,
|
|
131
|
+
}, la[2]);
|
|
132
|
+
}
|
|
133
|
+
// Containers
|
|
134
|
+
if (config.metrics_containers) {
|
|
135
|
+
await this.updateContainers(sysId, system.id, containers, config);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await this.deleteChannelIfExists(`${sysId}.containers`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Remove device objects for systems that are no longer in Beszel.
|
|
143
|
+
*
|
|
144
|
+
* @param activeSystemNames
|
|
145
|
+
*/
|
|
146
|
+
async cleanupSystems(activeSystemNames) {
|
|
147
|
+
const activeIds = new Set(activeSystemNames.map((n) => `systems.${this.sanitize(n)}`));
|
|
148
|
+
const objects = await this.adapter.getObjectViewAsync("system", "device", {
|
|
149
|
+
startkey: `${this.adapter.namespace}.systems.`,
|
|
150
|
+
endkey: `${this.adapter.namespace}.systems.\u9999`,
|
|
151
|
+
});
|
|
152
|
+
if (!objects?.rows) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const row of objects.rows) {
|
|
156
|
+
const id = row.id;
|
|
157
|
+
// Extract the relative id part
|
|
158
|
+
const relativeId = id.startsWith(`${this.adapter.namespace}.`)
|
|
159
|
+
? id.slice(this.adapter.namespace.length + 1)
|
|
160
|
+
: id;
|
|
161
|
+
// Only delete direct children of "systems." (one level deep)
|
|
162
|
+
const parts = relativeId.split(".");
|
|
163
|
+
if (parts.length === 2 &&
|
|
164
|
+
parts[0] === "systems" &&
|
|
165
|
+
!activeIds.has(relativeId)) {
|
|
166
|
+
this.adapter.log.info(`Removing stale system: ${relativeId}`);
|
|
167
|
+
await this.adapter.delObjectAsync(relativeId, { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Delete states for metrics that have been disabled in the config.
|
|
173
|
+
* Called on startup to clean up previously-enabled states.
|
|
174
|
+
*
|
|
175
|
+
* @param systemId
|
|
176
|
+
* @param config
|
|
177
|
+
*/
|
|
178
|
+
async cleanupMetrics(systemId, config) {
|
|
179
|
+
const sysId = `systems.${systemId}`;
|
|
180
|
+
const toDelete = [];
|
|
181
|
+
if (!config.metrics_uptime) {
|
|
182
|
+
toDelete.push(`${sysId}.uptime`, `${sysId}.uptime_text`);
|
|
183
|
+
}
|
|
184
|
+
if (!config.metrics_agentVersion) {
|
|
185
|
+
toDelete.push(`${sysId}.agent_version`);
|
|
186
|
+
}
|
|
187
|
+
if (!config.metrics_services) {
|
|
188
|
+
toDelete.push(`${sysId}.services_total`, `${sysId}.services_failed`);
|
|
189
|
+
}
|
|
190
|
+
if (!config.metrics_cpu) {
|
|
191
|
+
toDelete.push(`${sysId}.cpu_usage`);
|
|
192
|
+
}
|
|
193
|
+
if (!config.metrics_loadAvg) {
|
|
194
|
+
toDelete.push(`${sysId}.load_avg_1m`, `${sysId}.load_avg_5m`, `${sysId}.load_avg_15m`);
|
|
195
|
+
}
|
|
196
|
+
if (!config.metrics_cpuBreakdown) {
|
|
197
|
+
toDelete.push(`${sysId}.cpu_user`, `${sysId}.cpu_system`, `${sysId}.cpu_iowait`, `${sysId}.cpu_steal`, `${sysId}.cpu_idle`);
|
|
198
|
+
}
|
|
199
|
+
if (!config.metrics_memory) {
|
|
200
|
+
toDelete.push(`${sysId}.memory_percent`, `${sysId}.memory_used`, `${sysId}.memory_total`);
|
|
201
|
+
}
|
|
202
|
+
if (!config.metrics_memoryDetails) {
|
|
203
|
+
toDelete.push(`${sysId}.memory_buffers`, `${sysId}.memory_zfs_arc`);
|
|
204
|
+
}
|
|
205
|
+
if (!config.metrics_swap) {
|
|
206
|
+
toDelete.push(`${sysId}.swap_used`, `${sysId}.swap_total`);
|
|
207
|
+
}
|
|
208
|
+
if (!config.metrics_disk) {
|
|
209
|
+
toDelete.push(`${sysId}.disk_percent`, `${sysId}.disk_used`, `${sysId}.disk_total`);
|
|
210
|
+
}
|
|
211
|
+
if (!config.metrics_diskSpeed) {
|
|
212
|
+
toDelete.push(`${sysId}.disk_read`, `${sysId}.disk_write`);
|
|
213
|
+
}
|
|
214
|
+
if (!config.metrics_network) {
|
|
215
|
+
toDelete.push(`${sysId}.network_sent`, `${sysId}.network_recv`);
|
|
216
|
+
}
|
|
217
|
+
if (!config.metrics_temperature) {
|
|
218
|
+
toDelete.push(`${sysId}.temperature`);
|
|
219
|
+
}
|
|
220
|
+
if (!config.metrics_battery) {
|
|
221
|
+
toDelete.push(`${sysId}.battery_percent`, `${sysId}.battery_charging`);
|
|
222
|
+
}
|
|
223
|
+
for (const id of toDelete) {
|
|
224
|
+
const obj = await this.adapter.getObjectAsync(id);
|
|
225
|
+
if (obj) {
|
|
226
|
+
await this.adapter.delObjectAsync(id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Channels
|
|
230
|
+
if (!config.metrics_temperatureDetails) {
|
|
231
|
+
await this.deleteChannelIfExists(`${sysId}.temperatures`);
|
|
232
|
+
}
|
|
233
|
+
if (!config.metrics_gpu) {
|
|
234
|
+
await this.deleteChannelIfExists(`${sysId}.gpu`);
|
|
235
|
+
}
|
|
236
|
+
if (!config.metrics_extraFs) {
|
|
237
|
+
await this.deleteChannelIfExists(`${sysId}.filesystems`);
|
|
238
|
+
}
|
|
239
|
+
if (!config.metrics_containers) {
|
|
240
|
+
await this.deleteChannelIfExists(`${sysId}.containers`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// -------------------------------------------------------------------------
|
|
244
|
+
// Private helpers
|
|
245
|
+
// -------------------------------------------------------------------------
|
|
246
|
+
async updateStatsStates(sysId, system, stats, config) {
|
|
247
|
+
// CPU
|
|
248
|
+
if (config.metrics_cpu) {
|
|
249
|
+
await this.createAndSetState(`${sysId}.cpu_usage`, {
|
|
250
|
+
name: "CPU Usage",
|
|
251
|
+
type: "number",
|
|
252
|
+
role: "level",
|
|
253
|
+
unit: "%",
|
|
254
|
+
min: 0,
|
|
255
|
+
max: 100,
|
|
256
|
+
read: true,
|
|
257
|
+
write: false,
|
|
258
|
+
}, stats.cpu ?? null);
|
|
259
|
+
}
|
|
260
|
+
// Load avg — prefer stats.la, fallback to system.info.la
|
|
261
|
+
if (config.metrics_loadAvg) {
|
|
262
|
+
const la = stats.la ?? system.info.la;
|
|
263
|
+
await this.createAndSetState(`${sysId}.load_avg_1m`, {
|
|
264
|
+
name: "Load Average 1m",
|
|
265
|
+
type: "number",
|
|
266
|
+
role: "value",
|
|
267
|
+
read: true,
|
|
268
|
+
write: false,
|
|
269
|
+
}, la?.[0] ?? null);
|
|
270
|
+
await this.createAndSetState(`${sysId}.load_avg_5m`, {
|
|
271
|
+
name: "Load Average 5m",
|
|
272
|
+
type: "number",
|
|
273
|
+
role: "value",
|
|
274
|
+
read: true,
|
|
275
|
+
write: false,
|
|
276
|
+
}, la?.[1] ?? null);
|
|
277
|
+
await this.createAndSetState(`${sysId}.load_avg_15m`, {
|
|
278
|
+
name: "Load Average 15m",
|
|
279
|
+
type: "number",
|
|
280
|
+
role: "value",
|
|
281
|
+
read: true,
|
|
282
|
+
write: false,
|
|
283
|
+
}, la?.[2] ?? null);
|
|
284
|
+
}
|
|
285
|
+
// CPU breakdown
|
|
286
|
+
if (config.metrics_cpuBreakdown && stats.cpub && stats.cpub.length >= 5) {
|
|
287
|
+
const [user, sys, iowait, steal, idle] = stats.cpub;
|
|
288
|
+
await this.createAndSetState(`${sysId}.cpu_user`, {
|
|
289
|
+
name: "CPU User %",
|
|
290
|
+
type: "number",
|
|
291
|
+
role: "level",
|
|
292
|
+
unit: "%",
|
|
293
|
+
min: 0,
|
|
294
|
+
max: 100,
|
|
295
|
+
read: true,
|
|
296
|
+
write: false,
|
|
297
|
+
}, user);
|
|
298
|
+
await this.createAndSetState(`${sysId}.cpu_system`, {
|
|
299
|
+
name: "CPU System %",
|
|
300
|
+
type: "number",
|
|
301
|
+
role: "level",
|
|
302
|
+
unit: "%",
|
|
303
|
+
min: 0,
|
|
304
|
+
max: 100,
|
|
305
|
+
read: true,
|
|
306
|
+
write: false,
|
|
307
|
+
}, sys);
|
|
308
|
+
await this.createAndSetState(`${sysId}.cpu_iowait`, {
|
|
309
|
+
name: "CPU IOWait %",
|
|
310
|
+
type: "number",
|
|
311
|
+
role: "level",
|
|
312
|
+
unit: "%",
|
|
313
|
+
min: 0,
|
|
314
|
+
max: 100,
|
|
315
|
+
read: true,
|
|
316
|
+
write: false,
|
|
317
|
+
}, iowait);
|
|
318
|
+
await this.createAndSetState(`${sysId}.cpu_steal`, {
|
|
319
|
+
name: "CPU Steal %",
|
|
320
|
+
type: "number",
|
|
321
|
+
role: "level",
|
|
322
|
+
unit: "%",
|
|
323
|
+
min: 0,
|
|
324
|
+
max: 100,
|
|
325
|
+
read: true,
|
|
326
|
+
write: false,
|
|
327
|
+
}, steal);
|
|
328
|
+
await this.createAndSetState(`${sysId}.cpu_idle`, {
|
|
329
|
+
name: "CPU Idle %",
|
|
330
|
+
type: "number",
|
|
331
|
+
role: "level",
|
|
332
|
+
unit: "%",
|
|
333
|
+
min: 0,
|
|
334
|
+
max: 100,
|
|
335
|
+
read: true,
|
|
336
|
+
write: false,
|
|
337
|
+
}, idle);
|
|
338
|
+
}
|
|
339
|
+
// Memory
|
|
340
|
+
if (config.metrics_memory) {
|
|
341
|
+
await this.createAndSetState(`${sysId}.memory_percent`, {
|
|
342
|
+
name: "Memory %",
|
|
343
|
+
type: "number",
|
|
344
|
+
role: "level",
|
|
345
|
+
unit: "%",
|
|
346
|
+
min: 0,
|
|
347
|
+
max: 100,
|
|
348
|
+
read: true,
|
|
349
|
+
write: false,
|
|
350
|
+
}, stats.mp ?? null);
|
|
351
|
+
await this.createAndSetState(`${sysId}.memory_used`, {
|
|
352
|
+
name: "Memory Used",
|
|
353
|
+
type: "number",
|
|
354
|
+
role: "value",
|
|
355
|
+
unit: "GB",
|
|
356
|
+
read: true,
|
|
357
|
+
write: false,
|
|
358
|
+
}, stats.mu ?? null);
|
|
359
|
+
await this.createAndSetState(`${sysId}.memory_total`, {
|
|
360
|
+
name: "Memory Total",
|
|
361
|
+
type: "number",
|
|
362
|
+
role: "value",
|
|
363
|
+
unit: "GB",
|
|
364
|
+
read: true,
|
|
365
|
+
write: false,
|
|
366
|
+
}, stats.m ?? null);
|
|
367
|
+
}
|
|
368
|
+
// Memory details
|
|
369
|
+
if (config.metrics_memoryDetails) {
|
|
370
|
+
await this.createAndSetState(`${sysId}.memory_buffers`, {
|
|
371
|
+
name: "Memory Buffers+Cache",
|
|
372
|
+
type: "number",
|
|
373
|
+
role: "value",
|
|
374
|
+
unit: "GB",
|
|
375
|
+
read: true,
|
|
376
|
+
write: false,
|
|
377
|
+
}, stats.mb ?? null);
|
|
378
|
+
await this.createAndSetState(`${sysId}.memory_zfs_arc`, {
|
|
379
|
+
name: "Memory ZFS ARC",
|
|
380
|
+
type: "number",
|
|
381
|
+
role: "value",
|
|
382
|
+
unit: "GB",
|
|
383
|
+
read: true,
|
|
384
|
+
write: false,
|
|
385
|
+
}, stats.mz ?? null);
|
|
386
|
+
}
|
|
387
|
+
// Swap
|
|
388
|
+
if (config.metrics_swap) {
|
|
389
|
+
await this.createAndSetState(`${sysId}.swap_used`, {
|
|
390
|
+
name: "Swap Used",
|
|
391
|
+
type: "number",
|
|
392
|
+
role: "value",
|
|
393
|
+
unit: "GB",
|
|
394
|
+
read: true,
|
|
395
|
+
write: false,
|
|
396
|
+
}, stats.su ?? null);
|
|
397
|
+
await this.createAndSetState(`${sysId}.swap_total`, {
|
|
398
|
+
name: "Swap Total",
|
|
399
|
+
type: "number",
|
|
400
|
+
role: "value",
|
|
401
|
+
unit: "GB",
|
|
402
|
+
read: true,
|
|
403
|
+
write: false,
|
|
404
|
+
}, stats.s ?? null);
|
|
405
|
+
}
|
|
406
|
+
// Disk
|
|
407
|
+
if (config.metrics_disk) {
|
|
408
|
+
await this.createAndSetState(`${sysId}.disk_percent`, {
|
|
409
|
+
name: "Disk %",
|
|
410
|
+
type: "number",
|
|
411
|
+
role: "level",
|
|
412
|
+
unit: "%",
|
|
413
|
+
min: 0,
|
|
414
|
+
max: 100,
|
|
415
|
+
read: true,
|
|
416
|
+
write: false,
|
|
417
|
+
}, stats.dp ?? null);
|
|
418
|
+
await this.createAndSetState(`${sysId}.disk_used`, {
|
|
419
|
+
name: "Disk Used",
|
|
420
|
+
type: "number",
|
|
421
|
+
role: "value",
|
|
422
|
+
unit: "GB",
|
|
423
|
+
read: true,
|
|
424
|
+
write: false,
|
|
425
|
+
}, stats.du ?? null);
|
|
426
|
+
await this.createAndSetState(`${sysId}.disk_total`, {
|
|
427
|
+
name: "Disk Total",
|
|
428
|
+
type: "number",
|
|
429
|
+
role: "value",
|
|
430
|
+
unit: "GB",
|
|
431
|
+
read: true,
|
|
432
|
+
write: false,
|
|
433
|
+
}, stats.d ?? null);
|
|
434
|
+
}
|
|
435
|
+
// Disk speed
|
|
436
|
+
if (config.metrics_diskSpeed) {
|
|
437
|
+
await this.createAndSetState(`${sysId}.disk_read`, {
|
|
438
|
+
name: "Disk Read",
|
|
439
|
+
type: "number",
|
|
440
|
+
role: "value",
|
|
441
|
+
unit: "MB/s",
|
|
442
|
+
read: true,
|
|
443
|
+
write: false,
|
|
444
|
+
}, stats.dr ?? null);
|
|
445
|
+
await this.createAndSetState(`${sysId}.disk_write`, {
|
|
446
|
+
name: "Disk Write",
|
|
447
|
+
type: "number",
|
|
448
|
+
role: "value",
|
|
449
|
+
unit: "MB/s",
|
|
450
|
+
read: true,
|
|
451
|
+
write: false,
|
|
452
|
+
}, stats.dw ?? null);
|
|
453
|
+
}
|
|
454
|
+
// Network
|
|
455
|
+
if (config.metrics_network) {
|
|
456
|
+
await this.createAndSetState(`${sysId}.network_sent`, {
|
|
457
|
+
name: "Network Sent",
|
|
458
|
+
type: "number",
|
|
459
|
+
role: "value",
|
|
460
|
+
unit: "MB/s",
|
|
461
|
+
read: true,
|
|
462
|
+
write: false,
|
|
463
|
+
}, stats.ns ?? null);
|
|
464
|
+
await this.createAndSetState(`${sysId}.network_recv`, {
|
|
465
|
+
name: "Network Received",
|
|
466
|
+
type: "number",
|
|
467
|
+
role: "value",
|
|
468
|
+
unit: "MB/s",
|
|
469
|
+
read: true,
|
|
470
|
+
write: false,
|
|
471
|
+
}, stats.nr ?? null);
|
|
472
|
+
}
|
|
473
|
+
// Temperature (average of top 3)
|
|
474
|
+
if (config.metrics_temperature) {
|
|
475
|
+
const avgTemp = this.computeTopAvgTemp(stats.t);
|
|
476
|
+
await this.createAndSetState(`${sysId}.temperature`, {
|
|
477
|
+
name: "Temperature (avg top 3)",
|
|
478
|
+
type: "number",
|
|
479
|
+
role: "value.temperature",
|
|
480
|
+
unit: "°C",
|
|
481
|
+
read: true,
|
|
482
|
+
write: false,
|
|
483
|
+
}, avgTemp);
|
|
484
|
+
}
|
|
485
|
+
// Temperature details
|
|
486
|
+
if (config.metrics_temperatureDetails && stats.t) {
|
|
487
|
+
await this.ensureChannel(`${sysId}.temperatures`, "Temperatures");
|
|
488
|
+
for (const [sensor, temp] of Object.entries(stats.t)) {
|
|
489
|
+
const sensorId = this.sanitize(sensor);
|
|
490
|
+
await this.createAndSetState(`${sysId}.temperatures.${sensorId}`, {
|
|
491
|
+
name: sensor,
|
|
492
|
+
type: "number",
|
|
493
|
+
role: "value.temperature",
|
|
494
|
+
unit: "°C",
|
|
495
|
+
read: true,
|
|
496
|
+
write: false,
|
|
497
|
+
}, temp);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else if (!config.metrics_temperatureDetails) {
|
|
501
|
+
await this.deleteChannelIfExists(`${sysId}.temperatures`);
|
|
502
|
+
}
|
|
503
|
+
// Battery
|
|
504
|
+
if (config.metrics_battery) {
|
|
505
|
+
const bat = stats.bat ?? system.info.bat;
|
|
506
|
+
await this.createAndSetState(`${sysId}.battery_percent`, {
|
|
507
|
+
name: "Battery %",
|
|
508
|
+
type: "number",
|
|
509
|
+
role: "value",
|
|
510
|
+
unit: "%",
|
|
511
|
+
min: 0,
|
|
512
|
+
max: 100,
|
|
513
|
+
read: true,
|
|
514
|
+
write: false,
|
|
515
|
+
}, bat?.[0] ?? null);
|
|
516
|
+
await this.createAndSetState(`${sysId}.battery_charging`, {
|
|
517
|
+
name: "Battery Charging",
|
|
518
|
+
type: "boolean",
|
|
519
|
+
role: "indicator",
|
|
520
|
+
read: true,
|
|
521
|
+
write: false,
|
|
522
|
+
}, bat ? bat[1] > 0 : null);
|
|
523
|
+
}
|
|
524
|
+
// GPU
|
|
525
|
+
if (config.metrics_gpu && stats.g && Object.keys(stats.g).length > 0) {
|
|
526
|
+
await this.ensureChannel(`${sysId}.gpu`, "GPU");
|
|
527
|
+
for (const [gpuId, gpuData] of Object.entries(stats.g)) {
|
|
528
|
+
const safeId = this.sanitize(gpuId);
|
|
529
|
+
const gpuLabel = gpuData.n ?? gpuId;
|
|
530
|
+
await this.ensureChannel(`${sysId}.gpu.${safeId}`, gpuLabel);
|
|
531
|
+
await this.createAndSetState(`${sysId}.gpu.${safeId}.usage`, {
|
|
532
|
+
name: "GPU Usage",
|
|
533
|
+
type: "number",
|
|
534
|
+
role: "level",
|
|
535
|
+
unit: "%",
|
|
536
|
+
min: 0,
|
|
537
|
+
max: 100,
|
|
538
|
+
read: true,
|
|
539
|
+
write: false,
|
|
540
|
+
}, gpuData.u ?? null);
|
|
541
|
+
await this.createAndSetState(`${sysId}.gpu.${safeId}.memory_used`, {
|
|
542
|
+
name: "GPU Memory Used",
|
|
543
|
+
type: "number",
|
|
544
|
+
role: "value",
|
|
545
|
+
unit: "GB",
|
|
546
|
+
read: true,
|
|
547
|
+
write: false,
|
|
548
|
+
}, gpuData.mu ?? null);
|
|
549
|
+
await this.createAndSetState(`${sysId}.gpu.${safeId}.memory_total`, {
|
|
550
|
+
name: "GPU Memory Total",
|
|
551
|
+
type: "number",
|
|
552
|
+
role: "value",
|
|
553
|
+
unit: "GB",
|
|
554
|
+
read: true,
|
|
555
|
+
write: false,
|
|
556
|
+
}, gpuData.mt ?? null);
|
|
557
|
+
await this.createAndSetState(`${sysId}.gpu.${safeId}.power`, {
|
|
558
|
+
name: "GPU Power",
|
|
559
|
+
type: "number",
|
|
560
|
+
role: "value",
|
|
561
|
+
unit: "W",
|
|
562
|
+
read: true,
|
|
563
|
+
write: false,
|
|
564
|
+
}, gpuData.p ?? null);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else if (!config.metrics_gpu) {
|
|
568
|
+
await this.deleteChannelIfExists(`${sysId}.gpu`);
|
|
569
|
+
}
|
|
570
|
+
// Extra filesystems
|
|
571
|
+
if (config.metrics_extraFs &&
|
|
572
|
+
stats.efs &&
|
|
573
|
+
Object.keys(stats.efs).length > 0) {
|
|
574
|
+
await this.ensureChannel(`${sysId}.filesystems`, "Filesystems");
|
|
575
|
+
for (const [fsName, fsData] of Object.entries(stats.efs)) {
|
|
576
|
+
const safeId = this.sanitize(fsName);
|
|
577
|
+
await this.ensureChannel(`${sysId}.filesystems.${safeId}`, fsName);
|
|
578
|
+
const total = fsData.d ?? null;
|
|
579
|
+
const used = fsData.du ?? null;
|
|
580
|
+
const percent = total !== null && used !== null && total > 0
|
|
581
|
+
? Math.round((used / total) * 100)
|
|
582
|
+
: null;
|
|
583
|
+
await this.createAndSetState(`${sysId}.filesystems.${safeId}.disk_percent`, {
|
|
584
|
+
name: "Disk %",
|
|
585
|
+
type: "number",
|
|
586
|
+
role: "level",
|
|
587
|
+
unit: "%",
|
|
588
|
+
min: 0,
|
|
589
|
+
max: 100,
|
|
590
|
+
read: true,
|
|
591
|
+
write: false,
|
|
592
|
+
}, percent);
|
|
593
|
+
await this.createAndSetState(`${sysId}.filesystems.${safeId}.disk_used`, {
|
|
594
|
+
name: "Disk Used",
|
|
595
|
+
type: "number",
|
|
596
|
+
role: "value",
|
|
597
|
+
unit: "GB",
|
|
598
|
+
read: true,
|
|
599
|
+
write: false,
|
|
600
|
+
}, used);
|
|
601
|
+
await this.createAndSetState(`${sysId}.filesystems.${safeId}.disk_total`, {
|
|
602
|
+
name: "Disk Total",
|
|
603
|
+
type: "number",
|
|
604
|
+
role: "value",
|
|
605
|
+
unit: "GB",
|
|
606
|
+
read: true,
|
|
607
|
+
write: false,
|
|
608
|
+
}, total);
|
|
609
|
+
await this.createAndSetState(`${sysId}.filesystems.${safeId}.read_speed`, {
|
|
610
|
+
name: "Read Speed",
|
|
611
|
+
type: "number",
|
|
612
|
+
role: "value",
|
|
613
|
+
unit: "MB/s",
|
|
614
|
+
read: true,
|
|
615
|
+
write: false,
|
|
616
|
+
}, fsData.r ?? null);
|
|
617
|
+
await this.createAndSetState(`${sysId}.filesystems.${safeId}.write_speed`, {
|
|
618
|
+
name: "Write Speed",
|
|
619
|
+
type: "number",
|
|
620
|
+
role: "value",
|
|
621
|
+
unit: "MB/s",
|
|
622
|
+
read: true,
|
|
623
|
+
write: false,
|
|
624
|
+
}, fsData.w ?? null);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
else if (!config.metrics_extraFs) {
|
|
628
|
+
await this.deleteChannelIfExists(`${sysId}.filesystems`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async updateContainers(sysId, systemId, allContainers, _config) {
|
|
632
|
+
const sysContainers = allContainers.filter((c) => c.system === systemId);
|
|
633
|
+
if (sysContainers.length === 0) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
await this.ensureChannel(`${sysId}.containers`, "Containers");
|
|
637
|
+
const healthLabels = ["none", "starting", "healthy", "unhealthy"];
|
|
638
|
+
for (const container of sysContainers) {
|
|
639
|
+
const cId = this.sanitize(container.name);
|
|
640
|
+
await this.ensureChannel(`${sysId}.containers.${cId}`, container.name);
|
|
641
|
+
await this.createAndSetState(`${sysId}.containers.${cId}.status`, {
|
|
642
|
+
name: "Status",
|
|
643
|
+
type: "string",
|
|
644
|
+
role: "text",
|
|
645
|
+
read: true,
|
|
646
|
+
write: false,
|
|
647
|
+
}, container.status);
|
|
648
|
+
await this.createAndSetState(`${sysId}.containers.${cId}.health`, {
|
|
649
|
+
name: "Health",
|
|
650
|
+
type: "string",
|
|
651
|
+
role: "text",
|
|
652
|
+
read: true,
|
|
653
|
+
write: false,
|
|
654
|
+
}, healthLabels[container.health] ?? "unknown");
|
|
655
|
+
await this.createAndSetState(`${sysId}.containers.${cId}.cpu`, {
|
|
656
|
+
name: "CPU Usage",
|
|
657
|
+
type: "number",
|
|
658
|
+
role: "level",
|
|
659
|
+
unit: "%",
|
|
660
|
+
min: 0,
|
|
661
|
+
max: 100,
|
|
662
|
+
read: true,
|
|
663
|
+
write: false,
|
|
664
|
+
}, container.cpu);
|
|
665
|
+
await this.createAndSetState(`${sysId}.containers.${cId}.memory`, {
|
|
666
|
+
name: "Memory",
|
|
667
|
+
type: "number",
|
|
668
|
+
role: "value",
|
|
669
|
+
unit: "MB",
|
|
670
|
+
read: true,
|
|
671
|
+
write: false,
|
|
672
|
+
}, container.memory);
|
|
673
|
+
await this.createAndSetState(`${sysId}.containers.${cId}.image`, {
|
|
674
|
+
name: "Image",
|
|
675
|
+
type: "string",
|
|
676
|
+
role: "text",
|
|
677
|
+
read: true,
|
|
678
|
+
write: false,
|
|
679
|
+
}, container.image);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
async ensureChannel(id, name) {
|
|
683
|
+
await this.adapter.setObjectNotExistsAsync(id, {
|
|
684
|
+
type: "channel",
|
|
685
|
+
common: { name },
|
|
686
|
+
native: {},
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
async deleteChannelIfExists(id) {
|
|
690
|
+
try {
|
|
691
|
+
const obj = await this.adapter.getObjectAsync(id);
|
|
692
|
+
if (obj) {
|
|
693
|
+
await this.adapter.delObjectAsync(id, { recursive: true });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// ignore
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
async createAndSetState(id, common, value) {
|
|
701
|
+
await this.adapter.setObjectNotExistsAsync(id, {
|
|
702
|
+
type: "state",
|
|
703
|
+
common,
|
|
704
|
+
native: {},
|
|
705
|
+
});
|
|
706
|
+
await this.adapter.setStateAsync(id, { val: value, ack: true });
|
|
707
|
+
}
|
|
708
|
+
computeTopAvgTemp(temps) {
|
|
709
|
+
if (!temps) {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
const values = Object.values(temps).filter((v) => typeof v === "number" && isFinite(v));
|
|
713
|
+
if (values.length === 0) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
values.sort((a, b) => b - a);
|
|
717
|
+
const top3 = values.slice(0, 3);
|
|
718
|
+
const avg = top3.reduce((sum, v) => sum + v, 0) / top3.length;
|
|
719
|
+
return Math.round(avg * 10) / 10;
|
|
720
|
+
}
|
|
721
|
+
formatUptime(seconds) {
|
|
722
|
+
const d = Math.floor(seconds / 86400);
|
|
723
|
+
const h = Math.floor((seconds % 86400) / 3600);
|
|
724
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
725
|
+
const parts = [];
|
|
726
|
+
if (d > 0) {
|
|
727
|
+
parts.push(`${d}d`);
|
|
728
|
+
}
|
|
729
|
+
if (h > 0) {
|
|
730
|
+
parts.push(`${h}h`);
|
|
731
|
+
}
|
|
732
|
+
if (m > 0 || parts.length === 0) {
|
|
733
|
+
parts.push(`${m}m`);
|
|
734
|
+
}
|
|
735
|
+
return parts.join(" ");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
exports.StateManager = StateManager;
|