@vitormnm/node-red-simple-opcua 1.4.2 → 1.5.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 +5 -0
- package/client/lib/opcua-client-browser.js +40 -2
- package/client/opcua-client-utils.js +12 -11
- package/client/view/opcua-client.js +10 -6
- package/package.json +42 -42
- package/resources/bmc-button.svg +22 -0
- package/server/lib/opcua-address-space-builder.js +122 -4
- package/server/lib/opcua-config.js +145 -14
- package/server/lib/opcua-constants.js +10 -0
- package/server/lib/opcua-server-runtime-child.js +56 -2
- package/server/lib/opcua-server-runtime.js +71 -6
- package/server/opcua-server-io.js +1 -0
- package/server/opcua-server.html +35 -1523
- package/server/opcua-server.js +24 -13
- package/server/view/opcua-server.css +492 -0
- package/server/view/opcua-server.js +1435 -0
package/server/opcua-server.html
CHANGED
|
@@ -44,14 +44,11 @@
|
|
|
44
44
|
<span style="margin-left: 8px;">Allow anonymous login</span>
|
|
45
45
|
</div>
|
|
46
46
|
<div class="form-row">
|
|
47
|
-
<label
|
|
48
|
-
<
|
|
49
|
-
</div>
|
|
50
|
-
<div class="form-row">
|
|
51
|
-
<label for="node-input-password"><i class="fa fa-tag"></i> Password</label>
|
|
52
|
-
<input type="password" id="node-input-password">
|
|
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>
|
|
53
49
|
</div>
|
|
54
50
|
|
|
51
|
+
|
|
55
52
|
<div class="form-row">
|
|
56
53
|
<label for="node-input-tree-editor">
|
|
57
54
|
<i class="fa fa-code"></i> Tree JSON
|
|
@@ -64,9 +61,38 @@
|
|
|
64
61
|
<a href="#" id="node-input-open-tree-modal" class="editor-button"><i class="fa fa-expand"></i> Open tree editor</a>
|
|
65
62
|
</div>
|
|
66
63
|
|
|
64
|
+
<input type="hidden" id="node-input-users">
|
|
65
|
+
<input type="hidden" id="node-input-groups">
|
|
66
|
+
<input type="hidden" id="node-input-username">
|
|
67
|
+
<input type="hidden" id="node-input-password">
|
|
67
68
|
<input type="hidden" id="node-input-tree">
|
|
68
69
|
<input type="hidden" id="node-input-namespaceUri">
|
|
69
70
|
|
|
71
|
+
<div id="node-input-auth-modal" class="opcua-tree-modal" style="display:none;">
|
|
72
|
+
<div class="opcua-tree-modal__dialog">
|
|
73
|
+
<div class="opcua-tree-modal__header">
|
|
74
|
+
<div class="opcua-tree-modal__title"><i class="fa fa-users"></i> User And Group Management</div>
|
|
75
|
+
<a href="#" id="node-input-close-auth-modal" class="editor-button editor-button-small"><i class="fa fa-times"></i> Close</a>
|
|
76
|
+
</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">
|
|
83
|
+
<div class="opcua-auth-panel">
|
|
84
|
+
<div class="opcua-tree-details-title">Groups</div>
|
|
85
|
+
<div id="opcua-auth-groups" class="opcua-auth-list"></div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="opcua-auth-panel">
|
|
88
|
+
<div class="opcua-tree-details-title">Users</div>
|
|
89
|
+
<div id="opcua-auth-users" class="opcua-auth-list"></div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
70
96
|
<div id="node-input-tree-modal" class="opcua-tree-modal" style="display:none;">
|
|
71
97
|
<div class="opcua-tree-modal__dialog">
|
|
72
98
|
<div class="opcua-tree-modal__header">
|
|
@@ -112,1525 +138,11 @@
|
|
|
112
138
|
</div>
|
|
113
139
|
</script>
|
|
114
140
|
|
|
115
|
-
<
|
|
116
|
-
body.opcua-tree-modal-open {
|
|
117
|
-
overflow: hidden;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.opcua-tree-editor {
|
|
121
|
-
width: 100%;
|
|
122
|
-
min-height: 180px;
|
|
123
|
-
border: 1px solid var(--red-ui-form-input-border-color, #d9d9d9);
|
|
124
|
-
background: var(--red-ui-form-input-background, #fff);
|
|
125
|
-
border-radius: 4px;
|
|
126
|
-
overflow: auto;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.opcua-tree-layout {
|
|
130
|
-
display: grid;
|
|
131
|
-
grid-template-columns: minmax(360px, 1.1fr) minmax(280px, 0.9fr);
|
|
132
|
-
gap: 12px;
|
|
133
|
-
height: 100%;
|
|
134
|
-
min-height: 0;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.opcua-tree-left,
|
|
138
|
-
.opcua-tree-right {
|
|
139
|
-
min-height: 0;
|
|
140
|
-
display: flex;
|
|
141
|
-
flex-direction: column;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.opcua-tree-details-title {
|
|
145
|
-
font-size: 12px;
|
|
146
|
-
text-transform: uppercase;
|
|
147
|
-
color: var(--red-ui-primary-text-color, #666);
|
|
148
|
-
margin-bottom: 6px;
|
|
149
|
-
font-weight: 600;
|
|
150
|
-
letter-spacing: 0.03em;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.opcua-tree-details {
|
|
154
|
-
border: 1px solid var(--red-ui-form-input-border-color, #d9d9d9);
|
|
155
|
-
border-radius: 4px;
|
|
156
|
-
background: var(--red-ui-form-input-background, #fff);
|
|
157
|
-
padding: 8px;
|
|
158
|
-
overflow: auto;
|
|
159
|
-
flex: 1 1 auto;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.opcua-tree-details .form-row {
|
|
163
|
-
display: flex;
|
|
164
|
-
align-items: center;
|
|
165
|
-
gap: 8px;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.opcua-tree-details .form-row label {
|
|
169
|
-
width: 140px;
|
|
170
|
-
min-width: 140px;
|
|
171
|
-
margin: 0;
|
|
172
|
-
overflow: hidden;
|
|
173
|
-
text-overflow: ellipsis;
|
|
174
|
-
white-space: nowrap;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.opcua-tree-details .form-row input,
|
|
178
|
-
.opcua-tree-details .form-row select,
|
|
179
|
-
.opcua-tree-details .form-row textarea {
|
|
180
|
-
flex: 1 1 auto;
|
|
181
|
-
width: auto;
|
|
182
|
-
min-width: 0;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.opcua-nodeid-field {
|
|
186
|
-
display: flex;
|
|
187
|
-
align-items: center;
|
|
188
|
-
gap: 8px;
|
|
189
|
-
flex: 1 1 auto;
|
|
190
|
-
min-width: 0;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.opcua-nodeid-prefix {
|
|
194
|
-
flex: 0 0 auto;
|
|
195
|
-
color: var(--red-ui-primary-text-color, #666);
|
|
196
|
-
white-space: nowrap;
|
|
197
|
-
font-family: monospace;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.opcua-nodeid-field input {
|
|
201
|
-
flex: 1 1 auto;
|
|
202
|
-
min-width: 0;
|
|
203
|
-
font-family: monospace;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.opcua-tree-breadcrumbs {
|
|
207
|
-
margin-bottom: 8px;
|
|
208
|
-
min-height: 24px;
|
|
209
|
-
padding: 3px 6px;
|
|
210
|
-
border: 1px solid var(--red-ui-form-input-border-color, #d9d9d9);
|
|
211
|
-
border-radius: 4px;
|
|
212
|
-
font-size: 12px;
|
|
213
|
-
line-height: 18px;
|
|
214
|
-
background: var(--red-ui-secondary-background, #fafafa);
|
|
215
|
-
color: var(--red-ui-primary-text-color, #666);
|
|
216
|
-
overflow: hidden;
|
|
217
|
-
white-space: nowrap;
|
|
218
|
-
text-overflow: ellipsis;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.opcua-tree-row {
|
|
222
|
-
display: flex;
|
|
223
|
-
align-items: center;
|
|
224
|
-
gap: 5px;
|
|
225
|
-
min-height: 24px;
|
|
226
|
-
padding: 2px 6px;
|
|
227
|
-
font-size: 12px;
|
|
228
|
-
cursor: pointer;
|
|
229
|
-
border-bottom: 1px solid transparent;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.opcua-tree-row:hover {
|
|
233
|
-
background: var(--red-ui-list-item-background-hover, #f3f7fd);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.opcua-tree-row.is-selected {
|
|
237
|
-
background: var(--red-ui-list-item-background-selected, #d9ecff);
|
|
238
|
-
color: var(--red-ui-list-item-color-selected, inherit);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.opcua-tree-indent {
|
|
242
|
-
flex: 0 0 auto;
|
|
243
|
-
width: 14px;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.opcua-tree-twisty {
|
|
247
|
-
width: 16px;
|
|
248
|
-
text-align: center;
|
|
249
|
-
color: #777;
|
|
250
|
-
flex: 0 0 16px;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.opcua-tree-icon {
|
|
254
|
-
width: 14px;
|
|
255
|
-
text-align: center;
|
|
256
|
-
color: #777;
|
|
257
|
-
flex: 0 0 14px;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.opcua-tree-label {
|
|
261
|
-
flex: 1 1 auto;
|
|
262
|
-
overflow: hidden;
|
|
263
|
-
text-overflow: ellipsis;
|
|
264
|
-
white-space: nowrap;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.opcua-tree-type {
|
|
268
|
-
color: #777;
|
|
269
|
-
font-size: 11px;
|
|
270
|
-
flex: 0 0 auto;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.opcua-tree-context-menu {
|
|
274
|
-
position: fixed;
|
|
275
|
-
z-index: 2100;
|
|
276
|
-
min-width: 140px;
|
|
277
|
-
border: 1px solid var(--red-ui-form-input-border-color, #d9d9d9);
|
|
278
|
-
border-radius: 4px;
|
|
279
|
-
background: var(--red-ui-form-input-background, #fff);
|
|
280
|
-
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
|
|
281
|
-
overflow: hidden;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.opcua-tree-context-menu a {
|
|
285
|
-
display: block;
|
|
286
|
-
padding: 6px 8px;
|
|
287
|
-
color: inherit;
|
|
288
|
-
text-decoration: none;
|
|
289
|
-
font-size: 12px;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.opcua-tree-context-menu a:hover {
|
|
293
|
-
background: var(--red-ui-list-item-background-hover, #f3f7fd);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.opcua-tree-modal {
|
|
297
|
-
position: fixed;
|
|
298
|
-
inset: 0;
|
|
299
|
-
z-index: 2000;
|
|
300
|
-
background: rgba(0, 0, 0, 0.45);
|
|
301
|
-
padding: 24px;
|
|
302
|
-
box-sizing: border-box;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.opcua-tree-modal__dialog {
|
|
306
|
-
display: flex;
|
|
307
|
-
flex-direction: column;
|
|
308
|
-
width: 100%;
|
|
309
|
-
height: 100%;
|
|
310
|
-
background: #f3f3f3;
|
|
311
|
-
border-radius: 8px;
|
|
312
|
-
overflow: hidden;
|
|
313
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
.opcua-tree-modal__header,
|
|
317
|
-
.opcua-tree-modal__toolbar {
|
|
318
|
-
display: flex;
|
|
319
|
-
align-items: center;
|
|
320
|
-
flex-wrap: wrap;
|
|
321
|
-
gap: 10px;
|
|
322
|
-
padding: 14px 18px;
|
|
323
|
-
background: #fff;
|
|
324
|
-
border-bottom: 1px solid #d9d9d9;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.opcua-tree-modal__title {
|
|
328
|
-
font-size: 18px;
|
|
329
|
-
font-weight: 600;
|
|
330
|
-
color: #333;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.opcua-tree-modal__header .editor-button-small {
|
|
334
|
-
margin-left: auto;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
.opcua-tree-modal__body {
|
|
338
|
-
flex: 1 1 auto;
|
|
339
|
-
overflow: auto;
|
|
340
|
-
padding: 18px;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
.opcua-tree-search {
|
|
344
|
-
display: flex;
|
|
345
|
-
align-items: center;
|
|
346
|
-
gap: 8px;
|
|
347
|
-
min-width: 280px;
|
|
348
|
-
margin-left: auto;
|
|
349
|
-
padding: 6px 10px;
|
|
350
|
-
border: 1px solid #d9d9d9;
|
|
351
|
-
border-radius: 6px;
|
|
352
|
-
background: #fafafa;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.opcua-tree-search input {
|
|
356
|
-
flex: 1 1 auto;
|
|
357
|
-
min-width: 120px;
|
|
358
|
-
border: 0;
|
|
359
|
-
outline: 0;
|
|
360
|
-
background: transparent;
|
|
361
|
-
box-shadow: none;
|
|
362
|
-
margin: 0;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
.opcua-tree-search-results {
|
|
366
|
-
margin-bottom: 12px;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
.opcua-tree-search-meta {
|
|
370
|
-
margin-bottom: 10px;
|
|
371
|
-
color: #666;
|
|
372
|
-
font-size: 12px;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
.opcua-tree-search-result {
|
|
376
|
-
margin-bottom: 8px;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
.opcua-tree-search-path {
|
|
380
|
-
margin-bottom: 4px;
|
|
381
|
-
color: #666;
|
|
382
|
-
font-size: 12px;
|
|
383
|
-
font-family: Consolas, monospace;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.opcua-tree-group,
|
|
387
|
-
.opcua-tree-node {
|
|
388
|
-
border: 1px solid #d9d9d9;
|
|
389
|
-
border-radius: 4px;
|
|
390
|
-
background: #fff;
|
|
391
|
-
margin-bottom: 8px;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
.opcua-tree-node[data-depth="1"],
|
|
395
|
-
.opcua-tree-group[data-depth="1"] {
|
|
396
|
-
margin-left: 2px;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
.opcua-tree-node[data-depth="2"],
|
|
400
|
-
.opcua-tree-group[data-depth="2"] {
|
|
401
|
-
margin-left: 2px;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
.opcua-tree-node[data-depth="3"],
|
|
405
|
-
.opcua-tree-group[data-depth="3"] {
|
|
406
|
-
margin-left: 2px;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
.opcua-tree-node[data-depth="4"],
|
|
410
|
-
.opcua-tree-group[data-depth="4"] {
|
|
411
|
-
margin-left: 2px;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
.opcua-tree-header {
|
|
415
|
-
display: flex;
|
|
416
|
-
align-items: center;
|
|
417
|
-
flex-wrap: wrap;
|
|
418
|
-
gap: 4px;
|
|
419
|
-
padding: 4px 5px;
|
|
420
|
-
background: #f8f8f8;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
.opcua-tree-body {
|
|
424
|
-
padding: 5px;
|
|
425
|
-
border-top: 1px solid #ededed;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
.opcua-tree-toggle {
|
|
429
|
-
width: 24px;
|
|
430
|
-
height: 24px;
|
|
431
|
-
border: 0;
|
|
432
|
-
padding: 0;
|
|
433
|
-
background: transparent;
|
|
434
|
-
color: #666;
|
|
435
|
-
cursor: pointer;
|
|
436
|
-
flex: 0 0 auto;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.opcua-tree-title {
|
|
440
|
-
min-width: 100px;
|
|
441
|
-
font-weight: 600;
|
|
442
|
-
color: #444;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
.opcua-tree-summary {
|
|
446
|
-
color: #777;
|
|
447
|
-
font-size: 12px;
|
|
448
|
-
white-space: nowrap;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
.opcua-tree-input {
|
|
452
|
-
flex: 1 1 auto;
|
|
453
|
-
min-width: 220px;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
.opcua-tree-actions {
|
|
457
|
-
margin-left: auto;
|
|
458
|
-
display: flex;
|
|
459
|
-
gap: 6px;
|
|
460
|
-
align-items: center;
|
|
461
|
-
flex-wrap: wrap;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
.opcua-tree-empty {
|
|
465
|
-
padding: 14px;
|
|
466
|
-
border: 1px dashed #d9d9d9;
|
|
467
|
-
border-radius: 4px;
|
|
468
|
-
background: #fafafa;
|
|
469
|
-
color: #777;
|
|
470
|
-
text-align: center;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
.opcua-tree-grid {
|
|
474
|
-
display: grid;
|
|
475
|
-
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
476
|
-
gap: 12px;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
.opcua-tree-grid .form-row {
|
|
480
|
-
margin-bottom: 0;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.opcua-tree-grid .form-row label {
|
|
484
|
-
width: 100px !important;
|
|
485
|
-
flex: 0 0 100px;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
.opcua-tree-grid .form-row input,
|
|
489
|
-
.opcua-tree-grid .form-row select,
|
|
490
|
-
.opcua-tree-grid .form-row textarea {
|
|
491
|
-
width: auto;
|
|
492
|
-
flex: 1 1 auto;
|
|
493
|
-
min-width: 0;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
.opcua-tree-form-row {
|
|
497
|
-
display: flex;
|
|
498
|
-
align-items: center;
|
|
499
|
-
gap: 8px;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
.opcua-tree-form-row--full {
|
|
503
|
-
grid-column: 1 / -1;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
.opcua-tree-divider {
|
|
507
|
-
grid-column: 1 / -1;
|
|
508
|
-
border-top: 1px solid #e3e3e3;
|
|
509
|
-
margin: 2px 0;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
.opcua-tree-section-title {
|
|
513
|
-
grid-column: 1 / -1;
|
|
514
|
-
font-size: 12px;
|
|
515
|
-
font-weight: 700;
|
|
516
|
-
text-transform: uppercase;
|
|
517
|
-
letter-spacing: 0.04em;
|
|
518
|
-
color: #666;
|
|
519
|
-
margin-top: 2px;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
@media (max-width: 900px) {
|
|
523
|
-
.opcua-tree-modal {
|
|
524
|
-
padding: 10px;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
.opcua-tree-search {
|
|
528
|
-
min-width: 100%;
|
|
529
|
-
margin-left: 0;
|
|
530
|
-
}
|
|
141
|
+
<link rel="stylesheet" type="text/css" href="opcua-server-resource/opcua-server.css">
|
|
531
142
|
|
|
532
|
-
|
|
533
|
-
grid-template-columns: 1fr;
|
|
534
|
-
}
|
|
143
|
+
<script type="text/javascript" src="opcua-server-resource/opcua-server.js"></script>
|
|
535
144
|
|
|
536
|
-
.opcua-tree-header {
|
|
537
|
-
align-items: flex-start;
|
|
538
|
-
}
|
|
539
145
|
|
|
540
|
-
.opcua-tree-title,
|
|
541
|
-
.opcua-tree-summary,
|
|
542
|
-
.opcua-tree-input,
|
|
543
|
-
.opcua-tree-actions {
|
|
544
|
-
width: 100%;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
.opcua-tree-actions {
|
|
548
|
-
margin-left: 0;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
</style>
|
|
552
|
-
|
|
553
|
-
<script type="text/javascript">
|
|
554
|
-
(function () {
|
|
555
|
-
var editorState = { objects: [], folders: [], objectsTypes: [], nameSpaces: [] };
|
|
556
|
-
var expansionState = {};
|
|
557
|
-
var selectedPath = "";
|
|
558
|
-
var pendingCreate = null;
|
|
559
|
-
var pendingPasswordHashes = 0;
|
|
560
|
-
var treeSearchValue = "";
|
|
561
|
-
var treeSearchTerm = "";
|
|
562
|
-
var isSyncing = false;
|
|
563
|
-
var DEFAULT_NAMESPACE_ID = 2;
|
|
564
|
-
|
|
565
|
-
function openTreeModal() { $("#node-input-tree-modal").show(); $("body").addClass("opcua-tree-modal-open"); }
|
|
566
|
-
function closeTreeModal() { $("#node-input-tree-modal").hide(); $("body").removeClass("opcua-tree-modal-open"); }
|
|
567
|
-
|
|
568
|
-
function parseTree(rawValue, strict) {
|
|
569
|
-
if (!rawValue) return { objects: [], folders: [], objectsTypes: [], nameSpaces: [] };
|
|
570
|
-
if (typeof rawValue === "object") return rawValue;
|
|
571
|
-
try { var parsed = JSON.parse(rawValue); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed; }
|
|
572
|
-
catch (error) { if (strict) throw error; }
|
|
573
|
-
return { objects: [], folders: [], objectsTypes: [], nameSpaces: [] };
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function normalizeNamespaceId(value) {
|
|
577
|
-
var parsed = Number(value);
|
|
578
|
-
return Number.isInteger(parsed) && parsed >= DEFAULT_NAMESPACE_ID ? parsed : DEFAULT_NAMESPACE_ID;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
function normalizeNamespaceDefinition(namespaceItem) {
|
|
582
|
-
namespaceItem = namespaceItem || {};
|
|
583
|
-
return {
|
|
584
|
-
id: normalizeNamespaceId(namespaceItem.id),
|
|
585
|
-
name: namespaceItem.name ? String(namespaceItem.name) : ""
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function ensureNamespaces(tree) {
|
|
590
|
-
if (!Array.isArray(tree.nameSpaces)) tree.nameSpaces = [];
|
|
591
|
-
var hasDefaultNamespace = tree.nameSpaces.some(function (item) { return normalizeNamespaceId(item.id) === DEFAULT_NAMESPACE_ID; });
|
|
592
|
-
if (!hasDefaultNamespace) {
|
|
593
|
-
tree.nameSpaces.unshift(normalizeNamespaceDefinition({
|
|
594
|
-
id: DEFAULT_NAMESPACE_ID,
|
|
595
|
-
name: $("#node-input-namespaceUri").val() || "urn:node-red:opc-ua-server"
|
|
596
|
-
}));
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
tree.nameSpaces = tree.nameSpaces
|
|
600
|
-
.map(normalizeNamespaceDefinition)
|
|
601
|
-
.sort(function (left, right) { return left.id - right.id; });
|
|
602
|
-
|
|
603
|
-
return tree;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function normalizeVariable(variable) {
|
|
607
|
-
return {
|
|
608
|
-
name: variable && variable.name ? String(variable.name) : "",
|
|
609
|
-
type: variable && variable.type ? String(variable.type) : "Int32",
|
|
610
|
-
value: variable && variable.value !== undefined ? variable.value : "",
|
|
611
|
-
access: variable && variable.access ? String(variable.access).toLowerCase() : "readwrite",
|
|
612
|
-
description: variable && variable.description ? String(variable.description) : "",
|
|
613
|
-
displayName: variable && variable.displayName ? String(variable.displayName) : "",
|
|
614
|
-
nodeId: variable && variable.nodeId ? String(variable.nodeId) : "",
|
|
615
|
-
namespaceId: normalizeNamespaceId(variable && variable.namespaceId)
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
function normalizeAlarm(alarm) {
|
|
619
|
-
alarm = alarm || {};
|
|
620
|
-
var type = alarm.type ? String(alarm.type) : "levelAlarm";
|
|
621
|
-
return {
|
|
622
|
-
name: alarm.name ? String(alarm.name) : "",
|
|
623
|
-
variableNodeId: alarm.variableNodeId ? String(alarm.variableNodeId) : "",
|
|
624
|
-
type: type,
|
|
625
|
-
enabled: alarm.enabled !== undefined ? !!alarm.enabled : true,
|
|
626
|
-
severity: alarm.severity !== undefined ? alarm.severity : 500,
|
|
627
|
-
description: alarm.description ? String(alarm.description) : "",
|
|
628
|
-
displayName: alarm.displayName ? String(alarm.displayName) : "",
|
|
629
|
-
nodeId: alarm.nodeId ? String(alarm.nodeId) : "",
|
|
630
|
-
namespaceId: normalizeNamespaceId(alarm.namespaceId),
|
|
631
|
-
highHighLimit: alarm.highHighLimit !== undefined ? alarm.highHighLimit : 100,
|
|
632
|
-
highHighMessage: alarm.highHighMessage ? String(alarm.highHighMessage) : "High High alarm",
|
|
633
|
-
highLimit: alarm.highLimit !== undefined ? alarm.highLimit : 80,
|
|
634
|
-
highMessage: alarm.highMessage ? String(alarm.highMessage) : "High alarm",
|
|
635
|
-
lowLimit: alarm.lowLimit !== undefined ? alarm.lowLimit : 20,
|
|
636
|
-
lowMessage: alarm.lowMessage ? String(alarm.lowMessage) : "Low alarm",
|
|
637
|
-
lowLowLimit: alarm.lowLowLimit !== undefined ? alarm.lowLowLimit : 0,
|
|
638
|
-
lowLowMessage: alarm.lowLowMessage ? String(alarm.lowLowMessage) : "Low Low alarm",
|
|
639
|
-
normalStateValue: alarm.normalStateValue !== undefined ? alarm.normalStateValue : 0,
|
|
640
|
-
digitalMessage: alarm.digitalMessage ? String(alarm.digitalMessage) : "Digital alarm"
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function normalizeMethodArg(arg) {
|
|
645
|
-
arg = arg || {};
|
|
646
|
-
return {
|
|
647
|
-
name: arg.name ? String(arg.name) : "",
|
|
648
|
-
type: arg.type ? String(arg.type) : "Float",
|
|
649
|
-
displayName: arg.displayName ? String(arg.displayName) : "",
|
|
650
|
-
description: arg.description ? String(arg.description) : ""
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function normalizeMethod(method) {
|
|
655
|
-
method = method || {};
|
|
656
|
-
return {
|
|
657
|
-
name: method.name ? String(method.name) : "",
|
|
658
|
-
description: method.description ? String(method.description) : "",
|
|
659
|
-
displayName: method.displayName ? String(method.displayName) : "",
|
|
660
|
-
nodeId: method.nodeId ? String(method.nodeId) : "",
|
|
661
|
-
namespaceId: normalizeNamespaceId(method.namespaceId),
|
|
662
|
-
inputs: Array.isArray(method.inputs)
|
|
663
|
-
? method.inputs.map(normalizeMethodArg)
|
|
664
|
-
: Array.isArray(method.inputArguments)
|
|
665
|
-
? method.inputArguments.map(normalizeMethodArg)
|
|
666
|
-
: [],
|
|
667
|
-
outputs: Array.isArray(method.outputs)
|
|
668
|
-
? method.outputs.map(normalizeMethodArg)
|
|
669
|
-
: Array.isArray(method.outputArguments)
|
|
670
|
-
? method.outputArguments.map(normalizeMethodArg)
|
|
671
|
-
: []
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function normalizeBranch(branch) {
|
|
676
|
-
branch = branch || {};
|
|
677
|
-
return {
|
|
678
|
-
name: branch.name ? String(branch.name) : "",
|
|
679
|
-
displayName: branch.displayName ? String(branch.displayName) : "",
|
|
680
|
-
description: branch.description ? String(branch.description) : "",
|
|
681
|
-
nodeId: branch.nodeId ? String(branch.nodeId) : "",
|
|
682
|
-
namespaceId: normalizeNamespaceId(branch.namespaceId),
|
|
683
|
-
objectsType: branch.objectsType ? String(branch.objectsType) : (branch.objectType ? String(branch.objectType) : ""),
|
|
684
|
-
folders: Array.isArray(branch.folders) ? branch.folders.map(normalizeBranch) : [],
|
|
685
|
-
objects: Array.isArray(branch.objects) ? branch.objects.map(normalizeBranch) : [],
|
|
686
|
-
variables: Array.isArray(branch.variables) ? branch.variables.map(normalizeVariable) : [],
|
|
687
|
-
alarms: Array.isArray(branch.alarms) ? branch.alarms.map(normalizeAlarm) : [],
|
|
688
|
-
methods: Array.isArray(branch.methods) ? branch.methods.map(normalizeMethod) : (Array.isArray(branch.method) ? branch.method.map(normalizeMethod) : []),
|
|
689
|
-
objectsTypes: Array.isArray(branch.objectsTypes) ? branch.objectsTypes.map(normalizeBranch) : []
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function normalizeTree(tree) {
|
|
694
|
-
tree = ensureNamespaces(tree || {});
|
|
695
|
-
return ensureNamespaces({
|
|
696
|
-
objects: Array.isArray(tree.objects) ? tree.objects.map(normalizeBranch) : [],
|
|
697
|
-
folders: Array.isArray(tree.folders) ? tree.folders.map(normalizeBranch) : [],
|
|
698
|
-
objectsTypes: Array.isArray(tree.objectsTypes) ? tree.objectsTypes.map(normalizeBranch) : (Array.isArray(tree.objectTypes) ? tree.objectTypes.map(normalizeBranch) : []),
|
|
699
|
-
nameSpaces: Array.isArray(tree.nameSpaces) ? tree.nameSpaces.map(normalizeNamespaceDefinition) : (Array.isArray(tree.namespaces) ? tree.namespaces.map(normalizeNamespaceDefinition) : [])
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function prettyTree(tree) { return JSON.stringify(normalizeTree(tree), null, 2); }
|
|
704
|
-
function cloneTree(tree) { return JSON.parse(prettyTree(tree)); }
|
|
705
|
-
function pathToTokens(path) { return String(path || "").split(".").filter(function (t) { return t !== ""; }); }
|
|
706
|
-
|
|
707
|
-
function getAtPath(tree, path) {
|
|
708
|
-
return pathToTokens(path).reduce(function (current, token) {
|
|
709
|
-
if (current === undefined || current === null) return undefined;
|
|
710
|
-
if (/^\d+$/.test(token)) return current[Number(token)];
|
|
711
|
-
return current[token];
|
|
712
|
-
}, tree);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function removeAtPath(tree, path) {
|
|
716
|
-
var tokens = pathToTokens(path);
|
|
717
|
-
var lastToken = tokens.pop();
|
|
718
|
-
var parent = getAtPath(tree, tokens.join("."));
|
|
719
|
-
if (parent === undefined || parent === null || lastToken === undefined) return;
|
|
720
|
-
if (/^\d+$/.test(lastToken) && Array.isArray(parent)) parent.splice(Number(lastToken), 1);
|
|
721
|
-
else delete parent[lastToken];
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function updateTreeField(serializedTree, notifyChange) {
|
|
725
|
-
var field = $("#node-input-tree");
|
|
726
|
-
var previousValue = field.val();
|
|
727
|
-
field.val(serializedTree);
|
|
728
|
-
if (notifyChange && previousValue !== serializedTree) field.trigger("change");
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
function syncStateToJson(notifyChange) {
|
|
732
|
-
if (isSyncing) return;
|
|
733
|
-
isSyncing = true;
|
|
734
|
-
enforceFixedNodeIdsInObjectTypeModels(editorState);
|
|
735
|
-
var json = prettyTree(editorState);
|
|
736
|
-
updateTreeField(json, false);
|
|
737
|
-
$("#node-input-tree-editor").typedInput("value", json);
|
|
738
|
-
if (notifyChange) $("#node-input-tree").trigger("change");
|
|
739
|
-
isSyncing = false;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
function normalizeSearchTerm(value) { return String(value || "").trim().toLowerCase(); }
|
|
743
|
-
function isExpanded(path, defaultValue) { if (expansionState[path] === undefined) expansionState[path] = !!defaultValue; return expansionState[path]; }
|
|
744
|
-
function nodeClassFromPath(path) {
|
|
745
|
-
var tokens = pathToTokens(path);
|
|
746
|
-
if (!tokens.length) return "Object";
|
|
747
|
-
|
|
748
|
-
var collectionToken = tokens.length > 1 ? tokens[tokens.length - 2] : tokens[0];
|
|
749
|
-
if (collectionToken === "variables") return "Variable";
|
|
750
|
-
if (collectionToken === "methods") return "Method";
|
|
751
|
-
if (collectionToken === "alarms") return "Alarm";
|
|
752
|
-
if (collectionToken === "objectsTypes" || collectionToken === "objectTypes") return "ObjectType";
|
|
753
|
-
if (collectionToken === "nameSpaces" || collectionToken === "namespaces") return "Namespace";
|
|
754
|
-
if (collectionToken === "folders") return "Folder";
|
|
755
|
-
return "Object";
|
|
756
|
-
}
|
|
757
|
-
function getNodeDisplayName(path) { var item = getAtPath(editorState, path); return item ? (item.name || "(unnamed)") : ""; }
|
|
758
|
-
function getNamespaceOptions() {
|
|
759
|
-
return Array.isArray(editorState.nameSpaces) ? editorState.nameSpaces.slice().sort(function (left, right) { return left.id - right.id; }) : [];
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
function getDefinedObjectTypeNames() {
|
|
763
|
-
var names = [];
|
|
764
|
-
(editorState.objectsTypes || []).forEach(function (ot) {
|
|
765
|
-
if (ot && ot.name) names.push(String(ot.name));
|
|
766
|
-
});
|
|
767
|
-
return names;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
function buildObjectTypeSelect(id, currentValue) {
|
|
772
|
-
var names = getDefinedObjectTypeNames();
|
|
773
|
-
var cv = String(currentValue || "");
|
|
774
|
-
var opts = "";
|
|
775
|
-
var noneSelected = (cv === "") ? " selected" : "";
|
|
776
|
-
opts += "<option value=\"\"" + noneSelected + ">\u2014 none \u2014</option>";
|
|
777
|
-
names.forEach(function (n) {
|
|
778
|
-
var esc = n.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
779
|
-
var sel = (n === cv) ? " selected" : "";
|
|
780
|
-
opts += "<option value=\"" + esc + "\"" + sel + ">" + esc + "</option>";
|
|
781
|
-
});
|
|
782
|
-
return "<select id=\"" + id + "\">" + opts + "</select>";
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function getNamespaceLabel(namespaceId) {
|
|
786
|
-
var match = getNamespaceOptions().find(function (item) { return item.id === normalizeNamespaceId(namespaceId); });
|
|
787
|
-
return match ? String(match.id) + " - " + match.name : String(normalizeNamespaceId(namespaceId));
|
|
788
|
-
}
|
|
789
|
-
function getNodeNamespaceId(path) {
|
|
790
|
-
var item = getAtPath(editorState, path);
|
|
791
|
-
return normalizeNamespaceId(item && item.namespaceId);
|
|
792
|
-
}
|
|
793
|
-
function getNodeIdPrefix(namespaceId) {
|
|
794
|
-
return "ns=" + normalizeNamespaceId(namespaceId) + ";s=";
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
function buildDefaultNodeIdSuffixFromEditorPath(path) {
|
|
798
|
-
var tokens = pathToTokens(path);
|
|
799
|
-
var current = editorState;
|
|
800
|
-
var parts = [];
|
|
801
|
-
// var preservedCollections = { alarms: true, methods: true, objectsTypes: true };
|
|
802
|
-
var preservedCollections = { alarms: true, methods: true };
|
|
803
|
-
|
|
804
|
-
tokens.forEach(function (token) {
|
|
805
|
-
if (/^\d+$/.test(token)) {
|
|
806
|
-
current = current ? current[Number(token)] : null;
|
|
807
|
-
if (current && current.name) parts.push(current.name);
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
current = current ? current[token] : null;
|
|
812
|
-
if (preservedCollections[token]) parts.push(token);
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
return parts.join(".");
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
function nodeIdSuffixFromValue(nodeId, defaultSuffix) {
|
|
820
|
-
var raw = String(nodeId || "").trim();
|
|
821
|
-
if (!raw) return defaultSuffix;
|
|
822
|
-
var match = /^ns=\d+;s=(.*)$/.exec(raw);
|
|
823
|
-
if (match) return match[1];
|
|
824
|
-
return raw;
|
|
825
|
-
}
|
|
826
|
-
function isObjectTypeModelPath(path) {
|
|
827
|
-
var tokens = pathToTokens(path);
|
|
828
|
-
return tokens.length > 0 && tokens[0] === "objectsTypes";
|
|
829
|
-
}
|
|
830
|
-
function buildGeneratedNodeIdForPath(path) {
|
|
831
|
-
return getNodeIdPrefix(getNodeNamespaceId(path)) + buildDefaultNodeIdSuffixFromEditorPath(path);
|
|
832
|
-
}
|
|
833
|
-
function assignFixedNodeIdsToBranch(path) {
|
|
834
|
-
var branch = getAtPath(editorState, path);
|
|
835
|
-
if (!branch || typeof branch !== "object") return;
|
|
836
|
-
branch.nodeId = buildGeneratedNodeIdForPath(path);
|
|
837
|
-
(branch.folders || []).forEach(function (_, index) { assignFixedNodeIdsToBranch(path + ".folders." + index); });
|
|
838
|
-
(branch.objects || []).forEach(function (_, index) { assignFixedNodeIdsToBranch(path + ".objects." + index); });
|
|
839
|
-
(branch.variables || []).forEach(function (item, index) {
|
|
840
|
-
if (!item) return;
|
|
841
|
-
item.nodeId = buildGeneratedNodeIdForPath(path + ".variables." + index);
|
|
842
|
-
});
|
|
843
|
-
(branch.methods || []).forEach(function (item, index) {
|
|
844
|
-
if (!item) return;
|
|
845
|
-
item.nodeId = buildGeneratedNodeIdForPath(path + ".methods." + index);
|
|
846
|
-
});
|
|
847
|
-
(branch.alarms || []).forEach(function (item, index) {
|
|
848
|
-
if (!item) return;
|
|
849
|
-
item.nodeId = buildGeneratedNodeIdForPath(path + ".alarms." + index);
|
|
850
|
-
});
|
|
851
|
-
(branch.objectsTypes || []).forEach(function (_, index) { assignFixedNodeIdsToBranch(path + ".objectsTypes." + index); });
|
|
852
|
-
}
|
|
853
|
-
function enforceFixedNodeIdsInObjectTypeModels(tree) {
|
|
854
|
-
(tree.objectsTypes || []).forEach(function (_, index) {
|
|
855
|
-
assignFixedNodeIdsToBranch("objectsTypes." + index);
|
|
856
|
-
});
|
|
857
|
-
return tree;
|
|
858
|
-
}
|
|
859
|
-
function buildDisplayNodeIdFromEditorPath(path) {
|
|
860
|
-
var item = getAtPath(editorState, path);
|
|
861
|
-
if (isObjectTypeModelPath(path)) return buildGeneratedNodeIdForPath(path);
|
|
862
|
-
var customNodeId = item && item.nodeId ? String(item.nodeId).trim() : "";
|
|
863
|
-
var suffix = nodeIdSuffixFromValue(customNodeId, buildDefaultNodeIdSuffixFromEditorPath(path));
|
|
864
|
-
return getNodeIdPrefix(getNodeNamespaceId(path)) + suffix;
|
|
865
|
-
}
|
|
866
|
-
function normalizeCustomNodeIdFromSuffix(path, suffix) {
|
|
867
|
-
if (isObjectTypeModelPath(path)) return "";
|
|
868
|
-
var nextSuffix = String(suffix || "").trim();
|
|
869
|
-
var defaultSuffix = buildDefaultNodeIdSuffixFromEditorPath(path);
|
|
870
|
-
if (!nextSuffix || nextSuffix === defaultSuffix) return "";
|
|
871
|
-
return getNodeIdPrefix(getNodeNamespaceId(path)) + nextSuffix;
|
|
872
|
-
}
|
|
873
|
-
function copyNodeIdValue(nodeId) {
|
|
874
|
-
if (!nodeId) {
|
|
875
|
-
RED.notify("NodeId not found for the selected item.", "warning");
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
880
|
-
navigator.clipboard.writeText(nodeId).then(function () {
|
|
881
|
-
RED.notify("NodeId copied.", "success");
|
|
882
|
-
}).catch(function () {
|
|
883
|
-
RED.notify("Failed to copy NodeId.", "error");
|
|
884
|
-
});
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
var input = $("<textarea readonly></textarea>").val(nodeId).css({
|
|
889
|
-
position: "fixed",
|
|
890
|
-
left: "-9999px",
|
|
891
|
-
top: "0"
|
|
892
|
-
});
|
|
893
|
-
$("body").append(input);
|
|
894
|
-
input[0].select();
|
|
895
|
-
try {
|
|
896
|
-
document.execCommand("copy");
|
|
897
|
-
RED.notify("NodeId copied.", "success");
|
|
898
|
-
} catch (error) {
|
|
899
|
-
RED.notify("Failed to copy NodeId.", "error");
|
|
900
|
-
}
|
|
901
|
-
input.remove();
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
function getChildrenByPath(path) {
|
|
905
|
-
var item = getAtPath(editorState, path);
|
|
906
|
-
if (!item) return [];
|
|
907
|
-
var children = [];
|
|
908
|
-
(item.folders || []).forEach(function (_, i) { children.push(path + ".folders." + i); });
|
|
909
|
-
(item.objects || []).forEach(function (_, i) { children.push(path + ".objects." + i); });
|
|
910
|
-
(item.variables || []).forEach(function (_, i) { children.push(path + ".variables." + i); });
|
|
911
|
-
(item.methods || []).forEach(function (_, i) { children.push(path + ".methods." + i); });
|
|
912
|
-
(item.alarms || []).forEach(function (_, i) { children.push(path + ".alarms." + i); });
|
|
913
|
-
(item.objectsTypes || []).forEach(function (_, i) { children.push(path + ".objectsTypes." + i); });
|
|
914
|
-
return children;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
function getTopLevelPaths() {
|
|
918
|
-
var paths = [];
|
|
919
|
-
(editorState.folders || []).forEach(function (_, i) { paths.push("folders." + i); });
|
|
920
|
-
(editorState.objects || []).forEach(function (_, i) { paths.push("objects." + i); });
|
|
921
|
-
(editorState.objectsTypes || []).forEach(function (_, i) { paths.push("objectsTypes." + i); });
|
|
922
|
-
(editorState.nameSpaces || []).forEach(function (_, i) { paths.push("nameSpaces." + i); });
|
|
923
|
-
return paths;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
function selectNode(path) {
|
|
927
|
-
selectedPath = path || "";
|
|
928
|
-
renderVisualEditor();
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
function nodeMatchesSearch(path) {
|
|
932
|
-
if (!treeSearchTerm) return true;
|
|
933
|
-
var item = getAtPath(editorState, path);
|
|
934
|
-
if (!item) return false;
|
|
935
|
-
var values = [path, item.name, item.displayName, item.description, nodeClassFromPath(path), item.type, item.value, item.id, item.namespaceId];
|
|
936
|
-
return values.some(function (x) { return String(x || "").toLowerCase().indexOf(treeSearchTerm) !== -1; });
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
function branchHasSearchMatch(path) {
|
|
940
|
-
if (nodeMatchesSearch(path)) return true;
|
|
941
|
-
return getChildrenByPath(path).some(branchHasSearchMatch);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
function iconForNodeClass(nodeClass) {
|
|
945
|
-
if (nodeClass === "Folder") return "fa-folder";
|
|
946
|
-
if (nodeClass === "Object") return "fa-cube";
|
|
947
|
-
if (nodeClass === "Variable") return "fa-tag";
|
|
948
|
-
if (nodeClass === "ObjectType") return "fa-cubes";
|
|
949
|
-
if (nodeClass === "Namespace") return "fa-sitemap";
|
|
950
|
-
if (nodeClass === "Alarm") return "fa-bell";
|
|
951
|
-
if (nodeClass === "Method") return "fa-cog";
|
|
952
|
-
return "fa-tag";
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// ── performance helpers ───────────────────────────────────────────
|
|
956
|
-
var _renderTreePending = false;
|
|
957
|
-
|
|
958
|
-
function debounce(fn, delay) {
|
|
959
|
-
var timer;
|
|
960
|
-
return function () { var ctx = this, args = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(ctx, args); }, delay); };
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
function escapeHtml(v) {
|
|
964
|
-
return String(v || "").replace(/[&<>"']/g, function (c) {
|
|
965
|
-
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
function appendNodeToFrag(frag, path, depth, ancestorMatched) {
|
|
970
|
-
var item = getAtPath(editorState, path);
|
|
971
|
-
if (!item) return;
|
|
972
|
-
var nodeClass = nodeClassFromPath(path);
|
|
973
|
-
var hasChildren = nodeClass !== "Variable" && nodeClass !== "Alarm" && nodeClass !== "Namespace" && getChildrenByPath(path).length > 0;
|
|
974
|
-
var expanded = isExpanded(path, depth < 1 || !!treeSearchTerm);
|
|
975
|
-
var subtreeVisible = !!ancestorMatched || nodeMatchesSearch(path);
|
|
976
|
-
|
|
977
|
-
var indents = "";
|
|
978
|
-
for (var i = 0; i < depth; i++) indents += '<span class="opcua-tree-indent"></span>';
|
|
979
|
-
|
|
980
|
-
var row = document.createElement("div");
|
|
981
|
-
row.className = "opcua-tree-row" + (path === selectedPath ? " is-selected" : "");
|
|
982
|
-
row.setAttribute("data-path", path);
|
|
983
|
-
row.innerHTML = indents
|
|
984
|
-
+ '<span class="opcua-tree-twisty">' + (hasChildren ? '<i class="fa ' + (expanded ? "fa-caret-down" : "fa-caret-right") + '"></i>' : "") + "</span>"
|
|
985
|
-
+ '<span class="opcua-tree-icon"><i class="fa ' + iconForNodeClass(nodeClass) + '"></i></span>'
|
|
986
|
-
+ '<span class="opcua-tree-label">' + escapeHtml(item.name || "(unnamed)") + "</span>"
|
|
987
|
-
+ '<span class="opcua-tree-type">' + escapeHtml(nodeClass) + "</span>";
|
|
988
|
-
frag.appendChild(row);
|
|
989
|
-
|
|
990
|
-
if (hasChildren && expanded) {
|
|
991
|
-
getChildrenByPath(path).forEach(function (childPath) {
|
|
992
|
-
if (!treeSearchTerm || subtreeVisible || branchHasSearchMatch(childPath)) {
|
|
993
|
-
appendNodeToFrag(frag, childPath, depth + 1, subtreeVisible);
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
function renderTree() {
|
|
1000
|
-
if (_renderTreePending) return;
|
|
1001
|
-
_renderTreePending = true;
|
|
1002
|
-
setTimeout(function () {
|
|
1003
|
-
_renderTreePending = false;
|
|
1004
|
-
var list = document.getElementById("node-input-object-list");
|
|
1005
|
-
if (!list) return;
|
|
1006
|
-
var frag = document.createDocumentFragment();
|
|
1007
|
-
var roots = getTopLevelPaths();
|
|
1008
|
-
if (!roots.length) {
|
|
1009
|
-
var empty = document.createElement("div");
|
|
1010
|
-
empty.className = "opcua-tree-empty";
|
|
1011
|
-
empty.textContent = "No OPC UA items. Use Add folder, Add object, or Add namespace.";
|
|
1012
|
-
frag.appendChild(empty);
|
|
1013
|
-
} else {
|
|
1014
|
-
roots.forEach(function (path) {
|
|
1015
|
-
if (!treeSearchTerm || branchHasSearchMatch(path)) appendNodeToFrag(frag, path, 0, false);
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
list.innerHTML = "";
|
|
1019
|
-
list.appendChild(frag);
|
|
1020
|
-
}, 0);
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
function renderBreadcrumbs() {
|
|
1024
|
-
var el = $("#opcua-tree-breadcrumbs");
|
|
1025
|
-
if (!selectedPath) { el.text("No selection"); return; }
|
|
1026
|
-
var tokens = pathToTokens(selectedPath);
|
|
1027
|
-
var cursor = [];
|
|
1028
|
-
var parts = [];
|
|
1029
|
-
tokens.forEach(function (token) {
|
|
1030
|
-
cursor.push(token);
|
|
1031
|
-
if (/^\d+$/.test(token)) parts.push(getNodeDisplayName(cursor.join(".")) || ("#" + token));
|
|
1032
|
-
});
|
|
1033
|
-
el.text(parts.join("."));
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
function updateNode(path, patch) {
|
|
1037
|
-
var item = getAtPath(editorState, path);
|
|
1038
|
-
if (!item) return;
|
|
1039
|
-
Object.keys(patch || {}).forEach(function (k) { item[k] = patch[k]; });
|
|
1040
|
-
syncStateToJson(false);
|
|
1041
|
-
// Avoid rebuilding the details form on every keystroke to preserve input focus.
|
|
1042
|
-
renderTree();
|
|
1043
|
-
renderBreadcrumbs();
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
function openCreateForm(path, kind) {
|
|
1047
|
-
if (!path) return;
|
|
1048
|
-
if (nodeClassFromPath(path) === "Variable" || nodeClassFromPath(path) === "Namespace") {
|
|
1049
|
-
RED.notify("Selected item cannot have children", "warning");
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
pendingCreate = {
|
|
1053
|
-
parentPath: path,
|
|
1054
|
-
kind: kind,
|
|
1055
|
-
name: kind === "variable" ? "newVariable" : kind === "folder" ? "newFolder" : kind === "objecttype" ? "newObjectType" : kind === "alarm" ? "newAlarm" : kind === "method" ? "newMethod" : "newObject",
|
|
1056
|
-
displayName: "",
|
|
1057
|
-
dataType: "Int32",
|
|
1058
|
-
value: "",
|
|
1059
|
-
access: "readwrite",
|
|
1060
|
-
objectsType: "",
|
|
1061
|
-
alarmType: "levelAlarm",
|
|
1062
|
-
variableNodeId: "",
|
|
1063
|
-
severity: 500,
|
|
1064
|
-
highHighLimit: 100,
|
|
1065
|
-
highHighMessage: "High High alarm",
|
|
1066
|
-
highLimit: 80,
|
|
1067
|
-
highMessage: "High alarm",
|
|
1068
|
-
lowLimit: 20,
|
|
1069
|
-
lowMessage: "Low alarm",
|
|
1070
|
-
lowLowLimit: 0,
|
|
1071
|
-
lowLowMessage: "Low Low alarm",
|
|
1072
|
-
normalStateValue: 0,
|
|
1073
|
-
digitalMessage: "Digital alarm"
|
|
1074
|
-
};
|
|
1075
|
-
renderDetails();
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
function saveCreateForm() {
|
|
1079
|
-
if (!pendingCreate) return;
|
|
1080
|
-
var parentPath = pendingCreate.parentPath;
|
|
1081
|
-
var kind = pendingCreate.kind;
|
|
1082
|
-
var branchTargetPath = kind === "variable"
|
|
1083
|
-
? (parentPath + ".variables")
|
|
1084
|
-
: kind === "folder"
|
|
1085
|
-
? (parentPath + ".folders")
|
|
1086
|
-
: kind === "objecttype"
|
|
1087
|
-
? (parentPath + ".objectsTypes")
|
|
1088
|
-
: kind === "alarm"
|
|
1089
|
-
? (parentPath + ".alarms")
|
|
1090
|
-
: kind === "method"
|
|
1091
|
-
? (parentPath + ".methods")
|
|
1092
|
-
: (parentPath + ".objects");
|
|
1093
|
-
var target = getAtPath(editorState, branchTargetPath);
|
|
1094
|
-
if (!Array.isArray(target)) return;
|
|
1095
|
-
if (kind === "variable") {
|
|
1096
|
-
target.push(normalizeVariable({
|
|
1097
|
-
name: pendingCreate.name,
|
|
1098
|
-
displayName: pendingCreate.displayName || "",
|
|
1099
|
-
type: pendingCreate.dataType,
|
|
1100
|
-
value: pendingCreate.value,
|
|
1101
|
-
access: pendingCreate.access || "readwrite"
|
|
1102
|
-
}));
|
|
1103
|
-
} else if (kind === "folder") {
|
|
1104
|
-
target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
|
|
1105
|
-
} else if (kind === "objecttype") {
|
|
1106
|
-
target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "", objectsType: pendingCreate.objectsType || "" }));
|
|
1107
|
-
target[target.length - 1].nodeId = buildGeneratedNodeIdForPath(branchTargetPath + "." + (target.length - 1));
|
|
1108
|
-
} else if (kind === "alarm") {
|
|
1109
|
-
target.push(normalizeAlarm({
|
|
1110
|
-
displayName: pendingCreate.displayName || "",
|
|
1111
|
-
name: pendingCreate.name,
|
|
1112
|
-
type: pendingCreate.alarmType,
|
|
1113
|
-
variableNodeId: pendingCreate.variableNodeId,
|
|
1114
|
-
severity: Number(pendingCreate.severity || 500),
|
|
1115
|
-
highHighLimit: pendingCreate.highHighLimit,
|
|
1116
|
-
highHighMessage: pendingCreate.highHighMessage,
|
|
1117
|
-
highLimit: pendingCreate.highLimit,
|
|
1118
|
-
highMessage: pendingCreate.highMessage,
|
|
1119
|
-
lowLimit: pendingCreate.lowLimit,
|
|
1120
|
-
lowMessage: pendingCreate.lowMessage,
|
|
1121
|
-
lowLowLimit: pendingCreate.lowLowLimit,
|
|
1122
|
-
lowLowMessage: pendingCreate.lowLowMessage,
|
|
1123
|
-
normalStateValue: pendingCreate.normalStateValue,
|
|
1124
|
-
digitalMessage: pendingCreate.digitalMessage
|
|
1125
|
-
}));
|
|
1126
|
-
} else if (kind === "method") {
|
|
1127
|
-
target.push(normalizeMethod({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
|
|
1128
|
-
} else {
|
|
1129
|
-
target.push(normalizeBranch({ name: pendingCreate.name, displayName: pendingCreate.displayName || "" }));
|
|
1130
|
-
}
|
|
1131
|
-
expansionState[parentPath] = true;
|
|
1132
|
-
pendingCreate = null;
|
|
1133
|
-
syncStateToJson(true);
|
|
1134
|
-
renderVisualEditor();
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
function cancelCreateForm() {
|
|
1138
|
-
pendingCreate = null;
|
|
1139
|
-
renderDetails();
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
function renderDetails() {
|
|
1143
|
-
var panel = $("#opcua-tree-details");
|
|
1144
|
-
panel.empty();
|
|
1145
|
-
if (pendingCreate) {
|
|
1146
|
-
panel.append('<div class="form-row"><label>Parent</label><input type="text" id="opcua-create-parent" readonly></div>');
|
|
1147
|
-
panel.append('<div class="form-row"><label>Type</label><input type="text" id="opcua-create-kind" readonly></div>');
|
|
1148
|
-
panel.append('<div class="form-row"><label>Name</label><input type="text" id="opcua-create-name"></div>');
|
|
1149
|
-
panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-create-displayname" placeholder="Leave blank to use browseName"></div>');
|
|
1150
|
-
if (pendingCreate.kind === "variable") {
|
|
1151
|
-
panel.append('<div class="form-row"><label>dataType</label><select id="opcua-create-type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option></select></div>');
|
|
1152
|
-
panel.append('<div class="form-row"><label>Value</label><input type="text" id="opcua-create-value"></div>');
|
|
1153
|
-
panel.append('<div class="form-row"><label>Access</label><select id="opcua-create-access"><option value="readwrite">readwrite</option><option value="readonly">readonly</option></select></div>');
|
|
1154
|
-
}
|
|
1155
|
-
if (pendingCreate.kind === "objecttype") {
|
|
1156
|
-
panel.append('<div class="form-row"><label>objectsType</label>' + buildObjectTypeSelect("opcua-create-objectstype", pendingCreate.objectsType || "") + '</div>');
|
|
1157
|
-
}
|
|
1158
|
-
if (pendingCreate.kind === "alarm") {
|
|
1159
|
-
panel.append('<div class="form-row"><label>alarmType</label><select id="opcua-create-alarm-type"><option value="levelAlarm">levelAlarm</option><option value="digitalAlarm">digitalAlarm</option></select></div>');
|
|
1160
|
-
panel.append('<div class="form-row"><label>variablePath</label><input type="text" id="opcua-create-variable-nodeid"></div>');
|
|
1161
|
-
panel.append('<div class="form-row"><label>severity</label><input type="number" id="opcua-create-severity"></div>');
|
|
1162
|
-
if (pendingCreate.alarmType === "levelAlarm") {
|
|
1163
|
-
panel.append('<div class="form-row"><label>highHighLimit</label><input type="number" id="opcua-create-highhighlimit"></div>');
|
|
1164
|
-
panel.append('<div class="form-row"><label>highHighMessage</label><input type="text" id="opcua-create-highhighmessage"></div>');
|
|
1165
|
-
panel.append('<div class="form-row"><label>highLimit</label><input type="number" id="opcua-create-highlimit"></div>');
|
|
1166
|
-
panel.append('<div class="form-row"><label>highMessage</label><input type="text" id="opcua-create-highmessage"></div>');
|
|
1167
|
-
panel.append('<div class="form-row"><label>lowLimit</label><input type="number" id="opcua-create-lowlimit"></div>');
|
|
1168
|
-
panel.append('<div class="form-row"><label>lowMessage</label><input type="text" id="opcua-create-lowmessage"></div>');
|
|
1169
|
-
panel.append('<div class="form-row"><label>lowLowLimit</label><input type="number" id="opcua-create-lowlowlimit"></div>');
|
|
1170
|
-
panel.append('<div class="form-row"><label>lowLowMessage</label><input type="text" id="opcua-create-lowlowmessage"></div>');
|
|
1171
|
-
} else {
|
|
1172
|
-
panel.append('<div class="form-row"><label>normalStateValue</label><input type="number" id="opcua-create-normalstatevalue"></div>');
|
|
1173
|
-
panel.append('<div class="form-row"><label>digitalMessage</label><input type="text" id="opcua-create-digitalmessage"></div>');
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
panel.append('<div class="form-row"><label style="width:90px;">Actions</label><div><a href="#" id="opcua-create-save" class="editor-button editor-button-small"><i class="fa fa-save"></i> Save</a> <a href="#" id="opcua-create-cancel" class="editor-button editor-button-small"><i class="fa fa-times"></i> Cancel</a></div></div>');
|
|
1177
|
-
$("#opcua-create-parent").val(pendingCreate.parentPath);
|
|
1178
|
-
$("#opcua-create-kind").val(pendingCreate.kind);
|
|
1179
|
-
$("#opcua-create-name").val(pendingCreate.name);
|
|
1180
|
-
$("#opcua-create-displayname").val(pendingCreate.displayName || "");
|
|
1181
|
-
$("#opcua-create-type").val(pendingCreate.dataType);
|
|
1182
|
-
$("#opcua-create-value").val(pendingCreate.value);
|
|
1183
|
-
$("#opcua-create-objectstype").val(pendingCreate.objectsType);
|
|
1184
|
-
$("#opcua-create-access").val(pendingCreate.access || "readwrite");
|
|
1185
|
-
$("#opcua-create-alarm-type").val(pendingCreate.alarmType);
|
|
1186
|
-
$("#opcua-create-variable-nodeid").val(pendingCreate.variableNodeId);
|
|
1187
|
-
$("#opcua-create-severity").val(pendingCreate.severity);
|
|
1188
|
-
$("#opcua-create-highhighlimit").val(pendingCreate.highHighLimit);
|
|
1189
|
-
$("#opcua-create-highhighmessage").val(pendingCreate.highHighMessage);
|
|
1190
|
-
$("#opcua-create-highlimit").val(pendingCreate.highLimit);
|
|
1191
|
-
$("#opcua-create-highmessage").val(pendingCreate.highMessage);
|
|
1192
|
-
$("#opcua-create-lowlimit").val(pendingCreate.lowLimit);
|
|
1193
|
-
$("#opcua-create-lowmessage").val(pendingCreate.lowMessage);
|
|
1194
|
-
$("#opcua-create-lowlowlimit").val(pendingCreate.lowLowLimit);
|
|
1195
|
-
$("#opcua-create-lowlowmessage").val(pendingCreate.lowLowMessage);
|
|
1196
|
-
$("#opcua-create-normalstatevalue").val(pendingCreate.normalStateValue);
|
|
1197
|
-
$("#opcua-create-digitalmessage").val(pendingCreate.digitalMessage);
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
if (!selectedPath) { panel.append('<div class="opcua-tree-empty">Select a node to edit browseName, namespace, nodeId, and description.</div>'); return; }
|
|
1201
|
-
var item = getAtPath(editorState, selectedPath);
|
|
1202
|
-
if (!item) { panel.append('<div class="opcua-tree-empty">Selected node not found.</div>'); return; }
|
|
1203
|
-
var nodeClass = nodeClassFromPath(selectedPath);
|
|
1204
|
-
var nodeId = buildDisplayNodeIdFromEditorPath(selectedPath);
|
|
1205
|
-
var nodeIdSuffix = nodeIdSuffixFromValue(item.nodeId, buildDefaultNodeIdSuffixFromEditorPath(selectedPath));
|
|
1206
|
-
var nodeIdLocked = isObjectTypeModelPath(selectedPath);
|
|
1207
|
-
var namespaceId = getNodeNamespaceId(selectedPath);
|
|
1208
|
-
var namespaceOptions = getNamespaceOptions();
|
|
1209
|
-
if (nodeClass === "Namespace") {
|
|
1210
|
-
panel.append('<div class="form-row"><label>namespaceId</label><input type="number" id="opcua-detail-namespace-entry-id" min="2"></div>');
|
|
1211
|
-
panel.append('<div class="form-row"><label>name</label><input type="text" id="opcua-detail-namespace-entry-name"></div>');
|
|
1212
|
-
panel.append('<div class="form-row"><label style="width:90px;">Actions</label><div>' + (normalizeNamespaceId(item.id) === DEFAULT_NAMESPACE_ID ? '' : '<a href="#" id="opcua-detail-remove" class="editor-button editor-button-small"><i class="fa fa-trash"></i> Remove</a>') + '</div></div>');
|
|
1213
|
-
$("#opcua-detail-namespace-entry-id").val(item.id !== undefined ? item.id : DEFAULT_NAMESPACE_ID);
|
|
1214
|
-
$("#opcua-detail-namespace-entry-name").val(item.name || "");
|
|
1215
|
-
if (normalizeNamespaceId(item.id) === DEFAULT_NAMESPACE_ID) $("#opcua-detail-namespace-entry-id").prop("disabled", true);
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
panel.append('<div class="form-row"><label>browseName</label><input type="text" id="opcua-detail-name"></div>');
|
|
1219
|
-
panel.append('<div class="form-row"><label>nodeClass</label><input type="text" id="opcua-detail-class" readonly></div>');
|
|
1220
|
-
panel.append('<div class="form-row"><label>namespace</label><select id="opcua-detail-namespace"></select></div>');
|
|
1221
|
-
panel.append('<div class="form-row"><label>nodeId</label><div class="opcua-nodeid-field"><span class="opcua-nodeid-prefix">' + getNodeIdPrefix(namespaceId) + '</span><input type="text" id="opcua-detail-nodeid"' + (nodeIdLocked ? ' readonly title="Generated automatically for object type models."' : '') + '><a href="#" id="opcua-detail-copy-nodeid" class="editor-button editor-button-small"><i class="fa fa-copy"></i> Copy</a></div></div>');
|
|
1222
|
-
panel.append('<div class="form-row"><label>Description</label><input type="text" id="opcua-detail-description"></div>');
|
|
1223
|
-
panel.append('<div class="form-row"><label>displayName</label><input type="text" id="opcua-detail-displayname" placeholder="Leave blank to use browseName"></div>');
|
|
1224
|
-
if (nodeClass === "ObjectType") {
|
|
1225
|
-
panel.append('<div class="form-row"><label>objectsType</label>' + buildObjectTypeSelect("opcua-detail-objectstype", item.objectsType || "") + '</div>');
|
|
1226
|
-
}
|
|
1227
|
-
if (nodeClass === "Variable") {
|
|
1228
|
-
panel.append('<div class="form-row"><label>dataType</label><select id="opcua-detail-type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option><option value="ByteString">ByteString</option></select></div>');
|
|
1229
|
-
panel.append('<div class="form-row"><label>Value</label><input type="text" id="opcua-detail-value"></div>');
|
|
1230
|
-
panel.append('<div class="form-row"><label>Access</label><select id="opcua-detail-access"><option value="readwrite">readwrite</option><option value="readonly">readonly</option></select></div>');
|
|
1231
|
-
}
|
|
1232
|
-
if (nodeClass === "Method") {
|
|
1233
|
-
panel.append('<hr style="margin:8px 0; border-color:#e3e3e3;">');
|
|
1234
|
-
panel.append('<div style="font-size:11px;font-weight:700;text-transform:uppercase;color:#666;margin-bottom:4px;">Inputs</div>');
|
|
1235
|
-
var inputsDiv = $('<div id="opcua-detail-inputs"></div>').appendTo(panel);
|
|
1236
|
-
(item.inputs || []).forEach(function (arg, idx) {
|
|
1237
|
-
var argPath = selectedPath + ".inputs." + idx;
|
|
1238
|
-
var argBlock = $('<div style="border:1px solid #e3e3e3;border-radius:4px;padding:6px;margin-bottom:4px;"></div>');
|
|
1239
|
-
argBlock.append('<div class="form-row"><label>name</label><input type="text" class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="name"></div>');
|
|
1240
|
-
argBlock.append('<div class="form-row"><label>type</label><select class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option></select></div>');
|
|
1241
|
-
argBlock.append('<div class="form-row"><label>displayName</label><input type="text" class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="displayName"></div>');
|
|
1242
|
-
argBlock.append('<div class="form-row"><label>description</label><input type="text" class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="description"></div>');
|
|
1243
|
-
argBlock.append('<div class="form-row"><label></label><a href="#" class="editor-button editor-button-small opcua-method-arg-remove" data-arg-path="' + argPath + '"><i class="fa fa-trash"></i> Remove</a></div>');
|
|
1244
|
-
argBlock.find('[data-field="name"]').val(arg.name || "");
|
|
1245
|
-
argBlock.find('[data-field="type"]').val(arg.type || "Float");
|
|
1246
|
-
argBlock.find('[data-field="displayName"]').val(arg.displayName || "");
|
|
1247
|
-
argBlock.find('[data-field="description"]').val(arg.description || "");
|
|
1248
|
-
inputsDiv.append(argBlock);
|
|
1249
|
-
});
|
|
1250
|
-
panel.append('<div class="form-row"><label></label><a href="#" class="editor-button editor-button-small" id="opcua-method-add-input"><i class="fa fa-plus"></i> Add input</a></div>');
|
|
1251
|
-
panel.append('<hr style="margin:8px 0; border-color:#e3e3e3;">');
|
|
1252
|
-
panel.append('<div style="font-size:11px;font-weight:700;text-transform:uppercase;color:#666;margin-bottom:4px;">Outputs</div>');
|
|
1253
|
-
var outputsDiv = $('<div id="opcua-detail-outputs"></div>').appendTo(panel);
|
|
1254
|
-
(item.outputs || []).forEach(function (arg, idx) {
|
|
1255
|
-
var argPath = selectedPath + ".outputs." + idx;
|
|
1256
|
-
var argBlock = $('<div style="border:1px solid #e3e3e3;border-radius:4px;padding:6px;margin-bottom:4px;"></div>');
|
|
1257
|
-
argBlock.append('<div class="form-row"><label>name</label><input type="text" class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="name"></div>');
|
|
1258
|
-
argBlock.append('<div class="form-row"><label>type</label><select class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="type"><option value="Int16">Int16</option><option value="Int32">Int32</option><option value="Float">Float</option><option value="Boolean">Boolean</option><option value="String">String</option></select></div>');
|
|
1259
|
-
argBlock.append('<div class="form-row"><label>displayName</label><input type="text" class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="displayName"></div>');
|
|
1260
|
-
argBlock.append('<div class="form-row"><label>description</label><input type="text" class="opcua-method-arg-bind" data-arg-path="' + argPath + '" data-field="description"></div>');
|
|
1261
|
-
argBlock.append('<div class="form-row"><label></label><a href="#" class="editor-button editor-button-small opcua-method-arg-remove" data-arg-path="' + argPath + '"><i class="fa fa-trash"></i> Remove</a></div>');
|
|
1262
|
-
argBlock.find('[data-field="name"]').val(arg.name || "");
|
|
1263
|
-
argBlock.find('[data-field="type"]').val(arg.type || "Float");
|
|
1264
|
-
argBlock.find('[data-field="displayName"]').val(arg.displayName || "");
|
|
1265
|
-
argBlock.find('[data-field="description"]').val(arg.description || "");
|
|
1266
|
-
outputsDiv.append(argBlock);
|
|
1267
|
-
});
|
|
1268
|
-
panel.append('<div class="form-row"><label></label><a href="#" class="editor-button editor-button-small" id="opcua-method-add-output"><i class="fa fa-plus"></i> Add output</a></div>');
|
|
1269
|
-
}
|
|
1270
|
-
if (nodeClass === "Alarm") {
|
|
1271
|
-
panel.append('<div class="form-row"><label>alarmType</label><select id="opcua-detail-alarm-type"><option value="levelAlarm">levelAlarm</option><option value="digitalAlarm">digitalAlarm</option></select></div>');
|
|
1272
|
-
panel.append('<div class="form-row"><label>variablePath</label><input type="text" id="opcua-detail-variable-nodeid"></div>');
|
|
1273
|
-
panel.append('<div class="form-row"><label>severity</label><input type="number" id="opcua-detail-severity"></div>');
|
|
1274
|
-
if ((item.type || "levelAlarm") === "levelAlarm") {
|
|
1275
|
-
panel.append('<div class="form-row"><label>highHighLimit</label><input type="number" id="opcua-detail-highhighlimit"></div>');
|
|
1276
|
-
panel.append('<div class="form-row"><label>highHighMessage</label><input type="text" id="opcua-detail-highhighmessage"></div>');
|
|
1277
|
-
panel.append('<div class="form-row"><label>highLimit</label><input type="number" id="opcua-detail-highlimit"></div>');
|
|
1278
|
-
panel.append('<div class="form-row"><label>highMessage</label><input type="text" id="opcua-detail-highmessage"></div>');
|
|
1279
|
-
panel.append('<div class="form-row"><label>lowLimit</label><input type="number" id="opcua-detail-lowlimit"></div>');
|
|
1280
|
-
panel.append('<div class="form-row"><label>lowMessage</label><input type="text" id="opcua-detail-lowmessage"></div>');
|
|
1281
|
-
panel.append('<div class="form-row"><label>lowLowLimit</label><input type="number" id="opcua-detail-lowlowlimit"></div>');
|
|
1282
|
-
panel.append('<div class="form-row"><label>lowLowMessage</label><input type="text" id="opcua-detail-lowlowmessage"></div>');
|
|
1283
|
-
} else {
|
|
1284
|
-
panel.append('<div class="form-row"><label>normalStateValue</label><input type="number" id="opcua-detail-normalstatevalue"></div>');
|
|
1285
|
-
panel.append('<div class="form-row"><label>digitalMessage</label><input type="text" id="opcua-detail-digitalmessage"></div>');
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
panel.append('<div class="form-row"><label style="width:90px;">Actions</label><div><a href="#" id="opcua-detail-edit" class="editor-button editor-button-small"><i class="fa fa-pencil"></i> Edit</a> <a href="#" id="opcua-detail-remove" class="editor-button editor-button-small"><i class="fa fa-trash"></i> Remove</a></div></div>');
|
|
1289
|
-
$("#opcua-detail-name").val(item.name || "");
|
|
1290
|
-
$("#opcua-detail-class").val(nodeClass);
|
|
1291
|
-
$("#opcua-detail-nodeid").val(nodeIdLocked ? buildDefaultNodeIdSuffixFromEditorPath(selectedPath) : nodeIdSuffix);
|
|
1292
|
-
$("#opcua-detail-description").val(item.description || "");
|
|
1293
|
-
namespaceOptions.forEach(function (option) {
|
|
1294
|
-
$("#opcua-detail-namespace").append($("<option></option>").val(option.id).text(getNamespaceLabel(option.id)));
|
|
1295
|
-
});
|
|
1296
|
-
$("#opcua-detail-namespace").val(String(namespaceId));
|
|
1297
|
-
$("#opcua-detail-displayname").val(item.displayName || "");
|
|
1298
|
-
if (nodeClass === "ObjectType") { $("#opcua-detail-objectstype").val(item.objectsType || ""); }
|
|
1299
|
-
if (nodeClass === "Variable") { $("#opcua-detail-type").val(item.type || "Int32"); $("#opcua-detail-value").val(item.value !== undefined ? item.value : ""); $("#opcua-detail-access").val(item.access || "readwrite"); }
|
|
1300
|
-
if (nodeClass === "Alarm") {
|
|
1301
|
-
$("#opcua-detail-alarm-type").val(item.type || "levelAlarm");
|
|
1302
|
-
$("#opcua-detail-variable-nodeid").val(item.variableNodeId || "");
|
|
1303
|
-
$("#opcua-detail-severity").val(item.severity !== undefined ? item.severity : 500);
|
|
1304
|
-
$("#opcua-detail-highhighlimit").val(item.highHighLimit !== undefined ? item.highHighLimit : 100);
|
|
1305
|
-
$("#opcua-detail-highhighmessage").val(item.highHighMessage || "High High alarm");
|
|
1306
|
-
$("#opcua-detail-highlimit").val(item.highLimit !== undefined ? item.highLimit : 80);
|
|
1307
|
-
$("#opcua-detail-highmessage").val(item.highMessage || "High alarm");
|
|
1308
|
-
$("#opcua-detail-lowlimit").val(item.lowLimit !== undefined ? item.lowLimit : 20);
|
|
1309
|
-
$("#opcua-detail-lowmessage").val(item.lowMessage || "Low alarm");
|
|
1310
|
-
$("#opcua-detail-lowlowlimit").val(item.lowLowLimit !== undefined ? item.lowLowLimit : 0);
|
|
1311
|
-
$("#opcua-detail-lowlowmessage").val(item.lowLowMessage || "Low Low alarm");
|
|
1312
|
-
$("#opcua-detail-normalstatevalue").val(item.normalStateValue !== undefined ? item.normalStateValue : 0);
|
|
1313
|
-
$("#opcua-detail-digitalmessage").val(item.digitalMessage || "Digital alarm");
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
function addNode(path, explicitKind) {
|
|
1318
|
-
var kind = explicitKind || "object";
|
|
1319
|
-
openCreateForm(path, kind);
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
function removeNode(path) {
|
|
1323
|
-
if (!path) return;
|
|
1324
|
-
if (nodeClassFromPath(path) === "Namespace") {
|
|
1325
|
-
var namespaceItem = getAtPath(editorState, path);
|
|
1326
|
-
if (normalizeNamespaceId(namespaceItem && namespaceItem.id) === DEFAULT_NAMESPACE_ID) {
|
|
1327
|
-
RED.notify("Namespace 2 is fixed and cannot be removed.", "warning");
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
removeAtPath(editorState, path);
|
|
1332
|
-
if (selectedPath === path) selectedPath = "";
|
|
1333
|
-
syncStateToJson(true);
|
|
1334
|
-
renderVisualEditor();
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
function renderVisualEditor() {
|
|
1338
|
-
renderTree();
|
|
1339
|
-
renderDetails();
|
|
1340
|
-
renderBreadcrumbs();
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
function getNextNamespaceId() {
|
|
1344
|
-
var ids = getNamespaceOptions().map(function (item) { return normalizeNamespaceId(item.id); });
|
|
1345
|
-
var nextId = DEFAULT_NAMESPACE_ID;
|
|
1346
|
-
while (ids.indexOf(nextId) !== -1) nextId += 1;
|
|
1347
|
-
return nextId;
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
function addItem(parentPath, kind) {
|
|
1351
|
-
var target = getAtPath(editorState, parentPath);
|
|
1352
|
-
if (!Array.isArray(target)) return;
|
|
1353
|
-
if (kind === "object" || kind === "folder" || kind === "objectTypeDefinition") {
|
|
1354
|
-
target.push(normalizeBranch());
|
|
1355
|
-
if (kind === "objectTypeDefinition") {
|
|
1356
|
-
target[target.length - 1].nodeId = buildGeneratedNodeIdForPath(parentPath + "." + (target.length - 1));
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
if (kind === "namespace") {
|
|
1360
|
-
var namespaceId = getNextNamespaceId();
|
|
1361
|
-
target.push(normalizeNamespaceDefinition({
|
|
1362
|
-
id: namespaceId,
|
|
1363
|
-
name: "urn:namespace:" + namespaceId
|
|
1364
|
-
}));
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
$(document).on("change", "#node-input-tree", function () {
|
|
1369
|
-
var jsonText = $(this).val();
|
|
1370
|
-
if (!jsonText) {
|
|
1371
|
-
editorState = normalizeTree({ objects: [], folders: [], objectsTypes: [], nameSpaces: [] });
|
|
1372
|
-
updateTreeField(prettyTree(editorState), true);
|
|
1373
|
-
renderVisualEditor();
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
try {
|
|
1377
|
-
editorState = cloneTree(normalizeTree(parseTree(jsonText, true)));
|
|
1378
|
-
renderVisualEditor();
|
|
1379
|
-
} catch (error) { RED.notify("Invalid JSON: " + error.message, "error"); }
|
|
1380
|
-
});
|
|
1381
|
-
|
|
1382
|
-
$(document).on("click", ".opcua-tree-row", function (event) {
|
|
1383
|
-
var path = $(this).attr("data-path");
|
|
1384
|
-
if ($(event.target).closest(".opcua-tree-twisty").length) {
|
|
1385
|
-
expansionState[path] = !isExpanded(path, false);
|
|
1386
|
-
renderTree();
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
selectNode(path);
|
|
1390
|
-
});
|
|
1391
|
-
|
|
1392
|
-
$(document).on("contextmenu", ".opcua-tree-row", function (event) {
|
|
1393
|
-
event.preventDefault();
|
|
1394
|
-
selectNode($(this).attr("data-path"));
|
|
1395
|
-
$("#opcua-tree-context-menu").css({ left: event.clientX + "px", top: event.clientY + "px" }).show();
|
|
1396
|
-
});
|
|
1397
|
-
|
|
1398
|
-
$(document).on("click", function () { $("#opcua-tree-context-menu").hide(); });
|
|
1399
|
-
$(document).on("click", "#opcua-tree-context-menu a", function (event) {
|
|
1400
|
-
event.preventDefault();
|
|
1401
|
-
var action = $(this).attr("data-action");
|
|
1402
|
-
if (action === "add-folder") addNode(selectedPath, "folder");
|
|
1403
|
-
if (action === "add-object") addNode(selectedPath, "object");
|
|
1404
|
-
if (action === "add-variable") addNode(selectedPath, "variable");
|
|
1405
|
-
if (action === "add-objecttype") addNode(selectedPath, "objecttype");
|
|
1406
|
-
if (action === "add-alarm") addNode(selectedPath, "alarm");
|
|
1407
|
-
if (action === "add-method") addNode(selectedPath, "method");
|
|
1408
|
-
if (action === "add-method") addNode(selectedPath, "method");
|
|
1409
|
-
if (action === "remove") removeNode(selectedPath);
|
|
1410
|
-
if (action === "edit") renderDetails();
|
|
1411
|
-
$("#opcua-tree-context-menu").hide();
|
|
1412
|
-
});
|
|
1413
|
-
|
|
1414
|
-
$(document).on("input change", ".opcua-method-arg-bind", function () {
|
|
1415
|
-
var el = $(this);
|
|
1416
|
-
var argPath = el.attr("data-arg-path");
|
|
1417
|
-
var field = el.attr("data-field");
|
|
1418
|
-
var arg = getAtPath(editorState, argPath);
|
|
1419
|
-
if (!arg) return;
|
|
1420
|
-
arg[field] = el.val();
|
|
1421
|
-
syncStateToJson(false);
|
|
1422
|
-
});
|
|
1423
|
-
|
|
1424
|
-
$(document).on("click", ".opcua-method-arg-remove", function (e) {
|
|
1425
|
-
e.preventDefault();
|
|
1426
|
-
var argPath = $(this).attr("data-arg-path");
|
|
1427
|
-
removeAtPath(editorState, argPath);
|
|
1428
|
-
syncStateToJson(false);
|
|
1429
|
-
renderDetails();
|
|
1430
|
-
});
|
|
1431
|
-
|
|
1432
|
-
$(document).on("click", "#opcua-method-add-input", function (e) {
|
|
1433
|
-
e.preventDefault();
|
|
1434
|
-
var item = getAtPath(editorState, selectedPath);
|
|
1435
|
-
if (!item) return;
|
|
1436
|
-
if (!Array.isArray(item.inputs)) item.inputs = [];
|
|
1437
|
-
item.inputs.push(normalizeMethodArg());
|
|
1438
|
-
syncStateToJson(false);
|
|
1439
|
-
renderDetails();
|
|
1440
|
-
});
|
|
1441
|
-
|
|
1442
|
-
$(document).on("click", "#opcua-method-add-output", function (e) {
|
|
1443
|
-
e.preventDefault();
|
|
1444
|
-
var item = getAtPath(editorState, selectedPath);
|
|
1445
|
-
if (!item) return;
|
|
1446
|
-
if (!Array.isArray(item.outputs)) item.outputs = [];
|
|
1447
|
-
item.outputs.push(normalizeMethodArg());
|
|
1448
|
-
syncStateToJson(false);
|
|
1449
|
-
renderDetails();
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
$(document).on("input", "#opcua-detail-displayname", function () { updateNode(selectedPath, { displayName: $(this).val() }); });
|
|
1453
|
-
|
|
1454
|
-
$(document).on("input", "#opcua-detail-name", function () { updateNode(selectedPath, { name: $(this).val() }); });
|
|
1455
|
-
$(document).on("change", "#opcua-detail-namespace", function () {
|
|
1456
|
-
var nextNamespaceId = normalizeNamespaceId($(this).val());
|
|
1457
|
-
var item = getAtPath(editorState, selectedPath);
|
|
1458
|
-
if (!item) return;
|
|
1459
|
-
item.namespaceId = nextNamespaceId;
|
|
1460
|
-
item.nodeId = normalizeCustomNodeIdFromSuffix(selectedPath, $("#opcua-detail-nodeid").val());
|
|
1461
|
-
syncStateToJson(false);
|
|
1462
|
-
renderTree();
|
|
1463
|
-
renderBreadcrumbs();
|
|
1464
|
-
renderDetails();
|
|
1465
|
-
});
|
|
1466
|
-
$(document).on("input", "#opcua-detail-nodeid", function () { updateNode(selectedPath, { nodeId: normalizeCustomNodeIdFromSuffix(selectedPath, $(this).val()) }); });
|
|
1467
|
-
$(document).on("click", "#opcua-detail-copy-nodeid", function (event) {
|
|
1468
|
-
event.preventDefault();
|
|
1469
|
-
copyNodeIdValue(buildDisplayNodeIdFromEditorPath(selectedPath));
|
|
1470
|
-
});
|
|
1471
|
-
$(document).on("input", "#opcua-detail-namespace-entry-id", function () {
|
|
1472
|
-
var nextId = normalizeNamespaceId($(this).val());
|
|
1473
|
-
var currentPath = selectedPath;
|
|
1474
|
-
var duplicate = (editorState.nameSpaces || []).some(function (namespaceItem, index) {
|
|
1475
|
-
return currentPath !== ("nameSpaces." + index) && normalizeNamespaceId(namespaceItem.id) === nextId;
|
|
1476
|
-
});
|
|
1477
|
-
if (duplicate) {
|
|
1478
|
-
RED.notify("Namespace id must be unique.", "warning");
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
updateNode(selectedPath, { id: nextId });
|
|
1482
|
-
});
|
|
1483
|
-
$(document).on("input", "#opcua-detail-namespace-entry-name", function () {
|
|
1484
|
-
var nextName = $(this).val();
|
|
1485
|
-
updateNode(selectedPath, { name: nextName });
|
|
1486
|
-
var namespaceItem = getAtPath(editorState, selectedPath);
|
|
1487
|
-
if (normalizeNamespaceId(namespaceItem && namespaceItem.id) === DEFAULT_NAMESPACE_ID) {
|
|
1488
|
-
$("#node-input-namespaceUri").val(nextName);
|
|
1489
|
-
}
|
|
1490
|
-
});
|
|
1491
|
-
$(document).on("input", "#opcua-detail-description", function () { updateNode(selectedPath, { description: $(this).val() }); });
|
|
1492
|
-
$(document).on("change", "#opcua-detail-objectstype", function () { updateNode(selectedPath, { objectsType: $(this).val() }); });
|
|
1493
|
-
$(document).on("change", "#opcua-detail-type", function () { updateNode(selectedPath, { type: $(this).val() }); });
|
|
1494
|
-
$(document).on("change", "#opcua-detail-access", function () { updateNode(selectedPath, { access: $(this).val() }); });
|
|
1495
|
-
$(document).on("input", "#opcua-detail-value", function () { updateNode(selectedPath, { value: $(this).val() }); });
|
|
1496
|
-
$(document).on("change", "#opcua-detail-alarm-type", function () {
|
|
1497
|
-
updateNode(selectedPath, { type: $(this).val() });
|
|
1498
|
-
renderDetails();
|
|
1499
|
-
});
|
|
1500
|
-
$(document).on("input", "#opcua-detail-variable-nodeid", function () { updateNode(selectedPath, { variableNodeId: $(this).val() }); });
|
|
1501
|
-
$(document).on("input", "#opcua-detail-severity", function () { updateNode(selectedPath, { severity: Number($(this).val() || 0) }); });
|
|
1502
|
-
$(document).on("input", "#opcua-detail-highhighlimit", function () { updateNode(selectedPath, { highHighLimit: Number($(this).val() || 0) }); });
|
|
1503
|
-
$(document).on("input", "#opcua-detail-highhighmessage", function () { updateNode(selectedPath, { highHighMessage: $(this).val() }); });
|
|
1504
|
-
$(document).on("input", "#opcua-detail-highlimit", function () { updateNode(selectedPath, { highLimit: Number($(this).val() || 0) }); });
|
|
1505
|
-
$(document).on("input", "#opcua-detail-highmessage", function () { updateNode(selectedPath, { highMessage: $(this).val() }); });
|
|
1506
|
-
$(document).on("input", "#opcua-detail-lowlimit", function () { updateNode(selectedPath, { lowLimit: Number($(this).val() || 0) }); });
|
|
1507
|
-
$(document).on("input", "#opcua-detail-lowmessage", function () { updateNode(selectedPath, { lowMessage: $(this).val() }); });
|
|
1508
|
-
$(document).on("input", "#opcua-detail-lowlowlimit", function () { updateNode(selectedPath, { lowLowLimit: Number($(this).val() || 0) }); });
|
|
1509
|
-
$(document).on("input", "#opcua-detail-lowlowmessage", function () { updateNode(selectedPath, { lowLowMessage: $(this).val() }); });
|
|
1510
|
-
$(document).on("input", "#opcua-detail-normalstatevalue", function () { updateNode(selectedPath, { normalStateValue: Number($(this).val() || 0) }); });
|
|
1511
|
-
$(document).on("input", "#opcua-detail-digitalmessage", function () { updateNode(selectedPath, { digitalMessage: $(this).val() }); });
|
|
1512
|
-
$(document).on("input", "#opcua-create-name", function () { if (pendingCreate) pendingCreate.name = $(this).val(); });
|
|
1513
|
-
$(document).on("input", "#opcua-create-displayname", function () { if (pendingCreate) pendingCreate.displayName = $(this).val(); });
|
|
1514
|
-
$(document).on("change", "#opcua-create-type", function () { if (pendingCreate) pendingCreate.dataType = $(this).val(); });
|
|
1515
|
-
$(document).on("input", "#opcua-create-value", function () { if (pendingCreate) pendingCreate.value = $(this).val(); });
|
|
1516
|
-
$(document).on("change", "#opcua-create-objectstype", function () { if (pendingCreate) pendingCreate.objectsType = $(this).val(); });
|
|
1517
|
-
$(document).on("change", "#opcua-create-access", function () { if (pendingCreate) pendingCreate.access = $(this).val(); });
|
|
1518
|
-
$(document).on("change", "#opcua-create-alarm-type", function () {
|
|
1519
|
-
if (pendingCreate) {
|
|
1520
|
-
pendingCreate.alarmType = $(this).val();
|
|
1521
|
-
renderDetails();
|
|
1522
|
-
}
|
|
1523
|
-
});
|
|
1524
|
-
$(document).on("input", "#opcua-create-variable-nodeid", function () { if (pendingCreate) pendingCreate.variableNodeId = $(this).val(); });
|
|
1525
|
-
$(document).on("input", "#opcua-create-severity", function () { if (pendingCreate) pendingCreate.severity = Number($(this).val() || 0); });
|
|
1526
|
-
$(document).on("input", "#opcua-create-highhighlimit", function () { if (pendingCreate) pendingCreate.highHighLimit = Number($(this).val() || 0); });
|
|
1527
|
-
$(document).on("input", "#opcua-create-highhighmessage", function () { if (pendingCreate) pendingCreate.highHighMessage = $(this).val(); });
|
|
1528
|
-
$(document).on("input", "#opcua-create-highlimit", function () { if (pendingCreate) pendingCreate.highLimit = Number($(this).val() || 0); });
|
|
1529
|
-
$(document).on("input", "#opcua-create-highmessage", function () { if (pendingCreate) pendingCreate.highMessage = $(this).val(); });
|
|
1530
|
-
$(document).on("input", "#opcua-create-lowlimit", function () { if (pendingCreate) pendingCreate.lowLimit = Number($(this).val() || 0); });
|
|
1531
|
-
$(document).on("input", "#opcua-create-lowmessage", function () { if (pendingCreate) pendingCreate.lowMessage = $(this).val(); });
|
|
1532
|
-
$(document).on("input", "#opcua-create-lowlowlimit", function () { if (pendingCreate) pendingCreate.lowLowLimit = Number($(this).val() || 0); });
|
|
1533
|
-
$(document).on("input", "#opcua-create-lowlowmessage", function () { if (pendingCreate) pendingCreate.lowLowMessage = $(this).val(); });
|
|
1534
|
-
$(document).on("input", "#opcua-create-normalstatevalue", function () { if (pendingCreate) pendingCreate.normalStateValue = Number($(this).val() || 0); });
|
|
1535
|
-
$(document).on("input", "#opcua-create-digitalmessage", function () { if (pendingCreate) pendingCreate.digitalMessage = $(this).val(); });
|
|
1536
|
-
$(document).on("click", "#opcua-create-save", function (e) { e.preventDefault(); saveCreateForm(); });
|
|
1537
|
-
$(document).on("click", "#opcua-create-cancel", function (e) { e.preventDefault(); cancelCreateForm(); });
|
|
1538
|
-
$(document).on("click", "#opcua-detail-edit", function (e) { e.preventDefault(); renderDetails(); });
|
|
1539
|
-
$(document).on("click", "#opcua-detail-remove", function (e) { e.preventDefault(); removeNode(selectedPath); });
|
|
1540
|
-
|
|
1541
|
-
RED.nodes.registerType("opc-ua-server", {
|
|
1542
|
-
category: "network",
|
|
1543
|
-
color: "#d9edf7",
|
|
1544
|
-
credentials: { username: { type: "text" }, password: { type: "password" } },
|
|
1545
|
-
defaults: {
|
|
1546
|
-
name: { value: "" }, resourcePath: { value: "/" }, serverName: { value: "Node-RED OPC UA Server", required: true }, allowAnonymous: { value: true },
|
|
1547
|
-
port: { value: 4840, required: true, validate: function (value) { var port = Number(value); return Number.isInteger(port) && port > 0 && port < 65536; } },
|
|
1548
|
-
maxConnections: { value: 10, required: true, validate: function (value) { var n = Number(value); return Number.isInteger(n) && n > 0; } },
|
|
1549
|
-
securityPolicy: { value: "None", required: true }, securityMode: { value: "None", required: true }, namespaceUri: { value: "urn:node-red:opc-ua-server", required: true },
|
|
1550
|
-
tree: {
|
|
1551
|
-
value: "{\n \"folders\": [],\n \"objects\": [],\n \"objectsTypes\": [],\n \"nameSpaces\": [\n {\n \"id\": 2,\n \"name\": \"urn:node-red:opc-ua-server\"\n }\n ]\n}",
|
|
1552
|
-
validate: function (value) {
|
|
1553
|
-
try { var parsed = parseTree(value, true); return Array.isArray(parsed.objects || []) && Array.isArray(parsed.folders || []) && Array.isArray(parsed.objectsTypes || parsed.objectTypes || []) && Array.isArray(parsed.nameSpaces || parsed.namespaces || []); }
|
|
1554
|
-
catch (error) { return false; }
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
},
|
|
1558
|
-
inputs: 1,
|
|
1559
|
-
outputs: 0,
|
|
1560
|
-
icon: "opcua.svg",
|
|
1561
|
-
label: function () { return this.name || this.serverName || "opc-ua-server"; },
|
|
1562
|
-
oneditprepare: function () {
|
|
1563
|
-
var node = this;
|
|
1564
|
-
editorState = cloneTree(normalizeTree(parseTree(node.tree)));
|
|
1565
|
-
var defaultNamespaceEntry = (editorState.nameSpaces || []).find(function (item) { return normalizeNamespaceId(item.id) === DEFAULT_NAMESPACE_ID; });
|
|
1566
|
-
if (defaultNamespaceEntry && defaultNamespaceEntry.name) {
|
|
1567
|
-
$("#node-input-namespaceUri").val(defaultNamespaceEntry.name);
|
|
1568
|
-
}
|
|
1569
|
-
updateTreeField(prettyTree(editorState), false);
|
|
1570
|
-
$("#node-input-tree-editor").typedInput({ type: "json", types: ["json"] });
|
|
1571
|
-
$("#node-input-tree-editor").typedInput("value", prettyTree(editorState));
|
|
1572
|
-
$("#node-input-tree-editor").on("change", function () {
|
|
1573
|
-
if (isSyncing) return;
|
|
1574
|
-
var val = $(this).typedInput("value");
|
|
1575
|
-
$("#node-input-tree").val(val).trigger("change");
|
|
1576
|
-
});
|
|
1577
|
-
|
|
1578
|
-
treeSearchValue = ""; treeSearchTerm = ""; selectedPath = "";
|
|
1579
|
-
$("#node-input-tree-search").val(""); $("#node-input-tree-search-clear").hide();
|
|
1580
|
-
renderVisualEditor();
|
|
1581
|
-
|
|
1582
|
-
$("#node-input-open-tree-modal").off("click").on("click", function (event) { event.preventDefault(); openTreeModal(); });
|
|
1583
|
-
$("#node-input-close-tree-modal").off("click").on("click", function (event) { event.preventDefault(); closeTreeModal(); });
|
|
1584
|
-
$("#node-input-tree-modal").off("click").on("click", function (event) { if (event.target === this) closeTreeModal(); });
|
|
1585
|
-
|
|
1586
|
-
$("#node-input-tree-search").off("input").on("input", debounce(function () {
|
|
1587
|
-
treeSearchValue = $(this).val(); treeSearchTerm = normalizeSearchTerm(treeSearchValue);
|
|
1588
|
-
$("#node-input-tree-search-clear").toggle(!!treeSearchTerm); renderTree();
|
|
1589
|
-
}, 200));
|
|
1590
|
-
$("#node-input-tree-search-clear").off("click").on("click", function (event) {
|
|
1591
|
-
event.preventDefault(); treeSearchValue = ""; treeSearchTerm = "";
|
|
1592
|
-
$("#node-input-tree-search").val(""); $(this).hide(); renderTree();
|
|
1593
|
-
});
|
|
1594
|
-
$("#node-input-namespaceUri").off("input.opcuaNamespaceDefault").on("input.opcuaNamespaceDefault", function () {
|
|
1595
|
-
var namespaceEntry = (editorState.nameSpaces || []).find(function (item) { return normalizeNamespaceId(item.id) === DEFAULT_NAMESPACE_ID; });
|
|
1596
|
-
if (!namespaceEntry) return;
|
|
1597
|
-
namespaceEntry.name = $(this).val() || "urn:node-red:opc-ua-server";
|
|
1598
|
-
syncStateToJson(false);
|
|
1599
|
-
if (selectedPath && nodeClassFromPath(selectedPath) === "Namespace") renderDetails();
|
|
1600
|
-
renderTree();
|
|
1601
|
-
});
|
|
1602
|
-
$(document).off("keydown.opcuaTreeModal").on("keydown.opcuaTreeModal", function (event) { if (event.key === "Escape") closeTreeModal(); });
|
|
1603
|
-
|
|
1604
|
-
$("#node-input-add-object").off("click").on("click", function (event) { event.preventDefault(); addItem("objects", "object"); syncStateToJson(true); renderVisualEditor(); });
|
|
1605
|
-
$("#node-input-add-folder").off("click").on("click", function (event) { event.preventDefault(); addItem("folders", "folder"); syncStateToJson(true); renderVisualEditor(); });
|
|
1606
|
-
$("#node-input-add-object-type").off("click").on("click", function (event) { event.preventDefault(); addItem("objectsTypes", "objectTypeDefinition"); syncStateToJson(true); renderVisualEditor(); });
|
|
1607
|
-
$("#node-input-add-namespace").off("click").on("click", function (event) { event.preventDefault(); addItem("nameSpaces", "namespace"); syncStateToJson(true); renderVisualEditor(); });
|
|
1608
|
-
$("#node-input-expand-all").off("click").on("click", function (event) {
|
|
1609
|
-
event.preventDefault();
|
|
1610
|
-
function walk(path) { expansionState[path] = true; getChildrenByPath(path).forEach(walk); }
|
|
1611
|
-
getTopLevelPaths().forEach(walk); renderTree();
|
|
1612
|
-
});
|
|
1613
|
-
$("#node-input-collapse-all").off("click").on("click", function (event) { event.preventDefault(); expansionState = {}; renderTree(); });
|
|
1614
|
-
syncStateToJson(false);
|
|
1615
|
-
},
|
|
1616
|
-
oneditsave: function () {
|
|
1617
|
-
var editorValue = $("#node-input-tree-editor").typedInput("value");
|
|
1618
|
-
$("#node-input-tree").val(editorValue);
|
|
1619
|
-
var jsonText = $("#node-input-tree").val().trim();
|
|
1620
|
-
try { editorState = cloneTree(normalizeTree(parseTree(jsonText, true))); }
|
|
1621
|
-
catch (error) { RED.notify("Invalid OPC UA tree JSON: " + error.message, "error"); return; }
|
|
1622
|
-
if (pendingPasswordHashes > 0) { RED.notify("Wait for password hashing to finish before saving.", "warning"); return; }
|
|
1623
|
-
updateTreeField(prettyTree(editorState), true);
|
|
1624
|
-
closeTreeModal();
|
|
1625
|
-
$(document).off("keydown.opcuaTreeModal");
|
|
1626
|
-
},
|
|
1627
|
-
oneditcancel: function () {
|
|
1628
|
-
closeTreeModal();
|
|
1629
|
-
$(document).off("keydown.opcuaTreeModal");
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1632
|
-
})();
|
|
1633
|
-
</script>
|
|
1634
146
|
|
|
1635
147
|
<script type="text/html" data-help-name="opc-ua-server">
|
|
1636
148
|
<p>Creates a configurable OPC UA server using <code>node-opcua</code>.</p>
|
|
@@ -1640,5 +152,5 @@
|
|
|
1640
152
|
<h3>Details</h3>
|
|
1641
153
|
<p>Supported variable types: <code>Int32</code>, <code>Float</code>, <code>Boolean</code>, <code>String</code>.</p>
|
|
1642
154
|
<p>Supported access modes: <code>readonly</code> and <code>readwrite</code>.</p>
|
|
1643
|
-
<p>Authentication can be configured in the editor with
|
|
155
|
+
<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>
|
|
1644
156
|
</script>
|