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:
commit
a4fb33ee73
@ -30,7 +30,7 @@ From the top-level directory, run:
|
||||
|
||||
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
|
||||
|
||||
|
27
README.md
27
README.md
@ -12,6 +12,10 @@ Check out [INSTALL](INSTALL.md) for full instructions on getting started.
|
||||
4. node red.js
|
||||
5. Open <http://localhost:1880>
|
||||
|
||||
## Documentation
|
||||
|
||||
More documentation can be found [here](http://nodered.org/docs).
|
||||
|
||||
## Browser Support
|
||||
|
||||
The Node-RED editor runs in the browser. We routinely develop and test using
|
||||
@ -31,8 +35,21 @@ been raised.
|
||||
### Creating new nodes
|
||||
|
||||
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
|
||||
there yet.
|
||||
its capabilities.
|
||||
|
||||
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
|
||||
|
||||
@ -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
|
||||
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
|
||||
|
||||
|
@ -15,61 +15,62 @@
|
||||
-->
|
||||
|
||||
<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">
|
||||
<label for="node-input-payload"><i class="icon-envelope"></i> Payload</label>
|
||||
<input type="text" id="node-input-payload" placeholder="Payload">
|
||||
</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">
|
||||
<label> </label>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-row">
|
||||
<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>
|
||||
<input type="hidden" id="node-input-repeat" placeholder="Payload">
|
||||
<input type="hidden" id="node-input-crontab" placeholder="Payload">
|
||||
</div>
|
||||
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
<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>
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
|
||||
at <input id="inject-time-time" value="12:00"></input><br/>
|
||||
on <select id="inject-time-time-days" class="inject-time-days"></select>
|
||||
</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>
|
||||
|
||||
|
||||
<div class="form-tips">Tip: Injects Date.now() if no payload set</div>
|
||||
<script>
|
||||
{
|
||||
|
||||
|
||||
$("#inject-time-type-select").change(function() {
|
||||
var id = $("#inject-time-type-select option:selected").val();
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-row-"+id).show();
|
||||
});
|
||||
|
||||
|
||||
var days = [
|
||||
{v:"*",t:"every day"},
|
||||
{v:"1-5",t:"Mondays to Fridays"},
|
||||
@ -87,7 +88,7 @@
|
||||
$(this).append($("<option></option>").val(days[d].v).text(days[d].t));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$(".inject-time-times").each(function() {
|
||||
for (var i=0;i<24;i++) {
|
||||
var l = (i<10?"0":"")+i+":00";
|
||||
@ -98,14 +99,14 @@
|
||||
min:1,
|
||||
max:60
|
||||
});
|
||||
|
||||
|
||||
$("#inject-time-interval-units").change(function() {
|
||||
var units = $("#inject-time-interval-units option:selected").val();
|
||||
$("#inject-time-interval-days").prop("disabled",(units == "s")?"disabled":false);
|
||||
$(".inject-time-count").spinner("option","max",(units == "h")?24:60);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
$.widget( "ui.injecttimespinner", $.ui.spinner, {
|
||||
options: {
|
||||
@ -131,16 +132,16 @@
|
||||
var d = new Date(value);
|
||||
var h = d.getHours();
|
||||
var m = d.getMinutes();
|
||||
|
||||
|
||||
return ((h<10)?"0":"")+h+":"+((m<10)?"0":"")+m;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$("#inject-time-time").injecttimespinner();
|
||||
};
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</script>
|
||||
<style>
|
||||
@ -169,7 +170,7 @@
|
||||
width: 30px !important;
|
||||
}
|
||||
.
|
||||
|
||||
|
||||
</style>
|
||||
<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>
|
||||
@ -194,7 +195,7 @@
|
||||
outputs:1,
|
||||
icon: "inject.png",
|
||||
label: function() {
|
||||
return this.name||this.topic||this.payload;
|
||||
return this.name||this.topic||this.payload||"inject";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@ -216,7 +217,7 @@
|
||||
$("#inject-time-time").val(time);
|
||||
$("#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);
|
||||
|
||||
|
||||
} else if (cronparts[0] == "0") {
|
||||
// interval - hours
|
||||
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
|
||||
var startparts = timeparts[0].split("-");
|
||||
start = startparts[0];
|
||||
|
||||
|
||||
var endparts = timeparts[1].split("-");
|
||||
if (endparts.length == 1) {
|
||||
end = Number(endparts[0])+1;
|
||||
@ -272,12 +273,12 @@
|
||||
} else {
|
||||
$("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
|
||||
}
|
||||
|
||||
|
||||
$(".inject-time-row").hide();
|
||||
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
|
||||
$("#inject-time-row-"+repeattype).show();
|
||||
|
||||
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
var repeat = "";
|
||||
@ -297,7 +298,7 @@
|
||||
} else if (units == "h") {
|
||||
crontab = "0 */"+count+" * * "+days;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type == "interval-time") {
|
||||
var count = $("#inject-time-interval-time-units").val();
|
||||
var startTime = Number($("#inject-time-interval-time-start option:selected").val());
|
||||
@ -338,10 +339,10 @@
|
||||
repeat = 0;
|
||||
crontab = parts[1]+" "+parts[0]+" * * "+days;
|
||||
}
|
||||
|
||||
|
||||
$("#node-input-repeat").val(repeat);
|
||||
$("#node-input-crontab").val(crontab);
|
||||
|
||||
|
||||
},
|
||||
button: {
|
||||
onclick: function() {
|
||||
|
@ -117,6 +117,9 @@
|
||||
|
||||
var errornotification = null;
|
||||
|
||||
var messageCount = 0;
|
||||
|
||||
|
||||
function debugConnect() {
|
||||
//console.log("debug ws connecting");
|
||||
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>':'')+
|
||||
'<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);
|
||||
}
|
||||
@ -176,6 +185,7 @@
|
||||
|
||||
$("#debug-tab-clear").click(function() {
|
||||
$(".debug-message").remove();
|
||||
messageCount = 0;
|
||||
RED.nodes.eachNode(function(node) {
|
||||
node.highlighted = false;
|
||||
node.dirty = true;
|
||||
|
@ -43,7 +43,7 @@
|
||||
outputs:0,
|
||||
icon: "file.png",
|
||||
label: function() {
|
||||
return this.name||"comment";
|
||||
return this.name||"";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@ -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) {
|
||||
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) {
|
||||
if (err) {
|
||||
util.log('[36-rpi-gpio.js] Error: "gpio load spi" command failed for some reason.');
|
||||
}
|
||||
|
||||
exec("gpio mode 1 in");
|
||||
exec("gpio mode 2 in");
|
||||
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 out",GPIOOutNode);
|
||||
|
||||
|
@ -48,7 +48,7 @@ function BlinkStick(n) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.warn("No BlinkStick found");
|
||||
//node.warn("No BlinkStick found");
|
||||
node.led = blinkstick.findFirst();
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
outputs:1,
|
||||
icon: "bridge.png",
|
||||
label: function() {
|
||||
return this.name||this.topic;
|
||||
return this.name||this.topic||"mqtt";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@ -90,7 +90,7 @@
|
||||
icon: "bridge.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||this.topic;
|
||||
return this.name||this.topic||"mqtt";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@ -40,7 +40,6 @@ function HTTPIn(n) {
|
||||
RED.nodes.registerType("http in",HTTPIn);
|
||||
|
||||
HTTPIn.prototype.close = function() {
|
||||
console.log(RED.app.routes[this.method]);
|
||||
var routes = RED.app.routes[this.method];
|
||||
for (var i in routes) {
|
||||
if (routes[i].path == this.url) {
|
||||
@ -48,6 +47,5 @@ HTTPIn.prototype.close = function() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log(RED.app.routes[this.method]);
|
||||
}
|
||||
|
||||
|
@ -46,15 +46,13 @@ function SerialOutNode(n) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.port.on("ready",function() {
|
||||
node.on("input",function(msg) {
|
||||
node.on("input",function(msg) {
|
||||
//console.log("{",msg,"}");
|
||||
node.port.write(msg.payload,function(err,res) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.error("missing serial config");
|
||||
@ -123,10 +121,18 @@ var serialPool = function() {
|
||||
}
|
||||
newline = newline.replace("\\n","\n").replace("\\r","\r");
|
||||
var setupSerial = function() {
|
||||
obj.serial = new serialp.SerialPort(port,{
|
||||
baudrate: baud,
|
||||
parser: serialp.parsers.readline(newline)
|
||||
});
|
||||
if (newline == "") {
|
||||
obj.serial = new serialp.SerialPort(port,{
|
||||
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) {
|
||||
util.log("[serial] serial port "+port+" error "+err);
|
||||
obj.tout = setTimeout(function() {
|
||||
@ -147,7 +153,15 @@ var serialPool = function() {
|
||||
obj._emitter.emit('ready');
|
||||
});
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
setupSerial();
|
||||
|
@ -50,10 +50,10 @@
|
||||
outputs:1,
|
||||
icon: "white-globe.png",
|
||||
label: function() {
|
||||
return this.name||this.baseurl||"http(s) get";
|
||||
return this.name||this.baseurl;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return (this.name||!this.baseurl)?"node_label_italic":"";
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -25,6 +25,10 @@
|
||||
<label for="node-config-input-db"><i class="icon-briefcase"></i> Database</label>
|
||||
<input type="text" id="node-config-input-db" placeholder="test">
|
||||
</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 type="text/javascript">
|
||||
@ -35,9 +39,10 @@
|
||||
hostname: { value:"127.0.0.1",required:true},
|
||||
port: { value: 27017,required:true},
|
||||
db: { value:"",required:true},
|
||||
name: { value:"" }
|
||||
},
|
||||
label: function() {
|
||||
return this.hostname+":"+this.port+"//"+this.db;
|
||||
return this.name||this.hostname+":"+this.port+"//"+this.db;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -52,7 +57,14 @@
|
||||
<label for="node-input-collection"><i class="icon-briefcase"></i> Collection</label>
|
||||
<input type="text" id="node-input-collection" placeholder="collection">
|
||||
</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> </label>
|
||||
<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>
|
||||
@ -61,6 +73,13 @@
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</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 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>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>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 type="text/javascript">
|
||||
@ -79,7 +100,8 @@
|
||||
mongodb: { type:"mongodb",required:true},
|
||||
name: {value:""},
|
||||
collection: {value:"",required:true},
|
||||
payonly: {value:false}
|
||||
payonly: {value:false},
|
||||
operation: {value:"store"}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
@ -87,7 +109,7 @@
|
||||
align: "right",
|
||||
label: function() {
|
||||
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() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@ -114,7 +136,7 @@
|
||||
<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>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 type="text/javascript">
|
||||
@ -124,7 +146,7 @@
|
||||
defaults: {
|
||||
mongodb: { type:"mongodb",required:true},
|
||||
name: {value:""},
|
||||
collection: {value:"",required:true},
|
||||
collection: {value:"",required:true}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
|
@ -22,6 +22,7 @@ function MongoNode(n) {
|
||||
this.hostname = n.hostname;
|
||||
this.port = n.port;
|
||||
this.db = n.db;
|
||||
this.name = n.name;
|
||||
}
|
||||
RED.nodes.registerType("mongodb",MongoNode);
|
||||
|
||||
@ -31,6 +32,7 @@ function MongoOutNode(n) {
|
||||
this.collection = n.collection;
|
||||
this.mongodb = n.mongodb;
|
||||
this.payonly = n.payonly || false;
|
||||
this.operation = n.operation;
|
||||
this.mongoConfig = RED.nodes.getNode(this.mongodb);
|
||||
|
||||
if (this.mongoConfig) {
|
||||
@ -43,9 +45,15 @@ function MongoOutNode(n) {
|
||||
if (err) { node.error(err); }
|
||||
else {
|
||||
node.on("input",function(msg) {
|
||||
if (node.operation == "store") {
|
||||
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);}});
|
||||
}
|
||||
if (node.operation == "delete") {
|
||||
console.log(msg.payload);
|
||||
coll.remove(msg.payload, {w:1}, function(err, items){ if (err) node.error(err); });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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>
|
@ -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);
|
10
package.json
10
package.json
@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"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": {
|
||||
"start": "node red.js"
|
||||
},
|
||||
"main": "lib/server.js",
|
||||
"author": "Nicholas O'Leary",
|
||||
"main": "red/red.js",
|
||||
"author": "Nick O'Leary",
|
||||
"contributors": [ {"name": "Dave Conway-Jones"} ],
|
||||
"keywords": ["editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm"],
|
||||
"license": "Apache",
|
||||
@ -17,5 +18,6 @@
|
||||
"mustache": "*",
|
||||
"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
BIN
public/icons/db.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 558 B |
@ -857,7 +857,11 @@ RED.view = function() {
|
||||
var root_node = new_ms[0].n;
|
||||
var dx = root_node.x;
|
||||
var dy = root_node.y;
|
||||
|
||||
|
||||
if (mouse_position == null) {
|
||||
mouse_position = [0,0];
|
||||
}
|
||||
|
||||
for (var i in new_ms) {
|
||||
new_ms[i].n.selected = true;
|
||||
new_ms[i].n.x -= dx - mouse_position[0];
|
||||
|
10
red.js
10
red.js
@ -19,20 +19,18 @@ var util = require("util");
|
||||
var express = require("express");
|
||||
var crypto = require("crypto");
|
||||
var settings = require("./settings");
|
||||
var RED = require("./red/red.js");
|
||||
|
||||
|
||||
var server;
|
||||
var app = express();
|
||||
|
||||
var redApp = null;
|
||||
|
||||
if (settings.https) {
|
||||
server = https.createServer(settings.https,function(req,res){app(req,res);});
|
||||
} else {
|
||||
server = http.createServer(function(req,res){app(req,res);});
|
||||
}
|
||||
|
||||
redApp = require('./red/server.js').init(server,settings);
|
||||
|
||||
settings.httpRoot = settings.httpRoot||"/";
|
||||
|
||||
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);
|
||||
RED.start();
|
||||
util.log('[red] Server now running at http'+(settings.https?'s':'')+'://127.0.0.1:'+settings.uiPort+settings.httpRoot);
|
||||
|
||||
|
141
red/library.js
141
red/library.js
@ -16,78 +16,80 @@
|
||||
|
||||
var fs = require("fs");
|
||||
var fspath = require("path");
|
||||
var redUI = require("./server");
|
||||
var redApp = null;
|
||||
|
||||
// -------- Flow Library --------
|
||||
redUI.app.post(new RegExp("/library/flows\/(.*)"), function(req,res) {
|
||||
var fullBody = '';
|
||||
req.on('data', function(chunk) {
|
||||
fullBody += chunk.toString();
|
||||
});
|
||||
req.on('end', function() {
|
||||
var fn = "lib/flows/"+req.params[0]+".json";
|
||||
var parts = fn.split("/");
|
||||
for (var i = 3;i<parts.length;i+=1) {
|
||||
var dirname = parts.slice(0,i).join("/");
|
||||
if (!fs.existsSync(dirname)) {
|
||||
fs.mkdirSync(dirname);
|
||||
function init() {
|
||||
redApp = require("./server").app;
|
||||
// -------- Flow Library --------
|
||||
redApp.post(new RegExp("/library/flows\/(.*)"), function(req,res) {
|
||||
var fullBody = '';
|
||||
req.on('data', function(chunk) {
|
||||
fullBody += chunk.toString();
|
||||
});
|
||||
req.on('end', function() {
|
||||
var fn = "lib/flows/"+req.params[0]+".json";
|
||||
var parts = fn.split("/");
|
||||
for (var i = 3;i<parts.length;i+=1) {
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
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]);
|
||||
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) {
|
||||
|
||||
@ -96,11 +98,11 @@ function createLibrary(type) {
|
||||
var root = fspath.join("lib",type)+"/";
|
||||
|
||||
fs.exists(root,function(exists) {
|
||||
if (!exists) {
|
||||
fs.mkdir(root);
|
||||
}
|
||||
if (!exists) {
|
||||
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 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 fullBody = '';
|
||||
req.on('data', function(chunk) {
|
||||
@ -246,4 +248,5 @@ function getFileBody(root,path,res) {
|
||||
res.end();
|
||||
}
|
||||
|
||||
module.exports.init = init;
|
||||
module.exports.register = createLibrary;
|
||||
|
14
red/nodes.js
14
red/nodes.js
@ -66,7 +66,11 @@ var registry = (function() {
|
||||
events.emit("nodes-stopped");
|
||||
nodes = {};
|
||||
},
|
||||
|
||||
each: function(cb) {
|
||||
for (var n in nodes) {
|
||||
cb(nodes[n]);
|
||||
}
|
||||
},
|
||||
addLogHandler: function(handler) {
|
||||
logHandlers.push(handler);
|
||||
}
|
||||
@ -132,6 +136,7 @@ util.inherits(Node,EventEmitter);
|
||||
|
||||
Node.prototype.close = function() {
|
||||
// called when a node is removed
|
||||
this.emit("close");
|
||||
}
|
||||
|
||||
|
||||
@ -237,7 +242,7 @@ module.exports.load = function() {
|
||||
if (stats.isFile()) {
|
||||
if (/\.js$/.test(fn)) {
|
||||
try {
|
||||
require("../"+dir+"/"+fn);
|
||||
require(dir+"/"+fn);
|
||||
} catch(err) {
|
||||
util.log("["+fn+"] "+err);
|
||||
//console.log(err.stack);
|
||||
@ -251,8 +256,7 @@ module.exports.load = function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadNodes("nodes");
|
||||
loadNodes(__dirname+"/../nodes");
|
||||
|
||||
//events.emit("nodes-loaded");
|
||||
}
|
||||
@ -325,7 +329,7 @@ var parseConfig = function() {
|
||||
util.log("[red] unknown type: "+activeConfig[i].type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Clean up any orphaned credentials
|
||||
var deletedCredentials = false;
|
||||
for (var c in credentials) {
|
||||
|
19
red/red.js
19
red/red.js
@ -18,18 +18,29 @@ var events = require("./events");
|
||||
var server = require("./server");
|
||||
var nodes = require("./nodes");
|
||||
var library = require("./library");
|
||||
var settings = require("../settings");
|
||||
var settings = null;
|
||||
|
||||
|
||||
var events = require("events");
|
||||
|
||||
var RED = {
|
||||
|
||||
init: function(httpServer,userSettings) {
|
||||
settings = userSettings;
|
||||
server.init(httpServer,settings);
|
||||
library.init();
|
||||
return server.app;
|
||||
},
|
||||
|
||||
start: server.start,
|
||||
|
||||
nodes: nodes,
|
||||
app: server.app,
|
||||
server: server.server,
|
||||
settings: settings,
|
||||
library: library,
|
||||
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;
|
||||
|
@ -19,6 +19,8 @@ var util = require('util');
|
||||
var createUI = require("./ui");
|
||||
var redNodes = require("./nodes");
|
||||
var host = require('os').hostname();
|
||||
//TODO: relocated user dir
|
||||
var rulesfile = process.argv[2] || 'flows_'+host+'.json';
|
||||
|
||||
var app = null;
|
||||
var server = null;
|
||||
@ -28,8 +30,12 @@ function createServer(_server,settings) {
|
||||
app = createUI(settings);
|
||||
|
||||
//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) {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.write(redNodes.getNodeConfigs());
|
||||
@ -65,7 +71,8 @@ function createServer(_server,settings) {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
function start() {
|
||||
console.log("\nWelcome to Node-RED\n===================\n");
|
||||
util.log("[red] Loading palette nodes");
|
||||
util.log("------------------------------------------");
|
||||
@ -78,20 +85,23 @@ function createServer(_server,settings) {
|
||||
util.log(' npm install {the module name}');
|
||||
util.log('or any other errors are resolved');
|
||||
util.log("------------------------------------------");
|
||||
|
||||
|
||||
|
||||
fs.exists(rulesfile, function (exists) {
|
||||
if (exists) {
|
||||
util.log("[red] Loading workspace flow : "+rulesfile);
|
||||
util.log("[red] Loading flows : "+rulesfile);
|
||||
fs.readFile(rulesfile,'utf8',function(err,data) {
|
||||
redNodes.setConfig(JSON.parse(data));
|
||||
});
|
||||
} else {
|
||||
util.log("[red] Flows file not found : "+rulesfile);
|
||||
}
|
||||
});
|
||||
return app;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: createServer
|
||||
init: createServer,
|
||||
start: start
|
||||
}
|
||||
|
||||
module.exports.__defineGetter__("app", function() { return app });
|
||||
|
Loading…
Reference in New Issue
Block a user