mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
129ca0e39f | ||
|
906703db5f | ||
|
0cd4a2b4ec | ||
|
aef8aaa0bd | ||
|
428fbb8622 | ||
|
b9f03e7d80 | ||
|
db686388b9 | ||
|
626cba4002 | ||
|
37d4a6b9e2 | ||
|
12c4561aba | ||
|
fed49e3718 | ||
|
3af37d3984 | ||
|
0f49a11228 | ||
|
27d3e165b0 | ||
|
e941c22f6c | ||
|
7281e4deb6 | ||
|
f2191e94b3 | ||
|
349ebfe4db | ||
|
708365c4ac | ||
|
0e9ea0aff1 | ||
|
63ba05a193 | ||
|
4b702815cf | ||
|
55e66ebcac | ||
|
dcd8b3699c | ||
|
0e2d13172a | ||
|
2e2556fdad | ||
|
8bfab8f73d |
@@ -23,6 +23,9 @@ RED.history = (function() {
|
||||
undo_history[i].dirty = true;
|
||||
}
|
||||
},
|
||||
list: function() {
|
||||
return undo_history
|
||||
},
|
||||
depth: function() {
|
||||
return undo_history.length;
|
||||
},
|
||||
@@ -284,6 +287,9 @@ RED.history = (function() {
|
||||
RED.workspaces.refresh();
|
||||
RED.sidebar.config.refresh();
|
||||
}
|
||||
},
|
||||
peek: function() {
|
||||
return undo_history[undo_history.length-1];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -103,8 +103,10 @@ var RED = (function() {
|
||||
var id = m.id;
|
||||
RED.nodes.addNodeSet(m);
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
RED.i18n.loadCatalog(id, function() {
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (addedTypes.length) {
|
||||
@@ -142,6 +144,8 @@ var RED = (function() {
|
||||
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
}
|
||||
// Refresh flow library to ensure any examples are updated
|
||||
RED.library.loadFlowLibrary();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -116,6 +116,9 @@ RED.editor = (function() {
|
||||
*/
|
||||
function validateNodeProperty(node,definition,property,value) {
|
||||
var valid = true;
|
||||
if (/^\$\([a-zA-Z_][a-zA-Z0-9_]*\)$/.test(value)) {
|
||||
return true;
|
||||
}
|
||||
if ("required" in definition[property] && definition[property].required) {
|
||||
valid = value !== "";
|
||||
}
|
||||
@@ -126,8 +129,8 @@ RED.editor = (function() {
|
||||
if (!value || value == "_ADD_") {
|
||||
valid = definition[property].hasOwnProperty("required") && !definition[property].required;
|
||||
} else {
|
||||
var v = RED.nodes.node(value).valid;
|
||||
valid = (v==null || v);
|
||||
var configNode = RED.nodes.node(value);
|
||||
valid = (configNode !== null && (configNode.valid == null || configNode.valid));
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,7 +27,9 @@ RED.library = (function() {
|
||||
var li;
|
||||
var a;
|
||||
var ul = document.createElement("ul");
|
||||
ul.id = "menu-item-import-library-submenu";
|
||||
if (root === "") {
|
||||
ul.id = "menu-item-import-library-submenu";
|
||||
}
|
||||
ul.className = "dropdown-menu";
|
||||
if (data.d) {
|
||||
for (i in data.d) {
|
||||
@@ -36,7 +38,8 @@ RED.library = (function() {
|
||||
li.className = "dropdown-submenu pull-left";
|
||||
a = document.createElement("a");
|
||||
a.href="#";
|
||||
a.innerHTML = i;
|
||||
var label = i.replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/," ").replace(/_/," ");
|
||||
a.innerHTML = label;
|
||||
li.appendChild(a);
|
||||
li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i));
|
||||
ul.appendChild(li);
|
||||
@@ -53,7 +56,7 @@ RED.library = (function() {
|
||||
a.flowName = root+(root!==""?"/":"")+data.f[i];
|
||||
a.onclick = function() {
|
||||
$.get('library/flows/'+this.flowName, function(data) {
|
||||
RED.view.importNodes(data);
|
||||
RED.view.importNodes(data);
|
||||
});
|
||||
};
|
||||
li.appendChild(a);
|
||||
@@ -63,7 +66,17 @@ RED.library = (function() {
|
||||
}
|
||||
return ul;
|
||||
};
|
||||
var examples;
|
||||
if (data.d && data.d._examples_) {
|
||||
examples = data.d._examples_;
|
||||
delete data.d._examples_;
|
||||
}
|
||||
var menu = buildMenu(data,"");
|
||||
$("#menu-item-import-examples").remove();
|
||||
if (examples) {
|
||||
RED.menu.addItem("menu-item-import",{id:"menu-item-import-examples",label:RED._("menu.label.examples"),options:[]})
|
||||
$("#menu-item-import-examples-submenu").replaceWith(buildMenu(examples,"_examples_"));
|
||||
}
|
||||
//TODO: need an api in RED.menu for this
|
||||
$("#menu-item-import-library-submenu").replaceWith(menu);
|
||||
});
|
||||
@@ -117,7 +130,7 @@ RED.library = (function() {
|
||||
$(".active",bc).removeClass("active");
|
||||
bc.append(bcli);
|
||||
$.getJSON("library/"+options.url+root+dirName,function(data) {
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -125,20 +138,20 @@ RED.library = (function() {
|
||||
ul.appendChild(li);
|
||||
} else {
|
||||
// file
|
||||
li = buildFileListItem(v);
|
||||
li.innerHTML = v.name;
|
||||
li.onclick = (function() {
|
||||
var item = v;
|
||||
return function(e) {
|
||||
$(".list-selected",ul).removeClass("list-selected");
|
||||
$(this).addClass("list-selected");
|
||||
$.get("library/"+options.url+root+item.fn, function(data) {
|
||||
selectedLibraryItem = item;
|
||||
libraryEditor.setValue(data,-1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
ul.appendChild(li);
|
||||
li = buildFileListItem(v);
|
||||
li.innerHTML = v.name;
|
||||
li.onclick = (function() {
|
||||
var item = v;
|
||||
return function(e) {
|
||||
$(".list-selected",ul).removeClass("list-selected");
|
||||
$(this).addClass("list-selected");
|
||||
$.get("library/"+options.url+root+item.fn, function(data) {
|
||||
selectedLibraryItem = item;
|
||||
libraryEditor.setValue(data,-1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
return ul;
|
||||
@@ -428,8 +441,8 @@ RED.library = (function() {
|
||||
data: $("#node-input-library-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify(RED._("library.savedNodes"),"success");
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify(RED._("library.savedNodes"),"success");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
||||
});
|
||||
@@ -452,7 +465,7 @@ RED.library = (function() {
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
});
|
||||
exportToLibraryDialog.children(".dialog-form").append($(
|
||||
'<div class="form-row">'+
|
||||
'<label for="node-input-library-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>'+
|
||||
|
@@ -66,7 +66,7 @@ RED.palette = (function() {
|
||||
var lineHeight = 20;
|
||||
var portHeight = 10;
|
||||
|
||||
var words = label.split(" ");
|
||||
var words = label.split(/[ -]/);
|
||||
|
||||
var displayLines = [];
|
||||
|
||||
@@ -134,22 +134,27 @@ RED.palette = (function() {
|
||||
d.id = "palette_node_"+nodeTypeId;
|
||||
d.type = nt;
|
||||
|
||||
var label;
|
||||
|
||||
if (typeof def.paletteLabel === "undefined") {
|
||||
label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
|
||||
} else {
|
||||
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
|
||||
var label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
|
||||
if (typeof def.paletteLabel !== "undefined") {
|
||||
try {
|
||||
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+nt+".paletteLabel",err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
|
||||
|
||||
d.className="palette_node";
|
||||
|
||||
|
||||
if (def.icon) {
|
||||
var icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
|
||||
var icon_url = "arrow-in.png";
|
||||
try {
|
||||
icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+nt+".icon",err);
|
||||
}
|
||||
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
|
||||
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
|
||||
}
|
||||
|
@@ -350,7 +350,11 @@ RED.view = (function() {
|
||||
}
|
||||
|
||||
if (nn._def.onadd) {
|
||||
nn._def.onadd.call(nn);
|
||||
try {
|
||||
nn._def.onadd.call(nn);
|
||||
} catch(err) {
|
||||
console.log("onadd:",err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var subflow = RED.nodes.subflow(m[1]);
|
||||
@@ -396,7 +400,7 @@ RED.view = (function() {
|
||||
|
||||
var spliceLink = $(ui.helper).data('splice');
|
||||
if (spliceLink) {
|
||||
// TODO: DRY - canvasMouseUp
|
||||
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
|
||||
RED.nodes.removeLink(spliceLink);
|
||||
var link1 = {
|
||||
source:spliceLink.source,
|
||||
@@ -643,7 +647,7 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mouse_mode == RED.state.MOVING_ACTIVE && moving_set.length === 1) {
|
||||
if ((mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) && moving_set.length === 1) {
|
||||
node = moving_set[0];
|
||||
if (spliceActive) {
|
||||
if (!spliceTimer) {
|
||||
@@ -766,7 +770,7 @@ RED.view = (function() {
|
||||
}
|
||||
historyEvent = {t:'move',nodes:ns,dirty:RED.nodes.dirty()};
|
||||
if (activeSpliceLink) {
|
||||
// TODO: DRY - droppable
|
||||
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
|
||||
var spliceLink = d3.select(activeSpliceLink).data()[0];
|
||||
RED.nodes.removeLink(spliceLink);
|
||||
var link1 = {
|
||||
@@ -1181,6 +1185,29 @@ RED.view = (function() {
|
||||
//RED.touch.radialMenu.show(d3.select(this),pos);
|
||||
if (mouse_mode == RED.state.IMPORT_DRAGGING) {
|
||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||
|
||||
if (activeSpliceLink) {
|
||||
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
|
||||
var spliceLink = d3.select(activeSpliceLink).data()[0];
|
||||
RED.nodes.removeLink(spliceLink);
|
||||
var link1 = {
|
||||
source:spliceLink.source,
|
||||
sourcePort:spliceLink.sourcePort,
|
||||
target: moving_set[0].n
|
||||
};
|
||||
var link2 = {
|
||||
source:moving_set[0].n,
|
||||
sourcePort:0,
|
||||
target: spliceLink.target
|
||||
};
|
||||
RED.nodes.addLink(link1);
|
||||
RED.nodes.addLink(link2);
|
||||
var historyEvent = RED.history.peek();
|
||||
historyEvent.links = [link1,link2];
|
||||
historyEvent.removedLinks = [spliceLink];
|
||||
updateActiveNodes();
|
||||
}
|
||||
|
||||
updateSelection();
|
||||
RED.nodes.dirty(true);
|
||||
redraw();
|
||||
@@ -1253,7 +1280,11 @@ RED.view = (function() {
|
||||
d.dirty = true;
|
||||
}
|
||||
if (d._def.button.onclick) {
|
||||
d._def.button.onclick.call(d);
|
||||
try {
|
||||
d._def.button.onclick.call(d);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".onclick",err);
|
||||
}
|
||||
}
|
||||
if (d.dirty) {
|
||||
redraw();
|
||||
@@ -1408,7 +1439,12 @@ RED.view = (function() {
|
||||
var node = d3.select(this);
|
||||
node.attr("id",d.id);
|
||||
var l = d._def.label;
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
try {
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".label",err);
|
||||
l = d.type;
|
||||
}
|
||||
d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) );
|
||||
d.h = Math.max(node_height,(d.outputs||0) * 15);
|
||||
|
||||
@@ -1591,7 +1627,12 @@ RED.view = (function() {
|
||||
//if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette
|
||||
if (d.resize) {
|
||||
var l = d._def.label;
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
try {
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".label",err);
|
||||
l = d.type;
|
||||
}
|
||||
var ow = d.w;
|
||||
d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) );
|
||||
d.h = Math.max(node_height,(d.outputs||0) * 15);
|
||||
@@ -1659,20 +1700,33 @@ RED.view = (function() {
|
||||
});
|
||||
}
|
||||
thisNode.selectAll('text.node_label').text(function(d,i){
|
||||
var l = "";
|
||||
if (d._def.label) {
|
||||
if (typeof d._def.label == "function") {
|
||||
return d._def.label.call(d);
|
||||
} else {
|
||||
return d._def.label;
|
||||
l = d._def.label;
|
||||
try {
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".label",err);
|
||||
l = d.type;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
return l;
|
||||
})
|
||||
.attr('y', function(d){return (d.h/2)-1;})
|
||||
.attr('class',function(d){
|
||||
var s = "";
|
||||
if (d._def.labelStyle) {
|
||||
s = d._def.labelStyle;
|
||||
try {
|
||||
s = (typeof s === "function" ? s.call(d) : s)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".labelStyle",err);
|
||||
s = "";
|
||||
}
|
||||
s = " "+s;
|
||||
}
|
||||
return 'node_label'+
|
||||
(d._def.align?' node_label_'+d._def.align:'')+
|
||||
(d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ;
|
||||
(d._def.align?' node_label_'+d._def.align:'')+s;
|
||||
});
|
||||
|
||||
if (d._def.icon) {
|
||||
@@ -1680,7 +1734,12 @@ RED.view = (function() {
|
||||
var current_url = icon.attr("xlink:href");
|
||||
var icon_url;
|
||||
if (typeof d._def.icon == "function") {
|
||||
icon_url = d._def.icon.call(d);
|
||||
try {
|
||||
icon_url = d._def.icon.call(d);
|
||||
} catch(err) {
|
||||
console.log("icon",err);
|
||||
icon_url = "arrow-in.png";
|
||||
}
|
||||
} else {
|
||||
icon_url = d._def.icon;
|
||||
}
|
||||
@@ -1744,7 +1803,12 @@ RED.view = (function() {
|
||||
thisNode.selectAll('text.node_badge_label').text(function(d,i) {
|
||||
if (d._def.badge) {
|
||||
if (typeof d._def.badge == "function") {
|
||||
return d._def.badge.call(d);
|
||||
try {
|
||||
return d._def.badge.call(d);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".badge",err);
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return d._def.badge;
|
||||
}
|
||||
@@ -1952,6 +2016,12 @@ RED.view = (function() {
|
||||
}
|
||||
if (!touchImport) {
|
||||
mouse_mode = RED.state.IMPORT_DRAGGING;
|
||||
spliceActive = false;
|
||||
if (new_ms.length === 1) {
|
||||
node = new_ms[0];
|
||||
spliceActive = node.n._def.inputs > 0 &&
|
||||
node.n._def.outputs > 0;
|
||||
}
|
||||
}
|
||||
|
||||
RED.keyboard.add(/* ESCAPE */ 27,function(){
|
||||
|
@@ -157,6 +157,9 @@
|
||||
<p>The repeat function allows the payload to be sent on the required schedule.</p>
|
||||
<p>The <i>Inject once at start</i> option actually waits a short interval before firing
|
||||
to give other nodes a chance to instantiate properly.</p>
|
||||
<p>The <i>Flow</i> and <i>Global</i> options allow one to inject a flow or global
|
||||
context value.
|
||||
</p>
|
||||
<p><b>Note: </b>"Interval between times" and "at a specific time" uses cron.
|
||||
This means that 20 minutes will be at the next hour, 20 minutes past and
|
||||
40 minutes past - not in 20 minutes time. If you want every 20 minutes
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2015 IBM Corp.
|
||||
Copyright 2015, 2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -106,7 +106,7 @@
|
||||
outputs:1,
|
||||
icon: "alert.png",
|
||||
label: function() {
|
||||
return this.name||this.scope?this._("catch.catchNodes",{number:this.scope.length}):this._("catch.catch");
|
||||
return this.name||(this.scope?this._("catch.catchNodes",{number:this.scope.length}):this._("catch.catch"));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2015 IBM Corp.
|
||||
Copyright 2015, 2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -97,7 +97,7 @@
|
||||
outputs:1,
|
||||
icon: "alert.png",
|
||||
label: function() {
|
||||
return this.name||this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status");
|
||||
return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status"));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@@ -19,15 +19,16 @@
|
||||
<div class="form-row">
|
||||
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
|
||||
<select id="node-input-pauseType" style="width:270px !important">
|
||||
<option value="delay" data-i18n="delay.delaymsg"></option>
|
||||
<option value="random" data-i18n="delay.randomdelay"></option>
|
||||
<option value="rate" data-i18n="delay.limitrate"></option>
|
||||
<option value="queue" data-i18n="delay.fairqueue"></option>
|
||||
<option value="delay" data-i18n="delay.delaymsg"></option>
|
||||
<option value="random" data-i18n="delay.randomdelay"></option>
|
||||
<option value="rate" data-i18n="delay.limitrate"></option>
|
||||
<option value="queue" data-i18n="delay.fairqueue"></option>
|
||||
<option value="timed" data-i18n="delay.timedqueue"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="delay-details" class="form-row">
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="delay.for"></span></label>
|
||||
<input type="text" id="node-input-timeout" placeholder="Time" style="text-align:right; width:50px !important">
|
||||
<input type="text" id="node-input-timeout" placeholder="Time" style="text-align:end; width:50px !important">
|
||||
<select id="node-input-timeoutUnits" style="width:200px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
@@ -39,7 +40,7 @@
|
||||
|
||||
<div id="rate-details" class="form-row">
|
||||
<label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="delay.rate"></span></label>
|
||||
<input type="text" id="node-input-rate" placeholder="1" style="text-align:right; width:30px !important">
|
||||
<input type="text" id="node-input-rate" placeholder="1" style="text-align:end; width:30px !important">
|
||||
<label for="node-input-rateUnits"><span data-i18n="delay.msgper"></span></label>
|
||||
<select id="node-input-rateUnits" style="width:140px !important">
|
||||
<option value="second" data-i18n="delay.sec"></option>
|
||||
@@ -54,9 +55,9 @@
|
||||
|
||||
<div id="random-details" class="form-row">
|
||||
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
|
||||
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:right; width:30px !important">
|
||||
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:30px !important">
|
||||
&
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:right; width:30px !important">
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:30px !important">
|
||||
<select id="node-input-randomUnits" style="width:140px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
@@ -82,6 +83,9 @@
|
||||
At each "tick", derived from the rate, the next "topic" is released.
|
||||
Any messages arriving on the same topic before release replace those in that position in the queue.
|
||||
So each "topic" gets a turn - but the most recent value is always the one sent.</p>
|
||||
<p>The "timed release queue" adds messages to an array based on their <code>msg.topic</code> property.
|
||||
At each "tick", all the latest messages are released. Any new messages arriving before release will
|
||||
replace those of the same topic.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -114,6 +118,9 @@
|
||||
} else if (this.pauseType == "random") {
|
||||
return this.name || this._("delay.label.random");
|
||||
}
|
||||
else if (this.pauseType == "timed") {
|
||||
return this.name || this.rate+" "+this._("delay.label.timed")+" "+this.rateUnits;
|
||||
}
|
||||
else {
|
||||
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
|
||||
return this.name || this._("delay.label.queue")+" "+this.rate+" msg/"+units;
|
||||
@@ -149,6 +156,11 @@
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.pauseType == "timed") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
}
|
||||
|
||||
if (!this.timeoutUnits) {
|
||||
@@ -184,6 +196,11 @@
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.value == "timed") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -147,10 +147,17 @@ module.exports = function(RED) {
|
||||
node.status({});
|
||||
});
|
||||
|
||||
} else if (this.pauseType === "queue") {
|
||||
} else if ((this.pauseType === "queue") || (this.pauseType === "timed")) {
|
||||
this.intervalID = setInterval(function() {
|
||||
if (node.buffer.length > 0) {
|
||||
node.send(node.buffer.shift()); // send the first on the queue
|
||||
if (this.pauseType === "queue") {
|
||||
if (node.buffer.length > 0) {
|
||||
node.send(node.buffer.shift()); // send the first on the queue
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (node.buffer.length > 0) { // send the whole queue
|
||||
node.send(node.buffer.shift());
|
||||
}
|
||||
}
|
||||
node.status({text:node.buffer.length});
|
||||
},node.rate);
|
||||
|
@@ -32,7 +32,7 @@
|
||||
<option value="wait" data-i18n="trigger.wait-for"></option>
|
||||
</select>
|
||||
<span class="node-type-wait">
|
||||
<input type="text" id="node-input-duration" style="text-align:right; width:70px !important">
|
||||
<input type="text" id="node-input-duration" style="text-align:end; width:70px !important">
|
||||
<select id="node-input-units" style="width:140px !important">
|
||||
<option value="ms" data-i18n="trigger.duration.ms"></option>
|
||||
<option value="s" data-i18n="trigger.duration.s"></option>
|
||||
@@ -60,7 +60,7 @@
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></input>
|
||||
</div>
|
||||
<div class="form-tips" data-i18n="[html]trigger.tip"></div>
|
||||
</script>
|
||||
|
@@ -73,7 +73,7 @@ module.exports = function(RED) {
|
||||
node.child.stdout.on('data', function (data) {
|
||||
data = data.toString().trim();
|
||||
if (data.length > 0) {
|
||||
if (node.buttonState !== -1) {
|
||||
if (node.running && node.buttonState !== -1) {
|
||||
node.send({ topic:"pi/"+node.pin, payload:Number(data) });
|
||||
}
|
||||
node.buttonState = data;
|
||||
@@ -87,8 +87,8 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
@@ -279,8 +279,8 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
|
@@ -16,6 +16,9 @@
|
||||
var util = require("util");
|
||||
var mqtt = require("mqtt");
|
||||
var events = require("events");
|
||||
|
||||
util.log("[warn] nodes/core/io/lib/mqtt.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
|
||||
|
||||
//var inspect = require("util").inspect;
|
||||
|
||||
//var Client = module.exports.Client = function(
|
||||
|
@@ -17,6 +17,8 @@ var util = require("util");
|
||||
var mqtt = require("./mqtt");
|
||||
var settings = require(process.env.NODE_RED_HOME+"/red/red").settings;
|
||||
|
||||
util.log("[warn] nodes/core/io/lib/mqttConnectionPool.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
|
||||
|
||||
var connections = {};
|
||||
|
||||
function matchTopic(ts,t) {
|
||||
@@ -60,7 +62,7 @@ module.exports = {
|
||||
subscribe: function(topic,qos,callback,ref) {
|
||||
ref = ref||0;
|
||||
subscriptions[topic] = subscriptions[topic]||{};
|
||||
|
||||
|
||||
var sub = {
|
||||
topic:topic,
|
||||
qos:qos,
|
||||
@@ -104,7 +106,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
disconnect: function(ref) {
|
||||
|
||||
|
||||
this._instances -= 1;
|
||||
if (this._instances == 0) {
|
||||
client.disconnect();
|
||||
|
@@ -152,6 +152,7 @@
|
||||
"randomdelay": "Random delay",
|
||||
"limitrate": "Limit rate to",
|
||||
"fairqueue": "Topic based fair queue",
|
||||
"timedqueue": "Timed release queue",
|
||||
"milisecs": "Miliseconds",
|
||||
"secs": "Seconds",
|
||||
"sec": "Second",
|
||||
@@ -169,7 +170,8 @@
|
||||
"delay": "delay",
|
||||
"limit": "limit",
|
||||
"random": "random",
|
||||
"queue": "queue"
|
||||
"queue": "queue",
|
||||
"timed": "releases per"
|
||||
},
|
||||
"error": {
|
||||
"buffer": "buffer exceeded 1000 messages"
|
||||
|
@@ -128,21 +128,37 @@
|
||||
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
|
||||
}
|
||||
|
||||
var propertyName = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-name",type:"text"}).appendTo(row1).typedInput({types:['msg','flow','global']});
|
||||
var finalspan = $('<span/>',{style:"float: right; margin-right: 10px;"}).appendTo(row1);
|
||||
var deleteButton = $('<a/>',{href:"#",class:"editor-button editor-button-small", style:"margin-top: 7px; margin-left: 5px;"}).appendTo(finalspan);
|
||||
var propertyName = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-name",type:"text"})
|
||||
.appendTo(row1)
|
||||
.typedInput({types:['msg','flow','global']});
|
||||
var finalspan = $('<span/>',{style:"float: right; margin-right: 10px;"})
|
||||
.appendTo(row1);
|
||||
var deleteButton = $('<a/>',{href:"#",class:"editor-button editor-button-small", style:"margin-top: 7px; margin-left: 5px;"})
|
||||
.appendTo(finalspan);
|
||||
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
||||
|
||||
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"}).text(to).appendTo(row2);
|
||||
var propertyValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2).typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']});
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"})
|
||||
.text(to)
|
||||
.appendTo(row2);
|
||||
var propertyValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-value",type:"text"})
|
||||
.appendTo(row2)
|
||||
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']});
|
||||
var row3_1 = $('<div/>').appendTo(row3);
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"}).text(search).appendTo(row3_1);
|
||||
var fromValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1).typedInput({default:'str',types:['msg','flow','global','str','re']});
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"})
|
||||
.text(search)
|
||||
.appendTo(row3_1);
|
||||
var fromValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-search-value",type:"text"})
|
||||
.appendTo(row3_1)
|
||||
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool']});
|
||||
|
||||
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"}).text(replace).appendTo(row3_2);
|
||||
var toValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2).typedInput({default:'str',types:['msg','flow','global','str','num','json']});
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"})
|
||||
.text(replace)
|
||||
.appendTo(row3_2);
|
||||
var toValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-replace-value",type:"text"})
|
||||
.appendTo(row3_2)
|
||||
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']});
|
||||
|
||||
selectField.change(function() {
|
||||
var width = $("#node-input-rule-container").width();
|
||||
|
@@ -64,12 +64,13 @@ module.exports = function(RED) {
|
||||
if (!rule.fromt) {
|
||||
rule.fromt = "str";
|
||||
}
|
||||
if (rule.t === "change") {
|
||||
if (rule.t === "change" && rule.fromt !== 'msg' && rule.fromt !== 'flow' && rule.fromt !== 'global') {
|
||||
rule.fromRE = rule.from;
|
||||
if (rule.fromt !== 're') {
|
||||
rule.from = rule.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
rule.fromRE = rule.fromRE.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
try {
|
||||
rule.from = new RegExp(rule.from, "g");
|
||||
rule.fromRE = new RegExp(rule.fromRE, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
this.error(RED._("change.errors.invalid-from",{error:e.message}));
|
||||
@@ -93,6 +94,10 @@ module.exports = function(RED) {
|
||||
try {
|
||||
var property = rule.p;
|
||||
var value = rule.to;
|
||||
var current;
|
||||
var fromValue;
|
||||
var fromType;
|
||||
var fromRE;
|
||||
if (rule.tot === "msg") {
|
||||
value = RED.util.getMessageProperty(msg,rule.to);
|
||||
} else if (rule.tot === 'flow') {
|
||||
@@ -100,16 +105,68 @@ module.exports = function(RED) {
|
||||
} else if (rule.tot === 'global') {
|
||||
value = node.context().global.get(rule.to);
|
||||
}
|
||||
|
||||
if (rule.t === 'change') {
|
||||
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||
if (rule.fromt === "msg") {
|
||||
fromValue = RED.util.getMessageProperty(msg,rule.from);
|
||||
} else if (rule.tot === 'flow') {
|
||||
fromValue = node.context().flow.get(rule.from);
|
||||
} else if (rule.tot === 'global') {
|
||||
fromValue = node.context().global.get(rule.from);
|
||||
}
|
||||
if (typeof fromValue === 'number' || fromValue instanceof Number) {
|
||||
fromType = 'num';
|
||||
} else if (typeof fromValue === 'boolean') {
|
||||
fromType = 'bool'
|
||||
} else if (fromValue instanceof RegExp) {
|
||||
fromType = 're';
|
||||
fromRE = fromValue;
|
||||
} else if (typeof fromValue === 'string') {
|
||||
fromType = 'str';
|
||||
fromRE = fromValue.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
try {
|
||||
fromRE = new RegExp(fromRE, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
node.error(RED._("change.errors.invalid-from",{error:e.message}));
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
node.error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)}));
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fromType = rule.fromt;
|
||||
fromValue = rule.from;
|
||||
fromRE = rule.fromRE;
|
||||
}
|
||||
}
|
||||
if (rule.pt === 'msg') {
|
||||
if (rule.t === 'delete') {
|
||||
RED.util.setMessageProperty(msg,property,undefined);
|
||||
} else if (rule.t === 'set') {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else if (rule.t === 'change') {
|
||||
var current = RED.util.getMessageProperty(msg,property);
|
||||
current = RED.util.getMessageProperty(msg,property);
|
||||
if (typeof current === 'string') {
|
||||
current = current.replace(rule.from,value);
|
||||
RED.util.setMessageProperty(msg,property,current);
|
||||
if ((fromType === 'num' || fromType === 'bool') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
RED.util.setMessageProperty(msg,property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -125,16 +182,30 @@ module.exports = function(RED) {
|
||||
} else if (rule.t === 'set') {
|
||||
target.set(property,value);
|
||||
} else if (rule.t === 'change') {
|
||||
var current = target.get(msg,property);
|
||||
current = target.get(msg,property);
|
||||
if (typeof current === 'string') {
|
||||
current = current.replace(rule.from,value);
|
||||
target.set(property,current);
|
||||
if ((fromType === 'num' || fromType === 'bool') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
target.set(property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
target.set(property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
target.set(property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
target.set(property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch(err) {console.log(err.stack)}
|
||||
} catch(err) {/*console.log(err.stack)*/}
|
||||
return msg;
|
||||
}
|
||||
if (valid) {
|
||||
@@ -142,6 +213,9 @@ module.exports = function(RED) {
|
||||
this.on('input', function(msg) {
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
msg = applyRule(msg,this.rules[i]);
|
||||
if (msg === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
|
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red",
|
||||
"version" : "0.13.3",
|
||||
"version" : "0.13.4",
|
||||
"description" : "A visual tool for wiring the Internet of Things",
|
||||
"homepage" : "http://nodered.org",
|
||||
"license" : "Apache-2.0",
|
||||
@@ -34,12 +34,12 @@
|
||||
"cron":"1.1.0",
|
||||
"express": "4.13.4",
|
||||
"follow-redirects":"0.0.7",
|
||||
"fs-extra": "0.26.5",
|
||||
"fs-extra": "0.26.7",
|
||||
"fs.notify":"0.0.4",
|
||||
"i18next":"1.10.6",
|
||||
"is-utf8":"0.2.1",
|
||||
"media-typer": "0.3.0",
|
||||
"mqtt": "1.7.2",
|
||||
"mqtt": "1.7.4",
|
||||
"mustache": "2.2.1",
|
||||
"nopt": "3.0.6",
|
||||
"oauth2orize":"1.2.2",
|
||||
@@ -47,10 +47,10 @@
|
||||
"passport":"0.3.2",
|
||||
"passport-http-bearer":"1.0.1",
|
||||
"passport-oauth2-client-password":"0.1.2",
|
||||
"raw-body":"2.1.5",
|
||||
"raw-body":"2.1.6",
|
||||
"semver": "5.1.0",
|
||||
"sentiment":"1.0.6",
|
||||
"uglify-js":"2.6.1",
|
||||
"uglify-js":"2.6.2",
|
||||
"when": "3.7.7",
|
||||
"ws": "0.8.1",
|
||||
"xml2js":"0.4.16",
|
||||
|
@@ -54,7 +54,6 @@ module.exports = {
|
||||
log.audit({event: "flow.update",id:id},req);
|
||||
res.json({id:id});
|
||||
}).otherwise(function(err) {
|
||||
console.log(err.stack);
|
||||
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
})
|
||||
@@ -63,7 +62,6 @@ module.exports = {
|
||||
log.audit({event: "flow.update",id:id,error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
} else {
|
||||
console.log(err.stack);
|
||||
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014, 2015 IBM Corp.
|
||||
* Copyright 2014, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,6 +40,7 @@ var log;
|
||||
var adminApp;
|
||||
var nodeApp;
|
||||
var server;
|
||||
var runtime;
|
||||
|
||||
var errorHandler = function(err,req,res,next) {
|
||||
if (err.message === "request entity too large") {
|
||||
@@ -51,8 +52,18 @@ var errorHandler = function(err,req,res,next) {
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
};
|
||||
|
||||
function init(_server,runtime) {
|
||||
var ensureRuntimeStarted = function(req,res,next) {
|
||||
if (!runtime.isStarted()) {
|
||||
log.error("Node-RED runtime not started");
|
||||
res.status(503).send("Not started");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
function init(_server,_runtime) {
|
||||
server = _server;
|
||||
runtime = _runtime;
|
||||
var settings = runtime.settings;
|
||||
i18n = runtime.i18n;
|
||||
log = runtime.log;
|
||||
@@ -75,7 +86,7 @@ function init(_server,runtime) {
|
||||
if (!settings.disableEditor) {
|
||||
ui.init(runtime);
|
||||
var editorApp = express();
|
||||
editorApp.get("/",ui.ensureSlash,ui.editor);
|
||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||
editorApp.get("/icons/:icon",ui.icon);
|
||||
theme.init(runtime);
|
||||
if (settings.editorTheme) {
|
||||
|
@@ -13,6 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var fs = require('fs');
|
||||
var fspath = require('path');
|
||||
var when = require('when');
|
||||
|
||||
var redApp = null;
|
||||
var storage;
|
||||
@@ -68,40 +71,135 @@ function createLibrary(type) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var exampleRoots = {};
|
||||
var exampleFlows = {d:{}};
|
||||
var exampleCount = 0;
|
||||
|
||||
function getFlowsFromPath(path) {
|
||||
return when.promise(function(resolve,reject) {
|
||||
var result = {};
|
||||
fs.readdir(path,function(err,files) {
|
||||
var promises = [];
|
||||
var validFiles = [];
|
||||
files.forEach(function(file) {
|
||||
var fullPath = fspath.join(path,file);
|
||||
var stats = fs.lstatSync(fullPath);
|
||||
if (stats.isDirectory()) {
|
||||
validFiles.push(file);
|
||||
promises.push(getFlowsFromPath(fullPath));
|
||||
} else if (/\.json$/.test(file)){
|
||||
validFiles.push(file);
|
||||
exampleCount++;
|
||||
promises.push(when.resolve(file.split(".")[0]))
|
||||
}
|
||||
})
|
||||
var i=0;
|
||||
when.all(promises).then(function(results) {
|
||||
results.forEach(function(r) {
|
||||
if (typeof r === 'string') {
|
||||
result.f = result.f||[];
|
||||
result.f.push(r);
|
||||
} else {
|
||||
result.d = result.d||{};
|
||||
result.d[validFiles[i]] = r;
|
||||
}
|
||||
i++;
|
||||
})
|
||||
|
||||
resolve(result);
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function addNodeExamplesDir(module) {
|
||||
exampleRoots[module.name] = module.path;
|
||||
getFlowsFromPath(module.path).then(function(result) {
|
||||
exampleFlows.d[module.name] = result;
|
||||
});
|
||||
}
|
||||
function removeNodeExamplesDir(module) {
|
||||
delete exampleRoots[module];
|
||||
delete exampleFlows.d[module];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(app,runtime) {
|
||||
redApp = app;
|
||||
log = runtime.log;
|
||||
storage = runtime.storage;
|
||||
// TODO: this allows init to be called multiple times without
|
||||
// registering multiple instances of the listener.
|
||||
// It isn't.... ideal.
|
||||
runtime.events.removeListener("node-examples-dir",addNodeExamplesDir);
|
||||
runtime.events.on("node-examples-dir",addNodeExamplesDir);
|
||||
runtime.events.removeListener("node-module-uninstalled",removeNodeExamplesDir);
|
||||
runtime.events.on("node-module-uninstalled",removeNodeExamplesDir);
|
||||
|
||||
},
|
||||
register: createLibrary,
|
||||
|
||||
getAll: function(req,res) {
|
||||
storage.getAllFlows().then(function(flows) {
|
||||
log.audit({event: "library.get.all",type:"flow"},req);
|
||||
if (exampleCount > 0) {
|
||||
flows.d = flows.d||{};
|
||||
flows.d._examples_ = exampleFlows;
|
||||
}
|
||||
res.json(flows);
|
||||
});
|
||||
},
|
||||
get: function(req,res) {
|
||||
storage.getFlow(req.params[0]).then(function(data) {
|
||||
// data is already a JSON string
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(data);
|
||||
}).otherwise(function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
if (req.params[0].indexOf("_examples_/") === 0) {
|
||||
var m = /^_examples_\/([^\/]+)\/(.*)$/.exec(req.params[0]);
|
||||
if (m) {
|
||||
var module = m[1];
|
||||
var path = m[2]+".json";
|
||||
if (exampleRoots[module]) {
|
||||
var fullPath = fspath.join(exampleRoots[module],path);
|
||||
try {
|
||||
fs.statSync(fullPath);
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
return res.sendFile(fullPath,{
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// IF we get here, we didn't find the file
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
});
|
||||
return res.status(404).end();
|
||||
} else {
|
||||
storage.getFlow(req.params[0]).then(function(data) {
|
||||
// data is already a JSON string
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(data);
|
||||
}).otherwise(function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
});
|
||||
}
|
||||
},
|
||||
post: function(req,res) {
|
||||
// if (req.params[0].indexOf("_examples_/") === 0) {
|
||||
// log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:"forbidden"}));
|
||||
// log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
// return res.status(403).send({error:"unexpected_error", message:"forbidden"});
|
||||
// }
|
||||
var flow = JSON.stringify(req.body);
|
||||
storage.saveFlow(req.params[0],flow).then(function() {
|
||||
log.audit({event: "library.set",type:"flow",path:req.params[0]},req);
|
||||
|
@@ -31,6 +31,7 @@
|
||||
"export": "Export",
|
||||
"clipboard": "Clipboard",
|
||||
"library": "Library",
|
||||
"examples": "Examples",
|
||||
"subflows": "Subflows",
|
||||
"createSubflow": "Create Subflow",
|
||||
"selectionToSubflow": "Selection to Subflow",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,8 @@ var os = require("os");
|
||||
|
||||
var runtimeMetricInterval = null;
|
||||
|
||||
var started = false;
|
||||
|
||||
var stubbedExpressApp = {
|
||||
get: function() {},
|
||||
post: function() {},
|
||||
@@ -142,6 +144,7 @@ function start() {
|
||||
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
|
||||
}
|
||||
redNodes.loadFlows().then(redNodes.startFlows);
|
||||
started = true;
|
||||
}).otherwise(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
@@ -173,6 +176,7 @@ function stop() {
|
||||
clearInterval(runtimeMetricInterval);
|
||||
runtimeMetricInterval = null;
|
||||
}
|
||||
started = false;
|
||||
return redNodes.stopFlows();
|
||||
}
|
||||
|
||||
@@ -190,5 +194,8 @@ var runtime = module.exports = {
|
||||
events: events,
|
||||
nodes: redNodes,
|
||||
util: require("./util"),
|
||||
get adminApi() { return adminApi }
|
||||
get adminApi() { return adminApi },
|
||||
isStarted: function() {
|
||||
return started;
|
||||
}
|
||||
}
|
||||
|
@@ -121,7 +121,11 @@
|
||||
"localfilesystem": {
|
||||
"user-dir": "User directory : __path__",
|
||||
"flows-file": "Flows file : __path__",
|
||||
"create": "Creating new flow file"
|
||||
"create": "Creating new flow file",
|
||||
"empty": "Existing flow file is empty",
|
||||
"invalid": "Existing flow file is not valid json",
|
||||
"restore": "Restoring flow file backup : __path__",
|
||||
"restore-fail": "Restoring flow file backup failed : __message__"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -128,7 +128,7 @@ function setConfig(_config,type,muteLog) {
|
||||
|
||||
function getNode(id) {
|
||||
var node;
|
||||
if (activeNodesToFlow[id]) {
|
||||
if (activeNodesToFlow[id] && activeFlows[activeNodesToFlow[id]]) {
|
||||
return activeFlows[activeNodesToFlow[id]].getNode(id);
|
||||
}
|
||||
for (var flowId in activeFlows) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
* Copyright 2015, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -174,6 +174,8 @@ function uninstallModule(module) {
|
||||
} else {
|
||||
log.info(log._("server.install.uninstalled",{name:module}));
|
||||
reportRemovedModules(list);
|
||||
// TODO: tidy up internal event names
|
||||
events.emit("node-module-uninstalled",module)
|
||||
resolve(list);
|
||||
}
|
||||
}
|
||||
|
@@ -94,7 +94,9 @@ function createNodeApi(node) {
|
||||
}
|
||||
red["_"] = function() {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
args[0] = node.namespace+":"+args[0];
|
||||
if (args[0].indexOf(":") === -1) {
|
||||
args[0] = node.namespace+":"+args[0];
|
||||
}
|
||||
return runtime.i18n._.apply(null,args);
|
||||
}
|
||||
return red;
|
||||
|
@@ -184,6 +184,12 @@ function getModuleNodeFiles(module) {
|
||||
}
|
||||
}
|
||||
}
|
||||
var examplesDir = path.join(moduleDir,"examples");
|
||||
try {
|
||||
fs.statSync(examplesDir)
|
||||
events.emit("node-examples-dir",{name:pkg.name,path:examplesDir});
|
||||
} catch(err) {
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -135,7 +135,7 @@ var localfilesystem = {
|
||||
} catch(err) {
|
||||
settings.userDir = fspath.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE || process.env.NODE_RED_HOME,".node-red");
|
||||
if (!settings.readOnly) {
|
||||
promises.push(promiseDir(settings.userDir));
|
||||
promises.push(promiseDir(fspath.join(settings.userDir,"node_modules")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,10 +199,42 @@ var localfilesystem = {
|
||||
}
|
||||
fs.readFile(flowsFullPath,'utf8',function(err,data) {
|
||||
if (!err) {
|
||||
return resolve(JSON.parse(data));
|
||||
if (data.length === 0) {
|
||||
log.warn(log._("storage.localfilesystem.empty"));
|
||||
try {
|
||||
var backupStat = fs.statSync(flowsFileBackup);
|
||||
if (backupStat.size === 0) {
|
||||
// Empty flows, empty backup - return empty flow
|
||||
return resolve([]);
|
||||
}
|
||||
// Empty flows, restore backup
|
||||
log.warn(log._("storage.localfilesystem.restore",{path:flowsFileBackup}));
|
||||
fs.copy(flowsFileBackup,flowsFullPath,function(backupCopyErr) {
|
||||
if (backupCopyErr) {
|
||||
// Restore backup failed
|
||||
log.warn(log._("storage.localfilesystem.restore-fail",{message:backupCopyErr.toString()}));
|
||||
resolve([]);
|
||||
} else {
|
||||
// Loop back in to load the restored backup
|
||||
resolve(localfilesystem.getFlows());
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch(backupStatErr) {
|
||||
// Empty flow file, no back-up file
|
||||
return resolve([]);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return resolve(JSON.parse(data));
|
||||
} catch(parseErr) {
|
||||
log.warn(log._("storage.localfilesystem.invalid"));
|
||||
return resolve([]);
|
||||
}
|
||||
} else {
|
||||
log.info(log._("storage.localfilesystem.create"));
|
||||
resolve([]);
|
||||
}
|
||||
log.info(log._("storage.localfilesystem.create"));
|
||||
resolve([]);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@@ -477,4 +477,45 @@ describe('delay Node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles timed queue', function(done) {
|
||||
this.timeout(6000);
|
||||
|
||||
var flow = [{"id":"delayNode1","type":"delay","name":"delayNode","pauseType":"timed","timeout":5,"timeoutUnits":"seconds","rate":1000,"rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}];
|
||||
helper.load(delayNode, flow, function() {
|
||||
var delayNode1 = helper.getNode("delayNode1");
|
||||
var helperNode1 = helper.getNode("helperNode1");
|
||||
var messages = 2;
|
||||
var c = 0;
|
||||
|
||||
helperNode1.on("input", function(msg) {
|
||||
c += 1;
|
||||
if (c === 1) {
|
||||
msg.should.have.property("topic","A");
|
||||
msg.should.have.property("payload",3);
|
||||
}
|
||||
else if (c === 2) {
|
||||
msg.should.have.property("topic","_none_");
|
||||
msg.should.have.property("payload",2);
|
||||
}
|
||||
else if (c === 3) {
|
||||
msg.should.have.property("topic","_none_");
|
||||
msg.should.have.property("payload","Biscuit");
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
// send test messages
|
||||
delayNode1.receive({payload:1,topic:"A"});
|
||||
delayNode1.receive({payload:1});
|
||||
delayNode1.receive({payload:2,topic:"A"});
|
||||
delayNode1.receive({payload:3,topic:"A"}); // should get this
|
||||
delayNode1.receive({payload:2}); // and this
|
||||
setTimeout( function() {
|
||||
delayNode1.receive({payload:"Biscuit"}); // and then this
|
||||
},2000);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -51,7 +51,7 @@ describe('change Node', function() {
|
||||
helper.load(changeNode, flow, function() {
|
||||
//console.log(helper.getNode("c1"));
|
||||
helper.getNode("c1").should.have.property("name", "change1");
|
||||
helper.getNode("c1").should.have.property("rules", [ { from: /(?:)/g,fromt: 'str', p: '',pt: 'msg', re: undefined, t: 'change', to: '',tot: 'str' } ]);
|
||||
helper.getNode("c1").should.have.property("rules", [ { from: '', fromRE:/(?:)/g,fromt: 'str', p: '',pt: 'msg', re: undefined, t: 'change', to: '',tot: 'str' } ]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -424,7 +424,6 @@ describe('change Node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('changes the value - new rule format', function(done) {
|
||||
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"ABC","to":"123","fromt":"str","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}];
|
||||
@@ -442,6 +441,96 @@ describe('change Node', function() {
|
||||
changeNode1.receive({payload:"abcABCabc"});
|
||||
});
|
||||
});
|
||||
|
||||
it('changes the value using msg property', function(done) {
|
||||
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"topic","to":"123","fromt":"msg","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}];
|
||||
helper.load(changeNode, flow, function() {
|
||||
var changeNode1 = helper.getNode("changeNode1");
|
||||
var helperNode1 = helper.getNode("helperNode1");
|
||||
helperNode1.on("input", function(msg) {
|
||||
try {
|
||||
msg.payload.should.equal("abc123abc");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
changeNode1.receive({payload:"abcABCabc",topic:"ABC"});
|
||||
});
|
||||
});
|
||||
|
||||
it('changes the value using number - string payload', function(done) {
|
||||
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"123","to":"456","fromt":"num","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}];
|
||||
helper.load(changeNode, flow, function() {
|
||||
var changeNode1 = helper.getNode("changeNode1");
|
||||
var helperNode1 = helper.getNode("helperNode1");
|
||||
helperNode1.on("input", function(msg) {
|
||||
try {
|
||||
msg.payload.should.equal("456");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
changeNode1.receive({payload:"123"});
|
||||
});
|
||||
});
|
||||
|
||||
it('changes the value using number - number payload', function(done) {
|
||||
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"123","to":"abc","fromt":"num","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}];
|
||||
helper.load(changeNode, flow, function() {
|
||||
var changeNode1 = helper.getNode("changeNode1");
|
||||
var helperNode1 = helper.getNode("helperNode1");
|
||||
helperNode1.on("input", function(msg) {
|
||||
try {
|
||||
msg.payload.should.equal("abc");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
changeNode1.receive({payload:123});
|
||||
});
|
||||
});
|
||||
|
||||
it('changes the value using boolean - string payload', function(done) {
|
||||
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"true","to":"xxx","fromt":"bool","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}];
|
||||
helper.load(changeNode, flow, function() {
|
||||
var changeNode1 = helper.getNode("changeNode1");
|
||||
var helperNode1 = helper.getNode("helperNode1");
|
||||
helperNode1.on("input", function(msg) {
|
||||
try {
|
||||
msg.payload.should.equal("xxx");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
changeNode1.receive({payload:"true"});
|
||||
});
|
||||
});
|
||||
|
||||
it('changes the value using boolean - boolean payload', function(done) {
|
||||
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"true","to":"xxx","fromt":"bool","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}];
|
||||
helper.load(changeNode, flow, function() {
|
||||
var changeNode1 = helper.getNode("changeNode1");
|
||||
var helperNode1 = helper.getNode("helperNode1");
|
||||
helperNode1.on("input", function(msg) {
|
||||
try {
|
||||
msg.payload.should.equal("xxx");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
changeNode1.receive({payload:true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#delete", function() {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
* Copyright 2014, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,16 +21,259 @@ var bodyParser = require('body-parser');
|
||||
var sinon = require('sinon');
|
||||
var when = require('when');
|
||||
|
||||
var flows = require("../../../red/api/flows");
|
||||
var flow = require("../../../red/api/flow");
|
||||
|
||||
describe("flow api", function() {
|
||||
|
||||
var app;
|
||||
|
||||
it.skip('add flow');
|
||||
it.skip('update flow');
|
||||
it.skip('remove flow');
|
||||
it.skip('get flow');
|
||||
before(function() {
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.get("/flow/:id",flow.get);
|
||||
app.post("/flow",flow.post);
|
||||
app.put("/flow/:id",flow.put);
|
||||
app.delete("/flow/:id",flow.delete);
|
||||
});
|
||||
|
||||
describe("get", function() {
|
||||
before(function() {
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: {
|
||||
getFlow: function(id) {
|
||||
if (id === '123') {
|
||||
return {id:'123'}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
log:{ audit: sinon.stub() }
|
||||
});
|
||||
})
|
||||
it('gets a known flow', function(done) {
|
||||
request(app)
|
||||
.get('/flow/123')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('id','123');
|
||||
done();
|
||||
});
|
||||
})
|
||||
it('404s an unknown flow', function(done) {
|
||||
request(app)
|
||||
.get('/flow/456')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
})
|
||||
});
|
||||
|
||||
describe("add", function() {
|
||||
before(function() {
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: {
|
||||
addFlow: function(f) {
|
||||
if (f.id === "123") {
|
||||
return when.resolve('123')
|
||||
} else {
|
||||
return when.reject(new Error("test error"));
|
||||
}
|
||||
}
|
||||
},
|
||||
log:{ audit: sinon.stub() }
|
||||
});
|
||||
})
|
||||
it('adds a new flow', function(done) {
|
||||
request(app)
|
||||
.post('/flow')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('id','123');
|
||||
done();
|
||||
});
|
||||
})
|
||||
it('400 an invalid flow', function(done) {
|
||||
request(app)
|
||||
.post('/flow')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'error'})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error','unexpected_error');
|
||||
res.body.should.has.a.property('message','Error: test error');
|
||||
|
||||
done();
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", function() {
|
||||
var nodes;
|
||||
before(function() {
|
||||
nodes = {
|
||||
updateFlow: function(id,f) {
|
||||
var err;
|
||||
if (id === "123") {
|
||||
return when.resolve()
|
||||
} else if (id === "unknown") {
|
||||
err = new Error();
|
||||
err.code = 404;
|
||||
throw err;
|
||||
} else if (id === "unexpected") {
|
||||
err = new Error();
|
||||
err.code = 500;
|
||||
throw err;
|
||||
} else {
|
||||
return when.reject(new Error("test error"));
|
||||
}
|
||||
}
|
||||
};
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: nodes,
|
||||
log:{ audit: sinon.stub() }
|
||||
});
|
||||
})
|
||||
|
||||
it('updates an existing flow', function(done) {
|
||||
sinon.spy(nodes,"updateFlow");
|
||||
request(app)
|
||||
.put('/flow/123')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.expect(200)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('id','123');
|
||||
nodes.updateFlow.calledOnce.should.be.true;
|
||||
nodes.updateFlow.lastCall.args[0].should.eql('123');
|
||||
nodes.updateFlow.lastCall.args[1].should.eql({id:'123'});
|
||||
nodes.updateFlow.restore();
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('404s on an unknown flow', function(done) {
|
||||
request(app)
|
||||
.put('/flow/unknown')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.expect(404)
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('400 on async update error', function(done) {
|
||||
request(app)
|
||||
.put('/flow/async_error')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error','unexpected_error');
|
||||
res.body.should.has.a.property('message','Error: test error');
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('400 on sync update error', function(done) {
|
||||
request(app)
|
||||
.put('/flow/unexpected')
|
||||
.set('Accept', 'application/json')
|
||||
.send({id:'123'})
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error',500);
|
||||
res.body.should.has.a.property('message','Error');
|
||||
done();
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", function() {
|
||||
var nodes;
|
||||
before(function() {
|
||||
nodes = {
|
||||
removeFlow: function(id) {
|
||||
var err;
|
||||
if (id === "123") {
|
||||
return when.resolve()
|
||||
} else if (id === "unknown") {
|
||||
err = new Error();
|
||||
err.code = 404;
|
||||
throw err;
|
||||
} else if (id === "unexpected") {
|
||||
err = new Error();
|
||||
err.code = 500;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
flow.init({
|
||||
settings:{},
|
||||
nodes: nodes,
|
||||
log:{ audit: sinon.stub() }
|
||||
});
|
||||
})
|
||||
|
||||
it('updates an existing flow', function(done) {
|
||||
sinon.spy(nodes,"removeFlow");
|
||||
request(app)
|
||||
.delete('/flow/123')
|
||||
.expect(204)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
nodes.removeFlow.calledOnce.should.be.true;
|
||||
nodes.removeFlow.lastCall.args[0].should.eql('123');
|
||||
nodes.removeFlow.restore();
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('404s on an unknown flow', function(done) {
|
||||
request(app)
|
||||
.delete('/flow/unknown')
|
||||
.expect(404)
|
||||
.end(done);
|
||||
})
|
||||
|
||||
it('400 on remove error', function(done) {
|
||||
request(app)
|
||||
.delete('/flow/unexpected')
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.has.a.property('error',500);
|
||||
res.body.should.has.a.property('message','Error');
|
||||
done();
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
* Copyright 2014, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -87,6 +87,45 @@ describe("api index", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("editor warns if runtime not started", function() {
|
||||
var mockList = [
|
||||
'nodes','flows','library','info','theme','locales','credentials'
|
||||
]
|
||||
before(function() {
|
||||
mockList.forEach(function(m) {
|
||||
sinon.stub(require("../../../red/api/"+m),"init",function(){});
|
||||
});
|
||||
});
|
||||
after(function() {
|
||||
mockList.forEach(function(m) {
|
||||
require("../../../red/api/"+m).init.restore();
|
||||
})
|
||||
});
|
||||
|
||||
it('serves the editor', function(done) {
|
||||
var errorLog = sinon.spy();
|
||||
api.init({},{
|
||||
log:{audit:function(){},error:errorLog},
|
||||
settings:{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false},
|
||||
events:{on:function(){},removeListener:function(){}},
|
||||
isStarted: function() { return false; } // <-----
|
||||
});
|
||||
app = api.adminApp;
|
||||
request(app)
|
||||
.get("/")
|
||||
.expect(503)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.text.should.eql("Not started");
|
||||
errorLog.calledOnce.should.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("enables editor", function() {
|
||||
|
||||
var mockList = [
|
||||
@@ -107,7 +146,8 @@ describe("api index", function() {
|
||||
api.init({},{
|
||||
log:{audit:function(){}},
|
||||
settings:{httpNodeRoot:true, httpAdminRoot: true,disableEditor:false},
|
||||
events:{on:function(){},removeListener:function(){}}
|
||||
events:{on:function(){},removeListener:function(){}},
|
||||
isStarted: function() { return true; }
|
||||
});
|
||||
app = api.adminApp;
|
||||
});
|
||||
|
@@ -80,6 +80,10 @@ describe("library api", function() {
|
||||
libraryEntries[type][path] = body;
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
events: {
|
||||
on: function(){},
|
||||
removeListener: function(){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -428,6 +428,8 @@ describe("red/nodes/registry/registry",function() {
|
||||
});
|
||||
|
||||
describe('#registerNodeConstructor', function() {
|
||||
function TestNodeConstructor() {
|
||||
}
|
||||
beforeEach(function() {
|
||||
sinon.stub(events,'emit');
|
||||
});
|
||||
@@ -435,19 +437,19 @@ describe("red/nodes/registry/registry",function() {
|
||||
events.emit.restore();
|
||||
});
|
||||
it('registers a node constructor', function() {
|
||||
typeRegistry.registerNodeConstructor('node-type',{});
|
||||
typeRegistry.registerNodeConstructor('node-type',TestNodeConstructor);
|
||||
events.emit.calledOnce.should.be.true;
|
||||
events.emit.lastCall.args[0].should.eql('type-registered');
|
||||
events.emit.lastCall.args[1].should.eql('node-type');
|
||||
})
|
||||
it('throws error on duplicate node registration', function() {
|
||||
typeRegistry.registerNodeConstructor('node-type',{});
|
||||
typeRegistry.registerNodeConstructor('node-type',TestNodeConstructor);
|
||||
events.emit.calledOnce.should.be.true;
|
||||
events.emit.lastCall.args[0].should.eql('type-registered');
|
||||
events.emit.lastCall.args[1].should.eql('node-type');
|
||||
/*jshint immed: false */
|
||||
(function(){
|
||||
typeRegistry.registerNodeConstructor('node-type',{});
|
||||
typeRegistry.registerNodeConstructor('node-type',TestNodeConstructor);
|
||||
}).should.throw("node-type already registered");
|
||||
events.emit.calledOnce.should.be.true;
|
||||
})
|
||||
|
@@ -106,6 +106,45 @@ describe('LocalFileSystem', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty flow file, no backup',function(done) {
|
||||
localfilesystem.init({userDir:userDir}).then(function() {
|
||||
var flowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
|
||||
fs.closeSync(fs.openSync(flowFilePath, 'w'));
|
||||
fs.existsSync(flowFilePath).should.be.true;
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql([]);
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty flow file, restores backup',function(done) {
|
||||
localfilesystem.init({userDir:userDir}).then(function() {
|
||||
var flowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
var flowFilePath = path.join(userDir,flowFile);
|
||||
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
|
||||
fs.closeSync(fs.openSync(flowFilePath, 'w'));
|
||||
fs.existsSync(flowFilePath).should.be.true;
|
||||
fs.existsSync(flowFileBackupPath).should.be.false;
|
||||
fs.writeFileSync(flowFileBackupPath,JSON.stringify(testFlow));
|
||||
fs.existsSync(flowFileBackupPath).should.be.true;
|
||||
localfilesystem.getFlows().then(function(flows) {
|
||||
flows.should.eql(testFlow);
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should save flows to the default file',function(done) {
|
||||
localfilesystem.init({userDir:userDir}).then(function() {
|
||||
var flowFile = 'flows_'+require('os').hostname()+'.json';
|
||||
|
Reference in New Issue
Block a user