@vitormnm/node-red-simple-opcua 1.6.3 → 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.
- package/README.md +89 -136
- package/client/lib/opcua-client-browser.js +238 -10
- package/client/lib/opcua-client-method-service.js +1 -1
- package/client/lib/opcua-client-subscription-service.js +0 -2
- package/client/opcua-client-config.html +118 -1
- package/client/opcua-client-config.js +74 -8
- package/client/opcua-client-help.html +6 -0
- package/client/opcua-client-utils.js +34 -10
- package/client/opcua-client.html +7 -0
- package/client/opcua-client.js +97 -1
- package/examples/flows_simple_opc.json +1 -1
- package/package.json +1 -1
- package/server/lib/opcua-address-space-alarm.js +11 -5
- package/server/lib/opcua-address-space-builder.js +65 -15
- package/server/lib/opcua-config.js +81 -23
- package/server/lib/opcua-server-events-child.js +1 -1
- package/server/lib/opcua-server-runtime-child.js +284 -19
- package/server/lib/opcua-server-runtime.js +49 -5
- package/server/lib/opcua-server-status-child.js +14 -14
- 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 +76 -0
- package/server/opcua-server-io.js +130 -27
- package/server/opcua-server.css +52 -0
- package/server/opcua-server.html +166 -44
- package/server/opcua-server.js +115 -5
- package/server/view/opcua-server.css +89 -6
- package/server/view/opcua-server.js +523 -42
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">
|
|
@@ -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
|
|
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
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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>
|
package/server/opcua-server.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
457
|
-
|
|
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
|
-
|
|
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,81 @@ 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;
|
|
496
579
|
}
|