@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.
Files changed (45) hide show
  1. package/README.md +104 -136
  2. package/client/lib/opcua-client-browser.js +254 -11
  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/lib/opcua-client-write-service.js +14 -4
  6. package/client/opcua-client-config.html +118 -1
  7. package/client/opcua-client-config.js +112 -9
  8. package/client/opcua-client-help.html +6 -0
  9. package/client/opcua-client-utils.js +158 -10
  10. package/client/opcua-client.html +8 -0
  11. package/client/opcua-client.js +97 -1
  12. package/client/view/opcua-client.js +106 -14
  13. package/examples/flows_simple_opc.json +1 -1
  14. package/package.json +2 -2
  15. package/server/lib/opcua-address-space-alarm.js +95 -32
  16. package/server/lib/opcua-address-space-builder.js +717 -59
  17. package/server/lib/opcua-config.js +110 -35
  18. package/server/lib/opcua-server-events-child.js +31 -5
  19. package/server/lib/opcua-server-runtime-child.js +424 -27
  20. package/server/lib/opcua-server-runtime.js +52 -5
  21. package/server/lib/opcua-server-status-child.js +46 -15
  22. package/server/nodered/simple_opcua/server/certificates/mutex +0 -0
  23. package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem +25 -0
  24. package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
  25. package/server/nodered/simple_opcua/server/certificates/own/openssl.cnf +72 -0
  26. package/server/nodered/simple_opcua/server/certificates/own/private/private_key.pem +28 -0
  27. package/server/nodered/simple_opcua/server/certificates/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
  28. package/server/nodered/simple_opcua/server/myServer1/mutex +0 -0
  29. package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem +25 -0
  30. package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
  31. package/server/nodered/simple_opcua/server/myServer1/own/openssl.cnf +72 -0
  32. package/server/nodered/simple_opcua/server/myServer1/own/private/private_key.pem +28 -0
  33. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[91e520c64ff891c67168f08a46dd194071e15dae].pem +25 -0
  34. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[98ae95da627cea4c500753c319161a3554ee38d7].pem +25 -0
  35. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[aef8d7a1cfba13d84189a0bcf1694208fc51a7f9].pem +25 -0
  36. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
  37. package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[ebdf9acf1d02e347917a14108d3144799c638ea3].pem +25 -0
  38. package/server/opcua-server-io.html +93 -1
  39. package/server/opcua-server-io.js +153 -29
  40. package/server/opcua-server-registry.js +8 -2
  41. package/server/opcua-server.css +64 -0
  42. package/server/opcua-server.html +168 -44
  43. package/server/opcua-server.js +115 -5
  44. package/server/view/opcua-server.css +100 -6
  45. package/server/view/opcua-server.js +746 -48
@@ -8,12 +8,29 @@ module.exports = function (RED) {
8
8
  const { fork } = require("child_process");
9
9
  const path = require("path");
10
10
 
11
+ const getCertificatesFolder = (serverName) => {
12
+ const safeServerName = (serverName || "default")
13
+ .replace(/[\\/:\*\?"<>|]/g, "_")
14
+ .replace(/^\.+$/, "");
15
+ try {
16
+ const userDir = (RED.settings && RED.settings.userDir) || path.join(require('os').homedir(), ".node-red");
17
+ let flowFile = (RED.settings && RED.settings.flowFile) || "flows.json";
18
+ if (typeof flowFile !== "string") {
19
+ flowFile = "flows.json";
20
+ }
21
+ const flowFileFolder = path.isAbsolute(flowFile) ? path.dirname(flowFile) : path.join(userDir, path.dirname(flowFile));
22
+ return path.join(flowFileFolder, "simple_opcua", "server", safeServerName);
23
+ } catch (err) {
24
+ return path.join(require('os').homedir(), ".node-red", "simple_opcua", "server", safeServerName);
25
+ }
26
+ };
27
+
11
28
  function OpcUaServerNode(config) {
12
29
  RED.nodes.createNode(this, config);
13
30
  const node = this;
14
31
  const parser = new OpcUaServerConfigParser(node);
15
-
16
32
  const settings = parser.parseNodeConfig(config, this.credentials || {});
33
+ settings.certificatesFolder = getCertificatesFolder(settings.serverName);
17
34
 
18
35
 
19
36
 
@@ -129,8 +146,10 @@ module.exports = function (RED) {
129
146
 
130
147
  done();
131
148
  } catch (error) {
132
- reportError(node, "Input processing failed", error);
133
- done(error);
149
+ reportError(node, "Input processing failed", error, msg);
150
+ if (done) {
151
+ done();
152
+ }
134
153
  }
135
154
  });
136
155
 
@@ -152,9 +171,13 @@ module.exports = function (RED) {
152
171
 
153
172
 
154
173
 
155
- function reportError(node, message, error) {
174
+ function reportError(node, message, error, msg) {
156
175
  const details = error && error.message ? error.message : String(error);
157
- node.error(message + ": " + details);
176
+ if (msg) {
177
+ node.error(message + ": " + details, msg);
178
+ } else {
179
+ node.error(message + ": " + details);
180
+ }
158
181
  node.status({ fill: "red", shape: "ring", text: message });
159
182
  }
160
183
 
@@ -170,6 +193,93 @@ module.exports = function (RED) {
170
193
  res.sendFile(jsPath);
171
194
  });
172
195
 
196
+ RED.httpAdmin.get("/opc-ua-server/certificates", function (req, res) {
197
+ const serverName = req.query.serverName || "default";
198
+ const certificatesFolder = getCertificatesFolder(serverName);
199
+
200
+ const fs = require("fs");
201
+ const trustedDir = path.join(certificatesFolder, "trusted", "certs");
202
+ const rejectedDir = path.join(certificatesFolder, "rejected");
203
+
204
+ // Ensure directories exist
205
+ try {
206
+ if (!fs.existsSync(trustedDir)) {
207
+ fs.mkdirSync(trustedDir, { recursive: true });
208
+ }
209
+ if (!fs.existsSync(rejectedDir)) {
210
+ fs.mkdirSync(rejectedDir, { recursive: true });
211
+ }
212
+ } catch (e) {
213
+ // Ignore directory creation errors (fallback to empty)
214
+ }
215
+
216
+ const listFiles = (dir) => {
217
+ try {
218
+ if (!fs.existsSync(dir)) {
219
+ return [];
220
+ }
221
+ return fs.readdirSync(dir).filter(file => {
222
+ const stats = fs.statSync(path.join(dir, file));
223
+ return stats.isFile() && (file.endsWith(".der") || file.endsWith(".pem") || file.endsWith(".crt"));
224
+ });
225
+ } catch (err) {
226
+ return [];
227
+ }
228
+ };
229
+
230
+ res.json({
231
+ trusted: listFiles(trustedDir),
232
+ rejected: listFiles(rejectedDir)
233
+ });
234
+ });
235
+
236
+ RED.httpAdmin.post("/opc-ua-server/certificates/move", function (req, res) {
237
+ const { serverName, filename, fromFolder, toFolder } = req.body;
238
+ const certificatesFolder = getCertificatesFolder(serverName);
239
+
240
+ const fs = require("fs");
241
+
242
+ if (!filename || !fromFolder || !toFolder) {
243
+ return res.status(400).json({ error: "Missing required fields" });
244
+ }
245
+
246
+ const getFolderDir = (folderName) => {
247
+ if (folderName === "trusted") {
248
+ return path.join(certificatesFolder, "trusted", "certs");
249
+ }
250
+ if (folderName === "rejected") {
251
+ return path.join(certificatesFolder, "rejected");
252
+ }
253
+ return null;
254
+ };
255
+
256
+ const srcDir = getFolderDir(fromFolder);
257
+ const destDir = getFolderDir(toFolder);
258
+
259
+ if (!srcDir || !destDir) {
260
+ return res.status(400).json({ error: "Invalid folders specified" });
261
+ }
262
+
263
+ const srcPath = path.join(srcDir, filename);
264
+ const destPath = path.join(destDir, filename);
265
+
266
+ try {
267
+ if (!fs.existsSync(srcPath)) {
268
+ return res.status(404).json({ error: "Source certificate not found" });
269
+ }
270
+
271
+ // Ensure destination directory exists
272
+ if (!fs.existsSync(destDir)) {
273
+ fs.mkdirSync(destDir, { recursive: true });
274
+ }
275
+
276
+ fs.renameSync(srcPath, destPath);
277
+ res.json({ success: true });
278
+ } catch (err) {
279
+ res.status(500).json({ error: "Failed to move certificate: " + err.message });
280
+ }
281
+ });
282
+
173
283
 
174
284
 
175
285
  RED.nodes.registerType("opc-ua-server", OpcUaServerNode, {
@@ -442,6 +442,9 @@ body.opcua-tree-modal-open {
442
442
 
443
443
  .opcua-auth-card .form-row {
444
444
  margin-bottom: 8px;
445
+ display: flex;
446
+ align-items: center;
447
+ gap: 8px;
445
448
  }
446
449
 
447
450
  .opcua-auth-card .form-row:last-child {
@@ -450,15 +453,18 @@ body.opcua-tree-modal-open {
450
453
 
451
454
  .opcua-auth-card label {
452
455
  width: 90px;
456
+ margin: 0;
457
+ flex-shrink: 0;
453
458
  }
454
459
 
455
- .opcua-auth-card input,
456
- .opcua-auth-card select {
457
- width: calc(100% - 100px);
458
- }
459
-
460
+ .opcua-auth-card input[type="text"],
461
+ .opcua-auth-card input[type="password"],
462
+ .opcua-auth-card input[type="number"],
463
+ .opcua-auth-card select,
460
464
  .opcua-auth-card .red-ui-typedInput-container {
461
- width: calc(100% - 100px) !important;
465
+ flex: 1 1 auto;
466
+ margin: 0;
467
+ width: auto !important;
462
468
  }
463
469
 
464
470
  @media (max-width: 900px) {
@@ -493,4 +499,92 @@ body.opcua-tree-modal-open {
493
499
  .opcua-tree-actions {
494
500
  margin-left: 0;
495
501
  }
502
+ }
503
+
504
+ .opcua-cert-item {
505
+ padding: 8px 12px;
506
+ border-bottom: 1px solid var(--red-ui-form-input-border-color, #e0e0e0);
507
+ cursor: pointer;
508
+ display: flex;
509
+ align-items: center;
510
+ gap: 8px;
511
+ user-select: none;
512
+ transition: background-color 0.2s;
513
+ }
514
+ .opcua-cert-item:last-child {
515
+ border-bottom: none;
516
+ }
517
+ .opcua-cert-item:hover {
518
+ background: var(--red-ui-list-item-background-hover, #f0f0f0);
519
+ }
520
+ .opcua-cert-item.is-selected {
521
+ background: var(--red-ui-list-item-background-selected, #d9ecff);
522
+ color: var(--red-ui-list-item-color-selected, inherit);
523
+ font-weight: 500;
524
+ }
525
+ .opcua-cert-item i {
526
+ font-size: 14px;
527
+ }
528
+
529
+ /* Standardized configuration card styling */
530
+ .opcua-config-card {
531
+ padding: 14px;
532
+ background: var(--red-ui-form-input-background, #fcfcfc);
533
+ border: 1px solid var(--red-ui-form-input-border-color, #d9d9d9);
534
+ border-radius: 6px;
535
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
536
+ }
537
+
538
+ .opcua-config-card .opcua-card-title {
539
+ margin-top: 0;
540
+ margin-bottom: 12px;
541
+ font-weight: bold;
542
+ border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);
543
+ padding-bottom: 6px;
544
+ }
545
+
546
+ .opcua-config-form {
547
+ display: flex;
548
+ flex-direction: column;
549
+ gap: 10px;
550
+ }
551
+
552
+ .opcua-config-row {
553
+ margin: 0 !important;
554
+ display: flex;
555
+ align-items: center;
556
+ }
557
+
558
+ .opcua-config-row--checkbox {
559
+ height: 34px;
560
+ }
561
+
562
+ .opcua-config-label {
563
+ width: 240px;
564
+ margin: 0 !important;
565
+ font-weight: 500;
566
+ flex-shrink: 0;
567
+ display: inline-block;
568
+ vertical-align: middle;
569
+ }
570
+
571
+ .opcua-config-input {
572
+ width: 300px !important;
573
+ margin: 0 !important;
574
+ }
575
+
576
+ .opcua-config-input[type="checkbox"] {
577
+ width: auto !important;
578
+ display: inline-block;
579
+ }
580
+
581
+ /* Drag and Drop styling for visual tree rows */
582
+ .opcua-tree-row.is-dragging {
583
+ opacity: 0.4;
584
+ border: 1px dashed var(--red-ui-form-input-border-color, #999);
585
+ }
586
+
587
+ .opcua-tree-row.drag-over {
588
+ background-color: var(--red-ui-list-item-background-hover, #f3f7fd) !important;
589
+ box-shadow: inset 0 0 0 2px var(--red-ui-form-input-border-color-selected, #2196F3);
496
590
  }