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