Compare commits

...

151 Commits

Author SHA1 Message Date
Nick O'Leary
120c8f2c28 Bump version 0.10.6 2015-04-01 10:28:04 +01:00
Nick O'Leary
fbfc5c8a2d Add error codes to api responses 2015-03-31 22:29:42 +01:00
dceejay
31b018c80e Missed closing brace in email node fix 2015-03-31 16:39:00 +01:00
dceejay
255d708fb6 updates to serial, watch, websocket, udp, twitter, email to handle no payload. 2015-03-31 09:21:11 +01:00
dceejay
78d1da5fbc Updates to sentiment, exec and range to handle missing payload properties 2015-03-31 08:35:56 +01:00
dceejay
9c22a770ef remove superfluous console.log from debug node 2015-03-30 21:59:54 +01:00
Nick O'Leary
b201828236 Remove 'loaded' property from external node info object
and fix everything that doing this broke
2015-03-30 21:49:20 +01:00
Nick O'Leary
2a8a885271 Standardise API error response format 2015-03-30 14:16:04 +01:00
Nick O'Leary
7adefd6ee0 Add access_token expiry 2015-03-30 14:14:32 +01:00
Nick O'Leary
f967a5ecdc Fix auth on comms link and for anon user
The move to honour scope level of token broke the comms link
checking as well as the permissions checking for anon users.
2015-03-29 22:27:07 +01:00
Nick O'Leary
c8d6dc2531 Auth permission should honour the token scope 2015-03-29 21:59:48 +01:00
Nick O'Leary
216b5fba7a Increment subflow name on import of duplicate 2015-03-28 22:12:18 +00:00
Nick O'Leary
f4ec9a72d5 Increase tab max-width 2015-03-28 22:08:39 +00:00
Nick O'Leary
cf0c2825eb Expand palette category when first entry added 2015-03-28 21:46:27 +00:00
Nick O'Leary
be46c419dc Center palette label whilst being dragged 2015-03-26 20:08:25 +00:00
Nick O'Leary
62c68d06fe Merge pull request #598 from dceejay/dcjtests
new tests for sentiment, file and csv nodes
2015-03-26 16:57:04 +00:00
Nick O'Leary
4f4d8419bc Nudge palette labels to rebalance them 2015-03-26 16:55:55 +00:00
dceejay
16e17954b4 add try catch to helper shutdown,
tiny fixes for exec and trigger based on tests
2015-03-26 15:02:08 +00:00
Nick O'Leary
cc1d080a5a Remove add/removeNode by filename api
Only support add/remove by module name
2015-03-26 11:39:35 +00:00
Nick O'Leary
dd7f4f6752 Filter loaded property from /nodes endpoint 2015-03-26 11:38:51 +00:00
Nick O'Leary
9daeba02b5 Flow.registerType should indicate if type was missing or not 2015-03-26 11:37:24 +00:00
dceejay
8a96dbd121 New tests for Trigger, catch and unknown nodes 2015-03-25 21:58:26 +00:00
Nick O'Leary
2a57d0b6d0 auth/login should return empty object when insecure 2015-03-25 20:33:29 +00:00
dceejay
8a5c1bade5 new tests for sentiment, file and csv
(inc a bugfix for csv :-)

tweka of 0.8 ?

temp remove buffer should equall test from file node test

comment out failing test in file node (0.8 specific fail...)

stagger multiple writes slightly in file test
2015-03-25 14:58:57 +00:00
dceejay
fcc6943f98 Extra tests for html, xml, json and tail nodes
(and some consistent passing of missing payloads)
2015-03-24 17:43:47 +00:00
dceejay
72a9de058d tests for defaults in switch, change and range nodes. 2015-03-24 17:35:54 +00:00
Nick O'Leary
8748be28b7 Bump to 0.10.5 2015-03-23 10:56:14 +00:00
Nick O'Leary
20bdea7ae0 Increase registry test coverage 2015-03-22 22:54:52 +00:00
Nick O'Leary
e19b8d35a9 Modules not properly removed from config.json 2015-03-22 21:48:12 +00:00
Nick O'Leary
81df74dfc8 Server not waiting for settings to load 2015-03-22 20:55:38 +00:00
Nick O'Leary
153fa7478f Increase flows test coverage 2015-03-22 20:12:10 +00:00
Nick O'Leary
500e9a4010 Increase localfilesystem test coverage 2015-03-22 17:40:42 +00:00
dceejay
5352fc87ee add extra tests to debug, delay & template 2015-03-22 09:38:42 +00:00
dceejay
f07fd64ffb Make trigger have sensible defaults when dragged on. 2015-03-21 19:27:39 +00:00
Nick O'Leary
36f299c031 Improve core test coverage 2015-03-21 17:42:06 +00:00
Nick O'Leary
78cf310c58 Collapse palette category when emptied 2015-03-20 23:12:52 +00:00
Nick O'Leary
18a3d71024 Double-click on subflow palette node to open flow 2015-03-20 23:11:24 +00:00
Nick O'Leary
26db1048f9 Remove dialog close button via css not jquery 2015-03-20 23:11:03 +00:00
Nick O'Leary
b61a250d58 Debug message formatting mangling brackets 2015-03-20 22:09:58 +00:00
Nick O'Leary
1d10eba0cc Propagate changed flag to parent subflow 2015-03-20 21:20:04 +00:00
Nick O'Leary
35042132fa Make node-info properties collapsable in sidebar 2015-03-20 21:13:00 +00:00
Nick O'Leary
57c049b49f Palette filter on label as well as id 2015-03-20 11:40:34 +00:00
Nick O'Leary
7a0ce0c957 Update style of nodes in palette to match workspace 2015-03-20 11:37:47 +00:00
Nick O'Leary
eb4cadb0b5 Propagate valid flag to parent subflows 2015-03-19 23:11:55 +00:00
Nick O'Leary
ac0ca083c0 Import of subflow loses certain internal wires
Wires between subflow inputs and outputs are getting lost when
JSON is imported.
2015-03-19 22:59:09 +00:00
dceejay
9afb4a9315 reverse overide behaviour on file, http and email. Node properties now
have priority. Warn user if msg.property tries to override. 
Warning should  be removed at next major verion bump.
2015-03-19 21:25:43 +00:00
dceejay
df065e94b7 add extra tests to user_spec to test some else conditions. 2015-03-19 19:57:41 +00:00
dceejay
a9789697e7 add bcrypt as optional dependency to speed up Pi,
but not force compile on Windows.
Fix to close Issue #585
2015-03-19 11:36:48 +00:00
Nick O'Leary
2d91be8814 Disable node buttons in subflow view
Closes #592
2015-03-19 11:19:44 +00:00
Nick O'Leary
5c58b0c2f4 Revalidate all config node users after edit 2015-03-19 10:49:56 +00:00
Nick O'Leary
f0139f9808 Add multi-rule support to Change node 2015-03-18 16:20:50 +00:00
dceejay
5610a3184e small spelling mistake in deploy.js 2015-03-17 15:38:31 +00:00
Nick O'Leary
b202c73708 Allow access_token to be provided in url 2015-03-17 15:15:24 +00:00
Nick O'Leary
dd4cec84bf Add node.send/on to Function node 2015-03-17 13:40:12 +00:00
Nick O'Leary
a1dac1e290 Resize ace editor on dialog-open 2015-03-17 13:36:50 +00:00
Nick O'Leary
e199d6725e Test helper - initialise credentials with express instance 2015-03-16 21:57:31 +00:00
dceejay
aef38b945d Change http request node info to suggest {{{ rather than {{ to supress
html escape when forming urls from mustache.
2015-03-16 20:22:25 +00:00
dceejay
cd5eac2cbb Add type and size reporting to the file watch node. 2015-03-16 20:21:05 +00:00
dceejay
8fea443e71 Add error msg to rpi node 2015-03-16 17:09:13 +00:00
dceejay
2a47951e46 make sure MQTT msg has a topic 2015-03-16 17:07:46 +00:00
dceejay
5234fda266 Tidy up arduino node slightly and update settings example to work nicer
with JohnnyFive
2015-03-16 17:07:17 +00:00
Nick O'Leary
e63067cd9f Add OS X command symbol to keyboard dialog 2015-03-16 15:40:25 +00:00
Nick O'Leary
be61cf6a88 Add node.error handling to core nodes 2015-03-16 13:58:01 +00:00
Nick O'Leary
5efc89d514 Warn when leaving editor with undeployed changes 2015-03-15 23:48:02 +00:00
Nick O'Leary
9c104faff3 Use RED.nodes.filterLinks in pref to RED.nodes.eachLink 2015-03-15 23:41:16 +00:00
Nick O'Leary
cf8fe16b09 Remove direct access to RED.nodes.nodes/links
- Adds RED.nodes.filterNodes and RED.nodes.filterLinks for
   doing simply queries to find elements that match a criteria
2015-03-15 23:31:38 +00:00
Nick O'Leary
71db193675 Move user menu creation to user module 2015-03-15 23:07:57 +00:00
Nick O'Leary
9952d9451e Move deploy menu/action to own module 2015-03-15 22:54:55 +00:00
Nick O'Leary
fb738ad9fa Track dirty state in RED.nodes not RED.view
- add 'change' event on RED.nodes for tracking dirty state change
2015-03-15 21:54:36 +00:00
Nick O'Leary
46f2f752b0 Node on deleted tab not removed on partial deploy 2015-03-15 21:27:11 +00:00
Nick O'Leary
42730b8fce Move external drag/drop to clipboard module 2015-03-14 22:53:31 +00:00
Nick O'Leary
1c2be579d9 Move keyboard shortcut dialog to keyboard module 2015-03-14 22:16:07 +00:00
Nick O'Leary
51e891ff88 Move sessionStorageModule into main storageModule
Fixes #586

 - add get/saveSessions to main storage module
 - handle storage modules without those functions
 - store .session file in userDir
2015-03-13 23:37:59 +00:00
Nick O'Leary
731efe1c01 Add credential extract unit tests 2015-03-13 21:26:50 +00:00
Nick O'Leary
f77dd06e65 Partial deploy with missing type breaks flow diff
Another refactor of Flow lifecycle.
 - diffFlow made a private static function
 - applyConfig now diffConfig - which returns a diff object that
   can be passed to .stop/.start to be properly applied
2015-03-13 17:54:58 +00:00
Nick O'Leary
af20f3df64 Partial deploy with missing node type breaks deploy 2015-03-13 13:15:20 +00:00
Nick O'Leary
4078212089 Split cliboard and workspaces out of editor view 2015-03-12 23:38:37 +00:00
Nick O'Leary
7bdb3181e2 Don't reload page on enter in subflow dialog 2015-03-12 17:23:17 +00:00
Nick O'Leary
933608aec1 Disable buttons of nodes with undeployed changes 2015-03-12 13:58:53 +00:00
Nick O'Leary
1d7f06bbba Redraw unselected link when joining nodes starts 2015-03-12 13:35:39 +00:00
Nick O'Leary
f26cadab7f Minimise link redrawing 2015-03-12 13:26:31 +00:00
Nick O'Leary
eacf41a4f6 Minimise filtering of nodes on redraw 2015-03-12 11:21:05 +00:00
Nick O'Leary
ab3e64271b Move subflow handling to own module 2015-03-12 00:09:30 +00:00
Nick O'Leary
e26ea14104 Undo subflow rename not reflected in palette 2015-03-12 00:09:30 +00:00
dceejay
3967e23828 change settings to replace commented out arduino library
with johnny-five to match example in docs to make life easier.
2015-03-11 17:49:06 +00:00
dceejay
1f8c6f87c9 add don't add payload to exec node
to close #578
2015-03-11 17:43:42 +00:00
Nick O'Leary
f6203fe60a Allow a config-node be marked as not required 2015-03-09 20:42:23 +00:00
Nick O'Leary
06bf710515 Improve editor dialog auto-sizing 2015-03-09 20:41:57 +00:00
Nick O'Leary
0f3cc3196c Log-in window incorrect
fixes #583
2015-03-09 20:02:13 +00:00
dceejay
4403a00651 Revert change to http until we fully deprecate msg/node priorities
Fix to close #582
2015-03-08 18:36:35 +00:00
dceejay
9c46feb22b more tests for log and Node 2015-03-08 16:53:48 +00:00
dceejay
10277aa956 revert/redo tests for api/index, log and Node_spec 2015-03-08 15:26:47 +00:00
Nick O'Leary
ff093d98c6 Merge pull request #576 from Belphemur/function-logger
Adding an Object Node to the sandbox of a function node
2015-03-07 23:37:00 +00:00
dceejay
acc0e0875b few more tests for permissions and strategies
reset log flags at end of log test
2015-03-07 13:22:21 +00:00
dceejay
69f85bd688 boost api index, nodes index and nodes Node test coverage 2015-03-06 22:58:30 +00:00
dceejay
910d983b82 More tests for red, log, info and util. 2015-03-06 14:14:47 +00:00
dceejay
128415bc9e back out some changes to red_spec test while investigate fail on v0.8 2015-03-06 10:51:57 +00:00
dceejay
082ce798d8 slightly enhance test coverage for info and log and settings. 2015-03-06 10:18:33 +00:00
dceejay
234abd82a2 Move away from __defineGetter syntax, in red and server
Bump test coverage forwards a bit
2015-03-06 10:17:00 +00:00
dceejay
3cbc1bbb1b Add ipv6 support to udp node 2015-03-05 13:07:38 +00:00
Antoine Aflalo
0ed9f6cc4f Adding an Object node to the sandbox of a function node
Permit the user of the sandbox to log using the Function Node.
Test provided and working.

Fix Display warning message in the debug log
Before they were displayed as error instead of warning
2015-03-05 09:50:11 +02:00
Nick O'Leary
10b092a9a7 Ignore 'type' when detecting config nodes 2015-03-04 22:38:53 +00:00
Nick O'Leary
444a897410 Resort to NODE_RED_HOME if User HOME not found
Fixes #575
2015-03-04 21:47:38 +00:00
Nick O'Leary
e013afb053 Import/Export dialogs cannot be cancelled
Fixes #577
2015-03-04 21:44:14 +00:00
Nick O'Leary
34364f5627 Allow node to register multiple close handlers
Closes #573
2015-03-04 21:42:11 +00:00
Nick O'Leary
cef378d820 Add selection-changed event on RED.view 2015-03-04 13:22:32 +00:00
Nick O'Leary
a27353c166 Add onadd/onremove event handlers to node definitions 2015-03-04 13:22:29 +00:00
dceejay
bbd197c71d Note in info that MQTT node can support binary. 2015-03-03 15:56:42 +00:00
Nick O'Leary
fabf013714 Allow the main view hold keyboard focus 2015-03-02 22:55:34 +00:00
dceejay
81dcfecb4e Catch very early exit null pointer when ctrl-c hit during startup. 2015-03-02 17:32:22 +00:00
Nick O'Leary
971a62ebc9 Merge pull request #571 from knolleary/errnode
Add Catch node
2015-02-26 22:58:03 +00:00
Nick O'Leary
04f2c92ba6 Add subflow/catch node tests 2015-02-26 22:40:54 +00:00
Nick O'Leary
00d0f8cfc7 Invoke catch node only when msg is provided 2015-02-26 22:40:54 +00:00
Nick O'Leary
c5c404ea05 Update catch node icon and help text 2015-02-26 22:40:54 +00:00
Nick O'Leary
c80a44933c Add errorHandler tests 2015-02-26 22:40:54 +00:00
Nick O'Leary
5599b999ec Add catch node 2015-02-26 22:40:53 +00:00
Nick O'Leary
172cbdaa84 Merge pull request #574 from knolleary/editor
Migrate to ACE editor
2015-02-26 22:27:02 +00:00
Nick O'Leary
a3c4f12764 Bump 0.10.4 2015-02-26 21:39:30 +00:00
Nick O'Leary
bf1cd457cd Add RED.editor.createEditor utility function 2015-02-26 21:29:56 +00:00
dceejay
8af50a51ba add validation triangle to ace function editor on errors 2015-02-26 17:08:50 +00:00
Nick O'Leary
ddf31e87b2 Update core nodes to use ACE editor 2015-02-26 17:08:50 +00:00
Nick O'Leary
5adbc012f3 Add ACE editor 2015-02-26 17:08:50 +00:00
Nick O'Leary
393fc349b9 Fix saving for node-library content 2015-02-26 17:08:20 +00:00
dceejay
dfed4963ed fix big labelling issue with Pi + pins ... 2015-02-26 14:18:49 +00:00
dceejay
131adb6f4e let email node mark mail as read
(for the ones it reads)
2015-02-26 14:18:49 +00:00
dceejay
a8b3cbb683 remove unecessary require from serial node 2015-02-26 14:18:49 +00:00
Nick O'Leary
e97d5c7354 Rename node-red wrapper to node-red-pi 2015-02-26 13:41:01 +00:00
Nick O'Leary
061c44f958 Move shebang to the correct red.js 2015-02-26 13:24:38 +00:00
Nick O'Leary
f5d8433341 Add node-red-pi command 2015-02-26 11:38:05 +00:00
Nick O'Leary
f78a71e8ed Load flows file from userDir when appropriate 2015-02-26 11:30:20 +00:00
Nick O'Leary
4d48c72146 Add node-red script
Needed to allow arguments to be passed to the node
engine, which isn't possible if red.js is run with
a #! line.
2015-02-25 22:37:56 +00:00
dceejay
71ff828947 Tidy up all console.log util.log from core nodes.
Try to make log,warn,error more consistent behaviour.

Especially make sure any existing catches produce errors
2015-02-25 19:10:59 +00:00
Nick O'Leary
b6245bdef7 Remove console.log from XML test spec 2015-02-25 14:25:37 +00:00
Nick O'Leary
ce1cd1ab9c Change default data dir
Changes the default location for user data to $HOME/.node-red.
2015-02-25 14:25:01 +00:00
dceejay
54b0debb3b Re-order palette categories, move subflows to top of list. 2015-02-25 10:10:10 +00:00
Nick O'Leary
1876b56022 Fix jshint error 2015-02-24 23:06:07 +00:00
Nick O'Leary
d148a23ed6 Handle config nodes appearing out of order in flow
The editor ensures config nodes appear first in the flow file. The
code in the runtime and editor assumes this to be the case, so that
when a node is instantiated that requires a config node, it can assume
the config node already exists.

This change allows a config node to appear in the flow file after a
node that wants to use it. In both the editor and runtime, the code
now scans for config nodes and handles them first.
2015-02-24 23:04:55 +00:00
dceejay
049a5f1be6 revert small whitespace change to server start messages. 2015-02-24 22:22:16 +00:00
Nick O'Leary
f3880b7601 Fix credential pruning and start/stop log messages 2015-02-24 22:03:04 +00:00
dceejay
fbb45a8961 make udp node consistent with it's info.... (re params passed out) 2015-02-24 13:22:48 +00:00
dceejay
b8c460b825 pass original url request through http request node
(will be useful when we handle errors... ;-)
2015-02-24 13:20:33 +00:00
Nick O'Leary
63191bc641 Bump 0.10.3 2015-02-23 22:18:14 +00:00
dceejay
9f012c261a Make parser nodes errors actual errors.
(more cleanup will probably be necessary - but this is a start)
2015-02-23 19:30:29 +00:00
dceejay
dc7701ad70 Add node.js version to startup log msgs for debug. 2015-02-23 19:30:29 +00:00
Nick O'Leary
e8666827e6 Restore httpAdminAuth with deprecation warning 2015-02-23 11:39:38 +00:00
Nick O'Leary
5e2c51a741 Handle deleted tab when diffing flows 2015-02-22 22:59:26 +00:00
dceejay
51421ce657 clone msg more correctly for CSV node multiple line output 2015-02-22 21:57:06 +00:00
Nick O'Leary
339e6039e1 Add engine restriction against node 0.12 2015-02-22 21:28:28 +00:00
dceejay
43054906dc preserve other msg properties when passing through CSV node 2015-02-22 19:23:36 +00:00
Nick O'Leary
57dedcf816 Add files to .gitignore 2015-02-21 00:28:29 +00:00
Nick O'Leary
4dc21c43fa Handle strings for limit/skip args to mongo node 2015-02-20 20:02:25 +00:00
164 changed files with 8704 additions and 4366 deletions

4
.gitignore vendored
View File

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

View File

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

View File

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

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

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

View File

@@ -156,7 +156,9 @@
msg:msg
});
}
function sanitize(m) {
return m.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var topic = (o.topic||"").toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var property = (o.property?o.property:'').replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var payload = (o.msg||"()").toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
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;

View File

@@ -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) +" ....";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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">
&nbsp;&nbsp;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"}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/ace/mode-html.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/ace/mode-json.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/ace/mode-yaml.js Normal file
View 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})

View File

@@ -0,0 +1 @@
ace.define("ace/snippets/handlebars",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="handlebars"})

File diff suppressed because one or more lines are too long

View 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"})

View 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 ![\n ![${1:alttext}](${2:/images/image.jpg} "${3:title}")\nsnippet ![*\n ![${1:alt}](${2:`@*`} "${3:title}")${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"})

View File

@@ -0,0 +1 @@
ace.define("ace/snippets/text",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="text"})

View File

@@ -0,0 +1 @@
ace.define("ace/snippets/yaml",["require","exports","module"],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="yaml"})

View 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)})

View 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)})

View 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)})

View 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)})

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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/&#8984;</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/&#8984;</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>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</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/&#8984;</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/&#8984;</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</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
}
})();

View File

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

View File

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

View File

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

View File

@@ -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>&nbsp;"+node.name+"</td></tr>";
}
table += "<tr><td>Type</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>ID</td><td>&nbsp;"+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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
} else if (type === "number") {
val = val.toString();
} else if ($.isArray(val)) {
@@ -113,12 +122,12 @@ RED.sidebar.info = (function() {
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
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
View 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();
}
}
})();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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