@vitormnm/node-red-simple-opcua 1.6.3 → 1.8.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 +104 -136
- package/client/lib/opcua-client-browser.js +254 -11
- package/client/lib/opcua-client-method-service.js +1 -1
- package/client/lib/opcua-client-subscription-service.js +0 -2
- package/client/lib/opcua-client-write-service.js +14 -4
- package/client/opcua-client-config.html +118 -1
- package/client/opcua-client-config.js +112 -9
- package/client/opcua-client-help.html +6 -0
- package/client/opcua-client-utils.js +158 -10
- package/client/opcua-client.html +8 -0
- package/client/opcua-client.js +97 -1
- package/client/view/opcua-client.js +106 -14
- package/examples/flows_simple_opc.json +1 -1
- package/package.json +2 -2
- package/server/lib/opcua-address-space-alarm.js +95 -32
- package/server/lib/opcua-address-space-builder.js +717 -59
- package/server/lib/opcua-config.js +110 -35
- package/server/lib/opcua-server-events-child.js +31 -5
- package/server/lib/opcua-server-runtime-child.js +424 -27
- package/server/lib/opcua-server-runtime.js +52 -5
- package/server/lib/opcua-server-status-child.js +46 -15
- package/server/nodered/simple_opcua/server/certificates/mutex +0 -0
- package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem +25 -0
- package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
- package/server/nodered/simple_opcua/server/certificates/own/openssl.cnf +72 -0
- package/server/nodered/simple_opcua/server/certificates/own/private/private_key.pem +28 -0
- package/server/nodered/simple_opcua/server/certificates/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/mutex +0 -0
- package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
- package/server/nodered/simple_opcua/server/myServer1/own/openssl.cnf +72 -0
- package/server/nodered/simple_opcua/server/myServer1/own/private/private_key.pem +28 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[91e520c64ff891c67168f08a46dd194071e15dae].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[98ae95da627cea4c500753c319161a3554ee38d7].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[aef8d7a1cfba13d84189a0bcf1694208fc51a7f9].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[ebdf9acf1d02e347917a14108d3144799c638ea3].pem +25 -0
- package/server/opcua-server-io.html +93 -1
- package/server/opcua-server-io.js +153 -29
- package/server/opcua-server-registry.js +8 -2
- package/server/opcua-server.css +64 -0
- package/server/opcua-server.html +168 -44
- package/server/opcua-server.js +115 -5
- package/server/view/opcua-server.css +100 -6
- package/server/view/opcua-server.js +746 -48
|
@@ -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,76 @@ 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
|
-
|
|
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
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (node.mode === "validateLogin") {
|
|
123
|
+
await handleValidateLogin(node, msg, { waitForServer: true, timeoutMs: 5000 });
|
|
124
|
+
done();
|
|
125
|
+
if (msg && msg._msgid) {
|
|
126
|
+
node._pendingDoneCallbacks.delete(msg._msgid);
|
|
127
|
+
}
|
|
91
128
|
}
|
|
92
129
|
|
|
93
|
-
done();
|
|
94
130
|
} catch (error) {
|
|
95
131
|
node.status({ fill: "red", shape: "ring", text: node.mode + " failed" });
|
|
96
|
-
|
|
132
|
+
node.error(error.message || String(error), msg);
|
|
133
|
+
if (done) {
|
|
134
|
+
done();
|
|
135
|
+
}
|
|
136
|
+
if (msg && msg._msgid) {
|
|
137
|
+
node._pendingDoneCallbacks.delete(msg._msgid);
|
|
138
|
+
node._pendingMessages.delete(msg._msgid);
|
|
139
|
+
}
|
|
97
140
|
}
|
|
98
141
|
});
|
|
99
142
|
|
|
@@ -104,6 +147,8 @@ module.exports = function (RED) {
|
|
|
104
147
|
}
|
|
105
148
|
|
|
106
149
|
detachChildListener(node, handler);
|
|
150
|
+
node._pendingDoneCallbacks.clear();
|
|
151
|
+
node._pendingMessages.clear();
|
|
107
152
|
|
|
108
153
|
|
|
109
154
|
if (node.mode === "method-input") {
|
|
@@ -144,21 +189,63 @@ module.exports = function (RED) {
|
|
|
144
189
|
data.payload = restoreBuffers(data.payload);
|
|
145
190
|
}
|
|
146
191
|
|
|
147
|
-
|
|
192
|
+
const originalMsgId = data && data._msgid;
|
|
193
|
+
let actualMsg = data;
|
|
194
|
+
if (originalMsgId && node._pendingMessages.has(originalMsgId)) {
|
|
195
|
+
actualMsg = node._pendingMessages.get(originalMsgId);
|
|
196
|
+
actualMsg.payload = data.payload;
|
|
197
|
+
actualMsg.opcua = data.opcua;
|
|
198
|
+
actualMsg.topic = data.topic;
|
|
199
|
+
node._pendingMessages.delete(originalMsgId);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
node.send(actualMsg);
|
|
203
|
+
|
|
204
|
+
if (originalMsgId && node._pendingDoneCallbacks.has(originalMsgId)) {
|
|
205
|
+
const pendingDone = node._pendingDoneCallbacks.get(originalMsgId);
|
|
206
|
+
pendingDone();
|
|
207
|
+
node._pendingDoneCallbacks.delete(originalMsgId);
|
|
208
|
+
}
|
|
148
209
|
}
|
|
149
210
|
|
|
150
211
|
if (msg.type === "error") {
|
|
151
212
|
node.status(msg.data);
|
|
152
|
-
|
|
213
|
+
|
|
214
|
+
const originalMsgId = msg.originalMsg && msg.originalMsg._msgid;
|
|
215
|
+
let catchMsg = msg.originalMsg || {};
|
|
216
|
+
if (originalMsgId && node._pendingMessages.has(originalMsgId)) {
|
|
217
|
+
catchMsg = node._pendingMessages.get(originalMsgId);
|
|
218
|
+
node._pendingMessages.delete(originalMsgId);
|
|
219
|
+
}
|
|
220
|
+
catchMsg.error = msg.error;
|
|
221
|
+
|
|
222
|
+
node.error(msg.error, catchMsg);
|
|
223
|
+
|
|
224
|
+
if (originalMsgId && node._pendingDoneCallbacks.has(originalMsgId)) {
|
|
225
|
+
const pendingDone = node._pendingDoneCallbacks.get(originalMsgId);
|
|
226
|
+
pendingDone();
|
|
227
|
+
node._pendingDoneCallbacks.delete(originalMsgId);
|
|
228
|
+
}
|
|
153
229
|
}
|
|
154
230
|
|
|
155
231
|
if (msg.type === "partialError") {
|
|
156
232
|
// Route failed items to catch node without changing the node status
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
233
|
+
const originalMsgId = msg.originalMsg && msg.originalMsg._msgid;
|
|
234
|
+
let catchMsg = msg.originalMsg || {};
|
|
235
|
+
if (originalMsgId && node._pendingMessages.has(originalMsgId)) {
|
|
236
|
+
catchMsg = node._pendingMessages.get(originalMsgId);
|
|
237
|
+
node._pendingMessages.delete(originalMsgId);
|
|
238
|
+
}
|
|
239
|
+
catchMsg.payload = msg.failed;
|
|
240
|
+
catchMsg.error = msg.error;
|
|
241
|
+
|
|
161
242
|
node.error(msg.error, catchMsg);
|
|
243
|
+
|
|
244
|
+
if (originalMsgId && node._pendingDoneCallbacks.has(originalMsgId)) {
|
|
245
|
+
const pendingDone = node._pendingDoneCallbacks.get(originalMsgId);
|
|
246
|
+
pendingDone();
|
|
247
|
+
node._pendingDoneCallbacks.delete(originalMsgId);
|
|
248
|
+
}
|
|
162
249
|
}
|
|
163
250
|
|
|
164
251
|
if (msg.type === "sendMethod") {
|
|
@@ -175,7 +262,8 @@ module.exports = function (RED) {
|
|
|
175
262
|
opcua: {
|
|
176
263
|
server: msg.data.serverName,
|
|
177
264
|
method: msg.data.methodName,
|
|
178
|
-
data: msg.data
|
|
265
|
+
data: msg.data,
|
|
266
|
+
users: Array.isArray(msg.data.users) ? msg.data.users : []
|
|
179
267
|
},
|
|
180
268
|
_callId: msg.data.callId
|
|
181
269
|
});
|
|
@@ -206,7 +294,8 @@ module.exports = function (RED) {
|
|
|
206
294
|
opcua: {
|
|
207
295
|
server: msg.data.serverName,
|
|
208
296
|
method: msg.data.methodName,
|
|
209
|
-
data: msg.data
|
|
297
|
+
data: msg.data,
|
|
298
|
+
users: Array.isArray(msg.data.users) ? msg.data.users : []
|
|
210
299
|
},
|
|
211
300
|
_callId: msg.data.callId
|
|
212
301
|
});
|
|
@@ -223,16 +312,20 @@ module.exports = function (RED) {
|
|
|
223
312
|
async function registerMethodInput(node) {
|
|
224
313
|
await sendToChild(node, {
|
|
225
314
|
type: "registerMethodInput",
|
|
226
|
-
node:
|
|
315
|
+
node: {
|
|
316
|
+
methodName: node.methodName
|
|
317
|
+
},
|
|
227
318
|
nodeId: node.id
|
|
228
319
|
|
|
229
|
-
}, { throwOnError: false, waitForServer: true, timeoutMs: 5000 });
|
|
320
|
+
}, { throwOnError: false, waitForServer: true, timeoutMs: 5000, silentOnError: true });
|
|
230
321
|
}
|
|
231
322
|
|
|
232
323
|
async function registerEvents(node, options) {
|
|
233
324
|
return sendToChild(node, {
|
|
234
325
|
type: "eventsServer",
|
|
235
|
-
node:
|
|
326
|
+
node: {
|
|
327
|
+
intervalMs: node.intervalMs
|
|
328
|
+
},
|
|
236
329
|
nodeId: node.id
|
|
237
330
|
}, options);
|
|
238
331
|
}
|
|
@@ -240,7 +333,30 @@ module.exports = function (RED) {
|
|
|
240
333
|
async function requestSnapshot(node, msg, options) {
|
|
241
334
|
return sendToChild(node, {
|
|
242
335
|
type: "buildServerSnapshot",
|
|
243
|
-
|
|
336
|
+
msg: msg,
|
|
337
|
+
nodeId: node.id
|
|
338
|
+
}, options);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function requestSessions(node, msg, options) {
|
|
342
|
+
return sendToChild(node, {
|
|
343
|
+
type: "readActiveSessions",
|
|
344
|
+
msg: msg,
|
|
345
|
+
nodeId: node.id
|
|
346
|
+
}, options);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function handleDeleteSessions(node, msg, options) {
|
|
350
|
+
return sendToChild(node, {
|
|
351
|
+
type: "deleteActiveSessions",
|
|
352
|
+
msg: msg,
|
|
353
|
+
nodeId: node.id
|
|
354
|
+
}, options);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function handleValidateLogin(node, msg, options) {
|
|
358
|
+
return sendToChild(node, {
|
|
359
|
+
type: "validateLogin",
|
|
244
360
|
msg: msg,
|
|
245
361
|
nodeId: node.id
|
|
246
362
|
}, options);
|
|
@@ -375,6 +491,10 @@ module.exports = function (RED) {
|
|
|
375
491
|
registerMethodInput(node);
|
|
376
492
|
}
|
|
377
493
|
|
|
494
|
+
if (node.mode === "events") {
|
|
495
|
+
registerEvents(node, { throwOnError: false, silentOnError: true });
|
|
496
|
+
}
|
|
497
|
+
|
|
378
498
|
return true;
|
|
379
499
|
}
|
|
380
500
|
|
|
@@ -455,7 +575,9 @@ module.exports = function (RED) {
|
|
|
455
575
|
const error = new Error("OPC UA server child process is not available for serverRef: " + node.serverRef);
|
|
456
576
|
node.status({ fill: "red", shape: "ring", text: "server unavailable" });
|
|
457
577
|
if (options.throwOnError === false) {
|
|
458
|
-
|
|
578
|
+
if (!options.silentOnError) {
|
|
579
|
+
node.error(error.message);
|
|
580
|
+
}
|
|
459
581
|
return false;
|
|
460
582
|
}
|
|
461
583
|
throw error;
|
|
@@ -473,7 +595,9 @@ module.exports = function (RED) {
|
|
|
473
595
|
node._attachedChild = null;
|
|
474
596
|
ensureChildListener(node, handler);
|
|
475
597
|
if (options.throwOnError === false) {
|
|
476
|
-
|
|
598
|
+
if (!options.silentOnError) {
|
|
599
|
+
node.error(error.message);
|
|
600
|
+
}
|
|
477
601
|
return false;
|
|
478
602
|
}
|
|
479
603
|
throw error;
|
|
@@ -216,8 +216,14 @@ function getActiveAlarms(node) {
|
|
|
216
216
|
conditionName: ConditionName,
|
|
217
217
|
ConfirmedState: ConfirmedState,
|
|
218
218
|
ackedState: isAcked,
|
|
219
|
-
alarmNode:
|
|
220
|
-
|
|
219
|
+
alarmNode: {
|
|
220
|
+
nodeId: alarmNode.nodeId,
|
|
221
|
+
browseName: alarmNode.browseName,
|
|
222
|
+
displayName: alarmNode.displayName,
|
|
223
|
+
description: alarmNode.description,
|
|
224
|
+
nodeClass: alarmNode.nodeClass,
|
|
225
|
+
typeDefinition: alarmNode.typeDefinition
|
|
226
|
+
}
|
|
221
227
|
})
|
|
222
228
|
});
|
|
223
229
|
|
package/server/opcua-server.css
CHANGED
|
@@ -263,3 +263,67 @@
|
|
|
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
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Drag and Drop styling for visual tree rows */
|
|
320
|
+
.opcua-tree-row.is-dragging {
|
|
321
|
+
opacity: 0.4;
|
|
322
|
+
border: 1px dashed var(--red-ui-form-input-border-color, #999);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.opcua-tree-row.drag-over {
|
|
326
|
+
background-color: var(--red-ui-list-item-background-hover, #f3f7fd) !important;
|
|
327
|
+
box-shadow: inset 0 0 0 2px var(--red-ui-form-input-border-color-selected, #2196F3);
|
|
328
|
+
}
|
|
329
|
+
|
package/server/opcua-server.html
CHANGED
|
@@ -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
|
|
20
|
-
<
|
|
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
|
|
43
|
-
<
|
|
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-
|
|
48
|
-
<a href="#" id="node-input-open-
|
|
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-
|
|
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-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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">
|
|
@@ -135,6 +211,8 @@
|
|
|
135
211
|
<a href="#" data-action="add-method"><i class="fa fa-plus"></i> Add Method</a>
|
|
136
212
|
<a href="#" data-action="edit"><i class="fa fa-pencil"></i> Edit</a>
|
|
137
213
|
<a href="#" data-action="remove"><i class="fa fa-trash"></i> Remove</a>
|
|
214
|
+
<a href="#" data-action="expand-all-below"><i class="fa fa-angle-double-down"></i> Expand All Below</a>
|
|
215
|
+
<a href="#" data-action="collapse-all-below"><i class="fa fa-angle-double-up"></i> Collapse All Below</a>
|
|
138
216
|
</div>
|
|
139
217
|
</div>
|
|
140
218
|
</div>
|
|
@@ -148,12 +226,58 @@
|
|
|
148
226
|
|
|
149
227
|
|
|
150
228
|
<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
|
|
229
|
+
<p>Creates a fully dynamic, configurable OPC UA server using <code>node-opcua</code>.</p>
|
|
230
|
+
<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>
|
|
231
|
+
|
|
153
232
|
<h3>Inputs</h3>
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
233
|
+
<dl class="message-properties">
|
|
234
|
+
<dt>payload <span class="property-type">object | string</span></dt>
|
|
235
|
+
<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>
|
|
236
|
+
</dl>
|
|
237
|
+
|
|
238
|
+
<h3>Address Space Categories</h3>
|
|
239
|
+
|
|
240
|
+
<h4>1. Folders & Objects</h4>
|
|
241
|
+
<ul>
|
|
242
|
+
<li><strong>Folders:</strong> Standard structural directories used to group objects and variables.</li>
|
|
243
|
+
<li><strong>Objects:</strong> Real-world component representations. Objects and folders can be nested hierarchically.</li>
|
|
244
|
+
</ul>
|
|
245
|
+
|
|
246
|
+
<h4>2. Variables</h4>
|
|
247
|
+
<ul>
|
|
248
|
+
<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>
|
|
249
|
+
<li><strong>Access Control:</strong> Variable access can be set to <code>readonly</code> or <code>readwrite</code>.</li>
|
|
250
|
+
</ul>
|
|
251
|
+
|
|
252
|
+
<h4>3. ObjectTypes (Templates)</h4>
|
|
253
|
+
<ul>
|
|
254
|
+
<li>Define reusable object type definitions (templates) under the <code>Types/ObjectTypes</code> visual folder.</li>
|
|
255
|
+
<li>Instantiate these types under folders/objects. Changes to the ObjectType structure will automatically propagate to all instances.</li>
|
|
256
|
+
<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>
|
|
257
|
+
</ul>
|
|
258
|
+
|
|
259
|
+
<h4>4. Enumerations (DataTypes)</h4>
|
|
260
|
+
<ul>
|
|
261
|
+
<li>Define custom enumeration data types mapping integer states to display strings (e.g. <code>0: Stopped</code>, <code>1: Running</code>).</li>
|
|
262
|
+
<li>Variables can select these custom Enumerations as their data type.</li>
|
|
263
|
+
</ul>
|
|
264
|
+
|
|
265
|
+
<h4>5. Methods</h4>
|
|
266
|
+
<ul>
|
|
267
|
+
<li>Define callable server methods with custom <strong>Input Arguments</strong> and <strong>Output Arguments</strong>.</li>
|
|
268
|
+
<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>
|
|
269
|
+
</ul>
|
|
270
|
+
|
|
271
|
+
<h4>6. Alarms</h4>
|
|
272
|
+
<ul>
|
|
273
|
+
<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>
|
|
274
|
+
<li><strong>Digital Alarms:</strong> Triggered when a boolean/numeric variable matches or deviates from a configured normal state.</li>
|
|
275
|
+
</ul>
|
|
276
|
+
|
|
277
|
+
<h3>Security & Access Control</h3>
|
|
278
|
+
<ul>
|
|
279
|
+
<li><strong>Authentication:</strong> Supports anonymous login and authenticated username/password login. Passwords can be hashed automatically.</li>
|
|
280
|
+
<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>
|
|
281
|
+
<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>
|
|
282
|
+
</ul>
|
|
159
283
|
</script>
|