mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00: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:
		
							
								
								
									
										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); | ||||
|         }); | ||||
|          | ||||
|          | ||||
|     }); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user