mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
120c8f2c28 | ||
|
|
fbfc5c8a2d | ||
|
|
31b018c80e | ||
|
|
255d708fb6 | ||
|
|
78d1da5fbc | ||
|
|
9c22a770ef | ||
|
|
b201828236 | ||
|
|
2a8a885271 | ||
|
|
7adefd6ee0 | ||
|
|
f967a5ecdc | ||
|
|
c8d6dc2531 | ||
|
|
216b5fba7a | ||
|
|
f4ec9a72d5 | ||
|
|
cf0c2825eb | ||
|
|
be46c419dc | ||
|
|
62c68d06fe | ||
|
|
4f4d8419bc | ||
|
|
16e17954b4 | ||
|
|
cc1d080a5a | ||
|
|
dd7f4f6752 | ||
|
|
9daeba02b5 | ||
|
|
8a96dbd121 | ||
|
|
2a57d0b6d0 | ||
|
|
8a5c1bade5 | ||
|
|
fcc6943f98 | ||
|
|
72a9de058d | ||
|
|
8748be28b7 | ||
|
|
20bdea7ae0 | ||
|
|
e19b8d35a9 | ||
|
|
81df74dfc8 | ||
|
|
153fa7478f | ||
|
|
500e9a4010 | ||
|
|
5352fc87ee | ||
|
|
f07fd64ffb | ||
|
|
36f299c031 | ||
|
|
78cf310c58 | ||
|
|
18a3d71024 | ||
|
|
26db1048f9 | ||
|
|
b61a250d58 | ||
|
|
1d10eba0cc | ||
|
|
35042132fa | ||
|
|
57c049b49f | ||
|
|
7a0ce0c957 | ||
|
|
eb4cadb0b5 | ||
|
|
ac0ca083c0 | ||
|
|
9afb4a9315 | ||
|
|
df065e94b7 | ||
|
|
a9789697e7 | ||
|
|
2d91be8814 | ||
|
|
5c58b0c2f4 | ||
|
|
f0139f9808 | ||
|
|
5610a3184e | ||
|
|
b202c73708 | ||
|
|
dd4cec84bf | ||
|
|
a1dac1e290 | ||
|
|
e199d6725e | ||
|
|
aef38b945d | ||
|
|
cd5eac2cbb | ||
|
|
8fea443e71 | ||
|
|
2a47951e46 | ||
|
|
5234fda266 | ||
|
|
e63067cd9f | ||
|
|
be61cf6a88 | ||
|
|
5efc89d514 | ||
|
|
9c104faff3 | ||
|
|
cf8fe16b09 | ||
|
|
71db193675 | ||
|
|
9952d9451e | ||
|
|
fb738ad9fa | ||
|
|
46f2f752b0 | ||
|
|
42730b8fce | ||
|
|
1c2be579d9 | ||
|
|
51e891ff88 | ||
|
|
731efe1c01 | ||
|
|
f77dd06e65 | ||
|
|
af20f3df64 | ||
|
|
4078212089 | ||
|
|
7bdb3181e2 | ||
|
|
933608aec1 | ||
|
|
1d7f06bbba | ||
|
|
f26cadab7f | ||
|
|
eacf41a4f6 | ||
|
|
ab3e64271b | ||
|
|
e26ea14104 | ||
|
|
3967e23828 | ||
|
|
1f8c6f87c9 | ||
|
|
f6203fe60a | ||
|
|
06bf710515 | ||
|
|
0f3cc3196c | ||
|
|
4403a00651 | ||
|
|
9c46feb22b | ||
|
|
10277aa956 | ||
|
|
ff093d98c6 | ||
|
|
acc0e0875b | ||
|
|
69f85bd688 | ||
|
|
910d983b82 | ||
|
|
128415bc9e | ||
|
|
082ce798d8 | ||
|
|
234abd82a2 | ||
|
|
3cbc1bbb1b | ||
|
|
0ed9f6cc4f | ||
|
|
10b092a9a7 | ||
|
|
444a897410 | ||
|
|
e013afb053 | ||
|
|
34364f5627 | ||
|
|
cef378d820 | ||
|
|
a27353c166 | ||
|
|
bbd197c71d | ||
|
|
fabf013714 | ||
|
|
81dcfecb4e | ||
|
|
971a62ebc9 | ||
|
|
04f2c92ba6 | ||
|
|
00d0f8cfc7 | ||
|
|
c5c404ea05 | ||
|
|
c80a44933c | ||
|
|
5599b999ec | ||
|
|
172cbdaa84 | ||
|
|
a3c4f12764 | ||
|
|
bf1cd457cd | ||
|
|
8af50a51ba | ||
|
|
ddf31e87b2 | ||
|
|
5adbc012f3 | ||
|
|
393fc349b9 | ||
|
|
dfed4963ed | ||
|
|
131adb6f4e | ||
|
|
a8b3cbb683 | ||
|
|
e97d5c7354 | ||
|
|
061c44f958 | ||
|
|
f5d8433341 | ||
|
|
f78a71e8ed | ||
|
|
4d48c72146 | ||
|
|
71ff828947 | ||
|
|
b6245bdef7 | ||
|
|
ce1cd1ab9c | ||
|
|
54b0debb3b | ||
|
|
1876b56022 | ||
|
|
d148a23ed6 | ||
|
|
049a5f1be6 | ||
|
|
f3880b7601 | ||
|
|
fbb45a8961 | ||
|
|
b8c460b825 | ||
|
|
63191bc641 | ||
|
|
9f012c261a | ||
|
|
dc7701ad70 | ||
|
|
e8666827e6 | ||
|
|
5e2c51a741 | ||
|
|
51421ce657 | ||
|
|
339e6039e1 | ||
|
|
43054906dc | ||
|
|
57dedcf816 | ||
|
|
4dc21c43fa |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,9 +1,11 @@
|
||||
node_modules
|
||||
credentials.json
|
||||
flows*.json
|
||||
flows.backup
|
||||
*.backup
|
||||
*_cred*
|
||||
nodes/node-red-nodes/
|
||||
.npm
|
||||
/coverage
|
||||
.config.json
|
||||
.sessions.json
|
||||
|
||||
|
||||
43
bin/node-red-pi
Executable file
43
bin/node-red-pi
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright 2015 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.
|
||||
#
|
||||
|
||||
# Separate out node/v8 options from node-red ones
|
||||
OPTIONS=""
|
||||
ARGS=""
|
||||
|
||||
for arg in "$@"
|
||||
do
|
||||
case $arg in
|
||||
--userDir|--settings|--help) ARGS="$ARGS $arg";;
|
||||
--*) OPTIONS="$OPTIONS $arg";;
|
||||
*) ARGS="$ARGS $arg";;
|
||||
esac
|
||||
done
|
||||
|
||||
# Find the real location of this script
|
||||
CURRENT_PATH=`pwd`
|
||||
SCRIPT_PATH="${BASH_SOURCE[0]}";
|
||||
while([ -h "${SCRIPT_PATH}" ]); do
|
||||
cd "`dirname "${SCRIPT_PATH}"`"
|
||||
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
|
||||
done
|
||||
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
|
||||
SCRIPT_PATH="`pwd`";
|
||||
cd $CURRENT_PATH
|
||||
|
||||
# Run Node-RED
|
||||
/usr/bin/env node $OPTIONS $SCRIPT_PATH/../red.js $ARGS
|
||||
@@ -23,10 +23,13 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
|
||||
this.on("input", function(msg) {
|
||||
sentiment(msg.payload, msg.overrides || null, function (err, result) {
|
||||
msg.sentiment = result;
|
||||
node.send(msg);
|
||||
});
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
sentiment(msg.payload, msg.overrides || null, function (err, result) {
|
||||
msg.sentiment = result;
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
else { node.send(msg); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("sentiment",SentimentNode);
|
||||
|
||||
@@ -32,21 +32,17 @@ module.exports = function(RED) {
|
||||
|
||||
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
|
||||
this.repeat = this.repeat * 1000;
|
||||
this.log("repeat = "+this.repeat);
|
||||
if (RED.settings.verbose) { this.log("repeat = "+this.repeat); }
|
||||
this.interval_id = setInterval( function() {
|
||||
node.emit("input",{});
|
||||
}, this.repeat );
|
||||
} else if (this.crontab) {
|
||||
if (cron) {
|
||||
this.log("crontab = "+this.crontab);
|
||||
this.cronjob = new cron.CronJob(this.crontab,
|
||||
function() {
|
||||
node.emit("input",{});
|
||||
},
|
||||
null,true);
|
||||
} else {
|
||||
this.error("'cron' module not found");
|
||||
}
|
||||
if (RED.settings.verbose) { this.log("crontab = "+this.crontab); }
|
||||
this.cronjob = new cron.CronJob(this.crontab,
|
||||
function() {
|
||||
node.emit("input",{});
|
||||
},
|
||||
null,true);
|
||||
}
|
||||
|
||||
if (this.once) {
|
||||
@@ -72,14 +68,14 @@ module.exports = function(RED) {
|
||||
InjectNode.prototype.close = function() {
|
||||
if (this.interval_id != null) {
|
||||
clearInterval(this.interval_id);
|
||||
this.log("inject: repeat stopped");
|
||||
if (RED.settings.verbose) { this.log("inject: repeat stopped"); }
|
||||
} else if (this.cronjob != null) {
|
||||
this.cronjob.stop();
|
||||
this.log("inject: cronjob stopped");
|
||||
if (RED.settings.verbose) { this.log("inject: cronjob stopped"); }
|
||||
delete this.cronjob;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
|
||||
var node = RED.nodes.getNode(req.params.id);
|
||||
if (node != null) {
|
||||
@@ -89,7 +85,6 @@ module.exports = function(RED) {
|
||||
} catch(err) {
|
||||
res.send(500);
|
||||
node.error("Inject failed:"+err);
|
||||
console.log(err.stack);
|
||||
}
|
||||
} else {
|
||||
res.send(404);
|
||||
|
||||
62
nodes/core/core/25-catch.html
Normal file
62
nodes/core/core/25-catch.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!--
|
||||
Copyright 2015 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="catch">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="name">
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/x-red" data-help-name="catch">
|
||||
<p>Catch errors thrown by nodes on the same tab.</p>
|
||||
<p>If a node throws a error whilst handling a message, the flow will typically
|
||||
halt. This node can be used to catch those errors and handle them with a
|
||||
dedicated flow.</p>
|
||||
<p>The node will catch errors thrown by any node on the same tab. If there
|
||||
are multiple catch nodes on a tab, they will all get triggered.</p>
|
||||
<p>If an error is thrown within a subflow, the error will get handled by any
|
||||
catch nodes within the subflow. If none exists, the error is propagated
|
||||
up to the tab the subflow instance is on.</p>
|
||||
<p>The message sent by this node will be the original message if the node that
|
||||
threw the error provided it. The message will have an <code>error</code>
|
||||
property with the following attributes:
|
||||
<ul>
|
||||
<li><code>message</code> : the error message</li>
|
||||
<li><code>source.id</code> : the id of the node that threw the error</li>
|
||||
<li><code>source.type</code> : the type of the node that threw the error</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>If the message already had a <code>error</code> property, it is copied to <code>_error</code>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('catch',{
|
||||
category: 'input',
|
||||
color:"#e49191",
|
||||
defaults: {
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "alert.png",
|
||||
label: function() {
|
||||
return this.name||"catch";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
29
nodes/core/core/25-catch.js
Normal file
29
nodes/core/core/25-catch.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright 2015 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.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
|
||||
function CatchNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.on("input",function(msg) {
|
||||
this.send(msg);
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("catch",CatchNode);
|
||||
}
|
||||
@@ -156,7 +156,9 @@
|
||||
msg:msg
|
||||
});
|
||||
}
|
||||
|
||||
function sanitize(m) {
|
||||
return m.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
}
|
||||
this.handleDebugMessage = function(t,o) {
|
||||
var msg = document.createElement("div");
|
||||
msg.onmouseover = function() {
|
||||
@@ -184,23 +186,34 @@
|
||||
}
|
||||
|
||||
};
|
||||
var name = (o.name?o.name:o.id).toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var topic = (o.topic||"").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var property = (o.property?o.property:'').replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var payload = (o.msg||"()").toString().replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
var typ = payload.substring(0,payload.indexOf(')')+1);
|
||||
payload = payload.substring(payload.indexOf(')')+1);
|
||||
//console.log(o);
|
||||
var name = sanitize(((o.name?o.name:o.id)||"").toString());
|
||||
var topic = sanitize((o.topic||"").toString());
|
||||
var property = sanitize(o.property?o.property:'');
|
||||
var payload = sanitize((o.msg||"").toString());
|
||||
var format = sanitize((o.format||"").toString());
|
||||
|
||||
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'');
|
||||
msg.innerHTML = '<span class="debug-message-date">'+
|
||||
getTimestamp()+'</span><span class="debug-message-name">['+name+']'+
|
||||
getTimestamp()+'</span>'+
|
||||
(name?'<span class="debug-message-name">['+name+']':'')+
|
||||
'</span>';
|
||||
// NOTE: relying on function error to have a "type" that all other msgs don't
|
||||
if (o.hasOwnProperty("type") && (o.type === "function")) {
|
||||
msg.className = 'debug-message debug-message-level-20';
|
||||
msg.innerHTML += '<span class="debug-message-topic">[function] : (error)</span>';
|
||||
var errorLvlType = 'error';
|
||||
var errorLvl = 20;
|
||||
if (o.hasOwnProperty("level") && o.level === 30) {
|
||||
errorLvl = 30;
|
||||
errorLvlType = 'warn'
|
||||
}
|
||||
msg.className = 'debug-message debug-message-level-' + errorLvl;
|
||||
msg.innerHTML += '<span class="debug-message-topic">[function] : (' + errorLvlType + ')</span>';
|
||||
} else {
|
||||
msg.innerHTML += '<span class="debug-message-topic">'+(o.topic?topic+' : ':'')+
|
||||
(o.property?'[msg.'+property+']':'[msg]')+" : "+typ+'</span>';
|
||||
msg.innerHTML += '<span class="debug-message-topic">'+
|
||||
(o.topic?topic+' : ':'')+
|
||||
(o.property?'[msg.'+property+']':'[msg]')+" : "+format+
|
||||
|
||||
'</span>';
|
||||
}
|
||||
msg.innerHTML += '<span class="debug-message-payload">'+ payload+ '</span>';
|
||||
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
|
||||
|
||||
@@ -25,14 +25,11 @@ module.exports = function(RED) {
|
||||
function DebugNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.name = n.name;
|
||||
this.complete = n.complete||"payload";
|
||||
this.complete = (n.complete||"payload").toString();
|
||||
|
||||
if (this.complete === "false") {
|
||||
this.complete = "payload";
|
||||
}
|
||||
if (this.complete === true) {
|
||||
this.complete = "true";
|
||||
}
|
||||
|
||||
this.console = n.console;
|
||||
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
|
||||
@@ -82,14 +79,18 @@ module.exports = function(RED) {
|
||||
|
||||
function sendDebug(msg) {
|
||||
if (msg.msg instanceof Error) {
|
||||
msg.format = "error";
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (msg.msg instanceof Buffer) {
|
||||
msg.msg = "(Buffer) "+msg.msg.toString('hex');
|
||||
msg.format = "buffer";
|
||||
msg.msg = msg.msg.toString('hex');
|
||||
} else if (typeof msg.msg === 'object') {
|
||||
var seen = [];
|
||||
var ty = "(Object) ";
|
||||
if (util.isArray(msg.msg)) { ty = "(Array) "; }
|
||||
msg.msg = ty + JSON.stringify(msg.msg, function(key, value) {
|
||||
msg.format = "object";
|
||||
if (util.isArray(msg.msg)) {
|
||||
msg.format = "array";
|
||||
}
|
||||
msg.msg = JSON.stringify(msg.msg, function(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.indexOf(value) !== -1) { return "[circular]"; }
|
||||
seen.push(value);
|
||||
@@ -98,14 +99,21 @@ module.exports = function(RED) {
|
||||
}," ");
|
||||
seen = null;
|
||||
} else if (typeof msg.msg === "boolean") {
|
||||
msg.msg = "(boolean) "+msg.msg.toString();
|
||||
msg.format = "boolean";
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (typeof msg.msg === "number") {
|
||||
msg.msg = "(number) "+msg.msg.toString();
|
||||
msg.format = "number";
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (msg.msg === 0) {
|
||||
msg.format = "number";
|
||||
msg.msg = "0";
|
||||
} else if (msg.msg === null || typeof msg.msg === "undefined") {
|
||||
msg.format = (msg.msg === null)?"null":"undefined";
|
||||
msg.msg = "(undefined)";
|
||||
} else { msg.msg = "(string) "+msg.msg; }
|
||||
} else {
|
||||
msg.format = "string";
|
||||
msg.msg = msg.msg;
|
||||
}
|
||||
|
||||
if (msg.msg.length > debuglength) {
|
||||
msg.msg = msg.msg.substr(0,debuglength) +" ....";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
Copyright 2013,2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,8 +20,13 @@
|
||||
<input type="text" id="node-input-command" placeholder="command">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-append"><i class="fa fa-list"></i> Append</label>
|
||||
<input type="text" id="node-input-append" placeholder="extra input">
|
||||
<label><i class="fa fa-plus"></i> Append</label>
|
||||
<input type="checkbox" id="node-input-addpay" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
msg.payload
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-append"> </label>
|
||||
<input type="text" id="node-input-append" placeholder="extra input parameters">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
@@ -32,7 +37,7 @@
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated.</div>
|
||||
<div class="form-tips" id="spawnTip">Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="exec">
|
||||
@@ -40,7 +45,7 @@
|
||||
<p>Provides 3 outputs... stdout, stderr, and return code.</p>
|
||||
<p>By default uses exec() which calls the command, blocks while waiting for completion, and then returns the complete result in one go, along with any errors.</p>
|
||||
<p>Optionally can use spawn() instead, which returns output from stdout and stderr as the command runs (ie one line at a time). On completion it then returns a return code (on the 3rd output).</p>
|
||||
<p>Spawn only expect one command word, with all extra parameters to be comma separated and passed as the append.</p>
|
||||
<p>Spawn only expects one command word, with all extra parameters to be comma separated and passed as the append.</p>
|
||||
<p>The optional append gets added to the command after the <b>msg.payload</b> - so you can do things like pipe the result to another command.</p>
|
||||
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
|
||||
</script>
|
||||
@@ -51,6 +56,7 @@
|
||||
color:"darksalmon",
|
||||
defaults: {
|
||||
command: {value:"",required:true},
|
||||
addpay: {value:true},
|
||||
append: {value:""},
|
||||
useSpawn: {value:""},
|
||||
name: {value:""}
|
||||
@@ -64,6 +70,15 @@
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-useSpawn").on("change",function() {
|
||||
if ($("#node-input-useSpawn").is(':checked')) {
|
||||
$("#spawnTip").show();
|
||||
} else {
|
||||
$("#spawnTip").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,8 +22,9 @@ module.exports = function(RED) {
|
||||
|
||||
function ExecNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.cmd = n.command.trim();
|
||||
this.append = n.append.trim() || "";
|
||||
this.cmd = (n.command || "").trim();
|
||||
this.addpay = n.addpay;
|
||||
this.append = (n.append || "").trim();
|
||||
this.useSpawn = n.useSpawn;
|
||||
|
||||
var node = this;
|
||||
@@ -32,10 +33,10 @@ module.exports = function(RED) {
|
||||
if (this.useSpawn === true) {
|
||||
// make the extra args into an array
|
||||
// then prepend with the msg.payload
|
||||
if (typeof(msg.payload !== "string")) { msg.payload = msg.payload.toString(); }
|
||||
if (typeof(msg.payload !== "string")) { msg.payload = (msg.payload || "").toString(); }
|
||||
var arg = [];
|
||||
if (node.append.length > 0) { arg = node.append.split(","); }
|
||||
if (msg.payload.trim() !== "") { arg.unshift(msg.payload); }
|
||||
if ((node.addpay === true) && (msg.payload.trim() !== "")) { arg.unshift(msg.payload); }
|
||||
if (RED.settings.verbose) { node.log(node.cmd+" ["+arg+"]"); }
|
||||
if (node.cmd.indexOf(" ") == -1) {
|
||||
var ex = spawn(node.cmd,arg);
|
||||
@@ -58,13 +59,15 @@ module.exports = function(RED) {
|
||||
node.send([null,null,msg]);
|
||||
});
|
||||
ex.on('error', function (code) {
|
||||
node.warn(code);
|
||||
node.error(code,msg);
|
||||
});
|
||||
}
|
||||
else { node.error("Spawn command must be just the command - no spaces or extra parameters"); }
|
||||
}
|
||||
else {
|
||||
var cl = node.cmd+" "+msg.payload+" "+node.append;
|
||||
var cl = node.cmd;
|
||||
if ((node.addpay === true) && ((msg.payload || "").trim() !== "")) { cl += " "+msg.payload; }
|
||||
if (node.append.trim() !== "") { cl += " "+node.append; }
|
||||
if (RED.settings.verbose) { node.log(cl); }
|
||||
var child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
|
||||
msg.payload = new Buffer(stdout,"binary");
|
||||
@@ -83,6 +86,5 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("exec",ExecNode);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013, 2014 IBM Corp.
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,8 +20,9 @@
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<label for="node-input-func"><i class="fa fa-wrench"></i> Function</label>
|
||||
<input type="hidden" id="node-input-func" autofocus="autofocus">
|
||||
<label for="node-input-func"><i class="fa fa-wrench"></i> Function</label>
|
||||
<input type="hidden" id="node-input-func" autofocus="autofocus">
|
||||
<input type="hidden" id="node-input-valid">
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||
@@ -34,22 +35,35 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="function">
|
||||
<p>A function block where you can write code to do more interesting things.</p>
|
||||
<p>The message is passed in as a JavaScript object called <code>msg</code>.</p>
|
||||
<p>By convention it will have a <code>msg.payload</code> property containing
|
||||
the body of the message.</p>
|
||||
<p>The function should return the messages it wants to pass on to the next nodes
|
||||
in the flow. It can return:</p>
|
||||
<ul>
|
||||
<li>a single message object - passed to nodes connected to the first output</li>
|
||||
<li>an array of message objects - passed to nodes connected to the corresponding outputs</li>
|
||||
<p>A function block where you can write code to do more interesting things.</p>
|
||||
<p>The message is passed in as a JavaScript object called <code>msg</code>.</p>
|
||||
<p>By convention it will have a <code>msg.payload</code> property containing
|
||||
the body of the message.</p>
|
||||
<h4>Logging and Error Handling</h4>
|
||||
<p>To log any information, or report an error, the following functions are available:</p>
|
||||
<ul>
|
||||
<li><code>node.log("Log")</code></li>
|
||||
<li><code>node.warn("Warning")</code></li>
|
||||
<li><code>node.error("Error")</code></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>The Catch node can also be used to handle errors. To invoke a Catch node,
|
||||
pass <code>msg</code> as a second argument to <code>node.error</code>:</p>
|
||||
<pre>node.error("Error",msg)</pre>
|
||||
<h4>Sending messages</h4>
|
||||
<p>The function can either return the messages it wants to pass on to the next nodes
|
||||
in the flow, or can call <code>node.send(messages)</code>.</p>
|
||||
<p>It can return/send:</p>
|
||||
<ul>
|
||||
<li>a single message object - passed to nodes connected to the first output</li>
|
||||
<li>an array of message objects - passed to nodes connected to the corresponding outputs</li>
|
||||
</ul>
|
||||
<p>If any element of the array is itself an array of messages, multiple
|
||||
messages are sent to the corresponding output.</p>
|
||||
<p>If null is returned, either by itself or as an element of the array, no
|
||||
message is passed on.</p>
|
||||
<p>See the <a target="_new" href="http://nodered.org/docs/writing-functions.html">online documentation</a> for more help.</p>
|
||||
|
||||
messages are sent to the corresponding output.</p>
|
||||
<p>If null is returned, either by itself or as an element of the array, no
|
||||
message is passed on.</p>
|
||||
<p>See the <a target="_new" href="http://nodered.org/docs/writing-functions.html">online documentation</a> for more help.</p>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -59,7 +73,8 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1}
|
||||
outputs: {value:1},
|
||||
valid: {value:true,required:true}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
@@ -68,6 +83,7 @@
|
||||
return this.name;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
$( "#node-input-outputs" ).spinner({
|
||||
min:1
|
||||
});
|
||||
@@ -81,8 +97,8 @@
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
that.editor.resize();
|
||||
};
|
||||
|
||||
$( "#dialog" ).on("dialogresize", functionDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-function');
|
||||
@@ -96,25 +112,33 @@
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",functionDialogResize);
|
||||
});
|
||||
var that = this;
|
||||
require(["orion/editor/edit"], function(edit) {
|
||||
that.editor = edit({
|
||||
parent:document.getElementById('node-input-func-editor'),
|
||||
lang:"js",
|
||||
contents: $("#node-input-func").val()
|
||||
});
|
||||
RED.library.create({
|
||||
url:"functions", // where to get the data from
|
||||
type:"function", // the type of object the library is for
|
||||
editor:that.editor, // the field name the main text body goes to
|
||||
fields:['name','outputs']
|
||||
});
|
||||
$("#node-input-name").focus();
|
||||
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-func-editor',
|
||||
mode: 'ace/mode/javascript'
|
||||
});
|
||||
this.editor.setValue($("#node-input-func").val(),-1);
|
||||
|
||||
RED.library.create({
|
||||
url:"functions", // where to get the data from
|
||||
type:"function", // the type of object the library is for
|
||||
editor:this.editor, // the field name the main text body goes to
|
||||
mode:"ace/mode/javascript",
|
||||
fields:['name','outputs']
|
||||
});
|
||||
this.editor.focus();
|
||||
},
|
||||
oneditsave: function() {
|
||||
$("#node-input-func").val(this.editor.getText())
|
||||
var annot = this.editor.getSession().getAnnotations();
|
||||
this.valid = true;
|
||||
for (var k=0; k < annot.length; k++) {
|
||||
//console.log(annot[k].type,":",annot[k].text, "on line", annot[k].row);
|
||||
if (annot[k].type === "error") {
|
||||
$("#node-input-valid").val(null);
|
||||
delete this.valid;
|
||||
}
|
||||
}
|
||||
$("#node-input-func").val(this.editor.getValue());
|
||||
delete this.editor;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,19 +19,76 @@ module.exports = function(RED) {
|
||||
var util = require("util");
|
||||
var vm = require("vm");
|
||||
|
||||
|
||||
function sendResults(node,_msgid,msgs) {
|
||||
if (msgs == null) {
|
||||
return;
|
||||
} else if (!util.isArray(msgs)) {
|
||||
msgs = [msgs];
|
||||
}
|
||||
var msgCount = 0;
|
||||
for (var m=0;m<msgs.length;m++) {
|
||||
if (msgs[m]) {
|
||||
if (util.isArray(msgs[m])) {
|
||||
for (var n=0; n < msgs[m].length; n++) {
|
||||
msgs[m][n]._msgid = _msgid;
|
||||
msgCount++;
|
||||
}
|
||||
} else {
|
||||
msgs[m]._msgid = _msgid;
|
||||
msgCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msgCount>0) {
|
||||
node.send(msgs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function FunctionNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.name = n.name;
|
||||
this.func = n.func;
|
||||
var functionText = "var results = null; results = (function(msg){\n"+this.func+"\n})(msg);";
|
||||
var functionText = "var results = null;"+
|
||||
"results = (function(msg){ "+
|
||||
"var __msgid__ = msg._msgid;"+
|
||||
"var node = {"+
|
||||
"log:__node__.log,"+
|
||||
"error:__node__.error,"+
|
||||
"warn:__node__.warn,"+
|
||||
"on:__node__.on,"+
|
||||
"send:function(msgs){ __node__.send(__msgid__,msgs);}"+
|
||||
"};\n"+
|
||||
this.func+"\n"+
|
||||
"})(msg);";
|
||||
this.topic = n.topic;
|
||||
var sandbox = {
|
||||
console:console,
|
||||
util:util,
|
||||
Buffer:Buffer,
|
||||
__node__: {
|
||||
log: function() {
|
||||
node.log.apply(node, arguments);
|
||||
},
|
||||
error: function(){
|
||||
node.error.apply(node, arguments);
|
||||
},
|
||||
warn: function() {
|
||||
node.warn.apply(node, arguments);
|
||||
},
|
||||
send: function(id,msgs) {
|
||||
sendResults(node,id,msgs);
|
||||
},
|
||||
on: function() {
|
||||
node.on.apply(node,arguments);
|
||||
}
|
||||
},
|
||||
context: {
|
||||
global:RED.settings.functionGlobalContext || {}
|
||||
}
|
||||
},
|
||||
setTimeout: setTimeout
|
||||
};
|
||||
var context = vm.createContext(sandbox);
|
||||
try {
|
||||
@@ -41,26 +98,8 @@ module.exports = function(RED) {
|
||||
var start = process.hrtime();
|
||||
context.msg = msg;
|
||||
this.script.runInContext(context);
|
||||
var results = context.results;
|
||||
if (results == null) {
|
||||
results = [];
|
||||
} else if (results.length == null) {
|
||||
results = [results];
|
||||
}
|
||||
if (msg._topic) {
|
||||
for (var m in results) {
|
||||
if (results[m]) {
|
||||
if (util.isArray(results[m])) {
|
||||
for (var n=0; n < results[m].length; n++) {
|
||||
results[m][n]._topic = msg._topic;
|
||||
}
|
||||
} else {
|
||||
results[m]._topic = msg._topic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.send(results);
|
||||
sendResults(this,msg._msgid,context.results);
|
||||
|
||||
var duration = process.hrtime(start);
|
||||
var converted = Math.floor((duration[0]* 1e9 + duration[1])/10000)/100;
|
||||
this.metric("duration", msg, converted);
|
||||
@@ -78,7 +117,7 @@ module.exports = function(RED) {
|
||||
errorMessage += " (line "+line+", col "+cha+")";
|
||||
}
|
||||
}
|
||||
this.error(errorMessage);
|
||||
this.error(errorMessage, msg);
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013,2014 IBM Corp.
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -22,13 +22,20 @@
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label>
|
||||
<input type="hidden" id="node-input-template" autofocus="autofocus">
|
||||
<select id="node-input-format" style=" font-size: 0.8em; margin-bottom: 3px; width:110px; float:right;">
|
||||
<option value="handlebars">mustache</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="text">none</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-field"><i class="fa fa-edit"></i> Property</label>
|
||||
msg.<input type="text" id="node-input-field" placeholder="payload" style="width: 64%;">
|
||||
msg.<input type="text" id="node-input-field" placeholder="payload" style="width:170px;">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
@@ -54,7 +61,8 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
field: {value:"payload"},
|
||||
template: {value:"This is the payload: {{payload}}!"},
|
||||
format: {value:"handlebars"},
|
||||
template: {value:"This is the payload: {{payload}} !"},
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
@@ -63,7 +71,7 @@
|
||||
return this.name;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
|
||||
var that = this;
|
||||
function templateDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
@@ -73,8 +81,8 @@
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
that.editor.resize();
|
||||
};
|
||||
|
||||
$( "#dialog" ).on("dialogresize", templateDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-template');
|
||||
@@ -88,25 +96,29 @@
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",templateDialogResize);
|
||||
});
|
||||
|
||||
var that = this;
|
||||
require(["orion/editor/edit"], function(edit) {
|
||||
that.editor = edit({
|
||||
parent:document.getElementById('node-input-template-editor'),
|
||||
lang:"html",
|
||||
contents: $("#node-input-template").val()
|
||||
});
|
||||
RED.library.create({
|
||||
url:"templates", // where to get the data from
|
||||
type:"template", // the type of object the library is for
|
||||
editor:that.editor, // the field name the main text body goes to
|
||||
fields:['name','field']
|
||||
});
|
||||
$("#node-input-name").focus();
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-template-editor',
|
||||
mode: 'ace/mode/html'
|
||||
});
|
||||
this.editor.setValue($("#node-input-template").val(),-1);
|
||||
RED.library.create({
|
||||
url:"functions", // where to get the data from
|
||||
type:"function", // the type of object the library is for
|
||||
editor:that.editor, // the field name the main text body goes to
|
||||
fields:['name','outputs']
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
$("#node-input-format").change(function() {
|
||||
var mod = "ace/mode/"+$("#node-input-format").val();
|
||||
that.editor.getSession().setMode({
|
||||
path: mod,
|
||||
v: Date.now()
|
||||
})
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
$("#node-input-template").val(this.editor.getText())
|
||||
$("#node-input-template").val(this.editor.getValue())
|
||||
delete this.editor;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,32 +31,29 @@ module.exports = function(RED) {
|
||||
|
||||
if (n.timeoutUnits === "milliseconds") {
|
||||
this.timeout = n.timeout;
|
||||
} else if (n.timeoutUnits === "seconds") {
|
||||
this.timeout = n.timeout * 1000;
|
||||
} else if (n.timeoutUnits === "minutes") {
|
||||
} else if (n.timeoutUnits === "minutes") {
|
||||
this.timeout = n.timeout * (60 * 1000);
|
||||
} else if (n.timeoutUnits === "hours") {
|
||||
this.timeout = n.timeout * (60 * 60 * 1000);
|
||||
} else if (n.timeoutUnits === "days") {
|
||||
this.timeout = n.timeout * (24 * 60 * 60 * 1000);
|
||||
} else { // Default to seconds
|
||||
this.timeout = n.timeout * 1000;
|
||||
}
|
||||
|
||||
if (n.rateUnits === "second") {
|
||||
this.rate = 1000/n.rate;
|
||||
} else if (n.rateUnits === "minute") {
|
||||
if (n.rateUnits === "minute") {
|
||||
this.rate = (60 * 1000)/n.rate;
|
||||
} else if (n.rateUnits === "hour") {
|
||||
this.rate = (60 * 60 * 1000)/n.rate;
|
||||
} else if (n.rateUnits === "day") {
|
||||
this.rate = (24 * 60 * 60 * 1000)/n.rate;
|
||||
} else { // Default to seconds
|
||||
this.rate = 1000/n.rate;
|
||||
}
|
||||
|
||||
if (n.randomUnits === "milliseconds") {
|
||||
this.randomFirst = n.randomFirst * 1;
|
||||
this.randomLast = n.randomLast * 1;
|
||||
} else if (n.randomUnits === "seconds") {
|
||||
this.randomFirst = n.randomFirst * 1000;
|
||||
this.randomLast = n.randomLast * 1000;
|
||||
} else if (n.randomUnits === "minutes") {
|
||||
this.randomFirst = n.randomFirst * (60 * 1000);
|
||||
this.randomLast = n.randomLast * (60 * 1000);
|
||||
@@ -66,6 +63,9 @@ module.exports = function(RED) {
|
||||
} else if (n.randomUnits === "days") {
|
||||
this.randomFirst = n.randomFirst * (24 * 60 * 60 * 1000);
|
||||
this.randomLast = n.randomLast * (24 * 60 * 60 * 1000);
|
||||
} else { // Default to seconds
|
||||
this.randomFirst = n.randomFirst * 1000;
|
||||
this.randomLast = n.randomLast * 1000;
|
||||
}
|
||||
|
||||
this.diff = this.randomLast - this.randomFirst;
|
||||
@@ -147,7 +147,6 @@ module.exports = function(RED) {
|
||||
node.send(node.buffer.shift()); // send the first on the queue
|
||||
}
|
||||
node.status({text:node.buffer.length});
|
||||
//console.log(node.buffer);
|
||||
},node.rate);
|
||||
|
||||
this.on("input", function(msg) {
|
||||
|
||||
@@ -99,8 +99,8 @@
|
||||
defaults: {
|
||||
op1: {value:"1"},
|
||||
op2: {value:"0"},
|
||||
op1type: {value:""},
|
||||
op2type: {value:""},
|
||||
op1type: {value:"val"},
|
||||
op2type: {value:"val"},
|
||||
duration: {value:"250",required:true,validate:RED.validators.number()},
|
||||
extend: {value:"false"},
|
||||
units: {value: "ms"},
|
||||
|
||||
@@ -23,7 +23,7 @@ module.exports = function(RED) {
|
||||
this.op2 = n.op2 || "0";
|
||||
this.op1type = n.op1type || "val";
|
||||
this.op2type = n.op2type || "val";
|
||||
this.extend = n.extend || false;
|
||||
this.extend = n.extend || "false";
|
||||
this.units = n.units || "ms";
|
||||
this.duration = n.duration || 250;
|
||||
if (this.duration <= 0) { this.duration = 0; }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-info-editor" ></div>
|
||||
</div>
|
||||
<div class="form-tips">Tip: The text here can be styled as <i><a href="https://help.github.com/articles/markdown-basics/" target="_new">Github flavored Markdown</a></i></div>
|
||||
<div class="form-tips">Tip: The text here can be styled as <i><a href="https://help.github.com/articles/markdown-basics/" target="_new">Github flavoured Markdown</a></i></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="comment">
|
||||
@@ -51,9 +51,12 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
info: function() {
|
||||
return "### "+this.name+"\n"+this.info;
|
||||
var t = this.name || "Comment node";
|
||||
var b = this.info || "Use this node to add simple documentation.\n\nAnything you add will be rendered in this info panel.\n\nYou may use Markdown syntax to **enhance** the *presentation*.";
|
||||
return "### "+t+"\n"+b;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
$( "#node-input-outputs" ).spinner({
|
||||
min:1
|
||||
});
|
||||
@@ -66,6 +69,7 @@
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
that.editor.resize();
|
||||
};
|
||||
$( "#dialog" ).on("dialogresize", functionDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
@@ -80,20 +84,15 @@
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",functionDialogResize);
|
||||
});
|
||||
var that = this;
|
||||
require(["orion/editor/edit"], function(edit) {
|
||||
that.editor = edit({
|
||||
parent:document.getElementById('node-input-info-editor'),
|
||||
lang:"text",
|
||||
showLinesRuler:false,
|
||||
showFoldingRuler:false,
|
||||
contents: $("#node-input-info").val()
|
||||
});
|
||||
$("#node-input-name").focus();
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-info-editor',
|
||||
mode: 'ace/mode/markdown'
|
||||
});
|
||||
this.editor.setValue($("#node-input-info").val(),-1);
|
||||
this.editor.focus();
|
||||
},
|
||||
oneditsave: function() {
|
||||
$("#node-input-info").val(this.editor.getText());
|
||||
$("#node-input-info").val(this.editor.getValue());
|
||||
delete this.editor;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var ArduinoFirmata = require('arduino-firmata');
|
||||
var fs = require('fs');
|
||||
var plat = require('os').platform();
|
||||
var portlist = ArduinoFirmata.list(function (err, ports) {
|
||||
portlist = ports;
|
||||
});
|
||||
@@ -33,14 +31,14 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
node.board = new ArduinoFirmata();
|
||||
if (portlist.indexOf(node.device) === -1) {
|
||||
node.warn("Device "+node.device+" not found");
|
||||
node.error("device "+node.device+" not found");
|
||||
}
|
||||
else {
|
||||
node.board.connect(node.device);
|
||||
}
|
||||
|
||||
node.board.on('boardReady', function(){
|
||||
node.log("version "+node.board.boardVersion);
|
||||
if (RED.settings.verbose) { node.log("version "+node.board.boardVersion); }
|
||||
});
|
||||
|
||||
node.on('close', function(done) {
|
||||
@@ -48,7 +46,7 @@ module.exports = function(RED) {
|
||||
try {
|
||||
node.board.close(function() {
|
||||
done();
|
||||
node.log("port closed");
|
||||
if (RED.settings.verbose) { node.log("port closed"); }
|
||||
});
|
||||
} catch(e) { done(); }
|
||||
} else { done(); }
|
||||
@@ -67,10 +65,8 @@ module.exports = function(RED) {
|
||||
this.serverConfig = RED.nodes.getNode(this.arduino);
|
||||
if (typeof this.serverConfig === "object") {
|
||||
this.board = this.serverConfig.board;
|
||||
//this.repeat = this.serverConfig.repeat;
|
||||
var node = this;
|
||||
node.status({fill:"red",shape:"ring",text:"connecting"});
|
||||
|
||||
node.board.on('connect', function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
//console.log("i",node.state,node.pin);
|
||||
@@ -81,9 +77,8 @@ module.exports = function(RED) {
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
if (node.state == "INPUT") {
|
||||
node.board.pinMode(node.pin, ArduinoFirmata.INPUT);
|
||||
node.board.on('digitalChange', function(e) {
|
||||
if (e.pin == node.pin) {
|
||||
@@ -92,10 +87,16 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (node.state == "SYSEX") {
|
||||
node.board.on('sysex', function(e) {
|
||||
var msg = {payload:e, topic:"sysex"};
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
util.log("[Firmata-arduino] port not configured");
|
||||
this.warn("port not configured");
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("arduino in",DuinoNodeIn);
|
||||
@@ -119,7 +120,7 @@ module.exports = function(RED) {
|
||||
//console.log("o",node.state,node.pin);
|
||||
node.board.pinMode(node.pin, node.state);
|
||||
node.on("input", function(msg) {
|
||||
if (node.state == "OUTPUT") {
|
||||
if (node.state === "OUTPUT") {
|
||||
if ((msg.payload == true)||(msg.payload == 1)||(msg.payload.toString().toLowerCase() == "on")) {
|
||||
node.board.digitalWrite(node.pin, true);
|
||||
}
|
||||
@@ -127,25 +128,26 @@ module.exports = function(RED) {
|
||||
node.board.digitalWrite(node.pin, false);
|
||||
}
|
||||
}
|
||||
if (node.state == "PWM") {
|
||||
if (node.state === "PWM") {
|
||||
msg.payload = msg.payload * 1;
|
||||
if ((msg.payload >= 0) && (msg.payload <= 255)) {
|
||||
//console.log(msg.payload, node.pin);
|
||||
node.board.analogWrite(node.pin, msg.payload);
|
||||
}
|
||||
}
|
||||
if (node.state == "SERVO") {
|
||||
if (node.state === "SERVO") {
|
||||
msg.payload = msg.payload * 1;
|
||||
if ((msg.payload >= 0) && (msg.payload <= 180)) {
|
||||
//console.log(msg.payload, node.pin);
|
||||
node.board.servoWrite(node.pin, msg.payload);
|
||||
}
|
||||
}
|
||||
if (node.state === "SYSEX") {
|
||||
node.board.sysex(msg.payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
util.log("[Firmata-arduino] port not configured");
|
||||
this.warn("port not configured");
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("arduino out",DuinoNodeOut);
|
||||
|
||||
@@ -102,15 +102,15 @@
|
||||
if ((data.type === "Model B+") || (data.type === "Model A+")) {
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO5"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO6"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO12"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO13"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO19"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO16"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO26"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO20"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO21"));
|
||||
$('#node-input-pin').val(pinnow);
|
||||
}
|
||||
});
|
||||
@@ -240,15 +240,15 @@
|
||||
if ((data.type === "Model B+") || (data.type === "Model A+")) {
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO22"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO26"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO23"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO24"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO27"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO25"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO28"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO29"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO5"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",31).text("31 - GPIO6"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",32).text("32 - GPIO12"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",33).text("33 - GPIO13"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",35).text("35 - GPIO19"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",36).text("36 - GPIO16"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",37).text("37 - GPIO26"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",38).text("38 - GPIO20"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",40).text("40 - GPIO21"));
|
||||
$('#node-input-pin').val(pinnow);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var fs = require('fs');
|
||||
@@ -24,17 +23,17 @@ module.exports = function(RED) {
|
||||
var gpioCommand = __dirname+'/nrgpio';
|
||||
|
||||
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
|
||||
//util.log("Info : Ignoring Raspberry Pi specific node.");
|
||||
//RED.log.info("Ignoring Raspberry Pi specific node.");
|
||||
throw "Info : Ignoring Raspberry Pi specific node.";
|
||||
}
|
||||
|
||||
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
|
||||
util.log("[rpi-gpio] Info : Can't find Pi RPi.GPIO python library.");
|
||||
RED.log.warn("Can't find Pi RPi.GPIO python library.");
|
||||
throw "Warning : Can't find Pi RPi.GPIO python library.";
|
||||
}
|
||||
|
||||
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
|
||||
util.log("[rpi-gpio] Error : "+gpioCommand+" needs to be executable.");
|
||||
RED.log.error(gpioCommand+" needs to be executable.");
|
||||
throw "Error : nrgpio must to be executable.";
|
||||
}
|
||||
|
||||
@@ -100,7 +99,7 @@ module.exports = function(RED) {
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
|
||||
else { node.log('error: ' + err); }
|
||||
else { node.error('error: ' + err.errno); }
|
||||
});
|
||||
|
||||
}
|
||||
@@ -113,7 +112,7 @@ module.exports = function(RED) {
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.stdin.write(" close "+node.pin);
|
||||
node.child.stdin.write("close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
}
|
||||
else { done(); }
|
||||
@@ -151,7 +150,7 @@ module.exports = function(RED) {
|
||||
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
|
||||
}
|
||||
else {
|
||||
node.error("nrpgio python command not running");
|
||||
node.error("nrpgio python command not running",msg);
|
||||
node.status({fill:"red",shape:"ring",text:"not running"});
|
||||
}
|
||||
}
|
||||
@@ -191,7 +190,7 @@ module.exports = function(RED) {
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
|
||||
else { node.log('error: ' + err); }
|
||||
else { node.error('error: ' + err.errno); }
|
||||
});
|
||||
|
||||
}
|
||||
@@ -204,7 +203,7 @@ module.exports = function(RED) {
|
||||
delete pinsInUse[node.pin];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.stdin.write(" close "+node.pin);
|
||||
node.child.stdin.write("close "+node.pin);
|
||||
node.child.kill('SIGKILL');
|
||||
}
|
||||
else { done(); }
|
||||
@@ -215,14 +214,14 @@ module.exports = function(RED) {
|
||||
var pitype = { type:"" };
|
||||
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
|
||||
if (err) {
|
||||
console.log('[rpi-gpio] Version command failed for some reason.');
|
||||
RED.log.info('Version command failed for some reason.');
|
||||
}
|
||||
else {
|
||||
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
|
||||
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
|
||||
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
|
||||
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
|
||||
else { console.log("SAW Pi TYPE",stdout.trim()); }
|
||||
else { RED.log.info("Saw Pi Type",stdout.trim()); }
|
||||
}
|
||||
});
|
||||
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
|
||||
@@ -259,7 +258,7 @@ module.exports = function(RED) {
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio ommand not executable'); }
|
||||
else { node.log('error: ' + err); }
|
||||
else { node.error('error: ' + err.errno); }
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
|
||||
@@ -38,11 +38,10 @@ if len(sys.argv) > 1:
|
||||
while True:
|
||||
try:
|
||||
data = raw_input()
|
||||
if data == "close":
|
||||
GPIO.cleanup(pin)
|
||||
if 'close' in data:
|
||||
sys.exit(0)
|
||||
p.ChangeDutyCycle(float(data))
|
||||
except EOFError: # hopefully always caused by us sigint'ing the program
|
||||
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
|
||||
GPIO.cleanup(pin)
|
||||
sys.exit(0)
|
||||
except Exception as ex:
|
||||
@@ -57,15 +56,14 @@ if len(sys.argv) > 1:
|
||||
while True:
|
||||
try:
|
||||
data = raw_input()
|
||||
if data == "close":
|
||||
GPIO.cleanup(pin)
|
||||
if 'close' in data:
|
||||
sys.exit(0)
|
||||
elif float(data) == 0:
|
||||
p.stop()
|
||||
else:
|
||||
p.start(50)
|
||||
p.ChangeFrequency(float(data))
|
||||
except EOFError: # hopefully always caused by us sigint'ing the program
|
||||
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
|
||||
GPIO.cleanup(pin)
|
||||
sys.exit(0)
|
||||
except Exception as ex:
|
||||
@@ -80,11 +78,10 @@ if len(sys.argv) > 1:
|
||||
while True:
|
||||
try:
|
||||
data = raw_input()
|
||||
if data == "close":
|
||||
GPIO.cleanup(pin)
|
||||
if 'close' in data:
|
||||
sys.exit(0)
|
||||
data = int(data)
|
||||
except EOFError: # hopefully always caused by us sigint'ing the program
|
||||
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
|
||||
GPIO.cleanup(pin)
|
||||
sys.exit(0)
|
||||
except:
|
||||
@@ -113,10 +110,9 @@ if len(sys.argv) > 1:
|
||||
while True:
|
||||
try:
|
||||
data = raw_input()
|
||||
if data == "close":
|
||||
GPIO.cleanup(pin)
|
||||
if 'close' in data:
|
||||
sys.exit(0)
|
||||
except EOFError: # hopefully always caused by us sigint'ing the program
|
||||
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
|
||||
GPIO.cleanup(pin)
|
||||
sys.exit(0)
|
||||
|
||||
@@ -128,11 +124,10 @@ if len(sys.argv) > 1:
|
||||
while True:
|
||||
try:
|
||||
data = raw_input()
|
||||
if data == "close":
|
||||
GPIO.cleanup()
|
||||
if 'close' in data:
|
||||
sys.exit(0)
|
||||
data = int(data)
|
||||
except EOFError: # hopefully always caused by us sigint'ing the program
|
||||
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
|
||||
GPIO.cleanup()
|
||||
sys.exit(0)
|
||||
except:
|
||||
@@ -159,14 +154,13 @@ if len(sys.argv) > 1:
|
||||
while True:
|
||||
try:
|
||||
data = raw_input()
|
||||
if data == "close":
|
||||
GPIO.cleanup()
|
||||
if 'close' in data:
|
||||
sys.exit(0)
|
||||
c = data.split(",")
|
||||
r.ChangeDutyCycle(float(c[0]))
|
||||
g.ChangeDutyCycle(float(c[1]))
|
||||
b.ChangeDutyCycle(float(c[2]))
|
||||
except EOFError: # hopefully always caused by us sigint'ing the program
|
||||
except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program
|
||||
GPIO.cleanup()
|
||||
sys.exit(0)
|
||||
except:
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<script type="text/x-red" data-help-name="mqtt in">
|
||||
<p>MQTT input node. Connects to a broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
|
||||
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>.</p>
|
||||
<p><b>msg.payload</b> is a String.</p>
|
||||
<p><b>msg.payload</b> is usually a string, but can also be a binary buffer.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -45,21 +45,26 @@ module.exports = function(RED) {
|
||||
this.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
|
||||
var node = this;
|
||||
this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
|
||||
if (isUtf8(payload)) { payload = payload.toString(); }
|
||||
var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
|
||||
if ((node.brokerConfig.broker === "localhost")||(node.brokerConfig.broker === "127.0.0.1")) {
|
||||
msg._topic = topic;
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
this.client.on("connectionlost",function() {
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
});
|
||||
this.client.on("connect",function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
this.client.connect();
|
||||
if (this.topic) {
|
||||
this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
|
||||
if (isUtf8(payload)) { payload = payload.toString(); }
|
||||
var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
|
||||
if ((node.brokerConfig.broker === "localhost")||(node.brokerConfig.broker === "127.0.0.1")) {
|
||||
msg._topic = topic;
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
this.client.on("connectionlost",function() {
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
});
|
||||
this.client.on("connect",function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
});
|
||||
this.client.connect();
|
||||
}
|
||||
else {
|
||||
this.error("topic not defined");
|
||||
}
|
||||
} else {
|
||||
this.error("missing broker configuration");
|
||||
}
|
||||
|
||||
@@ -139,7 +139,8 @@
|
||||
</ul>
|
||||
<p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_new">mustache-style</a> tags. These allow the
|
||||
url to be constructed using values of the incoming message. For example, if the url is set to
|
||||
<code>example.com/{{topic}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.</p>
|
||||
<code>example.com/{{{topic}}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.
|
||||
Using {{{...}}} prevents mustache from escaping characters like / & etc.</p>
|
||||
<p>
|
||||
The output message contains the following properties:
|
||||
<ul>
|
||||
|
||||
@@ -74,7 +74,7 @@ module.exports = function(RED) {
|
||||
corsHandler = cors(RED.settings.httpNodeCors);
|
||||
RED.httpNode.options(this.url,corsHandler);
|
||||
}
|
||||
|
||||
|
||||
var metricsHandler = function(req,res,next) { next(); }
|
||||
|
||||
if (this.metric()) {
|
||||
@@ -94,7 +94,7 @@ module.exports = function(RED) {
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (this.method == "get") {
|
||||
RED.httpNode.get(this.url,corsHandler,metricsHandler,this.callback,this.errorHandler);
|
||||
} else if (this.method == "post") {
|
||||
@@ -155,7 +155,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
msg.res.set('content-length', len);
|
||||
}
|
||||
|
||||
|
||||
msg.res._msgId = msg._id;
|
||||
msg.res.send(statusCode,msg.payload);
|
||||
}
|
||||
@@ -177,34 +177,24 @@ module.exports = function(RED) {
|
||||
this.on("input",function(msg) {
|
||||
var preRequestTimestamp = process.hrtime();
|
||||
node.status({fill:"blue",shape:"dot",text:"requesting"});
|
||||
var url;
|
||||
if (msg.url) {
|
||||
if (n.url && (n.url !== msg.url)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
url = msg.url;
|
||||
} else if (isTemplatedUrl) {
|
||||
var url = nodeUrl || msg.url;
|
||||
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
|
||||
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
if (isTemplatedUrl) {
|
||||
url = mustache.render(nodeUrl,msg);
|
||||
} else {
|
||||
url = nodeUrl;
|
||||
}
|
||||
// url must start http:// or https:// so assume http:// if not set
|
||||
if (!((url.indexOf("http://")===0) || (url.indexOf("https://")===0))) {
|
||||
url = "http://"+url;
|
||||
}
|
||||
|
||||
var method;
|
||||
if (msg.method) { // if method set in msg
|
||||
if (n.method && (n.method !== "use")) { // warn if override option not set
|
||||
node.warn("Deprecated: msg properties should not override fixed node properties. Use explicit override option. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
method = msg.method.toUpperCase(); // but use it anyway
|
||||
} else {
|
||||
if (n.method !== "use") {
|
||||
method = nodeMethod.toUpperCase(); // otherwise use the selected method
|
||||
} else { // unless they selected override
|
||||
method = "GET"; // - in which case default to GET
|
||||
}
|
||||
var method = nodeMethod.toUpperCase() || "GET";
|
||||
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
|
||||
node.warn("Warning: msg properties can no longer override fixed node properties. Use explicit override option. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
if (msg.method && n.method && (n.method === "use")) {
|
||||
method = msg.method.toUpperCase(); // use the msg parameter
|
||||
}
|
||||
var opts = urllib.parse(url);
|
||||
opts.method = method;
|
||||
@@ -251,6 +241,7 @@ module.exports = function(RED) {
|
||||
msg.statusCode = res.statusCode;
|
||||
msg.headers = res.headers;
|
||||
msg.payload = "";
|
||||
// msg.url = url; // revert when warning above finally removed
|
||||
res.on('data',function(chunk) {
|
||||
msg.payload += chunk;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -133,7 +133,7 @@ module.exports = function(RED) {
|
||||
if(this.isServer) {
|
||||
for (var i = 0; i < this.server.clients.length; i++) {
|
||||
this.server.clients[i].send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.server.send(data);
|
||||
@@ -181,7 +181,7 @@ module.exports = function(RED) {
|
||||
if (this.serverConfig.wholemsg) {
|
||||
delete msg._session;
|
||||
payload = JSON.stringify(msg);
|
||||
} else {
|
||||
} else if (msg.hasOwnProperty("payload")) {
|
||||
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
|
||||
payload = RED.util.ensureString(msg.payload);
|
||||
}
|
||||
@@ -189,14 +189,16 @@ module.exports = function(RED) {
|
||||
payload = msg.payload;
|
||||
}
|
||||
}
|
||||
if (msg._session && msg._session.type == "websocket") {
|
||||
node.serverConfig.reply(msg._session.id,payload);
|
||||
} else {
|
||||
node.serverConfig.broadcast(payload,function(error){
|
||||
if (!!error) {
|
||||
node.warn("An error occurred while sending:" + inspect(error));
|
||||
}
|
||||
});
|
||||
if (payload) {
|
||||
if (msg._session && msg._session.type == "websocket") {
|
||||
node.serverConfig.reply(msg._session.id,payload);
|
||||
} else {
|
||||
node.serverConfig.broadcast(payload,function(error){
|
||||
if (!!error) {
|
||||
node.warn("An error occurred while sending:" + inspect(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
<p>On Windows you must use double back-slashes \\ in any directory names.</p>
|
||||
<p>The full filename of the file that actually changed is put into <b>msg.payload</b>,
|
||||
while a stringified version of the watch list is returned in <b>msg.topic</b>.</p>
|
||||
<p><b>msg.file</b> contains just the short filename of the file that changed.</p>
|
||||
<p><b>msg.file</b> contains just the short filename of the file that changed.
|
||||
<b>msg.type</b> has the type of thing changed, usually <i>file</i> or <i>directory</i>,
|
||||
while <b>msg.size</b> holds the file size in bytes.</p>
|
||||
<p>Of course in Linux, <i>everything</i> is a file and thus can be watched...</p>
|
||||
<p><b>Note: </b>The directory or file must exist in order to be watched. If the file
|
||||
or directory gets deleted it may no longer be monitored even if it gets re-created.</p>
|
||||
|
||||
@@ -23,24 +23,35 @@ module.exports = function(RED) {
|
||||
function WatchNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.files = n.files.split(",");
|
||||
for (var f =0; f < this.files.length; f++) {
|
||||
this.files = (n.files || "").split(",");
|
||||
for (var f=0; f < this.files.length; f++) {
|
||||
this.files[f] = this.files[f].trim();
|
||||
}
|
||||
this.p = (this.files.length == 1) ? this.files[0] : JSON.stringify(this.files);
|
||||
this.p = (this.files.length === 1) ? this.files[0] : JSON.stringify(this.files);
|
||||
var node = this;
|
||||
|
||||
var notifications = new Notify(node.files);
|
||||
notifications.on('change', function (file, event, path) {
|
||||
var stat;
|
||||
try {
|
||||
if (fs.statSync(path).isDirectory()) { path = path + sep + file; }
|
||||
stat = fs.statSync(path);
|
||||
} catch(e) { }
|
||||
var msg = { payload: path, topic: node.p, file: file };
|
||||
var type = "other";
|
||||
if (stat.isFile()) { type = "file"; }
|
||||
else if (stat.isDirectory()) { type = "directory"; }
|
||||
else if (stat.isBlockDevice()) { type = "blockdevice"; }
|
||||
else if (stat.isCharacterDevice()) { type = "characterdevice"; }
|
||||
else if (stat.isSocket()) { type = "socket"; }
|
||||
else if (stat.isFIFO()) { type = "fifo"; }
|
||||
else { type = "n/a"; }
|
||||
var msg = { payload:path, topic:node.p, file:file, type:type, size:stat.size };
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
notifications.on('error', function (error, path) {
|
||||
node.warn(error);
|
||||
var msg = { payload:path };
|
||||
node.error(error,msg);
|
||||
});
|
||||
|
||||
this.close = function() {
|
||||
|
||||
@@ -18,7 +18,6 @@ module.exports = function(RED) {
|
||||
"use strict";
|
||||
var settings = RED.settings;
|
||||
var events = require("events");
|
||||
var util = require("util");
|
||||
var serialp = require("serialport");
|
||||
var bufMaxSize = 32768; // Max serial buffer size, for inputs...
|
||||
|
||||
@@ -56,22 +55,25 @@ module.exports = function(RED) {
|
||||
node.addCh = this.serialConfig.newline.replace("\\n","\n").replace("\\r","\r").replace("\\t","\t").replace("\\e","\e").replace("\\f","\f").replace("\\0","\0");
|
||||
}
|
||||
node.on("input",function(msg) {
|
||||
var payload = msg.payload;
|
||||
if (!Buffer.isBuffer(payload)) {
|
||||
if (typeof payload === "object") {
|
||||
payload = JSON.stringify(payload);
|
||||
} else {
|
||||
payload = payload.toString();
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
var payload = msg.payload;
|
||||
if (!Buffer.isBuffer(payload)) {
|
||||
if (typeof payload === "object") {
|
||||
payload = JSON.stringify(payload);
|
||||
} else {
|
||||
payload = payload.toString();
|
||||
}
|
||||
payload += node.addCh;
|
||||
} else if (node.addCh !== "") {
|
||||
payload = Buffer.concat([payload,new Buffer(node.addCh)]);
|
||||
}
|
||||
payload += node.addCh;
|
||||
} else if (node.addCh !== "") {
|
||||
payload = Buffer.concat([payload,new Buffer(node.addCh)]);
|
||||
node.port.write(payload,function(err,res) {
|
||||
if (err) {
|
||||
var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path);
|
||||
node.error(errmsg,msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
node.port.write(payload,function(err,res) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
node.port.on('ready', function() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
@@ -174,7 +176,7 @@ module.exports = function(RED) {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
else { console.log("Should never get here"); }
|
||||
else { node.log("should never get here"); }
|
||||
}
|
||||
});
|
||||
this.port.on('ready', function() {
|
||||
@@ -235,7 +237,7 @@ module.exports = function(RED) {
|
||||
// },true, function(err, results) { if (err) obj.serial.emit('error',err); });
|
||||
//}
|
||||
obj.serial.on('error', function(err) {
|
||||
util.log("[serial] serial port "+port+" error "+err);
|
||||
RED.log.error("serial port "+port+" error "+err);
|
||||
obj._emitter.emit('closed');
|
||||
obj.tout = setTimeout(function() {
|
||||
setupSerial();
|
||||
@@ -243,7 +245,7 @@ module.exports = function(RED) {
|
||||
});
|
||||
obj.serial.on('close', function() {
|
||||
if (!obj._closing) {
|
||||
util.log("[serial] serial port "+port+" closed unexpectedly");
|
||||
RED.log.error("serial port "+port+" closed unexpectedly");
|
||||
obj._emitter.emit('closed');
|
||||
obj.tout = setTimeout(function() {
|
||||
setupSerial();
|
||||
@@ -251,7 +253,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
obj.serial.on('open',function() {
|
||||
util.log("[serial] serial port "+port+" opened at "+baud+" baud "+databits+""+parity.charAt(0).toUpperCase()+stopbits);
|
||||
RED.log.info("serial port "+port+" opened at "+baud+" baud "+databits+""+parity.charAt(0).toUpperCase()+stopbits);
|
||||
if (obj.tout) { clearTimeout(obj.tout); }
|
||||
//obj.serial.flush();
|
||||
obj._emitter.emit('ready');
|
||||
@@ -269,7 +271,7 @@ module.exports = function(RED) {
|
||||
//}
|
||||
});
|
||||
obj.serial.on("disconnect",function() {
|
||||
util.log("[serial] serial port "+port+" gone away");
|
||||
RED.log.error("serial port "+port+" gone away");
|
||||
});
|
||||
}
|
||||
setupSerial();
|
||||
@@ -286,7 +288,7 @@ module.exports = function(RED) {
|
||||
connections[port]._closing = true;
|
||||
try {
|
||||
connections[port].close(function() {
|
||||
util.log("[serial] serial port closed");
|
||||
RED.log.info("serial port closed");
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -470,13 +470,13 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
client.on('error', function() {
|
||||
node.log('connect failed');
|
||||
node.error('connect failed',msg);
|
||||
node.status({fill:"red",shape:"ring",text:"error"});
|
||||
if (client) { client.end(); }
|
||||
});
|
||||
|
||||
client.on('timeout',function() {
|
||||
node.log('connect timeout');
|
||||
node.warn('connect timeout');
|
||||
if (client) {
|
||||
client.end();
|
||||
setTimeout(function() {
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
<!-- The Input Node -->
|
||||
<script type="text/x-red" data-template-name="udp in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> Listen</label>
|
||||
on port <input type="text" id="node-input-port" placeholder="Port" style="width: 45px">
|
||||
for <select id="node-input-multicast" style='width:40%'>
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> Listen for</label>
|
||||
<select id="node-input-multicast" style='width:62%'>
|
||||
<option value="false">udp messages</option>
|
||||
<option value="true">multicast messages</option>
|
||||
</select>
|
||||
@@ -32,6 +31,14 @@
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
|
||||
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> on Port</label>
|
||||
<input type="text" id="node-input-port" placeholder="Port" style="width: 80px">
|
||||
using <select id="node-input-ipv" style="width:80px">
|
||||
<option value="udp4">ipv4</option>
|
||||
<option value="udp6">ipv6</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> Output</label>
|
||||
<select id="node-input-datatype" style="width: 70%;">
|
||||
@@ -74,9 +81,10 @@
|
||||
name: {value:""},
|
||||
iface: {value:""},
|
||||
port: {value:"",required:true,validate:RED.validators.number()},
|
||||
datatype: {value:"buffer",required:true},
|
||||
ipv: {value:"udp4"},
|
||||
multicast: {value:"false"},
|
||||
group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} }
|
||||
group: {value:"",validate:function(v) { return (this.multicast !== "true")||v.length > 0;} },
|
||||
datatype: {value:"buffer",required:true}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
@@ -98,7 +106,7 @@
|
||||
<script type="text/x-red" data-template-name="udp out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-envelope"></i> Send a</label>
|
||||
<select id="node-input-multicast" style='width:40%'>
|
||||
<select id="node-input-multicast" style="width:40%">
|
||||
<option value="false">udp message</option>
|
||||
<option value="broad">broadcast message</option>
|
||||
<option value="multi">multicast message</option>
|
||||
@@ -107,7 +115,11 @@
|
||||
</div>
|
||||
<div class="form-row node-input-addr">
|
||||
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> Address</label>
|
||||
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 70%;">
|
||||
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 50%;">
|
||||
<select id="node-input-ipv" style="width:70px">
|
||||
<option value="udp4">ipv4</option>
|
||||
<option value="udp6">ipv6</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
|
||||
@@ -167,6 +179,7 @@
|
||||
addr: {value:""},
|
||||
iface: {value:""},
|
||||
port: {value:""},
|
||||
ipv: {value:"udp4"},
|
||||
outport: {value:""},
|
||||
base64: {value:false,required:true},
|
||||
multicast: {value:"false"}
|
||||
|
||||
@@ -26,9 +26,10 @@ module.exports = function(RED) {
|
||||
this.datatype = n.datatype;
|
||||
this.iface = n.iface || null;
|
||||
this.multicast = n.multicast;
|
||||
this.ipv = n.ipv || "udp4";
|
||||
var node = this;
|
||||
|
||||
var server = dgram.createSocket('udp4');
|
||||
var server = dgram.createSocket(node.ipv); // default to ipv4
|
||||
|
||||
server.on("error", function (err) {
|
||||
if ((err.code == "EACCES") && (node.port < 1024)) {
|
||||
@@ -42,9 +43,9 @@ module.exports = function(RED) {
|
||||
server.on('message', function (message, remote) {
|
||||
var msg;
|
||||
if (node.datatype =="base64") {
|
||||
msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port };
|
||||
msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else if (node.datatype =="utf8") {
|
||||
msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port };
|
||||
msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
} else {
|
||||
msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port };
|
||||
}
|
||||
@@ -81,7 +82,9 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
|
||||
server.bind(node.port,node.iface);
|
||||
// Hack for when you have both in and out udp nodes sharing a port
|
||||
// if udp in starts last it shares better - so give it a chance to be last
|
||||
setTimeout( function() { server.bind(node.port,node.iface); }, 250);
|
||||
}
|
||||
RED.nodes.registerType("udp in",UDPin);
|
||||
|
||||
@@ -96,9 +99,10 @@ module.exports = function(RED) {
|
||||
this.addr = n.addr;
|
||||
this.iface = n.iface || null;
|
||||
this.multicast = n.multicast;
|
||||
this.ipv = n.ipv || "udp4";
|
||||
var node = this;
|
||||
|
||||
var sock = dgram.createSocket('udp4'); // only use ipv4 for now
|
||||
var sock = dgram.createSocket(node.ipv); // default to ipv4
|
||||
|
||||
if (node.multicast != "false") {
|
||||
if (node.outport == "") { node.outport = node.port; }
|
||||
@@ -130,7 +134,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
node.on("input", function(msg) {
|
||||
if (msg.payload != null) {
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
var add = node.addr || msg.ip || "";
|
||||
var por = node.port || msg.port || 0;
|
||||
if (add == "") {
|
||||
@@ -150,7 +154,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
sock.send(message, 0, message.length, por, add, function(err, bytes) {
|
||||
if (err) {
|
||||
node.error("udp : "+err);
|
||||
node.error("udp : "+err,msg);
|
||||
}
|
||||
message = null;
|
||||
});
|
||||
|
||||
@@ -35,10 +35,10 @@ module.exports = function(RED) {
|
||||
|
||||
function SwitchNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.rules = n.rules;
|
||||
this.rules = n.rules || [];
|
||||
this.property = n.property;
|
||||
this.checkall = n.checkall || "true";
|
||||
var propertyParts = n.property.split(".");
|
||||
var propertyParts = (n.property || "payload").split(".");
|
||||
var node = this;
|
||||
|
||||
for (var i=0; i<this.rules.length; i+=1) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -15,42 +15,34 @@
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="change">
|
||||
<div>
|
||||
<select id="node-input-action" style="width:95%; margin-right:5px;">
|
||||
<option value="replace">Set the value of the message property</option>
|
||||
<option value="change">Search/replace the value of the message property</option>
|
||||
<option value="delete">Delete the message property</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" style="padding-top:10px;" id="node-prop1-row">
|
||||
<label for="node-input-property">called</label> msg.<input type="text" id="node-input-property" style="width: 63%;"/>
|
||||
</div>
|
||||
<div class="form-row" id="node-from-row">
|
||||
<label for="node-input-from" id="node-input-f"></label>
|
||||
<input type="text" id="node-input-from" placeholder="this"/>
|
||||
</div>
|
||||
<div class="form-row" id="node-to-row">
|
||||
<label for="node-input-to" id="node-input-t"></label>
|
||||
<input type="text" id="node-input-to" placeholder="that"/>
|
||||
</div>
|
||||
<div class="form-row" id="node-reg-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-reg" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-reg" style="width: 70%;">Use regular expressions</label>
|
||||
</div>
|
||||
<div class="form-tips" id="node-tip"></div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> Rules</label>
|
||||
</div>
|
||||
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
|
||||
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 300px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
|
||||
<ol id="node-input-rule-container" style=" list-style-type:none; margin: 0;"></ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> rule</a>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="change">
|
||||
<p>A simple function node to set, replace or delete properties of a message.</p>
|
||||
<p>When a message arrives, the selected property is modified by the defined rules.
|
||||
The message is then sent to the output.</p>
|
||||
<p><b>Note:</b> Set and replace only operate using <b>strings</b>. Anything else will be passed straight through.</p>
|
||||
<p>Set, change or delete properties of a message.</p>
|
||||
<p>The node can specify multiple rules that will be applied to the message in turn.</p>
|
||||
<p>The available operations are:</p>
|
||||
<ul>
|
||||
<li><b>Set</b> - set a property. The <b>to</b> property can either be a string value, or reference
|
||||
another message property by name, for example: <code>msg.topic</code>.</li>
|
||||
<li><b>Change</b> - search & replace parts of the property. If regular expressions
|
||||
are enabled, the <b>replace with</b> property can include capture groups, for example <code>$1</code></li>
|
||||
<li><b>Delete</b> - delete a property.</li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -58,24 +50,14 @@
|
||||
color: "#E2D96E",
|
||||
category: 'function',
|
||||
defaults: {
|
||||
action: {value:"replace",required:true},
|
||||
property: {value:"payload",required:true},
|
||||
from: {value:"",validate: function(v) {
|
||||
if (this.action === "change" && !this.from) {
|
||||
return false;
|
||||
} else if (this.action === "change" && this.reg) {
|
||||
try {
|
||||
var re = new RegExp(this.from, "g");
|
||||
return true;
|
||||
} catch(err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}},
|
||||
name: {value:""},
|
||||
rules:{value:[{t:"set",p:"payload",to:""}]},
|
||||
// legacy
|
||||
action: {value:""},
|
||||
property: {value:""},
|
||||
from: {value:""},
|
||||
to: {value:""},
|
||||
reg: {value:false},
|
||||
name: {value:""}
|
||||
reg: {value:false}
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
@@ -84,12 +66,26 @@
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.action === "replace") {
|
||||
return "set msg."+this.property;
|
||||
} else if (this.action === "change") {
|
||||
return "replace msg."+this.property;
|
||||
if (!this.rules) {
|
||||
if (this.action === "replace") {
|
||||
return "set msg."+this.property;
|
||||
} else if (this.action === "change") {
|
||||
return "change msg."+this.property;
|
||||
} else {
|
||||
return this.action+" msg."+this.property
|
||||
}
|
||||
} else {
|
||||
return this.action+" msg."+this.property
|
||||
if (this.rules.length == 1) {
|
||||
if (this.rules[0].t === "set") {
|
||||
return "set msg."+this.rules[0].p;
|
||||
} else if (this.rules[0].t === "change") {
|
||||
return "change msg."+this.rules[0].p;
|
||||
} else {
|
||||
return "delete msg."+this.rules[0].p;
|
||||
}
|
||||
} else {
|
||||
return "change: "+(this.rules.length||"no")+" rules";
|
||||
}
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
@@ -97,47 +93,155 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
|
||||
$("#node-input-action").change( function() {
|
||||
var a = $("#node-input-action").val();
|
||||
if (a === "replace") {
|
||||
$("#node-input-todo").html("called");
|
||||
//$("#node-input-f").html("name");
|
||||
$("#node-input-t").html("to");
|
||||
$("#node-from-row").hide();
|
||||
$("#node-to-row").show();
|
||||
$("#node-reg-row").hide();
|
||||
$("#node-tip").show();
|
||||
$("#node-tip").html("Tip: expects a new property name and either a fixed value OR the full name of another message property eg: msg.sentiment.score");
|
||||
}
|
||||
if (a === "delete") {
|
||||
$("#node-input-todo").html("called");
|
||||
//$("#node-input-f").html("called");
|
||||
//$("#node-input-t").html("to");
|
||||
$("#node-from-row").hide();
|
||||
$("#node-to-row").hide();
|
||||
$("#node-reg-row").hide();
|
||||
$("#node-tip").hide();
|
||||
}
|
||||
if (a === "change") {
|
||||
$("#node-input-todo").html("called");
|
||||
$("#node-input-f").html("Search for");
|
||||
$("#node-input-t").html("replace with");
|
||||
$("#node-from-row").show();
|
||||
$("#node-to-row").show();
|
||||
$("#node-reg-row").show();
|
||||
$("#node-tip").show();
|
||||
$("#node-tip").html("Tip: only works on string properties. If regular expressions are used, the <i>replace with</i> field can contain capture results, eg $1.");
|
||||
}
|
||||
//if (a === "replace") {
|
||||
// $("#node-input-todo").html("called");
|
||||
// //$("#node-input-f").html("with");
|
||||
// $("#node-input-t").html("with");
|
||||
// $("#node-from-row").hide();
|
||||
// $("#node-to-row").show();
|
||||
// $("#node-tip").html("Tip: accepts either a fixed value OR the full name of another msg.property eg: msg.sentiment.score");
|
||||
//}
|
||||
});
|
||||
$("#node-input-action").change();
|
||||
|
||||
function generateRule(rule) {
|
||||
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
|
||||
|
||||
var row1 = $('<div/>').appendTo(container);
|
||||
|
||||
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
|
||||
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
|
||||
|
||||
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 100px"}).appendTo(row1);
|
||||
var selectOptions = [{v:"set",l:"Set"},{v:"change",l:"Change"},{v:"delete",l:"Delete"}];
|
||||
for (var i=0;i<3;i++) {
|
||||
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
|
||||
}
|
||||
|
||||
$('<div/>',{style:"display:inline-block; width: 50px; text-align: right;"}).text("msg.").appendTo(row1);
|
||||
var propertyName = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-name",type:"text"}).appendTo(row1);
|
||||
|
||||
|
||||
var finalspan = $('<span/>',{style:"float: right; margin-top: 3px;margin-right: 10px;"}).appendTo(row1);
|
||||
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
|
||||
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
||||
|
||||
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("to").appendTo(row2);
|
||||
var propertyValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2);
|
||||
|
||||
var row3_1 = $('<div/>').appendTo(row3);
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("Search for").appendTo(row3_1);
|
||||
var fromValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1);
|
||||
|
||||
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("replace with").appendTo(row3_2);
|
||||
var toValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2);
|
||||
|
||||
var row3_3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
|
||||
var id = "node-input-rule-property-regex-"+Math.floor(Math.random()*10000);
|
||||
var useRegExp = $('<input/>',{id:id,class:"node-input-rule-property-re",type:"checkbox", style:"margin-left: 150px; margin-right: 10px; display: inline-block; width: auto; vertical-align: top;"}).appendTo(row3_3);
|
||||
$('<label/>',{for:id,style:"width: auto;"}).text("Use regular expressions").appendTo(row3_3);
|
||||
|
||||
|
||||
selectField.change(function() {
|
||||
var type = $(this).val();
|
||||
|
||||
if (type == "set") {
|
||||
row2.show();
|
||||
row3.hide();
|
||||
} else if (type == "change") {
|
||||
row2.hide();
|
||||
row3.show();
|
||||
} else if (type == "delete") {
|
||||
row2.hide();
|
||||
row3.hide();
|
||||
$("#node-tip").hide();
|
||||
}
|
||||
});
|
||||
deleteButton.click(function() {
|
||||
container.css({"background":"#fee"});
|
||||
container.fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
});
|
||||
|
||||
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
|
||||
propertyName.val(rule.p);
|
||||
propertyValue.val(rule.to);
|
||||
fromValue.val(rule.from);
|
||||
toValue.val(rule.to);
|
||||
useRegExp.prop('checked', rule.re);
|
||||
selectField.change();
|
||||
|
||||
$("#node-input-rule-container").append(container);
|
||||
}
|
||||
$("#node-input-add-rule").click(function() {
|
||||
generateRule({t:"replace",p:"payload"});
|
||||
});
|
||||
|
||||
if (!this.rules) {
|
||||
var rule = {
|
||||
t:(this.action=="replace"?"set":this.action),
|
||||
p:this.property
|
||||
}
|
||||
|
||||
if (rule.t === "set") {
|
||||
rule.to = this.to;
|
||||
} else if (rule.t === "change") {
|
||||
rule.from = this.from;
|
||||
rule.to = this.to;
|
||||
rule.re = this.reg;
|
||||
}
|
||||
|
||||
delete this.to;
|
||||
delete this.from;
|
||||
delete this.reg;
|
||||
delete this.action;
|
||||
delete this.property;
|
||||
|
||||
this.rules = [rule];
|
||||
}
|
||||
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
generateRule(this.rules[i]);
|
||||
}
|
||||
|
||||
function changeDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-rule-container-div").css("height",height+"px");
|
||||
};
|
||||
$( "#dialog" ).on("dialogresize", changeDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-change');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
changeDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
$( "#dialog" ).off("dialogresize",changeDialogResize);
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
var ruleset;
|
||||
var node = this;
|
||||
node.rules= [];
|
||||
rules.each(function(i) {
|
||||
var rule = $(this);
|
||||
var type = rule.find(".node-input-rule-type option:selected").val();
|
||||
var r = {
|
||||
t:type,
|
||||
p:rule.find(".node-input-rule-property-name").val()
|
||||
};
|
||||
if (type === "set") {
|
||||
r.to = rule.find(".node-input-rule-property-value").val();
|
||||
} else if (type === "change") {
|
||||
r.from = rule.find(".node-input-rule-property-search-value").val();
|
||||
r.to = rule.find(".node-input-rule-property-replace-value").val();
|
||||
r.re = rule.find(".node-input-rule-property-re").prop('checked');
|
||||
}
|
||||
node.rules.push(r);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,34 +19,54 @@ module.exports = function(RED) {
|
||||
|
||||
function ChangeNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.action = n.action;
|
||||
this.property = n.property || "";
|
||||
this.from = n.from || "";
|
||||
this.to = n.to || "";
|
||||
this.reg = (n.reg === null || n.reg);
|
||||
var node = this;
|
||||
if (node.reg === false) {
|
||||
this.from = this.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
|
||||
this.rules = n.rules;
|
||||
|
||||
if (!this.rules) {
|
||||
var rule = {
|
||||
t:(n.action=="replace"?"set":n.action),
|
||||
p:n.property||""
|
||||
}
|
||||
|
||||
if (rule.t === "set") {
|
||||
rule.to = n.to||"";
|
||||
} else if (rule.t === "change") {
|
||||
rule.from = n.from||"";
|
||||
rule.to = n.to||"";
|
||||
rule.re = (n.reg===null||n.reg);
|
||||
}
|
||||
this.rules = [rule];
|
||||
}
|
||||
|
||||
this.actions = [];
|
||||
|
||||
this.on('input', function(msg) {
|
||||
var valid = true;
|
||||
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
var rule = this.rules[i];
|
||||
if (rule.t === "change") {
|
||||
if (rule.re === false) {
|
||||
rule.from = rule.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
try {
|
||||
rule.from = new RegExp(rule.from, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
this.error("Invalid 'from' property: "+e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyRule(msg,rule) {
|
||||
var propertyParts;
|
||||
var depth = 0;
|
||||
|
||||
if (node.action === "change") {
|
||||
try {
|
||||
node.re = new RegExp(this.from, "g");
|
||||
} catch (e) {
|
||||
node.error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
propertyParts = node.property.split(".");
|
||||
propertyParts = rule.p.split(".");
|
||||
try {
|
||||
propertyParts.reduce(function(obj, i) {
|
||||
var to = node.to;
|
||||
var to = rule.to;
|
||||
// Set msg from property to another msg property
|
||||
if (node.action === "replace" && node.to.indexOf("msg.") === 0) {
|
||||
if (rule.t === "set" && rule.to.indexOf("msg.") === 0) {
|
||||
var parts = to.substring(4);
|
||||
var msgPropParts = parts.split(".");
|
||||
try {
|
||||
@@ -58,17 +78,17 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
if (++depth === propertyParts.length) {
|
||||
if (node.action === "change") {
|
||||
if (rule.t === "change") {
|
||||
if (typeof obj[i] === "string") {
|
||||
obj[i] = obj[i].replace(node.re, node.to);
|
||||
obj[i] = obj[i].replace(rule.from, rule.to);
|
||||
}
|
||||
} else if (node.action === "replace") {
|
||||
} else if (rule.t === "set") {
|
||||
if (typeof to === "undefined") {
|
||||
delete(obj[i]);
|
||||
} else {
|
||||
obj[i] = to;
|
||||
}
|
||||
} else if (node.action === "delete") {
|
||||
} else if (rule.t === "delete") {
|
||||
delete(obj[i]);
|
||||
}
|
||||
} else {
|
||||
@@ -83,8 +103,18 @@ module.exports = function(RED) {
|
||||
}
|
||||
}, msg);
|
||||
} catch (err) {}
|
||||
node.send(msg);
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
var node = this;
|
||||
this.on('input', function(msg) {
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
msg = applyRule(msg,this.rules[i]);
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("change", ChangeNode);
|
||||
};
|
||||
|
||||
@@ -27,21 +27,24 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
|
||||
this.on('input', function (msg) {
|
||||
var n = Number(msg.payload);
|
||||
if (!isNaN(n)) {
|
||||
if (node.action == "clamp") {
|
||||
if (n < node.minin) { n = node.minin; }
|
||||
if (n > node.maxin) { n = node.maxin; }
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
var n = Number(msg.payload);
|
||||
if (!isNaN(n)) {
|
||||
if (node.action == "clamp") {
|
||||
if (n < node.minin) { n = node.minin; }
|
||||
if (n > node.maxin) { n = node.maxin; }
|
||||
}
|
||||
if (node.action == "roll") {
|
||||
if (n >= node.maxin) { n = (n - node.minin) % (node.maxin - node.minin) + node.minin; }
|
||||
if (n < node.minin) { n = (n - node.minin) % (node.maxin - node.minin) + node.maxin; }
|
||||
}
|
||||
msg.payload = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
|
||||
if (node.round) { msg.payload = Math.round(msg.payload); }
|
||||
node.send(msg);
|
||||
}
|
||||
if (node.action == "roll") {
|
||||
if (n >= node.maxin) { n = (n - node.minin) % (node.maxin - node.minin) + node.minin; }
|
||||
if (n < node.minin) { n = (n - node.minin) % (node.maxin - node.minin) + node.maxin; }
|
||||
}
|
||||
msg.payload = ((n - node.minin) / (node.maxin - node.minin) * (node.maxout - node.minout)) + node.minout;
|
||||
if (node.round) { msg.payload = Math.round(msg.payload); }
|
||||
node.send(msg);
|
||||
else { node.log("Not a number: "+msg.payload); }
|
||||
}
|
||||
else { node.log("Not a number: "+msg.payload); }
|
||||
else { node.send(msg); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("range", RangeNode);
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = function(RED) {
|
||||
"use strict";
|
||||
function CSVNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.template = n.temp.split(",");
|
||||
this.template = (n.temp || "").split(",");
|
||||
this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
|
||||
this.quo = '"';
|
||||
this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
|
||||
@@ -72,9 +72,10 @@ module.exports = function(RED) {
|
||||
}
|
||||
ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
|
||||
}
|
||||
node.send({payload:ou});
|
||||
msg.payload = ou;
|
||||
node.send(msg);
|
||||
}
|
||||
catch(e) { node.log(e); }
|
||||
catch(e) { node.error(e,msg); }
|
||||
}
|
||||
else if (typeof msg.payload == "string") { // convert CSV string to object
|
||||
try {
|
||||
@@ -84,27 +85,28 @@ module.exports = function(RED) {
|
||||
var o = {}; // output object to build up
|
||||
var a = []; // output array is needed for multiline option
|
||||
var first = true; // is this the first line
|
||||
var line = msg.payload;
|
||||
var tmp = "";
|
||||
|
||||
// For now we are just going to assume that any \r or \n means an end of line...
|
||||
// got to be a weird csv that has singleton \r \n in it for another reason...
|
||||
|
||||
// Now process the whole file/line
|
||||
for (var i = 0; i < msg.payload.length; i++) {
|
||||
for (var i = 0; i < line.length; i++) {
|
||||
if ((node.hdrin === true) && first) { // if the template is in the first line
|
||||
if ((msg.payload[i] === "\n")||(msg.payload[i] === "\r")) { // look for first line break
|
||||
if ((line[i] === "\n")||(line[i] === "\r")) { // look for first line break
|
||||
node.template = clean(tmp.split(node.sep));
|
||||
first = false;
|
||||
}
|
||||
else { tmp += msg.payload[i]; }
|
||||
else { tmp += line[i]; }
|
||||
}
|
||||
else {
|
||||
if (msg.payload[i] === node.quo) { // if it's a quote toggle inside or outside
|
||||
if (line[i] === node.quo) { // if it's a quote toggle inside or outside
|
||||
f = !f;
|
||||
if (msg.payload[i-1] === node.quo) { k[j] += '\"'; } // if it's a quotequote then it's actually a quote
|
||||
if ((msg.payload[i-1] !== node.sep) && (msg.payload[i+1] !== node.sep)) { k[j] += msg.payload[i]; }
|
||||
if (line[i-1] === node.quo) { k[j] += '\"'; } // if it's a quotequote then it's actually a quote
|
||||
//if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; }
|
||||
}
|
||||
else if ((msg.payload[i] === node.sep) && f) { // if we are outside of quote (ie valid separator
|
||||
else if ((line[i] === node.sep) && f) { // if we are outside of quote (ie valid separator
|
||||
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
|
||||
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "" ) ) {
|
||||
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
|
||||
@@ -113,15 +115,20 @@ module.exports = function(RED) {
|
||||
j += 1;
|
||||
k[j] = "";
|
||||
}
|
||||
else if (f && ((msg.payload[i] === "\n") || (msg.payload[i] === "\r"))) { // handle multiple lines
|
||||
else if (f && ((line[i] === "\n") || (line[i] === "\r"))) { // handle multiple lines
|
||||
//console.log(j,k,o,k[j]);
|
||||
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
|
||||
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
|
||||
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
|
||||
else { k[j].replace(/\r$/,''); }
|
||||
o[node.template[j]] = k[j];
|
||||
}
|
||||
if (JSON.stringify(o) !== "{}") { // don't send empty objects
|
||||
if (node.multi === "one") { node.send({payload:o}); } // either send
|
||||
if (node.multi === "one") {
|
||||
var newMessage = RED.util.cloneMessage(msg);
|
||||
newMessage.payload = o;
|
||||
node.send(newMessage); // either send
|
||||
}
|
||||
else { a.push(o); } // or add to the array
|
||||
}
|
||||
j = 0;
|
||||
@@ -129,7 +136,7 @@ module.exports = function(RED) {
|
||||
o = {};
|
||||
}
|
||||
else { // just add to the part of the message
|
||||
k[j] += msg.payload[i];
|
||||
k[j] += line[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,17 +148,24 @@ module.exports = function(RED) {
|
||||
else { k[j].replace(/\r$/,''); }
|
||||
o[node.template[j]] = k[j];
|
||||
}
|
||||
msg.payload = o;
|
||||
if (JSON.stringify(o) !== "{}") { // don't send empty objects
|
||||
if (node.multi === "one") { node.send({payload:o}); } // either send
|
||||
if (node.multi === "one") {
|
||||
var newMessage = RED.util.cloneMessage(msg);
|
||||
newMessage.payload = o;
|
||||
node.send(newMessage); // either send
|
||||
}
|
||||
else { a.push(o); } // or add to the aray
|
||||
}
|
||||
if (node.multi !== "one") { node.send({payload:a}); } // finally send the array
|
||||
if (node.multi !== "one") {
|
||||
msg.payload = a;
|
||||
node.send(msg); // finally send the array
|
||||
}
|
||||
}
|
||||
catch(e) { node.log(e); }
|
||||
catch(e) { node.error(e,msg); }
|
||||
}
|
||||
else { node.log("This node only handles csv strings or js objects."); }
|
||||
else { node.warn("This node only handles csv strings or js objects."); }
|
||||
}
|
||||
else { node.send(msg); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("csv",CSVNode);
|
||||
|
||||
@@ -25,35 +25,39 @@ module.exports = function(RED) {
|
||||
this.as = n.as || "single";
|
||||
var node = this;
|
||||
this.on("input", function(msg) {
|
||||
try {
|
||||
var $ = cheerio.load(msg.payload);
|
||||
var pay = [];
|
||||
$(node.tag).each(function() {
|
||||
if (node.as === "multi") {
|
||||
var pay2 = null;
|
||||
if (node.ret === "html") { pay2 = $(this).html(); }
|
||||
if (node.ret === "text") { pay2 = $(this).text(); }
|
||||
//if (node.ret === "attr") { pay2 = $(this)[0]["attribs"]; }
|
||||
//if (node.ret === "val") { pay2 = $(this).val(); }
|
||||
if (pay2) {
|
||||
msg.payload = pay2;
|
||||
node.send(msg);
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
try {
|
||||
var $ = cheerio.load(msg.payload);
|
||||
var pay = [];
|
||||
$(node.tag).each(function() {
|
||||
if (node.as === "multi") {
|
||||
var pay2 = null;
|
||||
if (node.ret === "html") { pay2 = $(this).html(); }
|
||||
if (node.ret === "text") { pay2 = $(this).text(); }
|
||||
//if (node.ret === "attr") { pay2 = $(this)[0]["attribs"]; }
|
||||
//if (node.ret === "val") { pay2 = $(this).val(); }
|
||||
/* istanbul ignore else */
|
||||
if (pay2) {
|
||||
msg.payload = pay2;
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
if (node.as === "single") {
|
||||
if (node.ret === "html") { pay.push( $(this).html() ); }
|
||||
if (node.ret === "text") { pay.push( $(this).text() ); }
|
||||
//if (node.ret === "attr") { pay.push( $(this)[0]["attribs"] ); }
|
||||
//if (node.ret === "val") { pay.push( $(this).val() ); }
|
||||
}
|
||||
});
|
||||
if ((node.as === "single") && (pay.length !== 0)) {
|
||||
msg.payload = pay;
|
||||
node.send(msg);
|
||||
}
|
||||
if (node.as === "single") {
|
||||
if (node.ret === "html") { pay.push( $(this).html() ); }
|
||||
if (node.ret === "text") { pay.push( $(this).text() ); }
|
||||
//if (node.ret === "attr") { pay.push( $(this)[0]["attribs"] ); }
|
||||
//if (node.ret === "val") { pay.push( $(this).val() ); }
|
||||
}
|
||||
});
|
||||
if ((node.as === "single") && (pay.length !== 0)) {
|
||||
msg.payload = pay;
|
||||
node.send(msg);
|
||||
} catch (error) {
|
||||
node.error(error.message,msg);
|
||||
}
|
||||
} catch (error) {
|
||||
node.log('Error: '+error.message);
|
||||
}
|
||||
else { node.send(msg); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("html",CheerioNode);
|
||||
|
||||
@@ -28,18 +28,18 @@ module.exports = function(RED) {
|
||||
msg.payload = JSON.parse(msg.payload);
|
||||
node.send(msg);
|
||||
}
|
||||
catch(e) { node.log(e+ "\n"+msg.payload); }
|
||||
catch(e) { node.error(e.message,msg); }
|
||||
}
|
||||
else if (typeof msg.payload === "object") {
|
||||
if (!Buffer.isBuffer(msg.payload) ) {
|
||||
if (!util.isArray(msg.payload)) {
|
||||
msg.payload = JSON.stringify(msg.payload);
|
||||
node.send(msg);
|
||||
}
|
||||
if ((!Buffer.isBuffer(msg.payload)) && (!util.isArray(msg.payload))) {
|
||||
msg.payload = JSON.stringify(msg.payload);
|
||||
node.send(msg);
|
||||
}
|
||||
else { node.warn("Dropped: "+msg.payload); }
|
||||
}
|
||||
else { node.log("dropped: "+msg.payload); }
|
||||
else { node.warn("Dropped: "+msg.payload); }
|
||||
}
|
||||
else { node.send(msg); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("json",JSONNode);
|
||||
|
||||
@@ -33,15 +33,16 @@ module.exports = function(RED) {
|
||||
}
|
||||
else if (typeof msg.payload == "string") {
|
||||
parseString(msg.payload, {strict:true,async:true,attrkey:node.attrkey,charkey:node.charkey}, function (err, result) {
|
||||
if (err) { node.error(err); }
|
||||
if (err) { node.error(err, msg); }
|
||||
else {
|
||||
msg.payload = result;
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
else { node.log("This node only handles xml strings or js objects."); }
|
||||
else { node.warn("This node only handles xml strings or js objects."); }
|
||||
}
|
||||
else { node.send(msg); } // If no payload - just pass it on.
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("xml",XMLNode);
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
access_token: {type: "password"},
|
||||
access_token_secret: {type:"password"}
|
||||
},
|
||||
|
||||
label: function() {
|
||||
return this.screen_name;
|
||||
},
|
||||
@@ -177,11 +176,10 @@
|
||||
$("#node-input-tags-row").show();
|
||||
$("#node-input-tags-label").html("for");
|
||||
$("#node-input-tags").attr("placeholder","comma-separated words, @ids, #hashtags");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
$("#node-input-user").change();
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -218,7 +218,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
stream.on('limit', function(tweet) {
|
||||
node.log("tweet rate limit hit");
|
||||
node.warn("tweet rate limit hit");
|
||||
});
|
||||
stream.on('error', function(tweet,rc) {
|
||||
if (rc == 420) {
|
||||
@@ -280,48 +280,51 @@ module.exports = function(RED) {
|
||||
access_token_secret: credentials.access_token_secret
|
||||
});
|
||||
node.on("input", function(msg) {
|
||||
node.status({fill:"blue",shape:"dot",text:"tweeting"});
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
node.status({fill:"blue",shape:"dot",text:"tweeting"});
|
||||
|
||||
if (msg.payload.length > 140) {
|
||||
msg.payload = msg.payload.slice(0,139);
|
||||
node.warn("Tweet greater than 140 : truncated");
|
||||
}
|
||||
if (msg.payload.length > 140) {
|
||||
msg.payload = msg.payload.slice(0,139);
|
||||
node.warn("Tweet greater than 140 : truncated");
|
||||
}
|
||||
|
||||
if (msg.media && Buffer.isBuffer(msg.media)) {
|
||||
var apiUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json";
|
||||
var signedUrl = oa.signUrl(apiUrl,
|
||||
credentials.access_token,
|
||||
credentials.access_token_secret,
|
||||
"POST");
|
||||
if (msg.media && Buffer.isBuffer(msg.media)) {
|
||||
var apiUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json";
|
||||
var signedUrl = oa.signUrl(apiUrl,
|
||||
credentials.access_token,
|
||||
credentials.access_token_secret,
|
||||
"POST");
|
||||
|
||||
var r = request.post(signedUrl,function(err,httpResponse,body) {
|
||||
if (err) {
|
||||
node.error(err.toString());
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
} else {
|
||||
var response = JSON.parse(body);
|
||||
if (response.errors) {
|
||||
var errorList = response.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
|
||||
node.error("tweet failed: "+errorList);
|
||||
var r = request.post(signedUrl,function(err,httpResponse,body) {
|
||||
if (err) {
|
||||
node.error(err,msg);
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
} else {
|
||||
node.status({});
|
||||
var response = JSON.parse(body);
|
||||
if (response.errors) {
|
||||
var errorList = response.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
|
||||
node.error("Send tweet failed: "+errorList,msg);
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
} else {
|
||||
node.status({});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var form = r.form();
|
||||
form.append("status",msg.payload);
|
||||
form.append("media[]",msg.media,{filename:"image"});
|
||||
});
|
||||
var form = r.form();
|
||||
form.append("status",msg.payload);
|
||||
form.append("media[]",msg.media,{filename:"image"});
|
||||
|
||||
} else {
|
||||
twit.updateStatus(msg.payload, function (err, data) {
|
||||
if (err) {
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
node.error(err);
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
} else {
|
||||
twit.updateStatus(msg.payload, function (err, data) {
|
||||
if (err) {
|
||||
node.status({fill:"red",shape:"ring",text:"failed"});
|
||||
node.error(err,msg);
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
}
|
||||
else { node.warn("No payload to tweet"); }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -367,8 +370,8 @@ module.exports = function(RED) {
|
||||
credentials.oauth_verifier,
|
||||
function(error, oauth_access_token, oauth_access_token_secret, results){
|
||||
if (error){
|
||||
console.log(error);
|
||||
res.send("yeah something broke.");
|
||||
RED.log.error(error);
|
||||
res.send("something in twitter oauth broke.");
|
||||
} else {
|
||||
credentials = {};
|
||||
credentials.access_token = oauth_access_token;
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
defaults: {
|
||||
server: {value:"smtp.gmail.com",required:true},
|
||||
port: {value:"465",required:true},
|
||||
name: {value:"",required:true},
|
||||
name: {value:""},
|
||||
dname: {value:""}
|
||||
},
|
||||
credentials: {
|
||||
@@ -95,7 +95,6 @@
|
||||
password: {type: "password"},
|
||||
global: { type:"boolean"}
|
||||
},
|
||||
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "envelope.png",
|
||||
|
||||
@@ -69,39 +69,42 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
this.on("input", function(msg) {
|
||||
if (smtpTransport) {
|
||||
node.status({fill:"blue",shape:"dot",text:"sending"});
|
||||
if (msg.to && node.name && (msg.to !== node.name)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
var sendopts = { from: node.userid }; // sender address
|
||||
sendopts.to = msg.to || node.name; // comma separated list of addressees
|
||||
sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; // subject line
|
||||
if (Buffer.isBuffer(msg.payload)) { // if it's a buffer in the payload then auto create an attachment instead
|
||||
sendopts.attachments = [ { content: msg.payload, filename:(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin") } ];
|
||||
if (msg.hasOwnProperty("headers") && msg.headers.hasOwnProperty("content-type")) {
|
||||
sendopts.attachments[0].contentType = msg.headers["content-type"];
|
||||
if (msg.hasOwnProperty("payload")) {
|
||||
if (smtpTransport) {
|
||||
node.status({fill:"blue",shape:"dot",text:"sending"});
|
||||
if (msg.to && node.name && (msg.to !== node.name)) {
|
||||
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
// Create some body text..
|
||||
sendopts.text = "Your file from Node-RED is attached : "+(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin")+ (msg.hasOwnProperty("description") ? "\n\n"+msg.description : "");
|
||||
}
|
||||
else {
|
||||
var payload = RED.util.ensureString(msg.payload);
|
||||
sendopts.text = payload; // plaintext body
|
||||
if (/<[a-z][\s\S]*>/i.test(payload)) { sendopts.html = payload; } // html body
|
||||
if (msg.attachments) { sendopts.attachments = msg.attachments; } // add attachments
|
||||
}
|
||||
smtpTransport.sendMail(sendopts, function(error, info) {
|
||||
if (error) {
|
||||
node.error(error);
|
||||
node.status({fill:"red",shape:"ring",text:"send failed"});
|
||||
} else {
|
||||
node.log("Message sent: " + info.response);
|
||||
node.status({});
|
||||
var sendopts = { from: node.userid }; // sender address
|
||||
sendopts.to = node.name || msg.to; // comma separated list of addressees
|
||||
sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; // subject line
|
||||
if (Buffer.isBuffer(msg.payload)) { // if it's a buffer in the payload then auto create an attachment instead
|
||||
sendopts.attachments = [ { content: msg.payload, filename:(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin") } ];
|
||||
if (msg.hasOwnProperty("headers") && msg.headers.hasOwnProperty("content-type")) {
|
||||
sendopts.attachments[0].contentType = msg.headers["content-type"];
|
||||
}
|
||||
// Create some body text..
|
||||
sendopts.text = "Your file from Node-RED is attached : "+(msg.filename.replace(/^.*[\\\/]/, '') || "file.bin")+ (msg.hasOwnProperty("description") ? "\n\n"+msg.description : "");
|
||||
}
|
||||
});
|
||||
else {
|
||||
var payload = RED.util.ensureString(msg.payload);
|
||||
sendopts.text = payload; // plaintext body
|
||||
if (/<[a-z][\s\S]*>/i.test(payload)) { sendopts.html = payload; } // html body
|
||||
if (msg.attachments) { sendopts.attachments = msg.attachments; } // add attachments
|
||||
}
|
||||
smtpTransport.sendMail(sendopts, function(error, info) {
|
||||
if (error) {
|
||||
node.error(error,msg);
|
||||
node.status({fill:"red",shape:"ring",text:"send failed"});
|
||||
} else {
|
||||
node.log("Message sent: " + info.response);
|
||||
node.status({});
|
||||
}
|
||||
});
|
||||
}
|
||||
else { node.warn("No Email credentials found. See info panel."); }
|
||||
}
|
||||
else { node.warn("No Email credentials found. See info panel."); }
|
||||
else { node.warn("No payload to send"); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("e-mail",EmailNode,{
|
||||
@@ -170,9 +173,9 @@ module.exports = function(RED) {
|
||||
imap.once('ready', function() {
|
||||
node.status({fill:"blue",shape:"dot",text:"fetching"});
|
||||
var pay = {};
|
||||
imap.openBox('INBOX', true, function(err, box) {
|
||||
imap.openBox('INBOX', false, function(err, box) {
|
||||
if (box.messages.total > 0) {
|
||||
var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
|
||||
var f = imap.seq.fetch(box.messages.total + ':*', { markSeen:true, bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
|
||||
f.on('message', function(msg, seqno) {
|
||||
node.log('message: #'+ seqno);
|
||||
var prefix = '(#' + seqno + ') ';
|
||||
|
||||
@@ -57,10 +57,11 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
tail.stderr.on("data", function(data) {
|
||||
node.warn(data.toString());
|
||||
node.error(data.toString());
|
||||
});
|
||||
|
||||
this.on("close", function() {
|
||||
/* istanbul ignore else */
|
||||
if (tail) { tail.kill(); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
align: "right",
|
||||
label: function() {
|
||||
if (this.overwriteFile === "delete") { return this.name||"delete "+this.filename; }
|
||||
else { return this.name||this.filename; }
|
||||
else { return this.name||this.filename||"file"; }
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@@ -110,7 +110,7 @@
|
||||
outputs:1,
|
||||
icon: "file.png",
|
||||
label: function() {
|
||||
return this.name||this.filename;
|
||||
return this.name||this.filename||"file";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
||||
@@ -20,29 +20,26 @@ module.exports = function(RED) {
|
||||
|
||||
function FileNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.filename = n.filename || "";
|
||||
this.filename = n.filename;
|
||||
this.appendNewline = n.appendNewline;
|
||||
this.overwriteFile = n.overwriteFile.toString();
|
||||
var node = this;
|
||||
this.on("input",function(msg) {
|
||||
var filename;
|
||||
if (msg.filename) {
|
||||
if (n.filename && (n.filename !== msg.filename)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
filename = msg.filename;
|
||||
node.status({fill:"grey",shape:"dot",text:msg.filename});
|
||||
} else {
|
||||
filename = this.filename;
|
||||
var filename = this.filename || msg.filename || "";
|
||||
if (msg.filename && n.filename && (n.filename !== msg.filename)) {
|
||||
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
if (!this.filename) {
|
||||
node.status({fill:"grey",shape:"dot",text:filename});
|
||||
}
|
||||
if (filename === "") {
|
||||
node.warn('No filename specified');
|
||||
} else if (msg.hasOwnProperty('delete')) {
|
||||
node.warn("Deprecated: please use specific delete option in config dialog.");
|
||||
fs.unlink(filename, function (err) {
|
||||
if (err) { node.warn('Failed to delete file : '+err); }
|
||||
});
|
||||
} else if (typeof msg.payload != "undefined") {
|
||||
} else if (msg.hasOwnProperty('delete')) { // remove warning at some point in future
|
||||
node.warn("Warning: Invalid delete. Please use specific delete option in config dialog.");
|
||||
//fs.unlink(filename, function (err) {
|
||||
//if (err) { node.error('Failed to delete file : '+err,msg); }
|
||||
//});
|
||||
} else if (msg.payload && (typeof msg.payload != "undefined")) {
|
||||
var data = msg.payload;
|
||||
if ((typeof data === "object")&&(!Buffer.isBuffer(data))) {
|
||||
data = JSON.stringify(data);
|
||||
@@ -53,13 +50,13 @@ module.exports = function(RED) {
|
||||
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
|
||||
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
|
||||
fs.writeFile(filename, data, "binary", function (err) {
|
||||
if (err) { node.warn('Failed to write to file : '+err); }
|
||||
if (err) { node.error('Failed to write to file : '+err,msg); }
|
||||
else if (RED.settings.verbose) { node.log('wrote to file: '+filename); }
|
||||
});
|
||||
}
|
||||
else if (this.overwriteFile === "delete") {
|
||||
fs.unlink(filename, function (err) {
|
||||
if (err) { node.warn('Failed to delete file : '+err); }
|
||||
if (err) { node.error('Failed to delete file : '+err,msg); }
|
||||
else if (RED.settings.verbose) { node.log("deleted file: "+filename); }
|
||||
});
|
||||
}
|
||||
@@ -67,7 +64,7 @@ module.exports = function(RED) {
|
||||
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
|
||||
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
|
||||
fs.appendFile(filename, data, "binary", function (err) {
|
||||
if (err) { node.warn('Failed to append to file : '+err); }
|
||||
if (err) { node.error('Failed to append to file : '+err,msg); }
|
||||
else if (RED.settings.verbose) { node.log('appended to file: '+filename); }
|
||||
});
|
||||
}
|
||||
@@ -80,7 +77,7 @@ module.exports = function(RED) {
|
||||
function FileInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.filename = n.filename || "";
|
||||
this.filename = n.filename;
|
||||
this.format = n.format;
|
||||
var node = this;
|
||||
var options = {};
|
||||
@@ -88,14 +85,12 @@ module.exports = function(RED) {
|
||||
options['encoding'] = this.format;
|
||||
}
|
||||
this.on("input",function(msg) {
|
||||
var filename;
|
||||
if (msg.filename) {
|
||||
if (n.filename && (n.filename !== msg.filename)) {
|
||||
node.warn("Deprecated: msg properties should not override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
filename = msg.filename;
|
||||
} else {
|
||||
filename = this.filename;
|
||||
var filename = this.filename || msg.filename || "";
|
||||
if (msg.filename && n.filename && (n.filename !== msg.filename)) {
|
||||
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props");
|
||||
}
|
||||
if (!this.filename) {
|
||||
node.status({fill:"grey",shape:"dot",text:filename});
|
||||
}
|
||||
if (filename === "") {
|
||||
node.warn('No filename specified');
|
||||
@@ -103,7 +98,7 @@ module.exports = function(RED) {
|
||||
msg.filename = filename;
|
||||
fs.readFile(filename,options,function(err,data) {
|
||||
if (err) {
|
||||
node.warn(err);
|
||||
node.error(err,msg);
|
||||
msg.error = err;
|
||||
delete msg.payload;
|
||||
} else {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var util = require("util");
|
||||
var redis = require("redis");
|
||||
|
||||
var hashFieldRE = /^([^=]+)=(.*)$/;
|
||||
@@ -29,10 +28,10 @@ module.exports = function(RED) {
|
||||
if (!connections[id]) {
|
||||
connections[id] = redis.createClient(port,host);
|
||||
connections[id].on("error",function(err) {
|
||||
util.log("[redis] "+err);
|
||||
RED.log.error(err);
|
||||
});
|
||||
connections[id].on("connect",function() {
|
||||
util.log("[redis] connected to "+host+":"+port);
|
||||
if (RED.settings.verbose) { RED.log.info("connected to "+host+":"+port); }
|
||||
});
|
||||
connections[id]._id = id;
|
||||
connections[id]._nodeCount = 0;
|
||||
|
||||
@@ -76,7 +76,7 @@ module.exports = function(RED) {
|
||||
if (msg.collection) {
|
||||
coll = db.collection(msg.collection);
|
||||
} else {
|
||||
node.error("No collection defined");
|
||||
node.error("No collection defined",msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -89,13 +89,13 @@ module.exports = function(RED) {
|
||||
}
|
||||
coll.save(msg.payload,function(err, item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
node.error(err,msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
coll.save(msg,function(err, item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
node.error(err,msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,13 +106,13 @@ module.exports = function(RED) {
|
||||
}
|
||||
coll.insert(msg.payload, function(err, item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
node.error(err,msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
coll.insert(msg, function(err,item) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
node.error(err,msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -129,13 +129,13 @@ module.exports = function(RED) {
|
||||
|
||||
coll.update(query, payload, options, function(err, item) {
|
||||
if (err) {
|
||||
node.error(err + " " + payload);
|
||||
node.error(err,msg);
|
||||
}
|
||||
});
|
||||
} else if (node.operation === "delete") {
|
||||
coll.remove(msg.payload, function(err, items) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
node.error(err,msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -184,7 +184,16 @@ module.exports = function(RED) {
|
||||
if (node.operation === "find") {
|
||||
msg.projection = msg.projection || {};
|
||||
var selector = ensureValidSelectorObject(msg.payload);
|
||||
coll.find(selector,msg.projection).sort(msg.sort).limit(msg.limit).skip(msg.skip).toArray(function(err, items) {
|
||||
var limit = msg.limit;
|
||||
if (typeof limit === "string" && !isNaN(limit)) {
|
||||
limit = Number(limit);
|
||||
}
|
||||
var skip = msg.skip;
|
||||
if (typeof skip === "string" && !isNaN(skip)) {
|
||||
skip = Number(skip);
|
||||
}
|
||||
|
||||
coll.find(selector,msg.projection).sort(msg.sort).limit(limit).skip(skip).toArray(function(err, items) {
|
||||
if (err) {
|
||||
node.error(err);
|
||||
} else {
|
||||
|
||||
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red",
|
||||
"version" : "0.10.2",
|
||||
"version" : "0.10.6",
|
||||
"description" : "A visual tool for wiring the Internet of Things",
|
||||
"homepage" : "http://nodered.org",
|
||||
"license" : "Apache",
|
||||
@@ -13,6 +13,10 @@
|
||||
"start": "node red.js",
|
||||
"test": "./node_modules/.bin/grunt"
|
||||
},
|
||||
"bin" : {
|
||||
"node-red": "./red.js",
|
||||
"node-red-pi": "bin/node-red-pi"
|
||||
},
|
||||
"contributors": [
|
||||
{"name": "Nick O'Leary"},
|
||||
{"name": "Dave Conway-Jones"}
|
||||
@@ -55,6 +59,9 @@
|
||||
"passport-oauth2-client-password":"0.1.2",
|
||||
"oauth2orize":"1.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt":"0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "0.4.5",
|
||||
"grunt-cli": "0.1.13",
|
||||
@@ -66,6 +73,6 @@
|
||||
"supertest": "0.15.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
"node": ">=0.8 <0.12"
|
||||
}
|
||||
}
|
||||
|
||||
24
public/ace/LICENSE
Normal file
24
public/ace/LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2010, Ajax.org B.V.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Ajax.org B.V. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
11
public/ace/ace.js
Normal file
11
public/ace/ace.js
Normal file
File diff suppressed because one or more lines are too long
5
public/ace/ext-language_tools.js
Normal file
5
public/ace/ext-language_tools.js
Normal file
File diff suppressed because one or more lines are too long
5
public/ace/ext-searchbox.js
Normal file
5
public/ace/ext-searchbox.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/mode-handlebars.js
Normal file
1
public/ace/mode-handlebars.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/mode-html.js
Normal file
1
public/ace/mode-html.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/mode-javascript.js
Normal file
1
public/ace/mode-javascript.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/mode-json.js
Normal file
1
public/ace/mode-json.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/mode-markdown.js
Normal file
1
public/ace/mode-markdown.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/mode-yaml.js
Normal file
1
public/ace/mode-yaml.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(\:(?:\s+|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*\:(?:\s+|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:"[|>][-+\\d\\s]*$",next:"qqstring"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"}],qqstring:[{token:"string",regex:"(?=(?:(?:\\\\.)|(?:[^:]))*?:)",next:"start"},{token:"string",regex:".+"}]}};r.inherits(s,i),t.YamlHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u};r.inherits(a,i),function(){this.lineCommentStart="#",this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a})
|
||||
1
public/ace/snippets/handlebars.js
Normal file
1
public/ace/snippets/handlebars.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/handlebars",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="handlebars"})
|
||||
1
public/ace/snippets/html.js
Normal file
1
public/ace/snippets/html.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/snippets/javascript.js
Normal file
1
public/ace/snippets/javascript.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/javascript",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# Get Elements\nsnippet gett\n getElementsBy${1:TagName}(\'${2}\')${3}\n# Get Element\nsnippet get\n getElementBy${1:Id}(\'${2}\')${3}\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# singleton\nsnippet sing\n function ${1:Singleton} (${2:argument}) {\n var instance;\n $1 = function $1($2) {\n return instance;\n };\n $1.prototype = this;\n instance = new $1();\n instance.constructor = $1;\n\n ${3:// code ...}\n\n return instance;\n }\n# class\nsnippet class\nregex /^\\s*/clas{0,2}/\n var ${1:class} = function(${20}) {\n $40$0\n };\n \n (function() {\n ${60:this.prop = ""}\n }).call(${1:class}.prototype);\n \n exports.${1:class} = ${1:class};\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n\n\n#modules\nsnippet def\n ace.define(function(require, exports, module) {\n "use strict";\n var ${1/.*\\///} = require("${1}");\n \n $TM_SELECTED_TEXT\n });\nsnippet req\nguard ^\\s*\n var ${1/.*\\///} = require("${1}");\n $0\nsnippet requ\nguard ^\\s*\n var ${1/.*\\/(.)/\\u$1/} = require("${1}").${1/.*\\/(.)/\\u$1/};\n $0\n',t.scope="javascript"})
|
||||
1
public/ace/snippets/markdown.js
Normal file
1
public/ace/snippets/markdown.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/markdown",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Markdown\n\n# Includes octopress (http://octopress.org/) snippets\n\nsnippet [\n [${1:text}](http://${2:address} "${3:title}")\nsnippet [*\n [${1:link}](${2:`@*`} "${3:title}")${4}\n\nsnippet [:\n [${1:id}]: http://${2:url} "${3:title}"\nsnippet [:*\n [${1:id}]: ${2:`@*`} "${3:title}"\n\nsnippet \nsnippet ${4}\n\nsnippet ![:\n ![${1:id}]: ${2:url} "${3:title}"\nsnippet ![:*\n ![${1:id}]: ${2:`@*`} "${3:title}"\n\nsnippet ===\nregex /^/=+/=*//\n ${PREV_LINE/./=/g}\n \n ${0}\nsnippet ---\nregex /^/-+/-*//\n ${PREV_LINE/./-/g}\n \n ${0}\nsnippet blockquote\n {% blockquote %}\n ${1:quote}\n {% endblockquote %}\n\nsnippet blockquote-author\n {% blockquote ${1:author}, ${2:title} %}\n ${3:quote}\n {% endblockquote %}\n\nsnippet blockquote-link\n {% blockquote ${1:author} ${2:URL} ${3:link_text} %}\n ${4:quote}\n {% endblockquote %}\n\nsnippet bt-codeblock-short\n ```\n ${1:code_snippet}\n ```\n\nsnippet bt-codeblock-full\n ``` ${1:language} ${2:title} ${3:URL} ${4:link_text}\n ${5:code_snippet}\n ```\n\nsnippet codeblock-short\n {% codeblock %}\n ${1:code_snippet}\n {% endcodeblock %}\n\nsnippet codeblock-full\n {% codeblock ${1:title} lang:${2:language} ${3:URL} ${4:link_text} %}\n ${5:code_snippet}\n {% endcodeblock %}\n\nsnippet gist-full\n {% gist ${1:gist_id} ${2:filename} %}\n\nsnippet gist-short\n {% gist ${1:gist_id} %}\n\nsnippet img\n {% img ${1:class} ${2:URL} ${3:width} ${4:height} ${5:title_text} ${6:alt_text} %}\n\nsnippet youtube\n {% youtube ${1:video_id} %}\n\n# The quote should appear only once in the text. It is inherently part of it.\n# See http://octopress.org/docs/plugins/pullquote/ for more info.\n\nsnippet pullquote\n {% pullquote %}\n ${1:text} {" ${2:quote} "} ${3:text}\n {% endpullquote %}\n',t.scope="markdown"})
|
||||
1
public/ace/snippets/text.js
Normal file
1
public/ace/snippets/text.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/text",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="text"})
|
||||
1
public/ace/snippets/yaml.js
Normal file
1
public/ace/snippets/yaml.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/snippets/yaml",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="yaml"})
|
||||
1
public/ace/theme-chrome.js
Normal file
1
public/ace/theme-chrome.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/chrome",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-chrome",t.cssText='.ace-chrome .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-chrome .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-chrome {background-color: #FFFFFF;color: black;}.ace-chrome .ace_cursor {color: black;}.ace-chrome .ace_invisible {color: rgb(191, 191, 191);}.ace-chrome .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-chrome .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-chrome .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-chrome .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-chrome .ace_fold {}.ace-chrome .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-chrome .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-chrome .ace_support.ace_type,.ace-chrome .ace_support.ace_class.ace-chrome .ace_support.ace_other {color: rgb(109, 121, 222);}.ace-chrome .ace_variable.ace_parameter {font-style:italic;color:#FD971F;}.ace-chrome .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-chrome .ace_comment {color: #236e24;}.ace-chrome .ace_comment.ace_doc {color: #236e24;}.ace-chrome .ace_comment.ace_doc.ace_tag {color: #236e24;}.ace-chrome .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-chrome .ace_variable {color: rgb(49, 132, 149);}.ace-chrome .ace_xml-pe {color: rgb(104, 104, 91);}.ace-chrome .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-chrome .ace_heading {color: rgb(12, 7, 255);}.ace-chrome .ace_list {color:rgb(185, 6, 144);}.ace-chrome .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-chrome .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-chrome .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-chrome .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-chrome .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-chrome .ace_gutter-active-line {background-color : #dcdcdc;}.ace-chrome .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-chrome .ace_storage,.ace-chrome .ace_keyword,.ace-chrome .ace_meta.ace_tag {color: rgb(147, 15, 128);}.ace-chrome .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-chrome .ace_string {color: #1A1AA6;}.ace-chrome .ace_entity.ace_other.ace_attribute-name {color: #994409;}.ace-chrome .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
||||
1
public/ace/theme-crimson_editor.js
Normal file
1
public/ace/theme-crimson_editor.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/crimson_editor",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssText='.ace-crimson-editor .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-crimson-editor .ace_gutter-layer {width: 100%;text-align: right;}.ace-crimson-editor .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-crimson-editor {background-color: #FFFFFF;color: rgb(64, 64, 64);}.ace-crimson-editor .ace_cursor {color: black;}.ace-crimson-editor .ace_invisible {color: rgb(191, 191, 191);}.ace-crimson-editor .ace_identifier {color: black;}.ace-crimson-editor .ace_keyword {color: blue;}.ace-crimson-editor .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-crimson-editor .ace_constant.ace_language {color: rgb(255, 156, 0);}.ace-crimson-editor .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-crimson-editor .ace_invalid {text-decoration: line-through;color: rgb(224, 0, 0);}.ace-crimson-editor .ace_fold {}.ace-crimson-editor .ace_support.ace_function {color: rgb(192, 0, 0);}.ace-crimson-editor .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-crimson-editor .ace_support.ace_type,.ace-crimson-editor .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-crimson-editor .ace_keyword.ace_operator {color: rgb(49, 132, 149);}.ace-crimson-editor .ace_string {color: rgb(128, 0, 128);}.ace-crimson-editor .ace_comment {color: rgb(76, 136, 107);}.ace-crimson-editor .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-crimson-editor .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-crimson-editor .ace_constant.ace_numeric {color: rgb(0, 0, 64);}.ace-crimson-editor .ace_variable {color: rgb(0, 64, 128);}.ace-crimson-editor .ace_xml-pe {color: rgb(104, 104, 91);}.ace-crimson-editor .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-crimson-editor .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-crimson-editor .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-crimson-editor .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-crimson-editor .ace_marker-layer .ace_active-line {background: rgb(232, 242, 254);}.ace-crimson-editor .ace_gutter-active-line {background-color : #dcdcdc;}.ace-crimson-editor .ace_meta.ace_tag {color:rgb(28, 2, 255);}.ace-crimson-editor .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-crimson-editor .ace_string.ace_regex {color: rgb(192, 0, 192);}.ace-crimson-editor .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}',t.cssClass="ace-crimson-editor";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
||||
1
public/ace/theme-solarized_light.js
Normal file
1
public/ace/theme-solarized_light.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/solarized_light",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-solarized-light",t.cssText=".ace-solarized-light .ace_gutter {background: #fbf1d3;color: #333}.ace-solarized-light .ace_print-margin {width: 1px;background: #e8e8e8}.ace-solarized-light {background-color: #FDF6E3;color: #586E75}.ace-solarized-light .ace_cursor {color: #000000}.ace-solarized-light .ace_marker-layer .ace_selection {background: rgba(7, 54, 67, 0.09)}.ace-solarized-light.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #FDF6E3;border-radius: 2px}.ace-solarized-light .ace_marker-layer .ace_step {background: rgb(255, 255, 0)}.ace-solarized-light .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgba(147, 161, 161, 0.50)}.ace-solarized-light .ace_marker-layer .ace_active-line {background: #EEE8D5}.ace-solarized-light .ace_gutter-active-line {background-color : #EDE5C1}.ace-solarized-light .ace_marker-layer .ace_selected-word {border: 1px solid #073642}.ace-solarized-light .ace_invisible {color: rgba(147, 161, 161, 0.50)}.ace-solarized-light .ace_keyword,.ace-solarized-light .ace_meta,.ace-solarized-light .ace_support.ace_class,.ace-solarized-light .ace_support.ace_type {color: #859900}.ace-solarized-light .ace_constant.ace_character,.ace-solarized-light .ace_constant.ace_other {color: #CB4B16}.ace-solarized-light .ace_constant.ace_language {color: #B58900}.ace-solarized-light .ace_constant.ace_numeric {color: #D33682}.ace-solarized-light .ace_fold {background-color: #268BD2;border-color: #586E75}.ace-solarized-light .ace_entity.ace_name.ace_function,.ace-solarized-light .ace_entity.ace_name.ace_tag,.ace-solarized-light .ace_support.ace_function,.ace-solarized-light .ace_variable,.ace-solarized-light .ace_variable.ace_language {color: #268BD2}.ace-solarized-light .ace_storage {color: #073642}.ace-solarized-light .ace_string {color: #2AA198}.ace-solarized-light .ace_string.ace_regexp {color: #D30102}.ace-solarized-light .ace_comment,.ace-solarized-light .ace_entity.ace_other.ace_attribute-name {color: #93A1A1}.ace-solarized-light .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHjy8NJ/AAjgA5fzQUmBAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
||||
1
public/ace/theme-tomorrow.js
Normal file
1
public/ace/theme-tomorrow.js
Normal file
@@ -0,0 +1 @@
|
||||
ace.define("ace/theme/tomorrow",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-tomorrow",t.cssText=".ace-tomorrow .ace_gutter {background: #f6f6f6;color: #4D4D4C}.ace-tomorrow .ace_print-margin {width: 1px;background: #f6f6f6}.ace-tomorrow {background-color: #FFFFFF;color: #4D4D4C}.ace-tomorrow .ace_cursor {color: #AEAFAD}.ace-tomorrow .ace_marker-layer .ace_selection {background: #D6D6D6}.ace-tomorrow.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #FFFFFF;border-radius: 2px}.ace-tomorrow .ace_marker-layer .ace_step {background: rgb(255, 255, 0)}.ace-tomorrow .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #D1D1D1}.ace-tomorrow .ace_marker-layer .ace_active-line {background: #EFEFEF}.ace-tomorrow .ace_gutter-active-line {background-color : #dcdcdc}.ace-tomorrow .ace_marker-layer .ace_selected-word {border: 1px solid #D6D6D6}.ace-tomorrow .ace_invisible {color: #D1D1D1}.ace-tomorrow .ace_keyword,.ace-tomorrow .ace_meta,.ace-tomorrow .ace_storage,.ace-tomorrow .ace_storage.ace_type,.ace-tomorrow .ace_support.ace_type {color: #8959A8}.ace-tomorrow .ace_keyword.ace_operator {color: #3E999F}.ace-tomorrow .ace_constant.ace_character,.ace-tomorrow .ace_constant.ace_language,.ace-tomorrow .ace_constant.ace_numeric,.ace-tomorrow .ace_keyword.ace_other.ace_unit,.ace-tomorrow .ace_support.ace_constant,.ace-tomorrow .ace_variable.ace_parameter {color: #F5871F}.ace-tomorrow .ace_constant.ace_other {color: #666969}.ace-tomorrow .ace_invalid {color: #FFFFFF;background-color: #C82829}.ace-tomorrow .ace_invalid.ace_deprecated {color: #FFFFFF;background-color: #8959A8}.ace-tomorrow .ace_fold {background-color: #4271AE;border-color: #4D4D4C}.ace-tomorrow .ace_entity.ace_name.ace_function,.ace-tomorrow .ace_support.ace_function,.ace-tomorrow .ace_variable {color: #4271AE}.ace-tomorrow .ace_support.ace_class,.ace-tomorrow .ace_support.ace_type {color: #C99E00}.ace-tomorrow .ace_heading,.ace-tomorrow .ace_markup.ace_heading,.ace-tomorrow .ace_string {color: #718C00}.ace-tomorrow .ace_entity.ace_name.ace_tag,.ace-tomorrow .ace_entity.ace_other.ace_attribute-name,.ace-tomorrow .ace_meta.ace_tag,.ace-tomorrow .ace_string.ace_regexp,.ace-tomorrow .ace_variable {color: #C82829}.ace-tomorrow .ace_comment {color: #8E908C}.ace-tomorrow .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bdu3f/BwAlfgctduB85QAAAABJRU5ErkJggg==) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)})
|
||||
1
public/ace/worker-html.js
Normal file
1
public/ace/worker-html.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/worker-javascript.js
Normal file
1
public/ace/worker-javascript.js
Normal file
File diff suppressed because one or more lines are too long
1
public/ace/worker-json.js
Normal file
1
public/ace/worker-json.js
Normal file
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<!--
|
||||
Copyright 2013, 2014 IBM Corp.
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -32,11 +32,6 @@
|
||||
<div id="header">
|
||||
<span class="logo"><img src="node-red.png"> <span>Node-RED</span></span>
|
||||
<ul class="header-toolbar hide">
|
||||
<li><span class="deploy-button-group button-group">
|
||||
<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="images/deploy-full-o.png"> <span>Deploy</span></a>
|
||||
<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>
|
||||
</span></li>
|
||||
<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>
|
||||
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
|
||||
<ul>
|
||||
</div>
|
||||
@@ -107,57 +102,6 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="node-help" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="node-help-label" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<h5 id="node-help-label">Keyboard Shortcuts <span style="float: right;"><a href="http://nodered.org/docs" target="_blank">Open help in new window »</a></span></h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table>
|
||||
<tr>
|
||||
<td><span class="help-key">?</span></td><td>Help</td>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">a</span></td><td>Select all nodes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">Space</span></td><td>Toggle sidebar</td>
|
||||
<td><span class="help-key">Shift</span> <span class="help-key">Click</span></td><td>Select all connected nodes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">z</span></td><td>Undo</td>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">Click</span></td><td>Add/remove node from selection</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td><td></td>
|
||||
<td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">x</span></td><td>Cut selected nodes</td>
|
||||
<td></td><td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">c</span></td><td>Copy selected nodes</td>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">v</span></td><td>Paste nodes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">i</span></td><td>Import nodes</td>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">e</span></td><td>Export selected nodes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">+</span></td><td>Zoom in</td>
|
||||
<td><span class="help-key">Ctrl</span> <span class="help-key">-</span></td><td>Zoom out</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4">Mac users can use the <b>⌘ - Cmd</b> key rather than Ctrl key.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-library-save-confirm" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div style="text-align: center; padding-top: 30px;">
|
||||
@@ -211,27 +155,13 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/x-red" data-template-name="export-clipboard-dialog">
|
||||
<div class="form-row">
|
||||
<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>
|
||||
<textarea readonly style="font-family: monospace; font-size: 12px; background:rgb(226, 229, 255); padding-left: 0.5em;" class="input-block-level" id="node-input-export" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
Select the text above and copy to the clipboard with Ctrl-A Ctrl-C.
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="export-library-dialog">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/x-red" data-template-name="import-dialog">
|
||||
<div class="form-row">
|
||||
<label for="node-input-import"><i class="fa fa-clipboard"></i> Nodes:</label>
|
||||
<textarea style="font-family: monospace; font-size: 12px; background:rgb(226, 229, 255); padding-left: 0.5em;" class="input-block-level" id="node-input-import" rows="5" placeholder="Paste nodes here"></textarea>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="subflow">
|
||||
<div class="form-row">
|
||||
@@ -246,6 +176,8 @@
|
||||
<script src="jquery/js/jquery.ui.touch-punch.min.js"></script>
|
||||
<script src="marked/marked.min.js"></script>
|
||||
<script src="orion/built-editor.min.js"></script>
|
||||
<script src="ace/ace.js"></script>
|
||||
<script src="ace/ext-language_tools.js"></script>
|
||||
<script src="d3.v3.min.js"></script>
|
||||
<script src="red/main.js"></script>
|
||||
<script src="red/settings.js"></script>
|
||||
@@ -255,17 +187,21 @@
|
||||
<script src="red/nodes.js"></script>
|
||||
<script src="red/history.js"></script>
|
||||
<script src="red/validators.js"></script>
|
||||
<script src="red/ui/deploy.js"></script>
|
||||
<script src="red/ui/menu.js"></script>
|
||||
<script src="red/ui/keyboard.js"></script>
|
||||
<script src="red/ui/tabs.js"></script>
|
||||
<script src="red/ui/workspaces.js"></script>
|
||||
<script src="red/ui/view.js"></script>
|
||||
<script src="red/ui/sidebar.js"></script>
|
||||
<script src="red/ui/palette.js"></script>
|
||||
<script src="red/ui/tab-info.js"></script>
|
||||
<script src="red/ui/tab-config.js"></script>
|
||||
<script src="red/ui/editor.js"></script>
|
||||
<script src="red/ui/clipboard.js"></script>
|
||||
<script src="red/ui/library.js"></script>
|
||||
<script src="red/ui/notifications.js"></script>
|
||||
<script src="red/ui/subflow.js"></script>
|
||||
<script src="red/ui/touch/radialMenu.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,10 +32,16 @@ RED.history = (function() {
|
||||
pop: function() {
|
||||
var ev = undo_history.pop();
|
||||
var i;
|
||||
var node;
|
||||
var modifiedTabs = {};
|
||||
if (ev) {
|
||||
if (ev.t == 'add') {
|
||||
if (ev.nodes) {
|
||||
for (i=0;i<ev.nodes.length;i++) {
|
||||
node = RED.nodes.node(ev.nodes[i]);
|
||||
if (node.z) {
|
||||
modifiedTabs[node.z] = true;
|
||||
}
|
||||
RED.nodes.remove(ev.nodes[i]);
|
||||
}
|
||||
}
|
||||
@@ -47,20 +53,20 @@ RED.history = (function() {
|
||||
if (ev.workspaces) {
|
||||
for (i=0;i<ev.workspaces.length;i++) {
|
||||
RED.nodes.removeWorkspace(ev.workspaces[i].id);
|
||||
RED.view.removeWorkspace(ev.workspaces[i]);
|
||||
RED.workspaces.remove(ev.workspaces[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflows) {
|
||||
for (i=0;i<ev.subflows.length;i++) {
|
||||
RED.nodes.removeSubflow(ev.subflows[i]);
|
||||
RED.view.removeWorkspace(ev.subflows[i]);
|
||||
RED.workspaces.remove(ev.subflows[i]);
|
||||
}
|
||||
}
|
||||
} else if (ev.t == "delete") {
|
||||
if (ev.workspaces) {
|
||||
for (i=0;i<ev.workspaces.length;i++) {
|
||||
RED.nodes.addWorkspace(ev.workspaces[i]);
|
||||
RED.view.addWorkspace(ev.workspaces[i]);
|
||||
RED.workspaces.add(ev.workspaces[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflow) {
|
||||
@@ -92,22 +98,21 @@ RED.history = (function() {
|
||||
}
|
||||
}
|
||||
if (subflow) {
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+subflow.id) {
|
||||
n.changed = true;
|
||||
n.inputs = subflow.in.length;
|
||||
n.outputs = subflow.out.length;
|
||||
while (n.outputs > n.ports.length) {
|
||||
n.ports.push(n.ports.length);
|
||||
}
|
||||
n.resize = true;
|
||||
n.dirty = true;
|
||||
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
|
||||
n.changed = true;
|
||||
n.inputs = subflow.in.length;
|
||||
n.outputs = subflow.out.length;
|
||||
while (n.outputs > n.ports.length) {
|
||||
n.ports.push(n.ports.length);
|
||||
}
|
||||
n.resize = true;
|
||||
n.dirty = true;
|
||||
});
|
||||
}
|
||||
if (ev.nodes) {
|
||||
for (i=0;i<ev.nodes.length;i++) {
|
||||
RED.nodes.add(ev.nodes[i]);
|
||||
modifiedTabs[ev.nodes[i].z] = true;
|
||||
}
|
||||
}
|
||||
if (ev.links) {
|
||||
@@ -143,13 +148,11 @@ RED.history = (function() {
|
||||
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
|
||||
}
|
||||
}
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+ev.node.id) {
|
||||
n.changed = ev.changed;
|
||||
n.inputs = ev.node.in.length;
|
||||
n.outputs = ev.node.out.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
}
|
||||
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
|
||||
n.changed = ev.changed;
|
||||
n.inputs = ev.node.in.length;
|
||||
n.outputs = ev.node.out.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
});
|
||||
|
||||
RED.palette.refresh();
|
||||
@@ -166,11 +169,9 @@ RED.history = (function() {
|
||||
ev.node.changed = ev.changed;
|
||||
} else if (ev.t == "createSubflow") {
|
||||
if (ev.nodes) {
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.z === ev.subflow.id) {
|
||||
n.z = ev.activeWorkspace;
|
||||
n.dirty = true;
|
||||
}
|
||||
RED.nodes.filterNodes({z:ev.subflow.id}).forEach(function(n) {
|
||||
n.z = ev.activeWorkspace;
|
||||
n.dirty = true;
|
||||
});
|
||||
for (i=0;i<ev.nodes.length;i++) {
|
||||
RED.nodes.remove(ev.nodes[i]);
|
||||
@@ -183,7 +184,7 @@ RED.history = (function() {
|
||||
}
|
||||
|
||||
RED.nodes.removeSubflow(ev.subflow);
|
||||
RED.view.removeWorkspace(ev.subflow);
|
||||
RED.workspaces.remove(ev.subflow);
|
||||
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
@@ -191,8 +192,17 @@ RED.history = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
RED.view.dirty(ev.dirty);
|
||||
RED.view.redraw();
|
||||
Object.keys(modifiedTabs).forEach(function(id) {
|
||||
var subflow = RED.nodes.subflow(id);
|
||||
if (subflow) {
|
||||
RED.editor.validateNode(subflow);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
RED.nodes.dirty(ev.dirty);
|
||||
RED.view.redraw(true);
|
||||
RED.palette.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,114 +15,6 @@
|
||||
**/
|
||||
var RED = (function() {
|
||||
|
||||
var deploymentTypes = {
|
||||
"full":{img:"images/deploy-full-o.png"},
|
||||
"nodes":{img:"images/deploy-nodes-o.png"},
|
||||
"flows":{img:"images/deploy-flows-o.png"}
|
||||
}
|
||||
var deploymentType = "full";
|
||||
|
||||
function save(force) {
|
||||
if (RED.view.dirty()) {
|
||||
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
|
||||
|
||||
if (!force) {
|
||||
var invalid = false;
|
||||
var unknownNodes = [];
|
||||
RED.nodes.eachNode(function(node) {
|
||||
invalid = invalid || !node.valid;
|
||||
if (node.type === "unknown") {
|
||||
if (unknownNodes.indexOf(node.name) == -1) {
|
||||
unknownNodes.push(node.name);
|
||||
}
|
||||
invalid = true;
|
||||
}
|
||||
});
|
||||
if (invalid) {
|
||||
if (unknownNodes.length > 0) {
|
||||
$( "#node-dialog-confirm-deploy-config" ).hide();
|
||||
$( "#node-dialog-confirm-deploy-unknown" ).show();
|
||||
var list = "<li>"+unknownNodes.join("</li><li>")+"</li>";
|
||||
$( "#node-dialog-confirm-deploy-unknown-list" ).html(list);
|
||||
} else {
|
||||
$( "#node-dialog-confirm-deploy-config" ).show();
|
||||
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
||||
}
|
||||
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
var nns = RED.nodes.createCompleteNodeSet();
|
||||
|
||||
$("#btn-icn-deploy").removeClass('fa-download');
|
||||
$("#btn-icn-deploy").addClass('spinner');
|
||||
RED.view.dirty(false);
|
||||
|
||||
$.ajax({
|
||||
url:"flows",
|
||||
type: "POST",
|
||||
data: JSON.stringify(nns),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: {
|
||||
"Node-RED-Deployment-Type":deploymentType
|
||||
}
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.notify("Successfully deployed","success");
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (node.changed) {
|
||||
node.dirty = true;
|
||||
node.changed = false;
|
||||
}
|
||||
if(node.credentials) {
|
||||
delete node.credentials;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachConfig(function (confNode) {
|
||||
if (confNode.credentials) {
|
||||
delete confNode.credentials;
|
||||
}
|
||||
});
|
||||
// Once deployed, cannot undo back to a clean state
|
||||
RED.history.markAllDirty();
|
||||
RED.view.redraw();
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.view.dirty(true);
|
||||
if (xhr.responseText) {
|
||||
RED.notify("<strong>Error</strong>: "+xhr.responseText,"error");
|
||||
} else {
|
||||
RED.notify("<strong>Error</strong>: no response from server","error");
|
||||
}
|
||||
}).always(function() {
|
||||
$("#btn-icn-deploy").removeClass('spinner');
|
||||
$("#btn-icn-deploy").addClass('fa-download');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('#btn-deploy').click(function() { save(); });
|
||||
|
||||
$( "#node-dialog-confirm-deploy" ).dialog({
|
||||
title: "Confirm deploy",
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 530,
|
||||
height: 230,
|
||||
buttons: [
|
||||
{
|
||||
text: "Confirm deploy",
|
||||
click: function() {
|
||||
save(true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
function loadSettings() {
|
||||
RED.settings.init(loadNodeList);
|
||||
@@ -168,8 +60,8 @@ var RED = (function() {
|
||||
url: 'flows',
|
||||
success: function(nodes) {
|
||||
RED.nodes.import(nodes);
|
||||
RED.view.dirty(false);
|
||||
RED.view.redraw();
|
||||
RED.nodes.dirty(false);
|
||||
RED.view.redraw(true);
|
||||
RED.comms.subscribe("status/#",function(topic,msg) {
|
||||
var parts = topic.split("/");
|
||||
var node = RED.nodes.node(parts[1]);
|
||||
@@ -192,12 +84,10 @@ var RED = (function() {
|
||||
m = msg[i];
|
||||
var id = m.id;
|
||||
RED.nodes.addNodeSet(m);
|
||||
if (m.loaded) {
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
});
|
||||
}
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
});
|
||||
}
|
||||
if (addedTypes.length) {
|
||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||
@@ -245,29 +135,6 @@ var RED = (function() {
|
||||
RED.view.status(statusEnabled);
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
|
||||
var dialog = $('#node-help');
|
||||
|
||||
//$("#node-help").draggable({
|
||||
// handle: ".modal-header"
|
||||
//});
|
||||
|
||||
dialog.on('show',function() {
|
||||
RED.keyboard.disable();
|
||||
});
|
||||
dialog.on('hidden',function() {
|
||||
RED.keyboard.enable();
|
||||
});
|
||||
|
||||
dialog.modal();
|
||||
}
|
||||
|
||||
function changeDeploymentType(type) {
|
||||
deploymentType = type;
|
||||
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
||||
}
|
||||
|
||||
function loadEditor() {
|
||||
RED.menu.init({id:"btn-sidemenu",
|
||||
options: [
|
||||
@@ -275,91 +142,50 @@ var RED = (function() {
|
||||
{id:"btn-node-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
|
||||
null,
|
||||
{id:"btn-import-menu",label:"Import",options:[
|
||||
{id:"btn-import-clipboard",label:"Clipboard",onselect:RED.view.showImportNodesDialog},
|
||||
{id:"btn-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
|
||||
{id:"btn-import-library",label:"Library",options:[]}
|
||||
]},
|
||||
{id:"btn-export-menu",label:"Export",disabled:true,options:[
|
||||
{id:"btn-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.view.showExportNodesDialog},
|
||||
{id:"btn-export-library",label:"Library",disabled:true,onselect:RED.view.showExportNodesLibraryDialog}
|
||||
{id:"btn-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export},
|
||||
{id:"btn-export-library",label:"Library",disabled:true,onselect:RED.library.export}
|
||||
]},
|
||||
null,
|
||||
{id:"btn-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show},
|
||||
null,
|
||||
{id:"btn-subflow-menu",label:"Subflows", options: [
|
||||
{id:"btn-create-subflow",label:"Create subflow",onselect:RED.view.createSubflow},
|
||||
{id:"btn-convert-subflow",label:"Selection to subflow",disabled:true,onselect:RED.view.convertToSubflow},
|
||||
{id:"btn-create-subflow",label:"Create subflow",onselect:RED.subflow.createSubflow},
|
||||
{id:"btn-convert-subflow",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||
]},
|
||||
null,
|
||||
{id:"btn-workspace-menu",label:"Workspaces",options:[
|
||||
{id:"btn-workspace-add",label:"Add"},
|
||||
{id:"btn-workspace-edit",label:"Rename"},
|
||||
{id:"btn-workspace-delete",label:"Delete"},
|
||||
{id:"btn-workspace-add",label:"Add",onselect:RED.workspaces.add},
|
||||
{id:"btn-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
|
||||
{id:"btn-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
|
||||
null
|
||||
]},
|
||||
null,
|
||||
{id:"btn-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:showHelp},
|
||||
{id:"btn-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp},
|
||||
{id:"btn-help",label:"Node-RED Website", href:"http://nodered.org/docs"}
|
||||
]
|
||||
});
|
||||
|
||||
RED.menu.init({id:"btn-deploy-options",
|
||||
options: [
|
||||
{id:"btn-deploy-full",toggle:"deploy-type",icon:"images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||
{id:"btn-deploy-flow",toggle:"deploy-type",icon:"images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
|
||||
{id:"btn-deploy-node",toggle:"deploy-type",icon:"images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
|
||||
]
|
||||
});
|
||||
|
||||
if (RED.settings.user) {
|
||||
RED.menu.init({id:"btn-usermenu",
|
||||
options: []
|
||||
});
|
||||
|
||||
var updateUserMenu = function() {
|
||||
$("#btn-usermenu-submenu li").remove();
|
||||
if (RED.settings.user.anonymous) {
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-login",
|
||||
label:"Login",
|
||||
onselect: function() {
|
||||
RED.user.login({cancelable:true},function() {
|
||||
RED.settings.load(function() {
|
||||
RED.notify("Logged in as "+RED.settings.user.username,"success");
|
||||
updateUserMenu();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-username",
|
||||
label:"<b>"+RED.settings.user.username+"</b>"
|
||||
});
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-logout",
|
||||
label:"Logout",
|
||||
onselect: function() {
|
||||
RED.user.logout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
updateUserMenu();
|
||||
} else {
|
||||
$("#btn-usermenu").parent().hide();
|
||||
}
|
||||
|
||||
$("#main-container").show();
|
||||
$(".header-toolbar").show();
|
||||
RED.user.init();
|
||||
|
||||
RED.library.init();
|
||||
RED.palette.init();
|
||||
RED.sidebar.init();
|
||||
RED.subflow.init();
|
||||
RED.workspaces.init();
|
||||
RED.clipboard.init();
|
||||
RED.view.init();
|
||||
RED.deploy.init();
|
||||
|
||||
RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();});
|
||||
RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();});
|
||||
RED.comms.connect();
|
||||
|
||||
$("#main-container").show();
|
||||
$(".header-toolbar").show();
|
||||
|
||||
loadNodeList();
|
||||
}
|
||||
|
||||
@@ -369,6 +195,8 @@ var RED = (function() {
|
||||
document.title = "Node-RED : "+window.location.hostname;
|
||||
}
|
||||
|
||||
ace.require("ace/ext/language_tools");
|
||||
|
||||
RED.settings.init(loadEditor);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,13 @@ RED.nodes = (function() {
|
||||
var workspaces = {};
|
||||
var subflows = {};
|
||||
|
||||
var dirty = false;
|
||||
|
||||
function setDirty(d) {
|
||||
dirty = d;
|
||||
eventHandler.emit("change",{dirty:dirty});
|
||||
}
|
||||
|
||||
var registry = (function() {
|
||||
var nodeList = [];
|
||||
var nodeSets = {};
|
||||
@@ -160,13 +167,13 @@ RED.nodes = (function() {
|
||||
}
|
||||
nodes.push(n);
|
||||
}
|
||||
if (n._def.onadd) {
|
||||
n._def.onadd.call(n);
|
||||
}
|
||||
}
|
||||
function addLink(l) {
|
||||
links.push(l);
|
||||
}
|
||||
function addConfig(c) {
|
||||
configNodes[c.id] = c;
|
||||
}
|
||||
|
||||
function getNode(id) {
|
||||
if (id in configNodes) {
|
||||
@@ -183,11 +190,13 @@ RED.nodes = (function() {
|
||||
|
||||
function removeNode(id) {
|
||||
var removedLinks = [];
|
||||
var node;
|
||||
if (id in configNodes) {
|
||||
node = configNodes[id];
|
||||
delete configNodes[id];
|
||||
RED.sidebar.config.refresh();
|
||||
} else {
|
||||
var node = getNode(id);
|
||||
node = getNode(id);
|
||||
if (node) {
|
||||
nodes.splice(nodes.indexOf(node),1);
|
||||
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
|
||||
@@ -214,6 +223,9 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node._def.onremove) {
|
||||
node._def.onremove.call(n);
|
||||
}
|
||||
return removedLinks;
|
||||
}
|
||||
|
||||
@@ -224,12 +236,6 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function refreshValidation() {
|
||||
for (var n=0;n<nodes.length;n++) {
|
||||
RED.editor.validateNode(nodes[n]);
|
||||
}
|
||||
}
|
||||
|
||||
function addWorkspace(ws) {
|
||||
workspaces[ws.id] = ws;
|
||||
}
|
||||
@@ -254,7 +260,24 @@ RED.nodes = (function() {
|
||||
return {nodes:removedNodes,links:removedLinks};
|
||||
}
|
||||
|
||||
function addSubflow(sf) {
|
||||
function addSubflow(sf, createNewIds) {
|
||||
if (createNewIds) {
|
||||
var subflowNames = Object.keys(subflows).map(function(sfid) {
|
||||
return subflows[sfid].name;
|
||||
});
|
||||
|
||||
subflowNames.sort();
|
||||
var copyNumber = 1;
|
||||
var subflowName = sf.name;
|
||||
subflowNames.forEach(function(name) {
|
||||
if (subflowName == name) {
|
||||
copyNumber++;
|
||||
subflowName = sf.name+" ("+copyNumber+")";
|
||||
}
|
||||
});
|
||||
sf.name = subflowName;
|
||||
}
|
||||
|
||||
subflows[sf.id] = sf;
|
||||
RED.nodes.registerType("subflow:"+sf.id, {
|
||||
defaults:{name:{value:""}},
|
||||
@@ -424,16 +447,16 @@ RED.nodes = (function() {
|
||||
var exportedConfigNodes = {};
|
||||
var exportedSubflows = {};
|
||||
for (var n=0;n<set.length;n++) {
|
||||
var node = set[n].n;
|
||||
var node = set[n];
|
||||
if (node.type.substring(0,8) == "subflow:") {
|
||||
var subflowId = node.type.substring(8);
|
||||
if (!exportedSubflows[subflowId]) {
|
||||
exportedSubflows[subflowId] = true;
|
||||
var subflow = getSubflow(subflowId);
|
||||
var subflowSet = [{n:subflow}];
|
||||
var subflowSet = [subflow];
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.z == subflowId) {
|
||||
subflowSet.push({n:n});
|
||||
subflowSet.push(n);
|
||||
}
|
||||
});
|
||||
var exportableSubflow = createExportableNodeSet(subflowSet);
|
||||
@@ -494,280 +517,340 @@ RED.nodes = (function() {
|
||||
}
|
||||
|
||||
function importNodes(newNodesObj,createNewIds) {
|
||||
try {
|
||||
var i;
|
||||
var n;
|
||||
var newNodes;
|
||||
if (typeof newNodesObj === "string") {
|
||||
if (newNodesObj === "") {
|
||||
return;
|
||||
}
|
||||
var i;
|
||||
var n;
|
||||
var newNodes;
|
||||
if (typeof newNodesObj === "string") {
|
||||
if (newNodesObj === "") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
newNodes = JSON.parse(newNodesObj);
|
||||
} else {
|
||||
newNodes = newNodesObj;
|
||||
} catch(err) {
|
||||
var e = new Error("Invalid flow: "+err.message);
|
||||
e.code = "NODE_RED";
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
newNodes = newNodesObj;
|
||||
}
|
||||
|
||||
if (!$.isArray(newNodes)) {
|
||||
newNodes = [newNodes];
|
||||
}
|
||||
var unknownTypes = [];
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type != "workspace" &&
|
||||
n.type != "tab" &&
|
||||
n.type != "subflow" &&
|
||||
!registry.getNodeType(n.type) &&
|
||||
n.type.substring(0,8) != "subflow:") {
|
||||
// TODO: get this UI thing out of here! (see below as well)
|
||||
|
||||
if (unknownTypes.indexOf(n.type)==-1) {
|
||||
unknownTypes.push(n.type);
|
||||
}
|
||||
//if (n.x == null && n.y == null) {
|
||||
// // config node - remove it
|
||||
// newNodes.splice(i,1);
|
||||
// i--;
|
||||
//}
|
||||
}
|
||||
}
|
||||
if (unknownTypes.length > 0) {
|
||||
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
|
||||
var type = "type"+(unknownTypes.length > 1?"s":"");
|
||||
RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
|
||||
//"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
|
||||
}
|
||||
|
||||
var activeWorkspace = RED.view.getWorkspace();
|
||||
var activeSubflow = getSubflow(activeWorkspace);
|
||||
if (activeSubflow) {
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
|
||||
if (m) {
|
||||
var subflowId = m[1];
|
||||
var err;
|
||||
if (subflowId === activeSubflow.id) {
|
||||
err = new Error("Cannot add subflow to itself");
|
||||
}
|
||||
if (subflowContains(m[1],activeSubflow.id)) {
|
||||
err = new Error("Cannot add subflow - circular reference detected");
|
||||
}
|
||||
if (err) {
|
||||
// TODO: standardise error codes
|
||||
err.code = "NODE_RED";
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$.isArray(newNodes)) {
|
||||
newNodes = [newNodes];
|
||||
}
|
||||
var unknownTypes = [];
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type != "workspace" &&
|
||||
n.type != "tab" &&
|
||||
n.type != "subflow" &&
|
||||
!registry.getNodeType(n.type) &&
|
||||
n.type.substring(0,8) != "subflow:" &&
|
||||
unknownTypes.indexOf(n.type)==-1) {
|
||||
|
||||
var new_workspaces = [];
|
||||
var workspace_map = {};
|
||||
var new_subflows = [];
|
||||
var subflow_map = {};
|
||||
var nid;
|
||||
unknownTypes.push(n.type);
|
||||
}
|
||||
}
|
||||
if (unknownTypes.length > 0) {
|
||||
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
|
||||
var type = "type"+(unknownTypes.length > 1?"s":"");
|
||||
RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
|
||||
//"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
|
||||
}
|
||||
|
||||
var activeWorkspace = RED.workspaces.active();
|
||||
var activeSubflow = getSubflow(activeWorkspace);
|
||||
if (activeSubflow) {
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type === "workspace" || n.type === "tab") {
|
||||
if (n.type === "workspace") {
|
||||
n.type = "tab";
|
||||
var m = /^subflow:(.+)$/.exec(newNodes[i].type);
|
||||
if (m) {
|
||||
var subflowId = m[1];
|
||||
var err;
|
||||
if (subflowId === activeSubflow.id) {
|
||||
err = new Error("Cannot add subflow to itself");
|
||||
}
|
||||
if (defaultWorkspace == null) {
|
||||
defaultWorkspace = n;
|
||||
if (subflowContains(m[1],activeSubflow.id)) {
|
||||
err = new Error("Cannot add subflow - circular reference detected");
|
||||
}
|
||||
if (createNewIds) {
|
||||
nid = getID();
|
||||
workspace_map[n.id] = nid;
|
||||
n.id = nid;
|
||||
if (err) {
|
||||
// TODO: standardise error codes
|
||||
err.code = "NODE_RED";
|
||||
throw err;
|
||||
}
|
||||
addWorkspace(n);
|
||||
RED.view.addWorkspace(n);
|
||||
new_workspaces.push(n);
|
||||
} else if (n.type === "subflow") {
|
||||
subflow_map[n.id] = n;
|
||||
if (createNewIds) {
|
||||
nid = getID();
|
||||
n.id = nid;
|
||||
}
|
||||
// TODO: handle createNewIds - map old to new subflow ids
|
||||
n.in.forEach(function(input,i) {
|
||||
input.type = "subflow";
|
||||
input.direction = "in";
|
||||
input.z = n.id;
|
||||
input.i = i;
|
||||
input.id = getID();
|
||||
});
|
||||
n.out.forEach(function(output,i) {
|
||||
output.type = "subflow";
|
||||
output.direction = "out";
|
||||
output.z = n.id;
|
||||
output.i = i;
|
||||
output.id = getID();
|
||||
});
|
||||
new_subflows.push(n);
|
||||
addSubflow(n);
|
||||
|
||||
}
|
||||
}
|
||||
if (defaultWorkspace == null) {
|
||||
defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
|
||||
addWorkspace(defaultWorkspace);
|
||||
RED.view.addWorkspace(defaultWorkspace);
|
||||
new_workspaces.push(defaultWorkspace);
|
||||
activeWorkspace = RED.view.getWorkspace();
|
||||
}
|
||||
|
||||
var node_map = {};
|
||||
var new_nodes = [];
|
||||
var new_links = [];
|
||||
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
|
||||
var def = registry.getNodeType(n.type);
|
||||
if (def && def.category == "config") {
|
||||
if (!RED.nodes.node(n.id)) {
|
||||
var configNode = {id:n.id,type:n.type,users:[]};
|
||||
for (var d in def.defaults) {
|
||||
if (def.defaults.hasOwnProperty(d)) {
|
||||
configNode[d] = n[d];
|
||||
}
|
||||
}
|
||||
|
||||
var new_workspaces = [];
|
||||
var workspace_map = {};
|
||||
var new_subflows = [];
|
||||
var subflow_map = {};
|
||||
var nid;
|
||||
var def;
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type === "workspace" || n.type === "tab") {
|
||||
if (n.type === "workspace") {
|
||||
n.type = "tab";
|
||||
}
|
||||
if (defaultWorkspace == null) {
|
||||
defaultWorkspace = n;
|
||||
}
|
||||
if (createNewIds) {
|
||||
nid = getID();
|
||||
workspace_map[n.id] = nid;
|
||||
n.id = nid;
|
||||
}
|
||||
addWorkspace(n);
|
||||
RED.workspaces.add(n);
|
||||
new_workspaces.push(n);
|
||||
} else if (n.type === "subflow") {
|
||||
subflow_map[n.id] = n;
|
||||
if (createNewIds) {
|
||||
nid = getID();
|
||||
n.id = nid;
|
||||
}
|
||||
// TODO: handle createNewIds - map old to new subflow ids
|
||||
n.in.forEach(function(input,i) {
|
||||
input.type = "subflow";
|
||||
input.direction = "in";
|
||||
input.z = n.id;
|
||||
input.i = i;
|
||||
input.id = getID();
|
||||
});
|
||||
n.out.forEach(function(output,i) {
|
||||
output.type = "subflow";
|
||||
output.direction = "out";
|
||||
output.z = n.id;
|
||||
output.i = i;
|
||||
output.id = getID();
|
||||
});
|
||||
new_subflows.push(n);
|
||||
addSubflow(n,createNewIds);
|
||||
} else {
|
||||
def = registry.getNodeType(n.type);
|
||||
if (def && def.category == "config") {
|
||||
if (!RED.nodes.node(n.id)) {
|
||||
var configNode = {id:n.id,type:n.type,users:[]};
|
||||
for (var d in def.defaults) {
|
||||
if (def.defaults.hasOwnProperty(d)) {
|
||||
configNode[d] = n[d];
|
||||
}
|
||||
configNode.label = def.label;
|
||||
configNode._def = def;
|
||||
RED.nodes.add(configNode);
|
||||
}
|
||||
} else {
|
||||
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
|
||||
if (createNewIds) {
|
||||
if (subflow_map[node.z]) {
|
||||
node.z = subflow_map[node.z].id;
|
||||
} else {
|
||||
node.z = workspace_map[node.z];
|
||||
if (!workspaces[node.z]) {
|
||||
node.z = activeWorkspace;
|
||||
}
|
||||
}
|
||||
node.id = getID();
|
||||
configNode.label = def.label;
|
||||
configNode._def = def;
|
||||
RED.nodes.add(configNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defaultWorkspace == null) {
|
||||
defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
|
||||
addWorkspace(defaultWorkspace);
|
||||
RED.workspaces.add(defaultWorkspace);
|
||||
new_workspaces.push(defaultWorkspace);
|
||||
activeWorkspace = RED.workspaces.active();
|
||||
}
|
||||
|
||||
var node_map = {};
|
||||
var new_nodes = [];
|
||||
var new_links = [];
|
||||
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
|
||||
def = registry.getNodeType(n.type);
|
||||
if (!def || def.category != "config") {
|
||||
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
|
||||
if (createNewIds) {
|
||||
if (subflow_map[node.z]) {
|
||||
node.z = subflow_map[node.z].id;
|
||||
} else {
|
||||
node.id = n.id;
|
||||
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
|
||||
node.z = workspace_map[node.z];
|
||||
if (!workspaces[node.z]) {
|
||||
node.z = activeWorkspace;
|
||||
}
|
||||
}
|
||||
node.type = n.type;
|
||||
node._def = def;
|
||||
if (n.type.substring(0,7) === "subflow") {
|
||||
var parentId = n.type.split(":")[1];
|
||||
var subflow = subflow_map[parentId]||getSubflow(parentId);
|
||||
if (createNewIds) {
|
||||
parentId = subflow.id;
|
||||
node.type = "subflow:"+parentId;
|
||||
node._def = registry.getNodeType(node.type);
|
||||
delete node.i;
|
||||
}
|
||||
node.name = n.name;
|
||||
node.outputs = subflow.out.length;
|
||||
node.inputs = subflow.in.length;
|
||||
} else {
|
||||
if (!node._def) {
|
||||
if (node.x && node.y) {
|
||||
node._def = {
|
||||
color:"#fee",
|
||||
defaults: {},
|
||||
label: "unknown: "+n.type,
|
||||
labelStyle: "node_label_italic",
|
||||
outputs: n.outputs||n.wires.length
|
||||
}
|
||||
} else {
|
||||
node._def = {
|
||||
category:"config"
|
||||
};
|
||||
node.users = [];
|
||||
}
|
||||
var orig = {};
|
||||
for (var p in n) {
|
||||
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
|
||||
orig[p] = n[p];
|
||||
}
|
||||
}
|
||||
node._orig = orig;
|
||||
node.name = n.type;
|
||||
node.type = "unknown";
|
||||
}
|
||||
if (node._def.category != "config") {
|
||||
node.inputs = n.inputs||node._def.inputs;
|
||||
node.outputs = n.outputs||node._def.outputs;
|
||||
for (var d2 in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(d2)) {
|
||||
node[d2] = n[d2];
|
||||
}
|
||||
}
|
||||
}
|
||||
node.id = getID();
|
||||
} else {
|
||||
node.id = n.id;
|
||||
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
|
||||
node.z = activeWorkspace;
|
||||
}
|
||||
}
|
||||
node.type = n.type;
|
||||
node._def = def;
|
||||
if (n.type.substring(0,7) === "subflow") {
|
||||
var parentId = n.type.split(":")[1];
|
||||
var subflow = subflow_map[parentId]||getSubflow(parentId);
|
||||
if (createNewIds) {
|
||||
parentId = subflow.id;
|
||||
node.type = "subflow:"+parentId;
|
||||
node._def = registry.getNodeType(node.type);
|
||||
delete node.i;
|
||||
}
|
||||
node.name = n.name;
|
||||
node.outputs = subflow.out.length;
|
||||
node.inputs = subflow.in.length;
|
||||
} else {
|
||||
if (!node._def) {
|
||||
if (node.x && node.y) {
|
||||
node._def = {
|
||||
color:"#fee",
|
||||
defaults: {},
|
||||
label: "unknown: "+n.type,
|
||||
labelStyle: "node_label_italic",
|
||||
outputs: n.outputs||n.wires.length
|
||||
}
|
||||
} else {
|
||||
node._def = {
|
||||
category:"config"
|
||||
};
|
||||
node.users = [];
|
||||
}
|
||||
var orig = {};
|
||||
for (var p in n) {
|
||||
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
|
||||
orig[p] = n[p];
|
||||
}
|
||||
}
|
||||
node._orig = orig;
|
||||
node.name = n.type;
|
||||
node.type = "unknown";
|
||||
}
|
||||
addNode(node);
|
||||
RED.editor.validateNode(node);
|
||||
node_map[n.id] = node;
|
||||
if (node._def.category != "config") {
|
||||
new_nodes.push(node);
|
||||
node.inputs = n.inputs||node._def.inputs;
|
||||
node.outputs = n.outputs||node._def.outputs;
|
||||
for (var d2 in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(d2)) {
|
||||
node[d2] = n[d2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addNode(node);
|
||||
RED.editor.validateNode(node);
|
||||
node_map[n.id] = node;
|
||||
if (node._def.category != "config") {
|
||||
new_nodes.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0;i<new_nodes.length;i++) {
|
||||
n = new_nodes[i];
|
||||
for (var w1=0;w1<n.wires.length;w1++) {
|
||||
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
|
||||
for (var w2=0;w2<wires.length;w2++) {
|
||||
if (wires[w2] in node_map) {
|
||||
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete n.wires;
|
||||
}
|
||||
for (i=0;i<new_subflows.length;i++) {
|
||||
n = new_subflows[i];
|
||||
n.in.forEach(function(input) {
|
||||
input.wires.forEach(function(wire) {
|
||||
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
});
|
||||
delete input.wires;
|
||||
});
|
||||
n.out.forEach(function(output) {
|
||||
output.wires.forEach(function(wire) {
|
||||
var link;
|
||||
if (wire.id == n.id) {
|
||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
||||
} else {
|
||||
link = {source:node_map[wire.id], sourcePort:wire.port,target:output};
|
||||
}
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
});
|
||||
delete output.wires;
|
||||
});
|
||||
}
|
||||
return [new_nodes,new_links,new_workspaces,new_subflows];
|
||||
} catch(error) {
|
||||
if (error.code != "NODE_RED") {
|
||||
console.log(error.stack);
|
||||
RED.notify("<strong>Error</strong>: "+error,"error");
|
||||
} else {
|
||||
RED.notify("<strong>Error</strong>: "+error.message,"error");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
for (i=0;i<new_nodes.length;i++) {
|
||||
n = new_nodes[i];
|
||||
for (var w1=0;w1<n.wires.length;w1++) {
|
||||
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
|
||||
for (var w2=0;w2<wires.length;w2++) {
|
||||
if (wires[w2] in node_map) {
|
||||
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete n.wires;
|
||||
}
|
||||
for (i=0;i<new_subflows.length;i++) {
|
||||
n = new_subflows[i];
|
||||
n.in.forEach(function(input) {
|
||||
input.wires.forEach(function(wire) {
|
||||
var link = {source:input, sourcePort:0, target:node_map[wire.id]};
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
});
|
||||
delete input.wires;
|
||||
});
|
||||
n.out.forEach(function(output) {
|
||||
output.wires.forEach(function(wire) {
|
||||
var link;
|
||||
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
|
||||
link = {source:n.in[wire.port], sourcePort:wire.port,target:output};
|
||||
} else {
|
||||
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:output};
|
||||
}
|
||||
addLink(link);
|
||||
new_links.push(link);
|
||||
});
|
||||
delete output.wires;
|
||||
});
|
||||
}
|
||||
|
||||
return [new_nodes,new_links,new_workspaces,new_subflows];
|
||||
}
|
||||
|
||||
|
||||
// TODO: supports filter.z|type
|
||||
function filterNodes(filter) {
|
||||
var result = [];
|
||||
|
||||
for (var n=0;n<nodes.length;n++) {
|
||||
var node = nodes[n];
|
||||
if (filter.z && node.z !== filter.z) {
|
||||
continue;
|
||||
}
|
||||
if (filter.type && node.type !== filter.type) {
|
||||
continue;
|
||||
}
|
||||
result.push(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function filterLinks(filter) {
|
||||
var result = [];
|
||||
|
||||
for (var n=0;n<links.length;n++) {
|
||||
var link = links[n];
|
||||
if (filter.source) {
|
||||
if (filter.source.id && link.source.id !== filter.source.id) {
|
||||
continue;
|
||||
}
|
||||
if (filter.source.z && link.source.z !== filter.source.z) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (filter.target) {
|
||||
if (filter.target.id && link.target.id !== filter.target.id) {
|
||||
continue;
|
||||
}
|
||||
if (filter.target.z && link.target.z !== filter.target.z) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (filter.sourcePort && link.sourcePort !== filter.sourcePort) {
|
||||
continue;
|
||||
}
|
||||
result.push(link);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
handlers[evt].push(func);
|
||||
},
|
||||
emit: function(evt,arg) {
|
||||
if (handlers[evt]) {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
on: eventHandler.on,
|
||||
|
||||
registry:registry,
|
||||
setNodeList: registry.setNodeList,
|
||||
|
||||
@@ -820,14 +903,24 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
node: getNode,
|
||||
|
||||
filterNodes: filterNodes,
|
||||
filterLinks: filterLinks,
|
||||
|
||||
import: importNodes,
|
||||
refreshValidation: refreshValidation,
|
||||
|
||||
getAllFlowNodes: getAllFlowNodes,
|
||||
createExportableNodeSet: createExportableNodeSet,
|
||||
createCompleteNodeSet: createCompleteNodeSet,
|
||||
id: getID,
|
||||
nodes: nodes, // TODO: exposed for d3 vis
|
||||
links: links // TODO: exposed for d3 vis
|
||||
dirty: function(d) {
|
||||
if (d == null) {
|
||||
return dirty;
|
||||
} else {
|
||||
setDirty(d);
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -68,13 +68,20 @@ RED.settings = (function () {
|
||||
};
|
||||
|
||||
var init = function (done) {
|
||||
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
|
||||
if (accessTokenMatch) {
|
||||
var accessToken = accessTokenMatch[1];
|
||||
RED.settings.set("auth-tokens",{access_token: accessToken});
|
||||
window.location.search = "";
|
||||
}
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR,settings) {
|
||||
// Only attach auth header for requests to relative paths
|
||||
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
|
||||
var auth_tokens = RED.settings.get("auth-tokens");
|
||||
if (auth_tokens) {
|
||||
jqXHR.setRequestHeader("authorization","bearer "+auth_tokens.access_token);
|
||||
jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +91,6 @@ RED.settings = (function () {
|
||||
}
|
||||
|
||||
var load = function(done) {
|
||||
|
||||
$.ajax({
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
@@ -102,6 +108,9 @@ RED.settings = (function () {
|
||||
},
|
||||
error: function(jqXHR,textStatus,errorThrown) {
|
||||
if (jqXHR.status === 401) {
|
||||
if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
|
||||
window.location.search = "";
|
||||
}
|
||||
RED.user.login(function() { load(done); });
|
||||
} else {
|
||||
console.log("Unexpected error:",jqXHR.status,textStatus);
|
||||
|
||||
178
public/red/ui/clipboard.js
Normal file
178
public/red/ui/clipboard.js
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright 2015 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.
|
||||
**/
|
||||
|
||||
|
||||
RED.clipboard = (function() {
|
||||
|
||||
var dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
id: "clipboard-dialog-ok",
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
if (/Import/.test(dialog.dialog("option","title"))) {
|
||||
RED.view.importNodes($("#clipboard-import").val());
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-cancel",
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-close",
|
||||
text: "Close",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
|
||||
var dialogContainer = dialog.children(".dialog-form");
|
||||
|
||||
var exportNodesDialog = '<div class="form-row">'+
|
||||
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>'+
|
||||
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
|
||||
'</div>'+
|
||||
'<div class="form-tips">'+
|
||||
'Select the text above and copy to the clipboard with Ctrl-C.'+
|
||||
'</div>';
|
||||
|
||||
var importNodesDialog = '<div class="form-row">'+
|
||||
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="Paste nodes here"></textarea>'+
|
||||
'</div>';
|
||||
|
||||
|
||||
function importNodes() {
|
||||
dialogContainer.empty();
|
||||
dialogContainer.append($(importNodesDialog));
|
||||
$("#clipboard-dialog-ok").show();
|
||||
$("#clipboard-dialog-cancel").show();
|
||||
$("#clipboard-dialog-close").hide();
|
||||
$("#clipboard-dialog-ok").button("disable");
|
||||
$("#clipboard-import").keyup(function() {
|
||||
var v = $(this).val();
|
||||
try {
|
||||
JSON.parse(v);
|
||||
$(this).removeClass("input-error");
|
||||
$("#clipboard-dialog-ok").button("enable");
|
||||
} catch(err) {
|
||||
if (v !== "") {
|
||||
$(this).addClass("input-error");
|
||||
}
|
||||
$("#clipboard-dialog-ok").button("disable");
|
||||
}
|
||||
});
|
||||
dialog.dialog("option","title","Import nodes").dialog("open");
|
||||
}
|
||||
|
||||
function exportNodes() {
|
||||
dialogContainer.empty();
|
||||
dialogContainer.append($(exportNodesDialog));
|
||||
$("#clipboard-dialog-ok").hide();
|
||||
$("#clipboard-dialog-cancel").hide();
|
||||
$("#clipboard-dialog-close").show();
|
||||
var selection = RED.view.selection();
|
||||
if (selection.nodes) {
|
||||
var nns = RED.nodes.createExportableNodeSet(selection.nodes);
|
||||
$("#clipboard-export")
|
||||
.val(JSON.stringify(nns))
|
||||
.focus(function() {
|
||||
var textarea = $(this);
|
||||
textarea.select();
|
||||
textarea.mouseup(function() {
|
||||
textarea.unbind("mouseup");
|
||||
return false;
|
||||
})
|
||||
});
|
||||
dialog.dialog("option","title","Export nodes to clipboard").dialog( "open" );
|
||||
}
|
||||
}
|
||||
|
||||
function hideDropTarget() {
|
||||
$("#dropTarget").hide();
|
||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
RED.menu.setDisabled("btn-export-menu",true);
|
||||
RED.menu.setDisabled("btn-export-clipboard",true);
|
||||
RED.menu.setDisabled("btn-export-library",true);
|
||||
} else {
|
||||
RED.menu.setDisabled("btn-export-menu",false);
|
||||
RED.menu.setDisabled("btn-export-clipboard",false);
|
||||
RED.menu.setDisabled("btn-export-library",false);
|
||||
}
|
||||
});
|
||||
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
|
||||
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
|
||||
|
||||
|
||||
|
||||
$('#chart').on("dragenter",function(event) {
|
||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||
$("#dropTarget").css({display:'table'});
|
||||
RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget);
|
||||
}
|
||||
});
|
||||
|
||||
$('#dropTarget').on("dragover",function(event) {
|
||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||
event.preventDefault();
|
||||
}
|
||||
})
|
||||
.on("dragleave",function(event) {
|
||||
hideDropTarget();
|
||||
})
|
||||
.on("drop",function(event) {
|
||||
var data = event.originalEvent.dataTransfer.getData("text/plain");
|
||||
hideDropTarget();
|
||||
RED.view.importNodes(data);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
import: importNodes,
|
||||
export: exportNodes
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})();
|
||||
165
public/red/ui/deploy.js
Normal file
165
public/red/ui/deploy.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright 2015 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.
|
||||
**/
|
||||
|
||||
RED.deploy = (function() {
|
||||
|
||||
var deploymentTypes = {
|
||||
"full":{img:"images/deploy-full-o.png"},
|
||||
"nodes":{img:"images/deploy-nodes-o.png"},
|
||||
"flows":{img:"images/deploy-flows-o.png"}
|
||||
}
|
||||
|
||||
var deploymentType = "full";
|
||||
|
||||
function changeDeploymentType(type) {
|
||||
deploymentType = type;
|
||||
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
var deployButton = $('<li><span class="deploy-button-group button-group">'+
|
||||
'<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="images/deploy-full-o.png"> <span>Deploy</span></a>'+
|
||||
'<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>'+
|
||||
'</span></li>').prependTo(".header-toolbar");
|
||||
|
||||
$('#btn-deploy').click(function() { save(); });
|
||||
|
||||
$( "#node-dialog-confirm-deploy" ).dialog({
|
||||
title: "Confirm deploy",
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 530,
|
||||
height: 230,
|
||||
buttons: [
|
||||
{
|
||||
text: "Confirm deploy",
|
||||
click: function() {
|
||||
save(true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
RED.menu.init({id:"btn-deploy-options",
|
||||
options: [
|
||||
{id:"btn-deploy-full",toggle:"deploy-type",icon:"images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||
{id:"btn-deploy-flow",toggle:"deploy-type",icon:"images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}},
|
||||
{id:"btn-deploy-node",toggle:"deploy-type",icon:"images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
|
||||
]
|
||||
});
|
||||
|
||||
RED.nodes.on('change',function(state) {
|
||||
if (state.dirty) {
|
||||
window.onbeforeunload = function() {
|
||||
return "You have undeployed changes.\n\nLeaving this page will lose these changes.";
|
||||
}
|
||||
$("#btn-deploy").removeClass("disabled");
|
||||
} else {
|
||||
window.onbeforeunload = null;
|
||||
$("#btn-deploy").addClass("disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function save(force) {
|
||||
if (RED.nodes.dirty()) {
|
||||
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
|
||||
|
||||
if (!force) {
|
||||
var invalid = false;
|
||||
var unknownNodes = [];
|
||||
RED.nodes.eachNode(function(node) {
|
||||
invalid = invalid || !node.valid;
|
||||
if (node.type === "unknown") {
|
||||
if (unknownNodes.indexOf(node.name) == -1) {
|
||||
unknownNodes.push(node.name);
|
||||
}
|
||||
invalid = true;
|
||||
}
|
||||
});
|
||||
if (invalid) {
|
||||
if (unknownNodes.length > 0) {
|
||||
$( "#node-dialog-confirm-deploy-config" ).hide();
|
||||
$( "#node-dialog-confirm-deploy-unknown" ).show();
|
||||
var list = "<li>"+unknownNodes.join("</li><li>")+"</li>";
|
||||
$( "#node-dialog-confirm-deploy-unknown-list" ).html(list);
|
||||
} else {
|
||||
$( "#node-dialog-confirm-deploy-config" ).show();
|
||||
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
||||
}
|
||||
$( "#node-dialog-confirm-deploy" ).dialog( "open" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
var nns = RED.nodes.createCompleteNodeSet();
|
||||
|
||||
$("#btn-icn-deploy").removeClass('fa-download');
|
||||
$("#btn-icn-deploy").addClass('spinner');
|
||||
RED.nodes.dirty(false);
|
||||
|
||||
$.ajax({
|
||||
url:"flows",
|
||||
type: "POST",
|
||||
data: JSON.stringify(nns),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: {
|
||||
"Node-RED-Deployment-Type":deploymentType
|
||||
}
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.notify("Successfully deployed","success");
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (node.changed) {
|
||||
node.dirty = true;
|
||||
node.changed = false;
|
||||
}
|
||||
if(node.credentials) {
|
||||
delete node.credentials;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachConfig(function (confNode) {
|
||||
if (confNode.credentials) {
|
||||
delete confNode.credentials;
|
||||
}
|
||||
});
|
||||
// Once deployed, cannot undo back to a clean state
|
||||
RED.history.markAllDirty();
|
||||
RED.view.redraw();
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.nodes.dirty(true);
|
||||
if (xhr.responseText) {
|
||||
RED.notify("<strong>Error</strong>: "+xhr.responseJSON.message,"error");
|
||||
} else {
|
||||
RED.notify("<strong>Error</strong>: no response from server","error");
|
||||
}
|
||||
}).always(function() {
|
||||
$("#btn-icn-deploy").removeClass('spinner');
|
||||
$("#btn-icn-deploy").addClass('fa-download');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
})();
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,6 @@
|
||||
**/
|
||||
RED.editor = (function() {
|
||||
var editing_node = null;
|
||||
// TODO: should IMPORT/EXPORT get their own dialogs?
|
||||
|
||||
function getCredentialsURL(nodeType, nodeID) {
|
||||
var dashedType = nodeType.replace(/\s+/g, '-');
|
||||
@@ -29,13 +28,62 @@ RED.editor = (function() {
|
||||
*/
|
||||
function validateNode(node) {
|
||||
var oldValue = node.valid;
|
||||
node.valid = validateNodeProperties(node, node._def.defaults, node);
|
||||
if (node._def._creds) {
|
||||
node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds);
|
||||
var oldChanged = node.changed;
|
||||
node.valid = true;
|
||||
var subflow;
|
||||
var isValid;
|
||||
var hasChanged;
|
||||
|
||||
if (node.type.indexOf("subflow:")===0) {
|
||||
subflow = RED.nodes.subflow(node.type.substring(8));
|
||||
isValid = subflow.valid;
|
||||
hasChanged = subflow.changed;
|
||||
if (isValid === undefined) {
|
||||
isValid = validateNode(subflow);
|
||||
hasChanged = subflow.changed;
|
||||
}
|
||||
node.valid = isValid;
|
||||
node.changed = hasChanged;
|
||||
} else if (node._def) {
|
||||
node.valid = validateNodeProperties(node, node._def.defaults, node);
|
||||
if (node._def._creds) {
|
||||
node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds);
|
||||
}
|
||||
} else if (node.type == "subflow") {
|
||||
var subflowNodes = RED.nodes.filterNodes({z:node.id});
|
||||
for (var i=0;i<subflowNodes.length;i++) {
|
||||
isValid = subflowNodes[i].valid;
|
||||
hasChanged = subflowNodes[i].changed;
|
||||
if (isValid === undefined) {
|
||||
isValid = validateNode(subflowNodes[i]);
|
||||
hasChanged = subflowNodes[i].changed;
|
||||
}
|
||||
node.valid = node.valid && isValid;
|
||||
node.changed = node.changed || hasChanged;
|
||||
}
|
||||
var subflowInstances = RED.nodes.filterNodes({type:"subflow:"+node.id});
|
||||
var modifiedTabs = {};
|
||||
for (i=0;i<subflowInstances.length;i++) {
|
||||
subflowInstances[i].valid = node.valid;
|
||||
subflowInstances[i].changed = node.changed;
|
||||
subflowInstances[i].dirty = true;
|
||||
modifiedTabs[subflowInstances[i].z] = true;
|
||||
}
|
||||
Object.keys(modifiedTabs).forEach(function(id) {
|
||||
var subflow = RED.nodes.subflow(id);
|
||||
if (subflow) {
|
||||
validateNode(subflow);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (oldValue != node.valid) {
|
||||
if (oldValue !== node.valid || oldChanged !== node.changed) {
|
||||
node.dirty = true;
|
||||
subflow = RED.nodes.subflow(node.z);
|
||||
if (subflow) {
|
||||
validateNode(subflow);
|
||||
}
|
||||
}
|
||||
return node.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +123,7 @@ RED.editor = (function() {
|
||||
}
|
||||
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
|
||||
if (!value || value == "_ADD_") {
|
||||
valid = false;
|
||||
valid = definition[property].hasOwnProperty("required") && !definition[property].required;
|
||||
} else {
|
||||
var v = RED.nodes.node(value).valid;
|
||||
valid = (v==null || v);
|
||||
@@ -112,11 +160,7 @@ RED.editor = (function() {
|
||||
}
|
||||
}
|
||||
if (node.inputs === 0) {
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.target === node) {
|
||||
removedLinks.push(l);
|
||||
}
|
||||
});
|
||||
removedLinks.concat(RED.nodes.filterLinks({target:node}));
|
||||
}
|
||||
for (var l=0;l<removedLinks.length;l++) {
|
||||
RED.nodes.removeLink(removedLinks[l]);
|
||||
@@ -129,8 +173,10 @@ RED.editor = (function() {
|
||||
$( "#dialog" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
closeOnEscape: false,
|
||||
width: 500,
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
buttons: [
|
||||
{
|
||||
id: "node-dialog-ok",
|
||||
@@ -139,7 +185,7 @@ RED.editor = (function() {
|
||||
if (editing_node) {
|
||||
var changes = {};
|
||||
var changed = false;
|
||||
var wasDirty = RED.view.dirty();
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var d;
|
||||
|
||||
if (editing_node._def.oneditsave) {
|
||||
@@ -225,30 +271,26 @@ RED.editor = (function() {
|
||||
if (changed) {
|
||||
var wasChanged = editing_node.changed;
|
||||
editing_node.changed = true;
|
||||
RED.view.dirty(true);
|
||||
RED.nodes.dirty(true);
|
||||
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
validateNode(editing_node);
|
||||
RED.view.redraw();
|
||||
} else if (RED.view.state() == RED.state.EXPORT) {
|
||||
if (/library/.test($( "#dialog" ).dialog("option","title"))) {
|
||||
//TODO: move this to RED.library
|
||||
var flowName = $("#node-input-filename").val();
|
||||
if (!/^\s*$/.test(flowName)) {
|
||||
$.ajax({
|
||||
url:'library/flows/'+flowName,
|
||||
type: "POST",
|
||||
data: $("#node-input-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify("Saved nodes","success");
|
||||
});
|
||||
}
|
||||
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
|
||||
//TODO: move this to RED.library
|
||||
var flowName = $("#node-input-filename").val();
|
||||
if (!/^\s*$/.test(flowName)) {
|
||||
$.ajax({
|
||||
url:'library/flows/'+flowName,
|
||||
type: "POST",
|
||||
data: $("#node-input-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify("Saved nodes","success");
|
||||
});
|
||||
}
|
||||
} else if (RED.view.state() == RED.state.IMPORT) {
|
||||
RED.view.importNodes($("#node-input-import").val());
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@@ -257,7 +299,7 @@ RED.editor = (function() {
|
||||
id: "node-dialog-cancel",
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
if (editing_node._def) {
|
||||
if (editing_node && editing_node._def) {
|
||||
if (editing_node._def.oneditcancel) {
|
||||
editing_node._def.oneditcancel.call(editing_node);
|
||||
}
|
||||
@@ -272,7 +314,12 @@ RED.editor = (function() {
|
||||
}
|
||||
},
|
||||
open: function(e) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
var minWidth = $(this).dialog('option','minWidth');
|
||||
if ($(this).outerWidth() < minWidth) {
|
||||
$(this).dialog('option','width',minWidth);
|
||||
} else {
|
||||
$(this).dialog('option','width',$(this).outerWidth());
|
||||
}
|
||||
RED.keyboard.disable();
|
||||
if (editing_node) {
|
||||
var size = $(this).dialog('option','sizeCache-'+editing_node.type);
|
||||
@@ -289,7 +336,7 @@ RED.editor = (function() {
|
||||
RED.view.state(RED.state.DEFAULT);
|
||||
}
|
||||
$( this ).dialog('option','height','auto');
|
||||
$( this ).dialog('option','width','500');
|
||||
$( this ).dialog('option','width','auto');
|
||||
if (editing_node) {
|
||||
RED.sidebar.info.refresh(editing_node);
|
||||
}
|
||||
@@ -493,7 +540,7 @@ RED.editor = (function() {
|
||||
class: 'leftButton',
|
||||
text: "Edit flow",
|
||||
click: function() {
|
||||
RED.view.showSubflow(id);
|
||||
RED.workspaces.show(id);
|
||||
$("#node-dialog-ok").click();
|
||||
}
|
||||
});
|
||||
@@ -567,7 +614,7 @@ RED.editor = (function() {
|
||||
validateNode(user);
|
||||
}
|
||||
updateConfigNodeSelect(configProperty,configType,"");
|
||||
RED.view.dirty(true);
|
||||
RED.nodes.dirty(true);
|
||||
$( this ).dialog( "close" );
|
||||
RED.view.redraw();
|
||||
}
|
||||
@@ -610,7 +657,9 @@ RED.editor = (function() {
|
||||
$( "#node-config-dialog" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
closeOnEscape: false,
|
||||
buttons: [
|
||||
{
|
||||
@@ -657,8 +706,12 @@ RED.editor = (function() {
|
||||
configTypeDef.oneditsave.call(RED.nodes.node(configId));
|
||||
}
|
||||
validateNode(configNode);
|
||||
for (var i=0;i<configNode.users.length;i++) {
|
||||
var user = configNode.users[i];
|
||||
validateNode(user);
|
||||
}
|
||||
|
||||
RED.view.dirty(true);
|
||||
RED.nodes.dirty(true);
|
||||
$(this).dialog("close");
|
||||
|
||||
}
|
||||
@@ -689,13 +742,18 @@ RED.editor = (function() {
|
||||
],
|
||||
resize: function(e,ui) {
|
||||
},
|
||||
open: function(e,ui) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
open: function(e) {
|
||||
var minWidth = $(this).dialog('option','minWidth');
|
||||
if ($(this).outerWidth() < minWidth) {
|
||||
$(this).dialog('option','width',minWidth);
|
||||
}
|
||||
if (RED.view.state() != RED.state.EDITING) {
|
||||
RED.keyboard.disable();
|
||||
}
|
||||
},
|
||||
close: function(e) {
|
||||
$(this).dialog('option','width','auto');
|
||||
$(this).dialog('option','height','auto');
|
||||
$("#dialog-config-form").html("");
|
||||
if (RED.view.state() != RED.state.EDITING) {
|
||||
RED.keyboard.enable();
|
||||
@@ -707,8 +765,10 @@ RED.editor = (function() {
|
||||
$( "#subflow-dialog" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
closeOnEscape: false,
|
||||
width: 500,
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
buttons: [
|
||||
{
|
||||
id: "subflow-dialog-ok",
|
||||
@@ -718,7 +778,7 @@ RED.editor = (function() {
|
||||
var i;
|
||||
var changes = {};
|
||||
var changed = false;
|
||||
var wasDirty = RED.view.dirty();
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
|
||||
var newName = $("#subflow-input-name").val();
|
||||
|
||||
@@ -740,7 +800,7 @@ RED.editor = (function() {
|
||||
});
|
||||
var wasChanged = editing_node.changed;
|
||||
editing_node.changed = true;
|
||||
RED.view.dirty(true);
|
||||
RED.nodes.dirty(true);
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:editing_node,
|
||||
@@ -766,9 +826,12 @@ RED.editor = (function() {
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e,ui) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
var minWidth = $(this).dialog('option','minWidth');
|
||||
if ($(this).outerWidth() < minWidth) {
|
||||
$(this).dialog('option','width',minWidth);
|
||||
}
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
@@ -780,6 +843,8 @@ RED.editor = (function() {
|
||||
editing_node = null;
|
||||
}
|
||||
});
|
||||
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
|
||||
|
||||
|
||||
function showEditSubflowDialog(subflow) {
|
||||
editing_node = subflow;
|
||||
@@ -805,6 +870,29 @@ RED.editor = (function() {
|
||||
editConfig: showEditConfigNodeDialog,
|
||||
editSubflow: showEditSubflowDialog,
|
||||
validateNode: validateNode,
|
||||
updateNodeProperties: updateNodeProperties // TODO: only exposed for edit-undo
|
||||
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
|
||||
|
||||
createEditor: function(options) {
|
||||
var editor = ace.edit(options.id);
|
||||
editor.setTheme("ace/theme/tomorrow");
|
||||
if (options.mode) {
|
||||
editor.getSession().setMode(options.mode);
|
||||
}
|
||||
if (options.foldStyle) {
|
||||
editor.getSession().setFoldStyle(options.foldStyle);
|
||||
} else {
|
||||
editor.getSession().setFoldStyle('markbeginend');
|
||||
}
|
||||
if (options.options) {
|
||||
editor.setOptions(options.options);
|
||||
} else {
|
||||
editor.setOptions({
|
||||
enableBasicAutocompletion:true,
|
||||
enableSnippets:true
|
||||
});
|
||||
}
|
||||
editor.$blockScrolling = Infinity;
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -57,12 +57,58 @@ RED.keyboard = (function() {
|
||||
function removeHandler(key) {
|
||||
delete handlers[key];
|
||||
}
|
||||
|
||||
|
||||
|
||||
var dialog = $('<div id="keyboard-help-dialog" class="hide">'+
|
||||
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
|
||||
'<table class="keyboard-shortcuts">'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">a</span></td><td>Select all nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>Select all connected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</td></tr>'+
|
||||
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
|
||||
'<tr><td> </td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
|
||||
'<table class="keyboard-shortcuts">'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+
|
||||
'<tr><td></td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+
|
||||
'<tr><td></td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'</div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: "800",
|
||||
title:"Keyboard shortcuts",
|
||||
resizable: false,
|
||||
open: function() {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function() {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
|
||||
function showKeyboardHelp() {
|
||||
dialog.dialog("open");
|
||||
}
|
||||
|
||||
return {
|
||||
add: addHandler,
|
||||
remove: removeHandler,
|
||||
disable: function(){ active = false;},
|
||||
enable: function(){ active = true; }
|
||||
enable: function(){ active = true; },
|
||||
|
||||
showHelp: showKeyboardHelp
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -72,6 +72,19 @@ RED.library = (function() {
|
||||
var selectedLibraryItem = null;
|
||||
var libraryEditor = null;
|
||||
|
||||
// Orion editor has set/getText
|
||||
// ACE editor has set/getValue
|
||||
// normalise to set/getValue
|
||||
if (options.editor.setText) {
|
||||
// Orion doesn't like having pos passed in, so proxy the call to drop it
|
||||
options.editor.setValue = function(text,pos) {
|
||||
options.editor.setText.call(options.editor,text);
|
||||
}
|
||||
}
|
||||
if (options.editor.getText) {
|
||||
options.editor.getValue = options.editor.getText;
|
||||
}
|
||||
|
||||
function buildFileListItem(item) {
|
||||
var li = document.createElement("li");
|
||||
li.onmouseover = function(e) { $(this).addClass("list-hover"); };
|
||||
@@ -119,7 +132,7 @@ RED.library = (function() {
|
||||
$(this).addClass("list-selected");
|
||||
$.get("library/"+options.url+root+item.fn, function(data) {
|
||||
selectedLibraryItem = item;
|
||||
libraryEditor.setText(data);
|
||||
libraryEditor.setValue(data,-1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -144,7 +157,7 @@ RED.library = (function() {
|
||||
$("#node-select-library").children().remove();
|
||||
var bc = $("#node-dialog-library-breadcrumbs");
|
||||
bc.children().first().nextAll().remove();
|
||||
libraryEditor.setText('');
|
||||
libraryEditor.setValue('',-1);
|
||||
|
||||
$.getJSON("library/"+options.url,function(data) {
|
||||
$("#node-select-library").append(buildFileList("/",data));
|
||||
@@ -205,14 +218,18 @@ RED.library = (function() {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
require(["orion/editor/edit"], function(edit) {
|
||||
libraryEditor = edit({
|
||||
parent:document.getElementById('node-select-library-text'),
|
||||
lang:"js",
|
||||
readonly: true
|
||||
});
|
||||
libraryEditor = ace.edit('node-select-library-text');
|
||||
libraryEditor.setTheme("ace/theme/tomorrow");
|
||||
if (options.mode) {
|
||||
libraryEditor.getSession().setMode(options.mode);
|
||||
}
|
||||
libraryEditor.setOptions({
|
||||
readOnly: true,
|
||||
highlightActiveLine: false,
|
||||
highlightGutterLine: false
|
||||
});
|
||||
|
||||
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
|
||||
libraryEditor.$blockScrolling = Infinity;
|
||||
|
||||
$( "#node-dialog-library-lookup" ).dialog({
|
||||
title: options.type+" library",
|
||||
@@ -229,7 +246,7 @@ RED.library = (function() {
|
||||
var field = options.fields[i];
|
||||
$("#node-input-"+field).val(selectedLibraryItem[field]);
|
||||
}
|
||||
options.editor.setText(libraryEditor.getText());
|
||||
options.editor.setValue(libraryEditor.getValue(),-1);
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@@ -294,19 +311,26 @@ RED.library = (function() {
|
||||
//}
|
||||
}
|
||||
var queryArgs = [];
|
||||
var data = {};
|
||||
for (var i=0;i<options.fields.length;i++) {
|
||||
var field = options.fields[i];
|
||||
if (field == "name") {
|
||||
queryArgs.push("name="+encodeURIComponent(name));
|
||||
data.name = name;
|
||||
} else {
|
||||
queryArgs.push(encodeURIComponent(field)+"="+encodeURIComponent($("#node-input-"+field).val()));
|
||||
data[field] = $("#node-input-"+field).val();
|
||||
}
|
||||
}
|
||||
var queryString = queryArgs.join("&");
|
||||
|
||||
var text = options.editor.getText();
|
||||
$.post("library/"+options.url+'/'+fullpath+"?"+queryString,text,function() {
|
||||
RED.notify("Saved "+options.type,"success");
|
||||
data.text = options.editor.getValue();
|
||||
$.ajax({
|
||||
url:"library/"+options.url+'/'+fullpath,
|
||||
type: "POST",
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.notify("Saved "+options.type,"success");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.notify("Saved failed: "+xhr.responseJSON.message,"error");
|
||||
});
|
||||
}
|
||||
$( "#node-dialog-library-save-confirm" ).dialog({
|
||||
@@ -356,12 +380,34 @@ RED.library = (function() {
|
||||
|
||||
}
|
||||
|
||||
function exportFlow() {
|
||||
//TODO: don't rely on the main dialog
|
||||
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
|
||||
$("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
|
||||
$("#node-input-filename").attr('nodes',JSON.stringify(nns));
|
||||
$( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" );
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
RED.menu.setDisabled("btn-export-menu",true);
|
||||
RED.menu.setDisabled("btn-export-clipboard",true);
|
||||
RED.menu.setDisabled("btn-export-library",true);
|
||||
} else {
|
||||
RED.menu.setDisabled("btn-export-menu",false);
|
||||
RED.menu.setDisabled("btn-export-clipboard",false);
|
||||
RED.menu.setDisabled("btn-export-library",false);
|
||||
}
|
||||
});
|
||||
|
||||
loadFlowLibrary();
|
||||
},
|
||||
create: createUI,
|
||||
loadFlowLibrary: loadFlowLibrary
|
||||
loadFlowLibrary: loadFlowLibrary,
|
||||
|
||||
export: exportFlow
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,7 @@
|
||||
RED.palette = (function() {
|
||||
|
||||
var exclusion = ['config','unknown','deprecated'];
|
||||
var core = ['input', 'output', 'function', 'subflows', 'social', 'storage', 'analysis', 'advanced'];
|
||||
var core = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
|
||||
|
||||
function createCategoryContainer(category){
|
||||
var escapedCategory = category.replace(" ","_");
|
||||
@@ -98,7 +98,6 @@ RED.palette = (function() {
|
||||
}
|
||||
|
||||
function addNodeType(nt,def) {
|
||||
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
if ($("#palette_node_"+nodeTypeId).length) {
|
||||
return;
|
||||
@@ -121,17 +120,15 @@ RED.palette = (function() {
|
||||
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
|
||||
}
|
||||
|
||||
d.innerHTML = '<div class="palette_label"></div>';
|
||||
|
||||
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
|
||||
|
||||
d.className="palette_node";
|
||||
|
||||
|
||||
if (def.icon) {
|
||||
d.style.backgroundImage = "url(icons/"+def.icon+")";
|
||||
d.style.backgroundSize = "18px 27px";
|
||||
if (def.align == "right") {
|
||||
d.style.backgroundPosition = "95% 50%";
|
||||
} else if (def.inputs > 0) {
|
||||
d.style.backgroundPosition = "10% 50%";
|
||||
}
|
||||
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
|
||||
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+def.icon+")"}).appendTo(iconContainer);
|
||||
}
|
||||
|
||||
d.style.backgroundColor = def.color;
|
||||
@@ -169,6 +166,7 @@ RED.palette = (function() {
|
||||
container:'body'
|
||||
});
|
||||
$(d).click(function() {
|
||||
RED.view.focus();
|
||||
var help = '<div class="node-help">'+($("script[data-help-name|='"+d.type+"']").html()||"")+"</div>";
|
||||
$("#tab-info").html(help);
|
||||
});
|
||||
@@ -176,16 +174,41 @@ RED.palette = (function() {
|
||||
helper: 'clone',
|
||||
appendTo: 'body',
|
||||
revert: true,
|
||||
revertDuration: 50
|
||||
revertDuration: 50,
|
||||
start: function() {RED.view.focus();}
|
||||
});
|
||||
|
||||
if (def.category == "subflows") {
|
||||
$(d).dblclick(function(e) {
|
||||
RED.workspaces.show(nt.substring(8));
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
setLabel(nt,$(d),label);
|
||||
|
||||
var categoryNode = $("#palette-container-"+category);
|
||||
if (categoryNode.find(".palette_node").length === 1) {
|
||||
if (!categoryNode.find("i").hasClass("expanded")) {
|
||||
categoryNode.find(".palette-content").slideToggle();
|
||||
categoryNode.find("i").toggleClass("expanded");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function removeNodeType(nt) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
$("#palette_node_"+nodeTypeId).remove();
|
||||
var paletteNode = $("#palette_node_"+nodeTypeId);
|
||||
var categoryNode = paletteNode.closest(".palette-category");
|
||||
paletteNode.remove();
|
||||
if (categoryNode.find(".palette_node").length === 0) {
|
||||
if (categoryNode.find("i").hasClass("expanded")) {
|
||||
categoryNode.find(".palette-content").slideToggle();
|
||||
categoryNode.find("i").toggleClass("expanded");
|
||||
}
|
||||
}
|
||||
}
|
||||
function hideNodeType(nt) {
|
||||
var nodeTypeId = escapeNodeType(nt);
|
||||
@@ -232,7 +255,8 @@ RED.palette = (function() {
|
||||
|
||||
var re = new RegExp(val,'i');
|
||||
$(".palette_node").each(function(i,el) {
|
||||
if (val === "" || re.test(el.id)) {
|
||||
var currentLabel = $(el).find(".palette_label").text();
|
||||
if (val === "" || re.test(el.id) || re.test(currentLabel)) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -59,10 +59,8 @@ RED.sidebar = (function() {
|
||||
$("#chart-zoom-controls").css("right",newChartRight+20);
|
||||
$("#sidebar").width(0);
|
||||
RED.menu.setSelected("btn-sidebar",true);
|
||||
RED.view.resize();
|
||||
eventHandler.emit("resize");
|
||||
}
|
||||
|
||||
|
||||
sidebarSeparator.width = $("#sidebar").width();
|
||||
},
|
||||
drag: function(event,ui) {
|
||||
@@ -101,11 +99,9 @@ RED.sidebar = (function() {
|
||||
$("#sidebar").width(newSidebarWidth);
|
||||
|
||||
sidebar_tabs.resize();
|
||||
RED.view.resize();
|
||||
|
||||
eventHandler.emit("resize");
|
||||
},
|
||||
stop:function(event,ui) {
|
||||
RED.view.resize();
|
||||
if (sidebarSeparator.closing) {
|
||||
$("#sidebar").removeClass("closing");
|
||||
RED.menu.setSelected("btn-sidebar",false);
|
||||
@@ -117,6 +113,7 @@ RED.sidebar = (function() {
|
||||
}
|
||||
$("#sidebar-separator").css("left","auto");
|
||||
$("#sidebar-separator").css("right",($("#sidebar").width()+13)+"px");
|
||||
eventHandler.emit("resize");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -127,6 +124,7 @@ RED.sidebar = (function() {
|
||||
$("#main-container").removeClass("sidebar-closed");
|
||||
sidebar_tabs.resize();
|
||||
}
|
||||
eventHandler.emit("resize");
|
||||
}
|
||||
|
||||
function showSidebar(id) {
|
||||
@@ -145,13 +143,33 @@ RED.sidebar = (function() {
|
||||
RED.sidebar.info.show();
|
||||
}
|
||||
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
handlers[evt].push(func);
|
||||
},
|
||||
emit: function(evt,arg) {
|
||||
if (handlers[evt]) {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
init: init,
|
||||
addTab: addTab,
|
||||
removeTab: removeTab,
|
||||
show: showSidebar,
|
||||
containsTab: containsTab,
|
||||
toggleSidebar: toggleSidebar
|
||||
toggleSidebar: toggleSidebar,
|
||||
on: eventHandler.on
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
402
public/red/ui/subflow.js
Normal file
402
public/red/ui/subflow.js
Normal file
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* Copyright 2015 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.
|
||||
**/
|
||||
|
||||
RED.subflow = (function() {
|
||||
|
||||
|
||||
function getSubflow() {
|
||||
return RED.nodes.subflow(RED.workspaces.active());
|
||||
}
|
||||
|
||||
function findAvailableSubflowIOPosition(subflow) {
|
||||
var pos = {x:70,y:70};
|
||||
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
|
||||
var port;
|
||||
if (i < subflow.out.length) {
|
||||
port = subflow.out[i];
|
||||
} else {
|
||||
port = subflow.in[i-subflow.out.length];
|
||||
}
|
||||
if (port.x == pos.x && port.y == pos.y) {
|
||||
pos.x += 55;
|
||||
i=0;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
function addSubflowInput() {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
var newInput = {
|
||||
type:"subflow",
|
||||
direction:"in",
|
||||
z:subflow.id,
|
||||
i:subflow.in.length,
|
||||
x:position.x,
|
||||
y:position.y,
|
||||
id:RED.nodes.id()
|
||||
};
|
||||
var oldInCount = subflow.in.length;
|
||||
subflow.in.push(newInput);
|
||||
subflow.dirty = true;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = subflow.changed;
|
||||
subflow.changed = true;
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+subflow.id) {
|
||||
n.changed = true;
|
||||
n.inputs = subflow.in.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
}
|
||||
});
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:subflow,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged,
|
||||
subflow: {
|
||||
inputCount: oldInCount
|
||||
}
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",true);
|
||||
RED.view.select();
|
||||
}
|
||||
|
||||
function addSubflowOutput(id) {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
|
||||
var newOutput = {
|
||||
type:"subflow",
|
||||
direction:"out",
|
||||
z:subflow.id,
|
||||
i:subflow.out.length,
|
||||
x:position.x,
|
||||
y:position.y,
|
||||
id:RED.nodes.id()
|
||||
};
|
||||
var oldOutCount = subflow.out.length;
|
||||
subflow.out.push(newOutput);
|
||||
subflow.dirty = true;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = subflow.changed;
|
||||
subflow.changed = true;
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+subflow.id) {
|
||||
n.changed = true;
|
||||
n.outputs = subflow.out.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
}
|
||||
});
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:subflow,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged,
|
||||
subflow: {
|
||||
outputCount: oldOutCount
|
||||
}
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
RED.view.select();
|
||||
}
|
||||
|
||||
function init() {
|
||||
$("#workspace-subflow-edit").click(function(event) {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
|
||||
event.preventDefault();
|
||||
});
|
||||
$("#workspace-subflow-add-input").click(function(event) {
|
||||
event.preventDefault();
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
addSubflowInput();
|
||||
});
|
||||
$("#workspace-subflow-add-output").click(function(event) {
|
||||
event.preventDefault();
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
addSubflowOutput();
|
||||
});
|
||||
|
||||
$("#workspace-subflow-delete").click(function(event) {
|
||||
event.preventDefault();
|
||||
var removedNodes = [];
|
||||
var removedLinks = [];
|
||||
var startDirty = RED.nodes.dirty();
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+getSubflow().id) {
|
||||
removedNodes.push(n);
|
||||
}
|
||||
if (n.z == getSubflow().id) {
|
||||
removedNodes.push(n);
|
||||
}
|
||||
});
|
||||
|
||||
for (var i=0;i<removedNodes.length;i++) {
|
||||
var rmlinks = RED.nodes.remove(removedNodes[i].id);
|
||||
removedLinks = removedLinks.concat(rmlinks);
|
||||
}
|
||||
|
||||
var activeSubflow = getSubflow();
|
||||
|
||||
RED.nodes.removeSubflow(activeSubflow);
|
||||
|
||||
RED.history.push({
|
||||
t:'delete',
|
||||
nodes:removedNodes,
|
||||
links:removedLinks,
|
||||
subflow: activeSubflow,
|
||||
dirty:startDirty
|
||||
});
|
||||
|
||||
RED.workspaces.remove(activeSubflow);
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
RED.menu.setDisabled("btn-convert-subflow",true);
|
||||
} else {
|
||||
RED.menu.setDisabled("btn-convert-subflow",false);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function createSubflow() {
|
||||
var lastIndex = 0;
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
|
||||
if (m) {
|
||||
lastIndex = Math.max(lastIndex,m[1]);
|
||||
}
|
||||
});
|
||||
|
||||
var name = "Subflow "+(lastIndex+1);
|
||||
|
||||
var subflowId = RED.nodes.id();
|
||||
var subflow = {
|
||||
type:"subflow",
|
||||
id:subflowId,
|
||||
name:name,
|
||||
in: [],
|
||||
out: []
|
||||
};
|
||||
RED.nodes.addSubflow(subflow);
|
||||
RED.history.push({
|
||||
t:'createSubflow',
|
||||
subflow: subflow,
|
||||
dirty:RED.nodes.dirty()
|
||||
});
|
||||
RED.workspaces.show(subflowId);
|
||||
}
|
||||
|
||||
function convertToSubflow() {
|
||||
var selection = RED.view.selection();
|
||||
if (!selection.nodes) {
|
||||
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error");
|
||||
return;
|
||||
}
|
||||
var i;
|
||||
var nodes = {};
|
||||
var new_links = [];
|
||||
var removedLinks = [];
|
||||
|
||||
var candidateInputs = [];
|
||||
var candidateOutputs = [];
|
||||
|
||||
var boundingBox = [selection.nodes[0].x,
|
||||
selection.nodes[0].y,
|
||||
selection.nodes[0].x,
|
||||
selection.nodes[0].y];
|
||||
|
||||
for (i=0;i<selection.nodes.length;i++) {
|
||||
var n = selection.nodes[i];
|
||||
nodes[n.id] = {n:n,outputs:{}};
|
||||
boundingBox = [
|
||||
Math.min(boundingBox[0],n.x),
|
||||
Math.min(boundingBox[1],n.y),
|
||||
Math.max(boundingBox[2],n.x),
|
||||
Math.max(boundingBox[3],n.y)
|
||||
]
|
||||
}
|
||||
|
||||
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
|
||||
|
||||
RED.nodes.eachLink(function(link) {
|
||||
if (nodes[link.source.id] && nodes[link.target.id]) {
|
||||
// A link wholely within the selection
|
||||
}
|
||||
|
||||
if (nodes[link.source.id] && !nodes[link.target.id]) {
|
||||
// An outbound link from the selection
|
||||
candidateOutputs.push(link);
|
||||
removedLinks.push(link);
|
||||
}
|
||||
if (!nodes[link.source.id] && nodes[link.target.id]) {
|
||||
// An inbound link
|
||||
candidateInputs.push(link);
|
||||
removedLinks.push(link);
|
||||
}
|
||||
});
|
||||
|
||||
var outputs = {};
|
||||
candidateOutputs = candidateOutputs.filter(function(v) {
|
||||
if (outputs[v.source.id+":"+v.sourcePort]) {
|
||||
outputs[v.source.id+":"+v.sourcePort].targets.push(v.target);
|
||||
return false;
|
||||
}
|
||||
v.targets = [];
|
||||
v.targets.push(v.target);
|
||||
outputs[v.source.id+":"+v.sourcePort] = v;
|
||||
return true;
|
||||
});
|
||||
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
|
||||
|
||||
if (candidateInputs.length > 1) {
|
||||
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error");
|
||||
return;
|
||||
}
|
||||
//if (candidateInputs.length == 0) {
|
||||
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
|
||||
// return;
|
||||
//}
|
||||
|
||||
|
||||
var lastIndex = 0;
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
|
||||
if (m) {
|
||||
lastIndex = Math.max(lastIndex,m[1]);
|
||||
}
|
||||
});
|
||||
|
||||
var name = "Subflow "+(lastIndex+1);
|
||||
|
||||
var subflowId = RED.nodes.id();
|
||||
var subflow = {
|
||||
type:"subflow",
|
||||
id:subflowId,
|
||||
name:name,
|
||||
in: candidateInputs.map(function(v,i) { var index = i; return {
|
||||
type:"subflow",
|
||||
direction:"in",
|
||||
x:v.target.x-(v.target.w/2)-80,
|
||||
y:v.target.y,
|
||||
z:subflowId,
|
||||
i:index,
|
||||
id:RED.nodes.id(),
|
||||
wires:[{id:v.target.id}]
|
||||
}}),
|
||||
out: candidateOutputs.map(function(v,i) { var index = i; return {
|
||||
type:"subflow",
|
||||
direction:"in",
|
||||
x:v.source.x+(v.source.w/2)+80,
|
||||
y:v.source.y,
|
||||
z:subflowId,
|
||||
i:index,
|
||||
id:RED.nodes.id(),
|
||||
wires:[{id:v.source.id,port:v.sourcePort}]
|
||||
}})
|
||||
};
|
||||
RED.nodes.addSubflow(subflow);
|
||||
|
||||
var subflowInstance = {
|
||||
id:RED.nodes.id(),
|
||||
type:"subflow:"+subflow.id,
|
||||
x: center[0],
|
||||
y: center[1],
|
||||
z: RED.workspaces.active(),
|
||||
inputs: subflow.in.length,
|
||||
outputs: subflow.out.length,
|
||||
h: Math.max(30/*node_height*/,(subflow.out.length||0) * 15),
|
||||
changed:true
|
||||
}
|
||||
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
|
||||
RED.editor.validateNode(subflowInstance);
|
||||
RED.nodes.add(subflowInstance);
|
||||
|
||||
candidateInputs.forEach(function(l) {
|
||||
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
|
||||
candidateOutputs.forEach(function(output,i) {
|
||||
output.targets.forEach(function(target) {
|
||||
var link = {source:subflowInstance, sourcePort:i, target: target};
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
|
||||
subflow.in.forEach(function(input) {
|
||||
input.wires.forEach(function(wire) {
|
||||
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
subflow.out.forEach(function(output,i) {
|
||||
output.wires.forEach(function(wire) {
|
||||
var link = {source: RED.nodes.node(wire.id), sourcePort: wire.port , target: output }
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
|
||||
for (i=0;i<removedLinks.length;i++) {
|
||||
RED.nodes.removeLink(removedLinks[i]);
|
||||
}
|
||||
|
||||
for (i=0;i<selection.nodes.length;i++) {
|
||||
selection.nodes[i].z = subflow.id;
|
||||
}
|
||||
|
||||
RED.history.push({
|
||||
t:'createSubflow',
|
||||
nodes:[subflowInstance.id],
|
||||
links:new_links,
|
||||
subflow: subflow,
|
||||
|
||||
activeWorkspace: RED.workspaces.active(),
|
||||
removedLinks: removedLinks,
|
||||
|
||||
dirty:RED.nodes.dirty()
|
||||
});
|
||||
|
||||
RED.editor.validateNode(subflow);
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return {
|
||||
init: init,
|
||||
createSubflow: createSubflow,
|
||||
convertToSubflow: convertToSubflow
|
||||
}
|
||||
})();
|
||||
@@ -32,6 +32,8 @@ RED.sidebar.info = (function() {
|
||||
content.style.paddingLeft = "4px";
|
||||
content.style.paddingRight = "4px";
|
||||
|
||||
var propertiesExpanded = false;
|
||||
|
||||
function show() {
|
||||
if (!RED.sidebar.containsTab("info")) {
|
||||
RED.sidebar.addTab("info",content,false);
|
||||
@@ -59,6 +61,9 @@ RED.sidebar.info = (function() {
|
||||
function refresh(node) {
|
||||
var table = '<table class="node-info"><tbody>';
|
||||
table += '<tr class="blank"><td colspan="2">Node</td></tr>';
|
||||
if (node.type != "subflow" && node.name) {
|
||||
table += "<tr><td>Name</td><td> "+node.name+"</td></tr>";
|
||||
}
|
||||
table += "<tr><td>Type</td><td> "+node.type+"</td></tr>";
|
||||
table += "<tr><td>ID</td><td> "+node.id+"</td></tr>";
|
||||
|
||||
@@ -84,18 +89,22 @@ RED.sidebar.info = (function() {
|
||||
table += "<tr><td>instances</td><td>"+userCount+"</td></tr>";
|
||||
}
|
||||
|
||||
if (node.type != "subflow" && node.type != "comment") {
|
||||
table += '<tr class="blank"><td colspan="2">Properties</td></tr>';
|
||||
if (!m && node.type != "subflow" && node.type != "comment") {
|
||||
table += '<tr class="blank"><td colspan="2"><a href="#" class="node-info-property-header"><i style="width: 10px; text-align: center;" class="fa fa-caret-'+(propertiesExpanded?"down":"right")+'"></i> Properties</a></td></tr>';
|
||||
if (node._def) {
|
||||
for (var n in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(n)) {
|
||||
if (n != "name" && node._def.defaults.hasOwnProperty(n)) {
|
||||
var val = node[n]||"";
|
||||
var type = typeof val;
|
||||
if (type === "string") {
|
||||
if (val.length > 30) {
|
||||
val = val.substring(0,30)+" ...";
|
||||
if (val.length === 0) {
|
||||
val += '<span style="font-style: italic; color: #ccc;">blank</span>';
|
||||
} else {
|
||||
if (val.length > 30) {
|
||||
val = val.substring(0,30)+" ...";
|
||||
}
|
||||
val = val.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
}
|
||||
val = val.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
} else if (type === "number") {
|
||||
val = val.toString();
|
||||
} else if ($.isArray(val)) {
|
||||
@@ -113,12 +122,12 @@ RED.sidebar.info = (function() {
|
||||
val = val.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
}
|
||||
|
||||
table += "<tr><td>"+n+"</td><td>"+val+"</td></tr>";
|
||||
table += '<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
table += "</tbody></table><br/>";
|
||||
table += "</tbody></table><hr/>";
|
||||
if (node.type != "comment") {
|
||||
var helpText = $("script[data-help-name|='"+node.type+"']").html()||"";
|
||||
table += '<div class="node-help">'+helpText+"</div>";
|
||||
@@ -131,13 +140,52 @@ RED.sidebar.info = (function() {
|
||||
}
|
||||
|
||||
$("#tab-info").html(table);
|
||||
|
||||
$(".node-info-property-header").click(function(e) {
|
||||
var icon = $(this).find("i");
|
||||
if (icon.hasClass("fa-caret-right")) {
|
||||
icon.removeClass("fa-caret-right");
|
||||
icon.addClass("fa-caret-down");
|
||||
$(".node-info-property-row").show();
|
||||
propertiesExpanded = true;
|
||||
} else {
|
||||
icon.addClass("fa-caret-right");
|
||||
icon.removeClass("fa-caret-down");
|
||||
$(".node-info-property-row").hide();
|
||||
propertiesExpanded = false;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function clear() {
|
||||
$("#tab-info").html("");
|
||||
}
|
||||
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (selection.nodes) {
|
||||
if (selection.nodes.length == 1) {
|
||||
var node = selection.nodes[0];
|
||||
if (node.type === "subflow" && node.direction) {
|
||||
refresh(RED.nodes.subflow(node.z));
|
||||
} else {
|
||||
refresh(node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
if (subflow) {
|
||||
refresh(subflow);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
show: show,
|
||||
refresh:refresh,
|
||||
clear: function() {
|
||||
$("#tab-info").html("");
|
||||
}
|
||||
clear: clear
|
||||
}
|
||||
})();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
270
public/red/ui/workspaces.js
Normal file
270
public/red/ui/workspaces.js
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright 2015 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.
|
||||
**/
|
||||
|
||||
|
||||
RED.workspaces = (function() {
|
||||
|
||||
var activeWorkspace = 0;
|
||||
var workspaceIndex = 0;
|
||||
|
||||
function addWorkspace(ws) {
|
||||
if (ws) {
|
||||
workspace_tabs.addTab(ws);
|
||||
workspace_tabs.resize();
|
||||
} else {
|
||||
var tabId = RED.nodes.id();
|
||||
do {
|
||||
workspaceIndex += 1;
|
||||
} while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
|
||||
|
||||
ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex};
|
||||
RED.nodes.addWorkspace(ws);
|
||||
workspace_tabs.addTab(ws);
|
||||
workspace_tabs.activateTab(tabId);
|
||||
RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
|
||||
RED.nodes.dirty(true);
|
||||
}
|
||||
}
|
||||
function deleteWorkspace(ws,force) {
|
||||
if (workspace_tabs.count() == 1) {
|
||||
return;
|
||||
}
|
||||
var nodes = [];
|
||||
if (!force) {
|
||||
nodes = RED.nodes.filterNodes({z:ws.id});
|
||||
}
|
||||
if (force || nodes.length === 0) {
|
||||
removeWorkspace(ws);
|
||||
var historyEvent = RED.nodes.removeWorkspace(ws.id);
|
||||
historyEvent.t = 'delete';
|
||||
historyEvent.dirty = RED.nodes.dirty();
|
||||
historyEvent.workspaces = [ws];
|
||||
RED.history.push(historyEvent);
|
||||
RED.nodes.dirty(true);
|
||||
} else {
|
||||
$( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
|
||||
$( "#node-dialog-delete-workspace-name" ).text(ws.label);
|
||||
$( "#node-dialog-delete-workspace" ).dialog('open');
|
||||
}
|
||||
}
|
||||
function showRenameWorkspaceDialog(id) {
|
||||
var ws = RED.nodes.workspace(id);
|
||||
$( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws);
|
||||
|
||||
if (workspace_tabs.count() == 1) {
|
||||
$( "#node-dialog-rename-workspace").next().find(".leftButton")
|
||||
.prop('disabled',true)
|
||||
.addClass("ui-state-disabled");
|
||||
} else {
|
||||
$( "#node-dialog-rename-workspace").next().find(".leftButton")
|
||||
.prop('disabled',false)
|
||||
.removeClass("ui-state-disabled");
|
||||
}
|
||||
|
||||
$( "#node-input-workspace-name" ).val(ws.label);
|
||||
$( "#node-dialog-rename-workspace" ).dialog("open");
|
||||
}
|
||||
|
||||
var workspace_tabs = RED.tabs.create({
|
||||
id: "workspace-tabs",
|
||||
onchange: function(tab) {
|
||||
if (tab.type == "subflow") {
|
||||
$("#workspace-toolbar").show();
|
||||
} else {
|
||||
$("#workspace-toolbar").hide();
|
||||
}
|
||||
var event = {
|
||||
old: activeWorkspace
|
||||
}
|
||||
activeWorkspace = tab.id;
|
||||
event.workspace = activeWorkspace;
|
||||
|
||||
eventHandler.emit("change",event);
|
||||
},
|
||||
ondblclick: function(tab) {
|
||||
if (tab.type != "subflow") {
|
||||
showRenameWorkspaceDialog(tab.id);
|
||||
} else {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
|
||||
}
|
||||
},
|
||||
onadd: function(tab) {
|
||||
RED.menu.addItem("btn-workspace-menu",{
|
||||
id:"btn-workspace-menu-"+tab.id.replace(".","-"),
|
||||
label:tab.label,
|
||||
onselect:function() {
|
||||
workspace_tabs.activateTab(tab.id);
|
||||
}
|
||||
});
|
||||
RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1);
|
||||
},
|
||||
onremove: function(tab) {
|
||||
RED.menu.setDisabled("btn-workspace-delete",workspace_tabs.count() == 1);
|
||||
RED.menu.removeItem("btn-workspace-menu-"+tab.id.replace(".","-"));
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
|
||||
$( "#node-dialog-rename-workspace" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
title: "Rename sheet",
|
||||
buttons: [
|
||||
{
|
||||
class: 'leftButton',
|
||||
text: "Delete",
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
$( this ).dialog( "close" );
|
||||
deleteWorkspace(workspace);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
var label = $( "#node-input-workspace-name" ).val();
|
||||
if (workspace.label != label) {
|
||||
workspace_tabs.renameTab(workspace.id,label);
|
||||
RED.nodes.dirty(true);
|
||||
$("#btn-workspace-menu-"+workspace.id.replace(".","-")).text(label);
|
||||
// TODO: update entry in menu
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
$( "#node-dialog-delete-workspace" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
title: "Confirm delete",
|
||||
buttons: [
|
||||
{
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
deleteWorkspace(workspace,true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function init() {
|
||||
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
|
||||
RED.sidebar.on("resize",workspace_tabs.resize);
|
||||
|
||||
RED.menu.setAction('btn-workspace-delete',function() {
|
||||
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: DRY
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
handlers[evt].push(func);
|
||||
},
|
||||
emit: function(evt,arg) {
|
||||
if (handlers[evt]) {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function removeWorkspace(ws) {
|
||||
if (!ws) {
|
||||
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
|
||||
} else {
|
||||
if (workspace_tabs.contains(ws.id)) {
|
||||
workspace_tabs.removeTab(ws.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
init: init,
|
||||
on: eventHandler.on,
|
||||
add: addWorkspace,
|
||||
remove: removeWorkspace,
|
||||
|
||||
edit: function(id) {
|
||||
showRenameWorkspaceDialog(id||activeWorkspace);
|
||||
},
|
||||
contains: function(id) {
|
||||
return workspace_tabs.contains(id);
|
||||
},
|
||||
count: function() {
|
||||
return workspace_tabs.count();
|
||||
},
|
||||
active: function() {
|
||||
return activeWorkspace
|
||||
},
|
||||
show: function(id) {
|
||||
if (!workspace_tabs.contains(id)) {
|
||||
var sf = RED.nodes.subflow(id);
|
||||
if (sf) {
|
||||
addWorkspace({type:"subflow",id:id,label:"Subflow: "+sf.name, closeable: true});
|
||||
}
|
||||
}
|
||||
workspace_tabs.activateTab(id);
|
||||
},
|
||||
refresh: function() {
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
if (workspace_tabs.contains(sf.id)) {
|
||||
workspace_tabs.renameTab(sf.id,"Subflow: "+sf.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
resize: function() {
|
||||
workspace_tabs.resize();
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
* Copyright 2014, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -109,7 +109,53 @@ RED.user = (function() {
|
||||
})
|
||||
}
|
||||
|
||||
function updateUserMenu() {
|
||||
$("#btn-usermenu-submenu li").remove();
|
||||
if (RED.settings.user.anonymous) {
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-login",
|
||||
label:"Login",
|
||||
onselect: function() {
|
||||
RED.user.login({cancelable:true},function() {
|
||||
RED.settings.load(function() {
|
||||
RED.notify("Logged in as "+RED.settings.user.username,"success");
|
||||
updateUserMenu();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-username",
|
||||
label:"<b>"+RED.settings.user.username+"</b>"
|
||||
});
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"btn-logout",
|
||||
label:"Logout",
|
||||
onselect: function() {
|
||||
RED.user.logout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function init() {
|
||||
if (RED.settings.user) {
|
||||
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
|
||||
.prependTo(".header-toolbar");
|
||||
|
||||
RED.menu.init({id:"btn-usermenu",
|
||||
options: []
|
||||
});
|
||||
updateUserMenu();
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
init: init,
|
||||
login: login,
|
||||
logout: logout
|
||||
}
|
||||
|
||||
@@ -259,7 +259,9 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
||||
left:0px;
|
||||
right:0px;
|
||||
}
|
||||
|
||||
#chart svg:focus {
|
||||
outline: none;
|
||||
}
|
||||
#workspace-toolbar {
|
||||
display: none;
|
||||
position: absolute;
|
||||
@@ -279,7 +281,7 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
||||
|
||||
#palette {
|
||||
background: #f3f3f3;
|
||||
width: 140px;
|
||||
width: 170px;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
@@ -389,11 +391,15 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
||||
clear: both;
|
||||
}
|
||||
.palette_label {
|
||||
margin: 4px 0;
|
||||
margin: 4px 0 4px 28px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
.palette_label_right {
|
||||
margin: 4px 28px 4px 0;
|
||||
}
|
||||
|
||||
.palette_node {
|
||||
cursor:move;
|
||||
font-size:13px;
|
||||
@@ -404,7 +410,7 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
||||
border: 2px solid #999;
|
||||
background-position: 5% 50%;
|
||||
background-repeat: no-repeat;
|
||||
width: 90px;
|
||||
width: 120px;
|
||||
background-size: contain;
|
||||
position: relative;
|
||||
}
|
||||
@@ -425,13 +431,38 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.palette_port_output {
|
||||
left:85px;
|
||||
left:auto;
|
||||
right: -6px;
|
||||
}
|
||||
|
||||
.palette_node:hover .palette_port {
|
||||
border-color: #999;
|
||||
background-color: #eee;
|
||||
}
|
||||
.palette_icon_container {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top:0;
|
||||
bottom:0;
|
||||
left:0;
|
||||
width: 30px;
|
||||
border-right: 2px solid rgba(0,0,0,0.1);
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
}
|
||||
.palette_icon_container_right {
|
||||
left: auto;
|
||||
right: 0;
|
||||
border-right: none;
|
||||
border-left: 2px solid rgba(0,0,0,0.1);
|
||||
}
|
||||
.palette_icon {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 100%;
|
||||
background-position: 50% 50%;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
background: #fff;
|
||||
@@ -457,7 +488,7 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
||||
#workspace {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top:5px; left:160px; bottom: 10px; right: 330px;
|
||||
top:5px; left:190px; bottom: 10px; right: 330px;
|
||||
}
|
||||
#chart-zoom-controls {
|
||||
position: absolute;
|
||||
@@ -750,10 +781,12 @@ g.link_unknown path.link_line {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#dialog-form {
|
||||
.dialog-form, #dialog-form, #dialog-config-form {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.form-row {
|
||||
clear: both;
|
||||
margin-bottom:10px;
|
||||
@@ -786,6 +819,7 @@ button.input-append-right {
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #999;
|
||||
max-width: 450px;
|
||||
}
|
||||
.form-tips code {
|
||||
border: none;
|
||||
@@ -793,6 +827,7 @@ button.input-append-right {
|
||||
}
|
||||
|
||||
table.node-info {
|
||||
font-size: 14px;
|
||||
margin: 5px;
|
||||
width: 97%;
|
||||
}
|
||||
@@ -823,6 +858,16 @@ table.node-info td:last-child{
|
||||
div.node-info {
|
||||
margin: 5px;
|
||||
}
|
||||
.node-info-property-header {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.node-info-property-header:hover,
|
||||
.node-info-property-header:focus {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.input-error {
|
||||
border-color: rgb(214, 97, 95) !important;
|
||||
@@ -909,13 +954,21 @@ div.node-info {
|
||||
#node-help {
|
||||
width: 700px;
|
||||
}
|
||||
#node-help * td {
|
||||
padding: 0.8em 0.5em;
|
||||
#keyboard-help-dialog {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
#node-help * tr > td:first-child+td+td {
|
||||
padding-left: 5em;
|
||||
.keyboard-shortcuts {
|
||||
padding: 10px;
|
||||
}
|
||||
.keyboard-shortcuts td {
|
||||
padding: 7px 5px;
|
||||
margin-bottom: 10px;
|
||||
white-space: pre;
|
||||
}
|
||||
.keyboard-shortcuts td:first-child {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.help-key {
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px;
|
||||
@@ -965,6 +1018,7 @@ div.node-info {
|
||||
border:1px solid #ccc;
|
||||
border-radius:5px;
|
||||
overflow: hidden;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
#workspace-tabs {
|
||||
margin-right: 28px;
|
||||
@@ -1017,7 +1071,7 @@ ul.red-ui-tabs li {
|
||||
margin: 0 5px 0 0;
|
||||
height: 23px;
|
||||
line-height: 17px;
|
||||
max-width: 150px;
|
||||
max-width: 200px;
|
||||
width: 14%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
55
red.js
Normal file → Executable file
55
red.js
Normal file → Executable file
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,21 +21,24 @@ var express = require("express");
|
||||
var crypto = require("crypto");
|
||||
var nopt = require("nopt");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var RED = require("./red/red.js");
|
||||
|
||||
var server;
|
||||
var app = express();
|
||||
|
||||
var settingsFile = "./settings";
|
||||
var settingsFile;
|
||||
var flowFile;
|
||||
|
||||
var knownOpts = {
|
||||
"settings":[path],
|
||||
"userDir":[path],
|
||||
"v": Boolean,
|
||||
"help": Boolean
|
||||
};
|
||||
var shortHands = {
|
||||
"s":["--settings"],
|
||||
"u":["--userDir"],
|
||||
"?":["--help"]
|
||||
};
|
||||
nopt.invalidHandler = function(k,v,t) {
|
||||
@@ -45,10 +49,11 @@ var parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
|
||||
|
||||
if (parsedArgs.help) {
|
||||
console.log("Node-RED v"+RED.version());
|
||||
console.log("Usage: node red.js [-v] [-?] [--settings settings.js] [flows.json]");
|
||||
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR] [flows.json]");
|
||||
console.log("");
|
||||
console.log("Options:");
|
||||
console.log(" -s, --settings FILE use specified settings file");
|
||||
console.log(" -u, --userDir DIR use specified user directory");
|
||||
console.log(" -v enable verbose output");
|
||||
console.log(" -?, --help show usage");
|
||||
console.log("");
|
||||
@@ -60,13 +65,33 @@ if (parsedArgs.argv.remain.length > 0) {
|
||||
}
|
||||
|
||||
if (parsedArgs.settings) {
|
||||
// User-specified settings file
|
||||
settingsFile = parsedArgs.settings;
|
||||
} else if (parsedArgs.userDir && fs.existsSync(path.join(parsedArgs.userDir,"settings.js"))) {
|
||||
// User-specified userDir that contains a settings.js
|
||||
settingsFile = path.join(parsedArgs.userDir,"settings.js");
|
||||
} else {
|
||||
if (fs.existsSync(path.join(process.env.NODE_RED_HOME,".config.json"))) {
|
||||
// NODE_RED_HOME contains user data - use its settings.js
|
||||
settingsFile = path.join(process.env.NODE_RED_HOME,"settings.js");
|
||||
} else {
|
||||
var userSettingsFile = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,".node-red","settings.js");
|
||||
if (fs.existsSync(userSettingsFile)) {
|
||||
// $HOME/.node-red/settings.js exists
|
||||
settingsFile = userSettingsFile;
|
||||
} else {
|
||||
// Use default settings.js
|
||||
settingsFile = "./settings";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var settings = require(settingsFile);
|
||||
settings.settingsFile = settingsFile;
|
||||
} catch(err) {
|
||||
if (err.code == 'MODULE_NOT_FOUND') {
|
||||
console.log("Unable to load settings file "+settingsFile);
|
||||
console.log("Unable to load settings file: "+settingsFile);
|
||||
} else {
|
||||
console.log(err);
|
||||
}
|
||||
@@ -117,17 +142,23 @@ if (settings.httpNodeRoot !== false) {
|
||||
settings.uiPort = settings.uiPort||1880;
|
||||
settings.uiHost = settings.uiHost||"0.0.0.0";
|
||||
|
||||
settings.flowFile = flowFile || settings.flowFile;
|
||||
if (flowFile) {
|
||||
settings.flowFile = flowFile;
|
||||
}
|
||||
if (parsedArgs.userDir) {
|
||||
settings.userDir = parsedArgs.userDir;
|
||||
}
|
||||
|
||||
RED.init(server,settings);
|
||||
|
||||
//if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
|
||||
// app.use(settings.httpAdminRoot,
|
||||
// express.basicAuth(function(user, pass) {
|
||||
// return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass;
|
||||
// })
|
||||
// );
|
||||
//}
|
||||
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
|
||||
RED.log.warn("use of httpAdminAuth is deprecated. Use adminAuth instead");
|
||||
app.use(settings.httpAdminRoot,
|
||||
express.basicAuth(function(user, pass) {
|
||||
return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
|
||||
app.use(settings.httpNodeRoot,
|
||||
|
||||
@@ -34,22 +34,22 @@ var server = oauth2orize.createServer();
|
||||
|
||||
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
|
||||
|
||||
function init(_settings) {
|
||||
function init(_settings,storage) {
|
||||
settings = _settings;
|
||||
if (settings.adminAuth) {
|
||||
Users.init(settings.adminAuth);
|
||||
Tokens.init(settings)
|
||||
Tokens.init(settings.adminAuth,storage);
|
||||
}
|
||||
}
|
||||
|
||||
function needsPermission(permission) {
|
||||
return function(req,res,next) {
|
||||
if (settings.adminAuth) {
|
||||
if (settings && settings.adminAuth) {
|
||||
return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() {
|
||||
if (!req.user) {
|
||||
return next();
|
||||
}
|
||||
if (permissions.hasPermission(req.user,permission)) {
|
||||
if (permissions.hasPermission(req.authInfo.scope,permission)) {
|
||||
return next();
|
||||
}
|
||||
return res.send(401);
|
||||
@@ -74,9 +74,12 @@ function getToken(req,res,next) {
|
||||
}
|
||||
|
||||
function login(req,res) {
|
||||
var response = {
|
||||
"type":"credentials",
|
||||
"prompts":[{id:"username",type:"text",label:"Username"},{id:"password",type:"password",label:"Password"}]
|
||||
var response = {};
|
||||
if (settings.adminAuth) {
|
||||
response = {
|
||||
"type":"credentials",
|
||||
"prompts":[{id:"username",type:"text",label:"Username"},{id:"password",type:"password",label:"Password"}]
|
||||
}
|
||||
}
|
||||
res.json(response);
|
||||
}
|
||||
@@ -96,7 +99,6 @@ module.exports = {
|
||||
authenticateClient: authenticateClient,
|
||||
getToken: getToken,
|
||||
errorHandler: function(err,req,res,next) {
|
||||
//TODO: standardize json response
|
||||
//TODO: audit log statment
|
||||
//console.log(err.stack);
|
||||
//log.log({level:"audit",type:"auth",msg:err.toString()});
|
||||
|
||||
@@ -19,15 +19,37 @@ var util = require('util');
|
||||
var readRE = /^((.+)\.)?read$/
|
||||
var writeRE = /^((.+)\.)?write$/
|
||||
|
||||
function hasPermission(user,permission) {
|
||||
if (!user.permissions) {
|
||||
return false;
|
||||
}
|
||||
if (user.permissions == "*") {
|
||||
function hasPermission(userScope,permission) {
|
||||
var i;
|
||||
if (util.isArray(userScope)) {
|
||||
if (userScope.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (i=0;i<userScope.length;i++) {
|
||||
if (!hasPermission(userScope[i],permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (user.permissions == "read") {
|
||||
|
||||
if (userScope == "*") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (util.isArray(permission)) {
|
||||
for (i=0;i<permission.length;i++) {
|
||||
if (!hasPermission(userScope,permission[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (userScope == "read") {
|
||||
return readRE.test(permission);
|
||||
} else {
|
||||
return false; // anything not allowed is disallowed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ var util = require("util");
|
||||
var Tokens = require("./tokens");
|
||||
var Users = require("./users");
|
||||
var Clients = require("./clients");
|
||||
var permissions = require("./permissions");
|
||||
|
||||
var bearerStrategy = function (accessToken, done) {
|
||||
// is this a valid token?
|
||||
@@ -55,17 +56,18 @@ var clientPasswordStrategy = function(clientId, clientSecret, done) {
|
||||
clientPasswordStrategy.ClientPasswordStrategy = new ClientPasswordStrategy(clientPasswordStrategy);
|
||||
|
||||
var loginAttempts = [];
|
||||
var loginSignUpWindow = 36000000; // 10 minutes
|
||||
var loginSignInWindow = 600000; // 10 minutes
|
||||
|
||||
|
||||
var passwordTokenExchange = function(client, username, password, scope, done) {
|
||||
var now = Date.now();
|
||||
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||
return logEntry.time + loginSignUpWindow > now;
|
||||
return logEntry.time + loginSignInWindow > now;
|
||||
});
|
||||
loginAttempts.push({time:now, user:username});
|
||||
var attemptCount = 0;
|
||||
loginAttempts.forEach(function(logEntry) {
|
||||
/* istanbul ignore else */
|
||||
if (logEntry.user == username) {
|
||||
attemptCount++;
|
||||
}
|
||||
@@ -75,16 +77,20 @@ var passwordTokenExchange = function(client, username, password, scope, done) {
|
||||
done(new Error("Too many login attempts. Wait 10 minutes and try again"),false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Users.authenticate(username,password).then(function(user) {
|
||||
if (user) {
|
||||
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||
return logEntry.user !== username;
|
||||
});
|
||||
Tokens.create(username,client.id,scope).then(function(tokens) {
|
||||
// TODO: audit log
|
||||
done(null,tokens.accessToken);
|
||||
});
|
||||
if (permissions.hasPermission(user.permissions,scope)) {
|
||||
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||
return logEntry.user !== username;
|
||||
});
|
||||
Tokens.create(username,client.id,scope).then(function(tokens) {
|
||||
// TODO: audit log
|
||||
done(null,tokens.accessToken,null,{expires_in:tokens.expires_in});
|
||||
});
|
||||
} else {
|
||||
done(null,false);
|
||||
}
|
||||
} else {
|
||||
// TODO: audit log
|
||||
done(null,false);
|
||||
@@ -101,7 +107,7 @@ AnonymousStrategy.prototype.authenticate = function(req) {
|
||||
var self = this;
|
||||
Users.default().then(function(anon) {
|
||||
if (anon) {
|
||||
self.success(anon);
|
||||
self.success(anon,{scope:anon.permissions});
|
||||
} else {
|
||||
self.fail(401);
|
||||
}
|
||||
|
||||
98
red/api/auth/tokens.js
Normal file
98
red/api/auth/tokens.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var when = require("when");
|
||||
|
||||
function generateToken(length) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var token = [];
|
||||
for (var i=0;i<length;i++) {
|
||||
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||
}
|
||||
return token.join("");
|
||||
}
|
||||
|
||||
|
||||
var storage;
|
||||
|
||||
var sessionExpiryTime
|
||||
|
||||
var sessions = {};
|
||||
|
||||
function expireSessions() {
|
||||
var now = Date.now();
|
||||
var modified = false;
|
||||
for (var t in sessions) {
|
||||
if (sessions.hasOwnProperty(t)) {
|
||||
var session = sessions[t];
|
||||
if (!session.hasOwnProperty("expires") || session.expires < now) {
|
||||
delete sessions[t];
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
return storage.saveSessions(sessions);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(adminAuthSettings, _storage) {
|
||||
storage = _storage;
|
||||
|
||||
sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds
|
||||
|
||||
return storage.getSessions().then(function(_sessions) {
|
||||
sessions = _sessions||{};
|
||||
return expireSessions();
|
||||
});
|
||||
},
|
||||
get: function(token) {
|
||||
if (sessions[token]) {
|
||||
if (sessions[token].expires < Date.now()) {
|
||||
return expireSessions().then(function() { return null });
|
||||
}
|
||||
}
|
||||
return when.resolve(sessions[token]);
|
||||
},
|
||||
create: function(user,client,scope) {
|
||||
var accessToken = generateToken(128);
|
||||
|
||||
var accessTokenExpiresAt = Date.now() + (sessionExpiryTime*1000);
|
||||
|
||||
var session = {
|
||||
user:user,
|
||||
client:client,
|
||||
scope:scope,
|
||||
accessToken: accessToken,
|
||||
expires: accessTokenExpiresAt
|
||||
};
|
||||
sessions[accessToken] = session;
|
||||
return storage.saveSessions(sessions).then(function() {
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
expires_in: sessionExpiryTime
|
||||
}
|
||||
});
|
||||
},
|
||||
revoke: function(token) {
|
||||
delete sessions[token];
|
||||
return storage.saveSessions(sessions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var when = require("when");
|
||||
var Sessions;
|
||||
|
||||
function generateToken(length) {
|
||||
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||
var token = [];
|
||||
for (var i=0;i<length;i++) {
|
||||
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||
}
|
||||
return token.join("");
|
||||
}
|
||||
|
||||
|
||||
var sessionModule;
|
||||
|
||||
function moduleSelector(aSettings) {
|
||||
var toReturn;
|
||||
if (aSettings.sessionStorageModule) {
|
||||
if (typeof aSettings.sessionStorageModule === "string") {
|
||||
// TODO: allow storage modules to be specified by absolute path
|
||||
toReturn = require("./"+aSettings.sessionStorageModule);
|
||||
} else {
|
||||
toReturn = aSettings.sessionStorageModule;
|
||||
}
|
||||
} else {
|
||||
toReturn = require("./localfilesystem");
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(settings) {
|
||||
sessionModule = moduleSelector(settings);
|
||||
sessionModule.init(settings);
|
||||
},
|
||||
get: function(token) {
|
||||
return sessionModule.get(token);
|
||||
},
|
||||
create: function(user,client,scope) {
|
||||
var accessToken = generateToken(128);
|
||||
var session = {
|
||||
user:user,
|
||||
client:client,
|
||||
scope:scope,
|
||||
accessToken: accessToken,
|
||||
};
|
||||
return sessionModule.create(accessToken,session).then(function() {
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
}
|
||||
});
|
||||
},
|
||||
revoke: function(token) {
|
||||
return sessionModule.delete(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 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 fs = require('fs');
|
||||
var when = require('when');
|
||||
var nodeFn = require('when/node/function');
|
||||
var fspath = require("path");
|
||||
|
||||
var settings;
|
||||
var sessionsFile;
|
||||
|
||||
var sessions = {};
|
||||
|
||||
/**
|
||||
* Write content to a file using UTF8 encoding.
|
||||
* This forces a fsync before completing to ensure
|
||||
* the write hits disk.
|
||||
*/
|
||||
function writeFile(path,content) {
|
||||
return when.promise(function(resolve,reject) {
|
||||
var stream = fs.createWriteStream(path);
|
||||
stream.on('open',function(fd) {
|
||||
stream.end(content,'utf8',function() {
|
||||
fs.fsync(fd,resolve);
|
||||
});
|
||||
});
|
||||
stream.on('error',function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
var userDir = settings.userDir || process.env.NODE_RED_HOME;
|
||||
sessionsFile = fspath.join(userDir,".sessions.json");
|
||||
|
||||
try {
|
||||
sessions = JSON.parse(fs.readFileSync(sessionsFile,'utf8'));
|
||||
} catch(err) {
|
||||
sessions = {};
|
||||
}
|
||||
|
||||
return when.resolve();
|
||||
},
|
||||
|
||||
get: function(token) {
|
||||
return when.resolve(sessions[token]);
|
||||
},
|
||||
create: function(token,session) {
|
||||
sessions[token] = session;
|
||||
return writeFile(sessionsFile,JSON.stringify(sessions));
|
||||
},
|
||||
delete: function(token) {
|
||||
delete sessions[token];
|
||||
return writeFile(sessionsFile,JSON.stringify(sessions));
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
var when = require("when");
|
||||
var util = require("util");
|
||||
var bcrypt = require('bcryptjs');
|
||||
|
||||
var bcrypt;
|
||||
try { bcrypt = require('bcrypt'); }
|
||||
catch(e) { bcrypt = require('bcryptjs'); }
|
||||
var users = {};
|
||||
var passwords = {};
|
||||
var defaultUser = null;
|
||||
@@ -56,6 +57,7 @@ function init(config) {
|
||||
api.get = config.users;
|
||||
} else {
|
||||
var us = config.users;
|
||||
/* istanbul ignore else */
|
||||
if (!util.isArray(us)) {
|
||||
us = [us];
|
||||
}
|
||||
@@ -97,5 +99,3 @@ module.exports = {
|
||||
authenticate: function(username,password) { return api.authenticate(username,password) },
|
||||
default: function() { return api.default(); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ module.exports = {
|
||||
}).otherwise(function(err) {
|
||||
log.warn("Error saving flows : "+err.message);
|
||||
log.warn(err.stack);
|
||||
res.send(500,err.message);
|
||||
res.json(500,{error:"unexpected_error", message:err.message});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user