node-red-contrib-iiot-opcua
Version:
An Industrial IoT OPC UA toolbox contribution package for Node-RED based on node-opcua.
262 lines (229 loc) • 11.3 kB
HTML
<!--
The BSD 3-Clause License
Copyright 2022 - DATATRONiQ GmbH (https://datatroniq.com)
Copyright (c) 2018-2022 Klaus Landsdorf (http://node-red.plus/)
All rights reserved.
node-red-contrib-iiot-opcua
--> <script type='text/javascript'> RED.nodes.registerType('OPCUA-IIoT-Response', {
category: 'IIoT',
color: '#ABCDEF',
defaults: {
name: {value: ""},
compressStructure: {value: true},
showStatusActivities: {value: false},
showErrors: {value: false},
activateUnsetFilter: {value: false},
activateFilters: {value: false},
negateFilter: {value: false},
filters: {value: []},
},
inputs: 1,
outputs: 1,
align: "left",
icon: "icon.png",
label: function () {
let suffix = ''
if (this.compressStructure) {
suffix += ' ☇'
}
if (this.activateUnsetFilter) {
suffix += ' ø'
}
if (this.activateFilters) {
suffix += (this.negateFilter) ? ' ⊄' : ' ⊂'
}
return (this.name) ? this.name + suffix : "Response" + suffix;
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
let node = this
let cacheItemCount = 0
let tabs = RED.tabs.create({
id: "node-input-response-tabs",
onchange: function (tab) {
$("#node-input-response-tabs-content").children().hide()
$("#" + tab.id).show()
}
})
tabs.addTab({
id: "opcuaiiot-response-tab-settings",
label: this._("opcua-iiot-contrib.tabs-label.settings")
})
tabs.addTab({
id: "opcuaiiot-response-tab-filter",
label: this._("opcua-iiot-contrib.tabs-label.filter")
})
// Filter Management
if(node.filters && node.filters.length > 0) {
cacheItemCount = node.filters.length
node.filters.forEach(function(element, index, array) {
generateFilterEntry(element, index);
})
}
function generateFilterEntry(filter, id) {
let container = $('<li/>', {
style:
'background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;'
})
let row = $('<div class="row" id="row' + id + '" />').appendTo(container)
$('<i style="color: #eee; cursor: move;" class="node-input-addressSpaceItems-handle fa fa-bars"></i>').appendTo(row)
let filterNameField = $('<input/>', {
id: "node-input-response-filter-name" + id,
class: 'opcuaFilterName',
type: "text",
style: "margin-left:5px;width:240px;",
placeholder: 'nodeId'
}).appendTo(row)
let filterValueField = $('<input/>', {
id: "node-input-response-filter-value" + id,
class: 'opcuaFilterValue',
type: "text",
style: "margin: 0 auto;width:45%;min-width:60px;margin-left:5px",
placeholder: 'ns=0*'
}).appendTo(row)
filterNameField.val(filter.name)
filterValueField.val(filter.value)
let finalspan = $('<span/>', {style: 'float: right;margin-right: 10px;'}).appendTo(row)
let lookupItemButton = $('<a/>', {
href: '#',
id: 'node-button-response-filter-types' + id,
class: 'editor-button editor-button-small listButton',
style: 'margin-top: 7px; margin-left: 5px;'
}).appendTo(finalspan)
lookupItemButton.click(function () {
let filterLookupButton = $('#node-button-response-filter-types' + id)
let filterNameLookupField = $('#row' + id + ' #node-input-response-filter-name' + id)
filterLookupButton.addClass('disabled')
$.getJSON('/opcuaIIoT/list/FilterTypes', function (data) {
filterLookupButton.removeClass('disabled')
node.lookupItems = []
$.each(data, function (i, entry) {
node.lookupItems.push({name: entry.name, label: entry.label})
})
filterNameLookupField.autocomplete({
source: node.lookupItems,
minLength: 0,
focus: function (event, ui) {
filterNameLookupField.val(ui.item.name)
return false;
},
select: function (event, ui) {
filterNameLookupField.val(ui.item.name)
return false;
},
close: function (event, ui) {
filterNameLookupField.autocomplete("destroy")
}
}).autocomplete("search", "")
})
})
$('<i/>', {class: 'fa fa-search'}).appendTo(lookupItemButton)
let removeItemButton = $('<a/>', {
href: '#',
id: 'node-button-response-filter-remove' + id,
class: 'editor-button editor-button-small listButton',
style: 'margin-top: 7px; margin-left: 5px;'
}).appendTo(finalspan)
$('<i/>', {class: 'fa fa-remove'}).appendTo(removeItemButton)
removeItemButton.click(function () {
container.fadeOut(300, function () {
$(this).remove()
})
})
$('#node-input-response-filter-container').append(container)
}
$("#node-input-response-filter-container").sortable({
axis: "y",
handle: ".node-input-response-filter-handle",
cursor: "move"
});
$("#node-input-response-filter-container .node-input-response-filter-handle").disableSelection()
$("#node-input-response-filter-add").click(function () {
if(!cacheItemCount || cacheItemCount < 0) {
cacheItemCount = 0
}
generateFilterEntry({ name: '', value: '' }, cacheItemCount++) // length is every time one more than index
let responseContainerDiv = $("#node-input-response-filter-container-div")
responseContainerDiv.scrollTop(responseContainerDiv.get(0).scrollHeight)
})
function switchDialogResize() {
switchFilterDialogResize()
}
// dialog Filter handling
function switchFilterDialogResize() {
let rows = $("#dialog-form>div:not(.node-input-response-filter-container-row)")
let height = $("#dialog-form").height()
rows.each(function (index, row) {
height -= row.outerHeight(true)
})
let editorRow = $("#dialog-form>div.node-input-response-filter-container-row")
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")))
$("#node-input-response-filter-container-div").css("height", height + "px")
}
let dialogHandle = $("#dialog")
dialogHandle.on("dialogresize", switchDialogResize)
dialogHandle.on("dialogopen", function (ev) {
let size = dialogHandle.dialog('option', 'sizeCache-switch')
if (size) {
dialogHandle.dialog('option', 'width', size.width)
dialogHandle.dialog('option', 'height', size.height)
switchDialogResize()
} else {
setTimeout(switchDialogResize, 10)
}
})
dialogHandle.on("dialogclose", function (ev, ui) {
dialogHandle.off("dialogresize", switchDialogResize)
})
},
oneditsave: function () {
let node = this
let cacheFilters = $("#node-input-response-filter-container").children()
node.filters = []
cacheFilters.each(function () {
node.filters.push({
name: $(this).find('.opcuaFilterName').val(),
value: $(this).find('.opcuaFilterValue').val(),
})
})
}
}); </script> <script type='text/x-red' data-template-name='OPCUA-IIoT-Response'>
<div class='form-row'> <ul style='background:#fff;min-width:600px;margin-bottom:20px' id='node-input-response-tabs'>
</ul> </div> <div id='node-input-response-tabs-content' style='min-height:170px'> <div
id='opcuaiiot-response-tab-settings' style='display:none'> <div class='form-row' style='min-width:640px'> <label
for='node-input-name'><i class='fa fa-tag'></i> <span data-i18n='node-red:common.label.name'></span></label> <input
type='text' id='node-input-name' placeholder=''> </div> <div class='form-row'> <label style='min-width:160px'
for='node-input-compressStructure'><i class='fa fa-simplybuilt'></i> <span
data-i18n='opcua-iiot-contrib.label.compressStructure'></span></label> <input type='checkbox'
id='node-input-compressStructure' style='max-width:30px'> </div>
<!-- StatusCodes to add and remove to filter for in a second output --> <hr> <div class='form-row'> <label
style='min-width:160px' for='node-input-showStatusActivities'><i class='fa fa-bolt'></i> <span
data-i18n='opcua-iiot-contrib.label.showActivities'></span></label> <input type='checkbox'
id='node-input-showStatusActivities' style='max-width:30px'> </div> <div class='form-row'> <label
style='min-width:160px' for='node-input-showErrors'><i class='fa fa-exclamation-circle'></i> <span
data-i18n='opcua-iiot-contrib.label.showErrors'></span></label> <input type='checkbox' id='node-input-showErrors'
style='max-width:30px'> </div> </div> <div id='opcuaiiot-response-tab-filter' style='display:none'> <h4><span
data-i18n='opcua-iiot-contrib.label.filters'></span></h4> <div class='form-row'> <label style='min-width:160px'
for='node-input-activateUnsetFilter'><i class='fa fa-filter'></i> <span
data-i18n='opcua-iiot-contrib.label.activateUnsetFilter'></span></label> <input type='checkbox'
id='node-input-activateUnsetFilter' style='max-width:30px'> </div> <div class='form-row'> <label
style='min-width:160px' for='node-input-activateFilters'><i class='fa fa-check-circle'></i> <span
data-i18n='opcua-iiot-contrib.label.activateFilters'></span></label> <input type='checkbox'
id='node-input-activateFilters' style='max-width:30px'> </div> <div class='form-row'> <label style='min-width:160px'
for='node-input-negateFilter'><i class='fa fa-minus-circle'></i> <span
data-i18n='opcua-iiot-contrib.label.negateFilter'></span></label> <input type='checkbox' id='node-input-negateFilter'
style='max-width:30px'> </div> <div class='form-row node-input-response-filter-container-row' style='margin-bottom:0'>
<div id='node-input-response-filter-container-div'
style='box-sizing:border-box;border-radius:5px;height:480px;padding:5px;border:1px solid #ccc;overflow-y:scroll'> <ol
id='node-input-response-filter-container' style='list-style-type:none;margin:0'></ol> </div> </div> <div
class='form-row'> <a href='#' class='editor-button editor-button-small' id='node-input-response-filter-add'
style='margin-top:4px'><i class='fa fa-plus'></i> <span data-i18n='opcua-iiot-contrib.label.addButton'></span></a>
</div> </div> </div></script> <script type='text/x-red' data-help-name='OPCUA-IIoT-Response'>
<h2>OPC UA IIoT Response</h2> <p>
There is a compress option to get compressed data with less optional data in msg objects.</p> <p>
The Response node shows you the response data from your request in categories of Good, Bad, and Other.</p> <h3>Input
</h3> <p>Output message from client nodes like Read, Write, Listener, and Browser</p> <h3>Output</h3> <p>
The output is the input message including the entry status Array ( msg.entryStatus [Good, Bad, Other]).</p> <p>
Set showErrors to get errors from node-opcua on browse.</p> </script>