@vitormnm/node-red-simple-opcua 1.6.2 → 1.7.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.
Files changed (42) hide show
  1. package/README.md +89 -136
  2. package/client/lib/opcua-client-browser.js +238 -10
  3. package/client/lib/opcua-client-method-service.js +1 -1
  4. package/client/lib/opcua-client-subscription-service.js +0 -2
  5. package/client/opcua-client-config.html +118 -1
  6. package/client/opcua-client-config.js +74 -8
  7. package/client/opcua-client-help.html +6 -0
  8. package/client/opcua-client-utils.js +34 -10
  9. package/client/opcua-client.html +7 -0
  10. package/client/opcua-client.js +97 -1
  11. package/examples/flows_simple_opc.json +1 -1
  12. package/package.json +1 -1
  13. package/server/lib/opcua-address-space-alarm.js +11 -5
  14. package/server/lib/opcua-address-space-builder.js +65 -15
  15. package/server/lib/opcua-config.js +81 -23
  16. package/server/lib/opcua-server-events-child.js +1 -1
  17. package/server/lib/opcua-server-runtime-child.js +429 -59
  18. package/server/lib/opcua-server-runtime.js +49 -5
  19. package/server/lib/opcua-server-status-child.js +14 -14
  20. package/server/nodered/simple_opcua/server/certificates/mutex +0 -0
  21. package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem +25 -0
  22. package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
  23. package/server/nodered/simple_opcua/server/certificates/own/openssl.cnf +72 -0
  24. package/server/nodered/simple_opcua/server/certificates/own/private/private_key.pem +28 -0
  25. package/server/nodered/simple_opcua/server/certificates/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
  26. package/server/nodered/simple_opcua/server/myServer1/mutex +0 -0
  27. package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem +25 -0
  28. package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
  29. package/server/nodered/simple_opcua/server/myServer1/own/openssl.cnf +72 -0
  30. package/server/nodered/simple_opcua/server/myServer1/own/private/private_key.pem +28 -0
  31. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[91e520c64ff891c67168f08a46dd194071e15dae].pem +25 -0
  32. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[98ae95da627cea4c500753c319161a3554ee38d7].pem +25 -0
  33. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[aef8d7a1cfba13d84189a0bcf1694208fc51a7f9].pem +25 -0
  34. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
  35. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[ebdf9acf1d02e347917a14108d3144799c638ea3].pem +25 -0
  36. package/server/opcua-server-io.html +76 -0
  37. package/server/opcua-server-io.js +135 -23
  38. package/server/opcua-server.css +52 -0
  39. package/server/opcua-server.html +166 -44
  40. package/server/opcua-server.js +142 -7
  41. package/server/view/opcua-server.css +89 -6
  42. package/server/view/opcua-server.js +523 -42
@@ -170,6 +170,8 @@
170
170
  <option value="events">Events Server</option>
171
171
  <option value="status">Status Server</option>
172
172
  <option value="activeAlarms">Active Alarms</option>
173
+ <option value="getSessions">Get Sessions</option>
174
+ <option value="deleteSessions">Delete Sessions</option>
173
175
  <option value="method-input">Method Input</option>
174
176
  <option value="method-output">Method Output</option>
175
177
  </select>
@@ -341,6 +343,80 @@ msg.payload = [
341
343
  ]
342
344
  </code></pre>
343
345
 
346
+ <h3>Get Sessions</h3>
347
+ <p>Returns a JSON array of all currently active OPC UA sessions on the server.</p>
348
+ <p><b>Input</b>: any message triggers a session snapshot.</p>
349
+ <p><b>Output</b>: <code>msg.payload</code> is an array of session objects:</p>
350
+ <pre><code>[
351
+ {
352
+ "sessionId": "ns=1;g=...",
353
+ "sessionName": "UaExpert@hostname",
354
+ "status": "active",
355
+ "creationDate": "2024-01-01T00:00:00.000Z",
356
+ "sessionTimeout": 30000,
357
+ "clientLastContactTime": 1234567890,
358
+ "channelId": 1,
359
+ "clientDescription": {
360
+ "applicationUri": "urn:hostname:client",
361
+ "productUri": "urn:hostname:client",
362
+ "applicationName": "UaExpert",
363
+ "applicationType": "1"
364
+ },
365
+ "userIdentityToken": {
366
+ "policyId": "anonymous",
367
+ "userName": null,
368
+ "tokenType": "AnonymousIdentityToken"
369
+ },
370
+ "channel": {
371
+ "channelId": 1,
372
+ "remoteAddress": "127.0.0.1",
373
+ "remotePort": 54321,
374
+ "bytesRead": 500,
375
+ "bytesWritten": 300,
376
+ "transactionsCount": 10,
377
+ "securityMode": "None",
378
+ "securityPolicy": "None"
379
+ },
380
+ "currentSubscriptionCount": 1,
381
+ "cumulatedSubscriptionCount": 2,
382
+ "currentMonitoredItemCount": 5,
383
+ "aborted": false
384
+ }
385
+ ]</code></pre>
386
+ <p>The node also fetches sessions automatically on deploy, just like <b>Status Server</b> mode.</p>
387
+
388
+ <h3>Delete Sessions</h3>
389
+ <p>Forces the server to close one or more active OPC UA sessions by their <code>sessionId</code>.</p>
390
+ <p><b>Input</b>: <code>msg.payload</code> must be an array of objects, each containing a <code>sessionId</code>
391
+ (the GUID string returned by <b>Get Sessions</b>):</p>
392
+ <pre><code>[
393
+ { "sessionId": "ns=1;g=F4F511E2-664B-2191-89A7-46EE461DB86C" },
394
+ { "sessionId": "ns=1;g=A1B2C3D4-1234-5678-ABCD-EF0123456789" }
395
+ ]</code></pre>
396
+ <p><b>Output</b>: <code>msg.payload</code> is an array of result objects, one per requested session.
397
+ The node status badge summarises deleted / not found / error counts:</p>
398
+ <pre><code>[
399
+ {
400
+ "sessionId": "ns=1;g=F4F511E2-664B-2191-89A7-46EE461DB86C",
401
+ "status": "deleted"
402
+ },
403
+ {
404
+ "sessionId": "ns=1;g=AAAAAAAA-0000-0000-0000-000000000000",
405
+ "status": "not_found"
406
+ },
407
+ {
408
+ "sessionId": "ns=1;g=BBBBBBBB-0000-0000-0000-000000000000",
409
+ "status": "error",
410
+ "error": "cannot find session …"
411
+ }
412
+ ]</code></pre>
413
+ <p>Possible <code>status</code> values:</p>
414
+ <ul>
415
+ <li><b>deleted</b> — session was found and successfully closed with reason <code>Forcing</code>.</li>
416
+ <li><b>not_found</b> — no active session with that <code>sessionId</code> exists.</li>
417
+ <li><b>error</b> — the session was found but <code>engine.closeSession()</code> threw; the
418
+ <code>error</code> field contains the message.</li>
419
+ </ul>
344
420
 
345
421
 
346
422
 
@@ -36,6 +36,8 @@ module.exports = function (RED) {
36
36
  node._childListenerAttached = false;
37
37
  node._attachedChild = null;
38
38
  node._childListenerRetry = null;
39
+ node._pendingDoneCallbacks = new Map();
40
+ node._pendingMessages = new Map();
39
41
 
40
42
  const handler = (msg) => {
41
43
  onMessage(msg, node);
@@ -49,10 +51,13 @@ module.exports = function (RED) {
49
51
  registerMethodInput(node);
50
52
  }
51
53
  if (node.mode === "events") {
52
- registerEvents(node, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
54
+ registerEvents(node, { throwOnError: false, waitForServer: true, timeoutMs: 5000, silentOnError: true });
53
55
  }
54
56
  if (node.mode === "status") {
55
- requestSnapshot(node, null, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
57
+ requestSnapshot(node, null, { throwOnError: false, waitForServer: true, timeoutMs: 5000, silentOnError: true });
58
+ }
59
+ if (node.mode === "getSessions") {
60
+ requestSessions(node, null, { throwOnError: false, waitForServer: true, timeoutMs: 5000, silentOnError: true });
56
61
  }
57
62
 
58
63
 
@@ -62,38 +67,69 @@ module.exports = function (RED) {
62
67
  node.send.apply(node, arguments);
63
68
  };
64
69
 
70
+ if (msg && msg._msgid) {
71
+ node._pendingDoneCallbacks.set(msg._msgid, done);
72
+ node._pendingMessages.set(msg._msgid, msg);
73
+ }
74
+
65
75
  if (node.mode === "read") {
66
76
  await handleRead(node, msg, send);
67
77
  }
68
-
69
- if (node.mode === "write") {
78
+ else if (node.mode === "write") {
70
79
  await handleWrite(node, msg, send);
71
80
  }
72
-
73
- if (node.mode === "event") {
81
+ else if (node.mode === "event") {
74
82
  await handleEvent(node, msg, send);
75
83
  }
76
-
77
- if (node.mode === "activeAlarms") {
84
+ else if (node.mode === "activeAlarms") {
78
85
  await handleActiveAlarms(node, msg, send);
79
86
  }
80
-
81
- if (node.mode === "method-output") {
87
+ else if (node.mode === "method-output") {
82
88
  await handleMethodOutput(node, msg, send);
83
89
  done();
84
- return;
90
+ if (msg && msg._msgid) {
91
+ node._pendingDoneCallbacks.delete(msg._msgid);
92
+ }
85
93
  }
86
- if (node.mode === "events") {
94
+ else if (node.mode === "events") {
87
95
  await registerEvents(node, { waitForServer: true, timeoutMs: 5000 });
96
+ done();
97
+ if (msg && msg._msgid) {
98
+ node._pendingDoneCallbacks.delete(msg._msgid);
99
+ }
88
100
  }
89
- if (node.mode === "status") {
101
+ else if (node.mode === "status") {
90
102
  await requestSnapshot(node, msg, { waitForServer: true, timeoutMs: 5000 });
103
+ done();
104
+ if (msg && msg._msgid) {
105
+ node._pendingDoneCallbacks.delete(msg._msgid);
106
+ }
107
+ }
108
+ else if (node.mode === "getSessions") {
109
+ await requestSessions(node, msg, { waitForServer: true, timeoutMs: 5000 });
110
+ done();
111
+ if (msg && msg._msgid) {
112
+ node._pendingDoneCallbacks.delete(msg._msgid);
113
+ }
114
+ }
115
+ else if (node.mode === "deleteSessions") {
116
+ await handleDeleteSessions(node, msg, { waitForServer: true, timeoutMs: 5000 });
117
+ done();
118
+ if (msg && msg._msgid) {
119
+ node._pendingDoneCallbacks.delete(msg._msgid);
120
+ }
91
121
  }
92
122
 
93
- done();
94
123
  } catch (error) {
95
124
  node.status({ fill: "red", shape: "ring", text: node.mode + " failed" });
96
- done(error);
125
+ node.error(error.message || String(error), msg);
126
+ if (done) {
127
+ done();
128
+ }
129
+ if (msg && msg._msgid) {
130
+ node._pendingDoneCallbacks.delete(msg._msgid);
131
+ node._pendingMessages.delete(msg._msgid);
132
+ }
97
133
  }
98
134
  });
99
135
 
@@ -104,6 +140,8 @@ module.exports = function (RED) {
104
140
  }
105
141
 
106
142
  detachChildListener(node, handler);
143
+ node._pendingDoneCallbacks.clear();
144
+ node._pendingMessages.clear();
107
145
 
108
146
 
109
147
  if (node.mode === "method-input") {
@@ -144,12 +182,63 @@ module.exports = function (RED) {
144
182
  data.payload = restoreBuffers(data.payload);
145
183
  }
146
184
 
147
- node.send(data);
185
+ const originalMsgId = data && data._msgid;
186
+ let actualMsg = data;
187
+ if (originalMsgId && node._pendingMessages.has(originalMsgId)) {
188
+ actualMsg = node._pendingMessages.get(originalMsgId);
189
+ actualMsg.payload = data.payload;
190
+ actualMsg.opcua = data.opcua;
191
+ actualMsg.topic = data.topic;
192
+ node._pendingMessages.delete(originalMsgId);
193
+ }
194
+
195
+ node.send(actualMsg);
196
+
197
+ if (originalMsgId && node._pendingDoneCallbacks.has(originalMsgId)) {
198
+ const pendingDone = node._pendingDoneCallbacks.get(originalMsgId);
199
+ pendingDone();
200
+ node._pendingDoneCallbacks.delete(originalMsgId);
201
+ }
148
202
  }
149
203
 
150
204
  if (msg.type === "error") {
151
205
  node.status(msg.data);
152
- node.error(msg.error);
206
+
207
+ const originalMsgId = msg.originalMsg && msg.originalMsg._msgid;
208
+ let catchMsg = msg.originalMsg || {};
209
+ if (originalMsgId && node._pendingMessages.has(originalMsgId)) {
210
+ catchMsg = node._pendingMessages.get(originalMsgId);
211
+ node._pendingMessages.delete(originalMsgId);
212
+ }
213
+ catchMsg.error = msg.error;
214
+
215
+ node.error(msg.error, catchMsg);
216
+
217
+ if (originalMsgId && node._pendingDoneCallbacks.has(originalMsgId)) {
218
+ const pendingDone = node._pendingDoneCallbacks.get(originalMsgId);
219
+ pendingDone();
220
+ node._pendingDoneCallbacks.delete(originalMsgId);
221
+ }
222
+ }
223
+
224
+ if (msg.type === "partialError") {
225
+ // Route failed items to catch node without changing the node status
226
+ const originalMsgId = msg.originalMsg && msg.originalMsg._msgid;
227
+ let catchMsg = msg.originalMsg || {};
228
+ if (originalMsgId && node._pendingMessages.has(originalMsgId)) {
229
+ catchMsg = node._pendingMessages.get(originalMsgId);
230
+ node._pendingMessages.delete(originalMsgId);
231
+ }
232
+ catchMsg.payload = msg.failed;
233
+ catchMsg.error = msg.error;
234
+
235
+ node.error(msg.error, catchMsg);
236
+
237
+ if (originalMsgId && node._pendingDoneCallbacks.has(originalMsgId)) {
238
+ const pendingDone = node._pendingDoneCallbacks.get(originalMsgId);
239
+ pendingDone();
240
+ node._pendingDoneCallbacks.delete(originalMsgId);
241
+ }
153
242
  }
154
243
 
155
244
  if (msg.type === "sendMethod") {
@@ -214,16 +303,20 @@ module.exports = function (RED) {
214
303
  async function registerMethodInput(node) {
215
304
  await sendToChild(node, {
216
305
  type: "registerMethodInput",
217
- node: node,
306
+ node: {
307
+ methodName: node.methodName
308
+ },
218
309
  nodeId: node.id
219
310
 
220
- }, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
311
+ }, { throwOnError: false, waitForServer: true, timeoutMs: 5000, silentOnError: true });
221
312
  }
222
313
 
223
314
  async function registerEvents(node, options) {
224
315
  return sendToChild(node, {
225
316
  type: "eventsServer",
226
- node: node,
317
+ node: {
318
+ intervalMs: node.intervalMs
319
+ },
227
320
  nodeId: node.id
228
321
  }, options);
229
322
  }
@@ -231,7 +324,22 @@ module.exports = function (RED) {
231
324
  async function requestSnapshot(node, msg, options) {
232
325
  return sendToChild(node, {
233
326
  type: "buildServerSnapshot",
234
- node: node,
327
+ msg: msg,
328
+ nodeId: node.id
329
+ }, options);
330
+ }
331
+
332
+ async function requestSessions(node, msg, options) {
333
+ return sendToChild(node, {
334
+ type: "readActiveSessions",
335
+ msg: msg,
336
+ nodeId: node.id
337
+ }, options);
338
+ }
339
+
340
+ async function handleDeleteSessions(node, msg, options) {
341
+ return sendToChild(node, {
342
+ type: "deleteActiveSessions",
235
343
  msg: msg,
236
344
  nodeId: node.id
237
345
  }, options);
@@ -446,7 +554,9 @@ module.exports = function (RED) {
446
554
  const error = new Error("OPC UA server child process is not available for serverRef: " + node.serverRef);
447
555
  node.status({ fill: "red", shape: "ring", text: "server unavailable" });
448
556
  if (options.throwOnError === false) {
449
- node.error(error.message);
557
+ if (!options.silentOnError) {
558
+ node.error(error.message);
559
+ }
450
560
  return false;
451
561
  }
452
562
  throw error;
@@ -464,7 +574,9 @@ module.exports = function (RED) {
464
574
  node._attachedChild = null;
465
575
  ensureChildListener(node, handler);
466
576
  if (options.throwOnError === false) {
467
- node.error(error.message);
577
+ if (!options.silentOnError) {
578
+ node.error(error.message);
579
+ }
468
580
  return false;
469
581
  }
470
582
  throw error;
@@ -263,3 +263,55 @@
263
263
  margin-left: 0;
264
264
  }
265
265
  }
266
+
267
+ /* Standardized configuration card styling */
268
+ .opcua-config-card {
269
+ padding: 14px;
270
+ background: var(--red-ui-form-input-background, #fcfcfc);
271
+ border: 1px solid var(--red-ui-form-input-border-color, #d9d9d9);
272
+ border-radius: 6px;
273
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
274
+ }
275
+
276
+ .opcua-config-card .opcua-card-title {
277
+ margin-top: 0;
278
+ margin-bottom: 12px;
279
+ font-weight: bold;
280
+ border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);
281
+ padding-bottom: 6px;
282
+ }
283
+
284
+ .opcua-config-form {
285
+ display: flex;
286
+ flex-direction: column;
287
+ gap: 10px;
288
+ }
289
+
290
+ .opcua-config-row {
291
+ margin: 0 !important;
292
+ display: flex;
293
+ align-items: center;
294
+ }
295
+
296
+ .opcua-config-row--checkbox {
297
+ height: 34px;
298
+ }
299
+
300
+ .opcua-config-label {
301
+ width: 240px;
302
+ margin: 0 !important;
303
+ font-weight: 500;
304
+ flex-shrink: 0;
305
+ display: inline-block;
306
+ vertical-align: middle;
307
+ }
308
+
309
+ .opcua-config-input {
310
+ width: 300px !important;
311
+ margin: 0 !important;
312
+ }
313
+
314
+ .opcua-config-input[type="checkbox"] {
315
+ width: auto !important;
316
+ display: inline-block;
317
+ }
@@ -7,45 +7,21 @@
7
7
  <label for="node-input-serverName"><i class="fa fa-server"></i> Server name</label>
8
8
  <input type="text" id="node-input-serverName" placeholder="Node-RED OPC UA Server">
9
9
  </div>
10
- <div class="form-row">
11
- <label for="node-input-resourcePath"><i class="fa fa-server"></i> Resource path</label>
12
- <input type="text" id="node-input-resourcePath" placeholder="Resource path OPC UA Server">
13
- </div>
14
10
  <div class="form-row">
15
11
  <label for="node-input-port"><i class="fa fa-plug"></i> Port</label>
16
12
  <input type="number" id="node-input-port" min="1" placeholder="4840">
17
13
  </div>
18
14
  <div class="form-row">
19
- <label for="node-input-maxConnections"><i class="fa fa-users"></i> Number connections</label>
20
- <input type="number" id="node-input-maxConnections" min="1" placeholder="10">
21
- </div>
22
- <div class="form-row">
23
- <label for="node-input-securityPolicy"><i class="fa fa-lock"></i> Security</label>
24
- <select id="node-input-securityPolicy">
25
- <option value="None">None</option>
26
- <option value="Basic128Rsa15">Basic128Rsa15</option>
27
- <option value="Basic256">Basic256</option>
28
- <option value="Basic256Sha256">Basic256Sha256</option>
29
- <option value="Aes128_Sha256_RsaOaep">Aes128_Sha256_RsaOaep</option>
30
- <option value="Aes256_Sha256_RsaPss">Aes256_Sha256_RsaPss</option>
31
- </select>
32
- </div>
33
- <div class="form-row">
34
- <label for="node-input-securityMode"><i class="fa fa-shield"></i> Security mode</label>
35
- <select id="node-input-securityMode">
36
- <option value="None">None</option>
37
- <option value="Sign">Sign</option>
38
- <option value="SignAndEncrypt">SignAndEncrypt</option>
39
- </select>
15
+ <label style="width: auto;"><i class="fa fa-cogs"></i> Settings</label>
16
+ <a href="#" id="node-input-open-settings-modal" class="editor-button"><i class="fa fa-cog"></i> Open server settings</a>
40
17
  </div>
41
18
  <div class="form-row">
42
- <label for="node-input-allowAnonymous"><i class="fa fa-user-secret"></i> Anonymous</label>
43
- <input type="checkbox" id="node-input-allowAnonymous" style="display:inline-block; width:auto; vertical-align:top; margin-top:8px;">
44
- <span style="margin-left: 8px;">Allow anonymous login</span>
19
+ <label style="width: auto;"><i class="fa fa-shield"></i> Security</label>
20
+ <a href="#" id="node-input-open-auth-modal" class="editor-button"><i class="fa fa-lock"></i> Manage security and users</a>
45
21
  </div>
46
22
  <div class="form-row">
47
- <label style="width: auto;"><i class="fa fa-users"></i> Users</label>
48
- <a href="#" id="node-input-open-auth-modal" class="editor-button"><i class="fa fa-user-plus"></i> Manage users and groups</a>
23
+ <label style="width: auto;"><i class="fa fa-certificate"></i> Certificates</label>
24
+ <a href="#" id="node-input-open-cert-modal" class="editor-button"><i class="fa fa-folder-open"></i> Manage client certificates</a>
49
25
  </div>
50
26
 
51
27
 
@@ -71,15 +47,38 @@
71
47
  <div id="node-input-auth-modal" class="opcua-tree-modal" style="display:none;">
72
48
  <div class="opcua-tree-modal__dialog">
73
49
  <div class="opcua-tree-modal__header">
74
- <div class="opcua-tree-modal__title"><i class="fa fa-users"></i> User And Group Management</div>
50
+ <div class="opcua-tree-modal__title"><i class="fa fa-shield"></i> Security & User Management</div>
75
51
  <a href="#" id="node-input-close-auth-modal" class="editor-button editor-button-small"><i class="fa fa-times"></i> Close</a>
76
52
  </div>
77
- <div class="opcua-tree-modal__toolbar">
78
- <a href="#" id="node-input-add-auth-group" class="editor-button editor-button-small"><i class="fa fa-plus"></i> Add group</a>
79
- <a href="#" id="node-input-add-auth-user" class="editor-button editor-button-small"><i class="fa fa-plus"></i> Add user</a>
80
- </div>
81
- <div class="opcua-tree-modal__body">
82
- <div class="opcua-auth-layout">
53
+ <div class="opcua-tree-modal__body" style="display: flex; flex-direction: column; gap: 14px;">
54
+ <div class="opcua-config-card">
55
+ <div class="opcua-tree-details-title opcua-card-title"><i class="fa fa-shield"></i> Security Settings</div>
56
+ <div class="opcua-config-form">
57
+ <div class="opcua-config-row">
58
+ <label class="opcua-config-label" for="node-input-securityPolicy"><i class="fa fa-lock"></i> Security Policy</label>
59
+ <input class="opcua-config-input" type="text" id="node-input-securityPolicy">
60
+ </div>
61
+ <div class="opcua-config-row">
62
+ <label class="opcua-config-label" for="node-input-securityMode"><i class="fa fa-shield"></i> Security Mode</label>
63
+ <input class="opcua-config-input" type="text" id="node-input-securityMode">
64
+ </div>
65
+ <div class="opcua-config-row opcua-config-row--checkbox">
66
+ <label class="opcua-config-label" for="node-input-allowAnonymous"><i class="fa fa-user-secret"></i> Allow anonymous login</label>
67
+ <input class="opcua-config-input" type="checkbox" id="node-input-allowAnonymous">
68
+ </div>
69
+ <div class="opcua-config-row opcua-config-row--checkbox">
70
+ <label class="opcua-config-label" for="node-input-automaticallyAcceptUnknownCertificate"><i class="fa fa-certificate"></i> Automatically accept unknown certificates</label>
71
+ <input class="opcua-config-input" type="checkbox" id="node-input-automaticallyAcceptUnknownCertificate">
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <div style="display: flex; gap: 10px; margin-bottom: 2px;">
77
+ <a href="#" id="node-input-add-auth-group" class="editor-button editor-button-small"><i class="fa fa-plus"></i> Add group</a>
78
+ <a href="#" id="node-input-add-auth-user" class="editor-button editor-button-small"><i class="fa fa-plus"></i> Add user</a>
79
+ </div>
80
+
81
+ <div class="opcua-auth-layout" style="flex: 1 1 auto;">
83
82
  <div class="opcua-auth-panel">
84
83
  <div class="opcua-tree-details-title">Groups</div>
85
84
  <div id="opcua-auth-groups" class="opcua-auth-list"></div>
@@ -93,6 +92,83 @@
93
92
  </div>
94
93
  </div>
95
94
 
95
+ <div id="node-input-settings-modal" class="opcua-tree-modal" style="display:none;">
96
+ <div class="opcua-tree-modal__dialog">
97
+ <div class="opcua-tree-modal__header">
98
+ <div class="opcua-tree-modal__title"><i class="fa fa-cogs"></i> Server Settings</div>
99
+ <a href="#" id="node-input-close-settings-modal" class="editor-button editor-button-small"><i class="fa fa-times"></i> Close</a>
100
+ </div>
101
+ <div class="opcua-tree-modal__body" style="display: flex; flex-direction: column; gap: 14px;">
102
+ <div class="opcua-config-card">
103
+ <div class="opcua-tree-details-title opcua-card-title"><i class="fa fa-cogs"></i> General Settings</div>
104
+ <div class="opcua-config-form">
105
+ <div class="opcua-config-row">
106
+ <label class="opcua-config-label" for="node-input-resourcePath"><i class="fa fa-server"></i> Resource path</label>
107
+ <input class="opcua-config-input" type="text" id="node-input-resourcePath" placeholder="Resource path OPC UA Server">
108
+ </div>
109
+ <div class="opcua-config-row">
110
+ <label class="opcua-config-label" for="node-input-maxConnections"><i class="fa fa-users"></i> Number connections</label>
111
+ <input class="opcua-config-input" type="number" id="node-input-maxConnections" min="1" placeholder="10">
112
+ </div>
113
+ <div class="opcua-config-row">
114
+ <label class="opcua-config-label" for="node-input-minSessionTimeout"><i class="fa fa-clock-o"></i> Min Session Timeout (ms)</label>
115
+ <input class="opcua-config-input" type="number" id="node-input-minSessionTimeout" min="0" placeholder="100">
116
+ </div>
117
+ <div class="opcua-config-row">
118
+ <label class="opcua-config-label" for="node-input-defaultSessionTimeout"><i class="fa fa-clock-o"></i> Default Session Timeout (ms)</label>
119
+ <input class="opcua-config-input" type="number" id="node-input-defaultSessionTimeout" min="0" placeholder="30000">
120
+ </div>
121
+ <div class="opcua-config-row">
122
+ <label class="opcua-config-label" for="node-input-maxSessionTimeout"><i class="fa fa-clock-o"></i> Max Session Timeout (ms)</label>
123
+ <input class="opcua-config-input" type="number" id="node-input-maxSessionTimeout" min="0" placeholder="3000000">
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+
131
+ <div id="node-input-cert-modal" class="opcua-tree-modal" style="display:none;">
132
+ <div class="opcua-tree-modal__dialog">
133
+ <div class="opcua-tree-modal__header">
134
+ <div class="opcua-tree-modal__title"><i class="fa fa-certificate"></i> Client Certificate Management</div>
135
+ <a href="#" id="node-input-close-cert-modal" class="editor-button editor-button-small"><i class="fa fa-times"></i> Close</a>
136
+ </div>
137
+ <div class="opcua-tree-modal__body">
138
+ <div class="opcua-auth-layout">
139
+ <div class="opcua-auth-panel">
140
+ <div class="opcua-tree-details-title">Folders</div>
141
+ <div id="opcua-cert-folders" class="opcua-auth-list">
142
+ <div class="opcua-cert-item is-selected" data-folder="rejected">
143
+ <i class="fa fa-ban" style="color: #d9534f; width: 16px; text-align: center;"></i> Rejected Certificates
144
+ </div>
145
+ <div class="opcua-cert-item" data-folder="trusted">
146
+ <i class="fa fa-check-circle" style="color: #5cb85c; width: 16px; text-align: center;"></i> Trusted Certificates
147
+ </div>
148
+ </div>
149
+ </div>
150
+ <div class="opcua-auth-panel">
151
+ <div class="opcua-tree-details-title">Certificates</div>
152
+ <div id="opcua-cert-files" class="opcua-auth-list" style="display: flex; flex-direction: column; gap: 2px;">
153
+ <!-- List of certificates -->
154
+ </div>
155
+ <div id="opcua-cert-details" class="opcua-auth-card" style="margin-top: 10px; display: none;">
156
+ <div class="form-row" style="margin-bottom: 8px;">
157
+ <label style="width: auto; font-weight: bold;"><i class="fa fa-certificate"></i> Selected Certificate:</label>
158
+ <span id="opcua-selected-cert-name" style="word-break: break-all; margin-left: 8px;"></span>
159
+ </div>
160
+ <div class="form-row" style="margin-bottom: 0; display: flex; align-items: center; gap: 8px;">
161
+ <label style="width: auto;">Move to:</label>
162
+ <select id="opcua-cert-target-folder" style="width: 150px; margin: 0;"></select>
163
+ <a href="#" id="opcua-cert-move-btn" class="editor-button"><i class="fa fa-exchange"></i> Move</a>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
96
172
  <div id="node-input-tree-modal" class="opcua-tree-modal" style="display:none;">
97
173
  <div class="opcua-tree-modal__dialog">
98
174
  <div class="opcua-tree-modal__header">
@@ -148,12 +224,58 @@
148
224
 
149
225
 
150
226
  <script type="text/html" data-help-name="opc-ua-server">
151
- <p>Creates a configurable OPC UA server using <code>node-opcua</code>.</p>
152
- <p>The node starts an OPC UA server when deployed and exposes a dynamic tree under the configured namespace.</p>
227
+ <p>Creates a fully dynamic, configurable OPC UA server using <code>node-opcua</code>.</p>
228
+ <p>The server parses a JSON tree structure defined in the modal editor or provided dynamically to construct the OPC UA Address Space at runtime without manual coding.</p>
229
+
153
230
  <h3>Inputs</h3>
154
- <p><code>msg.payload</code>: JSON object describing the OPC UA tree. When provided, the node validates the structure and rebuilds the dynamic namespace.</p>
155
- <h3>Details</h3>
156
- <p>Supported variable types: <code>Int16</code>, <code>Int32</code>, <code>Int64</code>, <code>Float</code>, <code>Boolean</code>, <code>String</code>.</p>
157
- <p>Supported access modes: <code>readonly</code> and <code>readwrite</code>.</p>
158
- <p>Authentication can be configured in the editor with multiple local users and groups stored in Node-RED credentials, and anonymous login can be disabled.</p>
231
+ <dl class="message-properties">
232
+ <dt>payload <span class="property-type">object | string</span></dt>
233
+ <dd>A JSON object or string describing the target OPC UA tree configuration. The server will validate the payload and dynamically rebuild the entire address space.</dd>
234
+ </dl>
235
+
236
+ <h3>Address Space Categories</h3>
237
+
238
+ <h4>1. Folders & Objects</h4>
239
+ <ul>
240
+ <li><strong>Folders:</strong> Standard structural directories used to group objects and variables.</li>
241
+ <li><strong>Objects:</strong> Real-world component representations. Objects and folders can be nested hierarchically.</li>
242
+ </ul>
243
+
244
+ <h4>2. Variables</h4>
245
+ <ul>
246
+ <li><strong>Data Types:</strong> Supported data types include <code>Int16</code>, <code>UInt16</code>, <code>Int32</code>, <code>UInt32</code>, <code>Float</code>, <code>Boolean</code>, <code>String</code>, <code>ByteString</code>, and <code>LocalizedText</code>.</li>
247
+ <li><strong>Access Control:</strong> Variable access can be set to <code>readonly</code> or <code>readwrite</code>.</li>
248
+ </ul>
249
+
250
+ <h4>3. ObjectTypes (Templates)</h4>
251
+ <ul>
252
+ <li>Define reusable object type definitions (templates) under the <code>Types/ObjectTypes</code> visual folder.</li>
253
+ <li>Instantiate these types under folders/objects. Changes to the ObjectType structure will automatically propagate to all instances.</li>
254
+ <li><strong>Alarms on ObjectTypes:</strong> When configuring an alarm inside an ObjectType, the alarm variable path must reference the variable using the format <code>'ObjectTypeName'.'VariableName'</code> (e.g. <code>motor.status</code>).</li>
255
+ </ul>
256
+
257
+ <h4>4. Enumerations (DataTypes)</h4>
258
+ <ul>
259
+ <li>Define custom enumeration data types mapping integer states to display strings (e.g. <code>0: Stopped</code>, <code>1: Running</code>).</li>
260
+ <li>Variables can select these custom Enumerations as their data type.</li>
261
+ </ul>
262
+
263
+ <h4>5. Methods</h4>
264
+ <ul>
265
+ <li>Define callable server methods with custom <strong>Input Arguments</strong> and <strong>Output Arguments</strong>.</li>
266
+ <li>Incoming method calls are routed automatically to matching <code>opcua-server-io</code> nodes (set to <code>method-input</code> mode) where flow logic handles execution and returns the result via <code>method-output</code>.</li>
267
+ </ul>
268
+
269
+ <h4>6. Alarms</h4>
270
+ <ul>
271
+ <li><strong>Level Alarms:</strong> Monitor numeric variables and trigger limit events (<code>highHighLimit</code>, <code>highLimit</code>, <code>lowLimit</code>, <code>lowLowLimit</code>) with custom severity.</li>
272
+ <li><strong>Digital Alarms:</strong> Triggered when a boolean/numeric variable matches or deviates from a configured normal state.</li>
273
+ </ul>
274
+
275
+ <h3>Security & Access Control</h3>
276
+ <ul>
277
+ <li><strong>Authentication:</strong> Supports anonymous login and authenticated username/password login. Passwords can be hashed automatically.</li>
278
+ <li><strong>Access Permissions:</strong> Each folder, variable, method, or alarm can restrict access to specified user groups (comma-separated list, e.g. <code>operator,engineer</code>). If no users are configured, permission checks are disabled.</li>
279
+ <li><strong>Client Certificates:</strong> Open the Certificate modal to inspect, trust, or reject incoming client certificates. Trusted certificates reside in the simple_opcua server storage.</li>
280
+ </ul>
159
281
  </script>