@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.
@@ -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 for="node-input-username"><i class="fa fa-tag"></i> Username</label>
48
- <input type="text" id="node-input-username">
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
- <style>
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
- .opcua-tree-layout {
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, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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 { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[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 a local username list and bcrypt password hashes, and anonymous login can be disabled.</p>
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>