@vitormnm/node-red-simple-opcua 1.0.0 → 1.0.2

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