Resync with master

This commit is contained in:
Nick O'Leary 2017-03-01 17:45:09 +00:00
commit 73501f3ad3
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
24 changed files with 318 additions and 184 deletions

View File

@ -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 #### 0.16.1: Maintenance Release
- Add colour swatches to debug when hex colour matched - Add colour swatches to debug when hex colour matched

View File

@ -1037,9 +1037,13 @@ RED.nodes = (function() {
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]]; var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
for (var w2=0;w2<wires.length;w2++) { for (var w2=0;w2<wires.length;w2++) {
if (node_map.hasOwnProperty(wires[w2])) { if (node_map.hasOwnProperty(wires[w2])) {
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]}; if (n.z === node_map[wires[w2]].z) {
addLink(link); var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
new_links.push(link); addLink(link);
new_links.push(link);
} else {
console.log("Warning: dropping link that crosses tabs:",n.id,"->",node_map[wires[w2]].id);
}
} }
} }
} }

View File

@ -102,7 +102,7 @@ RED.settings = (function () {
url: 'settings', url: 'settings',
success: function (data) { success: function (data) {
setProperties(data); setProperties(data);
if (RED.settings.user && RED.settings.user.anonymous) { if (!RED.settings.user || RED.settings.user.anonymous) {
RED.settings.remove("auth-tokens"); RED.settings.remove("auth-tokens");
} }
console.log("Node-RED: " + data.version); console.log("Node-RED: " + data.version);

View File

@ -164,10 +164,12 @@ RED.clipboard = (function() {
dialogContainer.empty(); dialogContainer.empty();
dialogContainer.append($(exportNodesDialog)); dialogContainer.append($(exportNodesDialog));
dialogContainer.i18n(); dialogContainer.i18n();
var format = RED.settings.flowFilePretty ? "export-format-full" : "export-format-mini";
$("#export-format-group > a").click(function(evt) { $("#export-format-group > a").click(function(evt) {
evt.preventDefault(); evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) { if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
$("#clipboard-export").focus();
return; return;
} }
$(this).parent().children().removeClass('selected'); $(this).parent().children().removeClass('selected');
@ -177,18 +179,21 @@ RED.clipboard = (function() {
if (flow.length > 0) { if (flow.length > 0) {
var nodes = JSON.parse(flow); var nodes = JSON.parse(flow);
var format = $(this).attr('id'); format = $(this).attr('id');
if (format === 'export-format-full') { if (format === 'export-format-full') {
flow = JSON.stringify(nodes,null,4); flow = JSON.stringify(nodes,null,4);
} else { } else {
flow = JSON.stringify(nodes); flow = JSON.stringify(nodes);
} }
$("#clipboard-export").val(flow); $("#clipboard-export").val(flow);
$("#clipboard-export").focus();
} }
}); });
$("#export-range-group > a").click(function(evt) { $("#export-range-group > a").click(function(evt) {
evt.preventDefault(); evt.preventDefault();
if ($(this).hasClass('disabled') || $(this).hasClass('selected')) { if ($(this).hasClass('disabled') || $(this).hasClass('selected')) {
$("#clipboard-export").focus();
return; return;
} }
$(this).parent().children().removeClass('selected'); $(this).parent().children().removeClass('selected');
@ -209,7 +214,7 @@ RED.clipboard = (function() {
nodes = RED.nodes.createCompleteNodeSet(false); nodes = RED.nodes.createCompleteNodeSet(false);
} }
if (nodes !== null) { if (nodes !== null) {
if (RED.settings.flowFilePretty) { if (format === "export-format-full") {
flow = JSON.stringify(nodes,null,4); flow = JSON.stringify(nodes,null,4);
} else { } else {
flow = JSON.stringify(nodes); flow = JSON.stringify(nodes);
@ -221,6 +226,7 @@ RED.clipboard = (function() {
$("#export-copy").addClass('disabled'); $("#export-copy").addClass('disabled');
} }
$("#clipboard-export").val(flow); $("#clipboard-export").val(flow);
$("#clipboard-export").focus();
}) })
$("#clipboard-dialog-ok").hide(); $("#clipboard-dialog-ok").hide();
@ -234,7 +240,7 @@ RED.clipboard = (function() {
$("#export-range-selected").addClass('disabled').removeClass('selected'); $("#export-range-selected").addClass('disabled').removeClass('selected');
$("#export-range-flow").click(); $("#export-range-flow").click();
} }
if (RED.settings.flowFilePretty) { if (format === "export-format-full") {
$("#export-format-full").click(); $("#export-format-full").click();
} else { } else {
$("#export-format-mini").click(); $("#export-format-mini").click();

View File

@ -421,7 +421,7 @@ RED.editor = (function() {
* @param definition - the node definition * @param definition - the node definition
* @param prefix - the prefix to use in the input element ids (node-input|node-config-input) * @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) { for (var d in definition.defaults) {
if (definition.defaults.hasOwnProperty(d)) { if (definition.defaults.hasOwnProperty(d)) {
if (definition.defaults[d].type) { if (definition.defaults[d].type) {
@ -465,6 +465,9 @@ RED.editor = (function() {
} }
} }
validateNodeEditor(node,prefix); validateNodeEditor(node,prefix);
if (done) {
done();
}
} }
if (definition.credentials) { 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 trayFooter = tray.find(".editor-tray-footer");
var trayBody = tray.find('.editor-tray-body'); var trayBody = tray.find('.editor-tray-body');
trayBody.parent().css('overflow','hidden'); trayBody.parent().css('overflow','hidden');
@ -977,8 +980,10 @@ RED.editor = (function() {
buildEditForm(nodeProperties.content,"dialog-form",type,ns); buildEditForm(nodeProperties.content,"dialog-form",type,ns);
buildLabelForm(portLabels.content,node); buildLabelForm(portLabels.content,node);
prepareEditDialog(node,node._def,"node-input"); prepareEditDialog(node,node._def,"node-input", function() {
trayBody.i18n(); trayBody.i18n();
done();
});
}, },
close: function() { close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) { if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
@ -1063,11 +1068,11 @@ RED.editor = (function() {
try { try {
editing_config_node._def.oneditresize.call(editing_config_node,{width:form.width(),height:form.height()}); editing_config_node._def.oneditresize.call(editing_config_node,{width:form.width(),height:form.height()});
} catch(err) { } 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 trayHeader = tray.find(".editor-tray-header");
var trayFooter = tray.find(".editor-tray-footer"); 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); var dialogForm = buildEditForm(tray.find('.editor-tray-body'),"node-config-dialog-edit-form",type,ns);
prepareEditDialog(editing_config_node,node_def,"node-config-input"); prepareEditDialog(editing_config_node,node_def,"node-config-input", function() {
if (editing_config_node._def.exclusive) { if (editing_config_node._def.exclusive) {
$("#node-config-dialog-scope").hide(); $("#node-config-dialog-scope").hide();
} else { } else {
$("#node-config-dialog-scope").show(); $("#node-config-dialog-scope").show();
} }
$("#node-config-dialog-scope-warning").hide(); $("#node-config-dialog-scope-warning").hide();
var nodeUserFlows = {}; var nodeUserFlows = {};
editing_config_node.users.forEach(function(n) { editing_config_node.users.forEach(function(n) {
nodeUserFlows[n.z] = true; nodeUserFlows[n.z] = true;
});
var flowCount = Object.keys(nodeUserFlows).length;
var tabSelect = $("#node-config-dialog-scope").empty();
tabSelect.off("change");
tabSelect.append('<option value=""'+(!editing_config_node.z?" selected":"")+' data-i18n="sidebar.config.global"></option>');
tabSelect.append('<option disabled data-i18n="sidebar.config.flows"></option>');
RED.nodes.eachWorkspace(function(ws) {
var workspaceLabel = ws.label;
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
});
tabSelect.append('<option disabled data-i18n="sidebar.config.subflows"></option>');
RED.nodes.eachSubflow(function(ws) {
var workspaceLabel = ws.name;
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
});
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 flowCount = Object.keys(nodeUserFlows).length;
tabSelect.i18n(); var tabSelect = $("#node-config-dialog-scope").empty();
tabSelect.off("change");
tabSelect.append('<option value=""'+(!editing_config_node.z?" selected":"")+' data-i18n="sidebar.config.global"></option>');
tabSelect.append('<option disabled data-i18n="sidebar.config.flows"></option>');
RED.nodes.eachWorkspace(function(ws) {
var workspaceLabel = ws.label;
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
});
tabSelect.append('<option disabled data-i18n="sidebar.config.subflows"></option>');
RED.nodes.eachSubflow(function(ws) {
var workspaceLabel = ws.name;
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
});
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(); dialogForm.i18n();
if (node_def.hasUsers !== false) { if (node_def.hasUsers !== false) {
$("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show(); $("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show();
} }
done();
});
}, },
close: function() { close: function() {
RED.workspaces.refresh(); RED.workspaces.refresh();

View File

@ -66,6 +66,15 @@ RED.sidebar.info = (function() {
return value; 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) { function refresh(node) {
tips.stop(); tips.stop();
$(content).empty(); $(content).empty();
@ -117,14 +126,14 @@ RED.sidebar.info = (function() {
$("<hr/>").appendTo(content); $("<hr/>").appendTo(content);
if (!subflowNode && node.type != "comment") { if (!subflowNode && node.type != "comment") {
var helpText = $("script[data-help-name$='"+node.type+"']").html()||""; var helpText = $("script[data-help-name$='"+node.type+"']").html()||"";
$('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(helpText)+'">'+helpText+'</span></div>').appendTo(content); addTargetToExternalLinks($('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(helpText)+'">'+helpText+'</span></div>').appendTo(content));
} }
if (subflowNode) { if (subflowNode) {
$('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.info||"")+'">'+marked(subflowNode.info||"")+'</span></div>').appendTo(content); addTargetToExternalLinks($('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.info||"")+'">'+marked(subflowNode.info||"")+'</span></div>').appendTo(content));
} else if (node._def && node._def.info) { } else if (node._def && node._def.info) {
var info = node._def.info; var info = node._def.info;
var textInfo = (typeof info === "function" ? info.call(node) : info); var textInfo = (typeof info === "function" ? info.call(node) : info);
$('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(textInfo)+'">'+marked(textInfo)+'</span></div>').appendTo(content); addTargetToExternalLinks($('<div class="node-help"><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(textInfo)+'">'+marked(textInfo)+'</span></div>').appendTo(content));
//$('<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>'; //$('<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
} }

View File

@ -104,85 +104,70 @@ RED.tray = (function() {
} }
}); });
if (options.open) { function finishBuild() {
options.open(el); $("#header-shade").show();
} $("#editor-shade").show();
$("#palette-shade").show();
$(".sidebar-shade").show();
$("#header-shade").show(); tray.preferredWidth = Math.max(el.width(),500);
$("#editor-shade").show(); body.css({"minWidth":tray.preferredWidth-40});
$("#palette-shade").show();
$(".sidebar-shade").show();
tray.preferredWidth = Math.max(el.width(),500); if (options.width) {
body.css({"minWidth":tray.preferredWidth-40}); 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) { el.width(options.width);
options.width = $("#editor-stack").position().left-8; } else {
el.width(tray.preferredWidth);
} }
el.width(options.width);
} else {
el.width(tray.preferredWidth);
}
tray.width = el.width(); tray.width = el.width();
if (tray.width > $("#editor-stack").position().left-8) { if (tray.width > $("#editor-stack").position().left-8) {
tray.width = Math.max(0/*tray.preferredWidth*/,$("#editor-stack").position().left-8); tray.width = Math.max(0/*tray.preferredWidth*/,$("#editor-stack").position().left-8);
el.width(tray.width); 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({ el.css({
right: -(el.width()+10)+"px", right: -(el.width()+10)+"px",
transition: "right 0.25s ease" transition: "right 0.25s ease"
}); });
$("#workspace").scrollLeft(0); $("#workspace").scrollLeft(0);
handleWindowResize(); handleWindowResize();
openingTray = true; openingTray = true;
setTimeout(function() {
setTimeout(function() { 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() { setTimeout(function() {
// Delay resetting the flag, so we don't close prematurely if (!options.width) {
openingTray = false; el.width(Math.min(tray.preferredWidth,$("#editor-stack").position().left-8));
},200); }
body.find(":focusable:first").focus(); if (options.resize) {
options.resize({width:el.width()});
},150); }
el.css({right:0}); if (options.show) {
},0); options.show();
}
// growButton.click(function(e) { setTimeout(function() {
// e.preventDefault(); // Delay resetting the flag, so we don't close prematurely
// tray.lastWidth = tray.width; openingTray = false;
// tray.width = $("#editor-stack").position().left-8; },200);
// el.width(tray.width); body.find(":focusable:first").focus();
// 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});
// }
// });
},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() { function handleWindowResize() {

View File

@ -1311,7 +1311,7 @@ RED.view = (function() {
sp.className = className; sp.className = className;
sp.style.position = "absolute"; sp.style.position = "absolute";
sp.style.top = "-1000px"; sp.style.top = "-1000px";
sp.innerHTML = (str||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); sp.textContent = (str||"");
document.body.appendChild(sp); document.body.appendChild(sp);
var w = sp.offsetWidth; var w = sp.offsetWidth;
var h = sp.offsetHeight; var h = sp.offsetHeight;
@ -2323,10 +2323,10 @@ RED.view = (function() {
d.x2 = d.target.x-d.target.w/2; d.x2 = d.target.x-d.target.w/2;
d.y2 = d.target.y; d.y2 = d.target.y;
return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+ return "M "+d.x1+" "+d.y1+
" C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+ " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
(d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+ (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
(d.target.x-d.target.w/2)+" "+d.target.y; d.x2+" "+d.y2;
}); });
} }
}) })

View File

@ -170,7 +170,7 @@
this.handleDebugMessage = function(t,o) { this.handleDebugMessage = function(t,o) {
var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z); var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
if (sourceNode) { 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); RED.debug.handleDebugMessage(o);

View File

@ -47,10 +47,13 @@
<p>Calls out to a system command.<br/></p> <p>Calls out to a system command.<br/></p>
<p>Provides 3 outputs: stdout, stderr, and return code.</p> <p>Provides 3 outputs: stdout, stderr, and return code.</p>
<p>By default uses the <code>exec</code> system call which calls the command, then gets a callback <p>By default uses the <code>exec</code> system call which calls the command, then gets a callback
on completion, returning the complete result in one message, along with any errors.</p> 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.</p>
<p>Optionally can use <code>spawn</code> instead, which returns the output from stdout and stderr <p>Optionally can use <code>spawn</code> 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 as the command runs (usually one line at a time). On completion it then returns a return code
(on the 3rd output).</p> (on the 3rd output).</p>
<p>The <code>exec</code> 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.</p>
<p>The optional append gets added to the command after <code>msg.payload</code> - so you can do <p>The optional append gets added to the command after <code>msg.payload</code> - so you can do
things like pipe the result to another command.</p> things like pipe the result to another command.</p>
<p>Commands or parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p> <p>Commands or parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p>

View File

@ -115,12 +115,17 @@ module.exports = function(RED) {
child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) { child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
msg.payload = new Buffer(stdout,"binary"); msg.payload = new Buffer(stdout,"binary");
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); } if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
var msg2 = {payload:stderr}; var msg2 = null;
var msg3 = null; if(stderr) {
msg2 = {payload: stderr};
}
var msg3 = {payload:0};
node.status({});
//console.log('[exec] stdout: ' + stdout); //console.log('[exec] stdout: ' + stdout);
//console.log('[exec] stderr: ' + stderr); //console.log('[exec] stderr: ' + stderr);
if (error !== null) { 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); //console.log('[exec] error: ' + error);
} }
if (!msg3) { node.status({}); } if (!msg3) { node.status({}); }

View File

@ -22,8 +22,8 @@ module.exports = function(RED) {
* Custom Mustache Context capable to resolve message property and node * Custom Mustache Context capable to resolve message property and node
* flow and global context * flow and global context
*/ */
function NodeContext(msg, nodeContext) { function NodeContext(msg, nodeContext,parent) {
this.msgContext = new mustache.Context(msg); this.msgContext = new mustache.Context(msg,parent);
this.nodeContext = nodeContext; this.nodeContext = nodeContext;
} }
@ -31,26 +31,34 @@ module.exports = function(RED) {
NodeContext.prototype.lookup = function (name) { NodeContext.prototype.lookup = function (name) {
// try message first: // try message first:
var value = this.msgContext.lookup(name); try {
if (value !== undefined) { var value = this.msgContext.lookup(name);
return value; 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);
} }
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) { function TemplateNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.name = n.name; this.name = n.name;
@ -65,7 +73,7 @@ module.exports = function(RED) {
try { try {
var value; var value;
if (node.syntax === "mustache") { 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 { } else {
value = node.template; value = node.template;
} }

View File

@ -205,7 +205,7 @@ RED.debug = (function() {
var metaRow = $('<div class="debug-message-meta"></div>').appendTo(msg); var metaRow = $('<div class="debug-message-meta"></div>').appendTo(msg);
$('<span class="debug-message-date">'+ getTimestamp()+'</span>').appendTo(metaRow); $('<span class="debug-message-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
if (sourceNode) { if (sourceNode) {
$('<a>',{href:"#",class:"debug-message-name"}).html('node: '+sourceNode.id) $('<a>',{href:"#",class:"debug-message-name"}).html('node: '+(sourceNode.name||sourceNode.id))
.appendTo(metaRow) .appendTo(metaRow)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();

View File

@ -84,7 +84,7 @@ module.exports = function(RED) {
node.child.stdout.on('data', function (data) { node.child.stdout.on('data', function (data) {
var d = data.toString().trim().split("\n"); var d = data.toString().trim().split("\n");
for (var i = 0; i < d.length; i++) { 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.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
} }
node.buttonState = d[i]; node.buttonState = d[i];

View File

@ -99,9 +99,9 @@ if len(sys.argv) > 2:
elif cmd == "in": elif cmd == "in":
#print "Initialised pin "+str(pin)+" to IN" #print "Initialised pin "+str(pin)+" to IN"
bounce = int(sys.argv[4]) bounce = float(sys.argv[4])
def handle_callback(chan): def handle_callback(chan):
sleep(bounce/1000) sleep(bounce/1000.0)
print GPIO.input(chan) print GPIO.input(chan)
if sys.argv[3].lower() == "up": if sys.argv[3].lower() == "up":
@ -112,7 +112,7 @@ if len(sys.argv) > 2:
GPIO.setup(pin,GPIO.IN) GPIO.setup(pin,GPIO.IN)
print GPIO.input(pin) 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: while True:
try: try:

View File

@ -36,6 +36,7 @@ module.exports = function(RED) {
node.closing = false; node.closing = false;
function startconn() { // Connect to remote endpoint function startconn() { // Connect to remote endpoint
node.tout = null;
var socket = new ws(node.path); var socket = new ws(node.path);
socket.setMaxListeners(0); socket.setMaxListeners(0);
node.server = socket; // keep for closing 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); } if (node.isServer) { delete node._clients[id]; node.emit('closed',Object.keys(node._clients).length); }
else { node.emit('closed'); } else { node.emit('closed'); }
if (!node.closing && !node.isServer) { if (!node.closing && !node.isServer) {
clearTimeout(node.tout);
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? 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) { socket.on('error', function(err) {
node.emit('erro'); node.emit('erro');
if (!node.closing && !node.isServer) { if (!node.closing && !node.isServer) {
clearTimeout(node.tout);
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
} }
}); });
@ -123,7 +126,10 @@ module.exports = function(RED) {
else { else {
node.closing = true; node.closing = true;
node.server.close(); 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.error(RED._("websocket.errors.missing-conf"));
} }
this.on('close', function() { this.on('close', function() {
node.serverConfig.removeInputNode(node); if (node.serverConfig) {
node.serverConfig.removeInputNode(node);
}
node.status({}); node.status({});
}); });
} }
@ -217,7 +225,7 @@ module.exports = function(RED) {
this.server = (n.client)?n.client:n.server; this.server = (n.client)?n.client:n.server;
this.serverConfig = RED.nodes.getNode(this.server); this.serverConfig = RED.nodes.getNode(this.server);
if (!this.serverConfig) { if (!this.serverConfig) {
this.error(RED._("websocket.errors.missing-conf")); return this.error(RED._("websocket.errors.missing-conf"));
} }
else { else {
// TODO: nls // TODO: nls

View File

@ -444,7 +444,7 @@ module.exports = function(RED) {
//node.log(RED._("tcpin.errors.client-connected")); //node.log(RED._("tcpin.errors.client-connected"));
node.status({fill:"green",shape:"dot",text:"common.status.connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
if (clients[connection_id] && clients[connection_id].client) { 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); 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) { 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]) { if (clients[connection_id]) {
clients[connection_id].msg.payload = data; 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) { else if (node.splitc === 0) {
@ -533,7 +533,7 @@ module.exports = function(RED) {
//console.log("END"); //console.log("END");
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"}); node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
if (clients[connection_id] && clients[connection_id].client) { if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = false; clients[connection_id].connected = false;
clients[connection_id].client = null; clients[connection_id].client = null;
} }
}); });
@ -541,7 +541,7 @@ module.exports = function(RED) {
clients[connection_id].client.on('close', function() { clients[connection_id].client.on('close', function() {
//console.log("CLOSE"); //console.log("CLOSE");
if (clients[connection_id]) { if (clients[connection_id]) {
clients[connection_id].connected = false; clients[connection_id].connected = false;
} }
var anyConnected = false; var anyConnected = false;

View File

@ -1,6 +1,6 @@
{ {
"name" : "node-red", "name" : "node-red",
"version" : "0.16.1", "version" : "0.16.2",
"description" : "A visual tool for wiring the Internet of Things", "description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org", "homepage" : "http://nodered.org",
"license" : "Apache-2.0", "license" : "Apache-2.0",

17
red.js
View File

@ -196,6 +196,7 @@ try {
function basicAuthMiddleware(user,pass) { function basicAuthMiddleware(user,pass) {
var basicAuth = require('basic-auth'); var basicAuth = require('basic-auth');
var checkPassword; var checkPassword;
var localCachedPassword;
if (pass.length == "32") { if (pass.length == "32") {
// Assume its a legacy md5 password // Assume its a legacy md5 password
checkPassword = function(p) { 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) { return function(req,res,next) {
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return next(); return next();
} }
var requestUser = basicAuth(req); 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'); res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.sendStatus(401); return res.sendStatus(401);
} }

View File

@ -32,6 +32,7 @@
"upgrading": "Upgrading module: __name__ to version: __version__", "upgrading": "Upgrading module: __name__ to version: __version__",
"upgraded": "Upgraded module: __name__. Restart Node-RED to use the new 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", "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__", "uninstalling": "Uninstalling module: __name__",
"uninstall-failed": "Uninstall failed", "uninstall-failed": "Uninstall failed",
"uninstall-failed-long": "Uninstall of module __name__ failed:", "uninstall-failed-long": "Uninstall of module __name__ failed:",

View File

@ -44,6 +44,8 @@ var levelNames = {
var logHandlers = []; var logHandlers = [];
var verbose;
var metricsEnabled = false; var metricsEnabled = false;
var LogHandler = function(settings) { var LogHandler = function(settings) {
@ -72,11 +74,15 @@ var consoleLogger = function(msg) {
if (msg.level == log.METRIC || msg.level == log.AUDIT) { if (msg.level == log.METRIC || msg.level == log.AUDIT) {
util.log("["+levelNames[msg.level]+"] "+JSON.stringify(msg)); util.log("["+levelNames[msg.level]+"] "+JSON.stringify(msg));
} else { } else {
var message = msg.msg; if (verbose && msg.msg.stack) {
if (typeof message === 'object' && message.toString() === '[object Object]' && message.message) { util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack);
message = message.message; } 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; metricsEnabled = false;
logHandlers = []; logHandlers = [];
var loggerSettings = {}; var loggerSettings = {};
verbose = settings.verbose;
if (settings.logging) { if (settings.logging) {
var keys = Object.keys(settings.logging); var keys = Object.keys(settings.logging);
if (keys.length === 0) { if (keys.length === 0) {

View File

@ -315,6 +315,18 @@ function loadNodeSet(node) {
return loadPromise; return loadPromise;
} catch(err) { } catch(err) {
node.err = 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); return when.resolve(node);
} }
} }
@ -381,8 +393,10 @@ function getNodeHelp(node,lang) {
} }
if (help) { if (help) {
node.help[lang] = help; node.help[lang] = help;
} else if (lang === runtime.i18n.defaultLang) {
return null;
} else { } else {
node.help[lang] = node.help[runtime.i18n.defaultLang]; node.help[lang] = getNodeHelp(node, runtime.i18n.defaultLang);
} }
} }
return node.help[lang]; return node.help[lang];

View File

@ -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) { 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"}]; var flow = [{id:"n1", type:"template", field: "payload", template: "payload={{payload",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() { helper.load(templateNode, flow, function() {

View File

@ -514,7 +514,7 @@ describe("red/nodes/registry/loader",function() {
node.enabled.should.be.true(); node.enabled.should.be.true();
nodes.registerType.called.should.be.false(); nodes.registerType.called.should.be.false();
node.should.have.property('err'); 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(); done();
}).otherwise(function(err) { }).otherwise(function(err) {
@ -564,6 +564,45 @@ describe("red/nodes/registry/loader",function() {
loader.getNodeHelp(node,"fr").should.eql("foo"); loader.getNodeHelp(node,"fr").should.eql("foo");
fs.readFileSync.calledOnce.should.be.true(); 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);
});
}); });
}); });