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>.
Online documentation is available at <http://node-red.github.io/docs>.
Online documentation is available at <http://nodered.org/docs>.
## 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
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

View File

@ -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>&nbsp;</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() {

View File

@ -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;

View File

@ -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":"";

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) {
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);

View File

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

View File

@ -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":"";

View File

@ -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]);
}

View File

@ -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();

View File

@ -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>

View File

@ -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>&nbsp;</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,

View File

@ -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); });
}
});
}
});

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",
"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

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 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
View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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 });