@iobroker/js-controller-common 7.0.0 → 7.0.1
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/build/cjs/lib/common/notificationHandler.d.ts +40 -15
- package/build/cjs/lib/common/notificationHandler.js +71 -58
- package/build/cjs/lib/common/notificationHandler.js.map +3 -3
- package/build/cjs/lib/common/zipFiles.js +51 -57
- package/build/cjs/lib/common/zipFiles.js.map +2 -2
- package/build/esm/lib/common/notificationHandler.d.ts +40 -15
- package/build/esm/lib/common/notificationHandler.d.ts.map +1 -1
- package/build/esm/lib/common/notificationHandler.js +87 -70
- package/build/esm/lib/common/notificationHandler.js.map +1 -1
- package/build/esm/lib/common/zipFiles.d.ts.map +1 -1
- package/build/esm/lib/common/zipFiles.js +52 -62
- package/build/esm/lib/common/zipFiles.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +9 -9
|
@@ -9,13 +9,20 @@ import type { Client as StatesInRedisClient } from '@iobroker/db-states-redis';
|
|
|
9
9
|
import type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';
|
|
10
10
|
import type Winston from 'winston';
|
|
11
11
|
type MultilingualObject = Exclude<ioBroker.StringOrTranslated, string>;
|
|
12
|
+
/** Settings to configure an instance of NotificationHandler */
|
|
12
13
|
export interface NotificationHandlerSettings {
|
|
14
|
+
/** Name of the host (not id - hence, without `system.host.` prefix) */
|
|
13
15
|
host: string;
|
|
16
|
+
/** States DB instance */
|
|
14
17
|
states: StatesInRedisClient;
|
|
18
|
+
/** Objects DB instance */
|
|
15
19
|
objects: ObjectsInRedisClient;
|
|
20
|
+
/** Logger instance */
|
|
16
21
|
log: Winston.Logger | Console;
|
|
22
|
+
/** Prefix for the log messages */
|
|
17
23
|
logPrefix: string;
|
|
18
24
|
}
|
|
25
|
+
/** A notification config entry represents the top level of the notification structure */
|
|
19
26
|
export interface NotificationsConfigEntry {
|
|
20
27
|
/** e.g. system */
|
|
21
28
|
scope: string;
|
|
@@ -23,16 +30,23 @@ export interface NotificationsConfigEntry {
|
|
|
23
30
|
name: MultilingualObject;
|
|
24
31
|
/** multilingual description */
|
|
25
32
|
description: MultilingualObject;
|
|
33
|
+
/** The notification categories inside this scope */
|
|
26
34
|
categories: CategoryConfigEntry[];
|
|
27
35
|
}
|
|
28
36
|
export type Severity = 'info' | 'notify' | 'alert';
|
|
37
|
+
/** A notification category entry represents one of the categories of a scope */
|
|
29
38
|
export interface CategoryConfigEntry {
|
|
39
|
+
/** Id of the category */
|
|
30
40
|
category: string;
|
|
41
|
+
/** Name of the category */
|
|
31
42
|
name: MultilingualObject;
|
|
32
43
|
/** Allows defining the severity of the notification with `info` being the lowest, `notify` representing middle priority, `alert` representing high priority and often containing critical information */
|
|
33
44
|
severity: Severity;
|
|
45
|
+
/** Description of the category */
|
|
34
46
|
description: MultilingualObject;
|
|
47
|
+
/** Regex where `console.error` output is parsed against to check if a notification of this category should be generated */
|
|
35
48
|
regex: string[];
|
|
49
|
+
/** Restrict the maximum number of messages for this category */
|
|
36
50
|
limit: number;
|
|
37
51
|
}
|
|
38
52
|
interface NotificationMessageObject {
|
|
@@ -40,24 +54,27 @@ interface NotificationMessageObject {
|
|
|
40
54
|
ts: number;
|
|
41
55
|
contextData?: ioBroker.NotificationContextData;
|
|
42
56
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
name: MultilingualObject;
|
|
51
|
-
severity: Severity;
|
|
52
|
-
instances: {
|
|
53
|
-
[instance: string]: {
|
|
54
|
-
messages: NotificationMessageObject[];
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
};
|
|
57
|
+
interface FilteredNotificationCategory {
|
|
58
|
+
description: MultilingualObject;
|
|
59
|
+
name: MultilingualObject;
|
|
60
|
+
severity: Severity;
|
|
61
|
+
instances: {
|
|
62
|
+
[instance: string]: {
|
|
63
|
+
messages: NotificationMessageObject[];
|
|
58
64
|
};
|
|
59
65
|
};
|
|
60
66
|
}
|
|
67
|
+
interface FilteredNotificationScope {
|
|
68
|
+
description: MultilingualObject;
|
|
69
|
+
name: MultilingualObject;
|
|
70
|
+
categories: {
|
|
71
|
+
[category: string]: FilteredNotificationCategory;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** The filtered information as a response for `getFilteredInformation` */
|
|
75
|
+
export interface FilteredNotificationInformation {
|
|
76
|
+
[scope: string]: FilteredNotificationScope;
|
|
77
|
+
}
|
|
61
78
|
interface AddMessageOptions {
|
|
62
79
|
/** Scope of the message */
|
|
63
80
|
scope: string;
|
|
@@ -70,6 +87,9 @@ interface AddMessageOptions {
|
|
|
70
87
|
/** Additional context for the notification which can be used by notification processing adapters */
|
|
71
88
|
contextData?: ioBroker.NotificationContextData;
|
|
72
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The Notification handler class provides an interface to manage the ioBroker notification system
|
|
92
|
+
*/
|
|
73
93
|
export declare class NotificationHandler {
|
|
74
94
|
private states;
|
|
75
95
|
private objects;
|
|
@@ -79,6 +99,11 @@ export declare class NotificationHandler {
|
|
|
79
99
|
private readonly setup;
|
|
80
100
|
private readonly logPrefix;
|
|
81
101
|
private readonly host;
|
|
102
|
+
/**
|
|
103
|
+
* Creates a new instance of the notification handler class
|
|
104
|
+
*
|
|
105
|
+
* @param settings settings to configure the handler instance
|
|
106
|
+
*/
|
|
82
107
|
constructor(settings: NotificationHandlerSettings);
|
|
83
108
|
/**
|
|
84
109
|
* Get all adapter instances on this host and store their notifications config - and clears up removed instances notifications - should be called once after init
|
|
@@ -95,9 +95,9 @@ class NotificationHandler {
|
|
|
95
95
|
} catch (e) {
|
|
96
96
|
throw new Error(`Could not get notifications setup from instances: ${e.message}`);
|
|
97
97
|
}
|
|
98
|
-
for (const
|
|
99
|
-
for (const
|
|
100
|
-
for (const instance of Object.keys(
|
|
98
|
+
for (const notificationsCategory of Object.values(this.currentNotifications)) {
|
|
99
|
+
for (const instancesInfo of Object.values(notificationsCategory)) {
|
|
100
|
+
for (const instance of Object.keys(instancesInfo)) {
|
|
101
101
|
if (!instancesOnHost.includes(instance) && instance !== `system.host.${this.host}`) {
|
|
102
102
|
this.log.info(`${this.logPrefix} Instance ${instance} removed from host, clear notifications`);
|
|
103
103
|
await this.clearNotifications(null, null, instance);
|
|
@@ -134,13 +134,21 @@ class NotificationHandler {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
if (Array.isArray(scopeObj.categories) && scopeObj.categories.length) {
|
|
137
|
-
|
|
137
|
+
const setupScope = this.setup[scopeObj.scope] || {
|
|
138
138
|
name: scopeObj.name,
|
|
139
139
|
description: scopeObj.description,
|
|
140
140
|
categories: {}
|
|
141
141
|
};
|
|
142
|
+
this.setup[scopeObj.scope] = setupScope;
|
|
142
143
|
for (const categoryObj of scopeObj.categories) {
|
|
143
|
-
|
|
144
|
+
const setupCategory = setupScope.categories[categoryObj.category] || {
|
|
145
|
+
regex: [],
|
|
146
|
+
limit: categoryObj.limit,
|
|
147
|
+
name: categoryObj.name,
|
|
148
|
+
severity: categoryObj.severity,
|
|
149
|
+
description: categoryObj.description
|
|
150
|
+
};
|
|
151
|
+
setupScope.categories[categoryObj.category] = setupCategory;
|
|
144
152
|
try {
|
|
145
153
|
let regex = [];
|
|
146
154
|
if (Array.isArray(categoryObj.regex)) {
|
|
@@ -150,15 +158,9 @@ class NotificationHandler {
|
|
|
150
158
|
} else if (typeof categoryObj.regex === "string") {
|
|
151
159
|
regex = [new RegExp(categoryObj.regex)];
|
|
152
160
|
}
|
|
153
|
-
|
|
154
|
-
regex,
|
|
155
|
-
limit: categoryObj.limit,
|
|
156
|
-
name: categoryObj.name,
|
|
157
|
-
severity: categoryObj.severity,
|
|
158
|
-
description: categoryObj.description
|
|
159
|
-
};
|
|
161
|
+
setupCategory.regex = regex;
|
|
160
162
|
} catch (e) {
|
|
161
|
-
this.log.error(`${this.logPrefix} Cannot store ${JSON.stringify(categoryObj.regex)} for scope "${scopeObj.scope}", category "${categoryObj.category}": ${e.message}`);
|
|
163
|
+
this.log.error(`${this.logPrefix} Cannot store regex "${JSON.stringify(categoryObj.regex)}" for scope "${scopeObj.scope}", category "${categoryObj.category}": ${e.message}`);
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
}
|
|
@@ -186,25 +188,30 @@ class NotificationHandler {
|
|
|
186
188
|
if (!category) {
|
|
187
189
|
categories = this._parseText(scope, message);
|
|
188
190
|
}
|
|
191
|
+
const currentScopeObj = this.currentNotifications[scope] || {};
|
|
192
|
+
this.currentNotifications[scope] = currentScopeObj;
|
|
189
193
|
for (const _category of categories) {
|
|
190
|
-
if (_category) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
if (!_category) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const currentCategoryObj = currentScopeObj[_category] || {};
|
|
198
|
+
currentScopeObj[_category] = currentCategoryObj;
|
|
199
|
+
const currentInstances = currentCategoryObj[instance] || [];
|
|
200
|
+
currentCategoryObj[instance] = currentInstances;
|
|
201
|
+
const setupCategory = this.setup[scope]?.categories[_category];
|
|
202
|
+
if (!setupCategory) {
|
|
203
|
+
this.log.warn(`${this.logPrefix} No configuration found for scope "${scope}" and category "${_category}"`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
while (setupCategory.limit < currentInstances.length + 1) {
|
|
207
|
+
currentInstances.pop();
|
|
202
208
|
}
|
|
209
|
+
currentInstances.unshift({ message, ts: Date.now(), contextData });
|
|
203
210
|
}
|
|
204
211
|
if (import_js_controller_common_db.tools.isObject(this.currentNotifications[scope])) {
|
|
205
|
-
for (const
|
|
206
|
-
const categoryCounter = Object.keys(
|
|
207
|
-
stateVal[
|
|
212
|
+
for (const [categoryName, category2] of Object.entries(currentScopeObj)) {
|
|
213
|
+
const categoryCounter = Object.keys(category2).length;
|
|
214
|
+
stateVal[categoryName] = { count: categoryCounter };
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
try {
|
|
@@ -217,14 +224,14 @@ class NotificationHandler {
|
|
|
217
224
|
}
|
|
218
225
|
}
|
|
219
226
|
async _updateScopeStates() {
|
|
220
|
-
for (const scope of Object.
|
|
227
|
+
for (const [scope, scopeObj] of Object.entries(this.currentNotifications)) {
|
|
221
228
|
const stateVal = {};
|
|
222
|
-
for (const category of Object.
|
|
223
|
-
const catCounter = Object.keys(
|
|
229
|
+
for (const [category, categoryObj] of Object.entries(scopeObj)) {
|
|
230
|
+
const catCounter = Object.keys(categoryObj).length;
|
|
224
231
|
stateVal[category] = { count: catCounter };
|
|
225
232
|
}
|
|
226
233
|
try {
|
|
227
|
-
await this.states.
|
|
234
|
+
await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {
|
|
228
235
|
val: JSON.stringify(stateVal),
|
|
229
236
|
ack: true
|
|
230
237
|
});
|
|
@@ -235,13 +242,15 @@ class NotificationHandler {
|
|
|
235
242
|
}
|
|
236
243
|
_parseText(scope, message) {
|
|
237
244
|
const categories = [];
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
const scopeObj = this.setup[scope];
|
|
246
|
+
if (!scopeObj) {
|
|
247
|
+
return categories;
|
|
248
|
+
}
|
|
249
|
+
for (const [categoryId, categoryObj] of Object.entries(scopeObj.categories)) {
|
|
250
|
+
for (const regex of categoryObj.regex) {
|
|
251
|
+
if (regex.test(message)) {
|
|
252
|
+
categories.push(categoryId);
|
|
253
|
+
break;
|
|
245
254
|
}
|
|
246
255
|
}
|
|
247
256
|
}
|
|
@@ -263,38 +272,41 @@ class NotificationHandler {
|
|
|
263
272
|
}
|
|
264
273
|
getFilteredInformation(scopeFilter, categoryFilter, instanceFilter) {
|
|
265
274
|
const res = {};
|
|
266
|
-
for (const scope of Object.
|
|
275
|
+
for (const [scope, currentNotification] of Object.entries(this.currentNotifications)) {
|
|
267
276
|
if (scopeFilter && scopeFilter !== scope) {
|
|
268
277
|
continue;
|
|
269
278
|
}
|
|
270
|
-
|
|
279
|
+
const scopeObj = this.setup[scope];
|
|
280
|
+
if (!scopeObj) {
|
|
271
281
|
continue;
|
|
272
282
|
}
|
|
273
|
-
|
|
283
|
+
const scopeResult = {
|
|
274
284
|
categories: {},
|
|
275
|
-
description:
|
|
276
|
-
name:
|
|
285
|
+
description: scopeObj.description,
|
|
286
|
+
name: scopeObj.name
|
|
277
287
|
};
|
|
278
|
-
|
|
288
|
+
res[scope] = scopeResult;
|
|
289
|
+
for (const [category, currentCategoryObj] of Object.entries(currentNotification)) {
|
|
279
290
|
if (categoryFilter && categoryFilter !== category) {
|
|
280
291
|
continue;
|
|
281
292
|
}
|
|
282
|
-
const categoryObj =
|
|
293
|
+
const categoryObj = scopeObj.categories[category];
|
|
283
294
|
if (!categoryObj) {
|
|
284
295
|
continue;
|
|
285
296
|
}
|
|
286
|
-
|
|
297
|
+
const resultCategories = {
|
|
287
298
|
instances: {},
|
|
288
299
|
description: categoryObj.description,
|
|
289
300
|
name: categoryObj.name,
|
|
290
301
|
severity: categoryObj.severity
|
|
291
302
|
};
|
|
292
|
-
|
|
303
|
+
scopeResult.categories[category] = resultCategories;
|
|
304
|
+
for (const [instance, instanceObj] of Object.entries(currentCategoryObj)) {
|
|
293
305
|
if (instanceFilter && instanceFilter !== instance) {
|
|
294
306
|
continue;
|
|
295
307
|
}
|
|
296
|
-
|
|
297
|
-
messages:
|
|
308
|
+
resultCategories.instances[instance] = {
|
|
309
|
+
messages: instanceObj
|
|
298
310
|
};
|
|
299
311
|
}
|
|
300
312
|
}
|
|
@@ -303,7 +315,8 @@ class NotificationHandler {
|
|
|
303
315
|
}
|
|
304
316
|
async clearNotifications(scopeFilter, categoryFilter, instanceFilter) {
|
|
305
317
|
for (const scope of Object.keys(this.currentNotifications)) {
|
|
306
|
-
|
|
318
|
+
const currentNotificationScope = this.currentNotifications[scope];
|
|
319
|
+
if (!currentNotificationScope) {
|
|
307
320
|
delete this.currentNotifications[scope];
|
|
308
321
|
continue;
|
|
309
322
|
}
|
|
@@ -317,29 +330,29 @@ class NotificationHandler {
|
|
|
317
330
|
} catch (e) {
|
|
318
331
|
this.log.error(`${this.logPrefix} Could not get state for scope "${scope}": ${e.message}`);
|
|
319
332
|
}
|
|
320
|
-
for (const category of Object.
|
|
333
|
+
for (const [category, categoryObj] of Object.entries(currentNotificationScope)) {
|
|
321
334
|
if (categoryFilter && categoryFilter !== category) {
|
|
322
335
|
continue;
|
|
323
336
|
}
|
|
324
337
|
let categoryCounter = 0;
|
|
325
|
-
for (const instance of Object.keys(
|
|
338
|
+
for (const instance of Object.keys(categoryObj)) {
|
|
326
339
|
if (instanceFilter && instanceFilter !== instance) {
|
|
327
340
|
categoryCounter += 1;
|
|
328
341
|
continue;
|
|
329
342
|
}
|
|
330
|
-
delete
|
|
343
|
+
delete categoryObj[instance];
|
|
331
344
|
}
|
|
332
345
|
stateVal[category] = { count: categoryCounter };
|
|
333
|
-
if (!Object.keys(
|
|
334
|
-
delete
|
|
346
|
+
if (!Object.keys(categoryObj).length) {
|
|
347
|
+
delete currentNotificationScope[category];
|
|
335
348
|
delete stateVal[category];
|
|
336
349
|
}
|
|
337
350
|
}
|
|
338
|
-
if (!Object.keys(
|
|
351
|
+
if (!Object.keys(currentNotificationScope).length) {
|
|
339
352
|
delete this.currentNotifications[scope];
|
|
340
353
|
}
|
|
341
354
|
try {
|
|
342
|
-
await this.states.
|
|
355
|
+
await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {
|
|
343
356
|
val: JSON.stringify(stateVal),
|
|
344
357
|
ack: true
|
|
345
358
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/common/notificationHandler.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Notification handler class\n *\n * 2021-2024 foxriver76 <moritz.heusinger@gmail.com>\n */\n\nimport fs from 'fs-extra';\nimport { tools } from '@iobroker/js-controller-common-db';\nimport path from 'node:path';\nimport type { Client as StatesInRedisClient } from '@iobroker/db-states-redis';\nimport type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';\nimport type Winston from 'winston';\n\ntype MultilingualObject = Exclude<ioBroker.StringOrTranslated, string>;\n\nexport interface NotificationHandlerSettings {\n host: string;\n states: StatesInRedisClient;\n objects: ObjectsInRedisClient;\n log: Winston.Logger | Console;\n logPrefix: string;\n}\n\nexport interface NotificationsConfigEntry {\n /** e.g. system */\n scope: string;\n /** multilingual name */\n name: MultilingualObject;\n /** multilingual description */\n description: MultilingualObject;\n categories: CategoryConfigEntry[];\n}\n\nexport type Severity = 'info' | 'notify' | 'alert';\n\nexport interface CategoryConfigEntry {\n category: string;\n name: MultilingualObject;\n /** Allows defining the severity of the notification with `info` being the lowest, `notify` representing middle priority, `alert` representing high priority and often containing critical information */\n severity: Severity;\n description: MultilingualObject;\n regex: string[];\n limit: number;\n}\n\ninterface NotificationMessageObject {\n message: string;\n ts: number;\n contextData?: ioBroker.NotificationContextData;\n}\n\ninterface NotificationsObject {\n [scope: string]: {\n [category: string]: {\n [instance: string]: NotificationMessageObject[];\n };\n };\n}\n\nexport interface FilteredNotificationInformation {\n [scope: string]: {\n description: MultilingualObject;\n name: MultilingualObject;\n categories: {\n [category: string]: {\n description: MultilingualObject;\n name: MultilingualObject;\n severity: Severity;\n instances: {\n [instance: string]: {\n messages: NotificationMessageObject[];\n };\n };\n };\n };\n };\n}\n\ninterface NotificationSetupCategory {\n regex: RegExp[];\n limit: number;\n name: MultilingualObject;\n severity: Severity;\n description: MultilingualObject;\n}\n\ninterface NotificationSetupObject {\n [scope: string]: {\n name: MultilingualObject;\n description: MultilingualObject;\n categories: {\n [category: string]: NotificationSetupCategory;\n };\n };\n}\n\ninterface ScopeStateValue {\n [category: string]: {\n count: number;\n };\n}\n\ninterface AddMessageOptions {\n /** Scope of the message */\n scope: string;\n /** Category of the message, if non we check against regex of scope */\n category?: string | null;\n /** Message to add */\n message: string;\n /** Instance e.g., hm-rpc.1 or hostname, if hostname it needs to be prefixed like system.host.rpi */\n instance: string;\n /** Additional context for the notification which can be used by notification processing adapters */\n contextData?: ioBroker.NotificationContextData;\n}\n\nexport class NotificationHandler {\n private states: StatesInRedisClient;\n private objects: ObjectsInRedisClient;\n private log: Winston.Logger | Console;\n private currentNotifications: NotificationsObject = {};\n // default data dir is relative to controllerDir\n private readonly dataDir = path.join(tools.getControllerDir(), tools.getDefaultDataDir());\n private readonly setup: NotificationSetupObject = {};\n private readonly logPrefix: string;\n private readonly host: string;\n\n constructor(settings: NotificationHandlerSettings) {\n this.states = settings.states;\n this.objects = settings.objects;\n this.log = settings.log;\n this.logPrefix = settings.logPrefix;\n this.host = settings.host;\n\n // load notifications file\n this._loadNotifications();\n }\n\n /**\n * Get all adapter instances on this host and store their notifications config - and clears up removed instances notifications - should be called once after init\n */\n async getSetupOfAllAdaptersFromHost(): Promise<void> {\n // create the initial notifications object\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications`, {\n type: 'folder',\n common: {\n name: {\n en: 'Notifications',\n de: 'Benachrichtigungen',\n ru: '\u0423\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F',\n pt: 'Notifica\u00E7\u00F5es',\n nl: 'Meldingen',\n fr: 'Notifications',\n it: 'Notifiche',\n es: 'Notificaciones',\n pl: 'Powiadomienia',\n uk: '\u0421\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u043D\u044F',\n 'zh-cn': '\u901A\u77E5\u4E8B\u9879',\n },\n },\n native: {},\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not create notifications object: ${e.message}`);\n }\n }\n\n const instancesOnHost = [];\n // get all instances\n try {\n const res = await this.objects.getObjectViewAsync('system', 'instance', {\n startkey: 'system.adapter.',\n endkey: 'system.adapter.\\u9999',\n });\n\n for (const entry of res.rows) {\n // check that instance has notification settings\n if (entry.value.notifications) {\n await this.addConfig(entry.value.notifications);\n }\n\n if (entry.value.common && entry.value.common.host === this.host) {\n // if it's on our current host\n instancesOnHost.push(entry.id);\n }\n }\n } catch (e) {\n throw new Error(`Could not get notifications setup from instances: ${e.message}`);\n }\n\n // now clear up all notifications that do not belong to our host anymore\n for (const scope of Object.keys(this.currentNotifications)) {\n for (const category of Object.keys(this.currentNotifications[scope])) {\n for (const instance of Object.keys(this.currentNotifications[scope][category])) {\n if (!instancesOnHost.includes(instance) && instance !== `system.host.${this.host}`) {\n // instance no longer on host\n this.log.info(`${this.logPrefix} Instance ${instance} removed from host, clear notifications`);\n await this.clearNotifications(null, null, instance);\n }\n }\n }\n }\n\n // now sync RAM with state\n await this._updateScopeStates();\n }\n\n /**\n * Add a new category to the given scope with a provided optional list of regex\n *\n * @param notifications - Array with notifications\n */\n async addConfig(notifications: NotificationsConfigEntry[]): Promise<void> {\n // if valid attributes, store it\n if (Array.isArray(notifications)) {\n for (const scopeObj of notifications) {\n // create the state object for each scope if non-existing\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications.${scopeObj.scope}`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications.${scopeObj.scope}`, {\n type: 'state',\n common: {\n type: 'object',\n role: 'value',\n read: true,\n write: false,\n name: scopeObj.name,\n desc: scopeObj.description,\n },\n native: {},\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not create notifications object for scope \"${scopeObj.scope}\": ${e.message}`,\n );\n }\n }\n\n if (Array.isArray(scopeObj.categories) && scopeObj.categories.length) {\n // only if scope has at least one category\n this.setup[scopeObj.scope] = this.setup[scopeObj.scope] || {\n name: scopeObj.name,\n description: scopeObj.description,\n categories: {},\n };\n\n for (const categoryObj of scopeObj.categories) {\n this.setup[scopeObj.scope].categories[categoryObj.category] =\n this.setup[scopeObj.scope].categories[categoryObj.category] || {};\n try {\n let regex: RegExp[] = [];\n if (Array.isArray(categoryObj.regex)) {\n for (const regexString of categoryObj.regex) {\n regex.push(new RegExp(regexString));\n }\n } else if (typeof categoryObj.regex === 'string') {\n // if someone passes a string, convert to a single entry array\n regex = [new RegExp(categoryObj.regex)];\n }\n\n // we overwrite config, maybe it would also make sense to only add the new regex to existing ones\n this.setup[scopeObj.scope].categories[categoryObj.category] = {\n regex,\n limit: categoryObj.limit,\n name: categoryObj.name,\n severity: categoryObj.severity,\n description: categoryObj.description,\n };\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Cannot store ${JSON.stringify(categoryObj.regex)} for scope \"${\n scopeObj.scope\n }\", category \"${categoryObj.category}\": ${e.message}`,\n );\n }\n }\n }\n }\n }\n }\n\n /**\n * Add a message to the scope and category\n *\n * @param options The scope, category, message, instance and contextData information\n */\n async addMessage(options: AddMessageOptions): Promise<void> {\n const { message, scope, category, contextData } = options;\n let { instance } = options;\n\n if (typeof instance !== 'string') {\n this.log.error(\n `${this.logPrefix} [addMessage] Instance has to be of type \"string\", got \"${typeof instance}\"`,\n );\n return;\n }\n\n if (!instance.startsWith('system.adapter.') && !instance.startsWith('system.host.')) {\n instance = `system.adapter.${instance}`;\n }\n\n // get state of the scope\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state?.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n let categories = [category];\n\n if (!category) {\n // check regex - in parse text we can have multiple matches\n categories = this._parseText(scope, message);\n }\n\n for (const _category of categories) {\n if (_category) {\n this.currentNotifications[scope] = this.currentNotifications[scope] || {};\n this.currentNotifications[scope][_category] = this.currentNotifications[scope][_category] || {};\n // array of all messages for instance/category\n this.currentNotifications[scope][_category][instance] =\n this.currentNotifications[scope][_category][instance] || [];\n\n if (!this.setup[scope]?.categories[_category]) {\n // no setup for this instance/category combination found - so we have nothing to add\n this.log.warn(\n `${this.logPrefix} No configuration found for scope \"${scope}\" and category \"${_category}\"`,\n );\n continue;\n }\n\n // if limit exceeded, remove last element - use while if it somehow grew too big\n while (\n this.setup[scope].categories[_category].limit <\n this.currentNotifications[scope][_category][instance].length + 1\n ) {\n this.currentNotifications[scope][_category][instance].pop();\n }\n\n // add a new element at the beginning\n this.currentNotifications[scope][_category][instance].unshift({ message, ts: Date.now(), contextData });\n }\n }\n\n // now count all messages of this scope - if nothing matched it can be undefined, and we can skip\n if (tools.isObject(this.currentNotifications[scope])) {\n for (const _category of Object.keys(this.currentNotifications[scope])) {\n const categoryCounter = Object.keys(this.currentNotifications[scope][_category]).length;\n\n stateVal[_category] = { count: categoryCounter };\n }\n }\n\n // set updated scope state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`);\n }\n }\n\n /**\n * Updates all scope states by current notifications in RAM\n */\n private async _updateScopeStates(): Promise<void> {\n for (const scope of Object.keys(this.currentNotifications)) {\n const stateVal: ScopeStateValue = {};\n\n for (const category of Object.keys(this.currentNotifications[scope])) {\n // count the number of instances with this error\n const catCounter = Object.keys(this.currentNotifications[scope][category]).length;\n stateVal[category] = { count: catCounter };\n }\n\n // set updated scope state\n try {\n await this.states.setStateAsync(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check the given message against all regular expressions of the given scope\n *\n * @param scope - scope of the message\n * @param message - message to check\n */\n private _parseText(scope: string, message: string): string[] {\n const categories = [];\n if (this.setup[scope]) {\n for (const [categoryId, categoryObj] of Object.entries(this.setup[scope].categories)) {\n // check all regular expressions for this category\n for (const regex of categoryObj.regex) {\n if (regex.test(message)) {\n // matching category\n categories.push(categoryId);\n // no further testing needed for this category\n break;\n }\n }\n }\n }\n\n return categories;\n }\n\n /**\n * Load notifications from a file\n */\n private _loadNotifications(): void {\n try {\n this.currentNotifications = fs.readJSONSync(path.join(this.dataDir, 'notifications.json'));\n } catch (e) {\n // at first start its normal, that we cannot read notifications, so just info\n this.log.debug(`${this.logPrefix} Could not read notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Save current notifications to file\n */\n storeNotifications(): void {\n try {\n fs.writeJSONSync(path.join(this.dataDir, 'notifications.json'), this.currentNotifications);\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not write notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Returns the stored notifications matching the filters with description and name\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n getFilteredInformation(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): FilteredNotificationInformation {\n const res: FilteredNotificationInformation = {};\n for (const scope of Object.keys(this.currentNotifications)) {\n if (scopeFilter && scopeFilter !== scope) {\n // scope filtered out\n continue;\n }\n\n if (!this.setup[scope]) {\n // no scope set up\n continue;\n }\n\n res[scope] = {\n categories: {},\n description: this.setup[scope].description,\n name: this.setup[scope].name,\n };\n\n for (const category of Object.keys(this.currentNotifications[scope])) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n const categoryObj = this.setup[scope].categories[category];\n\n if (!categoryObj) {\n // no category set up\n continue;\n }\n\n res[scope].categories[category] = {\n instances: {},\n description: categoryObj.description,\n name: categoryObj.name,\n severity: categoryObj.severity,\n };\n\n for (const instance of Object.keys(this.currentNotifications[scope][category])) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out\n continue;\n }\n\n res[scope].categories[category].instances[instance] = {\n messages: this.currentNotifications[scope][category][instance],\n };\n }\n }\n }\n return res;\n }\n\n /**\n * Clears the stored notifications matching the filters\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n async clearNotifications(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): Promise<void> {\n for (const scope of Object.keys(this.currentNotifications)) {\n if (!this.currentNotifications[scope]) {\n delete this.currentNotifications[scope];\n continue;\n }\n if (scopeFilter && scopeFilter !== scope) {\n // skip this, because not desired to be deleted\n continue;\n }\n\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state && state.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n for (const category of Object.keys(this.currentNotifications[scope])) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n let categoryCounter = 0;\n for (const instance of Object.keys(this.currentNotifications[scope][category])) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out - message counter remains\n categoryCounter += 1;\n continue;\n }\n\n // not filtered out -> remove\n delete this.currentNotifications[scope][category][instance];\n }\n\n stateVal[category] = { count: categoryCounter };\n\n // check if all instances of this category deleted\n if (!Object.keys(this.currentNotifications[scope][category]).length) {\n delete this.currentNotifications[scope][category];\n delete stateVal[category];\n }\n }\n\n // check if all categories of this scope deleted\n if (!Object.keys(this.currentNotifications[scope]).length) {\n delete this.currentNotifications[scope];\n }\n\n // set updated state\n try {\n await this.states.setStateAsync(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check if given scope exists in config\n *\n * @param scope - scope to be checked for\n */\n scopeExists(scope: string): boolean {\n return !!this.setup[scope];\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;AAMA,sBAAe;AACf,qCAAsB;AACtB,uBAAiB;
|
|
6
|
-
"names": ["path", "fs"]
|
|
4
|
+
"sourcesContent": ["/**\n * Notification handler class\n *\n * 2021-2024 foxriver76 <moritz.heusinger@gmail.com>\n */\n\nimport fs from 'fs-extra';\nimport { tools } from '@iobroker/js-controller-common-db';\nimport path from 'node:path';\nimport type { Client as StatesInRedisClient } from '@iobroker/db-states-redis';\nimport type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';\nimport type Winston from 'winston';\n\ntype MultilingualObject = Exclude<ioBroker.StringOrTranslated, string>;\n\n/** Settings to configure an instance of NotificationHandler */\nexport interface NotificationHandlerSettings {\n /** Name of the host (not id - hence, without `system.host.` prefix) */\n host: string;\n /** States DB instance */\n states: StatesInRedisClient;\n /** Objects DB instance */\n objects: ObjectsInRedisClient;\n /** Logger instance */\n log: Winston.Logger | Console;\n /** Prefix for the log messages */\n logPrefix: string;\n}\n\n/** A notification config entry represents the top level of the notification structure */\nexport interface NotificationsConfigEntry {\n /** e.g. system */\n scope: string;\n /** multilingual name */\n name: MultilingualObject;\n /** multilingual description */\n description: MultilingualObject;\n /** The notification categories inside this scope */\n categories: CategoryConfigEntry[];\n}\n\nexport type Severity = 'info' | 'notify' | 'alert';\n\n/** A notification category entry represents one of the categories of a scope */\nexport interface CategoryConfigEntry {\n /** Id of the category */\n category: string;\n /** Name of the category */\n name: MultilingualObject;\n /** Allows defining the severity of the notification with `info` being the lowest, `notify` representing middle priority, `alert` representing high priority and often containing critical information */\n severity: Severity;\n /** Description of the category */\n description: MultilingualObject;\n /** Regex where `console.error` output is parsed against to check if a notification of this category should be generated */\n regex: string[];\n /** Restrict the maximum number of messages for this category */\n limit: number;\n}\n\ninterface NotificationMessageObject {\n message: string;\n ts: number;\n contextData?: ioBroker.NotificationContextData;\n}\n\ninterface NotificationsCategory {\n [category: string]: {\n [instance: string]: NotificationMessageObject[];\n };\n}\n\ninterface NotificationsObject {\n [scope: string]: NotificationsCategory;\n}\n\ninterface FilteredNotificationCategory {\n description: MultilingualObject;\n name: MultilingualObject;\n severity: Severity;\n instances: {\n [instance: string]: {\n messages: NotificationMessageObject[];\n };\n };\n}\n\ninterface FilteredNotificationScope {\n description: MultilingualObject;\n name: MultilingualObject;\n categories: {\n [category: string]: FilteredNotificationCategory;\n };\n}\n\n/** The filtered information as a response for `getFilteredInformation` */\nexport interface FilteredNotificationInformation {\n [scope: string]: FilteredNotificationScope;\n}\n\ninterface NotificationSetupCategory {\n regex: RegExp[];\n limit: number;\n name: MultilingualObject;\n severity: Severity;\n description: MultilingualObject;\n}\n\ninterface NotificationSetupObject {\n [scope: string]: {\n name: MultilingualObject;\n description: MultilingualObject;\n categories: {\n [category: string]: NotificationSetupCategory;\n };\n };\n}\n\ninterface ScopeStateValue {\n [category: string]: {\n count: number;\n };\n}\n\ninterface AddMessageOptions {\n /** Scope of the message */\n scope: string;\n /** Category of the message, if non we check against regex of scope */\n category?: string | null;\n /** Message to add */\n message: string;\n /** Instance e.g., hm-rpc.1 or hostname, if hostname it needs to be prefixed like system.host.rpi */\n instance: string;\n /** Additional context for the notification which can be used by notification processing adapters */\n contextData?: ioBroker.NotificationContextData;\n}\n\n/**\n * The Notification handler class provides an interface to manage the ioBroker notification system\n */\nexport class NotificationHandler {\n private states: StatesInRedisClient;\n private objects: ObjectsInRedisClient;\n private log: Winston.Logger | Console;\n private currentNotifications: NotificationsObject = {};\n // default data dir is relative to controllerDir\n private readonly dataDir = path.join(tools.getControllerDir(), tools.getDefaultDataDir());\n private readonly setup: NotificationSetupObject = {};\n private readonly logPrefix: string;\n private readonly host: string;\n\n /**\n * Creates a new instance of the notification handler class\n *\n * @param settings settings to configure the handler instance\n */\n constructor(settings: NotificationHandlerSettings) {\n this.states = settings.states;\n this.objects = settings.objects;\n this.log = settings.log;\n this.logPrefix = settings.logPrefix;\n this.host = settings.host;\n\n // load notifications file\n this._loadNotifications();\n }\n\n /**\n * Get all adapter instances on this host and store their notifications config - and clears up removed instances notifications - should be called once after init\n */\n async getSetupOfAllAdaptersFromHost(): Promise<void> {\n // create the initial notifications object\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications`, {\n type: 'folder',\n common: {\n name: {\n en: 'Notifications',\n de: 'Benachrichtigungen',\n ru: '\u0423\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F',\n pt: 'Notifica\u00E7\u00F5es',\n nl: 'Meldingen',\n fr: 'Notifications',\n it: 'Notifiche',\n es: 'Notificaciones',\n pl: 'Powiadomienia',\n uk: '\u0421\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u043D\u044F',\n 'zh-cn': '\u901A\u77E5\u4E8B\u9879',\n },\n },\n native: {},\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not create notifications object: ${e.message}`);\n }\n }\n\n const instancesOnHost = [];\n // get all instances\n try {\n const res = await this.objects.getObjectViewAsync('system', 'instance', {\n startkey: 'system.adapter.',\n endkey: 'system.adapter.\\u9999',\n });\n\n for (const entry of res.rows) {\n // check that instance has notification settings\n if (entry.value.notifications) {\n await this.addConfig(entry.value.notifications);\n }\n\n if (entry.value.common && entry.value.common.host === this.host) {\n // if it's on our current host\n instancesOnHost.push(entry.id);\n }\n }\n } catch (e) {\n throw new Error(`Could not get notifications setup from instances: ${e.message}`);\n }\n\n // now clear up all notifications that do not belong to our host anymore\n for (const notificationsCategory of Object.values(this.currentNotifications)) {\n for (const instancesInfo of Object.values(notificationsCategory)) {\n for (const instance of Object.keys(instancesInfo)) {\n if (!instancesOnHost.includes(instance) && instance !== `system.host.${this.host}`) {\n // instance no longer on host\n this.log.info(`${this.logPrefix} Instance ${instance} removed from host, clear notifications`);\n await this.clearNotifications(null, null, instance);\n }\n }\n }\n }\n\n // now sync RAM with state\n await this._updateScopeStates();\n }\n\n /**\n * Add a new category to the given scope with a provided optional list of regex\n *\n * @param notifications - Array with notifications\n */\n async addConfig(notifications: NotificationsConfigEntry[]): Promise<void> {\n // if valid attributes, store it\n if (Array.isArray(notifications)) {\n for (const scopeObj of notifications) {\n // create the state object for each scope if non-existing\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications.${scopeObj.scope}`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications.${scopeObj.scope}`, {\n type: 'state',\n common: {\n type: 'object',\n role: 'value',\n read: true,\n write: false,\n name: scopeObj.name,\n desc: scopeObj.description,\n },\n native: {},\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not create notifications object for scope \"${scopeObj.scope}\": ${e.message}`,\n );\n }\n }\n\n if (Array.isArray(scopeObj.categories) && scopeObj.categories.length) {\n const setupScope = this.setup[scopeObj.scope] || {\n name: scopeObj.name,\n description: scopeObj.description,\n categories: {},\n };\n\n // only if scope has at least one category\n this.setup[scopeObj.scope] = setupScope;\n\n for (const categoryObj of scopeObj.categories) {\n const setupCategory = setupScope.categories[categoryObj.category] || {\n regex: [],\n limit: categoryObj.limit,\n name: categoryObj.name,\n severity: categoryObj.severity,\n description: categoryObj.description,\n };\n setupScope.categories[categoryObj.category] = setupCategory;\n\n try {\n let regex: RegExp[] = [];\n if (Array.isArray(categoryObj.regex)) {\n for (const regexString of categoryObj.regex) {\n regex.push(new RegExp(regexString));\n }\n } else if (typeof categoryObj.regex === 'string') {\n // if someone passes a string, convert to a single entry array\n regex = [new RegExp(categoryObj.regex)];\n }\n\n setupCategory.regex = regex;\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Cannot store regex \"${JSON.stringify(categoryObj.regex)}\" for scope \"${\n scopeObj.scope\n }\", category \"${categoryObj.category}\": ${e.message}`,\n );\n }\n }\n }\n }\n }\n }\n\n /**\n * Add a message to the scope and category\n *\n * @param options The scope, category, message, instance and contextData information\n */\n async addMessage(options: AddMessageOptions): Promise<void> {\n const { message, scope, category, contextData } = options;\n let { instance } = options;\n\n if (typeof instance !== 'string') {\n this.log.error(\n `${this.logPrefix} [addMessage] Instance has to be of type \"string\", got \"${typeof instance}\"`,\n );\n return;\n }\n\n if (!instance.startsWith('system.adapter.') && !instance.startsWith('system.host.')) {\n instance = `system.adapter.${instance}`;\n }\n\n // get state of the scope\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state?.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n let categories = [category];\n\n if (!category) {\n // check regex - in parse text we can have multiple matches\n categories = this._parseText(scope, message);\n }\n\n const currentScopeObj = this.currentNotifications[scope] || {};\n this.currentNotifications[scope] = currentScopeObj;\n\n for (const _category of categories) {\n if (!_category) {\n continue;\n }\n\n const currentCategoryObj = currentScopeObj[_category] || {};\n currentScopeObj[_category] = currentCategoryObj;\n\n // array of all messages for instance/category\n const currentInstances = currentCategoryObj[instance] || [];\n currentCategoryObj[instance] = currentInstances;\n\n const setupCategory = this.setup[scope]?.categories[_category];\n\n if (!setupCategory) {\n // no setup for this instance/category combination found - so we have nothing to add\n this.log.warn(\n `${this.logPrefix} No configuration found for scope \"${scope}\" and category \"${_category}\"`,\n );\n continue;\n }\n\n // if limit exceeded, remove last element - use while if it somehow grew too big\n while (setupCategory.limit < currentInstances.length + 1) {\n currentInstances.pop();\n }\n\n // add a new element at the beginning\n currentInstances.unshift({ message, ts: Date.now(), contextData });\n }\n\n // now count all messages of this scope - if nothing matched it can be undefined, and we can skip\n if (tools.isObject(this.currentNotifications[scope])) {\n for (const [categoryName, category] of Object.entries(currentScopeObj)) {\n const categoryCounter = Object.keys(category).length;\n\n stateVal[categoryName] = { count: categoryCounter };\n }\n }\n\n // set updated scope state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`);\n }\n }\n\n /**\n * Updates all scope states by current notifications in RAM\n */\n private async _updateScopeStates(): Promise<void> {\n for (const [scope, scopeObj] of Object.entries(this.currentNotifications)) {\n const stateVal: ScopeStateValue = {};\n\n for (const [category, categoryObj] of Object.entries(scopeObj)) {\n // count the number of instances with this error\n const catCounter = Object.keys(categoryObj).length;\n stateVal[category] = { count: catCounter };\n }\n\n // set updated scope state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check the given message against all regular expressions of the given scope\n *\n * @param scope - scope of the message\n * @param message - message to check\n */\n private _parseText(scope: string, message: string): string[] {\n const categories: string[] = [];\n\n const scopeObj = this.setup[scope];\n\n if (!scopeObj) {\n return categories;\n }\n\n for (const [categoryId, categoryObj] of Object.entries(scopeObj.categories)) {\n // check all regular expressions for this category\n for (const regex of categoryObj.regex) {\n if (regex.test(message)) {\n // matching category\n categories.push(categoryId);\n // no further testing needed for this category\n break;\n }\n }\n }\n\n return categories;\n }\n\n /**\n * Load notifications from a file\n */\n private _loadNotifications(): void {\n try {\n this.currentNotifications = fs.readJSONSync(path.join(this.dataDir, 'notifications.json'));\n } catch (e) {\n // at first start its normal, that we cannot read notifications, so just info\n this.log.debug(`${this.logPrefix} Could not read notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Save current notifications to file\n */\n storeNotifications(): void {\n try {\n fs.writeJSONSync(path.join(this.dataDir, 'notifications.json'), this.currentNotifications);\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not write notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Returns the stored notifications matching the filters with description and name\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n getFilteredInformation(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): FilteredNotificationInformation {\n const res: FilteredNotificationInformation = {};\n for (const [scope, currentNotification] of Object.entries(this.currentNotifications)) {\n if (scopeFilter && scopeFilter !== scope) {\n // scope filtered out\n continue;\n }\n\n const scopeObj = this.setup[scope];\n\n if (!scopeObj) {\n // no scope set up\n continue;\n }\n\n const scopeResult: FilteredNotificationScope = {\n categories: {},\n description: scopeObj.description,\n name: scopeObj.name,\n };\n\n res[scope] = scopeResult;\n\n for (const [category, currentCategoryObj] of Object.entries(currentNotification)) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n const categoryObj = scopeObj.categories[category];\n\n if (!categoryObj) {\n // no category set up\n continue;\n }\n\n const resultCategories: FilteredNotificationCategory = {\n instances: {},\n description: categoryObj.description,\n name: categoryObj.name,\n severity: categoryObj.severity,\n };\n\n scopeResult.categories[category] = resultCategories;\n\n for (const [instance, instanceObj] of Object.entries(currentCategoryObj)) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out\n continue;\n }\n\n resultCategories.instances[instance] = {\n messages: instanceObj,\n };\n }\n }\n }\n return res;\n }\n\n /**\n * Clears the stored notifications matching the filters\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n async clearNotifications(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): Promise<void> {\n for (const scope of Object.keys(this.currentNotifications)) {\n const currentNotificationScope = this.currentNotifications[scope];\n\n if (!currentNotificationScope) {\n delete this.currentNotifications[scope];\n continue;\n }\n if (scopeFilter && scopeFilter !== scope) {\n // skip this, because not desired to be deleted\n continue;\n }\n\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state && state.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n for (const [category, categoryObj] of Object.entries(currentNotificationScope)) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n let categoryCounter = 0;\n for (const instance of Object.keys(categoryObj)) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out - message counter remains\n categoryCounter += 1;\n continue;\n }\n\n // not filtered out -> remove\n delete categoryObj[instance];\n }\n\n stateVal[category] = { count: categoryCounter };\n\n // check if all instances of this category deleted\n if (!Object.keys(categoryObj).length) {\n delete currentNotificationScope[category];\n delete stateVal[category];\n }\n }\n\n // check if all categories of this scope deleted\n if (!Object.keys(currentNotificationScope).length) {\n delete this.currentNotifications[scope];\n }\n\n // set updated state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check if given scope exists in config\n *\n * @param scope - scope to be checked for\n */\n scopeExists(scope: string): boolean {\n return !!this.setup[scope];\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["path", "category", "fs"]
|
|
7
7
|
}
|