@ralphwetzel/node-red-context-monitor 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -3
- package/lib/monitor.js +367 -0
- package/monitor.html +78 -13
- package/monitor.js +128 -242
- package/package.json +18 -4
- package/resources/object_monitor.png +0 -0
- package/resources/object_msg.png +0 -0
- package/resources/object_prop.png +0 -0
- package/resources/preview.png +0 -0
- package/.github/workflows/npm_publish.yml +0 -33
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @ralphwetzel/node-red-context-monitor
|
|
2
2
|
|
|
3
3
|
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/preview.png"
|
|
4
|
-
style="min-width:
|
|
4
|
+
style="min-width: 212px; width: 212px; align: center; border: 1px solid lightgray;"/>
|
|
5
5
|
|
|
6
6
|
A [Node-RED](https://www.nodered.org) node to monitor a [context](https://nodered.org/docs/user-guide/context).
|
|
7
7
|
|
|
@@ -11,7 +11,14 @@ This node allows to setup the reference to a context, then sends a message when
|
|
|
11
11
|
|
|
12
12
|
It sends a dedicated message on a separate port in case it detects that the value of the context was changed.
|
|
13
13
|
|
|
14
|
-
The message sent will carry the current value of the context as `msg.payload
|
|
14
|
+
The message sent will carry the current value of the context as `msg.payload`, the context key as `msg.topic`.
|
|
15
|
+
|
|
16
|
+
Monitoring details will be provided as `msg.monitoring`:
|
|
17
|
+
* The monitoring setup: `scope` & `key` always, `flow` (id) / `node` (id) if applicable.
|
|
18
|
+
* The id of the node that wrote to the context as `source`.
|
|
19
|
+
|
|
20
|
+
The message sent off the change port carries an additional property in `msg.monitoring`:
|
|
21
|
+
* The value overwritten as `previous`.
|
|
15
22
|
|
|
16
23
|
It is possible to monitor an infinite number of contexts with each instance of this node.
|
|
17
24
|
|
|
@@ -38,4 +45,38 @@ To monitor a `Node` scope context, set the scope to `Node`, then select flow & n
|
|
|
38
45
|
<img alt="node" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/node.png"
|
|
39
46
|
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
40
47
|
|
|
41
|
-
> Hint: This node doesn't create a context. It just tries to reference to those already existing. If you're referencing a non-existing context, no harm will happen.
|
|
48
|
+
> Hint: This node doesn't create a context. It just tries to reference to those already existing. If you're referencing a non-existing context, no harm will happen.
|
|
49
|
+
|
|
50
|
+
### Monitoring objects stored in context
|
|
51
|
+
You may of course define a setup that monitors objects stored in context.
|
|
52
|
+
|
|
53
|
+
If you create a reference to this object (stored in context) and write to its properties, this node issues its messages accordingly.
|
|
54
|
+
|
|
55
|
+
> Disclaimer: Monitoring changes to elements of an `Array` currently is not supported.
|
|
56
|
+
|
|
57
|
+
#### Example:
|
|
58
|
+
Monitoring context definition:
|
|
59
|
+
|
|
60
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/object_monitor.png"
|
|
61
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
62
|
+
|
|
63
|
+
Code in a `function` node:
|
|
64
|
+
|
|
65
|
+
``` javascript
|
|
66
|
+
// suppose, test_flow = { prop: "value" }
|
|
67
|
+
let obj = flow.get("test_flow");
|
|
68
|
+
obj.prop = "new";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Message sent by the node:
|
|
72
|
+
|
|
73
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/object_msg.png"
|
|
74
|
+
style="min-width: 310px; width: 310px; align: center; border: 1px solid lightgray;"/>
|
|
75
|
+
|
|
76
|
+
#### Object property monitoring
|
|
77
|
+
You may define a setup that doesn't monitor the (whole) object, but only one of its properties:
|
|
78
|
+
|
|
79
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/object_prop.png"
|
|
80
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
81
|
+
|
|
82
|
+
Such a monitor will react _only_, when this property and - if it's an object - its child properties are written to.
|
package/lib/monitor.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/*
|
|
2
|
+
node-red-context-monitor by @ralphwetzel
|
|
3
|
+
https://github.com/ralphwetzel/node-red-context-monitor
|
|
4
|
+
License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
let RED;
|
|
8
|
+
let monitors;
|
|
9
|
+
|
|
10
|
+
let init = function(_RED, cache) {
|
|
11
|
+
RED = _RED;
|
|
12
|
+
monitors = cache;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// *****
|
|
16
|
+
// The function to wrap the NR managed context into our monitoring object
|
|
17
|
+
// This cant' be done w/ a simple new Proxy(context, handler) as Proxy ensures that immutable functions
|
|
18
|
+
// stay immutable - which doesn't support our intentions!
|
|
19
|
+
|
|
20
|
+
// node_id: The node_id as passed to context.get
|
|
21
|
+
// flow_id: The flow_id as passed to context.get
|
|
22
|
+
// ctx: the contet as managed by Node-RED
|
|
23
|
+
// root_id (optional): if global/flow context is re-wrapped, this is the (original) node_id
|
|
24
|
+
|
|
25
|
+
let create_wrapper = function(node_id, flow_id, ctx, root_id) {
|
|
26
|
+
|
|
27
|
+
if (!RED || !monitors) {
|
|
28
|
+
console.log("*** Error while loading node-red-context-monitor:");
|
|
29
|
+
console.log("> Wrapper system not initialized.");
|
|
30
|
+
return ctx;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let context = ctx;
|
|
34
|
+
|
|
35
|
+
var context_id = node_id;
|
|
36
|
+
if (flow_id) {
|
|
37
|
+
context_id = node_id + ":" + flow_id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let obj = {};
|
|
41
|
+
let wrapper = new Proxy(obj, {
|
|
42
|
+
|
|
43
|
+
// *** Those 2 are valid only for function objects
|
|
44
|
+
|
|
45
|
+
// apply: function (target, thisArg, argumentsList) {
|
|
46
|
+
// return Reflect.apply(context, thisArg, argumentsList);
|
|
47
|
+
// },
|
|
48
|
+
// construct: function(target, argumentsList, newTarget) {
|
|
49
|
+
// return Reflect.construct(context, argumentsList, newTarget)
|
|
50
|
+
// },
|
|
51
|
+
|
|
52
|
+
// *** 'defineProperty' must reference the wrapper!
|
|
53
|
+
|
|
54
|
+
// defineProperty: function(target, propertyKey, attributes) {
|
|
55
|
+
// return Reflect.defineProperty(context, propertyKey, attributes);
|
|
56
|
+
// },
|
|
57
|
+
|
|
58
|
+
deleteProperty: function(target, propertyKey) {
|
|
59
|
+
return Reflect.deleteProperty(context, propertyKey);
|
|
60
|
+
},
|
|
61
|
+
get: function (target, propertyKey, receiver) {
|
|
62
|
+
if (["set", "get", "keys"].indexOf(propertyKey) > -1) {
|
|
63
|
+
return target[propertyKey];
|
|
64
|
+
} else if (propertyKey == "flow") {
|
|
65
|
+
// create a wrapper for the flow context
|
|
66
|
+
let flow_context = context.flow;
|
|
67
|
+
if (!flow_context) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
return create_wrapper(flow_id, undefined, flow_context, node_id);
|
|
71
|
+
} else if (propertyKey == "global") {
|
|
72
|
+
// create a wrapper for global context
|
|
73
|
+
let global_context = context.global;
|
|
74
|
+
if (!global_context) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
return create_wrapper('global', undefined, global_context, node_id);
|
|
78
|
+
}
|
|
79
|
+
return Reflect.get(context, propertyKey);
|
|
80
|
+
},
|
|
81
|
+
getOwnPropertyDescriptor: function (target, propertyKey) {
|
|
82
|
+
return Reflect.getOwnPropertyDescriptor(context, propertyKey);
|
|
83
|
+
},
|
|
84
|
+
getPrototypeOf: function (target){
|
|
85
|
+
return Reflect.getPrototypeOf(context);
|
|
86
|
+
},
|
|
87
|
+
has: function (target, propertyKey){
|
|
88
|
+
return Reflect.has(context, propertyKey);
|
|
89
|
+
},
|
|
90
|
+
isExtensible: function (target) {
|
|
91
|
+
return Reflect.isExtensible(context);
|
|
92
|
+
},
|
|
93
|
+
ownKeys: function (target) {
|
|
94
|
+
return Reflect.ownKeys(context);
|
|
95
|
+
},
|
|
96
|
+
preventExtensions: function (target) {
|
|
97
|
+
return Reflect.preventExtensions(context);
|
|
98
|
+
},
|
|
99
|
+
set: function (target, propertyKey, value, receiver) {
|
|
100
|
+
return Reflect.set(context, propertyKey, value);
|
|
101
|
+
},
|
|
102
|
+
setPrototypeOf: function (target, prototype) {
|
|
103
|
+
return Reflect.setPrototypeOf(context, prototype)
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
Object.defineProperties(wrapper, {
|
|
108
|
+
get: {
|
|
109
|
+
value: function(key, storage, callback) {
|
|
110
|
+
|
|
111
|
+
let create_object_wrapper = function(object, getter_key) {
|
|
112
|
+
|
|
113
|
+
let handler = {
|
|
114
|
+
get: (target, property, receiver) => {
|
|
115
|
+
let getted = Reflect.get(object, property);
|
|
116
|
+
|
|
117
|
+
// if getted is an object, wrap this (again)
|
|
118
|
+
// to ensure monitoring in case of direct reference access
|
|
119
|
+
if (
|
|
120
|
+
typeof getted === 'object' &&
|
|
121
|
+
!Array.isArray(getted) &&
|
|
122
|
+
getted !== null
|
|
123
|
+
) {
|
|
124
|
+
let prop_chain = property;
|
|
125
|
+
if (getter_key?.length) {
|
|
126
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
127
|
+
}
|
|
128
|
+
return create_object_wrapper(getted, prop_chain);
|
|
129
|
+
}
|
|
130
|
+
return getted;
|
|
131
|
+
},
|
|
132
|
+
set: function(target, propertyKey, value, receiver) {
|
|
133
|
+
// this is the monitoring function!
|
|
134
|
+
let previous_value = Reflect.get(object, propertyKey);
|
|
135
|
+
res = Reflect.set(object, propertyKey, value);
|
|
136
|
+
|
|
137
|
+
let prop_chain = propertyKey;
|
|
138
|
+
if (getter_key?.length) {
|
|
139
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value, prop_chain);
|
|
143
|
+
return res;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return new Proxy(object, handler);
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let create_array_wrapper = function(object, getter_key) {
|
|
152
|
+
|
|
153
|
+
let handler = {
|
|
154
|
+
get: (target, property, receiver) => {
|
|
155
|
+
let getted = Reflect.get(object, property);
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
typeof getted === 'object' &&
|
|
159
|
+
Array.isArray(getted)
|
|
160
|
+
) {
|
|
161
|
+
let prop_chain = property;
|
|
162
|
+
if (getter_key?.length) {
|
|
163
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
164
|
+
}
|
|
165
|
+
return create_object_wrapper(getted, prop_chain);
|
|
166
|
+
}
|
|
167
|
+
return getted;
|
|
168
|
+
},
|
|
169
|
+
set: function(target, propertyKey, value, receiver) {
|
|
170
|
+
// this is the monitoring function!
|
|
171
|
+
let previous_value = Reflect.get(object, propertyKey);
|
|
172
|
+
res = Reflect.set(object, propertyKey, value);
|
|
173
|
+
|
|
174
|
+
let prop_chain = propertyKey;
|
|
175
|
+
if (getter_key?.length) {
|
|
176
|
+
prop_chain = getter_key + "." + prop_chain;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value, prop_chain);
|
|
180
|
+
return res;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return new Proxy(object, handler);
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let apply_wrapper = function (getted) {
|
|
189
|
+
if (
|
|
190
|
+
typeof getted === 'object' &&
|
|
191
|
+
!Array.isArray(getted) &&
|
|
192
|
+
getted !== null
|
|
193
|
+
) {
|
|
194
|
+
return create_object_wrapper(getted);
|
|
195
|
+
}
|
|
196
|
+
return getted;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!callback && typeof storage === 'function') {
|
|
200
|
+
callback = storage;
|
|
201
|
+
storage = undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (callback) {
|
|
205
|
+
|
|
206
|
+
if (typeof callback !== 'function'){
|
|
207
|
+
throw new Error("Callback must be a function");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let callback_wrapper = function(err, value) {
|
|
211
|
+
let result = apply_wrapper(value);
|
|
212
|
+
callback(err, result);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
context.get(key, storage, callback_wrapper);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// be explicit: no (!!) callback here
|
|
220
|
+
let getted_value = context.get(key, storage);
|
|
221
|
+
return apply_wrapper(getted_value);
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
set: {
|
|
226
|
+
value: function(key, value, storage, callback) {
|
|
227
|
+
|
|
228
|
+
// this is the monitoring function!
|
|
229
|
+
let previous_value = context.get(key, storage);
|
|
230
|
+
|
|
231
|
+
if (!callback && typeof storage === 'function') {
|
|
232
|
+
callback = storage;
|
|
233
|
+
storage = undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (callback) {
|
|
237
|
+
|
|
238
|
+
if (typeof callback !== 'function'){
|
|
239
|
+
throw new Error("Callback must be a function");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let callback_wrapper = function(err, res) {
|
|
243
|
+
// intercept the callback & report success!
|
|
244
|
+
if (!err) {
|
|
245
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value);
|
|
246
|
+
}
|
|
247
|
+
return callback(err, res);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return context.set(key, value, storage, callback_wrapper);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let res = context.set(key, value, storage);
|
|
254
|
+
trigger(root_id ?? node_id, context_id, key, value, previous_value);
|
|
255
|
+
return res;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
keys: {
|
|
259
|
+
value: function(storage, callback) {
|
|
260
|
+
return context.keys(storage, callback);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return wrapper;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// *****
|
|
269
|
+
// The function to trigger the context-monitoring nodes
|
|
270
|
+
|
|
271
|
+
// root_id: The node.id that wrote to the context
|
|
272
|
+
// context_id: The context (id) that was written to
|
|
273
|
+
// key: The key (in context) that ws written to
|
|
274
|
+
// new_value: The value written
|
|
275
|
+
// previous_value: The value over-written
|
|
276
|
+
// prop_chain: In case the context is a (nested) object, the sequence of property keys.
|
|
277
|
+
|
|
278
|
+
let trigger = function(root_id, context_id, key, new_value, previous_value, prop_chain) {
|
|
279
|
+
|
|
280
|
+
function trigger_receivers(monitoring_nodes, message) {
|
|
281
|
+
monitoring_nodes.forEach(node => {
|
|
282
|
+
let n = RED.nodes.getNode(node.id);
|
|
283
|
+
if (n) {
|
|
284
|
+
|
|
285
|
+
let msg = RED.util.cloneMessage(message);
|
|
286
|
+
|
|
287
|
+
msg.monitoring = {
|
|
288
|
+
"scope": node.data.scope,
|
|
289
|
+
"source": root_id
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
switch (node.data.scope) {
|
|
293
|
+
case "global":
|
|
294
|
+
msg.monitoring.key = node.data.key;
|
|
295
|
+
break;
|
|
296
|
+
case "flow":
|
|
297
|
+
msg.monitoring.flow = node.data.flow;
|
|
298
|
+
msg.monitoring.key = node.data.key;
|
|
299
|
+
break;
|
|
300
|
+
case "node":
|
|
301
|
+
msg.monitoring.flow = node.data.flow;
|
|
302
|
+
msg.monitoring.node = node.data.node;
|
|
303
|
+
msg.monitoring.key = node.data.key;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
n.receive(msg);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let kc = key;
|
|
313
|
+
if (prop_chain?.length) {
|
|
314
|
+
kc += "." + prop_chain;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let keys = [];
|
|
318
|
+
try {
|
|
319
|
+
keys = RED.util.normalisePropertyExpression(kc);
|
|
320
|
+
} catch {}
|
|
321
|
+
|
|
322
|
+
for (let i=keys.length; i>0; i--) {
|
|
323
|
+
|
|
324
|
+
let cidk = context_id + ":" + keys.slice(0,i).join(".");
|
|
325
|
+
let mons = monitors[cidk] ?? [];
|
|
326
|
+
|
|
327
|
+
if (mons.length) {
|
|
328
|
+
let msg = {
|
|
329
|
+
payload: new_value,
|
|
330
|
+
previous: previous_value,
|
|
331
|
+
// do this once already here!
|
|
332
|
+
// ToDo: For non primitives: run a fast comparisor
|
|
333
|
+
changed: new_value != previous_value
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
msg.topic = kc;
|
|
337
|
+
|
|
338
|
+
trigger_receivers(mons, msg);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// End: The function ....
|
|
344
|
+
// *****
|
|
345
|
+
|
|
346
|
+
module.exports = {
|
|
347
|
+
init: init,
|
|
348
|
+
create_wrapper: create_wrapper
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (process.env.TESTING_CONTEXT_MONITOR) {
|
|
352
|
+
|
|
353
|
+
module.exports["trace"] = function(id) {
|
|
354
|
+
|
|
355
|
+
if (!RED || !monitors) {
|
|
356
|
+
console.log("*** Error while testing node-red-context-monitor:");
|
|
357
|
+
console.log("> Wrapper system not initialized.");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (id) {
|
|
362
|
+
return monitors[id];
|
|
363
|
+
}
|
|
364
|
+
return monitors;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
package/monitor.html
CHANGED
|
@@ -5,32 +5,70 @@
|
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
<script type="text/javascript">
|
|
8
|
+
|
|
9
|
+
function validate_context_key(key) {
|
|
10
|
+
try {
|
|
11
|
+
var parts = RED.utils.normalisePropertyExpression(key);
|
|
12
|
+
|
|
13
|
+
// If there's a changable part (e.g. test[msg.payload])
|
|
14
|
+
// this will become an array type in parts.
|
|
15
|
+
// Those might be ok for other situtions, but not here!
|
|
16
|
+
for (i=0; i<parts.length; i++) {
|
|
17
|
+
let p = parts[i];
|
|
18
|
+
if (Array.isArray(p)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
RED.nodes.registerType('context-monitor',{
|
|
9
29
|
category: 'input',
|
|
10
30
|
color: '#b0b0b0',
|
|
11
31
|
defaults: {
|
|
12
32
|
name: {value:""},
|
|
13
|
-
monitoring: {
|
|
33
|
+
monitoring: {
|
|
34
|
+
value: [],
|
|
35
|
+
validate: function(scopes, opt) {
|
|
36
|
+
let msg;
|
|
37
|
+
if (!scopes || scopes.length === 0) { return true }
|
|
38
|
+
for (let i=0; i<scopes.length; i++) {
|
|
39
|
+
let scope = scopes[i];
|
|
40
|
+
if (!validate_context_key(scope.key)) {
|
|
41
|
+
return RED._("node-red:change.errors.invalid-prop", {
|
|
42
|
+
property: scope.key
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
tostatus: {
|
|
50
|
+
value: false
|
|
51
|
+
}
|
|
14
52
|
},
|
|
15
53
|
inputs: 0,
|
|
16
54
|
outputs: 2,
|
|
17
55
|
outputLabels: ["context set", "context changed"],
|
|
18
|
-
icon: "font-awesome/fa-
|
|
56
|
+
icon: "font-awesome/fa-copyright",
|
|
19
57
|
label: function() {
|
|
20
58
|
if (this.name) {
|
|
21
59
|
return this.name;
|
|
22
60
|
}
|
|
23
61
|
let l = this.monitoring.length;
|
|
24
62
|
if (l > 1) {
|
|
25
|
-
return
|
|
63
|
+
return `context: ${l}`;
|
|
26
64
|
}
|
|
27
65
|
let key = this.monitoring[0]?.key;
|
|
28
66
|
if (key && key.length > 0) {
|
|
29
67
|
return key;
|
|
30
68
|
}
|
|
31
|
-
return "
|
|
69
|
+
return "context: 0";
|
|
32
70
|
},
|
|
33
|
-
paletteLabel: "
|
|
71
|
+
paletteLabel: "context",
|
|
34
72
|
oneditprepare: function() {
|
|
35
73
|
|
|
36
74
|
let node = this;
|
|
@@ -41,7 +79,8 @@
|
|
|
41
79
|
function add_context(tpl) {
|
|
42
80
|
let c = {
|
|
43
81
|
"scope": tpl.scope ?? "global",
|
|
44
|
-
"flow"
|
|
82
|
+
// decode "this flow" marker
|
|
83
|
+
"flow": ("." === tpl.flow) ? node.z : tpl.flow,
|
|
45
84
|
"node": tpl.node,
|
|
46
85
|
"key": tpl.key ?? ""
|
|
47
86
|
}
|
|
@@ -91,7 +130,7 @@
|
|
|
91
130
|
$(`<input type="text" id="context-scope-nodes-${index}" placeholder="Nodes">`).appendTo(line_node);
|
|
92
131
|
line_node.appendTo(fragment);
|
|
93
132
|
|
|
94
|
-
let line_key = $('<div class="form-row" style="margin-bottom:
|
|
133
|
+
let line_key = $('<div class="form-row" style="margin-bottom: 6px">');
|
|
95
134
|
// $(`<label for="context-scope-key-${index}"" style="margin-left:10px; width: 90px"><i class="fa fa-tag"></i> Key</label>`).appendTo(line_key);
|
|
96
135
|
$(`<label for="context-scope-key-${index}" style="margin-left:10px; width: 90px">Key: </label>`).appendTo(line_key);
|
|
97
136
|
$(`<input type="text" id="context-scope-key-${index}" placeholder="Context Variable Key">`).appendTo(line_key);
|
|
@@ -157,8 +196,8 @@
|
|
|
157
196
|
});
|
|
158
197
|
|
|
159
198
|
let flow_opts = [];
|
|
160
|
-
let group_opts = [];
|
|
161
|
-
let node_opts
|
|
199
|
+
// let group_opts = [];
|
|
200
|
+
let node_opts;
|
|
162
201
|
|
|
163
202
|
RED.nodes.eachWorkspace( cb => {
|
|
164
203
|
flow_opts.push({
|
|
@@ -194,7 +233,8 @@
|
|
|
194
233
|
// if ($(`#context-scope-${index}`).typedInput("value") == "group") {
|
|
195
234
|
// $(`#context-scope-key-${index}`).prop("disabled", gol < 1);
|
|
196
235
|
// }
|
|
197
|
-
|
|
236
|
+
|
|
237
|
+
node_opts = [];
|
|
198
238
|
RED.nodes.eachNode( n => {
|
|
199
239
|
|
|
200
240
|
if (n.z == value) {
|
|
@@ -271,6 +311,11 @@
|
|
|
271
311
|
data.key = $(this).val();
|
|
272
312
|
node.dirty = true;
|
|
273
313
|
}
|
|
314
|
+
$(this).toggleClass("input-error", !validate_context_key($(this).val()));
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
$(`#context-scope-key-${index}`).on( "input", function () {
|
|
318
|
+
$(this).toggleClass("input-error", !validate_context_key($(this).val()));
|
|
274
319
|
});
|
|
275
320
|
|
|
276
321
|
// initialize the form
|
|
@@ -303,7 +348,7 @@
|
|
|
303
348
|
}
|
|
304
349
|
}
|
|
305
350
|
|
|
306
|
-
$(`#context-scope-key-${index}`).val(data.key);
|
|
351
|
+
$(`#context-scope-key-${index}`).val(data.key).toggleClass("input-error", !validate_context_key(data.key));
|
|
307
352
|
|
|
308
353
|
_initing = false;
|
|
309
354
|
},
|
|
@@ -348,6 +393,9 @@
|
|
|
348
393
|
let top = el.position().top;
|
|
349
394
|
el.height(size.height - top);
|
|
350
395
|
|
|
396
|
+
let bottom = top + el.height();
|
|
397
|
+
let right = el.position().left + el.width();
|
|
398
|
+
|
|
351
399
|
let ti = $('[id*=context-scope-flows]:first');
|
|
352
400
|
let width;
|
|
353
401
|
if (ti.length > 0) {
|
|
@@ -356,6 +404,13 @@
|
|
|
356
404
|
if (width) {
|
|
357
405
|
$('[id*=context-scope-key]').outerWidth(width);
|
|
358
406
|
}
|
|
407
|
+
|
|
408
|
+
el = $('#tostatus-row');
|
|
409
|
+
let list = el.prev();
|
|
410
|
+
let add = list.find('.red-ui-editableList-addButton');
|
|
411
|
+
el.css("top" , add.position().top);
|
|
412
|
+
el.css("left", right - el.width());
|
|
413
|
+
|
|
359
414
|
},
|
|
360
415
|
oneditsave: function () {
|
|
361
416
|
let node = this;
|
|
@@ -379,8 +434,12 @@
|
|
|
379
434
|
// break;
|
|
380
435
|
case "node":
|
|
381
436
|
delete data.group;
|
|
382
|
-
|
|
437
|
+
}
|
|
383
438
|
|
|
439
|
+
if (data.flow == node.z) {
|
|
440
|
+
// set a special marker that 'this flow' shall be referenced
|
|
441
|
+
data.flow = ".";
|
|
442
|
+
}
|
|
384
443
|
})
|
|
385
444
|
|
|
386
445
|
node.monitoring = ctx;
|
|
@@ -399,11 +458,17 @@
|
|
|
399
458
|
<input type="text" id="node-input-monitoring" placeholder="Context">
|
|
400
459
|
</div> -->
|
|
401
460
|
<div class="form-row" style="margin-bottom:0;">
|
|
402
|
-
<label style="min-width:200px;"><i class="fa fa-
|
|
461
|
+
<label style="min-width:200px;"><i class="fa fa-copyright"></i> <span>Monitoring context definition</span></label>
|
|
403
462
|
</div>
|
|
404
463
|
<div class="form-row node-input-context-def-row">
|
|
405
464
|
<ol id="node-input-context-container"></ol>
|
|
406
465
|
</div>
|
|
466
|
+
<div id ="tostatus-row" style="position: absolute">
|
|
467
|
+
<label for="node-input-tostatus" style="margin-top: 4px">
|
|
468
|
+
<input type="checkbox" id="node-input-tostatus" style="display:inline-block; width:22px; vertical-align:top;" autocomplete="off">
|
|
469
|
+
<span>Show incoming data in node status</span>
|
|
470
|
+
</label>
|
|
471
|
+
</div>
|
|
407
472
|
</script>
|
|
408
473
|
|
|
409
474
|
<script type="text/markdown" data-help-name="context-monitor">
|
package/monitor.js
CHANGED
|
@@ -5,270 +5,100 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const fs = require('fs-extra');
|
|
8
|
-
const os = require("os");
|
|
9
8
|
const path = require("path");
|
|
10
|
-
|
|
11
|
-
let error_header = "*** Error while loading node-red-context-monitor:";
|
|
9
|
+
const monitor = require('./lib/monitor.js');
|
|
12
10
|
|
|
13
11
|
// this used to be the cache of ctx triggered @ set
|
|
14
12
|
// ... that's why it's the set_cache!
|
|
15
13
|
let set_cache = {};
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
// *****
|
|
16
|
+
// Patch support: Scan the require database for the path to a to-be-required file
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
let require_cache = {
|
|
19
|
+
".": require.main
|
|
20
|
+
}
|
|
22
21
|
|
|
23
|
-
function
|
|
22
|
+
function scan_for_require_path(req_path) {
|
|
24
23
|
|
|
25
|
-
let
|
|
24
|
+
let found;
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
26
|
+
if (process.env.NODE_RED_HOME) {
|
|
27
|
+
found = path.join(process.env.NODE_RED_HOME, "..", req_path);
|
|
28
|
+
console.log("@f", found);
|
|
29
|
+
if (fs.existsSync(found)) {
|
|
30
|
+
return found;
|
|
33
31
|
}
|
|
34
|
-
} catch (err) {
|
|
35
|
-
console.log(err);
|
|
36
|
-
console.log(error_header);
|
|
37
|
-
if (err.code == 'ENOENT') {
|
|
38
|
-
console.log("require.main.path not found.");
|
|
39
|
-
} else {
|
|
40
|
-
console.log("Error while handling require.main.path.")
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
32
|
}
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
rm = path.normalize(rm);
|
|
47
|
-
let rms = []
|
|
48
|
-
let rmp;
|
|
49
|
-
do {
|
|
50
|
-
rmp = path.parse(rm);
|
|
51
|
-
if (rmp.base.length > 0) {
|
|
52
|
-
rms.unshift(rmp.base);
|
|
53
|
-
rm = rmp.dir;
|
|
54
|
-
}
|
|
55
|
-
} while (rmp.base.length > 0)
|
|
34
|
+
let runner = 0;
|
|
56
35
|
|
|
57
|
-
|
|
36
|
+
while (runner < Object.keys(require_cache).length) {
|
|
58
37
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// dev: [...]/node-red/packages/node_modules/node-red
|
|
62
|
-
// install: [...]/lib/node_modules/node-red
|
|
63
|
-
// pi: /lib/node_modules/node-red/
|
|
38
|
+
let key = Object.keys(require_cache)[runner];
|
|
39
|
+
let entry = require_cache[key];
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
rms.splice(-2);
|
|
41
|
+
if (entry.id?.includes(req_path)) {
|
|
42
|
+
found = entry.id;
|
|
43
|
+
break;
|
|
69
44
|
}
|
|
45
|
+
|
|
46
|
+
let cc = entry.children;
|
|
47
|
+
cc.forEach(c => {
|
|
48
|
+
if (!(c.id in require_cache)) {
|
|
49
|
+
require_cache[c.id] = c;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
runner += 1;
|
|
70
54
|
}
|
|
71
55
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.log("> utils.js:", p);
|
|
82
|
-
return null;
|
|
56
|
+
if (found) {
|
|
57
|
+
if (!fs.existsSync(found)) {
|
|
58
|
+
console.log("*** Error while loading node-red-context-monitor:")
|
|
59
|
+
console.log("Failed to calculate path to required file.");
|
|
60
|
+
console.log("Please raise an issue @ our GitHub repository, stating the following information:");
|
|
61
|
+
console.log("> scanned for:", req_path);
|
|
62
|
+
console.log("> found:", found);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
83
65
|
}
|
|
84
66
|
|
|
85
|
-
|
|
67
|
+
console.log(found);
|
|
68
|
+
return found;
|
|
86
69
|
}
|
|
87
70
|
|
|
88
71
|
// End: "Patch support ..."
|
|
89
72
|
// *****
|
|
90
73
|
|
|
91
74
|
// *****
|
|
92
|
-
// Make available the Context Manager
|
|
75
|
+
// Make available & patch the Context Manager
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
if (!context_manager_path) return;
|
|
96
|
-
const context_manager = require(context_manager_path);
|
|
77
|
+
let context_manager;
|
|
97
78
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
// stay immutable - which doesn't support our intentions!
|
|
79
|
+
// When running a test, NODE_RED_HOME is not defined.
|
|
80
|
+
if (process.env.NODE_RED_HOME && !process.env.TESTING_CONTEXT_MONITOR) {
|
|
101
81
|
|
|
102
|
-
let
|
|
82
|
+
let context_manager_path = scan_for_require_path("@node-red/runtime/lib/nodes/context");
|
|
83
|
+
if (context_manager_path) {
|
|
103
84
|
|
|
104
|
-
|
|
85
|
+
context_manager = require(context_manager_path);
|
|
105
86
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let obj = {};
|
|
112
|
-
let wrapper = new Proxy(obj, {
|
|
113
|
-
|
|
114
|
-
// *** Those 2 are valid only for function objects
|
|
115
|
-
|
|
116
|
-
// apply: function (target, thisArg, argumentsList) {
|
|
117
|
-
// return Reflect.apply(context, thisArg, argumentsList);
|
|
118
|
-
// },
|
|
119
|
-
// construct: function(target, argumentsList, newTarget) {
|
|
120
|
-
// return Reflect.construct(context, argumentsList, newTarget)
|
|
121
|
-
// },
|
|
122
|
-
|
|
123
|
-
// *** 'defineProperty' must reference the wrapper!
|
|
124
|
-
|
|
125
|
-
// defineProperty: function(target, propertyKey, attributes) {
|
|
126
|
-
// return Reflect.defineProperty(context, propertyKey, attributes);
|
|
127
|
-
// },
|
|
128
|
-
|
|
129
|
-
deleteProperty: function(target, propertyKey) {
|
|
130
|
-
return Reflect.deleteProperty(context, propertyKey);
|
|
131
|
-
},
|
|
132
|
-
get: function (target, propertyKey, receiver) {
|
|
133
|
-
if (["set", "get", "keys"].indexOf(propertyKey) > -1) {
|
|
134
|
-
return target[propertyKey];
|
|
135
|
-
} else if (propertyKey == "flow") {
|
|
136
|
-
// create a wrapper for the flow context
|
|
137
|
-
let flow_context = context.flow;
|
|
138
|
-
if (!flow_context) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
return create_wrapper(flow_id, undefined, flow_context);
|
|
142
|
-
} else if (propertyKey == "global") {
|
|
143
|
-
// create a wrapper for global context
|
|
144
|
-
let global_context = context.global;
|
|
145
|
-
if (!global_context) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
return create_wrapper('global', undefined, global_context);
|
|
149
|
-
}
|
|
150
|
-
return Reflect.get(context, propertyKey, receiver);
|
|
151
|
-
},
|
|
152
|
-
getOwnPropertyDescriptor: function (target, propertyKey) {
|
|
153
|
-
return Reflect.getOwnPropertyDescriptor(context, propertyKey);
|
|
154
|
-
},
|
|
155
|
-
getPrototypeOf: function (target){
|
|
156
|
-
Reflect.getPrototypeOf(context);
|
|
157
|
-
},
|
|
158
|
-
has: function (target, propertyKey){
|
|
159
|
-
return Reflect.has(context, propertyKey);
|
|
160
|
-
},
|
|
161
|
-
isExtensible: function (target) {
|
|
162
|
-
return Reflect.isExtensible(context);
|
|
163
|
-
},
|
|
164
|
-
ownKeys: function (target) {
|
|
165
|
-
return Reflect.ownKeys(context);
|
|
166
|
-
},
|
|
167
|
-
preventExtensions: function (target) {
|
|
168
|
-
return Reflect.preventExtensions(context);
|
|
169
|
-
},
|
|
170
|
-
set: function (target, propertyKey, value, receiver) {
|
|
171
|
-
return Reflect.set(context, propertyKey, value, receiver);
|
|
172
|
-
},
|
|
173
|
-
setPrototypeOf: function (target, prototype) {
|
|
174
|
-
return Reflect.setPrototypeOf(context, prototype)
|
|
87
|
+
// patching into getContext (exported as 'get')
|
|
88
|
+
const orig_context_get = context_manager.get;
|
|
89
|
+
context_manager.get = function(nodeId, flowId) {
|
|
90
|
+
let context = orig_context_get(nodeId, flowId);
|
|
91
|
+
return monitor.create_wrapper(nodeId, flowId, context);
|
|
175
92
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
set: {
|
|
185
|
-
value: function(key, value, storage, callback) {
|
|
186
|
-
// this is the monitoring function!
|
|
187
|
-
let previous_value = context.get(key, storage);
|
|
188
|
-
let res = context.set(key, value, storage, callback);
|
|
189
|
-
trigger(context_id + ":" + key, value, previous_value);
|
|
190
|
-
return res;
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
keys: {
|
|
194
|
-
value: function(storage, callback) {
|
|
195
|
-
return context.keys(storage, callback);
|
|
196
|
-
}
|
|
93
|
+
|
|
94
|
+
// patching into getFlowContext
|
|
95
|
+
const orig_get_flow_context = context_manager.getFlowContext;
|
|
96
|
+
context_manager.getFlowContext = function(flowId, parentFlowId) {
|
|
97
|
+
let flow_context = orig_get_flow_context(flowId, parentFlowId);
|
|
98
|
+
return monitor.create_wrapper(flowId, undefined, flow_context);
|
|
197
99
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return wrapper;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// *****
|
|
204
|
-
// The function to trigger the context-monitoring nodes
|
|
205
|
-
|
|
206
|
-
let trigger = function(context_key_id, new_value, previous_value) {
|
|
207
|
-
|
|
208
|
-
function trigger_receivers(cache, message) {
|
|
209
|
-
cache.forEach(node => {
|
|
210
|
-
let n = _RED.nodes.getNode(node.id);
|
|
211
|
-
if (n) {
|
|
212
|
-
|
|
213
|
-
let msg = _RED.util.cloneMessage(message);
|
|
214
|
-
|
|
215
|
-
msg.topic = node.data.key;
|
|
216
|
-
msg.monitoring = {
|
|
217
|
-
"scope": node.data.scope,
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
switch (node.data.scope) {
|
|
221
|
-
case "global":
|
|
222
|
-
msg.monitoring.key = node.data.key;
|
|
223
|
-
break;
|
|
224
|
-
case "flow":
|
|
225
|
-
msg.monitoring.flow = node.data.flow;
|
|
226
|
-
msg.monitoring.key = node.data.key;
|
|
227
|
-
break;
|
|
228
|
-
case "node":
|
|
229
|
-
msg.monitoring.flow = node.data.flow;
|
|
230
|
-
msg.monitoring.node = node.data.node;
|
|
231
|
-
msg.monitoring.key = node.data.key;
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
n.receive(msg);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
let cache = set_cache[context_key_id] ?? [];
|
|
241
|
-
let msg = {
|
|
242
|
-
payload: new_value,
|
|
243
|
-
previous: previous_value
|
|
100
|
+
|
|
244
101
|
}
|
|
245
|
-
trigger_receivers(cache, msg);
|
|
246
|
-
|
|
247
|
-
// if (new_value !== previous_value) {
|
|
248
|
-
// let cache = change_cache[context_key_id] ?? [];
|
|
249
|
-
// let msg = {
|
|
250
|
-
// payload: new_value,
|
|
251
|
-
// previous: previous_value
|
|
252
|
-
// }
|
|
253
|
-
// trigger_receivers(cache, msg);
|
|
254
|
-
// }
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// End: The function ....
|
|
258
|
-
// *****
|
|
259
|
-
|
|
260
|
-
// patching into getContext (exported as 'get')
|
|
261
|
-
const orig_context_get = context_manager.get;
|
|
262
|
-
context_manager.get = function(nodeId, flowId) {
|
|
263
|
-
let context = orig_context_get(nodeId, flowId);
|
|
264
|
-
return create_wrapper(nodeId, flowId, context);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// patching into getFlowContext
|
|
268
|
-
const orig_get_flow_context = context_manager.getFlowContext;
|
|
269
|
-
context_manager.getFlowContext = function(flowId, parentFlowId) {
|
|
270
|
-
let flow_context = orig_get_flow_context(flowId, parentFlowId);
|
|
271
|
-
return create_wrapper(flowId, undefined, flow_context);
|
|
272
102
|
}
|
|
273
103
|
|
|
274
104
|
// End: "Make available ..."
|
|
@@ -279,31 +109,53 @@ context_manager.getFlowContext = function(flowId, parentFlowId) {
|
|
|
279
109
|
|
|
280
110
|
module.exports = function(RED) {
|
|
281
111
|
|
|
282
|
-
//
|
|
283
|
-
// necessary for trigger function to map node_id -> node object.
|
|
284
|
-
|
|
112
|
+
// Catch RED here & provide it to the monitor!
|
|
113
|
+
// It's necessary for trigger function to map node_id -> node object.
|
|
114
|
+
monitor.init(RED, set_cache);
|
|
285
115
|
|
|
286
116
|
function ContextMonitor(config) {
|
|
287
117
|
RED.nodes.createNode(this,config);
|
|
288
118
|
var node = this;
|
|
289
119
|
|
|
290
|
-
|
|
120
|
+
let scopes = config.monitoring ?? [];
|
|
121
|
+
node.data = scopes;
|
|
122
|
+
|
|
291
123
|
node.monitoring = [];
|
|
292
124
|
|
|
293
|
-
|
|
125
|
+
scopes.forEach( data => {
|
|
294
126
|
|
|
295
127
|
if (!data.key) return;
|
|
296
128
|
|
|
129
|
+
// support for complex keys
|
|
130
|
+
// test["mm"].value becomes test.mm.value
|
|
131
|
+
let key = data.key;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
let key_parts = RED.util.normalisePropertyExpression(key);
|
|
135
|
+
for (i=0; i<key_parts.length; i++) {
|
|
136
|
+
if (Array.isArray(key_parts[i])) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
key = key_parts.join('.');
|
|
141
|
+
} catch {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if ("." === data.flow) {
|
|
146
|
+
data.flow = node.z;
|
|
147
|
+
}
|
|
148
|
+
|
|
297
149
|
let ctx = "global";
|
|
298
150
|
switch (data.scope) {
|
|
299
151
|
case "global":
|
|
300
|
-
ctx = `global:${
|
|
152
|
+
ctx = `global:${key}`;
|
|
301
153
|
break;
|
|
302
154
|
case "flow":
|
|
303
|
-
ctx = `${data.flow}:${
|
|
155
|
+
ctx = `${data.flow}:${key}`;
|
|
304
156
|
break;
|
|
305
157
|
case "node":
|
|
306
|
-
ctx = `${data.node}:${data.flow}:${
|
|
158
|
+
ctx = `${data.node}:${data.flow}:${key}`;
|
|
307
159
|
break;
|
|
308
160
|
default:
|
|
309
161
|
return;
|
|
@@ -323,11 +175,16 @@ module.exports = function(RED) {
|
|
|
323
175
|
|
|
324
176
|
node.on("input", function(msg, send, done) {
|
|
325
177
|
|
|
178
|
+
let node = this;
|
|
179
|
+
let timeout;
|
|
180
|
+
|
|
326
181
|
// unfold & check if changed
|
|
327
182
|
let prev = msg.previous;
|
|
183
|
+
let changed = msg.changed;
|
|
328
184
|
delete msg.previous;
|
|
185
|
+
delete msg.changed;
|
|
329
186
|
|
|
330
|
-
if (
|
|
187
|
+
if (changed) {
|
|
331
188
|
// if changed, clone & emit @ second output terminal
|
|
332
189
|
let m = RED.util.cloneMessage(msg);
|
|
333
190
|
delete m._msgid;
|
|
@@ -338,17 +195,46 @@ module.exports = function(RED) {
|
|
|
338
195
|
send([msg, null]);
|
|
339
196
|
}
|
|
340
197
|
|
|
198
|
+
if (config.tostatus) {
|
|
199
|
+
|
|
200
|
+
if (timeout) {
|
|
201
|
+
clearTimeout(timeout);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
node.status({
|
|
205
|
+
fill: "blue",
|
|
206
|
+
shape: changed ? "dot" : "ring",
|
|
207
|
+
text: msg.topic + ": " + JSON.stringify(msg.payload)
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
timeout = setTimeout(function() {
|
|
211
|
+
node.status({});
|
|
212
|
+
timeout = undefined;
|
|
213
|
+
}, 2500);
|
|
214
|
+
}
|
|
215
|
+
|
|
341
216
|
done();
|
|
342
217
|
});
|
|
343
218
|
node.on("close",function() {
|
|
344
219
|
// remove this nodes ctx(s) from the trigger list
|
|
345
220
|
node.monitoring.forEach( ctx => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
221
|
+
let sc = set_cache[ctx];
|
|
222
|
+
if (sc) {
|
|
223
|
+
set_cache[ctx] = sc.filter( n => {
|
|
224
|
+
return n.id !== node.id;
|
|
225
|
+
})
|
|
226
|
+
if (set_cache[ctx].length < 1) {
|
|
227
|
+
delete set_cache[ctx];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
349
230
|
})
|
|
231
|
+
|
|
232
|
+
if (timeout) {
|
|
233
|
+
clearTimeout(timeout);
|
|
234
|
+
timeout = undefined;
|
|
235
|
+
}
|
|
350
236
|
});
|
|
351
237
|
}
|
|
352
238
|
|
|
353
239
|
RED.nodes.registerType("context-monitor",ContextMonitor);
|
|
354
|
-
}
|
|
240
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ralphwetzel/node-red-context-monitor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A Node-RED node to monitor a context.",
|
|
5
5
|
"main": "monitor.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "mocha \"test/**/*_spec.js\""
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -30,6 +30,20 @@
|
|
|
30
30
|
"fs-extra": "^11.1.1"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
34
|
-
}
|
|
33
|
+
"node": ">=14.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"mocha": "^10.2.0",
|
|
37
|
+
"node-red-node-test-helper": "^0.3.2",
|
|
38
|
+
"node-red": "^3.1.0"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"/examples",
|
|
42
|
+
"/lib",
|
|
43
|
+
"/resources",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"monitor.*",
|
|
46
|
+
"package.json",
|
|
47
|
+
"README.md"
|
|
48
|
+
]
|
|
35
49
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/resources/preview.png
CHANGED
|
Binary file
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: "NPM Publish"
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [created]
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
build:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v3
|
|
15
|
-
- uses: actions/setup-node@v3
|
|
16
|
-
with:
|
|
17
|
-
node-version: 16
|
|
18
|
-
# - run: npm ci
|
|
19
|
-
# - run: npm test
|
|
20
|
-
|
|
21
|
-
publish-npm:
|
|
22
|
-
needs: build
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
steps:
|
|
25
|
-
- uses: actions/checkout@v3
|
|
26
|
-
- uses: actions/setup-node@v3
|
|
27
|
-
with:
|
|
28
|
-
node-version: 16
|
|
29
|
-
registry-url: https://registry.npmjs.org/
|
|
30
|
-
# - run: npm ci
|
|
31
|
-
- run: npm publish --access public
|
|
32
|
-
env:
|
|
33
|
-
NODE_AUTH_TOKEN: ${{secrets.npm_access_token}}
|