UNPKG

node-red-contrib-opcua-server-refresh

Version:

Fork of the original 'node-red-contrib-opcua-server' package that is no longer maintained. This has been refactored to support the latest version of node-opcua and fixes incomplete/non-working features.

853 lines (772 loc) 43.3 kB
<!-- MIT License Copyright (c) 2018-2022 Klaus Landsdorf (http://node-red.plus/) Updated by Richard Meyer 2025 - Version 0.2.0 --> <script type="text/x-red" data-help-name="opcua-compact-server-refresh"> <h2>OPC UA Compact Server Refresh v0.2.0</h2> <p><strong>A modernized, secure OPC UA server</strong> with TypeScript implementation and enhanced security features. This node provides a programmable address space to build your own information model using JavaScript code based on the node-opcua API and OPC UA specification.</p> <p><strong>⚠️ Important:</strong> The product URI must be unique if you set a custom name (default: NodeOPCUA-Server-(port))</p> <h3>🔗 Default Endpoints</h3> <ul> <li><strong>Default endpoint:</strong> opc.tcp://localhost:54840</li> <li><strong>Named endpoint:</strong> opc.tcp://localhost:54840/UA/NodeRED/Compact</li> <li><strong>Default discovery:</strong> opc.tcp://localhost:4840/UADiscovery</li> <li><strong>All discovery:</strong> opc.tcp://localhost:4840/</li> </ul> <h3>🚀 Version 0.2.0 Improvements</h3> <ul> <li><strong>Complete TypeScript Migration:</strong> Enhanced developer experience with full type safety</li> <li><strong>Critical Security Fixes:</strong> Replaced vulnerable vm2 with secure Node.js vm implementation</li> <li><strong>Latest node-opcua Support:</strong> Updated to version 2.156.0 with compatibility fixes</li> <li><strong>Enhanced Security:</strong> Removed deprecated security policies (Basic128Rsa15, Basic256)</li> <li><strong>Comprehensive Testing:</strong> 61 tests ensuring reliability and stability</li> </ul> <h3>⚙️ Configuration Options</h3> <p>For detailed information about all configuration options, please refer to the <a href="https://node-opcua.github.io/" target="_blank">node-opcua API documentation</a>.</p> <ul> <li><strong>Delay Before Init:</strong> Initialize the server node after a specified delay (milliseconds)</li> <li><strong>Shutdown Timeout:</strong> Server shutdown timeout for graceful closure (milliseconds)</li> <li><strong>Delay On Close:</strong> Close the server node with delay relative to other nodes (milliseconds)</li> </ul> <h3>🔒 Security & Certificates</h3> <p>The server provides demo NodeOPCUA certificates and private keys for development. For production use, configure your own certificate and private key files.</p> <p><strong>Supported Security Policies:</strong></p> <ul> <li><strong>None:</strong> No security (development only)</li> <li><strong>Basic256Sha256:</strong> Recommended for production use</li> </ul> <p><strong>Note:</strong> Deprecated security policies (Basic128Rsa15, Basic256) have been removed for enhanced security.</p> <h3>👥 User Management</h3> <p>Configure user authentication for the server. User credentials are stored securely using Node-RED's credentials best practices.</p> <h3>📄 XML NodeSets</h3> <p>Add industry-standard nodesets including ISA95, DI (Device Integration), and Auto-ID to extend your server's capabilities.</p> <h3>🏗️ Address Space Programming</h3> <p>Use the built-in JavaScript editor to construct your custom address space. The editor provides:</p> <ul> <li>Syntax highlighting and code completion</li> <li>Access to the full node-opcua API</li> <li>Secure sandbox execution environment</li> <li>Example code templates for common patterns</li> </ul> <h3>🔍 Discovery Services</h3> <p>Configure how your server announces itself to the network:</p> <ul> <li><strong>HIDDEN:</strong> Server doesn't expose itself externally</li> <li><strong>MDNS:</strong> Server publishes itself via mDNS multicast</li> <li><strong>LDS:</strong> Server registers with Local Discovery Server (requires LDS running)</li> </ul> <p><strong>Tip:</strong> Enable "Show Errors" to get detailed error messages from node-opcua during browsing operations.</p> <h3>📚 Additional Resources</h3> <ul> <li><a href="https://github.com/Hisma/node-red-contrib-opcua-server-refresh" target="_blank">GitHub Repository</a></li> <li><a href="https://node-opcua.github.io/" target="_blank">Node-OPCUA Documentation</a></li> <li><a href="https://opcfoundation.org/" target="_blank">OPC Foundation</a></li> </ul> </script> <script type="text/javascript"> RED.nodes.registerType("opcua-compact-server-refresh", { category: "opcua", color: "#20B2aa", defaults: { port: { value: 54840, required: true, validate: val => { return val === "" || RED.validators.number(val); } }, endpoint: { value: "" }, productUri: { value: "" }, acceptExternalCommands: { value: true }, maxAllowedSessionNumber: { value: 10, validate: function(v) { return v === "" || (RED.validators.number(v) && v > 0 && v <= 10); } }, maxConnectionsPerEndpoint: { value: 10, validate: function(v) { return v === "" || (RED.validators.number(v) && v > 0 && v <= 10); } }, maxAllowedSubscriptionNumber: { value: 100, validate: function(v) { return v === "" || (RED.validators.number(v) && v > 0 && v <= 10000); } }, alternateHostname: { value: "" }, name: { value: "" }, showStatusActivities: { value: false }, showErrors: { value: false }, allowAnonymous: { value: true }, individualCerts: { value: false }, isAuditing: { value: false }, serverDiscovery: { value: true }, users: { value: [] }, xmlsetsOPCUA: { value: [] }, publicCertificateFile: { value: "" }, privateCertificateFile: { value: "" }, registerServerMethod: { value: 1 }, discoveryServerEndpointUrl: { value: "" }, capabilitiesForMDNS: { value: "" }, maxNodesPerRead: { value: 1000, validate: function(v) { return ( v === "" || (RED.validators.number(v) && v >= 0 && v <= 100000) ); } }, maxNodesPerWrite: { value: 1000, validate: function(v) { return ( v === "" || (RED.validators.number(v) && v >= 0 && v <= 100000) ); } }, maxNodesPerHistoryReadData: { value: 100, validate: function(v) { return v === "" || (RED.validators.number(v) && v >= 0 && v <= 10000); } }, maxNodesPerBrowse: { value: 3000, validate: function(v) { return ( v === "" || (RED.validators.number(v) && v >= 0 && v <= 200000) ); } }, maxBrowseContinuationPoints: { value: 10, validate: function(v) { return v === "" || (RED.validators.number(v) && v >= 0 && v <= 100); } }, maxHistoryContinuationPoints: { value: 10, validate: function(v) { return v === "" || (RED.validators.number(v) && v >= 0 && v <= 100); } }, delayToInit: { value: 1000, validate: function(v) { return ( v === "" || (RED.validators.number(v) && v >= 10 && v <= 200000) ); } }, delayToClose: { value: 200, validate: function(v) { return ( v === "" || (RED.validators.number(v) && v >= 10 && v <= 15000) ); } }, serverShutdownTimeout: { value: 100, validate: function(v) { return ( v === "" || (RED.validators.number(v) && v >= 10 && v <= 15000) ); } }, addressSpaceScript: { value: 'function(server, addressSpace, opcua, eventObjects, done) {\n\n node.warn(\"Starting OPCUA Server\");\n\n // Debug Statements to verify eventObjects and sandboxFlowContext\n if (!opcua) {\n node.error(\"OPCUA module is not available.\");\n return done(new Error(\"OPCUA module is undefined.\"));\n }\n\n const namespace = addressSpace.getOwnNamespace();\n\n const Variant = opcua.Variant;\n const DataType = opcua.DataType;\n const VariantArrayType = opcua.VariantArrayType;\n const DataValue = opcua.DataValue;\n const standardUnits = opcua.standardUnits;\n\n //Define ISA-95 folder structure for UNS\n const rootFolder = addressSpace.findNode(\"RootFolder\");\n\n const enterprise = namespace.addFolder(rootFolder.objects, { browseName: \"sji\" });\n\n const site = namespace.addFolder(enterprise, { browseName: \"garfield\" });\n\n const area = namespace.addFolder(site, { browseName: \"bop\" });\n\n const subsystem = namespace.addFolder(area, { browseName: \"flare\" });\n /*\n * variation 0:\n * ------------\n *\n * Add a variable in folder using a raw Variant.\n * Use this variation when the variable has to be read or written by the OPCUA clients\n */\n const variable0 = namespace.addVariable({\n organizedBy: subsystem,\n browseName: \"FanSpeed\",\n nodeId: \"ns=1;s=FanSpeed\",\n dataType: \"Double\",\n value: new Variant({ dataType: DataType.Double, value: 1000.0 })\n });\n\n setInterval(function () {\n const fluctuation = Math.random() * 100 - 50;\n variable0.setValueFromSource(new Variant({ dataType: DataType.Double, value: 1000.0 + fluctuation }));\n }, 10);\n \n /*\n * variation 1:\n * ------------\n *\n * Add a variable in folder using a single get function which returns the up to date variable value in Variant.\n * The server will set the timestamps automatically for us.\n * Use this variation when the variable value is controlled by the getter function\n * Avoid using this variation if the variable has to be made writable, as the server will call the getter\n * function prior to returning its value upon client read requests.\n */\n namespace.addVariable({\n organizedBy: subsystem,\n browseName: \"PumpSpeed\",\n nodeId: \"ns=1;s=PumpSpeed\",\n dataType: \"Double\",\n value: {\n /**\n * returns the current value as a Variant\n * @method get\n * @return {Variant}\n */\n get: function () {\n const pump_speed = 200 + 100 * Math.sin(Date.now() / 10000);\n return new Variant({ dataType: DataType.Double, value: pump_speed });\n }\n }\n });\n\n namespace.addVariable({\n organizedBy: subsystem,\n browseName: \"SomeDate\",\n nodeId: \"ns=1;s=SomeDate\",\n dataType: \"DateTime\",\n value: {\n get: function () {\n return new Variant({ dataType: DataType.DateTime, value: new Date(Date.UTC(2016, 9, 13, 8, 40, 0)) });\n }\n }\n });\n\n /*\n * variation 2:\n * ------------\n *\n * Add a variable in folder. This variable gets its value and source timestamps from the provided function.\n * The value and source timestamps are held in a external object.\n * The value and source timestamps are updated on a regular basis using a timer function.\n */\n const external_value_with_sourceTimestamp = new DataValue({\n value: new Variant({ dataType: DataType.Double, value: 10.0 }),\n sourceTimestamp: null,\n sourcePicoseconds: 0\n });\n setInterval(function () {\n external_value_with_sourceTimestamp.value.value = Math.random();\n external_value_with_sourceTimestamp.sourceTimestamp = new Date();\n }, 1000);\n\n namespace.addVariable({\n organizedBy: subsystem,\n browseName: \"Pressure\",\n nodeId: \"ns=1;s=Pressure\",\n dataType: \"Double\",\n value: {\n timestamped_get: function () {\n return external_value_with_sourceTimestamp;\n }\n }\n });\n /*\n * variation 3:\n * ------------\n *\n * Add a variable in a folder. This variable gets its value and source timestamps from the provided\n * asynchronous function.\n * The asynchronous function is called only when needed by the opcua Server read services and monitored item services\n *\n */\n\n namespace.addVariable({\n organizedBy: subsystem,\n browseName: \"Temperature\",\n nodeId: \"s=Temperature\",\n dataType: \"Double\",\n\n value: {\n refreshFunc: function (callback) {\n const temperature = 20 + 10 * Math.sin(Date.now() / 10000);\n const value = new Variant({ dataType: DataType.Double, value: temperature });\n const sourceTimestamp = new Date();\n\n // simulate a asynchronous behaviour\n setTimeout(function () {\n callback(null, new DataValue({ value: value, sourceTimestamp: sourceTimestamp }));\n }, 100);\n }\n }\n });\n\n // UAAnalogItem\n // add a UAAnalogItem\n const analogNode = namespace.addAnalogDataItem({\n organizedBy: subsystem,\n\n nodeId: \"s=TemperatureAnalogItem\",\n browseName: \"TemperatureAnalogItem\",\n definition: \"(tempA -25) + tempB\",\n valuePrecision: 0.5,\n engineeringUnitsRange: { low: 100, high: 200 },\n instrumentRange: { low: -100, high: +200 },\n engineeringUnits: standardUnits.degree_celsius,\n dataType: \"Double\",\n value: {\n get: function () {\n return new Variant({ dataType: DataType.Double, value: Math.random() + 19.0 });\n }\n }\n });\n\n const m3x3 = namespace.addVariable({\n organizedBy: addressSpace.rootFolder.objects,\n nodeId: \"s=Matrix\",\n browseName: \"Matrix\",\n dataType: \"Double\",\n valueRank: 2,\n arrayDimensions: [3, 3],\n value: {\n get: function () {\n return new Variant({\n dataType: DataType.Double,\n arrayType: VariantArrayType.Matrix,\n dimensions: [3, 3],\n value: [1, 2, 3, 4, 5, 6, 7, 8, 9]\n });\n }\n }\n });\n\n const xyz = namespace.addVariable({\n organizedBy: addressSpace.rootFolder.objects,\n nodeId: \"s=Position\",\n browseName: \"Position\",\n dataType: \"Double\",\n valueRank: 1,\n arrayDimensions: null,\n value: {\n get: function () {\n return new Variant({\n dataType: DataType.Double,\n arrayType: VariantArrayType.Array,\n value: [1, 2, 3, 4]\n });\n }\n }\n });\n \n //------------------------------------------------------------------------------\n // Add a view\n //------------------------------------------------------------------------------\n const view = namespace.addView({\n organizedBy: rootFolder.views,\n browseName: \"MyView\"\n });\n\n view.addReference({\n referenceType: \"Organizes\",\n nodeId: analogNode.nodeId\n });\n node.warn(\"Construction of new address space for OPC UA done\");\n done();\n}' } }, inputs: 0, outputs: 0, align: "right", icon: "icon.png", label: function() { return this.name || "Compact-Server-Refresh"; }, labelStyle: function() { return this.name ? "node_label_italic" : ""; }, oneditprepare: function() { let node = this; node.xmlsetSelect = []; let cacheItemCount = 0; let cacheItemCountSet = 0; let tabs = RED.tabs.create({ id: "node-input-server-tabs", onchange: function(tab) { $("#node-input-server-tabs-content") .children() .hide(); $("#" + tab.id).show(); } }); tabs.addTab({ id: "compact-server-tab-settings", label: this._("opcua-compact-contrib.tabs-label.settings") }); tabs.addTab({ id: "compact-server-tab-limits", label: this._("opcua-compact-contrib.tabs-label.limits") }); tabs.addTab({ id: "compact-server-tab-security", label: this._("opcua-compact-contrib.tabs-label.security") }); tabs.addTab({ id: "compact-server-tab-users", label: this._("opcua-compact-contrib.tabs-label.users") }); tabs.addTab({ id: "compact-server-tab-ass", label: this._("opcua-compact-contrib.tabs-label.ass") }); tabs.addTab({ id: "compact-server-tab-discovery", label: this._("opcua-compact-contrib.tabs-label.discovery") }); // User Management if (node.users && node.users.length > 0) { cacheItemCount = node.users.length; node.users.forEach(function(element, index, array) { generateUserEntry(element, index); }); } // XML-Set Management if (node.xmlsetsOPCUA && node.xmlsetsOPCUA.length > 0) { cacheItemCountSet = node.xmlsetsOPCUA.length; node.xmlsetsOPCUA.forEach(function(element, index, array) { generateXMLSetEntry(element, index); }); } function generateUserEntry(user, id) { let container = $("<li/>", { style: "background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;" }); let row = $('<div id="row' + id + '"/>').appendTo(container); $( '<i style="color: #eee; cursor: move;" class="node-input-server-users-handle fa fa-bars"></i>' ).appendTo(row); let userField = $("<input/>", { id: "node-input-server-users-name" + id, class: "opcuaUserName", type: "text", style: "margin-left:5px;width:160px;", placeholder: "name" }).appendTo(row); let passwordField = $("<input/>", { id: "node-input-server-users-password" + id, class: "opcuaUserPassword", type: "password", style: "margin: 0 auto;width:55%;min-width:60px;margin-left:5px", placeholder: "password" }).appendTo(row); userField.val(user.name); passwordField.val(user.password); let finalspan = $("<span/>", { style: "float: right;margin-right: 10px;" }).appendTo(row); let removeUserButton = $("<a/>", { href: "#", id: "node-button-user-remove" + id, class: "editor-button editor-button-small", style: "margin-top: 7px; margin-left: 5px;" }).appendTo(finalspan); $("<i/>", { class: "fa fa-remove" }).appendTo(removeUserButton); removeUserButton.click(function() { container.css({ background: "#fee" }); container.fadeOut(300, function() { $(this).remove(); }); }); $("#node-input-server-users-container").append(container); } function generateXMLSetEntry(xmlset, id) { let container = $("<li/>", { style: "background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;" }); let row = $('<div id="xmlsetRow' + id + '"/>').appendTo(container); $( '<i style="color: #eee; cursor: move;" class="node-input-server-xmlsets-handle fa fa-bars"></i>' ).appendTo(row); let nameField = $("<input/>", { id: "node-input-server-xmlsets-name" + id, type: "text", class: "xmlsetName", style: "margin-left:5px;width:30%;min-width:60px", placeholder: "name" }).appendTo(row); let pathField = $("<input/>", { id: "node-input-server-xmlsets-path" + id, class: "xmlsetPath", type: "text", style: "margin: 0 auto;width:60%;min-width:70px;margin-left:5px", placeholder: "public/nodesets/Opc.Ua.Di.NodeSet2.xml" }).appendTo(row); nameField.val(xmlset.name); pathField.val(xmlset.path); let finalspan = $("<span/>", { style: "float: right;margin-right: 10px;" }).appendTo(row); let lookupItemButton = $("<a/>", { href: "#", id: "node-button-xmlsets-lookup" + id, class: "editor-button editor-button-small", style: "margin-top: 7px; margin-left: 5px;" }).appendTo(finalspan); lookupItemButton.click(function() { let xmlsetLookupButton = $("#node-button-xmlsets-lookup" + id); let xmlsetLookupField = $( "#xmlsetRow" + id + " #node-input-server-xmlsets-path" + id ); xmlsetLookupButton.addClass("disabled"); $.getJSON("/OPCUA/compact/xmlsets/public", function(data) { xmlsetLookupButton.removeClass("disabled"); node.xmlsetSelect = []; $.each(data, function(i, filename) { node.xmlsetSelect.push(filename); }); xmlsetLookupField .autocomplete({ source: node.xmlsetSelect, minLength: 0, close: function(event, ui) { xmlsetLookupField.autocomplete("destroy"); } }) .autocomplete("search", ""); }); }); $("<i/>", { class: "fa fa-search" }).appendTo(lookupItemButton); let removeXMLSetButton = $("<a/>", { href: "#", id: "node-button-xmlset-remove" + id, class: "editor-button editor-button-small", style: "margin-top:7px;margin-left:5px;" }).appendTo(finalspan); $("<i/>", { class: "fa fa-remove" }).appendTo(removeXMLSetButton); removeXMLSetButton.click(function() { container.css({ background: "#fee" }); container.fadeOut(300, function() { $(this).remove(); }); }); $("#node-input-server-xmlsets-container").append(container); } $("#node-input-server-users-container").sortable({ axis: "y", handle: ".node-input-server-users-handle", cursor: "move" }); $("#node-input-server-xmlsets-container").sortable({ axis: "y", handle: ".node-input-server-xmlsets-handle", cursor: "move" }); $( "#node-input-server-users-container .node-input-server-users-handle" ).disableSelection(); $( "#node-input-server-xmlsets-container .node-input-server-xmlsets-handle" ).disableSelection(); $("#node-input-server-users-add").click(function() { if (!cacheItemCount || cacheItemCount < 0) { cacheItemCount = 0; } generateUserEntry({ name: "", password: "" }, cacheItemCount++); // length is every time one more than index $("#node-input-server-users-container-div").scrollTop( $("#node-input-server-users-container-div").get(0).scrollHeight ); }); $("#node-input-server-xmlsets-add").click(function() { if (!cacheItemCountSet || cacheItemCountSet < 0) { cacheItemCountSet = 0; } generateXMLSetEntry({ name: "", path: "" }, cacheItemCountSet++); // length is every time one more than index $("#node-input-server-xmlsets-container-div").scrollTop( $("#node-input-server-xmlsets-container-div").get(0).scrollHeight ); }); function switchDialogResize() { switchUserDialogResize(); switchXMLSetDialogSetResize(); } // dialog User handling function switchUserDialogResize() { let rows = $( "#dialog-form>div:not(.node-input-server-users-container-row)" ); let height = $("#dialog-form").height(); rows.each(function(index, row) { height -= row.outerHeight(true); }); let editorRow = $( "#dialog-form>div.node-input-server-users-container-row" ); height -= parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")); $("#node-input-server-users-container-div").css( "height", height + "px" ); } // dialog XML-Set handling function switchXMLSetDialogSetResize() { let rows = $( "#dialogSet-form>div:not(.node-input-server-xmlsets-container-row)" ); let height = $("#dialogSet-form").height(); rows.each(function(index, row) { height -= row.outerHeight(true); }); let editorRow = $( "#dialogSet-form>div.node-input-server-xmlsets-container-row" ); height -= parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")); $("#node-input-server-xmlsets-container-div").css( "height", height + "px" ); } $("#dialog").on("dialogresize", switchDialogResize); $("#dialog").on("dialogopen", function(ev) { let size = $("#dialog").dialog("option", "sizeCache-switch"); if (size) { $("#dialog").dialog("option", "width", size.width); $("#dialog").dialog("option", "height", size.height); switchDialogResize(); } else { setTimeout(switchDialogResize, 10); } }); $("#dialog").on("dialogclose", function(ev, ui) { $("#dialog").off("dialogresize", switchDialogResize); }); // address space script node.editorAddressSpaceScript = RED.editor.createEditor({ id: "node-input-func-editor-addressSpaceScript", mode: "ace/mode/javascript", value: $("#node-input-addressSpaceScript").val(), globals: { msg: true, context: true, RED: true, util: true, flow: true, global: true, console: true, Buffer: true, setTimeout: true, clearTimeout: true, setInterval: true, clearInterval: true, draggable: true } }); $(function() { $("#node-input-func-editor-addressSpaceScript").resizable(); }); let element = document.getElementById( "node-input-func-editor-addressSpaceScript" ); element.addEventListener("mouseup", resizeScript, false); function resizeScript() { node.editorAddressSpaceScript.resize(); } // Certificate Management try { let certsCheckbox = $("#node-input-individualCerts"); let configCertFields = $("#node-config-certFiles"); if (node.individualCerts) { certsCheckbox.prop("checked", true); configCertFields.show(); } else { certsCheckbox.prop("checked", false); configCertFields.hide(); } certsCheckbox.change(function() { if ($(this).is(":checked")) { configCertFields.show(); } else { configCertFields.hide(); $("#node-config-input-publicCertificateFile").val(""); $("#node-config-input-privateKeyFile").val(""); } }); } catch (err) { this.error(err); } }, oneditsave: function() { let node = this; let cacheUsers = $("#node-input-server-users-container").children(); node.users = []; cacheUsers.each(function() { node.users.push({ name: $(this) .find(".opcuaUserName") .val(), password: $(this) .find(".opcuaUserPassword") .val() }); }); let cacheXMLSets = $("#node-input-server-xmlsets-container").children(); node.xmlsetsOPCUA = []; cacheXMLSets.each(function() { node.xmlsetsOPCUA.push({ name: $(this) .find(".xmlsetName") .val(), path: $(this) .find(".xmlsetPath") .val() }); }); $("#node-input-addressSpaceScript").val( this.editorAddressSpaceScript.getValue() ); this.editorAddressSpaceScript.destroy(); delete this.editorAddressSpaceScript; // console.log('well done editsave ...') } }); </script> <script type="text/x-red" data-template-name="opcua-compact-server-refresh"> <div class="form-row"> <ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-input-server-tabs"></ul> </div> <div id="node-input-server-tabs-content" style="min-height: 170px;"> <div id="compact-server-tab-settings" style="display:none"> <div class="form-row"> <label for="node-input-port"><i class="fa fa-fort-awesom"></i> <span data-i18n="opcua-compact-contrib.label.port"></span></label> <!-- dynamic Ports from 49152 to 65535 (c000hex bis FFFFhex) --> <input type="text" id="node-input-port" placeholder="Ports 49152 bis 65535 (c000hex bis FFFFhex)"> </div> <div class="form-row"> <label for="node-input-endpoint"><i class="icon-tasks"></i> <span data-i18n="opcua-compact-contrib.label.endpoint"></span></label> <input type="text" id="node-input-endpoint" placeholder="UA/NodeRED/Compact"> </div> <div class="form-row"> <label for="node-input-productUri"><i class="icon-tasks"></i> <span data-i18n="opcua-compact-contrib.label.productUri"></span></label> <input type="text" id="node-input-productUri" placeholder="NodeOPCUA-Server-(port)"> </div> <div class="form-row"> <label for="node-input-alternateHostname"><i class="fa fa-server"></i> <span data-i18n="opcua-compact-contrib.label.alternateHostname"></span></label> <input type="text" id="node-input-alternateHostname" placeholder=""> </div> <hr> <div class="form-row"> <label for="node-input-delayToInit"><i class="icon-time"></i> <span data-i18n="opcua-compact-contrib.label.delayToInit"></span></label> <input type="text" id="node-input-delayToInit" placeholder="1000" style="width:80px"> </div> <div class="form-row"> <label for="node-input-serverShutdownTimeout"><i class="icon-time"></i> <span data-i18n="opcua-compact-contrib.label.serverShutdownTimeout"></span></label> <input type="text" id="node-input-serverShutdownTimeout" placeholder="100" style="width:80px"> </div> <div class="form-row"> <label for="node-input-delayToClose"><i class="icon-time"></i> <span data-i18n="opcua-compact-contrib.label.delayToClose"></span></label> <input type="text" id="node-input-delayToClose" placeholder="200" style="width:80px"> </div> <div class="form-row"> <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-isAuditing"><i class="fa fa-th"></i> <span data-i18n="opcua-compact-contrib.label.isAuditing"></span></label> <input type="checkbox" id="node-input-isAuditing" style="max-width:30px"> </div> <hr> <div class="form-row"> <label style="min-width:160px" for="node-input-showStatusActivities"><i class="fa fa-bolt"></i> <span data-i18n="opcua-compact-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-compact-contrib.label.showErrors"></span></label> <input type="checkbox" id="node-input-showErrors" style="max-width:30px"> </div> </div> <div id="compact-server-tab-limits" style="display:none"> <div class="form-row"> <label for="node-input-maxAllowedSessionNumber"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxAllowedSessionNumber"></span></label> <input type="text" id="node-input-maxAllowedSessionNumber" placeholder="10" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxConnectionsPerEndpoint"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxConnectionsPerEndpoint"></span></label> <input type="text" id="node-input-maxConnectionsPerEndpoint" placeholder="10" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxAllowedSubscriptionNumber"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxAllowedSubscriptionNumber"></span></label> <input type="text" id="node-input-maxAllowedSubscriptionNumber" placeholder="100" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxNodesPerRead"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxNodesPerRead"></span></label> <input type="text" id="node-input-maxNodesPerRead" placeholder="1000" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxNodesPerHistoryReadData"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxNodesPerHistoryReadData"></span></label> <input type="text" id="node-input-maxNodesPerHistoryReadData" placeholder="100" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxNodesPerWrite"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxNodesPerWrite"></span></label> <input type="text" id="node-input-maxNodesPerWrite" placeholder="1000" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxNodesPerBrowse"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxNodesPerBrowse"></span></label> <input type="text" id="node-input-maxNodesPerBrowse" placeholder="3000" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxBrowseContinuationPoints"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxBrowseContinuationPoints"></span></label> <input type="text" id="node-input-maxBrowseContinuationPoints" placeholder="10" style="width:80px"> </div> <div class="form-row"> <label for="node-input-maxHistoryContinuationPoints"><i class="icon-list"></i> <span data-i18n="opcua-compact-contrib.label.maxHistoryContinuationPoints"></span></label> <input type="text" id="node-input-maxHistoryContinuationPoints" placeholder="10" style="width:80px"> </div> </div> <div id="compact-server-tab-security" style="display:none"> <div class="form-row"> <!-- SecurityPolicy enum via REST --> <label for="node-input-securityPolicy" style="min-width:140px"><i class="fa fa-certificate"></i> <span data-i18n="opcua-compact-contrib.label.securityPolicy"></span></label> <ul style="margin-left:80px"> <li>None</li> <li>Basic256Sha256 (Recommended)</li> </ul> <p style="margin-left:80px; font-size:12px; color:#666;"> <strong>Note:</strong> Deprecated policies (Basic128Rsa15, Basic256) have been removed for security. </p> </div> <div class="form-row"> <!-- MessageSecurityMode enum via REST --> <label for="node-input-securityMode" style="min-width:140px"><i class="fa fa-key"></i> <span data-i18n="opcua-compact-contrib.label.securityMode"></span></label> <ul style="margin-left:80px"> <li>None</li> <li>Sign</li> <li>Sign&Encrypt</li> </ul> </div> <div class="form-row"> <label style="min-width:140px"> <i class="fa fa-certificate"></i> <span data-i18n="opcua-compact-contrib.label.certificateFiles"></span> </label> </div> <div class="form-row"> <label style="min-width:240px"> <i class="fa fa-file"></i> <span data-i18n="opcua-compact-contrib.label.individualCerts"></span> <input type="checkbox" id="node-input-individualCerts" style="max-width:30px"> </label> </div> <div id="node-config-certFiles"> <hr> <div class="form-row"> <label for="node-input-publicCertificateFile"><i class="fa fa-file"></i> <span data-i18n="opcua-compact-contrib.label.publicCertificateFile"></span></label> <input type="text" id="node-input-publicCertificateFile" placeholder="../certificates/server_selfsigned_cert_2048.pem" style="min-width:480px;width:100%"> </div> <div class="form-row"> <label for="node-input-privateCertificateFile"><i class="fa fa-file"></i> <span data-i18n="opcua-compact-contrib.label.privateCertificateFile"></span></label> <input type="text" id="node-input-privateCertificateFile" placeholder="../certificates/server_key_2048.pem" style="min-width:480px;width:100%"> </div> <hr> </div> <div class="form-row"> <label for="node-input-allowAnonymous" style="min-width:140px"><i class="icon-lock"></i> <span data-i18n="opcua-compact-contrib.label.allowAnonymous"></span></label> <input type="checkbox" id="node-input-allowAnonymous" style="max-width:35px"> </div> </div> <div id="compact-server-tab-users" style="display:none"> <h4><span data-i18n="opcua-compact-contrib.label.users"></span></h4> <div class="form-row node-input-server-users-container-row" style="margin-bottom: 0px;"> <div id="node-input-server-users-container-div" style="box-sizing: border-box; border-radius: 5px; height: 210px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;"> <ol id="node-input-server-users-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-server-users-add" style="margin-top: 4px;"><i class="fa fa-plus"></i> <span data-i18n="opcua-compact-contrib.label.addButton"></span></a> </div> <h4><span data-i18n="opcua-compact-contrib.label.xmlsets"></span></h4> <div class="form-row node-input-server-xmlsets-container-row" style="margin-bottom: 0px;"> <div id="node-input-server-xmlsets-container-div" style="box-sizing: border-box; border-radius: 5px; height: 240px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;"> <ol id="node-input-server-xmlsets-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-server-xmlsets-add" style="margin-top: 4px;"><i class="fa fa-plus"></i> <span data-i18n="opcua-compact-contrib.label.addButton"></span></a> </div> </div> <div id="compact-server-tab-ass" style="display:none"> <div class="form-row" style="margin-bottom: 0px;"> <label for="node-input-addressSpaceScript" style="width:50%;"><i class="fa fa-wrench"></i> <span data-i18n="opcua-compact-contrib.label.function"></span></label> <input type="hidden" id="node-input-addressSpaceScript"> </div> <div class="form-row node-text-editor-row" id="div-script-editor"> <div style="height: 350px; min-height:150px;" class="node-text-editor" id="node-input-func-editor-addressSpaceScript"></div> </div> </div> <div id="compact-server-tab-discovery" style="display:none"> <div class="form-row"> <!-- SecurityPolicy enum via REST --> <label for="node-input-registerServerMethod" style="min-width:160px"><i class="fa fa-discover"></i> <span data-i18n="opcua-compact-contrib.label.registerServerMethod"></span></label> <select type="text" id="node-input-registerServerMethod"> <option value="1">HIDDEN</option> <option value="2">MDNS</option> <option value="3">LDS</option> </select> </div> <div class="form-row"> <span style="min-width:160px"> <ul> <li>HIDDEN: the server doesn't expose itself to the external world <li>MDNS: the server publish itself to the mDNS Multicast network directly <li>LDS: the server registers itself to the LDS or LDS-ME (Local Discovery Server) </ul> </span> </div> <div class="form-row"> <label for="node-input-serverDiscovery" style="min-width:160px"><i class="fa fa-cc-discover"></i> <span data-i18n="opcua-compact-contrib.label.serverDiscovery"></span></label> <input type="checkbox" id="node-input-serverDiscovery" style="max-width:35px"> </div> <div class="form-row"> <label for="node-input-discoveryServerEndpointUrl" style="min-width:160px"><i class="icon-discover"></i> <span data-i18n="opcua-compact-contrib.label.discoveryServerEndpointUrl"></span></label> <input type="text" id="node-input-discoveryServerEndpointUrl" placeholder="opc.tcp://localhost:4840"> </div> <div class="form-row"> <label for="node-input-capabilitiesForMDNS" style="min-width:160px"><i class="icon-discover"></i> <span data-i18n="opcua-compact-contrib.label.capabilitiesForMDNS"></span></label> <input type="text" id="node-input-capabilitiesForMDNS" placeholder="NA,DA,..." > </div> </div> </div> </script>