1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Ben Hardill 2013-09-27 10:56:26 +01:00
commit a4fb33ee73
23 changed files with 271 additions and 365 deletions

View File

@ -30,7 +30,7 @@ From the top-level directory, run:
You can then access Node-RED at <http://localhost:1880>. You can then access Node-RED at <http://localhost:1880>.
Online documentation is available at <http://node-red.github.io/docs>. Online documentation is available at <http://nodered.org/docs>.
## Installing individual node dependencies ## Installing individual node dependencies

View File

@ -12,6 +12,10 @@ Check out [INSTALL](INSTALL.md) for full instructions on getting started.
4. node red.js 4. node red.js
5. Open <http://localhost:1880> 5. Open <http://localhost:1880>
## Documentation
More documentation can be found [here](http://nodered.org/docs).
## Browser Support ## Browser Support
The Node-RED editor runs in the browser. We routinely develop and test using The Node-RED editor runs in the browser. We routinely develop and test using
@ -31,8 +35,21 @@ been raised.
### Creating new nodes ### Creating new nodes
The plugin nature of Node-RED means anyone can create a new node to extend The plugin nature of Node-RED means anyone can create a new node to extend
its capabilities. Eventually, the nodes will be npm-installable, but we're not its capabilities.
there yet.
We want to avoid duplication as that can lead to confusion. Many of our existing
nodes offer a starting point of functionality. If they are missing features,
we would rather extend them than add separate 'advanced' versions. But the key
to that approach is getting the UX right to not lose the simplicity.
We are also going to be quite selective over what nodes are included in the main
repository - enough to be useful, but not so many that new user is overwhelmed.
To contribute a new node, please raise a pull-request against the
`node-red-nodes` repository.
Eventually, the nodes will be npm-installable, but we're not there yet. We'll
also have some sort of registry of nodes to help with discoverability.
### Pull-Requests ### Pull-Requests
@ -42,7 +59,11 @@ property license granted with any contribution. It is for your protection as a
Contributor as well as the protection of IBM and its customers; it does not Contributor as well as the protection of IBM and its customers; it does not
change your rights to use your own Contributions for any other purpose. change your rights to use your own Contributions for any other purpose.
We'll add some information on how to do this in the next few days. Once you have created a pull-request, we'll provide a link to the appropriate
CLA document.
If you are an IBMer, please contact us directly as the contribution process is
slightly different.
## Authors ## Authors

View File

@ -15,61 +15,62 @@
--> -->
<script type="text/x-red" data-template-name="inject"> <script type="text/x-red" data-template-name="inject">
<div class="form-row">
<label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row node-input-payload"> <div class="form-row node-input-payload">
<label for="node-input-payload"><i class="icon-envelope"></i> Payload</label> <label for="node-input-payload"><i class="icon-envelope"></i> Payload</label>
<input type="text" id="node-input-payload" placeholder="Payload"> <input type="text" id="node-input-payload" placeholder="Payload">
</div> </div>
<div class="form-row">
<label for="node-input-topic"><i class="icon-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-once" placeholder="once" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-once" placeholder="once" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label> <label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for=""><i class="icon-repeat"></i> Repeat</label> <label for=""><i class="icon-repeat"></i> Repeat</label>
<select id="inject-time-type-select"><option value="none">None</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select> <select id="inject-time-type-select"><option value="none">None</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
<input type="hidden" id="node-input-repeat" placeholder="Payload"> <input type="hidden" id="node-input-repeat" placeholder="Payload">
<input type="hidden" id="node-input-crontab" placeholder="Payload"> <input type="hidden" id="node-input-crontab" placeholder="Payload">
</div> </div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval"> <div class="form-row inject-time-row hidden" id="inject-time-row-interval">
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input> every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/> <select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
on <select disabled id="inject-time-interval-days" class="inject-time-days"></select> on <select disabled id="inject-time-interval-days" class="inject-time-days"></select>
</div> </div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time"> <div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
every <input id="inject-time-interval-time-units" class="inject-time-count" value="1"></input> minutes<br/> every <input id="inject-time-interval-time-units" class="inject-time-count" value="1"></input> minutes<br/>
between <select id="inject-time-interval-time-start" class="inject-time-times"></select> between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/> and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
on <select id="inject-time-interval-time-days" class="inject-time-days"></select> on <select id="inject-time-interval-time-days" class="inject-time-days"></select>
</div> </div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time"> <div class="form-row inject-time-row hidden" id="inject-time-row-time">
at <input id="inject-time-time" value="12:00"></input><br/> at <input id="inject-time-time" value="12:00"></input><br/>
on <select id="inject-time-time-days" class="inject-time-days"></select> on <select id="inject-time-time-days" class="inject-time-days"></select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
</div> </div>
<div class="form-tips">Tip: Injects Date.now() if no payload set</div> <div class="form-tips">Tip: Injects Date.now() if no payload set</div>
<script> <script>
{ {
$("#inject-time-type-select").change(function() { $("#inject-time-type-select").change(function() {
var id = $("#inject-time-type-select option:selected").val(); var id = $("#inject-time-type-select option:selected").val();
$(".inject-time-row").hide(); $(".inject-time-row").hide();
$("#inject-time-row-"+id).show(); $("#inject-time-row-"+id).show();
}); });
var days = [ var days = [
{v:"*",t:"every day"}, {v:"*",t:"every day"},
{v:"1-5",t:"Mondays to Fridays"}, {v:"1-5",t:"Mondays to Fridays"},
@ -87,7 +88,7 @@
$(this).append($("<option></option>").val(days[d].v).text(days[d].t)); $(this).append($("<option></option>").val(days[d].v).text(days[d].t));
} }
}); });
$(".inject-time-times").each(function() { $(".inject-time-times").each(function() {
for (var i=0;i<24;i++) { for (var i=0;i<24;i++) {
var l = (i<10?"0":"")+i+":00"; var l = (i<10?"0":"")+i+":00";
@ -98,14 +99,14 @@
min:1, min:1,
max:60 max:60
}); });
$("#inject-time-interval-units").change(function() { $("#inject-time-interval-units").change(function() {
var units = $("#inject-time-interval-units option:selected").val(); var units = $("#inject-time-interval-units option:selected").val();
$("#inject-time-interval-days").prop("disabled",(units == "s")?"disabled":false); $("#inject-time-interval-days").prop("disabled",(units == "s")?"disabled":false);
$(".inject-time-count").spinner("option","max",(units == "h")?24:60); $(".inject-time-count").spinner("option","max",(units == "h")?24:60);
}); });
$.widget( "ui.injecttimespinner", $.ui.spinner, { $.widget( "ui.injecttimespinner", $.ui.spinner, {
options: { options: {
@ -131,16 +132,16 @@
var d = new Date(value); var d = new Date(value);
var h = d.getHours(); var h = d.getHours();
var m = d.getMinutes(); var m = d.getMinutes();
return ((h<10)?"0":"")+h+":"+((m<10)?"0":"")+m; return ((h<10)?"0":"")+h+":"+((m<10)?"0":"")+m;
} }
}); });
$("#inject-time-time").injecttimespinner(); $("#inject-time-time").injecttimespinner();
}; };
</script> </script>
</script> </script>
<style> <style>
@ -169,7 +170,7 @@
width: 30px !important; width: 30px !important;
} }
. .
</style> </style>
<script type="text/x-red" data-help-name="inject"> <script type="text/x-red" data-help-name="inject">
<p>Pressing the button on the left side of the node allows a message on a topic to be injected into the flow. This is mainly for test purposes.</p> <p>Pressing the button on the left side of the node allows a message on a topic to be injected into the flow. This is mainly for test purposes.</p>
@ -194,7 +195,7 @@
outputs:1, outputs:1,
icon: "inject.png", icon: "inject.png",
label: function() { label: function() {
return this.name||this.topic||this.payload; return this.name||this.topic||this.payload||"inject";
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
@ -216,7 +217,7 @@
$("#inject-time-time").val(time); $("#inject-time-time").val(time);
$("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true); $("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
$("#inject-time-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true); $("#inject-time-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
} else if (cronparts[0] == "0") { } else if (cronparts[0] == "0") {
// interval - hours // interval - hours
var hours = cronparts[1].slice(2); var hours = cronparts[1].slice(2);
@ -257,7 +258,7 @@
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0 // 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
var startparts = timeparts[0].split("-"); var startparts = timeparts[0].split("-");
start = startparts[0]; start = startparts[0];
var endparts = timeparts[1].split("-"); var endparts = timeparts[1].split("-");
if (endparts.length == 1) { if (endparts.length == 1) {
end = Number(endparts[0])+1; end = Number(endparts[0])+1;
@ -272,12 +273,12 @@
} else { } else {
$("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true); $("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
} }
$(".inject-time-row").hide(); $(".inject-time-row").hide();
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true); $("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
$("#inject-time-row-"+repeattype).show(); $("#inject-time-row-"+repeattype).show();
}, },
oneditsave: function() { oneditsave: function() {
var repeat = ""; var repeat = "";
@ -297,7 +298,7 @@
} else if (units == "h") { } else if (units == "h") {
crontab = "0 */"+count+" * * "+days; crontab = "0 */"+count+" * * "+days;
} }
} }
} else if (type == "interval-time") { } else if (type == "interval-time") {
var count = $("#inject-time-interval-time-units").val(); var count = $("#inject-time-interval-time-units").val();
var startTime = Number($("#inject-time-interval-time-start option:selected").val()); var startTime = Number($("#inject-time-interval-time-start option:selected").val());
@ -338,10 +339,10 @@
repeat = 0; repeat = 0;
crontab = parts[1]+" "+parts[0]+" * * "+days; crontab = parts[1]+" "+parts[0]+" * * "+days;
} }
$("#node-input-repeat").val(repeat); $("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab); $("#node-input-crontab").val(crontab);
}, },
button: { button: {
onclick: function() { onclick: function() {

View File

@ -117,6 +117,9 @@
var errornotification = null; var errornotification = null;
var messageCount = 0;
function debugConnect() { function debugConnect() {
//console.log("debug ws connecting"); //console.log("debug ws connecting");
var ws = new WebSocket("ws://"+location.hostname+":"+location.port+document.location.pathname+"/debug"); var ws = new WebSocket("ws://"+location.hostname+":"+location.port+document.location.pathname+"/debug");
@ -160,7 +163,13 @@
(o.topic?'<span class="debug-message-topic">'+topic+'</span>':'')+ (o.topic?'<span class="debug-message-topic">'+topic+'</span>':'')+
'<span class="debug-message-payload">'+payload+'</span>'; '<span class="debug-message-payload">'+payload+'</span>';
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5; var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
messageCount++;
$(messages).append(msg); $(messages).append(msg);
if (messageCount > 200) {
$("#debug-content .debug-message:first").remove();
messageCount--;
}
if (atBottom) { if (atBottom) {
$(sbc).scrollTop(sbc.scrollHeight); $(sbc).scrollTop(sbc.scrollHeight);
} }
@ -176,6 +185,7 @@
$("#debug-tab-clear").click(function() { $("#debug-tab-clear").click(function() {
$(".debug-message").remove(); $(".debug-message").remove();
messageCount = 0;
RED.nodes.eachNode(function(node) { RED.nodes.eachNode(function(node) {
node.highlighted = false; node.highlighted = false;
node.dirty = true; node.dirty = true;

View File

@ -43,7 +43,7 @@
outputs:0, outputs:0,
icon: "file.png", icon: "file.png",
label: function() { label: function() {
return this.name||"comment"; return this.name||"";
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";

View File

@ -117,15 +117,17 @@ function GPIOOutNode(n) {
} }
} }
exec("gpio reset",function(err,stdout,stderr) { exec("gpio mode 0 in",function(err,stdout,stderr) {
if (err) { if (err) {
util.log('[36-rpi-gpio.js] Error: "gpio reset" command failed for some reason.'); util.log('[36-rpi-gpio.js] Error: "gpio" command failed for some reason.');
} }
exec("gpio load spi",function(err,stdout,stderr) { exec("gpio mode 1 in");
if (err) { exec("gpio mode 2 in");
util.log('[36-rpi-gpio.js] Error: "gpio load spi" command failed for some reason.'); exec("gpio mode 3 in");
} exec("gpio mode 4 in");
exec("gpio mode 5 in");
exec("gpio mode 6 in");
exec("gpio mode 7 in",function(err,stdout,stderr) {
RED.nodes.registerType("rpi-gpio in",GPIOInNode); RED.nodes.registerType("rpi-gpio in",GPIOInNode);
RED.nodes.registerType("rpi-gpio out",GPIOOutNode); RED.nodes.registerType("rpi-gpio out",GPIOOutNode);

View File

@ -48,7 +48,7 @@ function BlinkStick(n) {
} }
} }
else { else {
node.warn("No BlinkStick found"); //node.warn("No BlinkStick found");
node.led = blinkstick.findFirst(); node.led = blinkstick.findFirst();
} }
} }

View File

@ -47,7 +47,7 @@
outputs:1, outputs:1,
icon: "bridge.png", icon: "bridge.png",
label: function() { label: function() {
return this.name||this.topic; return this.name||this.topic||"mqtt";
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
@ -90,7 +90,7 @@
icon: "bridge.png", icon: "bridge.png",
align: "right", align: "right",
label: function() { label: function() {
return this.name||this.topic; return this.name||this.topic||"mqtt";
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";

View File

@ -40,7 +40,6 @@ function HTTPIn(n) {
RED.nodes.registerType("http in",HTTPIn); RED.nodes.registerType("http in",HTTPIn);
HTTPIn.prototype.close = function() { HTTPIn.prototype.close = function() {
console.log(RED.app.routes[this.method]);
var routes = RED.app.routes[this.method]; var routes = RED.app.routes[this.method];
for (var i in routes) { for (var i in routes) {
if (routes[i].path == this.url) { if (routes[i].path == this.url) {
@ -48,6 +47,5 @@ HTTPIn.prototype.close = function() {
break; break;
} }
} }
console.log(RED.app.routes[this.method]);
} }

View File

@ -46,15 +46,13 @@ function SerialOutNode(n) {
return; return;
} }
node.port.on("ready",function() { node.on("input",function(msg) {
node.on("input",function(msg) {
//console.log("{",msg,"}"); //console.log("{",msg,"}");
node.port.write(msg.payload,function(err,res) { node.port.write(msg.payload,function(err,res) {
if (err) { if (err) {
node.error(err); node.error(err);
} }
}); });
});
}); });
} else { } else {
this.error("missing serial config"); this.error("missing serial config");
@ -123,10 +121,18 @@ var serialPool = function() {
} }
newline = newline.replace("\\n","\n").replace("\\r","\r"); newline = newline.replace("\\n","\n").replace("\\r","\r");
var setupSerial = function() { var setupSerial = function() {
obj.serial = new serialp.SerialPort(port,{ if (newline == "") {
baudrate: baud, obj.serial = new serialp.SerialPort(port,{
parser: serialp.parsers.readline(newline) baudrate: baud,
}); parser: serialp.parsers.raw
});
}
else {
obj.serial = new serialp.SerialPort(port,{
baudrate: baud,
parser: serialp.parsers.readline(newline)
});
}
obj.serial.on('error', function(err) { obj.serial.on('error', function(err) {
util.log("[serial] serial port "+port+" error "+err); util.log("[serial] serial port "+port+" error "+err);
obj.tout = setTimeout(function() { obj.tout = setTimeout(function() {
@ -147,7 +153,15 @@ var serialPool = function() {
obj._emitter.emit('ready'); obj._emitter.emit('ready');
}); });
obj.serial.on('data',function(d) { obj.serial.on('data',function(d) {
if (typeof d !== "string") {
d = d.toString();
for (i=0; i<d.length; i++) {
obj._emitter.emit('data',d.charAt(i));
}
}
else {
obj._emitter.emit('data',d); obj._emitter.emit('data',d);
}
}); });
} }
setupSerial(); setupSerial();

View File

@ -50,10 +50,10 @@
outputs:1, outputs:1,
icon: "white-globe.png", icon: "white-globe.png",
label: function() { label: function() {
return this.name||this.baseurl||"http(s) get"; return this.name||this.baseurl;
}, },
labelStyle: function() { labelStyle: function() {
return (this.name||!this.baseurl)?"node_label_italic":""; return this.name?"node_label_italic":"";
} }
}); });
</script> </script>

View File

@ -25,6 +25,10 @@
<label for="node-config-input-db"><i class="icon-briefcase"></i> Database</label> <label for="node-config-input-db"><i class="icon-briefcase"></i> Database</label>
<input type="text" id="node-config-input-db" placeholder="test"> <input type="text" id="node-config-input-db" placeholder="test">
</div> </div>
<div class="form-row">
<label for="node-config-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Name">
</div>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -35,9 +39,10 @@
hostname: { value:"127.0.0.1",required:true}, hostname: { value:"127.0.0.1",required:true},
port: { value: 27017,required:true}, port: { value: 27017,required:true},
db: { value:"",required:true}, db: { value:"",required:true},
name: { value:"" }
}, },
label: function() { label: function() {
return this.hostname+":"+this.port+"//"+this.db; return this.name||this.hostname+":"+this.port+"//"+this.db;
} }
}); });
</script> </script>
@ -52,7 +57,14 @@
<label for="node-input-collection"><i class="icon-briefcase"></i> Collection</label> <label for="node-input-collection"><i class="icon-briefcase"></i> Collection</label>
<input type="text" id="node-input-collection" placeholder="collection"> <input type="text" id="node-input-collection" placeholder="collection">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-operation"><i class="icon-wrench"></i> Operation</label>
<select type="text" id="node-input-operation" style="display: inline-block; vertical-align: top;">
<option value=store>Store</option>
<option value=delete>Delete</option>
</select>
</div>
<div class="form-row node-input-payonly">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-payonly" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-payonly" placeholder="Only" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-payonly" style="width: 70%;">Only store msg.payload object ?</label> <label for="node-input-payonly" style="width: 70%;">Only store msg.payload object ?</label>
@ -61,6 +73,13 @@
<label for="node-input-name"><i class="icon-tag"></i> Name</label> <label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
</div> </div>
<script>
$("#node-input-operation").change(function() {
var id = $("#node-input-operation option:selected").val();
if (id == "delete") $(".node-input-payonly").hide();
else $(".node-input-payonly").show();
});
</script>
</script> </script>
<script type="text/x-red" data-help-name="mongodb out"> <script type="text/x-red" data-help-name="mongodb out">
@ -69,6 +88,8 @@
<p>If this is NOT the desired behaviour - ie you want repeated entries to overwrite, then you must set the <b>msg._id</b> property to be a constant by the use of a previous function node.</p> <p>If this is NOT the desired behaviour - ie you want repeated entries to overwrite, then you must set the <b>msg._id</b> property to be a constant by the use of a previous function node.</p>
<p>This could be a unique constant or you could create one based on some other msg property.</p> <p>This could be a unique constant or you could create one based on some other msg property.</p>
<p>Currently we do not limit or cap the collection size at all... this may well change.</p> <p>Currently we do not limit or cap the collection size at all... this may well change.</p>
<p>You can also choose to <b>remove</b> items. To do so the <b>msg.payload</b> <i>MUST</i> contain an object that will select the items(s) to remove.
A blank object will delete <i>all of the objects</i> in the collection. You have been warned...</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -79,7 +100,8 @@
mongodb: { type:"mongodb",required:true}, mongodb: { type:"mongodb",required:true},
name: {value:""}, name: {value:""},
collection: {value:"",required:true}, collection: {value:"",required:true},
payonly: {value:false} payonly: {value:false},
operation: {value:"store"}
}, },
inputs:1, inputs:1,
outputs:0, outputs:0,
@ -87,7 +109,7 @@
align: "right", align: "right",
label: function() { label: function() {
var mongoNode = RED.nodes.node(this.mongodb); var mongoNode = RED.nodes.node(this.mongodb);
return this.name||this.collection||(mongoNode?mongoNode.label():"mongodb"); return this.name||(mongoNode?mongoNode.label()+"//"+this.collection:"mongodb");
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
@ -114,7 +136,7 @@
<script type="text/x-red" data-help-name="mongodb in"> <script type="text/x-red" data-help-name="mongodb in">
<p>Queries a MongoDB collection by using the <b>msg.payload</b> to be a MongoDB query statement as per the .find() function.</p> <p>Queries a MongoDB collection by using the <b>msg.payload</b> to be a MongoDB query statement as per the .find() function.</p>
<p>You may also (via a function) set a <b>msg.projection</b> object to constrain the returned fields, a <b>msg.sort</b> object and a <b>msg.limit</b> object.</p> <p>You may also (via a function) set a <b>msg.projection</b> object to constrain the returned fields, a <b>msg.sort</b> object and a <b>msg.limit</b> object.</p>
<p>All are optional - see the <a href="http://docs.mongodb.org/manual/reference/method/db.collection.find/" target="new"><i>MongoDB find docs</i></a> for examples. <p>All are optional - see the <a href="http://docs.mongodb.org/manual/reference/method/db.collection.find/" target="new"><i>MongoDB find docs</i></a> for examples.</p>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -124,7 +146,7 @@
defaults: { defaults: {
mongodb: { type:"mongodb",required:true}, mongodb: { type:"mongodb",required:true},
name: {value:""}, name: {value:""},
collection: {value:"",required:true}, collection: {value:"",required:true}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,

View File

@ -22,6 +22,7 @@ function MongoNode(n) {
this.hostname = n.hostname; this.hostname = n.hostname;
this.port = n.port; this.port = n.port;
this.db = n.db; this.db = n.db;
this.name = n.name;
} }
RED.nodes.registerType("mongodb",MongoNode); RED.nodes.registerType("mongodb",MongoNode);
@ -31,6 +32,7 @@ function MongoOutNode(n) {
this.collection = n.collection; this.collection = n.collection;
this.mongodb = n.mongodb; this.mongodb = n.mongodb;
this.payonly = n.payonly || false; this.payonly = n.payonly || false;
this.operation = n.operation;
this.mongoConfig = RED.nodes.getNode(this.mongodb); this.mongoConfig = RED.nodes.getNode(this.mongodb);
if (this.mongoConfig) { if (this.mongoConfig) {
@ -43,9 +45,15 @@ function MongoOutNode(n) {
if (err) { node.error(err); } if (err) { node.error(err); }
else { else {
node.on("input",function(msg) { node.on("input",function(msg) {
if (node.operation == "store") {
delete msg._topic; delete msg._topic;
if (node.payonly) coll.save(msg.payload,function(err,item){if (err){node.error(err);}}); if (node.payonly) coll.save(msg.payload,function(err,item){ if (err){node.error(err);} });
else coll.save(msg,function(err,item){if (err){node.error(err);}}); else coll.save(msg,function(err,item){if (err){node.error(err);}});
}
if (node.operation == "delete") {
console.log(msg.payload);
coll.remove(msg.payload, {w:1}, function(err, items){ if (err) node.error(err); });
}
}); });
} }
}); });

View File

@ -1,114 +0,0 @@
<!--
Copyright 2013 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.
-->
<script type="text/x-red" data-template-name="leveldbase">
<div class="form-row">
<label for="node-config-input-db"><i class="icon-briefcase"></i> Database</label>
<input type="text" id="node-config-input-db" placeholder="database path/name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('leveldbase',{
category: 'config',
defaults: {
db: {value:"",required:true}
},
label: function() {
return this.db;
}
});
</script>
<script type="text/x-red" data-template-name="leveldb in">
<div class="form-row node-input-level">
<label for="node-input-level"><i class="icon-briefcase"></i> Database</label>
<input type="text" id="node-input-level">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="leveldb in">
<p>Uses <a href="https://code.google.com/p/leveldb/" target="_new"><i>LevelDB</i></a> for a simple key value pair database.</p>
<p>Use this node to <b>get</b>, or retrieve the data already saved in the database.</p>
<p><b>msg.topic</b> must hold the <i>key</i> for the database, and the result is returned in <b>msg.payload</b>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('leveldb in',{
category: 'storage-input',
color:"#dbb84d",
defaults: {
level: {type:"leveldbase",required:true},
name: {value:""}
},
inputs:1,
outputs:1,
icon: "leveldb.png",
label: function() {
var levelNode = RED.nodes.node(this.level);
return this.name||(levelNode?levelNode.label():"leveldb");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>
<script type="text/x-red" data-template-name="leveldb out">
<div class="form-row node-input-level">
<label for="node-input-level"><i class="icon-briefcase"></i> Database</label>
<input type="text" id="node-input-level">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="leveldb out">
<p>Uses <a href="https://code.google.com/p/leveldb/" target="_new"><i>LevelDB</i></a> for a simple key value pair database.</p>
<p>Use this node to <b>put</b> (save) the <b>msg.payload</b> to the named database file, using <b>msg.topic</b> as the key.</p>
<p>To <b>delete</b> information do a <b>put</b> to the required <b>msg.topic</b> (key) with a <b>msg.payload</b> of <b><i>null</i></b>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('leveldb out',{
category: 'storage-output',
color:"#dbb84d",
defaults: {
level: {type:"leveldbase",required:true},
op: {value:"put",required:true},
name: {value:""}
},
inputs:1,
outputs:0,
icon: "leveldb.png",
align: "right",
label: function() {
var levelNode = RED.nodes.node(this.level);
return this.name||(levelNode?levelNode.label():"leveldb");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -1,90 +0,0 @@
/**
* Copyright 2013 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 RED = require("../../red/red");
var lvldb = require('leveldb');
function LevelNode(n) {
RED.nodes.createNode(this,n);
this.dbname = n.db;
lvldb.open(this.dbname, { create_if_missing: true }, onOpen);
var node = this;
function onOpen(err, db) {
if (err) node.error(err);
node.db = db;
}
}
RED.nodes.registerType("leveldbase",LevelNode);
function LevelDBNodeIn(n) {
RED.nodes.createNode(this,n);
this.level = n.level;
this.op = n.op;
this.levelConfig = RED.nodes.getNode(this.level);
if (this.levelConfig) {
var node = this;
node.on("input", function(msg) {
if (typeof msg.topic === 'string') {
node.levelConfig.db.get(msg.topic, function(err, value) {
if (err) node.error(err);
msg.payload = JSON.parse(value);
delete msg.cmd;
node.send(msg);
});
}
else {
if (typeof msg.topic !== 'string') node.error("msg.topic (the key is not defined");
}
});
}
else {
this.error("LevelDB database name not configured");
}
}
RED.nodes.registerType("leveldb in",LevelDBNodeIn);
function LevelDBNodeOut(n) {
RED.nodes.createNode(this,n);
this.level = n.level;
this.levelConfig = RED.nodes.getNode(this.level);
if (this.levelConfig) {
var node = this;
node.on("input", function(msg) {
if (typeof msg.topic === 'string') {
console.log(msg);
if (msg.payload === null) {
node.levelConfig.db.del(msg.topic);
}
else {
node.levelConfig.db.put(msg.topic, JSON.stringify(msg.payload), function(err) {
if (err) node.error(err);
});
}
}
else {
if (typeof msg.topic !== 'string') node.error("msg.topic (the key is not defined");
}
});
}
else {
this.error("LevelDB database name not configured");
}
}
RED.nodes.registerType("leveldb out",LevelDBNodeOut);

View File

@ -1,12 +1,13 @@
{ {
"name": "node-red", "name": "node-red",
"version": "0.1.0", "version": "0.1.0",
"description" : "A visual tool for wiring the Internet of Things", "description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"scripts": { "scripts": {
"start": "node red.js" "start": "node red.js"
}, },
"main": "lib/server.js", "main": "red/red.js",
"author": "Nicholas O'Leary", "author": "Nick O'Leary",
"contributors": [ {"name": "Dave Conway-Jones"} ], "contributors": [ {"name": "Dave Conway-Jones"} ],
"keywords": ["editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm"], "keywords": ["editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm"],
"license": "Apache", "license": "Apache",
@ -17,5 +18,6 @@
"mustache": "*", "mustache": "*",
"cron":"*" "cron":"*"
}, },
"engines": { "node": ">=0.8" } "engines": { "node": ">=0.8" },
"repository": {"type":"git","url":"https://github.com/node-red/node-red.git"}
} }

BIN
public/icons/db.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

View File

@ -857,7 +857,11 @@ RED.view = function() {
var root_node = new_ms[0].n; var root_node = new_ms[0].n;
var dx = root_node.x; var dx = root_node.x;
var dy = root_node.y; var dy = root_node.y;
if (mouse_position == null) {
mouse_position = [0,0];
}
for (var i in new_ms) { for (var i in new_ms) {
new_ms[i].n.selected = true; new_ms[i].n.selected = true;
new_ms[i].n.x -= dx - mouse_position[0]; new_ms[i].n.x -= dx - mouse_position[0];

10
red.js
View File

@ -19,20 +19,18 @@ var util = require("util");
var express = require("express"); var express = require("express");
var crypto = require("crypto"); var crypto = require("crypto");
var settings = require("./settings"); var settings = require("./settings");
var RED = require("./red/red.js");
var server; var server;
var app = express(); var app = express();
var redApp = null;
if (settings.https) { if (settings.https) {
server = https.createServer(settings.https,function(req,res){app(req,res);}); server = https.createServer(settings.https,function(req,res){app(req,res);});
} else { } else {
server = http.createServer(function(req,res){app(req,res);}); server = http.createServer(function(req,res){app(req,res);});
} }
redApp = require('./red/server.js').init(server,settings);
settings.httpRoot = settings.httpRoot||"/"; settings.httpRoot = settings.httpRoot||"/";
if (settings.httpRoot[0] != "/") { if (settings.httpRoot[0] != "/") {
@ -51,9 +49,11 @@ if (settings.httpAuth) {
); );
} }
app.use(settings.httpRoot,redApp); var red = RED.init(server,settings);
app.use(settings.httpRoot,red);
server.listen(settings.uiPort); server.listen(settings.uiPort);
RED.start();
util.log('[red] Server now running at http'+(settings.https?'s':'')+'://127.0.0.1:'+settings.uiPort+settings.httpRoot); util.log('[red] Server now running at http'+(settings.https?'s':'')+'://127.0.0.1:'+settings.uiPort+settings.httpRoot);

View File

@ -16,78 +16,80 @@
var fs = require("fs"); var fs = require("fs");
var fspath = require("path"); var fspath = require("path");
var redUI = require("./server"); var redApp = null;
// -------- Flow Library -------- function init() {
redUI.app.post(new RegExp("/library/flows\/(.*)"), function(req,res) { redApp = require("./server").app;
var fullBody = ''; // -------- Flow Library --------
req.on('data', function(chunk) { redApp.post(new RegExp("/library/flows\/(.*)"), function(req,res) {
fullBody += chunk.toString(); var fullBody = '';
}); req.on('data', function(chunk) {
req.on('end', function() { fullBody += chunk.toString();
var fn = "lib/flows/"+req.params[0]+".json"; });
var parts = fn.split("/"); req.on('end', function() {
for (var i = 3;i<parts.length;i+=1) { var fn = "lib/flows/"+req.params[0]+".json";
var dirname = parts.slice(0,i).join("/"); var parts = fn.split("/");
if (!fs.existsSync(dirname)) { for (var i = 3;i<parts.length;i+=1) {
fs.mkdirSync(dirname); var dirname = parts.slice(0,i).join("/");
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname);
}
} }
fs.writeFile(fn,fullBody,function(err) {
res.writeHead(204, {'Content-Type': 'text/plain'});
res.end();
});
});
});
function listFiles(dir) {
var dirs = {};
var files = [];
var dirCount = 0;
fs.readdirSync(dir).sort().filter(function(fn) {
var stats = fs.lstatSync(dir+"/"+fn);
if (stats.isDirectory()) {
dirCount += 1;
dirs[fn] = listFiles(dir+"/"+fn);
} else {
files.push(fn.split(".")[0]);
} }
fs.writeFile(fn,fullBody,function(err) { });
res.writeHead(204, {'Content-Type': 'text/plain'}); var result = {};
if (dirCount > 0) { result.d = dirs; }
if (files.length > 0) { result.f = files; }
return result;
}
redApp.get("/library/flows",function(req,res) {
var flows = {};
if (fs.existsSync("lib/flows")) {
flows = listFiles("lib/flows");
} else {
fs.mkdirSync("lib/flows");
}
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(JSON.stringify(flows));
res.end();
});
redApp.get(new RegExp("/library/flows\/(.*)"), function(req,res) {
var fn = "lib/flows/"+req.params[0]+".json";
if (fs.existsSync(fn)) {
fs.readFile(fn,function(err,data) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(data);
res.end(); res.end();
}); });
});
});
function listFiles(dir) {
var dirs = {};
var files = [];
var dirCount = 0;
fs.readdirSync(dir).sort().filter(function(fn) {
var stats = fs.lstatSync(dir+"/"+fn);
if (stats.isDirectory()) {
dirCount += 1;
dirs[fn] = listFiles(dir+"/"+fn);
} else { } else {
files.push(fn.split(".")[0]); res.send(404);
} }
}); });
var result = {};
if (dirCount > 0) { result.d = dirs; } // ------------------------------
if (files.length > 0) { result.f = files; } }
return result;
}
redUI.app.get("/library/flows",function(req,res) {
var flows = {};
if (fs.existsSync("lib/flows")) {
flows = listFiles("lib/flows");
} else {
fs.mkdirSync("lib/flows");
}
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(JSON.stringify(flows));
res.end();
});
redUI.app.get(new RegExp("/library/flows\/(.*)"), function(req,res) {
var fn = "lib/flows/"+req.params[0]+".json";
if (fs.existsSync(fn)) {
fs.readFile(fn,function(err,data) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(data);
res.end();
});
} else {
res.send(404);
}
});
// ------------------------------
function createLibrary(type) { function createLibrary(type) {
@ -96,11 +98,11 @@ function createLibrary(type) {
var root = fspath.join("lib",type)+"/"; var root = fspath.join("lib",type)+"/";
fs.exists(root,function(exists) { fs.exists(root,function(exists) {
if (!exists) { if (!exists) {
fs.mkdir(root); fs.mkdir(root);
} }
}); });
redUI.app.get(new RegExp("/library/"+type+"($|\/(.*))"),function(req,res) { redApp.get(new RegExp("/library/"+type+"($|\/(.*))"),function(req,res) {
var path = req.params[1]||""; var path = req.params[1]||"";
var rootPath = fspath.join(root,path); var rootPath = fspath.join(root,path);
@ -141,7 +143,7 @@ function createLibrary(type) {
}); });
}); });
redUI.app.post(new RegExp("/library/"+type+"\/(.*)"),function(req,res) { redApp.post(new RegExp("/library/"+type+"\/(.*)"),function(req,res) {
var path = req.params[0]; var path = req.params[0];
var fullBody = ''; var fullBody = '';
req.on('data', function(chunk) { req.on('data', function(chunk) {
@ -246,4 +248,5 @@ function getFileBody(root,path,res) {
res.end(); res.end();
} }
module.exports.init = init;
module.exports.register = createLibrary; module.exports.register = createLibrary;

View File

@ -66,7 +66,11 @@ var registry = (function() {
events.emit("nodes-stopped"); events.emit("nodes-stopped");
nodes = {}; nodes = {};
}, },
each: function(cb) {
for (var n in nodes) {
cb(nodes[n]);
}
},
addLogHandler: function(handler) { addLogHandler: function(handler) {
logHandlers.push(handler); logHandlers.push(handler);
} }
@ -132,6 +136,7 @@ util.inherits(Node,EventEmitter);
Node.prototype.close = function() { Node.prototype.close = function() {
// called when a node is removed // called when a node is removed
this.emit("close");
} }
@ -237,7 +242,7 @@ module.exports.load = function() {
if (stats.isFile()) { if (stats.isFile()) {
if (/\.js$/.test(fn)) { if (/\.js$/.test(fn)) {
try { try {
require("../"+dir+"/"+fn); require(dir+"/"+fn);
} catch(err) { } catch(err) {
util.log("["+fn+"] "+err); util.log("["+fn+"] "+err);
//console.log(err.stack); //console.log(err.stack);
@ -251,8 +256,7 @@ module.exports.load = function() {
} }
}); });
} }
loadNodes(__dirname+"/../nodes");
loadNodes("nodes");
//events.emit("nodes-loaded"); //events.emit("nodes-loaded");
} }
@ -325,7 +329,7 @@ var parseConfig = function() {
util.log("[red] unknown type: "+activeConfig[i].type); util.log("[red] unknown type: "+activeConfig[i].type);
} }
} }
// Clean up any orphaned credentials // Clean up any orphaned credentials
var deletedCredentials = false; var deletedCredentials = false;
for (var c in credentials) { for (var c in credentials) {

View File

@ -18,18 +18,29 @@ var events = require("./events");
var server = require("./server"); var server = require("./server");
var nodes = require("./nodes"); var nodes = require("./nodes");
var library = require("./library"); var library = require("./library");
var settings = require("../settings"); var settings = null;
var events = require("events"); var events = require("events");
var RED = { var RED = {
init: function(httpServer,userSettings) {
settings = userSettings;
server.init(httpServer,settings);
library.init();
return server.app;
},
start: server.start,
nodes: nodes, nodes: nodes,
app: server.app,
server: server.server,
settings: settings,
library: library, library: library,
events: events events: events
}; };
RED.__defineGetter__("app", function() { return server.app });
RED.__defineGetter__("server", function() { return server.server });
RED.__defineGetter__("settings", function() { return settings });
module.exports = RED; module.exports = RED;

View File

@ -19,6 +19,8 @@ var util = require('util');
var createUI = require("./ui"); var createUI = require("./ui");
var redNodes = require("./nodes"); var redNodes = require("./nodes");
var host = require('os').hostname(); var host = require('os').hostname();
//TODO: relocated user dir
var rulesfile = process.argv[2] || 'flows_'+host+'.json';
var app = null; var app = null;
var server = null; var server = null;
@ -28,8 +30,12 @@ function createServer(_server,settings) {
app = createUI(settings); app = createUI(settings);
//TODO: relocated user dir //TODO: relocated user dir
var rulesfile = process.argv[2] || 'flows_'+host+'.json'; fs.exists("lib/",function(exists) {
if (!exists) {
fs.mkdir("lib");
}
});
app.get("/nodes",function(req,res) { app.get("/nodes",function(req,res) {
res.writeHead(200, {'Content-Type': 'text/plain'}); res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(redNodes.getNodeConfigs()); res.write(redNodes.getNodeConfigs());
@ -65,7 +71,8 @@ function createServer(_server,settings) {
}); });
}); });
}); });
}
function start() {
console.log("\nWelcome to Node-RED\n===================\n"); console.log("\nWelcome to Node-RED\n===================\n");
util.log("[red] Loading palette nodes"); util.log("[red] Loading palette nodes");
util.log("------------------------------------------"); util.log("------------------------------------------");
@ -78,20 +85,23 @@ function createServer(_server,settings) {
util.log(' npm install {the module name}'); util.log(' npm install {the module name}');
util.log('or any other errors are resolved'); util.log('or any other errors are resolved');
util.log("------------------------------------------"); util.log("------------------------------------------");
fs.exists(rulesfile, function (exists) { fs.exists(rulesfile, function (exists) {
if (exists) { if (exists) {
util.log("[red] Loading workspace flow : "+rulesfile); util.log("[red] Loading flows : "+rulesfile);
fs.readFile(rulesfile,'utf8',function(err,data) { fs.readFile(rulesfile,'utf8',function(err,data) {
redNodes.setConfig(JSON.parse(data)); redNodes.setConfig(JSON.parse(data));
}); });
} else {
util.log("[red] Flows file not found : "+rulesfile);
} }
}); });
return app;
} }
module.exports = { module.exports = {
init: createServer init: createServer,
start: start
} }
module.exports.__defineGetter__("app", function() { return app }); module.exports.__defineGetter__("app", function() { return app });