mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Add dynamic node api
Closes #322 - nodes modules can be installed/removed dynamically at runtime - nodes can be enabled/disabled - onpaletteadd/onpaletteremove api added to node definitions - initial implementation of nr-cli
This commit is contained in:
parent
00cb8d5bce
commit
da61fe12d0
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ flows.backup
|
||||
nodes/node-red-nodes/
|
||||
.npm
|
||||
/coverage
|
||||
.config.json
|
||||
|
@ -88,101 +88,104 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onpaletteadd: function() {
|
||||
var content = document.createElement("div");
|
||||
content.id = "tab-debug";
|
||||
|
||||
var toolbar = document.createElement("div");
|
||||
toolbar.id = "debug-toolbar";
|
||||
content.appendChild(toolbar);
|
||||
|
||||
toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="fa fa-trash"></i></a></div> ';
|
||||
|
||||
var messages = document.createElement("div");
|
||||
messages.id = "debug-content";
|
||||
content.appendChild(messages);
|
||||
|
||||
RED.sidebar.addTab("debug",content);
|
||||
|
||||
function getTimestamp() {
|
||||
var d = new Date();
|
||||
return d.toLocaleString();
|
||||
}
|
||||
|
||||
var sbc = document.getElementById("debug-content");
|
||||
|
||||
var messageCount = 0;
|
||||
var that = this;
|
||||
RED._debug = function(msg) {
|
||||
that.handleDebugMessage("",{
|
||||
name:"debug",
|
||||
msg:msg
|
||||
});
|
||||
}
|
||||
|
||||
this.handleDebugMessage = function(t,o) {
|
||||
var msg = document.createElement("div");
|
||||
msg.onmouseover = function() {
|
||||
msg.style.borderRightColor = "#999";
|
||||
var n = RED.nodes.node(o.id);
|
||||
if (n) {
|
||||
n.highlighted = true;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
};
|
||||
msg.onmouseout = function() {
|
||||
msg.style.borderRightColor = "";
|
||||
var n = RED.nodes.node(o.id);
|
||||
if (n) {
|
||||
n.highlighted = false;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
};
|
||||
msg.onclick = function() {
|
||||
var node = RED.nodes.node(o.id);
|
||||
if (node) {
|
||||
RED.view.showWorkspace(node.z);
|
||||
}
|
||||
|
||||
};
|
||||
var name = (o.name?o.name:o.id).toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var topic = (o.topic||"").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var payload = (o.msg||"").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'')
|
||||
msg.innerHTML = '<span class="debug-message-date">'+getTimestamp()+'</span>'+
|
||||
'<span class="debug-message-name">['+name+']</span>'+
|
||||
(o.topic?'<span class="debug-message-topic">'+topic+'</span>':'')+
|
||||
'<span class="debug-message-payload">'+payload+'</span>';
|
||||
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
|
||||
messageCount++;
|
||||
$(messages).append(msg);
|
||||
|
||||
if (messageCount > 200) {
|
||||
$("#debug-content .debug-message:first").remove();
|
||||
messageCount--;
|
||||
}
|
||||
if (atBottom) {
|
||||
$(sbc).scrollTop(sbc.scrollHeight);
|
||||
}
|
||||
};
|
||||
RED.comms.subscribe("debug",this.handleDebugMessage);
|
||||
|
||||
$("#debug-tab-clear").click(function() {
|
||||
$(".debug-message").remove();
|
||||
messageCount = 0;
|
||||
RED.nodes.eachNode(function(node) {
|
||||
node.highlighted = false;
|
||||
node.dirty = true;
|
||||
});
|
||||
RED.view.redraw();
|
||||
});
|
||||
},
|
||||
onpaletteremove: function() {
|
||||
RED.comms.unsubscribe("debug",this.handleDebugMessage);
|
||||
RED.sidebar.removeTab("debug");
|
||||
delete RED._debug;
|
||||
}
|
||||
});
|
||||
|
||||
(function() {
|
||||
var content = document.createElement("div");
|
||||
content.id = "tab-debug";
|
||||
|
||||
var toolbar = document.createElement("div");
|
||||
toolbar.id = "debug-toolbar";
|
||||
content.appendChild(toolbar);
|
||||
|
||||
toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="fa fa-trash"></i></a></div> ';
|
||||
|
||||
var messages = document.createElement("div");
|
||||
messages.id = "debug-content";
|
||||
content.appendChild(messages);
|
||||
|
||||
RED.sidebar.addTab("debug",content);
|
||||
|
||||
function getTimestamp() {
|
||||
var d = new Date();
|
||||
return d.toLocaleString();
|
||||
}
|
||||
|
||||
var sbc = document.getElementById("debug-content");
|
||||
|
||||
var messageCount = 0;
|
||||
|
||||
RED._debug = function(msg) {
|
||||
handleDebugMessage("",{
|
||||
name:"debug",
|
||||
msg:msg
|
||||
});
|
||||
}
|
||||
|
||||
var handleDebugMessage = function(t,o) {
|
||||
var msg = document.createElement("div");
|
||||
msg.onmouseover = function() {
|
||||
msg.style.borderRightColor = "#999";
|
||||
var n = RED.nodes.node(o.id);
|
||||
if (n) {
|
||||
n.highlighted = true;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
};
|
||||
msg.onmouseout = function() {
|
||||
msg.style.borderRightColor = "";
|
||||
var n = RED.nodes.node(o.id);
|
||||
if (n) {
|
||||
n.highlighted = false;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.view.redraw();
|
||||
};
|
||||
msg.onclick = function() {
|
||||
var node = RED.nodes.node(o.id);
|
||||
if (node) {
|
||||
RED.view.showWorkspace(node.z);
|
||||
}
|
||||
|
||||
};
|
||||
var name = (o.name?o.name:o.id).toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var topic = (o.topic||"").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var payload = (o.msg||"").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'')
|
||||
msg.innerHTML = '<span class="debug-message-date">'+getTimestamp()+'</span>'+
|
||||
'<span class="debug-message-name">['+name+']</span>'+
|
||||
(o.topic?'<span class="debug-message-topic">'+topic+'</span>':'')+
|
||||
'<span class="debug-message-payload">'+payload+'</span>';
|
||||
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
|
||||
messageCount++;
|
||||
$(messages).append(msg);
|
||||
|
||||
if (messageCount > 200) {
|
||||
$("#debug-content .debug-message:first").remove();
|
||||
messageCount--;
|
||||
}
|
||||
if (atBottom) {
|
||||
$(sbc).scrollTop(sbc.scrollHeight);
|
||||
}
|
||||
};
|
||||
RED.comms.subscribe("debug",handleDebugMessage);
|
||||
|
||||
$("#debug-tab-clear").click(function() {
|
||||
$(".debug-message").remove();
|
||||
messageCount = 0;
|
||||
RED.nodes.eachNode(function(node) {
|
||||
node.highlighted = false;
|
||||
node.dirty = true;
|
||||
});
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -43,7 +43,8 @@
|
||||
"uglify-js":"2.4.15",
|
||||
"nodemailer":"1.3.0",
|
||||
"imap":"0.8.13",
|
||||
"request":"2.42.0"
|
||||
"request":"2.42.0",
|
||||
"colors":"0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "0.4.5",
|
||||
|
@ -71,9 +71,23 @@ RED.comms = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function unsubscribe(topic,callback) {
|
||||
if (subscriptions.topic) {
|
||||
for (var i=0;i<subscriptions.topic.length;i++) {
|
||||
if (subscriptions.topic[i] === callback) {
|
||||
subscriptions.topic.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (subscriptions.topic.length === 0) {
|
||||
delete subscriptions.topic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
connect: connectWS,
|
||||
subscribe: subscribe
|
||||
subscribe: subscribe,
|
||||
unsubscribe:unsubscribe
|
||||
}
|
||||
})();
|
||||
|
@ -145,17 +145,35 @@ var RED = (function() {
|
||||
$.get('settings', function(data) {
|
||||
RED.settings = data;
|
||||
console.log("Node-RED: "+data.version);
|
||||
loadNodes();
|
||||
loadNodeList();
|
||||
});
|
||||
}
|
||||
function loadNodeList() {
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"application/json"
|
||||
},
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
RED.nodes.setNodeList(data);
|
||||
loadNodes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadNodes() {
|
||||
$.get('nodes', function(data) {
|
||||
$("body").append(data);
|
||||
$(".palette-spinner").hide();
|
||||
$(".palette-scroll").show();
|
||||
$("#palette-search").show();
|
||||
loadFlows();
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept":"text/html"
|
||||
},
|
||||
url: 'nodes',
|
||||
success: function(data) {
|
||||
$("body").append(data);
|
||||
$(".palette-spinner").hide();
|
||||
$(".palette-scroll").show();
|
||||
$("#palette-search").show();
|
||||
loadFlows();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -176,24 +194,56 @@ var RED = (function() {
|
||||
}
|
||||
});
|
||||
RED.comms.subscribe("node/#",function(topic,msg) {
|
||||
var i;
|
||||
var i,m;
|
||||
var typeList;
|
||||
var info;
|
||||
|
||||
if (topic == "node/added") {
|
||||
var addedTypes = [];
|
||||
for (i=0;i<msg.length;i++) {
|
||||
var m = msg[i];
|
||||
m = msg[i];
|
||||
var id = m.id;
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
var typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(m.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
|
||||
});
|
||||
RED.nodes.addNodeSet(m);
|
||||
if (m.loaded) {
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (addedTypes.length) {
|
||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(addedTypes.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
|
||||
}
|
||||
} else if (topic == "node/removed") {
|
||||
if (msg.types) {
|
||||
for (i=0;i<msg.types.length;i++) {
|
||||
RED.palette.remove(msg.types[i]);
|
||||
for (i=0;i<msg.length;i++) {
|
||||
m = msg[i];
|
||||
info = RED.nodes.removeNodeSet(m.id);
|
||||
if (info.added) {
|
||||
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(m.types.length!=1 ? "s":"")+" removed from palette:"+typeList,"success");
|
||||
}
|
||||
var typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" removed from palette:"+typeList,"success");
|
||||
}
|
||||
} else if (topic == "node/enabled") {
|
||||
if (msg.types) {
|
||||
info = RED.nodes.getNodeSet(msg.id);
|
||||
if (info.added) {
|
||||
RED.nodes.enableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" enabled:"+typeList,"success");
|
||||
} else {
|
||||
$.get('nodes/'+msg.id, function(data) {
|
||||
$("body").append(data);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (topic == "node/disabled") {
|
||||
if (msg.types) {
|
||||
RED.nodes.disableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" disabled:"+typeList,"success");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -21,21 +21,101 @@ RED.nodes = (function() {
|
||||
var links = [];
|
||||
var defaultWorkspace;
|
||||
var workspaces = {};
|
||||
|
||||
function registerType(nt,def) {
|
||||
node_defs[nt] = def;
|
||||
// TODO: too tightly coupled into palette UI
|
||||
RED.palette.add(nt,def);
|
||||
}
|
||||
|
||||
|
||||
var registry = (function() {
|
||||
var nodeList = [];
|
||||
var nodeSets = {};
|
||||
var typeToId = {};
|
||||
var nodeDefinitions = {};
|
||||
|
||||
var exports = {
|
||||
getNodeList: function() {
|
||||
return nodeList;
|
||||
},
|
||||
setNodeList: function(list) {
|
||||
nodeList = [];
|
||||
for(var i=0;i<list.length;i++) {
|
||||
var ns = list[i];
|
||||
exports.addNodeSet(ns);
|
||||
}
|
||||
},
|
||||
addNodeSet: function(ns) {
|
||||
ns.added = false;
|
||||
nodeSets[ns.id] = ns;
|
||||
for (var j=0;j<ns.types.length;j++) {
|
||||
typeToId[ns.types[j]] = ns.id;
|
||||
}
|
||||
nodeList.push(ns);
|
||||
},
|
||||
removeNodeSet: function(id) {
|
||||
var ns = nodeSets[id];
|
||||
for (var j=0;j<ns.types.length;j++) {
|
||||
if (ns.added) {
|
||||
// TODO: too tightly coupled into palette UI
|
||||
RED.palette.remove(ns.types[j]);
|
||||
var def = nodeDefinitions[ns.types[j]];
|
||||
if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
|
||||
def.onpaletteremove.call(def);
|
||||
}
|
||||
}
|
||||
delete typeToId[ns.types[j]];
|
||||
}
|
||||
delete nodeSets[id];
|
||||
for (var i=0;i<nodeList.length;i++) {
|
||||
if (nodeList[i].id == id) {
|
||||
nodeList.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ns;
|
||||
},
|
||||
getNodeSet: function(id) {
|
||||
return nodeSets[id];
|
||||
},
|
||||
enableNodeSet: function(id) {
|
||||
var ns = nodeSets[id];
|
||||
ns.enabled = true;
|
||||
for (var j=0;j<ns.types.length;j++) {
|
||||
// TODO: too tightly coupled into palette UI
|
||||
RED.palette.show(ns.types[j]);
|
||||
var def = nodeDefinitions[ns.types[j]];
|
||||
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
|
||||
def.onpaletteadd.call(def);
|
||||
}
|
||||
}
|
||||
},
|
||||
disableNodeSet: function(id) {
|
||||
var ns = nodeSets[id];
|
||||
ns.enabled = false;
|
||||
for (var j=0;j<ns.types.length;j++) {
|
||||
// TODO: too tightly coupled into palette UI
|
||||
RED.palette.hide(ns.types[j]);
|
||||
var def = nodeDefinitions[ns.types[j]];
|
||||
if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
|
||||
def.onpaletteremove.call(def);
|
||||
}
|
||||
}
|
||||
},
|
||||
registerNodeType: function(nt,def) {
|
||||
nodeDefinitions[nt] = def;
|
||||
nodeSets[typeToId[nt]].added = true;
|
||||
// TODO: too tightly coupled into palette UI
|
||||
RED.palette.add(nt,def);
|
||||
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
|
||||
def.onpaletteadd.call(def);
|
||||
}
|
||||
},
|
||||
getNodeType: function(nt) {
|
||||
return nodeDefinitions[nt];
|
||||
}
|
||||
}
|
||||
return exports;
|
||||
})();
|
||||
|
||||
function getID() {
|
||||
return (1+Math.random()*4294967295).toString(16);
|
||||
}
|
||||
|
||||
function getType(type) {
|
||||
return node_defs[type];
|
||||
}
|
||||
|
||||
function addNode(n) {
|
||||
if (n._def.category == "config") {
|
||||
configNodes[n.id] = n;
|
||||
@ -48,7 +128,7 @@ RED.nodes = (function() {
|
||||
if (n._def.defaults.hasOwnProperty(d)) {
|
||||
var property = n._def.defaults[d];
|
||||
if (property.type) {
|
||||
var type = getType(property.type)
|
||||
var type = registry.getNodeType(property.type)
|
||||
if (type && type.category == "config") {
|
||||
var configNode = configNodes[n[d]];
|
||||
if (configNode) {
|
||||
@ -101,7 +181,7 @@ RED.nodes = (function() {
|
||||
if (node._def.defaults.hasOwnProperty(d)) {
|
||||
var property = node._def.defaults[d];
|
||||
if (property.type) {
|
||||
var type = getType(property.type)
|
||||
var type = registry.getNodeType(property.type)
|
||||
if (type && type.category == "config") {
|
||||
var configNode = configNodes[node[d]];
|
||||
if (configNode) {
|
||||
@ -229,7 +309,7 @@ RED.nodes = (function() {
|
||||
for (var d in node._def.defaults) {
|
||||
if (node._def.defaults[d].type && node[d] in configNodes) {
|
||||
var confNode = configNodes[node[d]];
|
||||
var exportable = getType(node._def.defaults[d].type).exportable;
|
||||
var exportable = registry.getNodeType(node._def.defaults[d].type).exportable;
|
||||
if ((exportable == null || exportable)) {
|
||||
if (!(node[d] in exportedConfigNodes)) {
|
||||
exportedConfigNodes[node[d]] = true;
|
||||
@ -288,7 +368,7 @@ RED.nodes = (function() {
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type != "workspace" && n.type != "tab" && !getType(n.type)) {
|
||||
if (n.type != "workspace" && n.type != "tab" && !registry.getNodeType(n.type)) {
|
||||
// TODO: get this UI thing out of here! (see below as well)
|
||||
n.name = n.type;
|
||||
n.type = "unknown";
|
||||
@ -347,7 +427,7 @@ RED.nodes = (function() {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type !== "workspace" && n.type !== "tab") {
|
||||
var def = getType(n.type);
|
||||
var def = registry.getNodeType(n.type);
|
||||
if (def && def.category == "config") {
|
||||
if (!RED.nodes.node(n.id)) {
|
||||
var configNode = {id:n.id,type:n.type,users:[]};
|
||||
@ -424,8 +504,17 @@ RED.nodes = (function() {
|
||||
}
|
||||
|
||||
return {
|
||||
registerType: registerType,
|
||||
getType: getType,
|
||||
registry:registry,
|
||||
setNodeList: registry.setNodeList,
|
||||
|
||||
getNodeSet: registry.getNodeSet,
|
||||
addNodeSet: registry.addNodeSet,
|
||||
removeNodeSet: registry.removeNodeSet,
|
||||
enableNodeSet: registry.enableNodeSet,
|
||||
disableNodeSet: registry.disableNodeSet,
|
||||
|
||||
registerType: registry.registerNodeType,
|
||||
getType: registry.getNodeType,
|
||||
convertNode: convertNode,
|
||||
add: addNode,
|
||||
addLink: addLink,
|
||||
|
@ -29,14 +29,21 @@ RED.palette = (function() {
|
||||
'<div id="palette-'+category+'-function"></div>'+
|
||||
'</div>'+
|
||||
'</div>');
|
||||
|
||||
|
||||
$("#header-"+category).on('click', function(e) {
|
||||
$(this).next().slideToggle();
|
||||
$(this).children("i").toggleClass("expanded");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
core.forEach(createCategoryContainer);
|
||||
|
||||
function addNodeType(nt,def) {
|
||||
|
||||
if ($("#palette_node_"+nt).length) {
|
||||
var nodeTypeId = nt.replace(" ","_");
|
||||
|
||||
if ($("#palette_node_"+nodeTypeId).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -46,7 +53,7 @@ RED.palette = (function() {
|
||||
var rootCategory = category.split("-")[0];
|
||||
|
||||
var d = document.createElement("div");
|
||||
d.id = "palette_node_"+nt;
|
||||
d.id = "palette_node_"+nodeTypeId;
|
||||
d.type = nt;
|
||||
|
||||
var label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
|
||||
@ -106,17 +113,21 @@ RED.palette = (function() {
|
||||
revert: true,
|
||||
revertDuration: 50
|
||||
});
|
||||
|
||||
$("#header-"+category[0]).off('click').on('click', function(e) {
|
||||
$(this).next().slideToggle();
|
||||
$(this).children("i").toggleClass("expanded");
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function removeNodeType(type) {
|
||||
$("#palette_node_"+type).remove();
|
||||
function removeNodeType(nt) {
|
||||
var nodeTypeId = nt.replace(" ","_");
|
||||
$("#palette_node_"+nodeTypeId).remove();
|
||||
}
|
||||
function hideNodeType(nt) {
|
||||
var nodeTypeId = nt.replace(" ","_");
|
||||
$("#palette_node_"+nodeTypeId).hide();
|
||||
}
|
||||
|
||||
function showNodeType(nt) {
|
||||
var nodeTypeId = nt.replace(" ","_");
|
||||
$("#palette_node_"+nodeTypeId).show();
|
||||
}
|
||||
|
||||
function filterChange() {
|
||||
@ -164,6 +175,8 @@ RED.palette = (function() {
|
||||
|
||||
return {
|
||||
add:addNodeType,
|
||||
remove:removeNodeType
|
||||
remove:removeNodeType,
|
||||
hide:hideNodeType,
|
||||
show:showNodeType
|
||||
};
|
||||
})();
|
||||
|
@ -34,6 +34,10 @@ RED.sidebar = (function() {
|
||||
//$('#sidebar').tabs("refresh");
|
||||
}
|
||||
|
||||
function removeTab(title) {
|
||||
sidebar_tabs.removeTab("tab-"+title);
|
||||
}
|
||||
|
||||
var sidebarSeparator = {};
|
||||
$("#sidebar-separator").draggable({
|
||||
axis: "x",
|
||||
@ -141,6 +145,7 @@ RED.sidebar = (function() {
|
||||
|
||||
return {
|
||||
addTab: addTab,
|
||||
removeTab: removeTab,
|
||||
show: showSidebar,
|
||||
containsTab: containsTab,
|
||||
toggleSidebar: toggleSidebar
|
||||
|
179
red/bin/nr-cli.js
Executable file
179
red/bin/nr-cli.js
Executable file
@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env node
|
||||
;(function() {
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
|
||||
var request = require("request");
|
||||
var colors = require('colors');
|
||||
|
||||
|
||||
function formatBoolean(v,c) {
|
||||
if (v) {
|
||||
return ("["+c+"]");
|
||||
} else {
|
||||
return ("[ ]");
|
||||
}
|
||||
}
|
||||
|
||||
function formatNodeInfo(n) {
|
||||
var inError = n.hasOwnProperty("err");
|
||||
|
||||
var str = formatBoolean(n.enabled,"X")+formatBoolean(n.loaded,"L")+" ";
|
||||
str += n.id;
|
||||
if (n.enabled && n.loaded) {
|
||||
str = str.green;
|
||||
} else if (n.enabled && n.err) {
|
||||
str = str.red;
|
||||
} else {
|
||||
str = str.yellow;
|
||||
}
|
||||
if (n.module) {
|
||||
str += " ["+n.module+"]";
|
||||
}
|
||||
str += " "+n.types.join(", ");
|
||||
if (n.err) {
|
||||
str+=" "+n.err.red;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
var options;
|
||||
|
||||
if (process.argv[2] == "nodes") {
|
||||
|
||||
options = {
|
||||
url: 'http://localhost:1880/nodes',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
request(options, function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
var info = JSON.parse(body);
|
||||
for (var i=0;i<info.length;i++) {
|
||||
var n = info[i];
|
||||
console.log(formatNodeInfo(n))
|
||||
}
|
||||
} else if (error) {
|
||||
console.log(error.toString().red);
|
||||
} else {
|
||||
console.log((response.statusCode+": "+body).red);
|
||||
}
|
||||
});
|
||||
} else if (process.argv[2] == "node" && process.argv[3]) {
|
||||
options = {
|
||||
url: 'http://localhost:1880/nodes/'+process.argv[3],
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
};
|
||||
request(options, function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
var info = JSON.parse(body);
|
||||
console.log(formatNodeInfo(info));
|
||||
} else if (error) {
|
||||
console.log(error.toString().red);
|
||||
} else {
|
||||
console.log((response.statusCode+": "+body).red);
|
||||
}
|
||||
});
|
||||
} else if (process.argv[2] == "enable-node" && process.argv[3]) {
|
||||
options = {
|
||||
method: "PUT",
|
||||
url: 'http://localhost:1880/nodes/'+process.argv[3],
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'content-type':'application/json'
|
||||
},
|
||||
body: JSON.stringify({enabled:true})
|
||||
};
|
||||
request(options, function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
var info = JSON.parse(body);
|
||||
console.log(formatNodeInfo(info));
|
||||
} else if (error) {
|
||||
console.log(error.toString().red);
|
||||
} else {
|
||||
console.log((response.statusCode+": "+body).red);
|
||||
}
|
||||
});
|
||||
} else if (process.argv[2] == "disable-node" && process.argv[3]) {
|
||||
options = {
|
||||
method: "PUT",
|
||||
url: 'http://localhost:1880/nodes/'+process.argv[3],
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'content-type':'application/json'
|
||||
},
|
||||
body: JSON.stringify({enabled:false})
|
||||
};
|
||||
request(options, function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
var info = JSON.parse(body);
|
||||
console.log(formatNodeInfo(info));
|
||||
} else if (error) {
|
||||
console.log(error.toString().red);
|
||||
} else {
|
||||
console.log((response.statusCode+": "+body).red);
|
||||
}
|
||||
});
|
||||
} else if (process.argv[2] == "install" && process.argv[3]) {
|
||||
options = {
|
||||
method: "POST",
|
||||
url: 'http://localhost:1880/nodes',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'content-type':'application/json'
|
||||
},
|
||||
body: JSON.stringify({module:process.argv[3]})
|
||||
};
|
||||
request(options, function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
var info = JSON.parse(body);
|
||||
for (var i=0;i<info.length;i++) {
|
||||
var n = info[i];
|
||||
console.log(formatNodeInfo(n))
|
||||
}
|
||||
} else if (error) {
|
||||
console.log(error.toString().red);
|
||||
} else {
|
||||
console.log((response.statusCode+": "+body).red);
|
||||
}
|
||||
});
|
||||
} else if (process.argv[2] == "remove" && process.argv[3]) {
|
||||
options = {
|
||||
method: "DELETE",
|
||||
url: 'http://localhost:1880/nodes/'+process.argv[3],
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
};
|
||||
request(options, function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
var info = JSON.parse(body);
|
||||
for (var i=0;i<info.length;i++) {
|
||||
var n = info[i];
|
||||
console.log(formatNodeInfo(n))
|
||||
}
|
||||
} else if (error) {
|
||||
console.log(error.toString().red);
|
||||
} else {
|
||||
console.log((response.statusCode+": "+body).red);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
@ -51,11 +51,10 @@ function init(_settings,storage) {
|
||||
registry.init(_settings);
|
||||
}
|
||||
|
||||
|
||||
function removeNode(info) {
|
||||
var nodeInfo = registry.getNodeInfo(info);
|
||||
function checkTypeInUse(id) {
|
||||
var nodeInfo = registry.getNodeInfo(id);
|
||||
if (!nodeInfo) {
|
||||
throw new Error("Unrecognised type/id: "+info);
|
||||
throw new Error("Unrecognised id: "+info);
|
||||
}
|
||||
var inUse = {};
|
||||
flows.each(function(n) {
|
||||
@ -71,7 +70,25 @@ function removeNode(info) {
|
||||
var msg = nodesInUse.join(", ");
|
||||
throw new Error("Type in use: "+msg);
|
||||
}
|
||||
return registry.removeNode(nodeInfo.id);
|
||||
}
|
||||
|
||||
function removeNode(id) {
|
||||
checkTypeInUse(id);
|
||||
return registry.removeNode(id);
|
||||
}
|
||||
|
||||
function removeModule(module) {
|
||||
var info = registry.getNodeModuleInfo(module);
|
||||
for (var i=0;i<info.nodes.length;i++) {
|
||||
checkTypeInUse(info.nodes[i]);
|
||||
}
|
||||
return registry.removeModule(module);
|
||||
}
|
||||
|
||||
|
||||
function disableNode(id) {
|
||||
checkTypeInUse(id);
|
||||
return registry.disableNode(id);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -86,9 +103,17 @@ module.exports = {
|
||||
addNode: registry.addNode,
|
||||
removeNode: removeNode,
|
||||
|
||||
addModule: registry.addModule,
|
||||
removeModule: removeModule,
|
||||
|
||||
enableNode: registry.enableNode,
|
||||
disableNode: disableNode,
|
||||
|
||||
// Node type registry
|
||||
registerType: registerType,
|
||||
getType: registry.get,
|
||||
getNodeInfo: registry.getNodeInfo,
|
||||
getNodeModuleInfo: registry.getNodeModuleInfo,
|
||||
getNodeList: registry.getNodeList,
|
||||
getNodeConfigs: registry.getNodeConfigs,
|
||||
getNodeConfig: registry.getNodeConfig,
|
||||
|
@ -30,11 +30,17 @@ var settings;
|
||||
function filterNodeInfo(n) {
|
||||
var r = {
|
||||
id: n.id,
|
||||
types: n.types,
|
||||
name: n.name,
|
||||
types: n.types,
|
||||
enabled: n.enabled
|
||||
}
|
||||
if (n.err) {
|
||||
if (n.hasOwnProperty("loaded")) {
|
||||
r.loaded = n.loaded;
|
||||
}
|
||||
if (n.hasOwnProperty("module")) {
|
||||
r.module = n.module;
|
||||
}
|
||||
if (n.hasOwnProperty("err")) {
|
||||
r.err = n.err.toString();
|
||||
}
|
||||
return r;
|
||||
@ -46,14 +52,52 @@ var registry = (function() {
|
||||
var nodeList = [];
|
||||
var nodeConstructors = {};
|
||||
var nodeTypeToId = {};
|
||||
var nodeModules = {};
|
||||
|
||||
function saveNodeList() {
|
||||
var nodeList = {};
|
||||
|
||||
for (var i in nodeConfigs) {
|
||||
if (nodeConfigs.hasOwnProperty(i)) {
|
||||
var nodeConfig = nodeConfigs[i];
|
||||
var n = filterNodeInfo(nodeConfig);
|
||||
n.file = nodeConfig.file;
|
||||
delete n.loaded;
|
||||
delete n.err;
|
||||
delete n.file;
|
||||
delete n.id;
|
||||
nodeList[i] = n;
|
||||
}
|
||||
}
|
||||
settings.set("nodes",nodeList);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
if (settings.available()) {
|
||||
nodeConfigs = settings.get("nodes")||{};
|
||||
} else {
|
||||
nodeConfigs = {};
|
||||
}
|
||||
nodeModules = {};
|
||||
nodeTypeToId = {};
|
||||
nodeConstructors = {};
|
||||
nodeList = [];
|
||||
nodeConfigCache = null;
|
||||
},
|
||||
|
||||
addNodeSet: function(id,set) {
|
||||
if (!set.err) {
|
||||
set.types.forEach(function(t) {
|
||||
nodeTypeToId[t] = id;
|
||||
});
|
||||
}
|
||||
|
||||
if (set.module) {
|
||||
nodeModules[set.module] = nodeModules[set.module]||{nodes:[]};
|
||||
nodeModules[set.module].nodes.push(id);
|
||||
}
|
||||
|
||||
nodeConfigs[id] = set;
|
||||
nodeList.push(id);
|
||||
nodeConfigCache = null;
|
||||
@ -72,9 +116,27 @@ var registry = (function() {
|
||||
delete nodeConstructors[t];
|
||||
delete nodeTypeToId[t];
|
||||
});
|
||||
config.enabled = false;
|
||||
config.loaded = false;
|
||||
nodeConfigCache = null;
|
||||
return filterNodeInfo(config);
|
||||
},
|
||||
removeModule: function(module) {
|
||||
if (!settings.available()) {
|
||||
throw new Error("Settings unavailable");
|
||||
}
|
||||
var nodes = nodeModules[module];
|
||||
if (!nodes) {
|
||||
throw new Error("Unrecognised module: "+module);
|
||||
}
|
||||
var infoList = [];
|
||||
for (var i=0;i<nodes.nodes.length;i++) {
|
||||
infoList.push(registry.removeNode(nodes.nodes[i]));
|
||||
}
|
||||
delete nodeModules[module];
|
||||
saveNodeList();
|
||||
return infoList;
|
||||
},
|
||||
getNodeInfo: function(typeOrId) {
|
||||
if (nodeTypeToId[typeOrId]) {
|
||||
return filterNodeInfo(nodeConfigs[nodeTypeToId[typeOrId]]);
|
||||
@ -84,10 +146,13 @@ var registry = (function() {
|
||||
return null;
|
||||
},
|
||||
getNodeList: function() {
|
||||
return nodeList.map(function(id) {
|
||||
var n = nodeConfigs[id];
|
||||
return filterNodeInfo(n);
|
||||
});
|
||||
var list = [];
|
||||
for (var id in nodeConfigs) {
|
||||
if (nodeConfigs.hasOwnProperty(id)) {
|
||||
list.push(filterNodeInfo(nodeConfigs[id]))
|
||||
}
|
||||
}
|
||||
return list;
|
||||
},
|
||||
registerNodeConstructor: function(type,constructor) {
|
||||
if (nodeConstructors[type]) {
|
||||
@ -112,7 +177,7 @@ var registry = (function() {
|
||||
var script = "";
|
||||
for (var i=0;i<nodeList.length;i++) {
|
||||
var config = nodeConfigs[nodeList[i]];
|
||||
if (config.enabled) {
|
||||
if (config.enabled && !config.err) {
|
||||
result += config.config;
|
||||
script += config.script;
|
||||
}
|
||||
@ -131,7 +196,9 @@ var registry = (function() {
|
||||
var config = nodeConfigs[id];
|
||||
if (config) {
|
||||
var result = config.config;
|
||||
result += '<script type="text/javascript">'+config.script+'</script>';
|
||||
if (config.script) {
|
||||
result += '<script type="text/javascript">'+config.script+'</script>';
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
@ -140,7 +207,7 @@ var registry = (function() {
|
||||
|
||||
getNodeConstructor: function(type) {
|
||||
var config = nodeConfigs[nodeTypeToId[type]];
|
||||
if (!config || config.enabled) {
|
||||
if (!config || (config.enabled && !config.err)) {
|
||||
return nodeConstructors[type];
|
||||
}
|
||||
return null;
|
||||
@ -158,25 +225,47 @@ var registry = (function() {
|
||||
return nodeTypeToId[type];
|
||||
},
|
||||
|
||||
getModuleInfo: function(type) {
|
||||
return nodeModules[type];
|
||||
},
|
||||
|
||||
enableNodeSet: function(id) {
|
||||
if (!settings.available()) {
|
||||
throw new Error("Settings unavailable");
|
||||
}
|
||||
var config = nodeConfigs[id];
|
||||
if (config) {
|
||||
if (config.err) {
|
||||
throw new Error("cannot enable node with error");
|
||||
}
|
||||
delete config.err;
|
||||
config.enabled = true;
|
||||
if (!config.loaded) {
|
||||
// TODO: honour the promise this returns
|
||||
loadNodeModule(config);
|
||||
}
|
||||
nodeConfigCache = null;
|
||||
saveNodeList();
|
||||
} else {
|
||||
throw new Error("Unrecognised id: "+id);
|
||||
}
|
||||
return filterNodeInfo(config);
|
||||
},
|
||||
|
||||
disableNodeSet: function(id) {
|
||||
if (!settings.available()) {
|
||||
throw new Error("Settings unavailable");
|
||||
}
|
||||
var config = nodeConfigs[id];
|
||||
if (config) {
|
||||
// TODO: persist setting
|
||||
config.enabled = false;
|
||||
nodeConfigCache = null;
|
||||
saveNodeList();
|
||||
} else {
|
||||
throw new Error("Unrecognised id: "+id);
|
||||
}
|
||||
|
||||
}
|
||||
return filterNodeInfo(config);
|
||||
},
|
||||
|
||||
saveNodeList: saveNodeList
|
||||
}
|
||||
})();
|
||||
|
||||
@ -185,6 +274,7 @@ var registry = (function() {
|
||||
function init(_settings) {
|
||||
Node = require("./Node");
|
||||
settings = _settings;
|
||||
registry.init();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,22 +334,28 @@ function scanTreeForNodesModules(moduleName) {
|
||||
var pm = path.join(dir,"node_modules");
|
||||
try {
|
||||
var files = fs.readdirSync(pm);
|
||||
files.forEach(function(fn) {
|
||||
if (!moduleName || fn == moduleName) {
|
||||
var pkgfn = path.join(pm,fn,"package.json");
|
||||
try {
|
||||
var pkg = require(pkgfn);
|
||||
if (pkg['node-red']) {
|
||||
var moduleDir = path.join(pm,fn);
|
||||
results.push({dir:moduleDir,package:pkg});
|
||||
for (var i=0;i<files.length;i++) {
|
||||
var fn = files[i];
|
||||
if (!registry.getModuleInfo(fn)) {
|
||||
if (!moduleName || fn == moduleName) {
|
||||
var pkgfn = path.join(pm,fn,"package.json");
|
||||
try {
|
||||
var pkg = require(pkgfn);
|
||||
if (pkg['node-red']) {
|
||||
var moduleDir = path.join(pm,fn);
|
||||
results.push({dir:moduleDir,package:pkg});
|
||||
}
|
||||
} catch(err) {
|
||||
if (err.code != "MODULE_NOT_FOUND") {
|
||||
// TODO: handle unexpected error
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
if (err.code != "MODULE_NOT_FOUND") {
|
||||
// TODO: handle unexpected error
|
||||
if (fn == moduleName) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
|
||||
@ -313,15 +409,23 @@ function loadNodesFromModule(moduleDir,pkg) {
|
||||
function loadNodeConfig(file,module,name) {
|
||||
var id = crypto.createHash('sha1').update(file).digest("hex");
|
||||
|
||||
if (registry.getNodeInfo(id)) {
|
||||
throw new Error(file+" already loaded");
|
||||
var info = registry.getNodeInfo(id);
|
||||
|
||||
var isEnabled = true;
|
||||
|
||||
if (info) {
|
||||
if (info.hasOwnProperty("loaded")) {
|
||||
throw new Error(file+" already loaded");
|
||||
}
|
||||
isEnabled = info.enabled;
|
||||
}
|
||||
|
||||
var node = {
|
||||
id: id,
|
||||
file: file,
|
||||
template: file.replace(/\.js$/,".html"),
|
||||
enabled: true
|
||||
enabled: isEnabled,
|
||||
loaded:false
|
||||
}
|
||||
|
||||
if (module) {
|
||||
@ -411,7 +515,9 @@ function load(defaultNodesDir,disableNodePathScan) {
|
||||
when.settle(promises).then(function(results) {
|
||||
// Trigger a load of the configs to get it precached
|
||||
registry.getAllNodeConfigs();
|
||||
|
||||
if (settings.available()) {
|
||||
registry.saveNodeList();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@ -428,6 +534,9 @@ function load(defaultNodesDir,disableNodePathScan) {
|
||||
function loadNodeModule(node) {
|
||||
var nodeDir = path.dirname(node.file);
|
||||
var nodeFn = path.basename(node.file);
|
||||
if (!node.enabled) {
|
||||
return when.resolve(node);
|
||||
}
|
||||
try {
|
||||
var loadPromise = null;
|
||||
var r = require(node.file);
|
||||
@ -436,57 +545,74 @@ function loadNodeModule(node) {
|
||||
if (promise != null && typeof promise.then === "function") {
|
||||
loadPromise = promise.then(function() {
|
||||
node.enabled = true;
|
||||
node.loaded = true;
|
||||
return node;
|
||||
}).otherwise(function(err) {
|
||||
node.err = err;
|
||||
node.enabled = false;
|
||||
return node;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (loadPromise == null) {
|
||||
node.enabled = true;
|
||||
node.loaded = true;
|
||||
loadPromise = when.resolve(node);
|
||||
}
|
||||
return loadPromise;
|
||||
} catch(err) {
|
||||
node.err = err;
|
||||
node.enabled = false;
|
||||
return when.resolve(node);
|
||||
}
|
||||
}
|
||||
|
||||
function addNode(options) {
|
||||
var nodes = [];
|
||||
if (options.file) {
|
||||
try {
|
||||
nodes.push(loadNodeConfig(options.file));
|
||||
} catch(err) {
|
||||
return when.reject(err);
|
||||
}
|
||||
} else if (options.module) {
|
||||
var moduleFiles = scanTreeForNodesModules(options.module);
|
||||
if (moduleFiles.length === 0) {
|
||||
var err = new Error("Cannot find module '" + options.module + "'");
|
||||
err.code = 'MODULE_NOT_FOUND';
|
||||
return when.reject(err);
|
||||
}
|
||||
moduleFiles.forEach(function(moduleFile) {
|
||||
nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package));
|
||||
});
|
||||
}
|
||||
function loadNodeList(nodes) {
|
||||
var promises = [];
|
||||
nodes.forEach(function(node) {
|
||||
promises.push(loadNodeModule(node));
|
||||
});
|
||||
|
||||
return when.settle(promises).then(function(results) {
|
||||
return results.map(function(r) {
|
||||
registry.saveNodeList();
|
||||
var list = results.map(function(r) {
|
||||
return filterNodeInfo(r.value);
|
||||
});
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
function addNode(file) {
|
||||
if (!settings.available()) {
|
||||
throw new Error("Settings unavailable");
|
||||
}
|
||||
var nodes = [];
|
||||
try {
|
||||
nodes.push(loadNodeConfig(file));
|
||||
} catch(err) {
|
||||
return when.reject(err);
|
||||
}
|
||||
return loadNodeList(nodes);
|
||||
}
|
||||
|
||||
function addModule(module) {
|
||||
if (!settings.available()) {
|
||||
throw new Error("Settings unavailable");
|
||||
}
|
||||
var nodes = [];
|
||||
if (registry.getModuleInfo(module)) {
|
||||
return when.reject(new Error("Module already loaded"));
|
||||
}
|
||||
var moduleFiles = scanTreeForNodesModules(module);
|
||||
if (moduleFiles.length === 0) {
|
||||
var err = new Error("Cannot find module '" + module + "'");
|
||||
err.code = 'MODULE_NOT_FOUND';
|
||||
return when.reject(err);
|
||||
}
|
||||
moduleFiles.forEach(function(moduleFile) {
|
||||
nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package));
|
||||
});
|
||||
return loadNodeList(nodes);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init:init,
|
||||
load:load,
|
||||
@ -494,11 +620,15 @@ module.exports = {
|
||||
registerType: registry.registerNodeConstructor,
|
||||
get: registry.getNodeConstructor,
|
||||
getNodeInfo: registry.getNodeInfo,
|
||||
getNodeModuleInfo: registry.getModuleInfo,
|
||||
getNodeList: registry.getNodeList,
|
||||
getNodeConfigs: registry.getAllNodeConfigs,
|
||||
getNodeConfig: registry.getNodeConfig,
|
||||
addNode: addNode,
|
||||
removeNode: registry.removeNode,
|
||||
enableNode: registry.enableNodeSet,
|
||||
disableNode: registry.disableNodeSet
|
||||
disableNode: registry.disableNodeSet,
|
||||
|
||||
addModule: addModule,
|
||||
removeModule: registry.removeModule
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ var comms = require("./comms");
|
||||
var log = require("./log");
|
||||
var util = require("./util");
|
||||
var fs = require("fs");
|
||||
var settings = null;
|
||||
var settings = require("./settings");
|
||||
var credentials = require("./nodes/credentials");
|
||||
|
||||
var path = require('path');
|
||||
@ -33,9 +33,8 @@ var events = require("events");
|
||||
var RED = {
|
||||
|
||||
init: function(httpServer,userSettings) {
|
||||
settings = userSettings;
|
||||
settings.version = this.version();
|
||||
|
||||
userSettings.version = this.version();
|
||||
settings.init(userSettings);
|
||||
server.init(httpServer,settings);
|
||||
library.init();
|
||||
return server.app;
|
||||
@ -49,6 +48,7 @@ var RED = {
|
||||
events: events,
|
||||
log: log,
|
||||
comms: comms,
|
||||
settings:settings,
|
||||
util: util,
|
||||
version: function () {
|
||||
var p = require(path.join(process.env.NODE_RED_HOME,"package.json"));
|
||||
@ -64,6 +64,5 @@ RED.__defineGetter__("app", function() { console.log("Deprecated use of RED.app
|
||||
RED.__defineGetter__("httpAdmin", function() { return server.app });
|
||||
RED.__defineGetter__("httpNode", function() { return server.nodeApp });
|
||||
RED.__defineGetter__("server", function() { return server.server });
|
||||
RED.__defineGetter__("settings", function() { return settings });
|
||||
|
||||
module.exports = RED;
|
||||
|
283
red/server.js
283
red/server.js
@ -17,6 +17,7 @@
|
||||
var express = require('express');
|
||||
var util = require('util');
|
||||
var when = require('when');
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
var createUI = require("./ui");
|
||||
var redNodes = require("./nodes");
|
||||
@ -36,10 +37,6 @@ function createServer(_server,_settings) {
|
||||
app = createUI(settings);
|
||||
nodeApp = express();
|
||||
|
||||
app.get("/nodes",function(req,res) {
|
||||
res.send(redNodes.getNodeConfigs());
|
||||
});
|
||||
|
||||
app.get("/flows",function(req,res) {
|
||||
res.json(redNodes.getFlows());
|
||||
});
|
||||
@ -60,43 +57,82 @@ function createServer(_server,_settings) {
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
app.get("/nodes",function(req,res) {
|
||||
if (req.get("accept") == "application/json") {
|
||||
res.json(redNodes.getNodeList());
|
||||
} else {
|
||||
res.send(redNodes.getNodeConfigs());
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/nodes",
|
||||
express.json(),
|
||||
function(req,res) {
|
||||
if (!settings.available()) {
|
||||
res.send(400,new Error("Settings unavailable").toString());
|
||||
return;
|
||||
}
|
||||
var node = req.body;
|
||||
if (!node.file && !node.module) {
|
||||
var promise;
|
||||
if (node.file) {
|
||||
promise = redNodes.addNode(node.file).then(reportAddedModules);
|
||||
} else if (node.module) {
|
||||
var module = redNodes.getNodeModuleInfo(node.module);
|
||||
if (module) {
|
||||
res.send(400,"Module already loaded");
|
||||
return;
|
||||
}
|
||||
promise = installModule(node.module);
|
||||
} else {
|
||||
res.send(400,"Invalid request");
|
||||
return;
|
||||
}
|
||||
redNodes.addNode(node).then(function(info) {
|
||||
comms.publish("node/added",info,false);
|
||||
util.log("[red] Added node types:");
|
||||
for (var j=0;j<info.length;j++) {
|
||||
for (var i=0;i<info[j].types.length;i++) {
|
||||
util.log("[red] - "+info[j].types[i]);
|
||||
}
|
||||
}
|
||||
promise.then(function(info) {
|
||||
res.json(info);
|
||||
}).otherwise(function(err) {
|
||||
res.send(400,err.toString());
|
||||
if (err.code === 404) {
|
||||
res.send(404);
|
||||
} else {
|
||||
res.send(400,err.toString());
|
||||
}
|
||||
});
|
||||
},
|
||||
function(err,req,res,next) {
|
||||
console.log(err.toString());
|
||||
res.send(400,err);
|
||||
}
|
||||
);
|
||||
|
||||
app.delete("/nodes/:id",
|
||||
function(req,res) {
|
||||
if (!settings.available()) {
|
||||
res.send(400,new Error("Settings unavailable").toString());
|
||||
return;
|
||||
}
|
||||
var id = req.params.id;
|
||||
var removedNodes = [];
|
||||
try {
|
||||
var info = redNodes.removeNode(id);
|
||||
comms.publish("node/removed",info,false);
|
||||
util.log("[red] Removed node types:");
|
||||
for (var i=0;i<info.types.length;i++) {
|
||||
util.log("[red] - "+info.types[i]);
|
||||
var node = redNodes.getNodeInfo(id);
|
||||
var promise = null;
|
||||
if (!node) {
|
||||
var module = redNodes.getNodeModuleInfo(id);
|
||||
if (!module) {
|
||||
res.send(404);
|
||||
return;
|
||||
} else {
|
||||
promise = uninstallModule(id);
|
||||
}
|
||||
} else {
|
||||
promise = when.resolve([redNodes.removeNode(id)]).then(reportRemovedModules);
|
||||
}
|
||||
res.json(info);
|
||||
|
||||
promise.then(function(removedNodes) {
|
||||
res.json(removedNodes);
|
||||
}).otherwise(function(err) {
|
||||
console.log(err.stack);
|
||||
res.send(400,err.toString());
|
||||
});
|
||||
} catch(err) {
|
||||
res.send(400,err.toString());
|
||||
}
|
||||
@ -108,46 +144,199 @@ function createServer(_server,_settings) {
|
||||
|
||||
app.get("/nodes/:id", function(req,res) {
|
||||
var id = req.params.id;
|
||||
var config = redNodes.getNodeConfig(id);
|
||||
if (config) {
|
||||
res.send(config);
|
||||
var result = null;
|
||||
if (req.get("accept") == "application/json") {
|
||||
result = redNodes.getNodeInfo(id);
|
||||
} else {
|
||||
result = redNodes.getNodeConfig(id);
|
||||
}
|
||||
if (result) {
|
||||
res.send(result);
|
||||
} else {
|
||||
res.send(404);
|
||||
}
|
||||
});
|
||||
|
||||
app.put("/nodes/:id",
|
||||
express.json(),
|
||||
function(req,res) {
|
||||
if (!settings.available()) {
|
||||
res.send(400,new Error("Settings unavailable").toString());
|
||||
return;
|
||||
}
|
||||
var body = req.body;
|
||||
if (!body.hasOwnProperty("enabled")) {
|
||||
res.send(400,"Invalid request");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var info;
|
||||
var id = req.params.id;
|
||||
var node = redNodes.getNodeInfo(id);
|
||||
if (!node) {
|
||||
res.send(404);
|
||||
} else if (!node.err && node.enabled === body.enabled) {
|
||||
res.json(node);
|
||||
} else {
|
||||
if (body.enabled) {
|
||||
info = redNodes.enableNode(id);
|
||||
} else {
|
||||
info = redNodes.disableNode(id);
|
||||
}
|
||||
if (info.enabled == body.enabled && !info.err) {
|
||||
comms.publish("node/"+(body.enabled?"enabled":"disabled"),info,false);
|
||||
util.log("[red] "+(body.enabled?"Enabled":"Disabled")+" node types:");
|
||||
for (var i=0;i<info.types.length;i++) {
|
||||
util.log("[red] - "+info.types[i]);
|
||||
}
|
||||
} else if (body.enabled && info.err) {
|
||||
util.log("[red] Failed to enable node:");
|
||||
util.log("[red] - "+info.name+" : "+info.err);
|
||||
}
|
||||
res.json(info);
|
||||
}
|
||||
} catch(err) {
|
||||
res.send(400,err.toString());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
function reportAddedModules(info) {
|
||||
comms.publish("node/added",info,false);
|
||||
if (info.length > 0) {
|
||||
util.log("[red] Added node types:");
|
||||
for (var i=0;i<info.length;i++) {
|
||||
for (var j=0;j<info[i].types.length;j++) {
|
||||
util.log("[red] - "+
|
||||
(info[i].module?info[i].module+":":"")+
|
||||
info[i].types[j]+
|
||||
(info[i].err?" : "+info[i].err:"")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
function reportRemovedModules(removedNodes) {
|
||||
comms.publish("node/removed",removedNodes,false);
|
||||
util.log("[red] Removed node types:");
|
||||
for (var j=0;j<removedNodes.length;j++) {
|
||||
for (var i=0;i<removedNodes[j].types.length;i++) {
|
||||
util.log("[red] - "+(removedNodes[i].module?removedNodes[i].module+":":"")+removedNodes[j].types[i]);
|
||||
}
|
||||
}
|
||||
return removedNodes;
|
||||
}
|
||||
|
||||
function installModule(module) {
|
||||
//TODO: ensure module is 'safe'
|
||||
return when.promise(function(resolve,reject) {
|
||||
if (/[\s;]/.test(module)) {
|
||||
reject(new Error("Invalid module name"));
|
||||
return;
|
||||
}
|
||||
util.log("[red] Installing module: "+module);
|
||||
var child = exec('npm install --production '+module, function(err, stdin, stdout) {
|
||||
if (err) {
|
||||
var lookFor404 = new RegExp(" 404 .*"+module+"$","m");
|
||||
if (lookFor404.test(stdout)) {
|
||||
util.log("[red] Installation of module "+module+" failed: not found");
|
||||
var e = new Error();
|
||||
e.code = 404;
|
||||
reject(e);
|
||||
} else {
|
||||
util.log("[red] Installation of module "+module+" failed:");
|
||||
util.log("------------------------------------------");
|
||||
console.log(err.toString());
|
||||
util.log("------------------------------------------");
|
||||
reject(new Error("Install failed"));
|
||||
}
|
||||
} else {
|
||||
util.log("[red] Installed module: "+module);
|
||||
resolve(redNodes.addModule(module).then(reportAddedModules));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function uninstallModule(module) {
|
||||
var list = redNodes.removeModule(module);
|
||||
return when.promise(function(resolve,reject) {
|
||||
if (/[\s;]/.test(module)) {
|
||||
reject(new Error("Invalid module name"));
|
||||
return;
|
||||
}
|
||||
util.log("[red] Removing module: "+module);
|
||||
var child = exec('npm remove '+module, function(err, stdin, stdout) {
|
||||
if (err) {
|
||||
util.log("[red] Removal of module "+module+" failed:");
|
||||
util.log("------------------------------------------");
|
||||
console.log(err.toString());
|
||||
util.log("------------------------------------------");
|
||||
reject(new Error("Removal failed"));
|
||||
} else {
|
||||
util.log("[red] Removed module: "+module);
|
||||
reportRemovedModules(list);
|
||||
resolve(list);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
var defer = when.defer();
|
||||
|
||||
storage.init(settings).then(function() {
|
||||
console.log("\nWelcome to Node-RED\n===================\n");
|
||||
if (settings.version) {
|
||||
util.log("[red] Version: "+settings.version);
|
||||
}
|
||||
util.log("[red] Loading palette nodes");
|
||||
redNodes.init(settings,storage);
|
||||
redNodes.load().then(function() {
|
||||
var nodes = redNodes.getNodeList();
|
||||
var nodeErrors = nodes.filter(function(n) { return n.err!=null;});
|
||||
if (nodeErrors.length > 0) {
|
||||
util.log("------------------------------------------");
|
||||
if (settings.verbose) {
|
||||
for (var i=0;i<nodeErrors.length;i+=1) {
|
||||
util.log("["+nodeErrors[i].name+"] "+nodeErrors[i].err);
|
||||
}
|
||||
} else {
|
||||
util.log("[red] Failed to register "+nodeErrors.length+" node type"+(nodeErrors.length==1?"":"s"));
|
||||
util.log("[red] Run with -v for details");
|
||||
}
|
||||
util.log("------------------------------------------");
|
||||
settings.load(storage).then(function() {
|
||||
console.log("\nWelcome to Node-RED\n===================\n");
|
||||
if (settings.version) {
|
||||
util.log("[red] Version: "+settings.version);
|
||||
}
|
||||
defer.resolve();
|
||||
|
||||
redNodes.loadFlows();
|
||||
util.log("[red] Loading palette nodes");
|
||||
redNodes.init(settings,storage);
|
||||
redNodes.load().then(function() {
|
||||
var i;
|
||||
var nodes = redNodes.getNodeList();
|
||||
var nodeErrors = nodes.filter(function(n) { return n.err!=null;});
|
||||
var nodeMissing = nodes.filter(function(n) { return n.module && n.enabled && !n.loaded && !n.err;});
|
||||
if (nodeErrors.length > 0) {
|
||||
util.log("------------------------------------------");
|
||||
if (settings.verbose) {
|
||||
for (i=0;i<nodeErrors.length;i+=1) {
|
||||
util.log("["+nodeErrors[i].name+"] "+nodeErrors[i].err);
|
||||
}
|
||||
} else {
|
||||
util.log("[red] Failed to register "+nodeErrors.length+" node type"+(nodeErrors.length==1?"":"s"));
|
||||
util.log("[red] Run with -v for details");
|
||||
}
|
||||
util.log("------------------------------------------");
|
||||
}
|
||||
if (nodeMissing.length > 0) {
|
||||
util.log("[red] Missing node modules:");
|
||||
var missingModules = {};
|
||||
for (i=0;i<nodeMissing.length;i++) {
|
||||
var missing = nodeMissing[i];
|
||||
missingModules[missing.module] = (missingModules[missing.module]||[]).concat(missing.types);
|
||||
}
|
||||
var promises = [];
|
||||
for (i in missingModules) {
|
||||
if (missingModules.hasOwnProperty(i)) {
|
||||
util.log(" - "+i+": "+missingModules[i].join(", "));
|
||||
promises.push(installModule(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
defer.resolve();
|
||||
|
||||
redNodes.loadFlows();
|
||||
}).otherwise(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
comms.start();
|
||||
});
|
||||
comms.start();
|
||||
}).otherwise(function(err) {
|
||||
defer.reject(err);
|
||||
});
|
||||
|
70
red/settings.js
Normal file
70
red/settings.js
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var when = require("when");
|
||||
|
||||
var userSettings = null;
|
||||
var globalSettings = null;
|
||||
var storage = null;
|
||||
|
||||
var persistentSettings = {
|
||||
init: function(settings) {
|
||||
userSettings = settings;
|
||||
|
||||
for (var i in settings) {
|
||||
if (settings.hasOwnProperty(i)) {
|
||||
(function() {
|
||||
var j = i;
|
||||
persistentSettings.__defineGetter__(j,function() { return userSettings[j]; });
|
||||
persistentSettings.__defineSetter__(j,function() { throw new Error("Property '"+i+"' is read-only"); });
|
||||
})();
|
||||
}
|
||||
}
|
||||
globalSettings = null;
|
||||
},
|
||||
load: function(_storage) {
|
||||
storage = _storage;
|
||||
return storage.getSettings().then(function(_settings) {
|
||||
globalSettings = _settings;
|
||||
});
|
||||
},
|
||||
get: function(prop) {
|
||||
if (userSettings.hasOwnProperty(prop)) {
|
||||
return userSettings[prop];
|
||||
}
|
||||
if (globalSettings === null) {
|
||||
throw new Error("Settings not available");
|
||||
}
|
||||
return globalSettings[prop];
|
||||
},
|
||||
|
||||
set: function(prop,value) {
|
||||
if (userSettings.hasOwnProperty(prop)) {
|
||||
throw new Error("Property '"+prop+"' is read-only");
|
||||
}
|
||||
if (globalSettings === null) {
|
||||
throw new Error("Settings not available");
|
||||
}
|
||||
globalSettings[prop] = value;
|
||||
return storage.saveSettings(globalSettings);
|
||||
},
|
||||
|
||||
available: function() {
|
||||
return (globalSettings !== null);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = persistentSettings;
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,6 +17,7 @@
|
||||
var when = require('when');
|
||||
|
||||
var storageModule;
|
||||
var settingsAvailable;
|
||||
|
||||
function moduleSelector(aSettings) {
|
||||
var toReturn;
|
||||
@ -38,48 +39,64 @@ function is_malicious(path) {
|
||||
}
|
||||
|
||||
var storageModuleInterface = {
|
||||
init : function(settings) {
|
||||
init: function(settings) {
|
||||
try {
|
||||
storageModule = moduleSelector(settings);
|
||||
settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings");
|
||||
} catch (e) {
|
||||
return when.reject(e);
|
||||
}
|
||||
return storageModule.init(settings);
|
||||
},
|
||||
getFlows : function() {
|
||||
getFlows: function() {
|
||||
return storageModule.getFlows();
|
||||
},
|
||||
saveFlows : function(flows) {
|
||||
saveFlows: function(flows) {
|
||||
return storageModule.saveFlows(flows);
|
||||
},
|
||||
getCredentials : function() {
|
||||
getCredentials: function() {
|
||||
return storageModule.getCredentials();
|
||||
},
|
||||
saveCredentials : function(credentials) {
|
||||
saveCredentials: function(credentials) {
|
||||
return storageModule.saveCredentials(credentials);
|
||||
},
|
||||
getAllFlows : function() {
|
||||
getSettings: function() {
|
||||
if (settingsAvailable) {
|
||||
return storageModule.getSettings();
|
||||
} else {
|
||||
return when.resolve(null);
|
||||
}
|
||||
},
|
||||
saveSettings: function(settings) {
|
||||
if (settingsAvailable) {
|
||||
return storageModule.saveSettings(settings);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
/* Library Functions */
|
||||
getAllFlows: function() {
|
||||
return storageModule.getAllFlows();
|
||||
},
|
||||
getFlow : function(fn) {
|
||||
getFlow: function(fn) {
|
||||
if (is_malicious(fn)) {
|
||||
return when.reject(new Error('forbidden flow name'));
|
||||
}
|
||||
return storageModule.getFlow(fn);
|
||||
},
|
||||
saveFlow : function(fn, data) {
|
||||
saveFlow: function(fn, data) {
|
||||
if (is_malicious(fn)) {
|
||||
return when.reject(new Error('forbidden flow name'));
|
||||
}
|
||||
return storageModule.saveFlow(fn, data);
|
||||
},
|
||||
getLibraryEntry : function(type, path) {
|
||||
getLibraryEntry: function(type, path) {
|
||||
if (is_malicious(path)) {
|
||||
return when.reject(new Error('forbidden flow name'));
|
||||
}
|
||||
return storageModule.getLibraryEntry(type, path);
|
||||
},
|
||||
saveLibraryEntry : function(type, path, meta, body) {
|
||||
saveLibraryEntry: function(type, path, meta, body) {
|
||||
if (is_malicious(path)) {
|
||||
return when.reject(new Error('forbidden flow name'));
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ var oldCredentialsFile;
|
||||
var userDir;
|
||||
var libDir;
|
||||
var libFlowsDir;
|
||||
var globalSettingsFile;
|
||||
|
||||
function listFiles(dir) {
|
||||
var dirs = {};
|
||||
@ -140,6 +141,9 @@ var localfilesystem = {
|
||||
libDir = fspath.join(userDir,"lib");
|
||||
libFlowsDir = fspath.join(libDir,"flows");
|
||||
|
||||
|
||||
globalSettingsFile = fspath.join(userDir,".config.json");
|
||||
|
||||
return promiseDir(libFlowsDir);
|
||||
},
|
||||
|
||||
@ -207,7 +211,20 @@ var localfilesystem = {
|
||||
|
||||
return nodeFn.call(fs.writeFile, credentialsFile, credentialData)
|
||||
},
|
||||
|
||||
|
||||
getSettings: function() {
|
||||
if (fs.existsSync(globalSettingsFile)) {
|
||||
return nodeFn.call(fs.readFile,globalSettingsFile,'utf8').then(function(data) {
|
||||
return JSON.parse(data);
|
||||
});
|
||||
}
|
||||
return when.resolve({});
|
||||
},
|
||||
saveSettings: function(settings) {
|
||||
return nodeFn.call(fs.writeFile,globalSettingsFile,JSON.stringify(settings),'utf8');
|
||||
},
|
||||
|
||||
|
||||
getAllFlows: function() {
|
||||
return listFiles(libFlowsDir);
|
||||
},
|
||||
|
@ -51,36 +51,36 @@ var walkDirectory = function(dir, topdir, done) {
|
||||
errReturned = true;
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
|
||||
file = path.resolve(dir, file);
|
||||
fs.stat(file, function(err, stat) {
|
||||
if (stat && stat.isDirectory()) {
|
||||
walkDirectory(file, false, function(err) {
|
||||
if (!error) {
|
||||
error = err;
|
||||
} else {
|
||||
file = path.resolve(dir, file);
|
||||
fs.stat(file, function(err, stat) {
|
||||
if (stat && stat.isDirectory()) {
|
||||
walkDirectory(file, false, function(err) {
|
||||
if (!error) {
|
||||
error = err;
|
||||
}
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
if (path.extname(file) === ".js") {
|
||||
var testFile = file.replace(jsdir, testdir).replace(".js", "_spec.js");
|
||||
fs.exists(testFile, function (exists) {
|
||||
try {
|
||||
exists.should.equal(true, testFile + " does not exist");
|
||||
} catch (err) {
|
||||
if (!topdir) {
|
||||
return done(err);
|
||||
} else {
|
||||
error = err;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
if (path.extname(file) === ".js") {
|
||||
var testFile = file.replace(jsdir, testdir).replace(".js", "_spec.js");
|
||||
fs.exists(testFile, function (exists) {
|
||||
try {
|
||||
exists.should.equal(true, testFile + " does not exist");
|
||||
} catch (err) {
|
||||
if (!topdir) {
|
||||
return done(err);
|
||||
} else {
|
||||
error = err;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
});
|
||||
};
|
||||
|
@ -51,7 +51,11 @@ module.exports = {
|
||||
return defer.promise;
|
||||
},
|
||||
};
|
||||
redNodes.init({}, storage);
|
||||
var settings = {
|
||||
available: function() { return false; }
|
||||
}
|
||||
|
||||
redNodes.init(settings, storage);
|
||||
RED.nodes.registerType("helper", helperNode);
|
||||
testNode(RED);
|
||||
flows.load().then(function() {
|
||||
|
0
test/red/bin/nr-cli_spec.js
Normal file
0
test/red/bin/nr-cli_spec.js
Normal file
@ -170,6 +170,12 @@ describe('Credentials', function() {
|
||||
},
|
||||
saveCredentials: function(creds) {
|
||||
return when(true);
|
||||
},
|
||||
getSettings: function() {
|
||||
return when({});
|
||||
},
|
||||
saveSettings: function(s) {
|
||||
return when();
|
||||
}
|
||||
};
|
||||
function TestNode(n) {
|
||||
@ -188,8 +194,10 @@ describe('Credentials', function() {
|
||||
sinon.stub(util, 'log', function(msg) {
|
||||
logmsg = msg;
|
||||
});
|
||||
|
||||
index.init({}, storage);
|
||||
var settings = {
|
||||
available: function() { return false;}
|
||||
}
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
||||
|
@ -15,26 +15,29 @@
|
||||
**/
|
||||
|
||||
var should = require("should");
|
||||
var sinon = require("sinon");
|
||||
var when = require("when");
|
||||
var flows = require("../../../red/nodes/flows");
|
||||
var RedNode = require("../../../red/nodes/Node");
|
||||
var RED = require("../../../red/nodes");
|
||||
var events = require("../../../red/events");
|
||||
var typeRegistry = require("../../../red/nodes/registry");
|
||||
|
||||
|
||||
var settings = {
|
||||
available: function() { return false; }
|
||||
}
|
||||
|
||||
function loadFlows(testFlows, cb) {
|
||||
var storage = {
|
||||
getFlows: function() {
|
||||
var defer = when.defer();
|
||||
defer.resolve(testFlows);
|
||||
return defer.promise;
|
||||
return when.resolve(testFlows);
|
||||
},
|
||||
getCredentials: function() {
|
||||
var defer = when.defer();
|
||||
defer.resolve({});
|
||||
return defer.promise;
|
||||
},
|
||||
return when.resolve({});
|
||||
}
|
||||
};
|
||||
RED.init({}, storage);
|
||||
RED.init(settings, storage);
|
||||
flows.load().then(function() {
|
||||
should.deepEqual(testFlows, flows.getFlows());
|
||||
cb();
|
||||
@ -76,24 +79,34 @@ describe('flows', function() {
|
||||
});
|
||||
|
||||
it('should load and start an empty tab flow',function(done) {
|
||||
loadFlows([{"type":"tab","id":"tab1","label":"Sheet 1"}],
|
||||
function() {});
|
||||
loadFlows([{"type":"tab","id":"tab1","label":"Sheet 1"}], function() {});
|
||||
events.once('nodes-started', function() { done(); });
|
||||
});
|
||||
|
||||
it('should load and start a registered node type', function(done) {
|
||||
RED.registerType('debug', function() {});
|
||||
var typeRegistryGet = sinon.stub(typeRegistry,"get",function(nt) {
|
||||
return function() {};
|
||||
});
|
||||
loadFlows([{"id":"n1","type":"debug"}], function() { });
|
||||
events.once('nodes-started', function() { done(); });
|
||||
events.once('nodes-started', function() {
|
||||
typeRegistryGet.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load and start when node type is registered',
|
||||
function(done) {
|
||||
loadFlows([{"id":"n2","type":"inject"}],
|
||||
function() {
|
||||
RED.registerType('inject', function() { });
|
||||
});
|
||||
events.once('nodes-started', function() { done(); });
|
||||
it('should load and start when node type is registered', function(done) {
|
||||
var typeRegistryGet = sinon.stub(typeRegistry,"get");
|
||||
typeRegistryGet.onCall(0).returns(null);
|
||||
typeRegistryGet.returns(function(){});
|
||||
|
||||
loadFlows([{"id":"n2","type":"inject"}], function() {
|
||||
events.emit('type-registered','inject');
|
||||
});
|
||||
events.once('nodes-started', function() {
|
||||
typeRegistryGet.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -112,7 +125,7 @@ describe('flows', function() {
|
||||
return when(true);
|
||||
}
|
||||
};
|
||||
RED.init({}, storage);
|
||||
RED.init(settings, storage);
|
||||
flows.setFlows(testFlows);
|
||||
events.once('nodes-started', function() { done(); });
|
||||
});
|
||||
|
@ -43,7 +43,11 @@ describe("red/nodes/index", function() {
|
||||
saveCredentials: function(creds) {
|
||||
return when(true);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var settings = {
|
||||
available: function() { return false }
|
||||
};
|
||||
|
||||
function TestNode(n) {
|
||||
index.createNode(this, n);
|
||||
@ -55,7 +59,7 @@ describe("red/nodes/index", function() {
|
||||
|
||||
it('nodes are initialised with credentials',function(done) {
|
||||
|
||||
index.init({}, storage);
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
||||
@ -69,7 +73,7 @@ describe("red/nodes/index", function() {
|
||||
});
|
||||
|
||||
it('flows should be initialised',function(done) {
|
||||
index.init({}, storage);
|
||||
index.init(settings, storage);
|
||||
index.loadFlows().then(function() {
|
||||
should.deepEqual(testFlows, index.getFlows());
|
||||
done();
|
||||
@ -131,7 +135,7 @@ describe("red/nodes/index", function() {
|
||||
|
||||
});
|
||||
|
||||
describe('allows nodes to be removed from the registry', function() {
|
||||
describe('allows nodes to be added/remove/enabled/disabled from the registry', function() {
|
||||
var registry = require("../../../red/nodes/registry");
|
||||
var randomNodeInfo = {id:"5678",types:["random"]};
|
||||
|
||||
@ -148,17 +152,21 @@ describe("red/nodes/index", function() {
|
||||
sinon.stub(registry,"removeNode",function(id) {
|
||||
return randomNodeInfo;
|
||||
});
|
||||
sinon.stub(registry,"disableNode",function(id) {
|
||||
return randomNodeInfo;
|
||||
});
|
||||
});
|
||||
after(function() {
|
||||
registry.getNodeInfo.restore();
|
||||
registry.removeNode.restore();
|
||||
registry.disableNode.restore();
|
||||
});
|
||||
|
||||
it(': allows an unused node type to be removed',function(done) {
|
||||
index.init({}, storage);
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
var info = index.removeNode("random");
|
||||
var info = index.removeNode("5678");
|
||||
registry.removeNode.calledOnce.should.be.true;
|
||||
registry.removeNode.calledWith("5678").should.be.true;
|
||||
info.should.eql(randomNodeInfo);
|
||||
@ -167,9 +175,23 @@ describe("red/nodes/index", function() {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it(': allows an unused node type to be disabled',function(done) {
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
var info = index.disableNode("5678");
|
||||
registry.disableNode.calledOnce.should.be.true;
|
||||
registry.disableNode.calledWith("5678").should.be.true;
|
||||
info.should.eql(randomNodeInfo);
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it(': prevents removing a node type that is in use',function(done) {
|
||||
index.init({}, storage);
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
/*jshint immed: false */
|
||||
@ -183,8 +205,23 @@ describe("red/nodes/index", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it(': prevents disabling a node type that is in use',function(done) {
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
index.disabledNode("test");
|
||||
}).should.throw();
|
||||
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it(': prevents removing a node type that is unknown',function(done) {
|
||||
index.init({}, storage);
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
/*jshint immed: false */
|
||||
@ -192,6 +229,20 @@ describe("red/nodes/index", function() {
|
||||
index.removeNode("doesnotexist");
|
||||
}).should.throw();
|
||||
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it(': prevents disabling a node type that is unknown',function(done) {
|
||||
index.init(settings, storage);
|
||||
index.registerType('test', TestNode);
|
||||
index.loadFlows().then(function() {
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
index.disableNode("doesnotexist");
|
||||
}).should.throw();
|
||||
|
||||
done();
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
|
@ -31,6 +31,15 @@ describe('NodeRegistry', function() {
|
||||
|
||||
var resourcesDir = __dirname+ path.sep + "resources" + path.sep;
|
||||
|
||||
function stubSettings(s,available) {
|
||||
s.available = function() {return available;}
|
||||
s.set = function(s,v) {},
|
||||
s.get = function(s) { return null;}
|
||||
return s
|
||||
}
|
||||
var settings = stubSettings({},false);
|
||||
var settingsWithStorage = stubSettings({},true);
|
||||
|
||||
it('automatically registers new nodes',function() {
|
||||
var testNode = RedNodes.getNode('123');
|
||||
should.not.exist(n);
|
||||
@ -42,7 +51,7 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('handles nodes that export a function', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -64,7 +73,7 @@ describe('NodeRegistry', function() {
|
||||
|
||||
|
||||
it('handles nodes that export a function returning a resolving promise', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load(resourcesDir + "TestNode2",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -84,15 +93,14 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('handles nodes that export a function returning a rejecting promise', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load(resourcesDir + "TestNode3",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
list[0].should.have.property("id");
|
||||
list[0].should.have.property("name","TestNode3.js");
|
||||
list[0].should.have.property("types",["test-node-3"]);
|
||||
list[0].should.have.property("enabled",false);
|
||||
|
||||
list[0].should.have.property("enabled",true);
|
||||
list[0].should.have.property("err","fail");
|
||||
|
||||
var nodeConstructor = typeRegistry.get("test-node-3");
|
||||
@ -106,7 +114,7 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('handles files containing multiple nodes', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load(resourcesDir + "MultipleNodes1",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -129,7 +137,7 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('handles nested directories', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -146,7 +154,7 @@ describe('NodeRegistry', function() {
|
||||
|
||||
it('emits type-registered and node-icon-dir events', function(done) {
|
||||
var eventEmitSpy = sinon.spy(events,"emit");
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load(resourcesDir + "NestedDirectoryNode",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -173,9 +181,10 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('rejects a duplicate node type registration', function(done) {
|
||||
typeRegistry.init({
|
||||
|
||||
typeRegistry.init(stubSettings({
|
||||
nodesDir:[resourcesDir + "TestNode1",resourcesDir + "DuplicateTestNode"]
|
||||
});
|
||||
},false));
|
||||
typeRegistry.load("wontexist",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
|
||||
@ -191,7 +200,7 @@ describe('NodeRegistry', function() {
|
||||
|
||||
list[1].should.have.property("name","TestNode1.js");
|
||||
list[1].should.have.property("types",["test-node-1"]);
|
||||
list[1].should.have.property("enabled",false);
|
||||
list[1].should.have.property("enabled",true);
|
||||
list[1].should.have.property("err");
|
||||
/already registered/.test(list[1].err).should.be.true;
|
||||
|
||||
@ -206,11 +215,10 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('handles nodesDir as a string', function(done) {
|
||||
var settings = {
|
||||
nodesDir :resourcesDir + "TestNode1"
|
||||
}
|
||||
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.init(stubSettings({
|
||||
nodesDir :resourcesDir + "TestNode1"
|
||||
},false));
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -223,11 +231,10 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('handles invalid nodesDir',function(done) {
|
||||
var settings = {
|
||||
nodesDir : "wontexist"
|
||||
}
|
||||
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.init(stubSettings({
|
||||
nodesDir : "wontexist"
|
||||
},false));
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
@ -238,7 +245,7 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('returns nothing for an unregistered type config', function() {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var config = typeRegistry.getNodeConfig("imaginary-shark");
|
||||
(config === null).should.be.true;
|
||||
@ -248,10 +255,10 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('excludes node files listed in nodesExcludes',function(done) {
|
||||
typeRegistry.init({
|
||||
typeRegistry.init(stubSettings({
|
||||
nodesExcludes: [ "TestNode1.js" ],
|
||||
nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2"]
|
||||
});
|
||||
},false));
|
||||
typeRegistry.load("wontexist",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -263,9 +270,9 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('returns the node configurations', function(done) {
|
||||
typeRegistry.init({
|
||||
typeRegistry.init(stubSettings({
|
||||
nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2"]
|
||||
});
|
||||
},false));
|
||||
typeRegistry.load("wontexist",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
|
||||
@ -276,20 +283,59 @@ describe('NodeRegistry', function() {
|
||||
|
||||
var nodeId = list[0].id;
|
||||
var nodeConfig = typeRegistry.getNodeConfig(nodeId);
|
||||
nodeConfig.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n<script type=\"text/javascript\"></script>");
|
||||
nodeConfig.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n");
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
done(e);
|
||||
});
|
||||
});
|
||||
|
||||
it('stores the node list', function(done) {
|
||||
var settings = {
|
||||
nodesDir:[resourcesDir + "TestNode1",resourcesDir + "TestNode2",resourcesDir + "TestNode3"],
|
||||
available: function() { return true; },
|
||||
set: function(s,v) {},
|
||||
get: function(s) { return null;}
|
||||
}
|
||||
var settingsSave = sinon.spy(settings,"set");
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load("wontexist",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.Array.and.have.length(3);
|
||||
|
||||
settingsSave.callCount.should.equal(1);
|
||||
settingsSave.firstCall.args[0].should.be.equal("nodes");
|
||||
var savedList = settingsSave.firstCall.args[1];
|
||||
|
||||
savedList[list[0].id].name == list[0].name;
|
||||
savedList[list[1].id].name == list[1].name;
|
||||
savedList[list[2].id].name == list[2].name;
|
||||
|
||||
savedList[list[0].id].should.not.have.property("err");
|
||||
savedList[list[1].id].should.not.have.property("err");
|
||||
savedList[list[2].id].should.not.have.property("err");
|
||||
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
done(e);
|
||||
}).finally(function() {
|
||||
settingsSave.restore();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('allows nodes to be added by filename', function(done) {
|
||||
typeRegistry.init({});
|
||||
var settings = {
|
||||
available: function() { return true; },
|
||||
set: function(s,v) {},
|
||||
get: function(s) { return null;}
|
||||
}
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
|
||||
typeRegistry.addNode({file: resourcesDir + "TestNode1/TestNode1.js"}).then(function(node) {
|
||||
typeRegistry.addNode(resourcesDir + "TestNode1/TestNode1.js").then(function(node) {
|
||||
list = typeRegistry.getNodeList();
|
||||
list[0].should.have.property("id");
|
||||
list[0].should.have.property("name","TestNode1.js");
|
||||
@ -311,11 +357,11 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('fails to add non-existent filename', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
typeRegistry.addNode({file: resourcesDir + "DoesNotExist/DoesNotExist.js"}).then(function(node) {
|
||||
typeRegistry.addNode(resourcesDir + "DoesNotExist/DoesNotExist.js").then(function(node) {
|
||||
done(new Error("ENOENT not thrown"));
|
||||
}).otherwise(function(e) {
|
||||
e.code.should.eql("ENOENT");
|
||||
@ -328,7 +374,7 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('returns node info by type or id', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -357,7 +403,7 @@ describe('NodeRegistry', function() {
|
||||
|
||||
|
||||
it('rejects adding duplicate nodes', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load(resourcesDir + "TestNode1",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -376,18 +422,23 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('removes nodes from the registry', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load(resourcesDir + "TestNode1",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
list[0].should.have.property("id");
|
||||
list[0].should.have.property("name","TestNode1.js");
|
||||
list[0].should.have.property("types",["test-node-1"]);
|
||||
list[0].should.have.property("enabled",true);
|
||||
list[0].should.have.property("loaded",true);
|
||||
|
||||
typeRegistry.getNodeConfigs().length.should.be.greaterThan(0);
|
||||
|
||||
var info = typeRegistry.removeNode(list[0].id);
|
||||
info.should.eql(list[0]);
|
||||
|
||||
info.should.have.property("id",list[0].id);
|
||||
info.should.have.property("enabled",false);
|
||||
info.should.have.property("loaded",false);
|
||||
|
||||
typeRegistry.getNodeList().should.be.an.Array.and.be.empty;
|
||||
typeRegistry.getNodeConfigs().length.should.equal(0);
|
||||
@ -403,7 +454,7 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
|
||||
it('rejects removing unknown nodes from the registry', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load("wontexist",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
@ -451,7 +502,7 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
})();
|
||||
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load("wontexist",false).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(2);
|
||||
@ -464,7 +515,7 @@ describe('NodeRegistry', function() {
|
||||
list[1].should.have.property("id");
|
||||
list[1].should.have.property("name","TestNodeModule:TestNodeMod2");
|
||||
list[1].should.have.property("types",["test-node-mod-2"]);
|
||||
list[1].should.have.property("enabled",false);
|
||||
list[1].should.have.property("enabled",true);
|
||||
list[1].should.have.property("err");
|
||||
|
||||
|
||||
@ -516,13 +567,12 @@ describe('NodeRegistry', function() {
|
||||
return result;
|
||||
});
|
||||
})();
|
||||
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
|
||||
typeRegistry.addNode({module: "TestNodeModule"}).then(function(node) {
|
||||
typeRegistry.addModule("TestNodeModule").then(function(node) {
|
||||
list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(2);
|
||||
list[0].should.have.property("id");
|
||||
@ -534,7 +584,7 @@ describe('NodeRegistry', function() {
|
||||
list[1].should.have.property("id");
|
||||
list[1].should.have.property("name","TestNodeModule:TestNodeMod2");
|
||||
list[1].should.have.property("types",["test-node-mod-2"]);
|
||||
list[1].should.have.property("enabled",false);
|
||||
list[1].should.have.property("enabled",true);
|
||||
list[1].should.have.property("err");
|
||||
|
||||
node.should.eql(list);
|
||||
@ -552,13 +602,62 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('rejects adding duplicate node modules', function(done) {
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var pathJoin = (function() {
|
||||
var _join = path.join;
|
||||
return sinon.stub(path,"join",function() {
|
||||
if (arguments.length == 3 && arguments[2] == "package.json") {
|
||||
return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1],arguments[2]);
|
||||
}
|
||||
if (arguments.length == 2 && arguments[1] == "TestNodeModule") {
|
||||
return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1]);
|
||||
}
|
||||
return _join.apply(this,arguments);
|
||||
});
|
||||
})();
|
||||
|
||||
var readdirSync = (function() {
|
||||
var originalReaddirSync = fs.readdirSync;
|
||||
var callCount = 0;
|
||||
return sinon.stub(fs,"readdirSync",function(dir) {
|
||||
var result = [];
|
||||
if (callCount == 1) {
|
||||
result = originalReaddirSync(resourcesDir + "TestNodeModule" + path.sep + "node_modules");
|
||||
}
|
||||
callCount++;
|
||||
return result;
|
||||
});
|
||||
})();
|
||||
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load('wontexist',false).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(2);
|
||||
typeRegistry.addModule("TestNodeModule").then(function(node) {
|
||||
done(new Error("addModule resolved"));
|
||||
}).otherwise(function(err) {
|
||||
done();
|
||||
});
|
||||
}).catch(function(e) {
|
||||
done(e);
|
||||
}).finally(function() {
|
||||
readdirSync.restore();
|
||||
pathJoin.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('fails to add non-existent module name', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
|
||||
typeRegistry.addNode({module: "DoesNotExistModule"}).then(function(node) {
|
||||
typeRegistry.addModule("DoesNotExistModule").then(function(node) {
|
||||
done(new Error("ENOENT not thrown"));
|
||||
}).otherwise(function(e) {
|
||||
e.code.should.eql("MODULE_NOT_FOUND");
|
||||
@ -570,9 +669,80 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('removes nodes from the registry by module', function(done) {
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var pathJoin = (function() {
|
||||
var _join = path.join;
|
||||
return sinon.stub(path,"join",function() {
|
||||
if (arguments.length == 3 && arguments[2] == "package.json") {
|
||||
return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1],arguments[2]);
|
||||
}
|
||||
if (arguments.length == 2 && arguments[1] == "TestNodeModule") {
|
||||
return _join(resourcesDir,"TestNodeModule" + path.sep + "node_modules" + path.sep,arguments[1]);
|
||||
}
|
||||
return _join.apply(this,arguments);
|
||||
});
|
||||
})();
|
||||
|
||||
var readdirSync = (function() {
|
||||
var originalReaddirSync = fs.readdirSync;
|
||||
var callCount = 0;
|
||||
return sinon.stub(fs,"readdirSync",function(dir) {
|
||||
var result = [];
|
||||
if (callCount == 1) {
|
||||
result = originalReaddirSync(resourcesDir + "TestNodeModule" + path.sep + "node_modules");
|
||||
}
|
||||
callCount++;
|
||||
return result;
|
||||
});
|
||||
})();
|
||||
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load('wontexist',false).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(2);
|
||||
var res = typeRegistry.removeModule("TestNodeModule");
|
||||
|
||||
res.should.be.an.Array.and.have.lengthOf(2);
|
||||
res[0].should.have.a.property("id",list[0].id);
|
||||
res[1].should.have.a.property("id",list[1].id);
|
||||
|
||||
list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
done(e);
|
||||
}).finally(function() {
|
||||
readdirSync.restore();
|
||||
pathJoin.restore();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('fails to remove non-existent module name', function(done) {
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load("wontexist",true).then(function(){
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
typeRegistry.removeModule("DoesNotExistModule");
|
||||
}).should.throw();
|
||||
|
||||
done();
|
||||
|
||||
}).catch(function(e) {
|
||||
done(e);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('allows nodes to be enabled and disabled', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.init(settingsWithStorage);
|
||||
typeRegistry.load(resourcesDir+path.sep+"TestNode1",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -583,7 +753,9 @@ describe('NodeRegistry', function() {
|
||||
var nodeConfig = typeRegistry.getNodeConfigs();
|
||||
nodeConfig.length.should.be.greaterThan(0);
|
||||
|
||||
typeRegistry.disableNode(list[0].id);
|
||||
var info = typeRegistry.disableNode(list[0].id);
|
||||
info.should.have.property("id",list[0].id);
|
||||
info.should.have.property("enabled",false);
|
||||
|
||||
var list2 = typeRegistry.getNodeList();
|
||||
list2.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -591,7 +763,9 @@ describe('NodeRegistry', function() {
|
||||
|
||||
typeRegistry.getNodeConfigs().length.should.equal(0);
|
||||
|
||||
typeRegistry.enableNode(list[0].id);
|
||||
var info2 = typeRegistry.enableNode(list[0].id);
|
||||
info2.should.have.property("id",list[0].id);
|
||||
info2.should.have.property("enabled",true);
|
||||
|
||||
var list3 = typeRegistry.getNodeList();
|
||||
list3.should.be.an.Array.and.have.lengthOf(1);
|
||||
@ -606,27 +780,25 @@ describe('NodeRegistry', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not allow a node with error to be enabled', function(done) {
|
||||
typeRegistry.init({});
|
||||
typeRegistry.load(resourcesDir+path.sep+"TestNode3",true).then(function() {
|
||||
it('fails to enable/disable non-existent nodes', function(done) {
|
||||
typeRegistry.init(settings);
|
||||
typeRegistry.load("wontexist",true).then(function() {
|
||||
var list = typeRegistry.getNodeList();
|
||||
list.should.be.an.Array.and.have.lengthOf(1);
|
||||
list[0].should.have.property("id");
|
||||
list[0].should.have.property("name","TestNode3.js");
|
||||
list[0].should.have.property("enabled",false);
|
||||
list[0].should.have.property("err");
|
||||
list.should.be.an.Array.and.be.empty;
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
typeRegistry.enable(list[0].id);
|
||||
typeRegistry.disableNode("123");
|
||||
}).should.throw();
|
||||
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
typeRegistry.enableNode("123");
|
||||
}).should.throw();
|
||||
|
||||
done();
|
||||
}).catch(function(e) {
|
||||
done(e);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
109
test/red/settings_spec.js
Normal file
109
test/red/settings_spec.js
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var should = require("should");
|
||||
var when = require("when");
|
||||
|
||||
var settings = require("../../red/settings");
|
||||
|
||||
|
||||
describe("red/settings", function() {
|
||||
it('wraps the user settings as read-only properties', function() {
|
||||
var userSettings = {
|
||||
a: 123,
|
||||
b: "test",
|
||||
c: [1,2,3]
|
||||
}
|
||||
settings.init(userSettings);
|
||||
|
||||
settings.available().should.be.false;
|
||||
|
||||
settings.a.should.equal(123);
|
||||
settings.b.should.equal("test");
|
||||
settings.c.should.be.an.Array.with.lengthOf(3);
|
||||
|
||||
settings.get("a").should.equal(123);
|
||||
settings.get("b").should.equal("test");
|
||||
settings.get("c").should.be.an.Array.with.lengthOf(3);
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.a = 456;
|
||||
}).should.throw();
|
||||
|
||||
settings.c.push(5);
|
||||
settings.c.should.be.an.Array.with.lengthOf(4);
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.set("a",456);
|
||||
}).should.throw();
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.set("a",456);
|
||||
}).should.throw();
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.get("unknown");
|
||||
}).should.throw();
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.set("unknown",456);
|
||||
}).should.throw();
|
||||
|
||||
});
|
||||
|
||||
it('loads global settings from storage', function(done) {
|
||||
var userSettings = {
|
||||
a: 123,
|
||||
b: "test",
|
||||
c: [1,2,3]
|
||||
}
|
||||
var savedSettings = null;
|
||||
var storage = {
|
||||
getSettings: function() {
|
||||
return when.resolve({globalA:789});
|
||||
},
|
||||
saveSettings: function(settings) {
|
||||
savedSettings = settings;
|
||||
return when.resolve();
|
||||
}
|
||||
}
|
||||
settings.init(userSettings);
|
||||
|
||||
settings.available().should.be.false;
|
||||
|
||||
/*jshint immed: false */
|
||||
(function() {
|
||||
settings.get("unknown");
|
||||
}).should.throw();
|
||||
|
||||
settings.load(storage).then(function() {
|
||||
settings.available().should.be.true;
|
||||
settings.get("globalA").should.equal(789);
|
||||
settings.set("globalA","abc").then(function() {
|
||||
savedSettings.globalA.should.equal("abc");
|
||||
done();
|
||||
});
|
||||
}).otherwise(function(err) {
|
||||
done(err);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user