diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f67ba22..af804b1d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +#### 0.16.2: Maintenance Release + + - Ensure custom mustache context parent set in Template node fixes #1126 + - Display debug node name in debug panel if its known + - Ensure auth-tokens are removed when no user is specified in settings + - Ensure all a tags have blank target in info sidebar + - Ensure links do not span tabs in the editor + - Avoid creating multiple reconnect timers in websocket node + - Fix inner reference in install fail message catalog entry Fixes #1120 + - Display buffer data properly for truncated buffers under Object property + #### 0.16.1: Maintenance Release - Add colour swatches to debug when hex colour matched diff --git a/editor/js/nodes.js b/editor/js/nodes.js index 878b5fb02..8b400b417 100644 --- a/editor/js/nodes.js +++ b/editor/js/nodes.js @@ -1037,9 +1037,13 @@ RED.nodes = (function() { var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]]; for (var w2=0;w2",node_map[wires[w2]].id); + } } } } diff --git a/editor/js/settings.js b/editor/js/settings.js index ae01c66dc..b26dfe990 100644 --- a/editor/js/settings.js +++ b/editor/js/settings.js @@ -102,7 +102,7 @@ RED.settings = (function () { url: 'settings', success: function (data) { setProperties(data); - if (RED.settings.user && RED.settings.user.anonymous) { + if (!RED.settings.user || RED.settings.user.anonymous) { RED.settings.remove("auth-tokens"); } console.log("Node-RED: " + data.version); diff --git a/editor/js/ui/clipboard.js b/editor/js/ui/clipboard.js index 8b5e7a68e..16d481588 100644 --- a/editor/js/ui/clipboard.js +++ b/editor/js/ui/clipboard.js @@ -164,10 +164,12 @@ RED.clipboard = (function() { dialogContainer.empty(); dialogContainer.append($(exportNodesDialog)); dialogContainer.i18n(); + var format = RED.settings.flowFilePretty ? "export-format-full" : "export-format-mini"; $("#export-format-group > a").click(function(evt) { evt.preventDefault(); if ($(this).hasClass('disabled') || $(this).hasClass('selected')) { + $("#clipboard-export").focus(); return; } $(this).parent().children().removeClass('selected'); @@ -177,18 +179,21 @@ RED.clipboard = (function() { if (flow.length > 0) { var nodes = JSON.parse(flow); - var format = $(this).attr('id'); + format = $(this).attr('id'); if (format === 'export-format-full') { flow = JSON.stringify(nodes,null,4); } else { flow = JSON.stringify(nodes); } $("#clipboard-export").val(flow); + $("#clipboard-export").focus(); } }); + $("#export-range-group > a").click(function(evt) { evt.preventDefault(); if ($(this).hasClass('disabled') || $(this).hasClass('selected')) { + $("#clipboard-export").focus(); return; } $(this).parent().children().removeClass('selected'); @@ -209,7 +214,7 @@ RED.clipboard = (function() { nodes = RED.nodes.createCompleteNodeSet(false); } if (nodes !== null) { - if (RED.settings.flowFilePretty) { + if (format === "export-format-full") { flow = JSON.stringify(nodes,null,4); } else { flow = JSON.stringify(nodes); @@ -221,6 +226,7 @@ RED.clipboard = (function() { $("#export-copy").addClass('disabled'); } $("#clipboard-export").val(flow); + $("#clipboard-export").focus(); }) $("#clipboard-dialog-ok").hide(); @@ -234,7 +240,7 @@ RED.clipboard = (function() { $("#export-range-selected").addClass('disabled').removeClass('selected'); $("#export-range-flow").click(); } - if (RED.settings.flowFilePretty) { + if (format === "export-format-full") { $("#export-format-full").click(); } else { $("#export-format-mini").click(); diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index 865ec667d..f3485ee82 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -421,7 +421,7 @@ RED.editor = (function() { * @param definition - the node definition * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) */ - function prepareEditDialog(node,definition,prefix) { + function prepareEditDialog(node,definition,prefix,done) { for (var d in definition.defaults) { if (definition.defaults.hasOwnProperty(d)) { if (definition.defaults[d].type) { @@ -465,6 +465,9 @@ RED.editor = (function() { } } validateNodeEditor(node,prefix); + if (done) { + done(); + } } if (definition.credentials) { @@ -945,7 +948,7 @@ RED.editor = (function() { } } }, - open: function(tray) { + open: function(tray, done) { var trayFooter = tray.find(".editor-tray-footer"); var trayBody = tray.find('.editor-tray-body'); trayBody.parent().css('overflow','hidden'); @@ -977,8 +980,10 @@ RED.editor = (function() { buildEditForm(nodeProperties.content,"dialog-form",type,ns); buildLabelForm(portLabels.content,node); - prepareEditDialog(node,node._def,"node-input"); - trayBody.i18n(); + prepareEditDialog(node,node._def,"node-input", function() { + trayBody.i18n(); + done(); + }); }, close: function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { @@ -1063,11 +1068,11 @@ RED.editor = (function() { try { editing_config_node._def.oneditresize.call(editing_config_node,{width:form.width(),height:form.height()}); } catch(err) { - console.log("oneditresize",editing_node.id,editing_node.type,err.toString()); + console.log("oneditresize",editing_config_node.id,editing_config_node.type,err.toString()); } } }, - open: function(tray) { + open: function(tray, done) { var trayHeader = tray.find(".editor-tray-header"); var trayFooter = tray.find(".editor-tray-footer"); @@ -1078,58 +1083,60 @@ RED.editor = (function() { var dialogForm = buildEditForm(tray.find('.editor-tray-body'),"node-config-dialog-edit-form",type,ns); - prepareEditDialog(editing_config_node,node_def,"node-config-input"); - if (editing_config_node._def.exclusive) { - $("#node-config-dialog-scope").hide(); - } else { - $("#node-config-dialog-scope").show(); - } - $("#node-config-dialog-scope-warning").hide(); + prepareEditDialog(editing_config_node,node_def,"node-config-input", function() { + if (editing_config_node._def.exclusive) { + $("#node-config-dialog-scope").hide(); + } else { + $("#node-config-dialog-scope").show(); + } + $("#node-config-dialog-scope-warning").hide(); - var nodeUserFlows = {}; - editing_config_node.users.forEach(function(n) { - nodeUserFlows[n.z] = true; - }); - var flowCount = Object.keys(nodeUserFlows).length; - var tabSelect = $("#node-config-dialog-scope").empty(); - tabSelect.off("change"); - tabSelect.append(''); - tabSelect.append(''); - RED.nodes.eachWorkspace(function(ws) { - var workspaceLabel = ws.label; - if (nodeUserFlows[ws.id]) { - workspaceLabel = "* "+workspaceLabel; - } - tabSelect.append(''); - }); - tabSelect.append(''); - RED.nodes.eachSubflow(function(ws) { - var workspaceLabel = ws.name; - if (nodeUserFlows[ws.id]) { - workspaceLabel = "* "+workspaceLabel; - } - tabSelect.append(''); - }); - if (flowCount > 0) { - tabSelect.on('change',function() { - var newScope = $(this).val(); - if (newScope === '') { - // global scope - everyone can use it - $("#node-config-dialog-scope-warning").hide(); - } else if (!nodeUserFlows[newScope] || flowCount > 1) { - // a user will loose access to it - $("#node-config-dialog-scope-warning").show(); - } else { - $("#node-config-dialog-scope-warning").hide(); - } + var nodeUserFlows = {}; + editing_config_node.users.forEach(function(n) { + nodeUserFlows[n.z] = true; }); - } - tabSelect.i18n(); + var flowCount = Object.keys(nodeUserFlows).length; + var tabSelect = $("#node-config-dialog-scope").empty(); + tabSelect.off("change"); + tabSelect.append(''); + tabSelect.append(''); + RED.nodes.eachWorkspace(function(ws) { + var workspaceLabel = ws.label; + if (nodeUserFlows[ws.id]) { + workspaceLabel = "* "+workspaceLabel; + } + tabSelect.append(''); + }); + tabSelect.append(''); + RED.nodes.eachSubflow(function(ws) { + var workspaceLabel = ws.name; + if (nodeUserFlows[ws.id]) { + workspaceLabel = "* "+workspaceLabel; + } + tabSelect.append(''); + }); + if (flowCount > 0) { + tabSelect.on('change',function() { + var newScope = $(this).val(); + if (newScope === '') { + // global scope - everyone can use it + $("#node-config-dialog-scope-warning").hide(); + } else if (!nodeUserFlows[newScope] || flowCount > 1) { + // a user will loose access to it + $("#node-config-dialog-scope-warning").show(); + } else { + $("#node-config-dialog-scope-warning").hide(); + } + }); + } + tabSelect.i18n(); - dialogForm.i18n(); - if (node_def.hasUsers !== false) { - $("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show(); - } + dialogForm.i18n(); + if (node_def.hasUsers !== false) { + $("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show(); + } + done(); + }); }, close: function() { RED.workspaces.refresh(); diff --git a/editor/js/ui/tab-info.js b/editor/js/ui/tab-info.js index fd9e74a42..f96fbdbf3 100644 --- a/editor/js/ui/tab-info.js +++ b/editor/js/ui/tab-info.js @@ -66,6 +66,15 @@ RED.sidebar.info = (function() { return value; } + function addTargetToExternalLinks(el) { + $(el).find("a").each(function(el) { + var href = $(this).attr('href'); + if (/^https?:/.test(href)) { + $(this).attr('target','_blank'); + } + }); + return el; + } function refresh(node) { tips.stop(); $(content).empty(); @@ -117,14 +126,14 @@ RED.sidebar.info = (function() { $("
").appendTo(content); if (!subflowNode && node.type != "comment") { var helpText = $("script[data-help-name$='"+node.type+"']").html()||""; - $('
'+helpText+'
').appendTo(content); + addTargetToExternalLinks($('
'+helpText+'
').appendTo(content)); } if (subflowNode) { - $('
'+marked(subflowNode.info||"")+'
').appendTo(content); + addTargetToExternalLinks($('
'+marked(subflowNode.info||"")+'
').appendTo(content)); } else if (node._def && node._def.info) { var info = node._def.info; var textInfo = (typeof info === "function" ? info.call(node) : info); - $('
'+marked(textInfo)+'
').appendTo(content); + addTargetToExternalLinks($('
'+marked(textInfo)+'
').appendTo(content)); //$('
'+(typeof info === "function" ? info.call(node) : info)+'
'; } diff --git a/editor/js/ui/tray.js b/editor/js/ui/tray.js index 38760ef7c..4e5dc0b0e 100644 --- a/editor/js/ui/tray.js +++ b/editor/js/ui/tray.js @@ -104,85 +104,70 @@ RED.tray = (function() { } }); - if (options.open) { - options.open(el); - } + function finishBuild() { + $("#header-shade").show(); + $("#editor-shade").show(); + $("#palette-shade").show(); + $(".sidebar-shade").show(); - $("#header-shade").show(); - $("#editor-shade").show(); - $("#palette-shade").show(); - $(".sidebar-shade").show(); + tray.preferredWidth = Math.max(el.width(),500); + body.css({"minWidth":tray.preferredWidth-40}); - tray.preferredWidth = Math.max(el.width(),500); - body.css({"minWidth":tray.preferredWidth-40}); - - if (options.width) { - if (options.width > $("#editor-stack").position().left-8) { - options.width = $("#editor-stack").position().left-8; + if (options.width) { + if (options.width > $("#editor-stack").position().left-8) { + options.width = $("#editor-stack").position().left-8; + } + el.width(options.width); + } else { + el.width(tray.preferredWidth); } - el.width(options.width); - } else { - el.width(tray.preferredWidth); - } - tray.width = el.width(); - if (tray.width > $("#editor-stack").position().left-8) { - tray.width = Math.max(0/*tray.preferredWidth*/,$("#editor-stack").position().left-8); - el.width(tray.width); - } + tray.width = el.width(); + if (tray.width > $("#editor-stack").position().left-8) { + tray.width = Math.max(0/*tray.preferredWidth*/,$("#editor-stack").position().left-8); + el.width(tray.width); + } - // tray.body.parent().width(Math.min($("#editor-stack").position().left-8,tray.width)); + // tray.body.parent().width(Math.min($("#editor-stack").position().left-8,tray.width)); - el.css({ - right: -(el.width()+10)+"px", - transition: "right 0.25s ease" - }); - $("#workspace").scrollLeft(0); - handleWindowResize(); - openingTray = true; - setTimeout(function() { + el.css({ + right: -(el.width()+10)+"px", + transition: "right 0.25s ease" + }); + $("#workspace").scrollLeft(0); + handleWindowResize(); + openingTray = true; setTimeout(function() { - if (!options.width) { - el.width(Math.min(tray.preferredWidth,$("#editor-stack").position().left-8)); - } - if (options.resize) { - options.resize({width:el.width()}); - } - if (options.show) { - options.show(); - } setTimeout(function() { - // Delay resetting the flag, so we don't close prematurely - openingTray = false; - },200); - body.find(":focusable:first").focus(); - - },150); - el.css({right:0}); - },0); - - // growButton.click(function(e) { - // e.preventDefault(); - // tray.lastWidth = tray.width; - // tray.width = $("#editor-stack").position().left-8; - // el.width(tray.width); - // if (options.resize) { - // options.resize({width:tray.width}); - // } - // }); - // shrinkButton.click(function(e) { - // e.preventDefault(); - // if (tray.lastWidth && tray.width > tray.lastWidth) { - // tray.width = tray.lastWidth; - // } else if (tray.width > tray.preferredWidth) { - // tray.width = tray.preferredWidth; - // } - // el.width(tray.width); - // if (options.resize) { - // options.resize({width:tray.width}); - // } - // }); + if (!options.width) { + el.width(Math.min(tray.preferredWidth,$("#editor-stack").position().left-8)); + } + if (options.resize) { + options.resize({width:el.width()}); + } + if (options.show) { + options.show(); + } + setTimeout(function() { + // Delay resetting the flag, so we don't close prematurely + openingTray = false; + },200); + body.find(":focusable:first").focus(); + },150); + el.css({right:0}); + },0); + } + if (options.open) { + if (options.open.length === 1) { + options.open(el); + finishBuild(); + } else { + options.open(el,finishBuild); + } + } else { + finishBuild(); + } } function handleWindowResize() { diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index 1c5510dac..da684cf45 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -1311,7 +1311,7 @@ RED.view = (function() { sp.className = className; sp.style.position = "absolute"; sp.style.top = "-1000px"; - sp.innerHTML = (str||"").replace(/&/g,"&").replace(//g,">"); + sp.textContent = (str||""); document.body.appendChild(sp); var w = sp.offsetWidth; var h = sp.offsetHeight; @@ -2323,10 +2323,10 @@ RED.view = (function() { d.x2 = d.target.x-d.target.w/2; d.y2 = d.target.y; - return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+ - " C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+ - (d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+ - (d.target.x-d.target.w/2)+" "+d.target.y; + return "M "+d.x1+" "+d.y1+ + " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ + (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ + d.x2+" "+d.y2; }); } }) diff --git a/nodes/core/core/58-debug.html b/nodes/core/core/58-debug.html index d53623162..c421c0604 100644 --- a/nodes/core/core/58-debug.html +++ b/nodes/core/core/58-debug.html @@ -170,7 +170,7 @@ this.handleDebugMessage = function(t,o) { var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z); if (sourceNode) { - o._source = {id:sourceNode.id,z:sourceNode.z}; + o._source = {id:sourceNode.id,z:sourceNode.z,name:sourceNode.name}; } RED.debug.handleDebugMessage(o); diff --git a/nodes/core/core/75-exec.html b/nodes/core/core/75-exec.html index ffd8c5835..b9279cb73 100644 --- a/nodes/core/core/75-exec.html +++ b/nodes/core/core/75-exec.html @@ -47,10 +47,13 @@

Calls out to a system command.

Provides 3 outputs: stdout, stderr, and return code.

By default uses the exec system call which calls the command, then gets a callback - on completion, returning the complete result in one message, along with any errors.

+ on completion, returning stdout as the payload to the first, the error code as the third + and, if available, stderr to the second output. If no error occurred, a zero is returned on the third output.

Optionally can use spawn instead, which returns the output from stdout and stderr as the command runs (usually one line at a time). On completion it then returns a return code (on the 3rd output).

+

The exec method spawns a subshell and therefore can be used for more complicated + commands involving pipes. However, it waits for completion of the whole command before returing anything.

The optional append gets added to the command after msg.payload - so you can do things like pipe the result to another command.

Commands or parameters with spaces should be enclosed in quotes - "This is a single parameter"

diff --git a/nodes/core/core/75-exec.js b/nodes/core/core/75-exec.js index 17551864d..f59175e81 100644 --- a/nodes/core/core/75-exec.js +++ b/nodes/core/core/75-exec.js @@ -115,12 +115,17 @@ module.exports = function(RED) { child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) { msg.payload = new Buffer(stdout,"binary"); if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); } - var msg2 = {payload:stderr}; - var msg3 = null; + var msg2 = null; + if(stderr) { + msg2 = {payload: stderr}; + } + var msg3 = {payload:0}; + node.status({}); //console.log('[exec] stdout: ' + stdout); //console.log('[exec] stderr: ' + stderr); if (error !== null) { - msg3 = {payload:error}; + msg3 = {payload:error.code}; + node.status({fill:"red",shape:"dot",text:"error: "+error.code}); //console.log('[exec] error: ' + error); } if (!msg3) { node.status({}); } diff --git a/nodes/core/core/80-template.js b/nodes/core/core/80-template.js index 541a6b8a5..85efaccbe 100644 --- a/nodes/core/core/80-template.js +++ b/nodes/core/core/80-template.js @@ -22,8 +22,8 @@ module.exports = function(RED) { * Custom Mustache Context capable to resolve message property and node * flow and global context */ - function NodeContext(msg, nodeContext) { - this.msgContext = new mustache.Context(msg); + function NodeContext(msg, nodeContext,parent) { + this.msgContext = new mustache.Context(msg,parent); this.nodeContext = nodeContext; } @@ -31,26 +31,34 @@ module.exports = function(RED) { NodeContext.prototype.lookup = function (name) { // try message first: - var value = this.msgContext.lookup(name); - if (value !== undefined) { - return value; - } - - // try node context: - var dot = name.indexOf("."); - if (dot > 0) { - var contextName = name.substr(0, dot); - var variableName = name.substr(dot + 1); - - if (contextName === "flow" && this.nodeContext.flow) { - return this.nodeContext.flow.get(variableName); + try { + var value = this.msgContext.lookup(name); + if (value !== undefined) { + return value; } - else if (contextName === "global" && this.nodeContext.global) { - return this.nodeContext.global.get(variableName); + + // try node context: + var dot = name.indexOf("."); + if (dot > 0) { + var contextName = name.substr(0, dot); + var variableName = name.substr(dot + 1); + + if (contextName === "flow" && this.nodeContext.flow) { + return this.nodeContext.flow.get(variableName); + } + else if (contextName === "global" && this.nodeContext.global) { + return this.nodeContext.global.get(variableName); + } } + }catch(err) { + throw err; } } + NodeContext.prototype.push = function push (view) { + return new NodeContext(view, this.nodeContext,this.msgContext); + }; + function TemplateNode(n) { RED.nodes.createNode(this,n); this.name = n.name; @@ -65,7 +73,7 @@ module.exports = function(RED) { try { var value; if (node.syntax === "mustache") { - value = mustache.render(node.template, new NodeContext(msg, node.context())); + value = mustache.render(node.template,new NodeContext(msg, node.context())); } else { value = node.template; } diff --git a/nodes/core/core/lib/debug/debug-utils.js b/nodes/core/core/lib/debug/debug-utils.js index ad18dab2c..9c9f48661 100644 --- a/nodes/core/core/lib/debug/debug-utils.js +++ b/nodes/core/core/lib/debug/debug-utils.js @@ -205,7 +205,7 @@ RED.debug = (function() { var metaRow = $('
').appendTo(msg); $(''+ getTimestamp()+'').appendTo(metaRow); if (sourceNode) { - $('',{href:"#",class:"debug-message-name"}).html('node: '+sourceNode.id) + $('',{href:"#",class:"debug-message-name"}).html('node: '+(sourceNode.name||sourceNode.id)) .appendTo(metaRow) .click(function(evt) { evt.preventDefault(); diff --git a/nodes/core/hardware/36-rpi-gpio.js b/nodes/core/hardware/36-rpi-gpio.js index a8d8fa27d..48ad858d3 100644 --- a/nodes/core/hardware/36-rpi-gpio.js +++ b/nodes/core/hardware/36-rpi-gpio.js @@ -84,7 +84,7 @@ module.exports = function(RED) { node.child.stdout.on('data', function (data) { var d = data.toString().trim().split("\n"); for (var i = 0; i < d.length; i++) { - if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i]))) { + if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) { node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) }); } node.buttonState = d[i]; diff --git a/nodes/core/hardware/nrgpio.py b/nodes/core/hardware/nrgpio.py index 207729f18..73f395fb0 100755 --- a/nodes/core/hardware/nrgpio.py +++ b/nodes/core/hardware/nrgpio.py @@ -99,9 +99,9 @@ if len(sys.argv) > 2: elif cmd == "in": #print "Initialised pin "+str(pin)+" to IN" - bounce = int(sys.argv[4]) + bounce = float(sys.argv[4]) def handle_callback(chan): - sleep(bounce/1000) + sleep(bounce/1000.0) print GPIO.input(chan) if sys.argv[3].lower() == "up": @@ -112,7 +112,7 @@ if len(sys.argv) > 2: GPIO.setup(pin,GPIO.IN) print GPIO.input(pin) - GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=bounce) + GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce)) while True: try: diff --git a/nodes/core/io/22-websocket.js b/nodes/core/io/22-websocket.js index 2723c1d60..fe6c9ed96 100644 --- a/nodes/core/io/22-websocket.js +++ b/nodes/core/io/22-websocket.js @@ -36,6 +36,7 @@ module.exports = function(RED) { node.closing = false; function startconn() { // Connect to remote endpoint + node.tout = null; var socket = new ws(node.path); socket.setMaxListeners(0); node.server = socket; // keep for closing @@ -52,6 +53,7 @@ module.exports = function(RED) { if (node.isServer) { delete node._clients[id]; node.emit('closed',Object.keys(node._clients).length); } else { node.emit('closed'); } if (!node.closing && !node.isServer) { + clearTimeout(node.tout); node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? } }); @@ -61,6 +63,7 @@ module.exports = function(RED) { socket.on('error', function(err) { node.emit('erro'); if (!node.closing && !node.isServer) { + clearTimeout(node.tout); node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? } }); @@ -123,7 +126,10 @@ module.exports = function(RED) { else { node.closing = true; node.server.close(); - if (node.tout) { clearTimeout(node.tout); } + if (node.tout) { + clearTimeout(node.tout); + node.tout = null; + } } }); } @@ -205,7 +211,9 @@ module.exports = function(RED) { this.error(RED._("websocket.errors.missing-conf")); } this.on('close', function() { - node.serverConfig.removeInputNode(node); + if (node.serverConfig) { + node.serverConfig.removeInputNode(node); + } node.status({}); }); } @@ -217,7 +225,7 @@ module.exports = function(RED) { this.server = (n.client)?n.client:n.server; this.serverConfig = RED.nodes.getNode(this.server); if (!this.serverConfig) { - this.error(RED._("websocket.errors.missing-conf")); + return this.error(RED._("websocket.errors.missing-conf")); } else { // TODO: nls diff --git a/nodes/core/io/31-tcpin.js b/nodes/core/io/31-tcpin.js index 709552f28..4a9650802 100644 --- a/nodes/core/io/31-tcpin.js +++ b/nodes/core/io/31-tcpin.js @@ -444,7 +444,7 @@ module.exports = function(RED) { //node.log(RED._("tcpin.errors.client-connected")); node.status({fill:"green",shape:"dot",text:"common.status.connected"}); if (clients[connection_id] && clients[connection_id].client) { - clients[connection_id].connected = true; + clients[connection_id].connected = true; clients[connection_id].client.write(clients[connection_id].msg.payload); } }); @@ -454,10 +454,10 @@ module.exports = function(RED) { } clients[connection_id].client.on('data', function(data) { - if (node.out == "sit") { // if we are staying connected just send the buffer + if (node.out === "sit") { // if we are staying connected just send the buffer if (clients[connection_id]) { clients[connection_id].msg.payload = data; - node.send(clients[connection_id].msg); + node.send(RED.util.cloneMessage(clients[connection_id].msg)); } } else if (node.splitc === 0) { @@ -533,7 +533,7 @@ module.exports = function(RED) { //console.log("END"); node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"}); if (clients[connection_id] && clients[connection_id].client) { - clients[connection_id].connected = false; + clients[connection_id].connected = false; clients[connection_id].client = null; } }); @@ -541,7 +541,7 @@ module.exports = function(RED) { clients[connection_id].client.on('close', function() { //console.log("CLOSE"); if (clients[connection_id]) { - clients[connection_id].connected = false; + clients[connection_id].connected = false; } var anyConnected = false; diff --git a/package.json b/package.json index f54f315a9..2cc364e8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name" : "node-red", - "version" : "0.16.1", + "version" : "0.16.2", "description" : "A visual tool for wiring the Internet of Things", "homepage" : "http://nodered.org", "license" : "Apache-2.0", diff --git a/red.js b/red.js index 8969f2e4c..2cea58be2 100755 --- a/red.js +++ b/red.js @@ -196,6 +196,7 @@ try { function basicAuthMiddleware(user,pass) { var basicAuth = require('basic-auth'); var checkPassword; + var localCachedPassword; if (pass.length == "32") { // Assume its a legacy md5 password checkPassword = function(p) { @@ -207,12 +208,26 @@ function basicAuthMiddleware(user,pass) { } } + var checkPasswordAndCache = function(p) { + // For BasicAuth routes we know the password cannot change without + // a restart of Node-RED. This means we can cache the provided crypted + // version to save recalculating each time. + if (localCachedPassword === p) { + return true; + } + var result = checkPassword(p); + if (result) { + localCachedPassword = p; + } + return result; + } + return function(req,res,next) { if (req.method === 'OPTIONS') { return next(); } var requestUser = basicAuth(req); - if (!requestUser || requestUser.name !== user || !checkPassword(requestUser.pass)) { + if (!requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass)) { res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); return res.sendStatus(401); } diff --git a/red/runtime/locales/en-US/runtime.json b/red/runtime/locales/en-US/runtime.json index 553cf5536..fa5d6088c 100644 --- a/red/runtime/locales/en-US/runtime.json +++ b/red/runtime/locales/en-US/runtime.json @@ -32,6 +32,7 @@ "upgrading": "Upgrading module: __name__ to version: __version__", "upgraded": "Upgraded module: __name__. Restart Node-RED to use the new version", "upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found", + "install-failed-not-found": "$t(server.install.install-failed-long) module not found", "uninstalling": "Uninstalling module: __name__", "uninstall-failed": "Uninstall failed", "uninstall-failed-long": "Uninstall of module __name__ failed:", diff --git a/red/runtime/log.js b/red/runtime/log.js index d2e28726e..259ae4000 100644 --- a/red/runtime/log.js +++ b/red/runtime/log.js @@ -44,6 +44,8 @@ var levelNames = { var logHandlers = []; +var verbose; + var metricsEnabled = false; var LogHandler = function(settings) { @@ -72,11 +74,15 @@ var consoleLogger = function(msg) { if (msg.level == log.METRIC || msg.level == log.AUDIT) { util.log("["+levelNames[msg.level]+"] "+JSON.stringify(msg)); } else { - var message = msg.msg; - if (typeof message === 'object' && message.toString() === '[object Object]' && message.message) { - message = message.message; + if (verbose && msg.msg.stack) { + util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack); + } else { + var message = msg.msg; + if (typeof message === 'object' && message.toString() === '[object Object]' && message.message) { + message = message.message; + } + util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message); } - util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message); } } @@ -94,6 +100,7 @@ var log = module.exports = { metricsEnabled = false; logHandlers = []; var loggerSettings = {}; + verbose = settings.verbose; if (settings.logging) { var keys = Object.keys(settings.logging); if (keys.length === 0) { diff --git a/red/runtime/nodes/registry/loader.js b/red/runtime/nodes/registry/loader.js index dd4292c23..124fd8e2e 100644 --- a/red/runtime/nodes/registry/loader.js +++ b/red/runtime/nodes/registry/loader.js @@ -315,6 +315,18 @@ function loadNodeSet(node) { return loadPromise; } catch(err) { node.err = err; + var stack = err.stack; + var message; + if (stack) { + var i = stack.indexOf(node.file); + if (i > -1) { + var excerpt = stack.substring(i+node.file.length+1,i+node.file.length+20); + var m = /^(\d+):(\d+)/.exec(excerpt); + if (m) { + node.err = err+" (line:"+m[1]+")"; + } + } + } return when.resolve(node); } } @@ -381,8 +393,10 @@ function getNodeHelp(node,lang) { } if (help) { node.help[lang] = help; + } else if (lang === runtime.i18n.defaultLang) { + return null; } else { - node.help[lang] = node.help[runtime.i18n.defaultLang]; + node.help[lang] = getNodeHelp(node, runtime.i18n.defaultLang); } } return node.help[lang]; diff --git a/test/nodes/core/core/80-template_spec.js b/test/nodes/core/core/80-template_spec.js index 315aef6ae..811e7f179 100644 --- a/test/nodes/core/core/80-template_spec.js +++ b/test/nodes/core/core/80-template_spec.js @@ -166,6 +166,18 @@ describe('template node', function() { }); }); + it('should handle block contexts objects', function(done) { + var flow = [{id:"n1", type:"template", template: "A{{#payload.A}}{{payload.A}}{{.}}{{/payload.A}}B",wires:[["n2"]]},{id:"n2",type:"helper"}]; + helper.load(templateNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload','AabcabcB'); + done(); + }); + n1.receive({payload:{A:"abc"}}); + }); + }); it('should raise error if passed bad template', function(done) { var flow = [{id:"n1", type:"template", field: "payload", template: "payload={{payload",wires:[["n2"]]},{id:"n2",type:"helper"}]; helper.load(templateNode, flow, function() { diff --git a/test/red/runtime/nodes/registry/loader_spec.js b/test/red/runtime/nodes/registry/loader_spec.js index 39b25d55e..87e1c060b 100644 --- a/test/red/runtime/nodes/registry/loader_spec.js +++ b/test/red/runtime/nodes/registry/loader_spec.js @@ -514,7 +514,7 @@ describe("red/nodes/registry/loader",function() { node.enabled.should.be.true(); nodes.registerType.called.should.be.false(); node.should.have.property('err'); - node.err.message.should.eql("fail to require"); + node.err.toString().should.eql("Error: fail to require (line:1)"); done(); }).otherwise(function(err) { @@ -564,6 +564,45 @@ describe("red/nodes/registry/loader",function() { loader.getNodeHelp(node,"fr").should.eql("foo"); fs.readFileSync.calledOnce.should.be.true(); }); + it("loads help, defaulting to en-US content for extra nodes", function() { + stubs.push(sinon.stub(fs,"readFileSync", function(path) { + if (path.indexOf("en-US") >= 0) { + return 'bar'; + } + throw new Error("not found"); + })); + var node = { + template: "/tmp/node/directory/file.html", + help:{} + }; + delete node.help['en-US']; + + loader.getNodeHelp(node,"fr").should.eql("bar"); + node.help['fr'].should.eql("bar"); + fs.readFileSync.calledTwice.should.be.true(); + fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html")); + fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html")); + loader.getNodeHelp(node,"fr").should.eql("bar"); + fs.readFileSync.calledTwice.should.be.true(); + }); + it("fails to load en-US help content", function() { + stubs.push(sinon.stub(fs,"readFileSync", function(path) { + throw new Error("not found"); + })); + var node = { + template: "/tmp/node/directory/file.html", + help:{} + }; + delete node.help['en-US']; + + should.not.exist(loader.getNodeHelp(node,"en-US")); + should.not.exist(node.help['en-US']); + fs.readFileSync.calledTwice.should.be.true(); + fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html")); + fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en/file.html")); + should.not.exist(loader.getNodeHelp(node,"en-US")); + fs.readFileSync.callCount.should.eql(4); + }); }); });