@vitormnm/node-red-simple-opcua 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/client/icons/opcua.svg +132 -0
- package/client/lib/opcua-client-method-service.js +88 -0
- package/client/lib/opcua-client-write-service.js +1 -1
- package/client/opcua-client-config.js +5 -0
- package/client/opcua-client-help.html +61 -0
- package/client/opcua-client-utils.js +68 -2
- package/client/opcua-client.html +13 -1165
- package/client/opcua-client.js +108 -53
- package/client/testClient.json +18 -0
- package/client/view/opcua-client.css +411 -0
- package/client/view/opcua-client.js +1141 -0
- package/icons/opcua.svg +132 -0
- package/icons/opcua2.svg +132 -0
- package/package.json +3 -3
- package/server/icons/opcua.svg +132 -0
- package/server/lib/opcua-address-space-builder.js +24 -2
- package/server/lib/opcua-server-runtime-child.js +117 -26
- package/server/opcua-server-io.html +1 -1
- package/server/opcua-server-io.js +31 -2
- package/server/opcua-server.html +49 -19
- package/server/opcua-server.js +7 -3
- package/object.json +0 -65
package/client/opcua-client.html
CHANGED
|
@@ -1,1162 +1,6 @@
|
|
|
1
|
-
<style>
|
|
2
|
-
body.opcua-tree-modal-open {
|
|
3
|
-
overflow: hidden;
|
|
4
|
-
}
|
|
1
|
+
<link rel="stylesheet" type="text/css" href="opcua-client-resource/style.css">
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
width: 100%;
|
|
8
|
-
min-height: 120px;
|
|
9
|
-
border: 1px solid var(--red-ui-form-input-border-color, #d9d9d9);
|
|
10
|
-
background: var(--red-ui-form-input-background, #fff);
|
|
11
|
-
border-radius: 4px;
|
|
12
|
-
overflow: auto;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.opcua-tree-row {
|
|
16
|
-
display: flex;
|
|
17
|
-
align-items: center;
|
|
18
|
-
gap: 5px;
|
|
19
|
-
min-height: 24px;
|
|
20
|
-
padding: 2px 6px;
|
|
21
|
-
font-size: 12px;
|
|
22
|
-
border-bottom: 1px solid transparent;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.opcua-tree-row:hover {
|
|
26
|
-
background: var(--red-ui-list-item-background-hover, #f3f7fd);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.opcua-tree-row.is-selected {
|
|
30
|
-
background: var(--red-ui-list-item-background-selected, #d9ecff);
|
|
31
|
-
color: var(--red-ui-list-item-color-selected, inherit);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.opcua-tree-indent { flex: 0 0 auto; width: 14px; }
|
|
35
|
-
.opcua-tree-twisty { width: 16px; text-align: center; color: #777; flex: 0 0 16px; cursor: pointer; }
|
|
36
|
-
.opcua-tree-icon { width: 14px; text-align: center; color: #777; flex: 0 0 14px; }
|
|
37
|
-
.opcua-tree-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
38
|
-
.opcua-tree-type { color: #777; font-size: 11px; flex: 0 0 auto; }
|
|
39
|
-
|
|
40
|
-
.opcua-tree-title {
|
|
41
|
-
min-width: 100px;
|
|
42
|
-
font-weight: 600;
|
|
43
|
-
color: #444;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.opcua-client-nodeid-label {
|
|
47
|
-
margin-left: auto;
|
|
48
|
-
text-align: right;
|
|
49
|
-
word-break: break-all;
|
|
50
|
-
max-width: 320px;
|
|
51
|
-
color: #777;
|
|
52
|
-
font-size: 12px;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.opcua-tree-actions {
|
|
56
|
-
margin-left: auto;
|
|
57
|
-
display: flex;
|
|
58
|
-
gap: 6px;
|
|
59
|
-
align-items: center;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.opcua-tree-empty {
|
|
63
|
-
padding: 14px;
|
|
64
|
-
border: 1px dashed #d9d9d9;
|
|
65
|
-
border-radius: 4px;
|
|
66
|
-
background: #fafafa;
|
|
67
|
-
color: #777;
|
|
68
|
-
text-align: center;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.opcua-client-json {
|
|
72
|
-
width: 100%;
|
|
73
|
-
box-sizing: border-box;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.opcua-client-tag-box {
|
|
77
|
-
width: 100%;
|
|
78
|
-
min-height: 70px;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.opcua-client-tag-chip {
|
|
82
|
-
display: grid;
|
|
83
|
-
grid-template-columns: 18px 1fr auto;
|
|
84
|
-
grid-template-areas:
|
|
85
|
-
"icon name name"
|
|
86
|
-
"icon write right";
|
|
87
|
-
column-gap: 8px;
|
|
88
|
-
row-gap: 6px;
|
|
89
|
-
align-items: center;
|
|
90
|
-
border: 1px solid #d9d9d9;
|
|
91
|
-
border-radius: 4px;
|
|
92
|
-
background: #fafafa;
|
|
93
|
-
padding: 6px 8px;
|
|
94
|
-
margin-bottom: 6px;
|
|
95
|
-
}
|
|
96
|
-
.opcua-client-tag-chip .opcua-tree-icon { grid-area: icon; }
|
|
97
|
-
.opcua-client-tag-chip .opcua-tree-title { grid-area: name; min-width: 0; }
|
|
98
|
-
.opcua-client-tag-right {
|
|
99
|
-
grid-area: right;
|
|
100
|
-
display: flex;
|
|
101
|
-
align-items: center;
|
|
102
|
-
gap: 8px;
|
|
103
|
-
}
|
|
104
|
-
.opcua-client-tag-right .opcua-client-nodeid-label {
|
|
105
|
-
margin: 0;
|
|
106
|
-
max-width: 320px;
|
|
107
|
-
}
|
|
108
|
-
.opcua-client-tag-right .opcua-tree-actions {
|
|
109
|
-
margin: 0;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.opcua-client-tag-chip-meta,
|
|
113
|
-
.opcua-client-description {
|
|
114
|
-
color: #777;
|
|
115
|
-
font-size: 12px;
|
|
116
|
-
}
|
|
117
|
-
.opcua-client-tag-write {
|
|
118
|
-
display: flex;
|
|
119
|
-
gap: 8px;
|
|
120
|
-
align-items: center;
|
|
121
|
-
width: 100%;
|
|
122
|
-
margin-top: 0;
|
|
123
|
-
margin-left: 0;
|
|
124
|
-
grid-area: write;
|
|
125
|
-
}
|
|
126
|
-
.opcua-client-tag-write input {
|
|
127
|
-
font-size: 12px;
|
|
128
|
-
}
|
|
129
|
-
.opcua-client-tag-write input {
|
|
130
|
-
flex: 1 1 auto;
|
|
131
|
-
min-width: 120px;
|
|
132
|
-
}
|
|
133
|
-
.opcua-client-tag-write .red-ui-typedInput-container {
|
|
134
|
-
flex: 1 1 auto;
|
|
135
|
-
min-width: 220px;
|
|
136
|
-
width: 100% !important;
|
|
137
|
-
}
|
|
138
|
-
.opcua-client-tag-write .red-ui-typedInput-container input.red-ui-typedInput-input {
|
|
139
|
-
width: calc(100% - 92px);
|
|
140
|
-
min-width: 120px;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.opcua-client-load-row {
|
|
144
|
-
display: flex;
|
|
145
|
-
gap: 8px;
|
|
146
|
-
align-items: center;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.opcua-client-load-row input {
|
|
150
|
-
flex: 1 1 auto;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.opcua-tree-search {
|
|
154
|
-
display: flex;
|
|
155
|
-
align-items: center;
|
|
156
|
-
gap: 8px;
|
|
157
|
-
width: 100%;
|
|
158
|
-
padding: 6px 10px;
|
|
159
|
-
border: 1px solid #d9d9d9;
|
|
160
|
-
border-radius: 6px;
|
|
161
|
-
background: #fafafa;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.opcua-tree-search input {
|
|
165
|
-
flex: 1 1 auto;
|
|
166
|
-
min-width: 120px;
|
|
167
|
-
border: 0;
|
|
168
|
-
outline: 0;
|
|
169
|
-
background: transparent;
|
|
170
|
-
box-shadow: none;
|
|
171
|
-
margin: 0;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.opcua-tree-modal {
|
|
175
|
-
position: fixed;
|
|
176
|
-
inset: 0;
|
|
177
|
-
z-index: 2000;
|
|
178
|
-
background: rgba(0, 0, 0, 0.45);
|
|
179
|
-
padding: 24px;
|
|
180
|
-
box-sizing: border-box;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.opcua-tree-modal__dialog {
|
|
184
|
-
display: flex;
|
|
185
|
-
flex-direction: column;
|
|
186
|
-
width: 100%;
|
|
187
|
-
height: 100%;
|
|
188
|
-
background: #f3f3f3;
|
|
189
|
-
border-radius: 8px;
|
|
190
|
-
overflow: hidden;
|
|
191
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.opcua-tree-modal__header,
|
|
195
|
-
.opcua-tree-modal__toolbar {
|
|
196
|
-
display: flex;
|
|
197
|
-
align-items: center;
|
|
198
|
-
flex-wrap: wrap;
|
|
199
|
-
gap: 10px;
|
|
200
|
-
padding: 14px 18px;
|
|
201
|
-
background: #fff;
|
|
202
|
-
border-bottom: 1px solid #d9d9d9;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.opcua-tree-modal__title {
|
|
206
|
-
font-size: 18px;
|
|
207
|
-
font-weight: 600;
|
|
208
|
-
color: #333;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.opcua-tree-modal__header .editor-button-small {
|
|
212
|
-
margin-left: auto;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.opcua-tree-modal__body {
|
|
216
|
-
flex: 1 1 auto;
|
|
217
|
-
overflow: auto;
|
|
218
|
-
padding: 18px;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.opcua-tree-context-menu {
|
|
222
|
-
position: fixed;
|
|
223
|
-
z-index: 2200;
|
|
224
|
-
min-width: 170px;
|
|
225
|
-
background: #fff;
|
|
226
|
-
border: 1px solid #d9d9d9;
|
|
227
|
-
border-radius: 6px;
|
|
228
|
-
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
|
|
229
|
-
padding: 6px 0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.opcua-tree-context-menu a {
|
|
233
|
-
display: block;
|
|
234
|
-
padding: 7px 12px;
|
|
235
|
-
color: #333;
|
|
236
|
-
text-decoration: none;
|
|
237
|
-
font-size: 12px;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.opcua-tree-context-menu a:hover {
|
|
241
|
-
background: #f3f7fd;
|
|
242
|
-
}
|
|
243
|
-
</style>
|
|
244
|
-
|
|
245
|
-
<script type="text/javascript">
|
|
246
|
-
(function () {
|
|
247
|
-
var selectedItemsState = [];
|
|
248
|
-
var selectedNodeIdSet = {}; // fast O(1) lookup: nodeId → index
|
|
249
|
-
var browseState = null;
|
|
250
|
-
var expansionState = {};
|
|
251
|
-
var browseSearchValue = "";
|
|
252
|
-
var browseSearchTerm = "";
|
|
253
|
-
var contextMenuPath = "";
|
|
254
|
-
var browseSelectedPath = "";
|
|
255
|
-
var renderPending = false; // debounce flag for renderBrowseTree
|
|
256
|
-
|
|
257
|
-
function debounce(fn, delay) {
|
|
258
|
-
var timer;
|
|
259
|
-
return function () {
|
|
260
|
-
var ctx = this, args = arguments;
|
|
261
|
-
clearTimeout(timer);
|
|
262
|
-
timer = setTimeout(function () { fn.apply(ctx, args); }, delay);
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function rebuildNodeIdIndex() {
|
|
267
|
-
selectedNodeIdSet = {};
|
|
268
|
-
selectedItemsState.forEach(function (item, i) {
|
|
269
|
-
if (item.nodeID) selectedNodeIdSet[item.nodeID] = i;
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function openBrowseModal() { $("#node-input-browse-modal").show(); $("body").addClass("opcua-tree-modal-open"); }
|
|
274
|
-
function closeBrowseModal() {
|
|
275
|
-
hideTreeContextMenu();
|
|
276
|
-
$("#node-input-browse-modal").hide();
|
|
277
|
-
$("body").removeClass("opcua-tree-modal-open");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function getBrowseCacheKey() {
|
|
281
|
-
var connectionId = $("#node-input-connection").val() || "";
|
|
282
|
-
if (!connectionId) return "";
|
|
283
|
-
return "opcua-client-browse-cache:" + connectionId;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function saveBrowseSession() {
|
|
287
|
-
var key = getBrowseCacheKey();
|
|
288
|
-
if (!key || !window.sessionStorage) return;
|
|
289
|
-
try {
|
|
290
|
-
sessionStorage.setItem(key, JSON.stringify({
|
|
291
|
-
browseState: browseState,
|
|
292
|
-
expansionState: expansionState
|
|
293
|
-
}));
|
|
294
|
-
} catch (error) {}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function loadBrowseSession() {
|
|
298
|
-
var key = getBrowseCacheKey();
|
|
299
|
-
if (!key || !window.sessionStorage) return false;
|
|
300
|
-
try {
|
|
301
|
-
var raw = sessionStorage.getItem(key);
|
|
302
|
-
if (!raw) return false;
|
|
303
|
-
var parsed = JSON.parse(raw);
|
|
304
|
-
if (!parsed || typeof parsed !== "object") return false;
|
|
305
|
-
browseState = parsed.browseState && typeof parsed.browseState === "object" ? parsed.browseState : null;
|
|
306
|
-
expansionState = parsed.expansionState && typeof parsed.expansionState === "object" ? parsed.expansionState : {};
|
|
307
|
-
return !!browseState;
|
|
308
|
-
} catch (error) {
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function parseSelectedItems(rawValue) {
|
|
314
|
-
if (!rawValue) {
|
|
315
|
-
return [];
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
var parsed = JSON.parse(rawValue);
|
|
320
|
-
if (!Array.isArray(parsed)) {
|
|
321
|
-
return [];
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return parsed
|
|
325
|
-
.filter(function (item) {
|
|
326
|
-
return item && typeof item === "object" && !Array.isArray(item);
|
|
327
|
-
})
|
|
328
|
-
.map(function (item) {
|
|
329
|
-
return {
|
|
330
|
-
name: typeof item.name === "string" ? item.name.trim() : "",
|
|
331
|
-
nodeID: typeof (item.nodeID || item.nodeId) === "string" ? String(item.nodeID || item.nodeId).trim() : "",
|
|
332
|
-
type: typeof (item.type || item.dataType) === "string" ? String(item.type || item.dataType).trim() : "",
|
|
333
|
-
nodeClass: typeof item.nodeClass === "string" ? item.nodeClass.trim() : "",
|
|
334
|
-
typeDefinition: typeof (item.typeDefinition || item.typeDefinitionName) === "string" ? String(item.typeDefinition || item.typeDefinitionName).trim() : "",
|
|
335
|
-
hasTypeDefinition: item.hasTypeDefinition && typeof item.hasTypeDefinition === "object" ? item.hasTypeDefinition : null,
|
|
336
|
-
valueProperty: typeof item.valueProperty === "string" && item.valueProperty.trim() ? item.valueProperty.trim() : "payload",
|
|
337
|
-
valuePropertyType: (item.valuePropertyType === "msg" || item.valuePropertyType === "flow" || item.valuePropertyType === "global") ? item.valuePropertyType : "msg"
|
|
338
|
-
};
|
|
339
|
-
})
|
|
340
|
-
.filter(function (item) {
|
|
341
|
-
return !!item.nodeID;
|
|
342
|
-
});
|
|
343
|
-
} catch (error) {
|
|
344
|
-
return [];
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function serializeSelectedItems(items) {
|
|
349
|
-
return JSON.stringify(items.map(function (item) {
|
|
350
|
-
var result = {
|
|
351
|
-
name: item.name || item.nodeID,
|
|
352
|
-
nodeID: item.nodeID
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
if (item.type) {
|
|
356
|
-
result.type = item.type;
|
|
357
|
-
}
|
|
358
|
-
if (item.nodeClass) {
|
|
359
|
-
result.nodeClass = item.nodeClass;
|
|
360
|
-
}
|
|
361
|
-
if (item.typeDefinition) {
|
|
362
|
-
result.typeDefinition = item.typeDefinition;
|
|
363
|
-
}
|
|
364
|
-
if (item.hasTypeDefinition) {
|
|
365
|
-
result.hasTypeDefinition = item.hasTypeDefinition;
|
|
366
|
-
}
|
|
367
|
-
if (item.valueProperty) {
|
|
368
|
-
result.valueProperty = item.valueProperty;
|
|
369
|
-
}
|
|
370
|
-
if (item.valuePropertyType) {
|
|
371
|
-
result.valuePropertyType = item.valuePropertyType;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return result;
|
|
375
|
-
}), null, 2);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function escapeHtml(value) {
|
|
379
|
-
return String(value || "").replace(/[&<>"']/g, function (char) {
|
|
380
|
-
return {
|
|
381
|
-
"&": "&",
|
|
382
|
-
"<": "<",
|
|
383
|
-
">": ">",
|
|
384
|
-
'"': """,
|
|
385
|
-
"'": "'"
|
|
386
|
-
}[char];
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function updateSelectedItemsField() {
|
|
391
|
-
$("#node-input-selectedItems").val(serializeSelectedItems(selectedItemsState));
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function normalizeSearchTerm(value) {
|
|
395
|
-
return String(value || "").trim().toLowerCase();
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function textForSearch(item) {
|
|
399
|
-
if (!item || typeof item !== "object") return "";
|
|
400
|
-
return [
|
|
401
|
-
item.name,
|
|
402
|
-
item.displayName,
|
|
403
|
-
item.browseName,
|
|
404
|
-
item.nodeID,
|
|
405
|
-
item.nodeClass,
|
|
406
|
-
item.dataType,
|
|
407
|
-
item.description
|
|
408
|
-
].filter(Boolean).join(" ").toLowerCase();
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function nodeMatchesSearch(item, term) {
|
|
412
|
-
if (!term) return true;
|
|
413
|
-
return textForSearch(item).indexOf(term) >= 0;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function branchHasSearchMatch(item, term) {
|
|
417
|
-
if (nodeMatchesSearch(item, term)) return true;
|
|
418
|
-
if (!item || !Array.isArray(item.browse)) return false;
|
|
419
|
-
for (var i = 0; i < item.browse.length; i += 1) {
|
|
420
|
-
if (branchHasSearchMatch(item.browse[i], term)) return true;
|
|
421
|
-
}
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function renderSelectedTags() {
|
|
426
|
-
var container = $("#node-input-selected-tags");
|
|
427
|
-
container.empty();
|
|
428
|
-
var writeMode = $("#node-input-mode").val() === "write";
|
|
429
|
-
|
|
430
|
-
if (!selectedItemsState.length) {
|
|
431
|
-
container.append('<div class="opcua-tree-empty">No tags selected.</div>');
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
selectedItemsState.forEach(function (item, index) {
|
|
436
|
-
var row = $('<div class="opcua-client-tag-chip"></div>');
|
|
437
|
-
var icon = browseIconFor(item);
|
|
438
|
-
row.append('<div class="opcua-tree-icon"><i class="fa ' + icon + '"></i></div>');
|
|
439
|
-
row.append('<div class="opcua-tree-title">' + escapeHtml(item.name || item.nodeID) + '</div>');
|
|
440
|
-
if (writeMode) {
|
|
441
|
-
var type = (item.valuePropertyType === "flow" || item.valuePropertyType === "global") ? item.valuePropertyType : "msg";
|
|
442
|
-
var prop = item.valueProperty || "payload";
|
|
443
|
-
row.append('<div class="opcua-client-tag-write">'
|
|
444
|
-
+ '<input type="text" class="opcua-client-item-value-prop" id="opcua-client-item-value-prop-' + index + '" data-index="' + index + '" value="' + escapeHtml(prop) + '" placeholder="payload">'
|
|
445
|
-
+ '<input type="hidden" class="opcua-client-item-value-type" id="opcua-client-item-value-type-' + index + '" data-index="' + index + '" value="' + escapeHtml(type) + '">'
|
|
446
|
-
+ "</div>");
|
|
447
|
-
}
|
|
448
|
-
row.append('<div class="opcua-client-tag-right">'
|
|
449
|
-
+ '<div class="opcua-client-nodeid-label">' + escapeHtml(item.nodeID) + '</div>'
|
|
450
|
-
+ '<div class="opcua-tree-actions"><a href="#" class="editor-button editor-button-small opcua-client-remove-tag" data-index="' + index + '"><i class="fa fa-trash"></i></a></div>'
|
|
451
|
-
+ "</div>");
|
|
452
|
-
container.append(row);
|
|
453
|
-
});
|
|
454
|
-
initializeSelectedItemTypedInputs();
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function initializeSelectedItemTypedInputs() {
|
|
458
|
-
$(".opcua-client-item-value-prop").each(function () {
|
|
459
|
-
var input = $(this);
|
|
460
|
-
var index = Number(input.attr("data-index"));
|
|
461
|
-
var typeField = "#opcua-client-item-value-type-" + index;
|
|
462
|
-
if (input.data("typedInputInitialized")) {
|
|
463
|
-
input.typedInput("types", ["msg", "flow", "global"]);
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
input.typedInput({
|
|
468
|
-
type: $(typeField).val() || "msg",
|
|
469
|
-
types: ["msg", "flow", "global"],
|
|
470
|
-
typeField: typeField
|
|
471
|
-
});
|
|
472
|
-
input.data("typedInputInitialized", true);
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function syncSelectedItems() {
|
|
477
|
-
rebuildNodeIdIndex();
|
|
478
|
-
updateSelectedItemsField();
|
|
479
|
-
renderSelectedTags();
|
|
480
|
-
renderBrowseTree();
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function isExpanded(path, defaultValue) {
|
|
484
|
-
if (expansionState[path] === undefined) {
|
|
485
|
-
expansionState[path] = !!defaultValue;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return expansionState[path];
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function selectedIndexByNodeId(nodeId) {
|
|
492
|
-
var idx = selectedNodeIdSet[nodeId];
|
|
493
|
-
return (idx !== undefined) ? idx : -1;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function canExpand(item) {
|
|
497
|
-
// return item && item.nodeClass !== "Variable" && item.nodeClass !== "Method";
|
|
498
|
-
return item && item.nodeClass !== "Variable"
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function isVariable(item) {
|
|
502
|
-
return String(item && item.nodeClass || "").toLowerCase() === "variable";
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function nodeIdOf(item) {
|
|
506
|
-
return item && (item.nodeID || item.nodeId) ? String(item.nodeID || item.nodeId) : "";
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function loadBrowse(nodeId) {
|
|
510
|
-
var connectionId = $("#node-input-connection").val();
|
|
511
|
-
if (!connectionId) {
|
|
512
|
-
RED.notify("Select an OPC UA connection before browsing.", "warning");
|
|
513
|
-
return $.Deferred().reject().promise();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return $.getJSON("opcua-client-config/" + encodeURIComponent(connectionId) + "/browse", {
|
|
517
|
-
nodeId: nodeId || "i=84"
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function renderBrowseTree() {
|
|
522
|
-
if (renderPending) return;
|
|
523
|
-
renderPending = true;
|
|
524
|
-
setTimeout(function () {
|
|
525
|
-
renderPending = false;
|
|
526
|
-
_doRenderBrowseTree();
|
|
527
|
-
}, 0);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
function _doRenderBrowseTree() {
|
|
531
|
-
var container = $("#node-input-browse-tree");
|
|
532
|
-
var frag = document.createDocumentFragment();
|
|
533
|
-
|
|
534
|
-
if (!browseState) {
|
|
535
|
-
var empty = document.createElement("div");
|
|
536
|
-
empty.className = "opcua-tree-empty";
|
|
537
|
-
empty.textContent = "Click Browse to load the server tree.";
|
|
538
|
-
frag.appendChild(empty);
|
|
539
|
-
container[0].innerHTML = "";
|
|
540
|
-
container[0].appendChild(frag);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (browseSearchTerm) {
|
|
545
|
-
if (!branchHasSearchMatch(browseState, browseSearchTerm)) {
|
|
546
|
-
var noMatch = document.createElement("div");
|
|
547
|
-
noMatch.className = "opcua-tree-empty";
|
|
548
|
-
noMatch.textContent = "No items found in the already explored items.";
|
|
549
|
-
frag.appendChild(noMatch);
|
|
550
|
-
container[0].innerHTML = "";
|
|
551
|
-
container[0].appendChild(frag);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
renderBrowseRootFiltered(browseState, "root", 0, frag, browseSearchTerm);
|
|
555
|
-
container[0].innerHTML = "";
|
|
556
|
-
container[0].appendChild(frag);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
renderBrowseRoot(browseState, "root", 0, frag);
|
|
561
|
-
container[0].innerHTML = "";
|
|
562
|
-
container[0].appendChild(frag);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function isFolderNode(item) {
|
|
566
|
-
if (!item) return false;
|
|
567
|
-
var nodeClass = String(item.nodeClass || "");
|
|
568
|
-
var typeDefinition = String(item.typeDefinition || item.typeDefinitionName || "").toLowerCase();
|
|
569
|
-
var hasTypeDefinitionBrowseName = String(
|
|
570
|
-
item.hasTypeDefinition && item.hasTypeDefinition.browseName || ""
|
|
571
|
-
).toLowerCase();
|
|
572
|
-
var explicitType = String(item.type || item.kind || "").toLowerCase();
|
|
573
|
-
if (nodeClass === "Folder") return true;
|
|
574
|
-
if (hasTypeDefinitionBrowseName === "foldertype") return true;
|
|
575
|
-
if (typeDefinition.indexOf("folder") >= 0) return true;
|
|
576
|
-
if (explicitType === "folder") return true;
|
|
577
|
-
return false;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function browseIconFor(item) {
|
|
581
|
-
var nodeClass = String((item && item.nodeClass) || "");
|
|
582
|
-
if (isFolderNode(item)) return "fa-folder";
|
|
583
|
-
if (nodeClass === "Object") return "fa-cube";
|
|
584
|
-
if (nodeClass === "Method") return "fa-cog";
|
|
585
|
-
if (nodeClass === "Variable") return "fa-tag";
|
|
586
|
-
if (nodeClass === "ObjectType") return "fa-cubes";
|
|
587
|
-
if (nodeClass === "View") return "fa-eye";
|
|
588
|
-
if (nodeClass === "DataType") return "fa-database";
|
|
589
|
-
if (nodeClass === "ReferenceType") return "fa-random";
|
|
590
|
-
return "fa-tag";
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function makeEl(tag, className, html) {
|
|
594
|
-
var el = document.createElement(tag);
|
|
595
|
-
if (className) el.className = className;
|
|
596
|
-
if (html !== undefined) el.innerHTML = html;
|
|
597
|
-
return el;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function makeTreeRow(path, extraClass) {
|
|
601
|
-
var row = document.createElement("div");
|
|
602
|
-
row.className = "opcua-tree-row" + (extraClass ? " " + extraClass : "");
|
|
603
|
-
row.setAttribute("data-path", path);
|
|
604
|
-
return row;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function renderBrowseRoot(root, path, depth, frag) {
|
|
608
|
-
var expanded = isExpanded(path, true);
|
|
609
|
-
var row = makeTreeRow(path);
|
|
610
|
-
row.innerHTML = '<span class="opcua-tree-indent"></span>'
|
|
611
|
-
+ '<span class="opcua-tree-twisty opcua-client-toggle-tree" data-path="' + escapeHtml(path) + '">'
|
|
612
|
-
+ (expanded ? '<i class="fa fa-caret-down"></i>' : '<i class="fa fa-caret-right"></i>') + '</span>'
|
|
613
|
-
+ '<span class="opcua-tree-icon"><i class="fa fa-sitemap"></i></span>'
|
|
614
|
-
+ '<span class="opcua-tree-label">' + escapeHtml(root.name || root.nodeID || "RootFolder") + '</span>'
|
|
615
|
-
+ '<span class="opcua-tree-type">' + escapeHtml(root.nodeID || "") + '</span>';
|
|
616
|
-
frag.appendChild(row);
|
|
617
|
-
|
|
618
|
-
if (!expanded) return;
|
|
619
|
-
|
|
620
|
-
if (!Array.isArray(root.browse) || !root.browse.length) {
|
|
621
|
-
frag.appendChild(makeEl("div", "opcua-tree-empty", "No items found.."));
|
|
622
|
-
} else {
|
|
623
|
-
root.browse.forEach(function (item, index) {
|
|
624
|
-
renderBrowseItem(item, path + ".browse." + index, depth + 1, frag);
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function renderBrowseItem(item, path, depth, frag) {
|
|
630
|
-
var expanded = isExpanded(path, false);
|
|
631
|
-
var nodeId = nodeIdOf(item);
|
|
632
|
-
var selectedIndex = selectedIndexByNodeId(nodeId);
|
|
633
|
-
var hasChildren = canExpand(item);
|
|
634
|
-
var row = makeTreeRow(path, selectedIndex >= 0 ? "is-selected" : "");
|
|
635
|
-
|
|
636
|
-
var indents = "";
|
|
637
|
-
for (var i = 0; i < depth; i += 1) indents += '<span class="opcua-tree-indent"></span>';
|
|
638
|
-
|
|
639
|
-
var twisty = '<span class="opcua-tree-twisty' + (hasChildren ? ' opcua-client-toggle-tree' : '') + '" data-path="' + escapeHtml(path) + '">'
|
|
640
|
-
+ (hasChildren ? '<i class="fa ' + (expanded ? 'fa-caret-down' : 'fa-caret-right') + '"></i>' : '') + '</span>';
|
|
641
|
-
|
|
642
|
-
var actions = nodeId
|
|
643
|
-
? '<div class="opcua-tree-actions"><a href="#" class="editor-button editor-button-small opcua-client-toggle-tag" data-nodeid="' + escapeHtml(nodeId) + '" data-path="' + escapeHtml(path) + '"><i class="fa ' + (selectedIndex >= 0 ? 'fa-minus' : 'fa-plus') + '"></i> ' + (selectedIndex >= 0 ? 'Remove' : 'Add') + '</a></div>'
|
|
644
|
-
: '';
|
|
645
|
-
|
|
646
|
-
row.innerHTML = indents + twisty
|
|
647
|
-
+ '<span class="opcua-tree-icon"><i class="fa ' + browseIconFor(item) + '"></i></span>'
|
|
648
|
-
+ '<span class="opcua-tree-label">' + escapeHtml(item.displayName || item.browseName || item.nodeID) + '</span>'
|
|
649
|
-
+ '<span class="opcua-tree-type">' + escapeHtml(item.nodeClass || "") + (item.dataType ? " | " + escapeHtml(item.dataType) : "") + '</span>'
|
|
650
|
-
+ '<span class="opcua-client-nodeid-label">' + escapeHtml(nodeId) + '</span>'
|
|
651
|
-
+ actions;
|
|
652
|
-
frag.appendChild(row);
|
|
653
|
-
|
|
654
|
-
if (item.description) {
|
|
655
|
-
var desc = makeEl("div", "opcua-client-description");
|
|
656
|
-
desc.style.padding = "0 10px 8px " + String((depth + 2) * 14) + "px";
|
|
657
|
-
desc.textContent = item.description;
|
|
658
|
-
frag.appendChild(desc);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
if (expanded && hasChildren) {
|
|
662
|
-
if (Array.isArray(item.browse)) {
|
|
663
|
-
if (!item.browse.length) {
|
|
664
|
-
frag.appendChild(makeEl("div", "opcua-tree-empty", "No children found.."));
|
|
665
|
-
} else {
|
|
666
|
-
item.browse.forEach(function (child, index) {
|
|
667
|
-
renderBrowseItem(child, path + ".browse." + index, depth + 1, frag);
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
} else {
|
|
671
|
-
frag.appendChild(makeEl("div", "opcua-tree-empty", "Searching for items..."));
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
function renderBrowseRootFiltered(root, path, depth, frag, term) {
|
|
677
|
-
var row = makeTreeRow(path);
|
|
678
|
-
row.innerHTML = '<span class="opcua-tree-indent"></span>'
|
|
679
|
-
+ '<span class="opcua-tree-twisty"><i class="fa fa-caret-down"></i></span>'
|
|
680
|
-
+ '<span class="opcua-tree-icon"><i class="fa fa-sitemap"></i></span>'
|
|
681
|
-
+ '<span class="opcua-tree-label">' + escapeHtml(root.name || root.nodeID || "RootFolder") + '</span>'
|
|
682
|
-
+ '<span class="opcua-tree-type">' + escapeHtml(root.nodeID || "") + '</span>';
|
|
683
|
-
frag.appendChild(row);
|
|
684
|
-
|
|
685
|
-
if (!Array.isArray(root.browse) || !root.browse.length) {
|
|
686
|
-
frag.appendChild(makeEl("div", "opcua-tree-empty", "Nenhum item encontrado."));
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
root.browse.forEach(function (item, index) {
|
|
691
|
-
if (branchHasSearchMatch(item, term)) {
|
|
692
|
-
renderBrowseItemFiltered(item, path + ".browse." + index, depth + 1, frag, term, false);
|
|
693
|
-
}
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function renderBrowseItemFiltered(item, path, depth, frag, term, ancestorMatched) {
|
|
698
|
-
if (!branchHasSearchMatch(item, term)) return;
|
|
699
|
-
|
|
700
|
-
var nodeId = nodeIdOf(item);
|
|
701
|
-
var selectedIndex = selectedIndexByNodeId(nodeId);
|
|
702
|
-
var hasChildren = canExpand(item);
|
|
703
|
-
var subtreeVisible = !!ancestorMatched || nodeMatchesSearch(item, term);
|
|
704
|
-
var hasMatchingLoadedChild = hasChildren && Array.isArray(item.browse) && item.browse.some(function (child) {
|
|
705
|
-
return branchHasSearchMatch(child, term);
|
|
706
|
-
});
|
|
707
|
-
var hasExplicitExpansion = expansionState[path] !== undefined;
|
|
708
|
-
var expanded = hasChildren && (hasExplicitExpansion
|
|
709
|
-
? !!expansionState[path]
|
|
710
|
-
: ((subtreeVisible && Array.isArray(item.browse)) || hasMatchingLoadedChild));
|
|
711
|
-
|
|
712
|
-
var row = makeTreeRow(path, selectedIndex >= 0 ? "is-selected" : "");
|
|
713
|
-
|
|
714
|
-
var indents = "";
|
|
715
|
-
for (var i = 0; i < depth; i += 1) indents += '<span class="opcua-tree-indent"></span>';
|
|
716
|
-
|
|
717
|
-
var twisty = '<span class="opcua-tree-twisty' + (hasChildren ? ' opcua-client-toggle-tree' : '') + '" data-path="' + escapeHtml(path) + '">'
|
|
718
|
-
+ (hasChildren ? '<i class="fa ' + (expanded ? 'fa-caret-down' : 'fa-caret-right') + '"></i>' : '') + '</span>';
|
|
719
|
-
|
|
720
|
-
var actions = nodeId
|
|
721
|
-
? '<div class="opcua-tree-actions"><a href="#" class="editor-button editor-button-small opcua-client-toggle-tag" data-nodeid="' + escapeHtml(nodeId) + '" data-path="' + escapeHtml(path) + '"><i class="fa ' + (selectedIndex >= 0 ? 'fa-minus' : 'fa-plus') + '"></i> ' + (selectedIndex >= 0 ? 'Remove' : 'Add') + '</a></div>'
|
|
722
|
-
: '';
|
|
723
|
-
|
|
724
|
-
row.innerHTML = indents + twisty
|
|
725
|
-
+ '<span class="opcua-tree-icon"><i class="fa ' + browseIconFor(item) + '"></i></span>'
|
|
726
|
-
+ '<span class="opcua-tree-label">' + escapeHtml(item.displayName || item.browseName || item.nodeID) + '</span>'
|
|
727
|
-
+ '<span class="opcua-tree-type">' + escapeHtml(item.nodeClass || "") + (item.dataType ? " | " + escapeHtml(item.dataType) : "") + '</span>'
|
|
728
|
-
+ '<span class="opcua-client-nodeid-label">' + escapeHtml(nodeId) + '</span>'
|
|
729
|
-
+ actions;
|
|
730
|
-
frag.appendChild(row);
|
|
731
|
-
|
|
732
|
-
if (item.description) {
|
|
733
|
-
var desc = makeEl("div", "opcua-client-description");
|
|
734
|
-
desc.style.padding = "0 10px 8px " + String((depth + 2) * 14) + "px";
|
|
735
|
-
desc.textContent = item.description;
|
|
736
|
-
frag.appendChild(desc);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
if (expanded && hasChildren) {
|
|
740
|
-
if (Array.isArray(item.browse)) {
|
|
741
|
-
if (!item.browse.length) {
|
|
742
|
-
frag.appendChild(makeEl("div", "opcua-tree-empty", "Nenhum filho encontrado."));
|
|
743
|
-
} else {
|
|
744
|
-
item.browse.forEach(function (child, index) {
|
|
745
|
-
if (subtreeVisible || branchHasSearchMatch(child, term)) {
|
|
746
|
-
renderBrowseItemFiltered(child, path + ".browse." + index, depth + 1, frag, term, subtreeVisible);
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
} else {
|
|
751
|
-
frag.appendChild(makeEl("div", "opcua-tree-empty", "Expandindo..."));
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
function getItemAtPath(path) {
|
|
757
|
-
var tokens = String(path || "").split(".");
|
|
758
|
-
var current = browseState;
|
|
759
|
-
|
|
760
|
-
for (var index = 0; index < tokens.length; index += 1) {
|
|
761
|
-
var token = tokens[index];
|
|
762
|
-
if (!token || token === "root") {
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (!current) {
|
|
767
|
-
return null;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
if (/^\d+$/.test(token)) {
|
|
771
|
-
current = current[Number(token)];
|
|
772
|
-
} else {
|
|
773
|
-
current = current[token];
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
return current || null;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
function addSelectedItem(item) {
|
|
781
|
-
var normalized = {
|
|
782
|
-
name: item.displayName || item.browseName || item.name || item.nodeID,
|
|
783
|
-
nodeID: item.nodeID || item.nodeId,
|
|
784
|
-
type: item.dataType || item.type || "",
|
|
785
|
-
nodeClass: item.nodeClass || "",
|
|
786
|
-
typeDefinition: item.typeDefinition || item.typeDefinitionName || "",
|
|
787
|
-
hasTypeDefinition: item.hasTypeDefinition || null,
|
|
788
|
-
valueProperty: item.valueProperty || "payload",
|
|
789
|
-
valuePropertyType: item.valuePropertyType || "msg"
|
|
790
|
-
};
|
|
791
|
-
var currentIndex = selectedIndexByNodeId(normalized.nodeID);
|
|
792
|
-
|
|
793
|
-
if (currentIndex >= 0) {
|
|
794
|
-
selectedItemsState[currentIndex] = normalized;
|
|
795
|
-
} else {
|
|
796
|
-
selectedItemsState.push(normalized);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
syncSelectedItems();
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
function removeSelectedItemByIndex(index) {
|
|
803
|
-
selectedItemsState.splice(index, 1);
|
|
804
|
-
syncSelectedItems();
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
function toggleSelectedNode(path) {
|
|
808
|
-
var item = getItemAtPath(path);
|
|
809
|
-
var nodeId = nodeIdOf(item);
|
|
810
|
-
if (!item || !nodeId) {
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
var currentIndex = selectedIndexByNodeId(nodeId);
|
|
815
|
-
if (currentIndex >= 0) {
|
|
816
|
-
selectedItemsState.splice(currentIndex, 1);
|
|
817
|
-
} else {
|
|
818
|
-
addSelectedItem(item);
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
syncSelectedItems();
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
function refreshBrowseRoot() {
|
|
826
|
-
var rootNodeId = "i=84";
|
|
827
|
-
var container = $("#node-input-browse-tree");
|
|
828
|
-
container.html('<div class="opcua-tree-empty">Carregando...</div>');
|
|
829
|
-
|
|
830
|
-
loadBrowse(rootNodeId).done(function (payload) {
|
|
831
|
-
browseState = payload;
|
|
832
|
-
expansionState = { root: true };
|
|
833
|
-
saveBrowseSession();
|
|
834
|
-
renderBrowseTree();
|
|
835
|
-
}).fail(function (xhr) {
|
|
836
|
-
var message = xhr && xhr.responseJSON && xhr.responseJSON.error
|
|
837
|
-
? xhr.responseJSON.error
|
|
838
|
-
: "Falha ao navegar no servidor OPC UA.";
|
|
839
|
-
browseState = null;
|
|
840
|
-
container.html('<div class="opcua-tree-empty">' + escapeHtml(message) + '</div>');
|
|
841
|
-
RED.notify(message, "error");
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
function hideTreeContextMenu() {
|
|
846
|
-
contextMenuPath = "";
|
|
847
|
-
$("#node-input-browse-context-menu").hide();
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function showTreeContextMenu(x, y, path) {
|
|
851
|
-
var menu = $("#node-input-browse-context-menu");
|
|
852
|
-
var item = getItemAtPath(path);
|
|
853
|
-
contextMenuPath = path || "";
|
|
854
|
-
$("#node-input-browse-context-refresh").toggle(!!item && !isVariable(item));
|
|
855
|
-
$("#node-input-browse-context-copy-nodeid").toggle(!!nodeIdOf(item));
|
|
856
|
-
menu.css({ left: x + "px", top: y + "px" }).show();
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
function copyNodeIdFromPath(path) {
|
|
860
|
-
var item = getItemAtPath(path);
|
|
861
|
-
var nodeId = nodeIdOf(item);
|
|
862
|
-
if (!nodeId) {
|
|
863
|
-
RED.notify("NodeID nao encontrado para o item selecionado.", "warning");
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
868
|
-
navigator.clipboard.writeText(nodeId).then(function () {
|
|
869
|
-
RED.notify("NodeID copiado.", "success");
|
|
870
|
-
}).catch(function () {
|
|
871
|
-
RED.notify("Falha ao copiar NodeID.", "error");
|
|
872
|
-
});
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
var input = $("<textarea readonly></textarea>").val(nodeId).css({
|
|
877
|
-
position: "fixed",
|
|
878
|
-
left: "-9999px",
|
|
879
|
-
top: "0"
|
|
880
|
-
});
|
|
881
|
-
$("body").append(input);
|
|
882
|
-
input[0].select();
|
|
883
|
-
try {
|
|
884
|
-
document.execCommand("copy");
|
|
885
|
-
RED.notify("NodeID copiado.", "success");
|
|
886
|
-
} catch (error) {
|
|
887
|
-
RED.notify("Falha ao copiar NodeID.", "error");
|
|
888
|
-
}
|
|
889
|
-
input.remove();
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
function setBrowseSelectedPath(path) {
|
|
893
|
-
browseSelectedPath = path || "";
|
|
894
|
-
$(".opcua-tree-row").removeClass("is-selected");
|
|
895
|
-
if (browseSelectedPath) {
|
|
896
|
-
$('.opcua-tree-row[data-path="' + browseSelectedPath + '"]').addClass("is-selected");
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
function refreshNode(path) {
|
|
901
|
-
if (!path) {
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
if (path === "root") {
|
|
905
|
-
refreshBrowseRoot();
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
var item = getItemAtPath(path);
|
|
910
|
-
if (!item || isVariable(item)) {
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// Force a new discovery using the same lazy-load flow from expand.
|
|
915
|
-
item.browse = undefined;
|
|
916
|
-
expansionState[path] = true;
|
|
917
|
-
saveBrowseSession();
|
|
918
|
-
renderBrowseTree();
|
|
919
|
-
expandNode(path);
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
function expandNode(path) {
|
|
923
|
-
var item = getItemAtPath(path);
|
|
924
|
-
if (!item || !canExpand(item)) {
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
if (isExpanded(path, false) && !Array.isArray(item.browse)) {
|
|
929
|
-
renderBrowseTree();
|
|
930
|
-
} else {
|
|
931
|
-
expansionState[path] = !isExpanded(path, false);
|
|
932
|
-
saveBrowseSession();
|
|
933
|
-
if (!expansionState[path]) {
|
|
934
|
-
renderBrowseTree();
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
if (Array.isArray(item.browse)) {
|
|
940
|
-
renderBrowseTree();
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
renderBrowseTree();
|
|
945
|
-
var browseNodeId = item.nodeID || item.nodeId;
|
|
946
|
-
loadBrowse(browseNodeId).done(function (payload) {
|
|
947
|
-
item.browse = Array.isArray(payload.browse) ? payload.browse : [];
|
|
948
|
-
saveBrowseSession();
|
|
949
|
-
renderBrowseTree();
|
|
950
|
-
}).fail(function (xhr) {
|
|
951
|
-
expansionState[path] = false;
|
|
952
|
-
saveBrowseSession();
|
|
953
|
-
renderBrowseTree();
|
|
954
|
-
var message = xhr && xhr.responseJSON && xhr.responseJSON.error
|
|
955
|
-
? xhr.responseJSON.error
|
|
956
|
-
: "Falha ao expandir o node.";
|
|
957
|
-
RED.notify(message, "error");
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
function toggleModeFields() {
|
|
962
|
-
var isSubscription = $("#node-input-mode").val() === "subscription" || $("#node-input-mode").val() === "events";
|
|
963
|
-
var supportsSelection = $("#node-input-mode").val() !== "method" && $("#node-input-mode").val() !== "getSubscriptionId" ;
|
|
964
|
-
$(".opcua-client-subscription-row").toggle(isSubscription);
|
|
965
|
-
$(".opcua-client-selection-row").toggle(supportsSelection);
|
|
966
|
-
renderSelectedTags();
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
RED.nodes.registerType("opcua-client", {
|
|
970
|
-
category: "network",
|
|
971
|
-
color: "#d9edf7",
|
|
972
|
-
defaults: {
|
|
973
|
-
name: { value: "" },
|
|
974
|
-
connection: { value: "", type: "opcua-client-config", required: true },
|
|
975
|
-
mode: { value: "read", required: true },
|
|
976
|
-
selectedItems: {
|
|
977
|
-
value: "[]",
|
|
978
|
-
validate: function (value) {
|
|
979
|
-
try {
|
|
980
|
-
return Array.isArray(JSON.parse(value || "[]"));
|
|
981
|
-
} catch (error) {
|
|
982
|
-
return false;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
},
|
|
986
|
-
samplingInterval: {
|
|
987
|
-
value: 250,
|
|
988
|
-
validate: RED.validators.number()
|
|
989
|
-
},
|
|
990
|
-
publishingInterval: {
|
|
991
|
-
value: 250,
|
|
992
|
-
validate: RED.validators.number()
|
|
993
|
-
}
|
|
994
|
-
},
|
|
995
|
-
inputs: 1,
|
|
996
|
-
outputs: 1,
|
|
997
|
-
icon: "bridge.svg",
|
|
998
|
-
label: function () {
|
|
999
|
-
return this.name || "opcua-client";
|
|
1000
|
-
},
|
|
1001
|
-
oneditprepare: function () {
|
|
1002
|
-
$("#node-input-selectedItems").typedInput({
|
|
1003
|
-
type: "json",
|
|
1004
|
-
types: ["json"]
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
selectedItemsState = parseSelectedItems(this.selectedItems);
|
|
1008
|
-
rebuildNodeIdIndex();
|
|
1009
|
-
|
|
1010
|
-
browseState = null;
|
|
1011
|
-
expansionState = {};
|
|
1012
|
-
browseSelectedPath = "";
|
|
1013
|
-
browseSearchValue = "";
|
|
1014
|
-
browseSearchTerm = "";
|
|
1015
|
-
$("#node-input-browse-search").val("");
|
|
1016
|
-
$("#node-input-browse-search-clear").hide();
|
|
1017
|
-
loadBrowseSession();
|
|
1018
|
-
syncSelectedItems();
|
|
1019
|
-
renderBrowseTree();
|
|
1020
|
-
|
|
1021
|
-
$("#node-input-mode").off("change").on("change", toggleModeFields);
|
|
1022
|
-
$("#node-input-browse-root").off("click").on("click", function (event) {
|
|
1023
|
-
event.preventDefault();
|
|
1024
|
-
refreshBrowseRoot();
|
|
1025
|
-
});
|
|
1026
|
-
$("#node-input-open-browse-modal").off("click").on("click", function (event) {
|
|
1027
|
-
event.preventDefault();
|
|
1028
|
-
openBrowseModal();
|
|
1029
|
-
});
|
|
1030
|
-
$("#node-input-close-browse-modal").off("click").on("click", function (event) {
|
|
1031
|
-
event.preventDefault();
|
|
1032
|
-
closeBrowseModal();
|
|
1033
|
-
});
|
|
1034
|
-
$("#node-input-browse-modal").off("click").on("click", function (event) {
|
|
1035
|
-
hideTreeContextMenu();
|
|
1036
|
-
if (event.target === this) closeBrowseModal();
|
|
1037
|
-
});
|
|
1038
|
-
$("#node-input-browse-search").off("input").on("input", debounce(function () {
|
|
1039
|
-
browseSearchValue = $(this).val();
|
|
1040
|
-
browseSearchTerm = normalizeSearchTerm(browseSearchValue);
|
|
1041
|
-
$("#node-input-browse-search-clear").toggle(!!browseSearchTerm);
|
|
1042
|
-
renderBrowseTree();
|
|
1043
|
-
}, 200));
|
|
1044
|
-
$("#node-input-browse-search-clear").off("click").on("click", function (event) {
|
|
1045
|
-
event.preventDefault();
|
|
1046
|
-
browseSearchValue = "";
|
|
1047
|
-
browseSearchTerm = "";
|
|
1048
|
-
$("#node-input-browse-search").val("");
|
|
1049
|
-
$(this).hide();
|
|
1050
|
-
renderBrowseTree();
|
|
1051
|
-
});
|
|
1052
|
-
$("#node-input-connection").off("change.opcuaClientBrowse").on("change.opcuaClientBrowse", function () {
|
|
1053
|
-
browseState = null;
|
|
1054
|
-
expansionState = {};
|
|
1055
|
-
browseSelectedPath = "";
|
|
1056
|
-
loadBrowseSession();
|
|
1057
|
-
renderBrowseTree();
|
|
1058
|
-
});
|
|
1059
|
-
$(document).off("keydown.opcuaClientBrowseModal").on("keydown.opcuaClientBrowseModal", function (event) {
|
|
1060
|
-
hideTreeContextMenu();
|
|
1061
|
-
if (event.key === "Escape") closeBrowseModal();
|
|
1062
|
-
});
|
|
1063
|
-
|
|
1064
|
-
toggleModeFields();
|
|
1065
|
-
},
|
|
1066
|
-
oneditsave: function () {
|
|
1067
|
-
updateSelectedItemsField();
|
|
1068
|
-
saveBrowseSession();
|
|
1069
|
-
closeBrowseModal();
|
|
1070
|
-
$(document).off("keydown.opcuaClientBrowseModal");
|
|
1071
|
-
}
|
|
1072
|
-
,
|
|
1073
|
-
oneditcancel: function () {
|
|
1074
|
-
closeBrowseModal();
|
|
1075
|
-
$(document).off("keydown.opcuaClientBrowseModal");
|
|
1076
|
-
}
|
|
1077
|
-
});
|
|
1078
|
-
|
|
1079
|
-
$(document).on("change", "#node-input-selectedItems", function () {
|
|
1080
|
-
selectedItemsState = parseSelectedItems($(this).val());
|
|
1081
|
-
renderSelectedTags();
|
|
1082
|
-
renderBrowseTree();
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
$(document).on("click", ".opcua-client-remove-tag", function (event) {
|
|
1086
|
-
event.preventDefault();
|
|
1087
|
-
removeSelectedItemByIndex(Number($(this).attr("data-index")));
|
|
1088
|
-
});
|
|
1089
|
-
|
|
1090
|
-
$(document).on("click", ".opcua-client-toggle-tag", function (event) {
|
|
1091
|
-
event.preventDefault();
|
|
1092
|
-
toggleSelectedNode($(this).attr("data-path"));
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
$(document).on("click", ".opcua-client-toggle-tree", function (event) {
|
|
1096
|
-
event.preventDefault();
|
|
1097
|
-
expandNode($(this).attr("data-path"));
|
|
1098
|
-
});
|
|
1099
|
-
$(document).on("change", ".opcua-client-item-value-type", function () {
|
|
1100
|
-
var index = Number($(this).attr("data-index"));
|
|
1101
|
-
if (!selectedItemsState[index]) return;
|
|
1102
|
-
selectedItemsState[index].valuePropertyType = $(this).val();
|
|
1103
|
-
updateSelectedItemsField();
|
|
1104
|
-
});
|
|
1105
|
-
$(document).on("change input", ".opcua-client-item-value-prop", function () {
|
|
1106
|
-
var index = Number($(this).attr("data-index"));
|
|
1107
|
-
if (!selectedItemsState[index]) return;
|
|
1108
|
-
selectedItemsState[index].valueProperty = $(this).typedInput ? $(this).typedInput("value") : $(this).val();
|
|
1109
|
-
var typeField = $("#opcua-client-item-value-type-" + index);
|
|
1110
|
-
selectedItemsState[index].valuePropertyType = (typeField.val() || "msg");
|
|
1111
|
-
updateSelectedItemsField();
|
|
1112
|
-
});
|
|
1113
|
-
|
|
1114
|
-
$(document).on("click", ".opcua-tree-row", function (event) {
|
|
1115
|
-
if ($(event.target).closest(".opcua-client-toggle-tree, .opcua-client-toggle-tag, .opcua-tree-actions, #node-input-browse-context-menu").length) {
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
setBrowseSelectedPath($(this).attr("data-path"));
|
|
1119
|
-
});
|
|
1120
|
-
|
|
1121
|
-
$(document).on("contextmenu", ".opcua-tree-row", function (event) {
|
|
1122
|
-
var clickedPath = $(this).attr("data-path");
|
|
1123
|
-
if (clickedPath) {
|
|
1124
|
-
setBrowseSelectedPath(clickedPath);
|
|
1125
|
-
}
|
|
1126
|
-
var path = browseSelectedPath || clickedPath;
|
|
1127
|
-
var item = getItemAtPath(path);
|
|
1128
|
-
if (!item) {
|
|
1129
|
-
hideTreeContextMenu();
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
event.preventDefault();
|
|
1134
|
-
showTreeContextMenu(event.clientX, event.clientY, path);
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
$(document).on("click", "#node-input-browse-context-refresh", function (event) {
|
|
1138
|
-
event.preventDefault();
|
|
1139
|
-
var highlightedPath = $(".opcua-tree-row.is-selected").first().attr("data-path") || "";
|
|
1140
|
-
var path = contextMenuPath || browseSelectedPath || highlightedPath || "";
|
|
1141
|
-
hideTreeContextMenu();
|
|
1142
|
-
refreshNode(path);
|
|
1143
|
-
});
|
|
1144
|
-
|
|
1145
|
-
$(document).on("click", "#node-input-browse-context-copy-nodeid", function (event) {
|
|
1146
|
-
event.preventDefault();
|
|
1147
|
-
var highlightedPath = $(".opcua-tree-row.is-selected").first().attr("data-path") || "";
|
|
1148
|
-
var path = contextMenuPath || browseSelectedPath || highlightedPath || "";
|
|
1149
|
-
hideTreeContextMenu();
|
|
1150
|
-
copyNodeIdFromPath(path);
|
|
1151
|
-
});
|
|
1152
|
-
|
|
1153
|
-
$(document).on("click", function (event) {
|
|
1154
|
-
if (!$(event.target).closest("#node-input-browse-context-menu").length) {
|
|
1155
|
-
hideTreeContextMenu();
|
|
1156
|
-
}
|
|
1157
|
-
});
|
|
1158
|
-
})();
|
|
1159
|
-
</script>
|
|
3
|
+
<script type="text/javascript" src="opcua-client-resource/script.js"></script>
|
|
1160
4
|
|
|
1161
5
|
<script type="text/html" data-template-name="opcua-client">
|
|
1162
6
|
<div class="form-row">
|
|
@@ -1179,14 +23,11 @@
|
|
|
1179
23
|
<option value="events">Events</option>
|
|
1180
24
|
</select>
|
|
1181
25
|
</div>
|
|
26
|
+
|
|
1182
27
|
<div class="form-row opcua-client-selection-row">
|
|
1183
28
|
<label for="node-input-selectedItems"><i class="fa fa-code"></i> JSON</label>
|
|
1184
29
|
<input type="text" id="node-input-selectedItems" class="opcua-client-json" style="width: 70%;">
|
|
1185
30
|
</div>
|
|
1186
|
-
<div class="form-row opcua-client-selection-row">
|
|
1187
|
-
<label style="width:auto;"><i class="fa fa-sitemap"></i> Browse Tree</label>
|
|
1188
|
-
<a href="#" id="node-input-open-browse-modal" class="editor-button"><i class="fa fa-expand"></i> Open browse tree</a>
|
|
1189
|
-
</div>
|
|
1190
31
|
<div id="node-input-browse-modal" class="opcua-tree-modal" style="display:none;">
|
|
1191
32
|
<div class="opcua-tree-modal__dialog">
|
|
1192
33
|
<div class="opcua-tree-modal__header">
|
|
@@ -1210,6 +51,14 @@
|
|
|
1210
51
|
<a href="#" id="node-input-browse-context-copy-nodeid"><i class="fa fa-copy"></i> Copy NodeID</a>
|
|
1211
52
|
</div>
|
|
1212
53
|
</div>
|
|
54
|
+
|
|
55
|
+
<div class="form-row opcua-client-selection-row">
|
|
56
|
+
<label style="width:auto;"><i class="fa fa-sitemap"></i> Browse Tree</label>
|
|
57
|
+
<a href="#" id="node-input-open-browse-modal" class="editor-button">
|
|
58
|
+
<i class="fa fa-expand"></i> Open browse tree
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
1213
62
|
<div class="form-row opcua-client-subscription-row">
|
|
1214
63
|
<label for="node-input-samplingInterval"><i class="fa fa-clock-o"></i> Sampling</label>
|
|
1215
64
|
<input type="text" id="node-input-samplingInterval" placeholder="250">
|
|
@@ -1218,6 +67,7 @@
|
|
|
1218
67
|
<label for="node-input-publishingInterval"><i class="fa fa-clock-o"></i> Publishing</label>
|
|
1219
68
|
<input type="text" id="node-input-publishingInterval" placeholder="250">
|
|
1220
69
|
</div>
|
|
70
|
+
|
|
1221
71
|
<div class="form-row opcua-client-selection-row" style="margin-bottom:0;">
|
|
1222
72
|
<label style="width:auto;"><i class="fa fa-tags"></i> Selected items</label>
|
|
1223
73
|
</div>
|
|
@@ -1226,8 +76,6 @@
|
|
|
1226
76
|
<div id="node-input-selected-tags" class="opcua-client-tag-box"></div>
|
|
1227
77
|
</div>
|
|
1228
78
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
79
|
</script>
|
|
1232
80
|
|
|
1233
81
|
<script type="text/html" data-help-name="opcua-client">
|
|
@@ -1266,7 +114,7 @@
|
|
|
1266
114
|
<p>If <code>msg.payload = []</code>, the node browses the OPC UA <code>RootFolder</code>.</p>
|
|
1267
115
|
|
|
1268
116
|
<h3>Method</h3>
|
|
1269
|
-
<p><code>
|
|
117
|
+
<p><code>nodeID</code> and <code>objectId</code> are accepted explicitly. For backward compatibility, <code>nodeID</code> is also accepted as the method id.</p>
|
|
1270
118
|
<p><b>Input</b> <code>msg.payload</code>:</p>
|
|
1271
119
|
<pre><code>[
|
|
1272
120
|
{
|