@ralphwetzel/node-red-context-monitor 1.0.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/.github/workflows/npm_publish.yml +33 -0
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/examples/context-monitor.json +177 -0
- package/monitor.html +449 -0
- package/monitor.js +354 -0
- package/package.json +35 -0
- package/resources/flow.png +0 -0
- package/resources/global.png +0 -0
- package/resources/node.png +0 -0
- package/resources/preview.png +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
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}}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Ralph Wetzel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @ralphwetzel/node-red-context-monitor
|
|
2
|
+
|
|
3
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/preview.png"
|
|
4
|
+
style="min-width: 250px; width: 250px; align: center; border: 1px solid lightgray;"/>
|
|
5
|
+
|
|
6
|
+
A [Node-RED](https://www.nodered.org) node to monitor a [context](https://nodered.org/docs/user-guide/context).
|
|
7
|
+
|
|
8
|
+
### What it does
|
|
9
|
+
|
|
10
|
+
This node allows to setup the reference to a context, then sends a message when this context is written to.
|
|
11
|
+
|
|
12
|
+
It sends a dedicated message on a separate port in case it detects that the value of the context was changed.
|
|
13
|
+
|
|
14
|
+
The message sent will carry the current value of the context as `msg.payload`. Monitoring details will be provided as `msg.monitoring`.
|
|
15
|
+
|
|
16
|
+
It is possible to monitor an infinite number of contexts with each instance of this node.
|
|
17
|
+
|
|
18
|
+
This node supports the three [context scope levels](https://nodered.org/docs/user-guide/context#context-scopes) `Global`, `Flow` & `Node`.
|
|
19
|
+
|
|
20
|
+
### Installation
|
|
21
|
+
|
|
22
|
+
Use the Node-RED palette manager to install this node.
|
|
23
|
+
|
|
24
|
+
### Details
|
|
25
|
+
|
|
26
|
+
To monitor a `Global` scope context, set the scope to `Global` and provide the context key.
|
|
27
|
+
|
|
28
|
+
<img alt="global" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/global.png"
|
|
29
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
30
|
+
|
|
31
|
+
To monitor a `Flow` scope context, set the scope to `Flow`, then select the owning flow and provide the context key.
|
|
32
|
+
|
|
33
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/flow.png"
|
|
34
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
35
|
+
|
|
36
|
+
To monitor a `Node` scope context, set the scope to `Node`, then select flow & node and provide the context key.
|
|
37
|
+
|
|
38
|
+
<img alt="node" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/node.png"
|
|
39
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
40
|
+
|
|
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.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "4980f94dd2c07b62",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "Context Monitoring Example",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "",
|
|
8
|
+
"env": []
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "f2874060375785b7",
|
|
12
|
+
"type": "inject",
|
|
13
|
+
"z": "4980f94dd2c07b62",
|
|
14
|
+
"name": "",
|
|
15
|
+
"props": [
|
|
16
|
+
{
|
|
17
|
+
"p": "payload"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"p": "topic",
|
|
21
|
+
"vt": "str"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"repeat": "",
|
|
25
|
+
"crontab": "",
|
|
26
|
+
"once": false,
|
|
27
|
+
"onceDelay": 0.1,
|
|
28
|
+
"topic": "",
|
|
29
|
+
"payload": "",
|
|
30
|
+
"payloadType": "date",
|
|
31
|
+
"x": 140,
|
|
32
|
+
"y": 80,
|
|
33
|
+
"wires": [
|
|
34
|
+
[
|
|
35
|
+
"c096819863211fb9"
|
|
36
|
+
]
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "db53945bf5a57413",
|
|
41
|
+
"type": "debug",
|
|
42
|
+
"z": "4980f94dd2c07b62",
|
|
43
|
+
"name": "debug 88",
|
|
44
|
+
"active": true,
|
|
45
|
+
"tosidebar": true,
|
|
46
|
+
"console": false,
|
|
47
|
+
"tostatus": false,
|
|
48
|
+
"complete": "false",
|
|
49
|
+
"statusVal": "",
|
|
50
|
+
"statusType": "auto",
|
|
51
|
+
"x": 660,
|
|
52
|
+
"y": 80,
|
|
53
|
+
"wires": []
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "c096819863211fb9",
|
|
57
|
+
"type": "change",
|
|
58
|
+
"z": "4980f94dd2c07b62",
|
|
59
|
+
"name": "",
|
|
60
|
+
"rules": [
|
|
61
|
+
{
|
|
62
|
+
"t": "set",
|
|
63
|
+
"p": "flow_test",
|
|
64
|
+
"pt": "flow",
|
|
65
|
+
"to": "payload",
|
|
66
|
+
"tot": "msg"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"t": "set",
|
|
70
|
+
"p": "global_test",
|
|
71
|
+
"pt": "global",
|
|
72
|
+
"to": "hello!",
|
|
73
|
+
"tot": "str"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"action": "",
|
|
77
|
+
"property": "",
|
|
78
|
+
"from": "",
|
|
79
|
+
"to": "",
|
|
80
|
+
"reg": false,
|
|
81
|
+
"x": 300,
|
|
82
|
+
"y": 80,
|
|
83
|
+
"wires": [
|
|
84
|
+
[
|
|
85
|
+
"b1d17d7418f7a28f"
|
|
86
|
+
]
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "db3afa60f9a080a8",
|
|
91
|
+
"type": "debug",
|
|
92
|
+
"z": "4980f94dd2c07b62",
|
|
93
|
+
"name": "debug on set",
|
|
94
|
+
"active": true,
|
|
95
|
+
"tosidebar": true,
|
|
96
|
+
"console": false,
|
|
97
|
+
"tostatus": false,
|
|
98
|
+
"complete": "true",
|
|
99
|
+
"targetType": "full",
|
|
100
|
+
"statusVal": "",
|
|
101
|
+
"statusType": "auto",
|
|
102
|
+
"x": 330,
|
|
103
|
+
"y": 180,
|
|
104
|
+
"wires": []
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "b1d17d7418f7a28f",
|
|
108
|
+
"type": "function",
|
|
109
|
+
"z": "4980f94dd2c07b62",
|
|
110
|
+
"name": "set node context",
|
|
111
|
+
"func": "context.set(\"node_test\", 15);\nreturn msg;",
|
|
112
|
+
"outputs": 1,
|
|
113
|
+
"timeout": 0,
|
|
114
|
+
"noerr": 0,
|
|
115
|
+
"initialize": "",
|
|
116
|
+
"finalize": "",
|
|
117
|
+
"libs": [],
|
|
118
|
+
"x": 490,
|
|
119
|
+
"y": 80,
|
|
120
|
+
"wires": [
|
|
121
|
+
[
|
|
122
|
+
"db53945bf5a57413"
|
|
123
|
+
]
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "42634cda458394e4",
|
|
128
|
+
"type": "context-monitor",
|
|
129
|
+
"z": "4980f94dd2c07b62",
|
|
130
|
+
"name": "",
|
|
131
|
+
"monitoring": [
|
|
132
|
+
{
|
|
133
|
+
"scope": "node",
|
|
134
|
+
"flow": "4980f94dd2c07b62",
|
|
135
|
+
"node": "b1d17d7418f7a28f",
|
|
136
|
+
"key": "node_test"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"scope": "global",
|
|
140
|
+
"key": "global_test"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"scope": "flow",
|
|
144
|
+
"flow": "4980f94dd2c07b62",
|
|
145
|
+
"node": "f2874060375785b7",
|
|
146
|
+
"key": "flow_test"
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"x": 140,
|
|
150
|
+
"y": 200,
|
|
151
|
+
"wires": [
|
|
152
|
+
[
|
|
153
|
+
"db3afa60f9a080a8"
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
"12a15905dd250321"
|
|
157
|
+
]
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "12a15905dd250321",
|
|
162
|
+
"type": "debug",
|
|
163
|
+
"z": "4980f94dd2c07b62",
|
|
164
|
+
"name": "debug on change",
|
|
165
|
+
"active": true,
|
|
166
|
+
"tosidebar": true,
|
|
167
|
+
"console": false,
|
|
168
|
+
"tostatus": false,
|
|
169
|
+
"complete": "true",
|
|
170
|
+
"targetType": "full",
|
|
171
|
+
"statusVal": "",
|
|
172
|
+
"statusType": "auto",
|
|
173
|
+
"x": 350,
|
|
174
|
+
"y": 220,
|
|
175
|
+
"wires": []
|
|
176
|
+
}
|
|
177
|
+
]
|
package/monitor.html
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
node-red-context-monitor by @ralphwetzel
|
|
3
|
+
https://github.com/ralphwetzel/node-red-context-monitor
|
|
4
|
+
License: MIT
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<script type="text/javascript">
|
|
8
|
+
RED.nodes.registerType('context-monitor',{
|
|
9
|
+
category: 'input',
|
|
10
|
+
color: '#b0b0b0',
|
|
11
|
+
defaults: {
|
|
12
|
+
name: {value:""},
|
|
13
|
+
monitoring: {value:[]}
|
|
14
|
+
},
|
|
15
|
+
inputs: 0,
|
|
16
|
+
outputs: 2,
|
|
17
|
+
outputLabels: ["context set", "context changed"],
|
|
18
|
+
icon: "font-awesome/fa-dot-circle-o",
|
|
19
|
+
label: function() {
|
|
20
|
+
if (this.name) {
|
|
21
|
+
return this.name;
|
|
22
|
+
}
|
|
23
|
+
let l = this.monitoring.length;
|
|
24
|
+
if (l > 1) {
|
|
25
|
+
return `${l} context keys`;
|
|
26
|
+
}
|
|
27
|
+
let key = this.monitoring[0]?.key;
|
|
28
|
+
if (key && key.length > 0) {
|
|
29
|
+
return key;
|
|
30
|
+
}
|
|
31
|
+
return "ctx monitor";
|
|
32
|
+
},
|
|
33
|
+
paletteLabel: "ctx monitor",
|
|
34
|
+
oneditprepare: function() {
|
|
35
|
+
|
|
36
|
+
let node = this;
|
|
37
|
+
|
|
38
|
+
// the (modified) context definition
|
|
39
|
+
let ctx = [];
|
|
40
|
+
|
|
41
|
+
function add_context(tpl) {
|
|
42
|
+
let c = {
|
|
43
|
+
"scope": tpl.scope ?? "global",
|
|
44
|
+
"flow": tpl.flow,
|
|
45
|
+
"node": tpl.node,
|
|
46
|
+
"key": tpl.key ?? ""
|
|
47
|
+
}
|
|
48
|
+
ctx.push(c);
|
|
49
|
+
return c;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
$('#node-input-context-container').css('min-height','150px').css('min-width','450px').editableList({
|
|
53
|
+
addItem: function(container, index, cfg) {
|
|
54
|
+
|
|
55
|
+
let data = add_context(cfg);
|
|
56
|
+
data.index = index;
|
|
57
|
+
|
|
58
|
+
// cfg is the data object from the node definition, stored in the 'data' store.
|
|
59
|
+
// We shall keep cfg untouched to ensure automated (non-)dirty check by the runtime.
|
|
60
|
+
// Thus we exchange the data stored against the 'data' object we just created.
|
|
61
|
+
container.data('data', data);
|
|
62
|
+
|
|
63
|
+
if (Object.keys(cfg).length < 1) {
|
|
64
|
+
node.dirty = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let _initing = true;
|
|
68
|
+
let fragment = document.createDocumentFragment();
|
|
69
|
+
|
|
70
|
+
let line_scope = $('<div class="form-row" style="margin-bottom: 6px">');
|
|
71
|
+
// $('<label for="context-scope" style="margin-left:10px; width: 90px"><i class="fa fa-tag"></i> Scope</label>').appendTo(line_scope);
|
|
72
|
+
$('<label for="context-scope" style="margin-left:10px; width: 90px">Scope:</label>').appendTo(line_scope);
|
|
73
|
+
$(`<input type="text" id="context-scope-${index}" placeholder="Scope">`).appendTo(line_scope);
|
|
74
|
+
line_scope.appendTo(fragment);
|
|
75
|
+
|
|
76
|
+
let line_flow = $('<div class="form-row" style="margin-bottom: 6px">');
|
|
77
|
+
// $(`<label for="context-scope-flows-${index}"" style="margin-left:10px; width: 90px"><i class="fa fa-tag"></i> Flows</label>`).appendTo(line_flow);
|
|
78
|
+
$(`<label for="context-scope-flows-${index}" style="margin-left:10px; width: 90px">Flows:</label>`).appendTo(line_flow);
|
|
79
|
+
$(`<input type="text" id="context-scope-flows-${index}" placeholder="Flows">`).appendTo(line_flow);
|
|
80
|
+
line_flow.appendTo(fragment);
|
|
81
|
+
|
|
82
|
+
// let line_group = $('<div class="form-row" style="margin-bottom: 6px">');
|
|
83
|
+
// // $(`<label for="context-scope-groups-${index}"" style="margin-left:10px; width: 90px"><i class="fa fa-tag"></i> Groups</label>`).appendTo(line_group);
|
|
84
|
+
// $(`<label for="context-scope-groups-${index}"" style="margin-left:10px; width: 90px">Groups: </label>`).appendTo(line_group);
|
|
85
|
+
// $(`<input type="text" id="context-scope-groups-${index}" placeholder="Groups">`).appendTo(line_group);
|
|
86
|
+
// line_group.appendTo(fragment);
|
|
87
|
+
|
|
88
|
+
let line_node = $('<div class="form-row" style="margin-bottom: 6px">');
|
|
89
|
+
// $(`<label for="context-scope-nodes-${index}"" style="margin-left:10px; width: 90px"><i class="fa fa-tag"></i> Nodes</label>`).appendTo(line_node);
|
|
90
|
+
$(`<label for="context-scope-nodes-${index}" style="margin-left:10px; width: 90px">Nodes: </label>`).appendTo(line_node);
|
|
91
|
+
$(`<input type="text" id="context-scope-nodes-${index}" placeholder="Nodes">`).appendTo(line_node);
|
|
92
|
+
line_node.appendTo(fragment);
|
|
93
|
+
|
|
94
|
+
let line_key = $('<div class="form-row" style="margin-bottom: 0px">');
|
|
95
|
+
// $(`<label for="context-scope-key-${index}"" style="margin-left:10px; width: 90px"><i class="fa fa-tag"></i> Key</label>`).appendTo(line_key);
|
|
96
|
+
$(`<label for="context-scope-key-${index}" style="margin-left:10px; width: 90px">Key: </label>`).appendTo(line_key);
|
|
97
|
+
$(`<input type="text" id="context-scope-key-${index}" placeholder="Context Variable Key">`).appendTo(line_key);
|
|
98
|
+
line_key.appendTo(fragment);
|
|
99
|
+
|
|
100
|
+
container[0].appendChild(fragment);
|
|
101
|
+
|
|
102
|
+
$(`#context-scope-${index}`).typedInput({type:"scope", types:[{
|
|
103
|
+
value: "scope",
|
|
104
|
+
options: [
|
|
105
|
+
{ value: "global", label: "Global"},
|
|
106
|
+
{ value: "flow", label: "Flow"},
|
|
107
|
+
// { value: "group", label: "Group"},
|
|
108
|
+
{ value: "node", label: "Node"},
|
|
109
|
+
]
|
|
110
|
+
}]}).on("change", function (event, type, value) {
|
|
111
|
+
switch (value) {
|
|
112
|
+
case "global": {
|
|
113
|
+
line_flow.hide();
|
|
114
|
+
// line_group.hide();
|
|
115
|
+
line_node.hide();
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "flow": {
|
|
119
|
+
line_flow.show();
|
|
120
|
+
// line_group.hide();
|
|
121
|
+
line_node.hide();
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case "group": {
|
|
125
|
+
line_flow.show();
|
|
126
|
+
// line_group.show();
|
|
127
|
+
line_node.hide();
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "node": {
|
|
131
|
+
line_flow.show();
|
|
132
|
+
// line_group.hide();
|
|
133
|
+
line_node.show();
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!_initing) {
|
|
138
|
+
data.scope = value;
|
|
139
|
+
|
|
140
|
+
data.flow = data.flow ?? $(`#context-scope-flows-${index}`).typedInput("value");
|
|
141
|
+
// data.group = data.group ?? $(`#context-scope-groups-${index}`).typedInput("value");
|
|
142
|
+
data.node = data.node ?? $(`#context-scope-nodes-${index}`).typedInput("value");
|
|
143
|
+
node.dirty = true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
switch (data.scope) {
|
|
147
|
+
// case "group": {
|
|
148
|
+
// $(`#context-scope-key-${index}`).prop("disabled", data.group == "");
|
|
149
|
+
// break;
|
|
150
|
+
// }
|
|
151
|
+
case "node": {
|
|
152
|
+
$(`#context-scope-key-${index}`).prop("disabled", data.node == "");
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
let flow_opts = [];
|
|
160
|
+
let group_opts = [];
|
|
161
|
+
let node_opts = [];
|
|
162
|
+
|
|
163
|
+
RED.nodes.eachWorkspace( cb => {
|
|
164
|
+
flow_opts.push({
|
|
165
|
+
value: cb.id,
|
|
166
|
+
label: cb.label
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
$(`#context-scope-flows-${index}`).typedInput({type:"flow", types:[{
|
|
171
|
+
value: "flow",
|
|
172
|
+
options: flow_opts
|
|
173
|
+
}]}).on("change", function (event, type, value) {
|
|
174
|
+
|
|
175
|
+
// RED.nodes.eachGroup( g => {
|
|
176
|
+
// console.log(g)
|
|
177
|
+
// if (g.z == value) {
|
|
178
|
+
// group_opts.push({
|
|
179
|
+
// value: g.id,
|
|
180
|
+
// label: g.label ?? g.id
|
|
181
|
+
// });
|
|
182
|
+
// }
|
|
183
|
+
// });
|
|
184
|
+
// let gol = group_opts.length;
|
|
185
|
+
// $(`#context-scope-groups-${index}`).typedInput('disable', gol < 1);
|
|
186
|
+
// if (gol < 1) {
|
|
187
|
+
// group_opts.push({
|
|
188
|
+
// value: undefined,
|
|
189
|
+
// label: "No group in this flow"
|
|
190
|
+
// })
|
|
191
|
+
// }
|
|
192
|
+
// $(`#context-scope-groups-${index}`).typedInput('types', [{options: group_opts}]);
|
|
193
|
+
// $(`#context-scope-groups-${index}`).parent().find(".red-ui-typedInput-option-label").css("color", gol < 1 ? "darkgrey" : "var(--red-ui-form-text-color)");
|
|
194
|
+
// if ($(`#context-scope-${index}`).typedInput("value") == "group") {
|
|
195
|
+
// $(`#context-scope-key-${index}`).prop("disabled", gol < 1);
|
|
196
|
+
// }
|
|
197
|
+
|
|
198
|
+
RED.nodes.eachNode( n => {
|
|
199
|
+
|
|
200
|
+
if (n.z == value) {
|
|
201
|
+
|
|
202
|
+
// from 25-status.html
|
|
203
|
+
let nodeDef = RED.nodes.getType(n.type);
|
|
204
|
+
let label;
|
|
205
|
+
let sublabel;
|
|
206
|
+
if (nodeDef) {
|
|
207
|
+
let l = nodeDef.label;
|
|
208
|
+
label = (typeof l === "function" ? l.call(n) : l)||"";
|
|
209
|
+
// sublabel = n.type;
|
|
210
|
+
// if (sublabel.indexOf("subflow:") === 0) {
|
|
211
|
+
// let subflowId = sublabel.substring(8);
|
|
212
|
+
// let subflow = RED.nodes.subflow(subflowId);
|
|
213
|
+
// sublabel = "subflow : "+subflow.name;
|
|
214
|
+
// }
|
|
215
|
+
}
|
|
216
|
+
if (!nodeDef || !label) {
|
|
217
|
+
label = n.type;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
node_opts.push({
|
|
221
|
+
value: n.id,
|
|
222
|
+
label: label
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
let nol = node_opts.length;
|
|
227
|
+
$(`#context-scope-nodes-${index}`).typedInput('disable', nol < 1);
|
|
228
|
+
if (nol < 1) {
|
|
229
|
+
node_opts.push({
|
|
230
|
+
value: undefined,
|
|
231
|
+
label: "No nodes in this flow"
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
$(`#context-scope-nodes-${index}`).typedInput('types', [{options: node_opts}]);
|
|
235
|
+
$(`#context-scope-nodes-${index}`).parent().find(".red-ui-typedInput-option-label").css("color", nol < 1 ? "darkgrey" : "var(--red-ui-form-text-color)");
|
|
236
|
+
if ($(`#context-scope-${index}`).typedInput("value") == "node") {
|
|
237
|
+
$(`#context-scope-key-${index}`).prop("disabled", nol < 1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!_initing) {
|
|
241
|
+
data.flow = value;
|
|
242
|
+
node.dirty = true;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// $(`#context-scope-groups-${index}`).typedInput({type:"group", types:[{
|
|
247
|
+
// value: "group",
|
|
248
|
+
// options: [
|
|
249
|
+
// { value: "test", label: "TEST"}
|
|
250
|
+
// ]
|
|
251
|
+
// }]}).on("change", function (event, type, value) {
|
|
252
|
+
// if (!_initing) {
|
|
253
|
+
// data.group = value;
|
|
254
|
+
// node.dirty = true;
|
|
255
|
+
// }
|
|
256
|
+
// });
|
|
257
|
+
|
|
258
|
+
$(`#context-scope-nodes-${index}`).typedInput({type:"node", types:[{
|
|
259
|
+
value: "node"
|
|
260
|
+
}]}).on("change", function (event, type, value) {
|
|
261
|
+
if (!_initing) {
|
|
262
|
+
data.node = value;
|
|
263
|
+
node.dirty = true;
|
|
264
|
+
}
|
|
265
|
+
$(`#context-scope-key-${index}`).prop("disabled", false);
|
|
266
|
+
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
$(`#context-scope-key-${index}`).change( function () {
|
|
270
|
+
if (!_initing) {
|
|
271
|
+
data.key = $(this).val();
|
|
272
|
+
node.dirty = true;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// initialize the form
|
|
277
|
+
$(`#context-scope-${index}`).typedInput("value", data.scope);
|
|
278
|
+
|
|
279
|
+
let f = data.flow || flow_opts[0]?.value;
|
|
280
|
+
if (f) {
|
|
281
|
+
if (flow_opts.filter( opts => {
|
|
282
|
+
return opts?.value == f;
|
|
283
|
+
}).length > 0) {
|
|
284
|
+
$(`#context-scope-flows-${index}`).typedInput("value", f);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// let g = data.group || group_opts[0]?.value;
|
|
289
|
+
// if (g) {
|
|
290
|
+
// if (group_opts.filter( opts => {
|
|
291
|
+
// return opts.value == g;
|
|
292
|
+
// }).length > 0) {
|
|
293
|
+
// $(`#context-scope-groups-${index}`).typedInput("value", g);
|
|
294
|
+
// }
|
|
295
|
+
// }
|
|
296
|
+
|
|
297
|
+
let n = data.node || node_opts[0]?.value;
|
|
298
|
+
if (n) {
|
|
299
|
+
if (node_opts.filter( opts => {
|
|
300
|
+
return opts?.value == n;
|
|
301
|
+
}).length > 0) {
|
|
302
|
+
$(`#context-scope-nodes-${index}`).typedInput("value", n);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
$(`#context-scope-key-${index}`).val(data.key);
|
|
307
|
+
|
|
308
|
+
_initing = false;
|
|
309
|
+
},
|
|
310
|
+
removable: true,
|
|
311
|
+
removeItem: function (data) {
|
|
312
|
+
let index = data.index;
|
|
313
|
+
ctx.splice(index, 1);
|
|
314
|
+
|
|
315
|
+
ctx.forEach( (c, index) => {
|
|
316
|
+
c.index = index;
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
node.dirty = true;
|
|
320
|
+
},
|
|
321
|
+
sortable: true,
|
|
322
|
+
sortItem: function (rows) {
|
|
323
|
+
|
|
324
|
+
// recreate ctx
|
|
325
|
+
ctx = [];
|
|
326
|
+
|
|
327
|
+
rows.forEach( (row, index) => {
|
|
328
|
+
row.data.index = index;
|
|
329
|
+
ctx.push(row.data);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
node.dirty = true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
node.monitoring.forEach(data => {
|
|
339
|
+
$('#node-input-context-container').editableList('addItem', data);
|
|
340
|
+
});
|
|
341
|
+
} catch {}
|
|
342
|
+
|
|
343
|
+
node._ctx = ctx;
|
|
344
|
+
|
|
345
|
+
},
|
|
346
|
+
oneditresize: function (size) {
|
|
347
|
+
let el = $('#node-input-context-container').parent();
|
|
348
|
+
let top = el.position().top;
|
|
349
|
+
el.height(size.height - top);
|
|
350
|
+
|
|
351
|
+
let ti = $('[id*=context-scope-flows]:first');
|
|
352
|
+
let width;
|
|
353
|
+
if (ti.length > 0) {
|
|
354
|
+
width = ti.next().outerWidth();
|
|
355
|
+
}
|
|
356
|
+
if (width) {
|
|
357
|
+
$('[id*=context-scope-key]').outerWidth(width);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
oneditsave: function () {
|
|
361
|
+
let node = this;
|
|
362
|
+
if (!node.dirty) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let ctx = node._ctx;
|
|
367
|
+
delete node._ctx;
|
|
368
|
+
|
|
369
|
+
ctx.forEach( data => {
|
|
370
|
+
delete data.index;
|
|
371
|
+
|
|
372
|
+
switch (data.scope) {
|
|
373
|
+
case "global":
|
|
374
|
+
delete data.flow;
|
|
375
|
+
case "flow":
|
|
376
|
+
delete data.group;
|
|
377
|
+
// case "group":
|
|
378
|
+
// delete data.node;
|
|
379
|
+
// break;
|
|
380
|
+
case "node":
|
|
381
|
+
delete data.group;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
node.monitoring = ctx;
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
</script>
|
|
391
|
+
|
|
392
|
+
<script type="text/html" data-template-name="context-monitor">
|
|
393
|
+
<div class="form-row">
|
|
394
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
395
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
396
|
+
</div>
|
|
397
|
+
<!-- <div class="form-row">
|
|
398
|
+
<label for="node-input-monitoring"><i class="fa fa-dot-circle-o"></i> Context</label>
|
|
399
|
+
<input type="text" id="node-input-monitoring" placeholder="Context">
|
|
400
|
+
</div> -->
|
|
401
|
+
<div class="form-row" style="margin-bottom:0;">
|
|
402
|
+
<label style="min-width:200px;"><i class="fa fa-dot-circle-o"></i> <span>Monitoring context definition</span></label>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="form-row node-input-context-def-row">
|
|
405
|
+
<ol id="node-input-context-container"></ol>
|
|
406
|
+
</div>
|
|
407
|
+
</script>
|
|
408
|
+
|
|
409
|
+
<script type="text/markdown" data-help-name="context-monitor">
|
|
410
|
+
|
|
411
|
+
A [Node-RED](https://www.nodered.org) node to monitor a [context](https://nodered.org/docs/user-guide/context).
|
|
412
|
+
|
|
413
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/preview.png"
|
|
414
|
+
style="min-width: 138px; width: 138px; align: center; border: 1px solid lightgray;"/>
|
|
415
|
+
|
|
416
|
+
### What it does
|
|
417
|
+
|
|
418
|
+
This node allows to setup the reference to a context, then sends a message when this context is written to.
|
|
419
|
+
|
|
420
|
+
It sends a dedicated message on a separate port in case it detects that the value of the context was changed.
|
|
421
|
+
|
|
422
|
+
The message sent will carry the current value of the context as `msg.payload`. Monitoring details will be provided as `msg.monitoring`.
|
|
423
|
+
|
|
424
|
+
It is possible to monitor an infinite number of contexts with each instance of this node.
|
|
425
|
+
|
|
426
|
+
This node supports the three [context scope levels](https://nodered.org/docs/user-guide/context#context-scopes) `Global`, `Flow` & `Node`.
|
|
427
|
+
|
|
428
|
+
### Installation
|
|
429
|
+
|
|
430
|
+
Use the Node-RED palette manager to install this node.
|
|
431
|
+
|
|
432
|
+
### Details
|
|
433
|
+
|
|
434
|
+
To monitor a `Global` scope context, set the scope to `Global` and provide the context key.
|
|
435
|
+
|
|
436
|
+
<img alt="global" src="resources/@ralphwetzel/node-red-context-monitor/global.png"
|
|
437
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
438
|
+
|
|
439
|
+
To monitor a `Flow` scope context, set the scope to `Flow`, then select the owning flow and provide the context key.
|
|
440
|
+
|
|
441
|
+
<img alt="flow" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/flow.png"
|
|
442
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
443
|
+
|
|
444
|
+
To monitor a `Node` scope context, set the scope to `Node`, then select flow & node and provide the context key.
|
|
445
|
+
|
|
446
|
+
<img alt="node" src="https://raw.githubusercontent.com/ralphwetzel/node-red-context-monitor/main/resources/node.png"
|
|
447
|
+
style="min-width: 474px; width: 474px; align: center; border: 1px solid lightgray;"/>
|
|
448
|
+
|
|
449
|
+
> 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.
|
package/monitor.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/*
|
|
2
|
+
node-red-context-monitor by @ralphwetzel
|
|
3
|
+
https://github.com/ralphwetzel/node-red-context-monitor
|
|
4
|
+
License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
|
|
11
|
+
let error_header = "*** Error while loading node-red-context-monitor:";
|
|
12
|
+
|
|
13
|
+
// this used to be the cache of ctx triggered @ set
|
|
14
|
+
// ... that's why it's the set_cache!
|
|
15
|
+
let set_cache = {};
|
|
16
|
+
|
|
17
|
+
let _RED;
|
|
18
|
+
|
|
19
|
+
// ****
|
|
20
|
+
// Copied from node-red-mcu-plugin:
|
|
21
|
+
// Patch support function: Calculate the path to a to-be-required file
|
|
22
|
+
|
|
23
|
+
function get_require_path(req_path) {
|
|
24
|
+
|
|
25
|
+
let rm = require.main.path;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
let stat = fs.lstatSync(rm);
|
|
29
|
+
if (!stat.isDirectory()) {
|
|
30
|
+
console.log(error_header);
|
|
31
|
+
console.log("require.main.path is not a directory.");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
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
|
+
}
|
|
44
|
+
|
|
45
|
+
// split path into segments ... the safe way
|
|
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)
|
|
56
|
+
|
|
57
|
+
let rmsl = rms.length;
|
|
58
|
+
|
|
59
|
+
if (rms.includes("packages")) {
|
|
60
|
+
if (rms[rmsl-3]=="packages" && rms[rmsl-2]=="node_modules" && rms[rmsl-1]=="node-red") {
|
|
61
|
+
// dev: [...]/node-red/packages/node_modules/node-red
|
|
62
|
+
// install: [...]/lib/node_modules/node-red
|
|
63
|
+
// pi: /lib/node_modules/node-red/
|
|
64
|
+
|
|
65
|
+
// dev: [...]/node-red/packages/node_modules/@node-red
|
|
66
|
+
// install: [...]/lib/node_modules/node-red/node_modules/@node-red
|
|
67
|
+
// pi: /lib/node_modules/node-red/node_modules/@node-red
|
|
68
|
+
rms.splice(-2);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// compose things again...
|
|
73
|
+
req_path = req_path.split("/");
|
|
74
|
+
let p = path.join(rmp.root, ...rms, ...req_path);
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(p)) {
|
|
77
|
+
console.log(error_header)
|
|
78
|
+
console.log("Failed to calculate correct patch path.");
|
|
79
|
+
console.log("Please raise an issue @ our GitHub repository, stating the following information:");
|
|
80
|
+
console.log("> require.main.path:", require.main.path);
|
|
81
|
+
console.log("> utils.js:", p);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return p;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// End: "Patch support ..."
|
|
89
|
+
// *****
|
|
90
|
+
|
|
91
|
+
// *****
|
|
92
|
+
// Make available the Context Manager
|
|
93
|
+
|
|
94
|
+
const context_manager_path = get_require_path("node_modules/@node-red/runtime/lib/nodes/context/index.js");
|
|
95
|
+
if (!context_manager_path) return;
|
|
96
|
+
const context_manager = require(context_manager_path);
|
|
97
|
+
|
|
98
|
+
// The function to wrap the NR managed context into our monitoring object
|
|
99
|
+
// This cant' be done w/ a simple new Proxy(context, handler) as Proxy ensures that immutable functions
|
|
100
|
+
// stay immutable - which doesn't support our intentions!
|
|
101
|
+
|
|
102
|
+
let create_wrapper = function(node_id, flow_id, ctx) {
|
|
103
|
+
|
|
104
|
+
let context = ctx;
|
|
105
|
+
|
|
106
|
+
var context_id = node_id;
|
|
107
|
+
if (flow_id) {
|
|
108
|
+
context_id = node_id + ":" + flow_id;
|
|
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)
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
Object.defineProperties(wrapper, {
|
|
179
|
+
get: {
|
|
180
|
+
value: function(key, storage, callback) {
|
|
181
|
+
return context.get(key, storage, callback);
|
|
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
|
+
}
|
|
197
|
+
}
|
|
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
|
|
244
|
+
}
|
|
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
|
+
}
|
|
273
|
+
|
|
274
|
+
// End: "Make available ..."
|
|
275
|
+
// *****
|
|
276
|
+
|
|
277
|
+
// *****
|
|
278
|
+
// node-red-context-monitor
|
|
279
|
+
|
|
280
|
+
module.exports = function(RED) {
|
|
281
|
+
|
|
282
|
+
// catch this here!
|
|
283
|
+
// necessary for trigger function to map node_id -> node object.
|
|
284
|
+
_RED = RED;
|
|
285
|
+
|
|
286
|
+
function ContextMonitor(config) {
|
|
287
|
+
RED.nodes.createNode(this,config);
|
|
288
|
+
var node = this;
|
|
289
|
+
|
|
290
|
+
node.data = config.monitoring;
|
|
291
|
+
node.monitoring = [];
|
|
292
|
+
|
|
293
|
+
config.monitoring.forEach( data => {
|
|
294
|
+
|
|
295
|
+
if (!data.key) return;
|
|
296
|
+
|
|
297
|
+
let ctx = "global";
|
|
298
|
+
switch (data.scope) {
|
|
299
|
+
case "global":
|
|
300
|
+
ctx = `global:${data.key}`;
|
|
301
|
+
break;
|
|
302
|
+
case "flow":
|
|
303
|
+
ctx = `${data.flow}:${data.key}`;
|
|
304
|
+
break;
|
|
305
|
+
case "node":
|
|
306
|
+
ctx = `${data.node}:${data.flow}:${data.key}`;
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
node.monitoring.push(ctx);
|
|
313
|
+
|
|
314
|
+
if (!set_cache[ctx]) {
|
|
315
|
+
set_cache[ctx] = [];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
set_cache[ctx].push({
|
|
319
|
+
"id": node.id,
|
|
320
|
+
"data": data
|
|
321
|
+
});
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
node.on("input", function(msg, send, done) {
|
|
325
|
+
|
|
326
|
+
// unfold & check if changed
|
|
327
|
+
let prev = msg.previous;
|
|
328
|
+
delete msg.previous;
|
|
329
|
+
|
|
330
|
+
if (msg.payload !== prev) {
|
|
331
|
+
// if changed, clone & emit @ second output terminal
|
|
332
|
+
let m = RED.util.cloneMessage(msg);
|
|
333
|
+
delete m._msgid;
|
|
334
|
+
m.monitoring.previous = prev;
|
|
335
|
+
send([msg, m]);
|
|
336
|
+
} else {
|
|
337
|
+
// just report set
|
|
338
|
+
send([msg, null]);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
done();
|
|
342
|
+
});
|
|
343
|
+
node.on("close",function() {
|
|
344
|
+
// remove this nodes ctx(s) from the trigger list
|
|
345
|
+
node.monitoring.forEach( ctx => {
|
|
346
|
+
set_cache[ctx] = set_cache[ctx].filter( n => {
|
|
347
|
+
return n.id !== node.id;
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
RED.nodes.registerType("context-monitor",ContextMonitor);
|
|
354
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ralphwetzel/node-red-context-monitor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Node-RED node to monitor a context.",
|
|
5
|
+
"main": "monitor.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ralphwetzel/node-red-context-monitor.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"node-red",
|
|
15
|
+
"context"
|
|
16
|
+
],
|
|
17
|
+
"node-red": {
|
|
18
|
+
"version": ">=3.0.0",
|
|
19
|
+
"nodes": {
|
|
20
|
+
"context-monitor": "monitor.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"author": "Ralph Wetzel",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/ralphwetzel/node-red-context-monitor/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/ralphwetzel/node-red-context-monitor#readme",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"fs-extra": "^11.1.1"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=16.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|