Compare commits

..

287 Commits

Author SHA1 Message Date
Nick O'Leary
1c86908b90 Bump 0.10.2 2015-02-20 10:07:10 +00:00
Nick O'Leary
9b26973883 Ensure activeWorkspace is set when a flow contains no tabs 2015-02-20 10:05:42 +00:00
Nick O'Leary
2585e983a5 Add test-* grunt tasks 2015-02-15 23:35:24 +00:00
Nick O'Leary
a808cb44c2 Merge pull request #563 from emiloberg/master
Fixes bug that prevented unsubscribe from WebSockets, browser side. Fixes node-red/node-red/#562
2015-02-15 21:54:48 +00:00
Nick O'Leary
edd9d2cb9c Fix Inject node handling of day selection
Fixes #564
2015-02-15 21:53:14 +00:00
Emil Öberg
33d24f79ce Fixes bug that prevented unsubscribe from WebSockets, browser side. Fixes node-red/node-red/#562 2015-02-14 22:25:25 +01:00
dceejay
cc095e4edf edit HTML node info to remove ref to jQuery. Link to CSSselect instead. 2015-02-14 19:06:35 +00:00
dceejay
07641d57ab recorrect debug logging level colour class names so sidebar is as-was.
in light of new logging levels
2015-02-14 12:14:06 +00:00
dceejay
5643c51507 Let debug node show "topic" correctly for errors in functions. 2015-02-13 21:14:54 +00:00
Nick O'Leary
ad6254c0b8 Add oneditdelete handler for config nodes
Already had the undocumented ondelete handler. Adding
this to be consistent with the other oneditXYZ handlers.
2015-02-13 11:20:08 +00:00
Nick O'Leary
d6ca421d59 Remove unecessary argument to oneditcancel 2015-02-13 11:05:45 +00:00
Nick O'Leary
79bd01f810 Remove X button on dialogs
Fixes #561

Also, oneditcancel not being honoured for regular nodes, only config nodes
2015-02-13 10:06:28 +00:00
Nick O'Leary
e357352240 Tidy up info tab handling of subflows and comments 2015-02-10 21:29:27 +00:00
dceejay
0accfade02 catch internal subflow info error in tab-info... 2015-02-10 20:15:00 +00:00
dceejay
00b7afe3ae spelling pedant alert in debug node... its not it's 2015-02-10 20:14:33 +00:00
dceejay
2e76541fa5 Update Debug node test to "unbreak" build... oops 2015-02-10 17:31:26 +00:00
dceejay
e2911078e3 tidy up tab-info so subflows show more useful information
(was broken before but no-one noticed ;-)
(and reorder fields in HTTP and XML nodes so name comes out first - ocd)
2015-02-10 16:56:07 +00:00
dceejay
c6157687c9 Move payload type label in Debug window to meta data row
rather than (object) etc at start of actual payload.
2015-02-10 16:56:07 +00:00
Nick O'Leary
87a1818486 Add missing FA font file 2015-02-09 09:48:31 +00:00
Nick O'Leary
d6c5d91e2c Remove package.json from gitignore because it breaks everything 2015-02-08 23:14:37 +00:00
Nick O'Leary
e042d9e8cd Bump version 0.10.0 2015-02-08 23:03:48 +00:00
dceejay
07c8c4bb45 Let Pi GPIO node show pins in use to help avoid clashes. 2015-02-08 19:40:23 +00:00
dceejay
2dd572f5bd Allow msg.description to add to the email body text
(for feed from Fluickr node)
2015-02-08 15:02:02 +00:00
dceejay
d4a27f88a1 Allow Email node to send html, and add attachments.
(outbound only at present)
2015-02-08 11:33:04 +00:00
dceejay
1513dbfcdd Add strict flag to debug (for consistency with others) 2015-02-08 11:32:26 +00:00
Nick O'Leary
8451c29a25 Add line/col report test 2015-02-07 19:56:22 +00:00
Nick O'Leary
ae7f1b38a8 Add line/col reporting to Function runtime errors 2015-02-07 19:52:14 +00:00
Nick O'Leary
83dad88ad3 Fix debug reporting of warning/errors 2015-02-07 19:29:43 +00:00
Nick O'Leary
8eb1a02407 Fix unit test failures following mocha upgrade 2015-02-06 17:27:43 +00:00
Nick O'Leary
1e6f45852f Update package dependencies 2015-02-06 17:17:44 +00:00
Nick O'Leary
3849855b57 Rename _id to _msgid to avoid mongo clash 2015-02-06 16:36:32 +00:00
Nick O'Leary
f2712f296b Update to font-awesome 4.3 2015-02-06 13:57:33 +00:00
Nick O'Leary
c7f13e3d0c Add needsPermission protection to core nodes 2015-02-06 13:57:15 +00:00
Nick O'Leary
d1fe997bf7 Doc updates 2015-02-06 13:16:46 +00:00
Nick O'Leary
86c8a5de81 Allow palette categories to be predefined 2015-02-06 13:16:19 +00:00
Nick O'Leary
010abbd3d5 Remove deprecated nodes 2015-02-06 11:33:04 +00:00
Nick O'Leary
3123aa6279 Merge pull request #558 from knolleary/auth
Add bearer token authentication to Editor
2015-02-06 11:28:12 +00:00
Nick O'Leary
dedda19626 Restyle user menu 2015-02-06 11:27:21 +00:00
Nick O'Leary
c31ffb98b0 Tie auth middleware to needsPermission api 2015-02-05 23:43:35 +00:00
Nick O'Leary
3ef6f29d6e Add Log.trace/debug/error functions 2015-02-05 17:05:39 +00:00
Nick O'Leary
26c42e500f Allow user.default to be an api function 2015-02-05 13:01:00 +00:00
Nick O'Leary
53a515176b Remove unused token expiry code 2015-02-05 13:01:00 +00:00
Nick O'Leary
fbf7ee50eb Increase unit test coverage of auth code 2015-02-05 13:00:56 +00:00
Nick O'Leary
b2aae93fa6 Hide user profile menu 2015-02-05 13:00:55 +00:00
Nick O'Leary
a494954275 Add permissions and user menu 2015-02-05 13:00:55 +00:00
Nick O'Leary
f5d7903ecb Stop lost connection message bouncing when not authed 2015-02-05 13:00:55 +00:00
Nick O'Leary
9bbe0799bd Allow adminAuth setting to provide functions 2015-02-05 13:00:55 +00:00
Nick O'Leary
f3eb85c449 Move over to settings.adminAuth 2015-02-05 13:00:54 +00:00
Nick O'Leary
74e1ef0823 Add auth awareness to comms channel 2015-02-05 13:00:49 +00:00
Nick O'Leary
982997c3df Add auth awareness to ui 2015-02-05 13:00:49 +00:00
Nick O'Leary
66005a2688 Use jquery ajax rather than d3 2015-02-05 13:00:49 +00:00
Nick O'Leary
42cd6f94a7 fix settings 2015-02-05 13:00:37 +00:00
Nick O'Leary
dfc20f3cc9 Add node-red logo for login screen 2015-02-05 13:00:29 +00:00
Nick O'Leary
2b5a1ce6d4 Add settings changes 2015-02-05 13:00:16 +00:00
Nick O'Leary
28823802ea Prompt login if auth enabled 2015-02-05 13:00:16 +00:00
Nick O'Leary
2128b57ab2 Add oauth grant 2015-02-05 13:00:07 +00:00
Nick O'Leary
c8ccacb035 Switch node sortable entries diable inputs in FF 2015-02-05 10:46:01 +00:00
dceejay
3334c795e0 Correct spelling in Pi error messages, fix mouse icon file type 2015-02-04 23:27:51 +00:00
Nick O'Leary
b97d251787 Add on-headers dependency to package.json 2015-02-04 22:32:27 +00:00
Nick O'Leary
482c4e9c5e Add memory metric reporting 2015-02-04 22:28:17 +00:00
Nick O'Leary
1712146836 Clear loghandlers on init and supress output in tests 2015-02-04 21:29:11 +00:00
Nick O'Leary
c8d2d690f0 Rename HTTP In content-length metric 2015-02-04 21:10:18 +00:00
Nick O'Leary
f2d4648384 Support of HTTP Node metrics 2015-02-04 20:48:46 +00:00
dceejay
86ca75bcd5 Pi Node status not displaying 0 fixed. 2015-02-04 19:02:32 +00:00
Nick O'Leary
31aa3901cc Fix global leak in localfilesystem 2015-02-04 15:41:18 +00:00
Nick O'Leary
ab831c34f3 Enable node status by default 2015-02-04 15:23:28 +00:00
Nick O'Leary
8443e48240 Place flows backup alongside flow file
Also backup credentials file in the same manner.
2015-02-04 15:23:28 +00:00
dceejay
a08b29dbd1 Add info to Twitter out node to make it obvious how to do DMs. 2015-02-04 11:36:43 +00:00
Nick O'Leary
97621b41b9 Ensure deleted core nodes are removed from config list 2015-02-04 10:27:02 +00:00
Nick O'Leary
f462446213 Make Function duration status optional 2015-02-04 10:01:46 +00:00
hbeeken
6b96c1876a changing function node metrics to new infrastructure 2015-02-04 09:46:54 +00:00
Nick O'Leary
0aaea1ec40 Update logging/metric system 2015-02-03 22:02:26 +00:00
hbeeken
7d6ce1ec12 Changing metric logging to take a primative rather than an object 2015-02-03 19:12:09 +00:00
hbeeken
b052324d36 Adding logging & metric recording configuration via settings 2015-02-03 19:12:09 +00:00
dceejay
a22f819f40 Bit more consistent error handling / status updates for Pi GPIO node 2015-02-03 17:22:44 +00:00
Nick O'Leary
07acc6642f Merge pull request #460 from hindessm/fix-lastSent-initial-value
Fix node.lastSent initial value after refactoring.
2015-02-03 13:27:48 +00:00
Nick O'Leary
950611ed31 Merge pull request #554 from hbeeken/Node-spec-fix
Fixing test where part wasn't exercised
2015-02-03 13:26:50 +00:00
dceejay
84e6417877 Change inject node to use tick boxes for days of week instead of select. 2015-02-03 11:21:29 +00:00
hbeeken
a87548a991 Fixing test where part wasn't exercised 2015-02-02 13:21:19 +00:00
dceejay
5e5a220f68 Better ref link for CSS selectors list for HTTP parser node. 2015-01-30 10:00:20 +00:00
dceejay
f451d0644a Allow msg.PATCH method on http request node
Simple fix  to Close PR #524
(no CLA)
2015-01-30 10:00:20 +00:00
Nick O'Leary
e9f0877da8 Skip .info call for subflow IO nodes 2015-01-30 09:53:11 +00:00
dceejay
f6c6301733 Add done() async close to tcp node(s) 2015-01-29 21:43:23 +00:00
dceejay
0709a118e3 more fun trying to close Pi and Arduin nodes async style with done()
(more consistent use of call done().)
2015-01-29 21:43:23 +00:00
Nick O'Leary
7576878ba5 Filter comments when generating palette tooltips
Fixes #549
2015-01-29 21:40:21 +00:00
Nick O'Leary
083e253e45 Configure marked once on start-up 2015-01-29 20:47:30 +00:00
Nick O'Leary
da67e69544 Add drag handle to switch node rules 2015-01-29 20:38:19 +00:00
dceejay
0ed8d28342 Add Markdown capability to Comment node
body is rendered in the info tab and can be styled with Markdown
2015-01-29 18:53:59 +00:00
dceejay
27f9056360 Add status to file node when you overide filename
(so you can see what it writes to... )
2015-01-29 18:53:59 +00:00
dceejay
8c075bfde3 Make HTML select node point to Cheerio select docs. 2015-01-29 18:53:59 +00:00
dceejay
cae755d948 Cleanup closing down of Pi gpio nodes 2015-01-29 18:53:58 +00:00
Nick O'Leary
ca9d84b1b7 Allow Switch rules to be drag-ordered 2015-01-29 16:28:18 +00:00
Nick O'Leary
f983e4da9f Renable unit tests following logging api changes 2015-01-29 09:57:09 +00:00
Nick O'Leary
109270b437 Merge pull request #541 from hbeeken/logging-infrastructure
Adding metric logging mechanism
2015-01-27 17:50:26 +00:00
hbeeken
0bfbb12211 Adding metric logging mechanism 2015-01-27 14:41:20 +00:00
dceejay
69998243cc Add fs.notify, feedparser and serialport to default install packages
To close #518  Pull request 9 by TJ
We want to keep arduino, mongo and redis out of the default install for now.
need for js2xmlparser will go in v0.10
2015-01-21 15:05:35 +00:00
dceejay
8b61c121e6 Small changes to info box language for file watch node 2015-01-21 15:05:35 +00:00
Nick O'Leary
56ef982345 Revert settings.js 2015-01-18 21:24:19 +00:00
Nick O'Leary
8389df9729 Merge branch 'master' of github.com:node-red/node-red 2015-01-18 09:39:05 +00:00
Nick O'Leary
a8f1a6df2c Update sidemenu links to nodered.org 2015-01-18 09:38:47 +00:00
dansu
68e51bb886 added tests for websocket-client
cleanup and prettify
2015-01-18 09:38:47 +00:00
Nick O'Leary
462c259f3a Allow node to provide dynamic content to Info tab
Closes #492

The node definition can now include an `info` property. This property can be either a string or a Function. Whenever the info tab is refreshed, such as the node is selected, the value of this property, or the result of the Function, will be appended to the Info tab.
2015-01-18 09:38:47 +00:00
Nick O'Leary
f6f4b0784b Make subflow delete option more obvious
Fixes #514
2015-01-18 09:38:47 +00:00
Nick O'Leary
57b39022d0 Merge pull request #537 from dsundberg/websocket-client-unittests
added tests for websocket-client
2015-01-17 22:45:22 +00:00
Nick O'Leary
110f0bf169 Allow node to provide dynamic content to Info tab
Closes #492

The node definition can now include an `info` property. This property can be either a string or a Function. Whenever the info tab is refreshed, such as the node is selected, the value of this property, or the result of the Function, will be appended to the Info tab.
2015-01-17 21:36:16 +00:00
Nick O'Leary
3f0fcb70a4 Make subflow delete option more obvious
Fixes #514
2015-01-17 21:02:28 +00:00
Nick O'Leary
d8c7ea8144 Tidy sidebar menu
- remove inconsistent ellipsis
- add Subflow submenu
2015-01-17 20:36:18 +00:00
dansu
44db2c28a3 added tests for websocket-client
cleanup and prettify
2015-01-17 21:10:38 +01:00
Nick O'Leary
8d5f99640e Merge pull request #533 from knolleary/deploy
Add smarter deployment options
2015-01-16 16:00:35 +00:00
Nick O'Leary
ffe417976c Merge pull request #532 from hindessm/random-delay-fixes
Random delay fixes
2015-01-16 15:46:53 +00:00
Nick O'Leary
d04ac00732 Add more Flow_spec tests 2015-01-16 15:43:47 +00:00
Nick O'Leary
dd5e851339 Better sidemenu style 2015-01-16 10:25:57 +00:00
Dave Conway-Jones
2890575b3d Merge pull request #531 from dsundberg/websocket-unittest
Websocket unit tests
2015-01-15 17:18:34 +00:00
Dave Conway-Jones
8b3fbae3f6 Merge pull request #516 from dsundberg/websocket_ext
Added option to specify remote WebSocket URL in web socket node.
2015-01-15 17:17:21 +00:00
Nick O'Leary
c97ab18e62 Add Flow spec 2015-01-15 17:12:50 +00:00
Nick O'Leary
b0ffc12142 Restyle sidebar menu 2015-01-15 10:28:12 +00:00
Nick O'Leary
687a66344e Deploy menu style 2015-01-15 10:28:11 +00:00
Nick O'Leary
a5afc258b1 Update unit tests 2015-01-15 10:28:11 +00:00
Nick O'Leary
afb5e8cbce Fix jshint errors 2015-01-15 10:28:11 +00:00
Nick O'Leary
83b40a7ba6 Fix credential update 2015-01-15 10:28:11 +00:00
Nick O'Leary
cf1371bfdf Add deployment types in runtime
- removes ui option as it needs work
2015-01-15 10:28:11 +00:00
Nick O'Leary
89fff339d5 Add deploy dropdown button 2015-01-15 10:28:10 +00:00
Nick O'Leary
e11abd2508 Identify modified nodes on deploy 2015-01-15 10:28:10 +00:00
dansu
9a0177b900 Added support for websocket-client in 22-wesocket node, selectable in ui
as Listen to/Connect to drop down.
2015-01-15 08:57:42 +01:00
Mark Hindess
5510dffe18 Fix random delay mode to be random delay not random rate-limited stack.
Documentation says "Introduces a delay into a flow or rate limits
messages." but this node was doing delay and rate limit in random mode
which doesn't seem that useful. Worse it was a stack not a queue. I
can't think of any sane use cases for that behaviour.
2015-01-14 14:19:21 +00:00
Mark Hindess
7909ca24d3 Fix random delay in milliseconds case and change test to reproduce bug.
Because there was no multiplier the node.randomFirst was a string so
the later '+' was a concatentation. The test failed to catch this because
it uses integers not strings to configure the node.
2015-01-14 10:18:47 +00:00
dceejay
eee2996e8a Make Pi GPIO node wait for close before restarting
Looking to address Issue #530
2015-01-14 09:55:30 +00:00
dansu
2577631334 Added unittests for websocket node 2015-01-13 10:38:53 +01:00
dansu
9f91493cd1 rename 'send' to 'reply' to not collide with internal node function names, a config node has no wires so the 'send' function gets overwritten with a noop (nodes/red/Node.js:42, introduced in 57ae297) 2015-01-13 08:48:59 +01:00
dceejay
fcec704b7b Reset some changes to sample to be in line with how most of our nodes
actually are.
2015-01-12 19:11:07 +00:00
dceejay
2066d53d3f Handle quotes in JSON inside CSV files in CSV node...
so they come out as real JSON rather than being stripped.
2015-01-09 20:32:05 +00:00
Dave Conway-Jones
8822335700 Merge pull request #522 from motiooon/patch-1
adding skip to the mongodb input node
Great - looks good - many thanks @motiooon
2015-01-09 20:27:09 +00:00
Gabriel Baciu
f461b121e1 mongodb node take a skip property so pagination can be achieved 2015-01-09 14:32:21 -05:00
dceejay
92b393d3df really enforce binary for exec node stdout when required. 2015-01-08 12:35:42 +00:00
dceejay
4fb2a44d74 correct exec binary stdout to be consistent with spawn stdout.
Binary = buffer
String = utf8
2015-01-07 21:33:15 +00:00
dceejay
00429ebe70 Update exec node to handle binary stdout 2015-01-07 21:12:01 +00:00
dceejay
25537e01d4 Add byte mode and mouse buttons to Pi node 2015-01-06 22:06:28 +00:00
Nick O'Leary
65e4d83625 Merge pull request #526 from anna2130/mongo-db-aggregate
Fixed check for array in MongoDB aggregate function
2015-01-05 11:14:18 +00:00
Anna Thomas
ed6272ce12 Fixed check for array in MongoDB aggregate function
Fixes #525
2015-01-05 10:57:49 +00:00
dceejay
9e4187d6a8 New Pi GPIO node based on RPI.GPIO library.
Adds PWM support of outputs and easier access to interrupts for inputs.
2014-12-27 13:11:44 +00:00
Dave C-J
b4dc66944a Make sample node more representaive of a real node...
(OK not really real but at least it now doesn't fail if you try to run it)
2014-12-26 15:28:31 +00:00
Dave C-J
a51056a91f Be a bit more relaxed about IRC node reconnect - it does happen eventually.
Trying to nail down Issue #447
2014-12-18 23:22:36 +00:00
Nick O'Leary
a3692944a2 Merge pull request #515 from anna2130/nr-cli-enhancements
No-op and return success on enabling/disabling node by type name
2014-12-17 14:03:38 +00:00
Anna Thomas
03765afefa Updated nodes_spec test to no-op when already enabled/disabled by type name 2014-12-17 13:58:49 +00:00
Anna Thomas
66213d151d No-op and return success on enabling/disabling node by type name 2014-12-17 13:35:57 +00:00
Nick O'Leary
0c699ae57f Merge pull request #511 from anna2130/nr-cli-enhancements
Updates to match cli changes
2014-12-17 13:10:49 +00:00
Dave C-J
bf8d549cf7 Add "advanced" options to XML parsing node
Allows setting of attrkey and charkey
Push to close #348
2014-12-15 17:05:18 +00:00
Dave C-J
1261bf97ea Remove : from inject node label - people didn't like it.
Claim they can tell the difference between topic and payload as they wrote
the flow...
2014-12-15 17:03:18 +00:00
Dave C-J
41552625e0 remove spurious line of debug from http node 2014-12-15 17:01:58 +00:00
Dave C-J
27ef7d972f Add Binary paylaod option to Http request node
Also add JSON parse option to output
Allow user to select override using msg.method to stop getting warning.
Fix to close #399
2014-12-09 14:40:03 +00:00
Dave C-J
6fc3aab907 Make Palette search Case InSeNsItIve
fix to close #496
2014-12-09 14:37:32 +00:00
Anna Thomas
10681f97d9 Updated tests to reflect addition of version getSet 2014-12-08 16:53:06 +00:00
Anna Thomas
475d9e110e Updated to match cli changes 2014-12-08 16:53:06 +00:00
Nick O'Leary
8c5fab61e6 Merge pull request #509 from anna2130/nr-cli-enhancements
nr-cli enhancements
2014-12-08 13:08:58 +00:00
Anna Thomas
e7ccff5a4b Get version from pkg 2014-12-08 10:15:21 +00:00
Anna Thomas
81e08e06e4 Updated test to reflect saving settings in nodes 2014-12-08 10:10:16 +00:00
Anna Thomas
b006ccf610 saveNodeList saves settings in nodes 2014-12-08 09:57:17 +00:00
Anna Thomas
bb0e48f271 Store version in settings 2014-12-08 09:55:51 +00:00
Nick O'Leary
6d0dffcdf7 Migrate node config to new format 2014-12-05 20:43:41 +00:00
Nick O'Leary
14b84f0c7b Merge pull request #498 from anna2130/nr-cli-enhancements
nr-cli enhancements - server side api
2014-12-05 20:39:42 +00:00
Anna Thomas
8b6e287a74 Updated remove modules tests to reflect getNodeModuleInfo changes 2014-12-05 16:51:26 +00:00
Anna Thomas
1a5751ff1d Update removeModule to reflect getNodeModuleInfo changes 2014-12-05 16:45:32 +00:00
Anna Thomas
851048077c Remove new lines from version 2014-12-04 13:22:42 +00:00
Dave C-J
5f74a1d237 Update file, http and email nodes to only show deprecation warning
if msg property is a real overide  to node property ( !== ).

Fix for comment by @drJeckyll  to Issue #399
2014-12-01 22:58:25 +00:00
Dave Conway-Jones
e9b1e287ba Merge pull request #500 from jacktech24/master
Fixed bug, in Arduino output node
2014-11-28 10:52:12 +00:00
Nick O'Leary
da7b3ce9e4 Incorrect reference to routes in http in node
Fixes #503
2014-11-28 10:09:38 +00:00
Anna Thomas
35b3912808 Removed CLI from repo 2014-11-28 09:21:39 +00:00
Anna Thomas
e28f933f64 Refactor saveNodeList 2014-11-28 09:17:46 +00:00
Anna Thomas
57bc83b2a7 Enabling and disabling non-existent nodes throws an error 2014-11-27 16:42:45 +00:00
Anna Thomas
ec43fc4fe2 Removed unused code 2014-11-27 13:12:47 +00:00
Anna Thomas
8f2a0b63d9 Changed cleanNodeList to cleanModuleList 2014-11-26 16:46:51 +00:00
Anna Thomas
bb6e27f662 Store node list as module list 2014-11-26 16:25:37 +00:00
jacktech24
4e28a308b0 fixed bug, replaced servoWrite with analogWrite where it should be 2014-11-25 20:42:17 +01:00
Anna Thomas
04ffaeb2b8 Refactor nodeModules.nodes to moduleNodes 2014-11-24 15:44:11 +00:00
Anna Thomas
d7f249eac4 Added version number to modules 2014-11-21 16:35:29 +00:00
Anna Thomas
a5064b3ab6 Reloads module info after enabling/disabling module 2014-11-21 16:34:57 +00:00
Anna Thomas
dd5821ee1b Installing a module returns module info
Removing a module checks module exists and checks type is not in use
2014-11-21 15:15:24 +00:00
Anna Thomas
4c9d53388c Removed plugins references 2014-11-21 11:31:07 +00:00
Anna Thomas
70f101497d Replaced delete response with 204 2014-11-21 11:25:51 +00:00
Anna Thomas
56cb985de9 Separated put response into /nodes/:mod and /nodes/:mod/:set
Updated put tests
2014-11-21 10:36:32 +00:00
Anna Thomas
d614b7c39f Moved get node set response to /nodes/:mod/:set
Updated tests
Changed plugin back to module
2014-11-20 15:18:16 +00:00
Anna Thomas
0ff65f6805 Updated registry tests to reflect id changes 2014-11-20 13:08:27 +00:00
Anna Thomas
591b5f3f91 Replaced hex id with 'module/set' id 2014-11-20 12:15:15 +00:00
Anna Thomas
50fddf474b Local nodes loaded with node-red as their module 2014-11-20 09:58:42 +00:00
Nick O'Leary
3a78a2fedd Tab name changes not persisting
Fixes #495
2014-11-19 16:03:12 +00:00
Anna Thomas
9552055b08 loadNodeConfig assumed to always be called with module and name parameters 2014-11-19 13:54:00 +00:00
Nick O'Leary
71bd5cd9e9 Merge pull request #485 from anna2130/nr-cli-enhancements
WIP: Command Line Tool API
2014-11-17 13:34:24 +00:00
Nick O'Leary
7cff7ed297 Merge pull request #491 from anna2130/mongo-msg-collection
Fixes collection only being set by first message
2014-11-17 11:15:45 +00:00
Anna Thomas
6ba0d83778 Fixes collection only being set by first message 2014-11-17 10:33:31 +00:00
Dave C-J
ca2ef7e71f Pi GPIO node was not displaying Model B+ pin numbers correctly
on subsequent edit.
2014-11-16 18:23:24 +00:00
Dave C-J
977a9e1c83 Better attempt at making Inject node label more "useful".
Addresses Issue #489
2014-11-16 18:22:33 +00:00
Dave C-J
7da108e129 Reverting fix for #489 while we debate the issue more fully.... 2014-11-14 23:50:49 +00:00
Dave C-J
7b14e753cd Let Twitter node save place as a location property rather than text. 2014-11-14 23:42:35 +00:00
Dave C-J
9863b6e178 Add payload to Inject label to make more helpful
Close #489
2014-11-14 17:28:08 +00:00
Nick O'Leary
0789b82c15 Restore dialog size on reopn properly 2014-11-13 22:14:05 +00:00
Nick O'Leary
a477c0b827 Fix serial config node edit layout 2014-11-13 20:53:15 +00:00
Nick O'Leary
3cb423a0b4 Fix func/temp/comment editor resizing 2014-11-13 17:21:12 +00:00
Nick O'Leary
9c8d9550a7 Do not assume subflows exist when deleting nodes 2014-11-13 16:00:46 +00:00
Anna Thomas
8d16f3c8be Registry tests for plugins and enable/disable in CLI 2014-11-13 15:14:20 +00:00
Nick O'Leary
b4c92b457a Flatten the dialog box style 2014-11-13 13:55:12 +00:00
Nick O'Leary
426fcc2fdd Rework subflow edit process 2014-11-13 12:59:28 +00:00
Nick O'Leary
5cb9a5b7eb All subflow input to be deleted by selection 2014-11-13 00:02:41 +00:00
Nick O'Leary
64a6fe11da Add subflow outputs to be delete by selection 2014-11-12 23:51:42 +00:00
Nick O'Leary
f72f7cdaa7 Merge pull request #469 from Belphemur/ui-localstorage
Ui localstorage
2014-11-11 10:43:06 +00:00
Antoine Aflalo
01f0d5390f Adding support to LocalStorage on client side
Save the state of the Menu Item between session (like activation of
node-status and sidebar)
2014-11-11 09:04:57 +02:00
Dave C-J
ed9951f065 Add PWM support to Pi GPIO Node - pin 12 (GPIO1)
(only pin that has hardware pwm support)

Note: It will interfere with any other audio output as they share 
same hardware/timers.
2014-11-10 20:03:51 +00:00
Nick O'Leary
4249cf5d69 Fix debugMaxLength description
Closes #484
2014-11-09 20:55:13 +00:00
Dave C-J
5da45b404c Add delete option to File node
to replace msg.delete option - now deprecated but not removed.
Addresses some of the confusion  for Issue #399
2014-11-08 15:34:54 +00:00
Dave C-J
8b7e367416 tweak CSV parser to better handle GSM style phone numbers. 2014-11-08 15:34:54 +00:00
Nick O'Leary
28da2dc38a Subflow palette node outputs not updating
part of #479
2014-11-07 16:12:27 +00:00
Dave C-J
5c5de028da Add remote server name to page title / tab
Makes selecting one of several servers easier.
2014-11-07 12:28:35 +00:00
Dave C-J
b861f490c6 tiny tidy-up on TCP request node 2014-11-07 12:28:34 +00:00
Nick O'Leary
e7dccf04d2 Changing subflow in/outs leaving wires behind
Fixes #477
2014-11-07 11:22:00 +00:00
Nick O'Leary
3e235ecc0b Move cloneMessage to RED.util.cloneMessage 2014-11-06 11:39:30 +00:00
Anna Thomas
deeaa09360 Renamed modules to plugins for CLI 2014-11-06 10:59:34 +00:00
Anna Thomas
2e7a97fb88 Get plugins tests 2014-11-06 10:59:06 +00:00
Dave C-J
9c92eeb9f5 Allow tcp request node to accept msg.host and msg.port as inputs
Overrides only allowed if edit setting left blank.
2014-11-06 10:21:14 +00:00
Anna Thomas
3e24601518 Functionality to get installed module info from the cli 2014-11-06 10:00:25 +00:00
Nick O'Leary
266a644ca6 Preserve querystring when ensuring path ends with slash 2014-11-06 00:01:01 +00:00
Nick O'Leary
def93214de Merge pull request #476 from knolleary/auth
Reorganise how adminApp is setup
2014-11-05 23:18:32 +00:00
Nick O'Leary
a520240b25 Ensure application/json on library flows reqs 2014-11-05 23:08:23 +00:00
Nick O'Leary
e7eb02fcb7 Add unit tests for refactored API modules 2014-11-05 23:07:50 +00:00
Nick O'Leary
72f9471f2b Reorganise how adminApp is setup 2014-11-05 22:45:18 +00:00
Nick O'Leary
67449eb65a Merge pull request #470 from anna2130/msg-property-overrides
Message properties overriding set node properties
2014-11-05 22:05:34 +00:00
Anna Thomas
069a47f35a Added node warnings when message properties override set node properties 2014-11-05 17:23:27 +00:00
Nick O'Leary
53b07581cf Merge pull request #474 from zobalogh/twitter-location
Adding location support to Twitter
2014-11-05 15:40:47 +00:00
zobalogh
11a29b4633 Adding location support to Twitter 2014-11-05 12:00:37 +00:00
Dave C-J
bdb46cbbe4 Add "is-utf8" npm to package - part of fix #435 2014-11-04 22:00:58 +00:00
Dave C-J
273acc0ec4 Let MQTT input node receive binary packets
Try to auto select output type to be string or buffer to be backwards compatible
Fixes #435
2014-11-04 21:56:15 +00:00
Nick O'Leary
1153619a03 Handle uninitialised node in single-wire fastpath 2014-11-04 11:36:28 +00:00
Nick O'Leary
f89ddb5f7a Merge pull request #463 from njh/redis-object
Added support for storing a msg.payload of type object in a Redis hash
2014-11-01 21:19:45 +00:00
Nick O'Leary
4494c11b03 Subflow name property getting dropped on import
Closes #468
2014-10-31 22:46:26 +00:00
Dave C-J
694649e8f9 Update feedparse in line with underlying npm. 2014-10-31 18:59:57 +00:00
Anna Thomas
9f925140c9 Updated MongoDB node info 2014-10-31 15:41:13 +00:00
Nick O'Leary
6a37a823df Update orion to 7.0 release
Closes #467

Version: http://download.eclipse.org/orion/drops/R-7.0-201410282256/index.html
2014-10-31 14:28:33 +00:00
Nick O'Leary
f0e9a0279f Ignore comms heartbeat messages in comms test 2014-10-31 13:06:08 +00:00
Nick O'Leary
863b85714d localfilesystem storage must fsync writes
Closes #465
2014-10-31 11:40:10 +00:00
Nick O'Leary
dfc79e3122 Preserve unknown node type properties across deploys
Closes #5
2014-10-30 21:41:42 +00:00
Nicholas Humfrey
afde3d0ab8 Added support for storing a msg.payload of type object in a Redis hash 2014-10-30 10:09:40 +00:00
Nick O'Leary
7419f62a20 Merge pull request #461 from hindessm/fix-tweet-post-error-reporting
Fix tweet post error reporting.
2014-10-30 08:44:08 +00:00
Mark Hindess
f06b52625f Fix tweet post error reporting. 2014-10-30 08:38:49 +00:00
Nick O'Leary
ccfbd69f24 Merge pull request #459 from hindessm/fix-info-id-refactoring-bug
Fix refactoring error; rename info to id.
2014-10-30 08:35:10 +00:00
Mark Hindess
d859412785 Fix node.lastSent initial value after refactoring. 2014-10-30 08:19:52 +00:00
Mark Hindess
3840bd117c Fix refactoring error; rename info to id. 2014-10-30 08:08:41 +00:00
Nick O'Leary
aff8a7802a Merge pull request #458 from knolleary/subflows
Add Subflows
2014-10-29 22:04:48 +00:00
Nick O'Leary
6169e4299a Add initial subflow tests 2014-10-29 21:44:33 +00:00
Nick O'Leary
d9648ca76b Add subflow support 2014-10-29 20:26:25 +00:00
Dave C-J
348b642d25 Return sensible name to display for Delay node Queue mode 2014-10-29 18:26:43 +00:00
Dave C-J
a1830def8e Fix http in node close wrinkle if cors enabled but not used. 2014-10-29 18:26:09 +00:00
Dave C-J
98b875c4a0 Neater fix for Mac meta key - thanks to Nick. 2014-10-29 09:05:48 +00:00
Nick O'Leary
d242f67be3 Merge pull request #457 from anna2130/change-node-multi-level-properties
Change node can set msg property to another msg property. Closes #456
2014-10-28 16:07:42 +00:00
Anna Thomas
ce6513e7f7 Change node can set msg property to another msg property. Closes #456 2014-10-28 15:50:50 +00:00
Dave C-J
bd75c1c753 Better attempt at adding Apple meta key 2014-10-28 09:03:00 +00:00
Nick O'Leary
48d3b8f37a Inject node interval error
part two of #455
2014-10-27 19:41:29 +00:00
Nick O'Leary
4c573b208c Merge branch 'master' of github.com:node-red/node-red 2014-10-27 19:36:43 +00:00
Nick O'Leary
a5228875a6 Inject node calculating hour interval period incorrectly
Fixes #455
2014-10-27 19:36:26 +00:00
Dave C-J
c51866c2c5 Add ⌘ Cmd key as alternative to Ctrl key to make Mac users happy. 2014-10-27 15:07:34 +00:00
Dave C-J
bbaf7bf247 correcting typos in trigger node... oops 2014-10-27 08:54:21 +00:00
Nick O'Leary
04673c65f4 Node drag start threshold incorrectly calculated 2014-10-26 22:23:56 +00:00
Nick O'Leary
57ae297efd Clone messages before any node.receive call 2014-10-25 23:12:30 +01:00
Dave C-J
0bb78ae491 Try to ensure TCP node closes server connections on redeploy
(to clean up properly - especially in case of port changes)

Closes Issue #454
2014-10-25 17:52:24 +01:00
Dave C-J
d9363f4974 Reduce logging for exec node
(can be re-enabled by using node -v red.js ... )
2014-10-25 17:50:55 +01:00
Dave C-J
b54e9edfa6 Add "topic based fair queue" option to delay node 2014-10-24 20:00:25 +01:00
Dave C-J
cf81de415a Minor UI tweaks to RPi node 2014-10-24 20:00:25 +01:00
Nick O'Leary
4cd78692e2 Handle debug boolean complete properties 2014-10-23 15:28:47 +01:00
Nick O'Leary
5fd1ff5137 Add hint on requiring fs module in settings.js 2014-10-22 21:44:01 +01:00
Nick O'Leary
fe95a98339 Merge pull request #441 from anna2130/reduce-message-cloning-overhead
Reduces message cloning overhead for single recipients
2014-10-22 15:09:59 +01:00
Nick O'Leary
da73972c41 Merge pull request #449 from hbeeken/update-test-helper
Adding capability to the test helper to load more than one node at once
2014-10-22 15:08:17 +01:00
Nick O'Leary
014389c55f Merge pull request #450 from Belphemur/patch-1
Correcting a Typo
2014-10-22 15:07:47 +01:00
Antoine Aflalo
ff4edccbbc Correcting a Typo 2014-10-22 16:33:48 +03:00
hbeeken
1e7ce2cfe7 Adding capability to the test helper to load more than one node at once 2014-10-22 11:29:54 +01:00
Anna Thomas
bc8e459ae6 Node does not clone first message sent
Tests updated to mirror this behaviour
    Annotated algorithm
2014-10-21 14:08:35 +01:00
Nick O'Leary
53a9a5fe93 Merge pull request #422 from anna2130/debug
Updated debug node to output other msg properties
2014-10-20 16:35:24 +01:00
Anna Thomas
17e4bf1a11 Tidy up node label and edit dialog 2014-10-20 12:52:07 +01:00
Nick O'Leary
bec4e429f9 Merge pull request #433 from anna2130/change-node-multi-level-properties
Change node: Multi-level properties
2014-10-20 11:12:59 +01:00
Dave C-J
472fdc65a9 Allow Raspberry Pi node to set initial output level.
Fix for #443
Also allow (optional) initial read of input pins on deploy.
Moved to Category Raspberry Pi 
(other Pi related nodes will be updated to match soon).
2014-10-19 13:54:21 +01:00
Dave C-J
28a4ba1aad Don't let IRC node try to join channels multiple times...
Addresses #447
2014-10-18 20:38:58 +01:00
Dave Conway-Jones
dd9fc6a250 Merge pull request #437 from hindessm/file-query-node-usability
Minor fixes to messages sent from "file in" nodes.
2014-10-14 17:54:47 +01:00
Dave C-J
7802939bb0 fix IRC node (once again)
refactoring created incorrect object references - which weren't picked up.
(sorry) -  Addresses Issue 439
2014-10-09 20:50:46 +01:00
Nick O'Leary
d4a21be666 Clone settings to avoid modifying original values 2014-10-09 14:21:53 +01:00
Mark Hindess
0d9abbb8b6 Minor fixes to messages sent from "file in" nodes.
Specifically:

* in the error case, set msg.filename to be the name of the file used (as
  is done in the non-error case),

* in the error case, delete msg.payload so that subsequent nodes only need
  check for a msg.payload to act upon if they don't care about error cases,
  and

* in the non-error case, delete msg.error to avoid passing through errors
  from earlier nodes to a subsequent node that does care about error cases

Messages sent will now always have well-defined behaviour with respect to
the payload, filename, and error in both error and non-error cases.
2014-10-09 11:19:14 +01:00
Nick O'Leary
9a32e79603 Merge pull request #436 from anna2130/calculate-text-width
Refactored palette.js and view.js to reuse calculateTextWidth
2014-10-09 10:21:42 +01:00
Anna Thomas
206b8ac34a Refactored to reuse calculateTextWidth 2014-10-09 10:07:17 +01:00
Anna Thomas
986ce8163f Added tests to check functionality when using multi-level properties 2014-10-07 16:12:35 +01:00
Anna Thomas
97e5c2e571 Added multi-level property functionality 2014-10-07 16:12:35 +01:00
Anna Thomas
2a753c9d22 Updated debug node test 2014-10-07 10:25:56 +01:00
Anna Thomas
f96b40cff2 Updated debug node to output other msg properties 2014-10-07 10:25:56 +01:00
171 changed files with 14431 additions and 5416 deletions

View File

@@ -2,6 +2,11 @@
We welcome contributions, but request you follow these guidelines.
- [Raising issues](#raising-issues)
- [Feature requests](#feature-requests)
- [Pull-Requests](#pull-requests)
- [Contributor License Agreement](#contributor-license-agreement)
## Raising issues
Please raise any bug reports on the project's
@@ -21,44 +26,17 @@ At a minimum, please include:
- Version of node.js - what does `node -v` say?
## New features
## Feature requests
For feature requests, please raise them on the [mailing list](https://groups.google.com/forum/#!forum/node-red).
## Pull-Requests
### Changes to existing code
if you want to raise a pull-request with a new feature, or a refactoring
If you want to raise a pull-request with a new feature, or a refactoring
of existing code, it may well get rejected if you haven't discussed it on
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
### New nodes
The plugin nature of Node-RED means anyone can create a new node to extend
its capabilities.
We want to avoid duplication as that can lead to confusion. Many of our existing
nodes offer a starting point of functionality. If they are missing features,
we would rather extend them than add separate 'advanced' versions. But the key
to that approach is getting the UX right to not lose the simplicity.
To contribute a new node, please raise a pull-request against the
`node-red-nodes` repository.
Eventually, the nodes will be npm-installable, but we're not there yet. We'll
also have some sort of registry of nodes to help with discoverability.
### Coding standards
Please ensure you follow the coding standards used through-out the existing
code base. Some basic rules include:
- all files must have the Apache license in the header.
- indent with 4-spaces, no tabs. No arguments.
- opening brace on same line as `if`/`for`/`function`/etc, closing brace on its
own line.
### Contributor License Aggreement
### Contributor License Agreement
In order for us to accept pull-requests, the contributor must first complete
a Contributor License Agreement (CLA). This clarifies the intellectual
@@ -74,6 +52,17 @@ You can download the CLAs here:
If you are an IBMer, please contact us directly as the contribution process is
slightly different.
### Coding standards
Please ensure you follow the coding standards used through-out the existing
code base. Some basic rules include:
- all files must have the Apache license in the header.
- indent with 4-spaces, no tabs. No arguments.
- opening brace on same line as `if`/`for`/`function`/etc, closing brace on its
own line.

View File

@@ -86,6 +86,10 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['jshint:core','jshint:tests','jshint:editor','simplemocha:core','simplemocha:nodes']);
grunt.registerTask('default', ['test-core','test-editor','test-nodes']);
grunt.registerTask('test-core', ['jshint:core','simplemocha:core']);
grunt.registerTask('test-editor', ['jshint:editor']);
grunt.registerTask('test-nodes', ['simplemocha:nodes']);
};

View File

@@ -19,23 +19,22 @@ Check out [INSTALL](INSTALL.md) for full instructions on getting started.
4. node red.js
5. Open <http://localhost:1880>
## Documentation
## Getting Help
More documentation can be found [here](http://nodered.org/docs).
For further help, or general discussion, there is also a [mailing list](https://groups.google.com/forum/#!forum/node-red).
For further help, or general discussion, please use the [mailing list](https://groups.google.com/forum/#!forum/node-red).
## Browser Support
The Node-RED editor runs in the browser. We routinely develop and test using
Chrome and Firefox. We have anecdotal evidence that it works in IE9.
Chrome and Firefox. We have anecdotal evidence that it works in recent versions of IE.
We do not yet support mobile browsers, although that is high on our priority
list.
We have basic support for using in mobile/tablet browsers.
## Contributing
Please see our [contributing guide](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md).
Before raising a pull-request, please read our [contributing guide](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md).
## Authors
@@ -48,4 +47,4 @@ For more open-source projects from IBM, head over [here](http://ibm.github.io).
## Copyright and license
Copyright 2013, 2014 IBM Corp. under [the Apache 2.0 license](LICENSE).
Copyright 2013, 2015 IBM Corp. under [the Apache 2.0 license](LICENSE).

View File

@@ -32,6 +32,9 @@ module.exports = function(RED) {
// Store local copies of the node configuration (as defined in the .html)
this.topic = n.topic;
// copy "this" object in case we need it in context of callbacks of other functions.
var node = this;
// Do whatever you need to do in here - declare callbacks etc
// Note: this sample doesn't do anything much - it will only send
// this message once at startup...
@@ -41,19 +44,20 @@ module.exports = function(RED) {
msg.payload = "Hello world !"
// send out the message to the rest of the workspace.
// ... this message will get sent at startup so you may not see it in a debug node.
this.send(msg);
// respond to inputs....
this.on('input', function (msg) {
node.warn("I saw a payload: "+msg.payload);
// in this example just send it straight on... should process it here really
this.send(msg);
node.send(msg);
});
this.on("close", function() {
// Called when the node is shutdown - eg on redeploy.
// Allows ports to be closed, connections dropped etc.
// eg: this.client.disconnect();
// eg: node.client.disconnect();
});
}

View File

@@ -1,437 +1,500 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="inject">
<div class="form-row node-input-payload">
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
<select id="node-input-payloadType" style="width:125px !important">
<option value="date">timestamp</option>
<option value="none">blank</option>
<option value="string">string</option>
</select>
</div>
<div class="form-row" id="node-input-row-payload">
<label for="node-input-payload"></label>
<input type="text" id="node-input-payload" placeholder="payload">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="topic">
</div>
<div class="form-row">
<label for=""><i class="fa fa-repeat"></i> Repeat</label>
<select id="inject-time-type-select" style="width: 288px"><option value="none">none</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
<input type="hidden" id="node-input-repeat" placeholder="payload">
<input type="hidden" id="node-input-crontab" placeholder="payload">
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
<!-- on <select disabled id="inject-time-interval-days" class="inject-time-days"></select> -->
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
at every <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="10">10</option>
<option value="12">12</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="0">60</option>
</select> minutes<br/>
between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
on <select id="inject-time-interval-time-days" class="inject-time-days"></select>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
at <input id="inject-time-time" value="12:00"></input><br/>
on <select id="inject-time-time-days" class="inject-time-days"></select>
</div>
<div class="form-row" id="node-once">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
</div>
<div class="form-row">
<label for="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"><b>Note:</b> "interval between times" and "at a specific time" will use cron.<br/>See info box for details.</div>
</script>
<style>
.inject-time-row {
padding-left: 110px;
}
.inject-time-row select {
margin: 3px 0;
}
.inject-time-days {
width: 262px;
}
.inject-time-times {
width: 90px;
}
.inject-time-row > .ui-spinner {
height: 28px;
margin: 3px 0;
border-color: rgb(204, 204, 204);
}
#inject-time-time {
margin-top: 3px;
width: 75px;
}
.inject-time-count {
width: 40px !important;
}
</style>
<script type="text/x-red" data-help-name="inject">
<p>Pressing the button on the left side of the node allows a message on a topic to be injected into the flow. This is mainly for test purposes.</p>
<p>If no payload is specified the payload is set to the current time in millisecs since 1970. This allows subsequent functions to perform time based actions.</p>
<p>The repeat function does what it says on the tin and continuously sends the payload every x seconds.</p>
<p>The Fire once at start option actually waits 50mS before firing to give other nodes a chance to instantiate properly.</p>
<p><b>Note: </b>"Interval between times" and "at a specific time" will use cron. This means that 20 minutes will be at the next hour, 20 minutes past and 40 minutes past - not in 20 minutes time.
If you want every 20 minutes from now - use the basic "interval" option.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('inject',{
category: 'input',
color:"#a6bbcf",
defaults: {
name: {value:""},
topic: {value:""},
payload: {value:""},
payloadType: {value:"date"},
repeat: {value:""},
crontab: {value:""},
once: {value:false}
},
inputs:0,
outputs:1,
icon: "inject.png",
label: function() {
if (this.payloadType === "string") {
return this.name||this.topic||this.payload||"inject";
}
else { return this.name||this.topic||"inject"; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#inject-time-type-select").change(function() {
$("#node-input-crontab").val('');
var id = $("#inject-time-type-select option:selected").val();
$(".inject-time-row").hide();
$("#inject-time-row-"+id).show();
if ((id == "none") || (id == "interval")) {
$("#node-once").show();
}
else {
$("#node-once").hide();
$("#node-input-once").prop('checked', false);
}
});
var days = [
{v:"*",t:"every day"},
{v:"1-5",t:"Mondays to Fridays"},
{v:"0,6",t:"Saturdays and Sundays"},
{v:"1",t:"Mondays"},
{v:"2",t:"Tuesdays"},
{v:"3",t:"Wednesdays"},
{v:"4",t:"Thursdays"},
{v:"5",t:"Fridays"},
{v:"6",t:"Saturdays"},
{v:"0",t:"Sundays"}
];
$(".inject-time-days").each(function() {
for (var d in days) {
$(this).append($("<option></option>").val(days[d].v).text(days[d].t));
}
});
$(".inject-time-times").each(function() {
for (var i=0;i<24;i++) {
var l = (i<10?"0":"")+i+":00";
$(this).append($("<option></option>").val(i).text(l));
}
});
$(".inject-time-count").spinner({
//max:60,
min:1
});
$("#inject-time-interval-units").change(function() {
var units = $("#inject-time-interval-units option:selected").val();
//$("#inject-time-interval-days").prop("disabled",(units == "s")?"disabled":false);
//$(".inject-time-count").spinner("option","max",(units == "h")?24:60);
});
$.widget( "ui.injecttimespinner", $.ui.spinner, {
options: {
// seconds
step: 60 * 1000,
// hours
page: 60
},
_parse: function( value ) {
if ( typeof value === "string" ) {
// already a timestamp
if ( Number( value ) == value ) {
return Number( value );
}
var p = value.split(":");
var offset = new Date().getTimezoneOffset();
return (((Number(p[0])+1)*60)+Number(p[1])+offset)*60*1000;
}
return value;
},
_format: function( value ) {
var d = new Date(value);
var h = d.getHours();
var m = d.getMinutes();
return ((h < 10)?"0":"")+h+":"+((m < 10)?"0":"")+m;
}
});
$("#inject-time-time").injecttimespinner();
var repeattype = "none";
if (this.repeat != "" && this.repeat != 0) {
repeattype = "interval";
var r = "s";
var c = this.repeat;
if (this.repeat % 60 === 0) { r = "m"; c = c/60; }
if (this.repeat % 1440 === 0) { r = "h"; c = c/24; }
$("#inject-time-interval-count").val(c);
$("#inject-time-interval-units").val(r);
//$("#inject-time-interval-units option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
$("#inject-time-interval-days").prop("disabled","disabled");
} else if (this.crontab) {
var cronparts = this.crontab.split(" ");
var days = cronparts[4];
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
repeattype = "time";
// Fixed time
var time = cronparts[1]+":"+cronparts[0];
$("#inject-time-time").val(time);
$("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
$("#inject-time-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
}
//else if (cronparts[0] == "0") {
// // interval - hours
// var hours = cronparts[1].slice(2);
// repeattype = "interval";
// $("#inject-time-interval-days").prop("disabled",false);
// $("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
// $("#inject-time-interval-count").val(hours)
// $("#inject-time-interval-units option").filter(function() {return $(this).val() == "h";}).attr('selected',true);
//} else if (cronparts[1] == "*") {
// // interval - minutes
// var minutes = cronparts[0].slice(2);
// repeattype = "interval";
// $("#inject-time-interval-days").prop("disabled",false);
// $("#inject-time-interval-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
// $("#inject-time-interval-count").val(minutes)
// $("#inject-time-interval-units option").filter(function() {return $(this).val() == "m";}).attr('selected',true);
//}
else {
repeattype = "interval-time";
// interval - time period
var minutes = cronparts[0].slice(2);
if (minutes === "") { minutes = "0"; }
$("#inject-time-interval-time-units").val(minutes);
$("#inject-time-interval-time-days option").filter(function() {return $(this).val() == days;}).attr('selected',true);
var time = cronparts[1];
var timeparts = time.split(",");
var start;
var end;
if (timeparts.length == 1) {
// 0 or 0-10
var hours = timeparts[0].split("-");
if (hours.length == 1) {
if (hours[0] === "") {
start = "0";
end = "0";
}
else {
start = hours[0];
end = Number(hours[0])+1;
}
} else {
start = hours[0];
end = (Number(hours[1])+1)%24;
}
} else {
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
var startparts = timeparts[0].split("-");
start = startparts[0];
var endparts = timeparts[1].split("-");
if (endparts.length == 1) {
end = Number(endparts[0])+1;
} else {
end = Number(endparts[1])+1;
}
}
$("#inject-time-interval-time-start option").filter(function() {return $(this).val() == start;}).attr('selected',true);
$("#inject-time-interval-time-end option").filter(function() {return $(this).val() == end;}).attr('selected',true);
}
} else {
$("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
}
$(".inject-time-row").hide();
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
$("#inject-time-row-"+repeattype).show();
if (this.payloadType == null) {
if (this.payload == "") {
this.payloadType = "date";
} else {
this.payloadType = "string";
}
}
$("#node-input-payloadType").change(function() {
var id = $("#node-input-payloadType option:selected").val();
if (id == "string") {
$("#node-input-row-payload").show();
} else {
$("#node-input-row-payload").hide();
}
});
$("#node-input-payloadType").val(this.payloadType);
$("#node-input-payloadType").change();
$("#inject-time-type-select").change();
},
oneditsave: function() {
var repeat = "";
var crontab = "";
var type = $("#inject-time-type-select option:selected").val();
if (type == "none") {
// nothing
} else if (type == "interval") {
var count = $("#inject-time-interval-count").val();
var units = $("#inject-time-interval-units option:selected").val();
var days = $("#inject-time-interval-days option:selected").val();
if (units == "s") {
repeat = count;
} else {
if (units == "m") {
//crontab = "*/"+count+" * * * "+days;
repeat = count * 60;
} else if (units == "h") {
//crontab = "0 */"+count+" * * "+days;
repeat = count * 60 * 24;
}
}
} else if (type == "interval-time") {
var count = $("#inject-time-interval-time-units").val();
var startTime = Number($("#inject-time-interval-time-start option:selected").val());
var endTime = Number($("#inject-time-interval-time-end option:selected").val());
var days = $("#inject-time-interval-time-days option:selected").val();
var timerange = "*";
if (startTime == endTime) {
//TODO: invalid
repeat = "";
crontab = "";
} else if (endTime == 0) {
timerange = startTime+"-23";
} else if (startTime+1 < endTime) {
timerange = startTime+"-"+(endTime-1);
} else if (startTime+1 == endTime) {
timerange = startTime;
} else {
var startpart = "";
var endpart = "";
if (startTime == 23) {
startpart = "23";
} else {
startpart = startTime+"-23";
}
if (endTime == 1) {
endpart = "0";
} else {
endpart = "0-"+(endTime-1);
}
timerange = startpart+","+endpart;
}
repeat = "";
if (count === "0") {
crontab = count+" "+timerange+" * * "+days;
}
else {
crontab = "*/"+count+" "+timerange+" * * "+days;
}
} else if (type == "time") {
var time = $("#inject-time-time").val();
var days = $("#inject-time-time-days option:selected").val();
var parts = time.split(":");
repeat = "";
crontab = parts[1]+" "+parts[0]+" * * "+days;
}
$("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab);
},
button: {
onclick: function() {
var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
if (this.payloadType === "date") { label = "timestamp"; }
if (this.payloadType === "none") { label = "blank"; }
d3.xhr("inject/"+this.id).post(function(err,resp) {
if (err) {
if (err.status == 404) {
RED.notify("<strong>Error</strong>: inject node not deployed","error");
} else if (err.status == 500) {
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
} else if (err.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+")"+err.response,"error");
}
} else if (resp.status == 200) {
RED.notify("Successfully injected: "+label,"success");
} else {
RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
}
});
}
}
});
</script>
<!--
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.
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="inject">
<div class="form-row node-input-payload">
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
<select id="node-input-payloadType" style="width:73%">
<option value="date">timestamp</option>
<option value="string">string</option>
<option value="none">blank</option>
</select>
</div>
<div class="form-row" id="node-input-row-payload">
<label for="node-input-payload"></label>
<input type="text" id="node-input-payload" placeholder="payload" style="width:70%">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="topic" style="width: 70%x">
</div>
<div class="form-row">
<label for=""><i class="fa fa-repeat"></i> Repeat</label>
<select id="inject-time-type-select" style="width: 73%"><option value="none">none</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select>
<input type="hidden" id="node-input-repeat" placeholder="payload">
<input type="hidden" id="node-input-crontab" placeholder="payload">
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval">
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
at every <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="10">10</option>
<option value="12">12</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="0">60</option>
</select> minutes<br/>
between <select id="inject-time-interval-time-start" class="inject-time-times"></select>
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
<div id="inject-time-interval-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> Monday</label>
<label><input type='checkbox' checked value='2'/> Tuesday</label>
<label><input type='checkbox' checked value='3'/> Wednesday</label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> Thursday</label>
<label><input type='checkbox' checked value='5'/> Friday</label>
<label><input type='checkbox' checked value='6'/> Saturday</label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> Sunday</label>
</div>
</div>
</div>
</div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time">
at <input id="inject-time-time" value="12:00"></input><br/>
<div id="inject-time-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
<div style="display:inline-block;">
<div>
<label><input type='checkbox' checked value='1'/> Monday</label>
<label><input type='checkbox' checked value='2'/> Tuesday</label>
<label><input type='checkbox' checked value='3'/> Wednesday</label>
</div>
<div>
<label><input type='checkbox' checked value='4'/> Thursday</label>
<label><input type='checkbox' checked value='5'/> Friday</label>
<label><input type='checkbox' checked value='6'/> Saturday</label>
</div>
<div>
<label><input type='checkbox' checked value='0'/> Sunday</label>
</div>
</div>
</div>
</div>
<div class="form-row" id="node-once">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label>
</div>
<div class="form-row">
<label for="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"><b>Note:</b> "interval between times" and "at a specific time" will use cron.<br/>See info box for details.</div>
</script>
<style>
.inject-time-row {
padding-left: 110px;
}
.inject-time-row select {
margin: 3px 0;
}
.inject-time-days label {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
vertical-align: top;
width: 100px;
}
.inject-time-days input {
width: auto;
}
.inject-time-times {
width: 90px;
}
.inject-time-row > .ui-spinner {
height: 28px;
margin: 3px 0;
border-color: rgb(204, 204, 204);
}
#inject-time-time {
margin-top: 3px;
width: 75px;
}
.inject-time-count {
width: 40px !important;
}
</style>
<script type="text/x-red" data-help-name="inject">
<p>Pressing the button on the left side of the node allows a message on a topic
to be injected into the flow. This is mainly for test purposes.</p>
<p>The payload defaults to the current time in millisecs since 1970, but can
also be set to a String or left blank.</p>
<p>The repeat function allows the payload to be sent on the required schedule.</p>
<p>The Fire once at start option actually waits a short interval before firing
to give other nodes a chance to instantiate properly.</p>
<p><b>Note: </b>"Interval between times" and "at a specific time" uses cron.
This means that 20 minutes will be at the next hour, 20 minutes past and
40 minutes past - not in 20 minutes time. If you want every 20 minutes
from now - use the "interval" option.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('inject',{
category: 'input',
color:"#a6bbcf",
defaults: {
name: {value:""},
topic: {value:""},
payload: {value:""},
payloadType: {value:"date"},
repeat: {value:""},
crontab: {value:""},
once: {value:false}
},
inputs:0,
outputs:1,
icon: "inject.png",
label: function() {
if (this.payloadType === "string") {
if ((this.topic !== "") && ((this.topic.length + this.payload.length) <= 32)) {
return this.name||this.topic + ":" + this.payload;
}
else if (this.payload.length < 24) {
return this.name||this.payload;
}
}
if ((this.topic.length < 24) && (this.topic.length > 0)) {
return this.name||this.topic;
}
else { return this.name||(this.payloadType==="date"?"timestamp":null)||"inject"; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#inject-time-type-select").change(function() {
$("#node-input-crontab").val('');
var id = $("#inject-time-type-select option:selected").val();
$(".inject-time-row").hide();
$("#inject-time-row-"+id).show();
if ((id == "none") || (id == "interval")) {
$("#node-once").show();
}
else {
$("#node-once").hide();
$("#node-input-once").prop('checked', false);
}
});
$(".inject-time-times").each(function() {
for (var i=0;i<24;i++) {
var l = (i<10?"0":"")+i+":00";
$(this).append($("<option></option>").val(i).text(l));
}
});
$("#inject-time-interval-time-start").change(function() {
var start = Number($("#inject-time-interval-time-start option:selected").val());
var end = Number($("#inject-time-interval-time-end option:selected").val());
$("#inject-time-interval-time-end option").remove();
for (var i=start+1;i<25;i++) {
var l = (i<10?"0":"")+i+":00";
if (i==24) {
l = "00:00";
}
var opt = $("<option></option>").val(i).text(l).appendTo("#inject-time-interval-time-end");
if (i === end) {
opt.attr("selected","selected");
}
}
});
$(".inject-time-count").spinner({
//max:60,
min:1
});
$("#inject-time-interval-units").change(function() {
var units = $("#inject-time-interval-units option:selected").val();
});
$.widget( "ui.injecttimespinner", $.ui.spinner, {
options: {
// seconds
step: 60 * 1000,
// hours
page: 60
},
_parse: function( value ) {
if ( typeof value === "string" ) {
// already a timestamp
if ( Number( value ) == value ) {
return Number( value );
}
var p = value.split(":");
var offset = new Date().getTimezoneOffset();
return (((Number(p[0])+1)*60)+Number(p[1])+offset)*60*1000;
}
return value;
},
_format: function( value ) {
var d = new Date(value);
var h = d.getHours();
var m = d.getMinutes();
return ((h < 10)?"0":"")+h+":"+((m < 10)?"0":"")+m;
}
});
$("#inject-time-time").injecttimespinner();
var repeattype = "none";
if (this.repeat != "" && this.repeat != 0) {
repeattype = "interval";
var r = "s";
var c = this.repeat;
if (this.repeat % 60 === 0) { r = "m"; c = c/60; }
if (this.repeat % 1440 === 0) { r = "h"; c = c/60; }
$("#inject-time-interval-count").val(c);
$("#inject-time-interval-units").val(r);
$("#inject-time-interval-days").prop("disabled","disabled");
} else if (this.crontab) {
var cronparts = this.crontab.split(" ");
var days = cronparts[4];
if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) {
repeattype = "time";
// Fixed time
var time = cronparts[1]+":"+cronparts[0];
$("#inject-time-time").val(time);
$("#inject-time-type-select option").filter(function() {return $(this).val() == "s";}).attr('selected',true);
if (days == "*") {
$("#inject-time-time-days input[type=checkbox]").prop("checked",true);
} else {
$("#inject-time-time-days input[type=checkbox]").removeAttr("checked");
days.split(",").forEach(function(v) {
$("#inject-time-time-days [value=" + v + "]").prop("checked", true);
});
}
} else {
repeattype = "interval-time";
// interval - time period
var minutes = cronparts[0].slice(2);
if (minutes === "") { minutes = "0"; }
$("#inject-time-interval-time-units").val(minutes);
if (days == "*") {
$("#inject-time-interval-time-days input[type=checkbox]").prop("checked",true);
} else {
$("#inject-time-interval-time-days input[type=checkbox]").removeAttr("checked");
days.split(",").forEach(function(v) {
$("#inject-time-interval-time-days [value=" + v + "]").prop("checked", true);
});
}
var time = cronparts[1];
var timeparts = time.split(",");
var start;
var end;
if (timeparts.length == 1) {
// 0 or 0-10
var hours = timeparts[0].split("-");
if (hours.length == 1) {
if (hours[0] === "") {
start = "0";
end = "0";
}
else {
start = hours[0];
end = Number(hours[0])+1;
}
} else {
start = hours[0];
end = (Number(hours[1])+1)%24;
}
} else {
// 23,0 or 17-23,0-10 or 23,0-2 or 17-23,0
var startparts = timeparts[0].split("-");
start = startparts[0];
var endparts = timeparts[1].split("-");
if (endparts.length == 1) {
end = Number(endparts[0])+1;
} else {
end = Number(endparts[1])+1;
}
}
$("#inject-time-interval-time-start option").filter(function() {return $(this).val() == start;}).attr('selected',true);
$("#inject-time-interval-time-end option").filter(function() {return $(this).val() == end;}).attr('selected',true);
}
} else {
$("#inject-time-type-select option").filter(function() {return $(this).val() == "none";}).attr('selected',true);
}
$(".inject-time-row").hide();
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
$("#inject-time-row-"+repeattype).show();
if (this.payloadType == null) {
if (this.payload == "") {
this.payloadType = "date";
} else {
this.payloadType = "string";
}
}
$("#node-input-payloadType").change(function() {
var id = $("#node-input-payloadType option:selected").val();
if (id === "string") {
$("#node-input-row-payload").show();
} else {
$("#node-input-row-payload").hide();
}
});
$("#node-input-payloadType").val(this.payloadType);
$("#node-input-payloadType").change();
$("#inject-time-type-select").change();
$("#inject-time-interval-time-start").change();
},
oneditsave: function() {
var repeat = "";
var crontab = "";
var type = $("#inject-time-type-select option:selected").val();
if (type == "none") {
// nothing
} else if (type == "interval") {
var count = $("#inject-time-interval-count").val();
var units = $("#inject-time-interval-units option:selected").val();
if (units == "s") {
repeat = count;
} else {
if (units == "m") {
//crontab = "*/"+count+" * * * "+days;
repeat = count * 60;
} else if (units == "h") {
//crontab = "0 */"+count+" * * "+days;
repeat = count * 60 * 60;
}
}
} else if (type == "interval-time") {
repeat = "";
var count = $("#inject-time-interval-time-units").val();
var startTime = Number($("#inject-time-interval-time-start option:selected").val());
var endTime = Number($("#inject-time-interval-time-end option:selected").val());
var days = $('#inject-time-interval-time-days input[type=checkbox]:checked').map(function(_, el) {
return $(el).val()
}).get();
if (days.length == 0) {
crontab = "";
} else {
if (days.length == 7) {
days="*";
} else {
days = days.join(",");
}
var timerange = "";
if (endTime == 0) {
timerange = startTime+"-23";
} else if (startTime+1 < endTime) {
timerange = startTime+"-"+(endTime-1);
} else if (startTime+1 == endTime) {
timerange = startTime;
} else {
var startpart = "";
var endpart = "";
if (startTime == 23) {
startpart = "23";
} else {
startpart = startTime+"-23";
}
if (endTime == 1) {
endpart = "0";
} else {
endpart = "0-"+(endTime-1);
}
timerange = startpart+","+endpart;
}
if (count === "0") {
crontab = count+" "+timerange+" * * "+days;
} else {
crontab = "*/"+count+" "+timerange+" * * "+days;
}
}
} else if (type == "time") {
var time = $("#inject-time-time").val();
var days = $('#inject-time-time-days input[type=checkbox]:checked').map(function(_, el) {
return $(el).val()
}).get();
if (days.length == 0) {
crontab = "";
} else {
if (days.length == 7) {
days="*";
} else {
days = days.join(",");
}
var parts = time.split(":");
repeat = "";
crontab = parts[1]+" "+parts[0]+" * * "+days;
}
}
$("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab);
},
button: {
onclick: function() {
var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
if (this.payloadType === "date") { label = "timestamp"; }
if (this.payloadType === "none") { label = "blank"; }
$.ajax({
url: "inject/"+this.id,
type:"POST",
success: function(resp) {
RED.notify("Successfully injected: "+label,"success");
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: inject node not deployed","error");
} else if (jqXHR.status == 500) {
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
} else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+jqXHR.status+") "+textStatus,"error");
}
}
});
}
}
});
</script>

View File

@@ -15,8 +15,9 @@
**/
module.exports = function(RED) {
"use strict";
var cron = require("cron");
function InjectNode(n) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
@@ -28,7 +29,7 @@ module.exports = function(RED) {
var node = this;
this.interval_id = null;
this.cronjob = null;
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000;
this.log("repeat = "+this.repeat);
@@ -47,16 +48,16 @@ module.exports = function(RED) {
this.error("'cron' module not found");
}
}
if (this.once) {
setTimeout( function(){ node.emit("input",{}); }, 100);
}
this.on("input",function(msg) {
var msg = {topic:this.topic};
if ( (this.payloadType == null && this.payload == "") || this.payloadType == "date") {
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null || this.payloadType == "string") {
} else if (this.payloadType == null || this.payloadType === "string") {
msg.payload = this.payload;
} else {
msg.payload = "";
@@ -65,9 +66,9 @@ module.exports = function(RED) {
msg = null;
});
}
RED.nodes.registerType("inject",InjectNode);
InjectNode.prototype.close = function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
@@ -79,19 +80,19 @@ module.exports = function(RED) {
}
}
RED.httpAdmin.post("/inject/:id", function(req,res) {
var node = RED.nodes.getNode(req.params.id);
if (node != null) {
try {
node.receive();
res.send(200);
} catch(err) {
res.send(500);
node.error("Inject failed:"+err);
console.log(err.stack);
}
} else {
res.send(404);
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
var node = RED.nodes.getNode(req.params.id);
if (node != null) {
try {
node.receive();
res.send(200);
} catch(err) {
res.send(500);
node.error("Inject failed:"+err);
console.log(err.stack);
}
} else {
res.send(404);
}
});
}

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.
@@ -16,12 +16,15 @@
<script type="text/x-red" data-template-name="debug">
<div class="form-row">
<label for="node-input-complete"><i class="fa fa-list"></i> Output</label>
<select type="text" id="node-input-complete" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">payload only</option>
<label for="node-input-select-complete"><i class="fa fa-list"></i> Output</label>
<select type="text" id="node-input-select-complete" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">message property</option>
<option value="true">complete msg object</option>
</select>
</div>
<div class="form-row" id="node-prop-row">
<label for="node-input-complete">&nbsp;</label>msg.<input type="text" style="width:208px" id="node-input-complete">
</div>
<div class="form-row">
<label for="node-input-console"><i class="fa fa-random"></i> to</label>
<select type="text" id="node-input-console" style="display: inline-block; width: 250px; vertical-align: top;">
@@ -36,27 +39,57 @@
</script>
<script type="text/x-red" data-help-name="debug">
<p>The Debug node can be connected to the output of any node. It will display the timestamp, <b>msg.topic</b> and <b>msg.payload</b> fields of any messages it receives in the debug tab of the sidebar.
<br/>The sidebar can be accessed under the options drop-down in the top right corner.</p>
<p>The button to the right of the node will toggle it's output on and off so you can de-clutter the debug window.</p>
<p>If the payload is an object it will be stringified first for display and indicate that by saying "(Object) ".</p>
<p>If the payload is a buffer it will be stringified first for display and indicate that by saying "(Buffer) ".</p>
<p>The Debug node can be connected to the output of any node. It can be used to display the output of any message property in the debug tab of the sidebar. The default is to display <b>msg.payload</b>.</p>
<p>Each message will also display the timestamp, <b>msg.topic</b> and the property chosen to output.</p>
<p>The sidebar can be accessed under the options drop-down in the top right corner.</p>
<p>The button to the right of the node will toggle its output on and off so you can de-clutter the debug window.</p>
<p>If the payload is an object or buffer it will be stringified first for display and indicate that by saying "(Object)" or "(Buffer)".</p>
<p>Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.</p>
<p>Optionally can show the complete msg object - but the screen can get messy.</p>
<p>Optionally can show the complete <b>msg</b> object.</p>
<p>In addition any calls to node.warn or node.error will appear here.</p>
</script>
<script type="text/javascript">
function oneditprepare() {
if (this.complete === "true" || this.complete === true) {
// show complete message object
$("#node-input-select-complete").val("true");
$("#node-prop-row").hide();
} else {
// show msg.[ ]
var property = (!this.complete||(this.complete === "false")) ? "payload" : this.complete+"";
$("#node-input-select-complete").val("false");
$("#node-input-complete").val(property);
$("#node-prop-row").show();
}
$("#node-input-select-complete").change(function() {
var v = $("#node-input-select-complete option:selected").val();
$("#node-input-complete").val(v);
if (v !== "true") {
$("#node-input-complete").val("payload");
$("#node-prop-row").show();
$("#node-input-complete").focus();
} else {
$("#node-prop-row").hide();
}
});
}
RED.nodes.registerType('debug',{
category: 'output',
defaults: {
name: {value:""},
active: {value:true},
console: {value:"false"},
complete: {value:"false"}
complete: {value:"false", required:true}
},
label: function() {
return this.name||"debug";
if (this.complete === true || this.complete === "true") {
return this.name||"msg";
} else {
return this.name || "msg." + ((!this.complete || this.complete === "false") ? "payload" : this.complete);
}
},
labelStyle: function() {
return this.name?"node_label_italic":"";
@@ -70,21 +103,24 @@
toggle: "active",
onclick: function() {
var label = this.name||"debug";
d3.xhr("debug/"+this.id+"/"+(this.active?"enable":"disable")).post(function(err,resp) {
if (err) {
if (err.status == 404) {
$.ajax({
url: "debug/"+this.id+"/"+(this.active?"enable":"disable"),
type: "POST",
success: function(resp, textStatus, xhr) {
if (xhr.status == 200) {
RED.notify("Successfully activated: "+label,"success");
} else if (xhr.status == 201) {
RED.notify("Successfully deactivated: "+label,"success");
}
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: debug node not deployed","error");
} else if (err.status == 0) {
} else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error");
} else {
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+")"+err.response,"error");
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+") "+err.response,"error");
}
} else if (resp.status == 200) {
RED.notify("Successfully activated: "+label,"success");
} else if (resp.status == 201) {
RED.notify("Successfully deactivated: "+label,"success");
} else {
RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
}
});
}
@@ -92,26 +128,26 @@
onpaletteadd: function() {
var content = document.createElement("div");
content.id = "tab-debug";
var toolbar = document.createElement("div");
toolbar.id = "debug-toolbar";
content.appendChild(toolbar);
toolbar.innerHTML = '<div class="btn-group pull-right"><a id="debug-tab-clear" title="clear log" class="btn btn-mini" href="#"><i class="fa fa-trash"></i></a></div> ';
var messages = document.createElement("div");
messages.id = "debug-content";
content.appendChild(messages);
RED.sidebar.addTab("debug",content);
function getTimestamp() {
var d = new Date();
return d.toLocaleString();
}
var sbc = document.getElementById("debug-content");
var messageCount = 0;
var that = this;
RED._debug = function(msg) {
@@ -120,7 +156,7 @@
msg:msg
});
}
this.handleDebugMessage = function(t,o) {
var msg = document.createElement("div");
msg.onmouseover = function() {
@@ -146,20 +182,31 @@
if (node) {
RED.view.showWorkspace(node.z);
}
};
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 payload = (o.msg||"").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);
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+']</span>'+
(o.topic?'<span class="debug-message-topic">'+topic+'</span>':'')+
'<span class="debug-message-payload">'+payload+'</span>';
msg.innerHTML = '<span class="debug-message-date">'+
getTimestamp()+'</span><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>';
} else {
msg.innerHTML += '<span class="debug-message-topic">'+(o.topic?topic+' : ':'')+
(o.property?'[msg.'+property+']':'[msg]')+" : "+typ+'</span>';
}
msg.innerHTML += '<span class="debug-message-payload">'+ payload+ '</span>';
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
messageCount++;
$(messages).append(msg);
if (messageCount > 200) {
$("#debug-content .debug-message:first").remove();
messageCount--;
@@ -169,7 +216,7 @@
}
};
RED.comms.subscribe("debug",this.handleDebugMessage);
$("#debug-tab-clear").click(function() {
$(".debug-message").remove();
messageCount = 0;
@@ -184,7 +231,8 @@
RED.comms.unsubscribe("debug",this.handleDebugMessage);
RED.sidebar.removeTab("debug");
delete RED._debug;
}
},
oneditprepare: oneditprepare
});
</script>
@@ -219,7 +267,7 @@
display: block;
background: #fff;
padding: 1px 5px;
font-size: 9px;
font-size: 10px;
color: #a66;
}
.debug-message-name {
@@ -237,11 +285,11 @@
border-left-color: #eee;
border-right-color: #eee;
}
.debug-message-level-warn {
.debug-message-level-30 {
border-left-color: #ffdf9d;
border-right-color: #ffdf9d;
}
.debug-message-level-error {
.debug-message-level-20 {
border-left-color: #f99;
border-right-color: #f99;
}

View File

@@ -15,45 +15,71 @@
**/
module.exports = function(RED) {
"use strict";
var util = require("util");
var events = require("events");
var debuglength = RED.settings.debugMaxLength||1000;
var useColors = false;
// util.inspect.styles.boolean = "red";
function DebugNode(n) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.complete = n.complete;
this.complete = n.complete||"payload";
if (this.complete === "false") {
this.complete = "payload";
}
if (this.complete === true) {
this.complete = "true";
}
this.console = n.console;
this.active = (n.active == null)||n.active;
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
var node = this;
this.on("input",function(msg) {
if (this.complete == "true") { // debug complete msg object
if (this.console == "true") {
if (this.complete === "true") {
// debug complete msg object
if (this.console === "true") {
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
}
if (this.active) {
sendDebug({id:this.id,name:this.name,topic:msg.topic,msg:msg,_path:msg._path});
}
} else { // debug just the msg.payload
if (this.console == "true") {
if (typeof msg.payload === "string") {
node.log((msg.payload.indexOf("\n") != -1 ? "\n" : "") + msg.payload);
} else {
// debug user defined msg property
var property = "payload";
var output = msg[property];
if (this.complete !== "false" && typeof this.complete !== "undefined") {
property = this.complete;
var propertyParts = property.split(".");
try {
output = propertyParts.reduce(function (obj, i) {
return obj[i];
}, msg);
} catch (err) {
output = undefined;
}
}
if (this.console === "true") {
if (typeof output === "string") {
node.log((output.indexOf("\n") !== -1 ? "\n" : "") + output);
} else if (typeof output === "object") {
node.log("\n"+util.inspect(output, {colors:useColors, depth:10}));
} else {
node.log(util.inspect(output, {colors:useColors}));
}
else if (typeof msg.payload === "object") { node.log("\n"+util.inspect(msg.payload, {colors:useColors, depth:10})); }
else { node.log(util.inspect(msg.payload, {colors:useColors})); }
}
if (this.active) {
sendDebug({id:this.id,name:this.name,topic:msg.topic,msg:msg.payload,_path:msg._path});
sendDebug({id:this.id,name:this.name,topic:msg.topic,property:property,msg:output,_path:msg._path});
}
}
});
}
RED.nodes.registerType("debug",DebugNode);
function sendDebug(msg) {
if (msg.msg instanceof Error) {
msg.msg = msg.msg.toString();
@@ -73,31 +99,33 @@ module.exports = function(RED) {
seen = null;
} else if (typeof msg.msg === "boolean") {
msg.msg = "(boolean) "+msg.msg.toString();
} else if (typeof msg.msg === "number") {
msg.msg = "(number) "+msg.msg.toString();
} else if (msg.msg === 0) {
msg.msg = "0";
} else if (msg.msg == null) {
} else if (msg.msg === null || typeof msg.msg === "undefined") {
msg.msg = "(undefined)";
}
} else { msg.msg = "(string) "+msg.msg; }
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substr(0,debuglength) +" ....";
}
RED.comms.publish("debug",msg);
}
DebugNode.logHandler = new events.EventEmitter();
DebugNode.logHandler.on("log",function(msg) {
if (msg.level == "warn" || msg.level == "error") {
if (msg.level === RED.log.WARN || msg.level === RED.log.ERROR) {
sendDebug(msg);
}
});
RED.log.addHandler(DebugNode.logHandler);
RED.httpAdmin.post("/debug/:id/:state", function(req,res) {
RED.httpAdmin.post("/debug/:id/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
var node = RED.nodes.getNode(req.params.id);
var state = req.params.state;
if (node != null) {
if (node !== null && typeof node !== "undefined" ) {
if (state === "enable") {
node.active = true;
res.send(200);
@@ -111,4 +139,4 @@ module.exports = function(RED) {
res.send(404);
}
});
}
};

View File

@@ -42,6 +42,7 @@
<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>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>
<script type="text/javascript">

View File

@@ -18,6 +18,7 @@ module.exports = function(RED) {
"use strict";
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var isUtf8 = require('is-utf8');
function ExecNode(n) {
RED.nodes.createNode(this,n);
@@ -35,17 +36,19 @@ module.exports = function(RED) {
var arg = [];
if (node.append.length > 0) { arg = node.append.split(","); }
if (msg.payload.trim() !== "") { arg.unshift(msg.payload); }
node.log(node.cmd+" ["+arg+"]");
if (RED.settings.verbose) { node.log(node.cmd+" ["+arg+"]"); }
if (node.cmd.indexOf(" ") == -1) {
var ex = spawn(node.cmd,arg);
ex.stdout.on('data', function (data) {
//console.log('[exec] stdout: ' + data);
msg.payload = data.toString();
if (isUtf8(data)) { msg.payload = data.toString(); }
else { msg.payload = data; }
node.send([msg,null,null]);
});
ex.stderr.on('data', function (data) {
//console.log('[exec] stderr: ' + data);
msg.payload = data.toString();
if (isUtf8(data)) { msg.payload = data.toString(); }
else { msg.payload = new Buffer(data); }
node.send([null,msg,null]);
});
ex.on('close', function (code) {
@@ -62,9 +65,10 @@ module.exports = function(RED) {
}
else {
var cl = node.cmd+" "+msg.payload+" "+node.append;
node.log(cl);
var child = exec(cl, function (error, stdout, stderr) {
msg.payload = stdout;
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");
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
var msg2 = {payload:stderr};
var msg3 = null;
//console.log('[exec] stdout: ' + stdout);

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013, 2014 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,9 +19,11 @@
<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">
<label for="node-input-func"><i class="fa fa-wrench"></i> Function</label>
<input type="hidden" id="node-input-func" autofocus="autofocus">
<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">
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
</div>
<div class="form-row">
@@ -70,15 +72,24 @@
min:1
});
function functionDialogResize(ev,ui) {
$("#node-input-func-editor").css("height",(ui.size.height-275)+"px");
function functionDialogResize() {
var rows = $("#dialog-form>div:not(.node-text-editor-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-text-editor-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", functionDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-function');
if (size) {
functionDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
functionDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {

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,7 +23,7 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.func = n.func;
var functionText = "var results = null; results = (function(msg){"+this.func+"\n})(msg);";
var functionText = "var results = null; results = (function(msg){\n"+this.func+"\n})(msg);";
this.topic = n.topic;
var sandbox = {
console:console,
@@ -62,14 +62,28 @@ module.exports = function(RED) {
}
this.send(results);
var duration = process.hrtime(start);
var converted = Math.floor((duration[0]* 1e9 + duration[1])/10000)/100;
this.metric("duration", msg, converted);
if (process.env.NODE_RED_FUNCTION_TIME) {
this.status({fill:"yellow",shape:"dot",text:""+Math.floor((duration[0]* 1e9 + duration[1])/10000)/100});
this.status({fill:"yellow",shape:"dot",text:""+converted});
}
} catch(err) {
this.error(err.toString());
var errorMessage = err.toString();
var stack = err.stack.split(/\r?\n/);
if (stack.length > 0) {
var m = /at undefined:(\d+):(\d+)$/.exec(stack[1]);
if (m) {
var line = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+line+", col "+cha+")";
}
}
this.error(errorMessage);
}
});
} catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this
this.error(err);
}
}

View File

@@ -19,9 +19,11 @@
<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">
<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">
</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">
@@ -62,15 +64,24 @@
},
oneditprepare: function() {
function templateDialogResize(ev,ui) {
$("#node-input-template-editor").css("height",(ui.size.height-200)+"px");
function templateDialogResize() {
var rows = $("#dialog-form>div:not(.node-text-editor-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-text-editor-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", templateDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-template');
if (size) {
templateDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
templateDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {

View File

@@ -21,8 +21,9 @@
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> Action</label>
<select id="node-input-pauseType" style="width:270px !important">
<option value="delay">Delay message</option>
<option value="rate">Limit rate to</option>
<option value="random">Random delay</option>
<option value="rate">Limit rate to</option>
<option value="queue">Topic based fair queue</option>
</select>
</div>
<div id="delay-details" class="form-row">
@@ -38,9 +39,9 @@
</div>
<div id="rate-details" class="form-row">
<label for="node-input-rate"><i class="fa fa-clock-o"></i> To</label>
<label for="node-input-rate"><i class="fa fa-clock-o"></i> Rate</label>
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
<label for="node-input-reateUnits">msg(s) per</label>
<label for="node-input-rateUnits">msg(s) per</label>
<select id="node-input-rateUnits" style="width:140px !important">
<option value="second">Second</option>
<option value="minute">Minute</option>
@@ -48,9 +49,10 @@
<option value="day">Day</option>
</select>
<br/>
<input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop">drop intermediate messages</label>
<div id="node-input-dr"><input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop">drop intermediate messages</label></div>
</div>
<div id="random-details" class="form-row">
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> Between</label>
<input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
@@ -74,8 +76,13 @@
<!-- Next, some simple help text is provided for the node. -->
<script type="text/x-red" data-help-name="delay">
<p>Introduces a delay into a flow or rate limts messges</p>
<p>Default delay is 5 seconds and rate limit of 1 msg/second, but both can be configured</p>
<p>Introduces a delay into a flow or rate limits messages.</p>
<p>Default delay is 5 seconds and rate limit of 1 msg/second, but both can be configured.</p>
<p>If you select a rate limit you may optionally discard any intermediate messages that arrive.</p>
<p>The "topic based fair queue" adds messages to a release queue tagged by their <b>msg.topic</b> property.
At each "tick", derived from the rate, the next "topic" is released.
Any messages arriving on the same topic before release replace those in that position in the queue.
So each "topic" gets a turn - but the most recent value is always the one sent.</p>
</script>
<!-- Finally, the node type is registered along with all of its properties -->
@@ -109,7 +116,10 @@
} else if (this.pauseType == "random") {
return this.name || "random";
}
return "foo";
else {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name || "queue" +this.rate+" msg/"+ units;
}
},
labelStyle: function() { // sets the class to apply to the label
return this.name?"node_label_italic":"";
@@ -125,14 +135,22 @@
$("#delay-details").show();
$("#rate-details").hide();
$("#random-details").hide();
$("#node-input-dr").hide();
} else if (this.pauseType == "rate") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
$("#node-input-dr").show();
} else if (this.pauseType == "random") {
$("#delay-details").hide();
$("#rate-details").hide();
$("#random-details").show();
$("#node-input-dr").hide();
} else if (this.pauseType == "queue") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
$("#node-input-dr").hide();
}
if (!this.timeoutUnits) {
@@ -152,14 +170,22 @@
$("#delay-details").show();
$("#rate-details").hide();
$("#random-details").hide();
$("#node-input-dr").hide();
} else if (this.value == "rate") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
} else if (this.value == "random") {
$("#node-input-dr").show();
} else if (this.value == "random") {
$("#delay-details").hide();
$("#rate-details").hide();
$("#random-details").show();
$("#node-input-dr").hide();
} else if (this.value == "queue") {
$("#delay-details").hide();
$("#rate-details").show();
$("#random-details").hide();
$("#node-input-dr").hide();
}
});
}

View File

@@ -17,19 +17,9 @@
//Simple node to introduce a pause into a flow
module.exports = function(RED) {
"use strict";
var MILLIS_TO_NANOS = 1000000;
var SECONDS_TO_NANOS = 1000000000;
function random(n) {
var wait = n.randomFirst + (n.diff * Math.random());
if (n.buffer.length > 0) {
n.send(n.buffer.pop());
n.randomID = setTimeout(function() {random(n);},wait);
} else {
n.randomID = -1;
}
}
function DelayNode(n) {
RED.nodes.createNode(this,n);
@@ -62,8 +52,8 @@ module.exports = function(RED) {
}
if (n.randomUnits === "milliseconds") {
this.randomFirst = n.randomFirst;
this.randomLast = n.randomLast;
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;
@@ -84,7 +74,7 @@ module.exports = function(RED) {
this.buffer = [];
this.intervalID = -1;
this.randomID = -1;
this.lastSent;
this.lastSent = null;
this.drop = n.drop;
var node = this;
@@ -136,7 +126,7 @@ module.exports = function(RED) {
if (node.lastSent) {
timeSinceLast = process.hrtime(node.lastSent);
}
if (!node.lastSent) { // ensuring that we always send the first message
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
node.send(msg);
} else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
@@ -151,20 +141,51 @@ module.exports = function(RED) {
this.buffer = [];
});
} else if (this.pauseType === "random") {
this.on("input",function(msg){
node.buffer.push(msg);
if (node.randomID === -1) {
var wait = node.randomFirst + (node.diff * Math.random());
node.randomID = setTimeout(function() {random(node);},wait);
} else if (this.pauseType === "queue") {
this.intervalID = setInterval(function() {
if (node.buffer.length > 0) {
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) {
if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; }
var hit = false;
for (var b in node.buffer) { // check if already in queue
if (msg.topic === node.buffer[b].topic) {
node.buffer[b] = msg; // if so - replace existing entry
hit = true;
}
}
if (!hit) { node.buffer.push(msg); } // if not add to end of queue
node.status({text:node.buffer.length});
});
this.on("close", function (){
if (this.randomID !== -1) {
clearTimeout(this.randomID);
}
this.on("close", function() {
clearInterval(this.intervalID);
this.buffer = [];
node.status({text:node.buffer.length});
});
} else if (this.pauseType === "random") {
this.on("input", function(msg) {
var wait = node.randomFirst + (node.diff * Math.random());
var id = setTimeout(function(){
node.idList.splice(node.idList.indexOf(id),1);
node.send(msg);
}, wait);
this.idList.push(id);
});
this.on("close", function() {
for (var i=0; i<this.idList.length; i++ ) {
clearTimeout(this.idList[i]);
}
this.idList = [];
});
}
}
RED.nodes.registerType("delay",DelayNode);

View File

@@ -37,11 +37,11 @@ module.exports = function(RED) {
if (!isNaN(this.op1)) { this.op1 = Number(this.op1); }
if (!isNaN(this.op2)) { this.op2 = Number(this.op2); }
if (this.op1 == "true") { this.op1 = true; }
if (this.op2 == "true") { this.op1 = true; }
if (this.op1 == "false") { this.op2 = false; }
if (this.op2 == "true") { this.op2 = true; }
if (this.op1 == "false") { this.op1 = false; }
if (this.op2 == "false") { this.op2 = false; }
if (this.op1 == "null") { this.op1 = null; }
if (this.op2 == "null") { this.op1 = null; }
if (this.op2 == "null") { this.op2 = null; }
try { this.op1 = JSON.parse(this.op1); }
catch(e) { this.op1 = this.op1; }
try { this.op2 = JSON.parse(this.op2); }

View File

@@ -16,19 +16,21 @@
<script type="text/x-red" data-template-name="comment">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-comment"></i> Comment</label>
<label for="node-input-name"><i class="fa fa-comment"></i> Title</label>
<input type="text" id="node-input-name" placeholder="Comment">
</div>
<div class="form-row">
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> More</label>
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> Body - will be rendered in info tab.</label>
<input type="hidden" id="node-input-info" autofocus="autofocus">
</div>
<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: this isn't meant for "War and Peace" - but useful notes can be kept here.</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>
</script>
<script type="text/x-red" data-help-name="comment">
<p>Simple comment block.</p>
<p>A node you can use to add comments to your flows.</p>
</script>
<script type="text/javascript">
@@ -48,18 +50,30 @@
labelStyle: function() {
return this.name?"node_label_italic":"";
},
info: function() {
return "### "+this.name+"\n"+this.info;
},
oneditprepare: function() {
$( "#node-input-outputs" ).spinner({
min:1
});
function functionDialogResize(ev,ui) {
$("#node-input-info-editor").css("height",(ui.size.height-235)+"px");
function functionDialogResize() {
var rows = $("#dialog-form>div:not(.node-text-editor-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-text-editor-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", functionDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-function');
if (size) {
functionDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
functionDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {

View File

@@ -16,13 +16,15 @@
<script type="text/x-red" data-template-name="unknown">
<div class="form-tips"><p>This node is a type unknown to your installation of Node-RED.</p>
<p><i>If you deploy with the node in this state, it will lose all of its configuration.</i></p>
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
the flow will not start until the missing type is installed.</i></p>
<p>See the Info side bar for more help</p></div>
</script>
<script type="text/x-red" data-help-name="unknown">
<p>This node is a type unknown to your installation of Node-RED.</p>
<p><i>If you deploy with the node in this state, it will lose all of its configuration.</i></p>
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
the flow will not start until the missing type is installed.</i></p>
<p>It is possible this node type is already installed, but is missing a dependency. Check the Node-RED start-up log for
any error messages associated with the missing node type. Use <b>npm install &lt;module&gt;</b> to install any missing modules
and restart Node-RED and reimport the nodes.</p>

View File

@@ -1,56 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="imap">
<div class="form-row node-input-repeat">
<label for="node-input-repeat"><i class="fa fa-repeat"></i>Repeat (S)</label>
<input type="text" id="node-input-repeat" placeholder="300">
</div>
<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="imap">
<p>Repeatedly gets a <b>single email</b> from an IMAP server and forwards on as a msg if not already seen.</p>
<p>The subject is loaded into <b>msg.topic</b> and <b>msg.payload</b> is the plain text body.
If there is text/html then that is returned in <b>msg.html</b>. <b>msg.from</b> and <b>msg.date</b> are also set if you need them.</p>
<p>Uses the imap module - you also need to pre-configure your email settings in a file emailkeys.js as per below.</p>
<p><pre>module.exports = { service: "Gmail", user: "blahblah@gmail.com", pass: "password", server: "imap.gmail.com", port: "993" }</pre></p>
<p>This <b>must</b> be located in the directory <b>above</b> node-red.</p>
<p><b>Note:</b> this node <i>only</i> gets the most recent single email from the inbox, so set the repeat (polling) time appropriately.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('imap',{
category: 'deprecated',
color:"#c7e9c0",
defaults: {
repeat: {value:"300",required:true},
name: {value:""}
},
inputs:0,
outputs:1,
icon: "envelope.png",
label: function() {
return this.name||"IMAP";
},
labelStyle: function() {
return (this.name||!this.topic)?"node_label_italic":"";
}
});
</script>

View File

@@ -1,139 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
var Imap = require('imap');
var util = require('util');
try {
var emailkey = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
} catch (err) {
//util.log("[61-imap.js] Info : No Email credentials found.");
}
if (emailkey) {
var imap = new Imap({
user: emailkey.user,
password: emailkey.pass,
host: emailkey.server||"imap.gmail.com",
port: emailkey.port||"993",
tls: true,
tlsOptions: { rejectUnauthorized: false }
});
function openInbox(cb) {
imap.openBox('INBOX', true, cb);
}
}
function ImapNode(n) {
RED.nodes.createNode(this,n);
this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'e-mail in' node.");
this.name = n.name;
this.repeat = n.repeat * 1000 || 300000;
var node = this;
this.interval_id = null;
var oldmail = {};
if (!isNaN(this.repeat) && this.repeat > 0) {
node.log("repeat = "+this.repeat);
this.interval_id = setInterval( function() {
node.emit("input",{});
}, this.repeat );
}
this.on("input", function(msg) {
if (imap) {
imap.once('ready', function() {
var pay = {};
openInbox(function(err, box) {
if (box.messages.total > 0) {
var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
f.on('message', function(msg, seqno) {
node.log('message: #'+ seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
var buffer = '';
stream.on('data', function(chunk) {
buffer += chunk.toString('utf8');
});
stream.on('end', function() {
if (info.which !== 'TEXT') {
pay.from = Imap.parseHeader(buffer).from[0];
pay.topic = Imap.parseHeader(buffer).subject[0];
pay.date = Imap.parseHeader(buffer).date[0];
} else {
var parts = buffer.split("Content-Type");
for (var p in parts) {
if (parts[p].indexOf("text/plain") >= 0) {
pay.payload = parts[p].split("\n").slice(1,-2).join("\n").trim();
}
if (parts[p].indexOf("text/html") >= 0) {
pay.html = parts[p].split("\n").slice(1,-2).join("\n").trim();
}
}
//pay.body = buffer;
}
});
});
msg.on('end', function() {
//node.log('Finished: '+prefix);
});
});
f.on('error', function(err) {
node.warn('fetch error: ' + err);
});
f.on('end', function() {
if (JSON.stringify(pay) !== oldmail) {
node.send(pay);
oldmail = JSON.stringify(pay);
node.log('sent new message: '+pay.topic);
}
else { node.log('duplicate not sent: '+pay.topic); }
imap.end();
});
}
else {
// node.log("you have achieved inbox zero");
imap.end();
}
});
});
imap.connect();
}
else { node.warn("No Email credentials found. See info panel."); }
});
if (imap) {
imap.on('error', function(err) {
util.log(err);
});
}
this.on("error", function(err) {
node.log("error: ",err);
});
this.on("close", function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
}
if (imap) { imap.destroy(); }
});
node.emit("input",{});
}
RED.nodes.registerType("imap",ImapNode);

View File

@@ -1,53 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="xml2js">
<!-- <div class="form-row">
<label>Use Console</label>
<input type="checkbox" id="node-input-useEyes" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useEyes" style="width: 70%;">Debug output in console ?</label>
</div> -->
<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-tips">Uses xml2js to process xml into javascript object.</div> -->
</script>
<script type="text/x-red" data-help-name="xml2js">
<p>A function that parses the <b>msg.payload</b> using the xml2js library. Places the result in the payload.</p>
<p>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('xml2js',{
category: 'deprecated',
color:"#E6E0F8",
defaults: {
//useEyes: {value:false},
name: {value:""}
},
inputs:1,
outputs:1,
icon: "arrow-in.png",
label: function() {
return this.name||"xml2json";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,47 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var util = require("util");
var parseString = require('xml2js').parseString;
var useColors = true;
//util.inspect.styles.boolean = "red";
function Xml2jsNode(n) {
RED.nodes.createNode(this,n);
this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'xml' node.");
this.useEyes = n.useEyes||false;
var node = this;
this.on("input", function(msg) {
try {
parseString(msg.payload, {strict:true,async:true}, function (err, result) {
//parseString(msg.payload, {strict:false,async:true}, function (err, result) {
if (err) { node.error(err); }
else {
msg.payload = result;
node.send(msg);
if (node.useEyes == true) {
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
}
}
});
}
catch(e) { util.log("[73-parsexml.js] "+e); }
});
}
RED.nodes.registerType("xml2js",Xml2jsNode);
}

View File

@@ -1,51 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="json2xml">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-list"></i> XML Root</label>
<input type="text" id="node-input-root" placeholder="object"></input>
</div>
<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"></input>
</div>
</script>
<script type="text/x-red" data-help-name="json2xml">
<p>A function that parses the <b>msg.payload</b> using the js2xmlparser library. Places the result back in <b>msg.payload</b>.</p>
<p>See the <a href="https://github.com/michaelkourlas/node-js2xmlparser" target="_new">js2xmlparser docs</a> for more information.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('json2xml',{
category: 'deprecated',
color:"#E6E0F8",
defaults: {
name: {value:""},
root: {value:"object"},
},
inputs:1,
outputs:1,
icon: "arrow-in.png",
label: function() {
return this.name||"json2xml";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,39 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var js2xmlparser = require("js2xmlparser");
function Js2XmlNode(n) {
RED.nodes.createNode(this,n);
this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'xml' node.");
this.root = n.root;
var node = this;
this.on("input", function(msg) {
try {
var root = node.root || typeof msg.payload;
if (typeof msg.payload !== "object") { msg.payload = '"'+msg.payload+'"'; }
console.log(root, typeof msg.payload,msg.payload);
msg.payload = js2xmlparser(root, msg.payload);
node.send(msg);
}
catch(e) { console.log(e); }
});
}
RED.nodes.registerType("json2xml",Js2XmlNode);
}

View File

@@ -1,61 +0,0 @@
<!--
Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="httpget">
<div class="form-tips"><b>Deprecated</b>: please use the <i>http request</i> node.</div>
<br>
<div class="form-row">
<label for="node-input-baseurl"><i class="fa fa-tasks"></i> Base URL</label>
<input type="text" id="node-input-baseurl" placeholder="http(s)://url">
</div>
<div class="form-row">
<label for="node-input-append"><i class="fa fa-tasks"></i> Append</label>
<input type="text" id="node-input-append" placeholder="">
</div>
<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-tips">The <b>Base URL</b> gets prepended to whatever payload is passed in. Leave blank if you pass in a full url.<br/>The append gets added to the end after any payload.<br/>The output Topic is the same as the input Topic.</div>
</script>
<script type="text/x-red" data-help-name="httpget">
<p>Performs an HTTP or HTTPS GET and returns the fetched page.</p>
<p>The return code is placed in <b>msg.rc</b>, and the full text of the result is in <b>msg.payload</b>.</p>
<p>The <b>msg.payload</b> is added to the base url, and then the optional append is added after.</p>
<p>This is mostly suitable for small pages as large results will need a lot of parsing....</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('httpget',{
category: 'deprecated',
color:"rgb(231, 231, 174)",
defaults: {
name: {value:""},
baseurl: {value:""},
append: {value:""}
},
inputs:1,
outputs:1,
icon: "white-globe.png",
label: function() {
return this.name||this.baseurl;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,53 +0,0 @@
/**
* Copyright 2013 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var RED = require(process.env.NODE_RED_HOME+"/red/red");
function HttpGet(n) {
RED.nodes.createNode(this,n);
this.warn("This node has been deprecated and will be deleted in a future release. Please update your flow to use the 'http request' node.");
this.baseurl = n.baseurl || "";
this.append = n.append || "";
var node = this;
if (this.baseurl.substring(0,5) === "https") { var http = require("https"); }
else { var http = require("http"); }
this.on("input", function(msg) {
msg._payload = msg.payload;
//util.log("[httpget] "+this.baseurl+msg.payload+this.append);
http.get(this.baseurl+msg.payload+this.append, function(res) {
node.log("Http response: " + res.statusCode);
msg.rc = res.statusCode;
msg.payload = "";
if ((msg.rc != 200) && (msg.rc != 404)) {
node.send(msg);
}
res.setEncoding('utf8');
res.on('data', function(chunk) {
msg.payload += chunk;
});
res.on('end', function() {
node.send(msg);
});
}).on('error', function(e) {
//node.error(e);
msg.rc = 503;
msg.payload = e;
node.send(msg);
});
});
}
RED.nodes.registerType("httpget",HttpGet);

View File

@@ -43,14 +43,15 @@ module.exports = function(RED) {
node.log("version "+node.board.boardVersion);
});
node.on('close', function() {
node.on('close', function(done) {
if (node.board) {
try {
node.board.close(function() {
done();
node.log("port closed");
});
} catch(e) { }
}
} catch(e) { done(); }
} else { done(); }
});
}
RED.nodes.registerType("arduino-board",ArduinoNode);
@@ -130,7 +131,7 @@ module.exports = function(RED) {
msg.payload = msg.payload * 1;
if ((msg.payload >= 0) && (msg.payload <= 255)) {
//console.log(msg.payload, node.pin);
node.board.servoWrite(node.pin, msg.payload);
node.board.analogWrite(node.pin, msg.payload);
}
}
if (node.state == "SERVO") {
@@ -149,12 +150,9 @@ module.exports = function(RED) {
}
RED.nodes.registerType("arduino out",DuinoNodeOut);
RED.httpAdmin.get("/arduinoports",function(req,res) {
RED.httpAdmin.get("/arduinoports", RED.auth.needsPermission("arduino.read"), function(req,res) {
ArduinoFirmata.list(function (err, ports) {
//console.log(JSON.stringify(ports));
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(JSON.stringify(ports));
res.end();
res.json(ports);
});
});
}

View File

@@ -17,8 +17,8 @@
<script type="text/x-red" data-template-name="rpi-gpio in">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
<select type="text" id="node-input-pin" style="width: 200px;">
<option value="-" disabled>select pin </option>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option>
<option value="3">3 - SDA1 </option>
<option value="5">5 - SCL1 </option>
<option value="7">7 - GPIO7</option>
@@ -40,42 +40,55 @@
&nbsp;<span id="pitype"></span>
</div>
<div class="form-row">
<label for="node-input-intype"><i class="fa fa-long-arrow-up"></i> Resistor?</label>
<label for="node-input-intype"><i class="fa fa-level-up"></i> Resistor ?</label>
<select type="text" id="node-input-intype" style="width: 150px;">
<option value="tri">none</option>
<option value="up">pullup</option>
<option value="down">pulldown</option>
<!--<option value="tri">tristate</option>-->
</select>
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-read" style="width: 70%;">Read initial state of pin on deploy/restart ?</label>
</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-tips">Tip: Only Digital I/O is supported - input must be 0 or 1.</div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
<div class="form-tips">Tip: Only Digital Input is supported - input must be 0 or 1.</div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Pi input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin. Requires the gpio command to work.</p>
<p>You may also enable the input pullup resitor or the pulldown resistor.</p>
<p>You may also enable the input pullup resistor or the pulldown resistor.</p>
<p>The <b>msg.topic</b> is set to <i>pi/{the pin number}</i></p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
<p><b>Note:</b> This node currently polls the pin every 250mS. This is not ideal as it loads the cpu, and will be rewritten shortly to try to use interrupts.</p>
</script>
</script>
<script type="text/javascript">
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio in',{
category: 'advanced-input',
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
intype: { value: "in" },
pin: { value:"",required:true,validate:RED.validators.number() },
intype: { value: "in" },
read: { value:false }
},
inputs:0,
outputs:1,
icon: "rpi.png",
info: function() {
if ( Object.keys(pinsInUse).length !== 0 ) {
return "**Pins in use** : "+Object.keys(pinsInUse);
}
else { return ""; }
},
label: function() {
return this.name||"Pin: "+this.pin ;
},
@@ -83,9 +96,10 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var pinnow = this.pin;
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if (data.type === "Model B+") {
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"));
@@ -97,18 +111,40 @@
$('#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').val(pinnow);
}
});
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn");
}
pinnow = pinnew;
}
});
$("#node-input-intype").change(function() {
var newtype = $("#node-input-intype option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
}
});
}
});
</script>
<script type="text/x-red" data-template-name="rpi-gpio out">
<div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label>
<select type="text" id="node-input-pin" style="width: 200px;">
<option value="-">select pin </option>
<select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option>
<option value="3">3 - SDA1 </option>
<option value="5">5 - SCL1 </option>
<option value="7">7 - GPIO7</option>
@@ -129,41 +165,79 @@
</select>
&nbsp;<span id="pitype"></span>
</div>
<div class="form-row" id="node-set-pwm">
<label>&nbsp;&nbsp;&nbsp;&nbsp;Type</label>
<select id="node-input-out" style="width: 250px;">
<option value="out">Digital output</option>
<option value="pwm">PWM output</option>
</select>
</div>
<div class="form-row" id="node-set-tick">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-set" style="width: 70%;">Initialise pin state ?</label>
</div>
<div class="form-row" id="node-set-state">
<label for="node-input-level">&nbsp;</label>
<select id="node-input-level" style="width: 250px;">
<option value="0">initial level of pin - low (0)</option>
<option value="1">initial level of pin - high (1)</option>
</select>
</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-tips">Tip: Only Digital I/O is supported - input must be 0 or 1.</div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
<div class="form-tips" id="dig-tip"><b>Tip</b>: For digital output - input must be 0 or 1.</div>
<div class="form-tips" id="pwm-tip"><b>Tip</b>: For PWM output - input must be between 0 to 1023.</div>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p>Raspberry Pi output node. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false). Requires the gpio command to work.</p>
<p>Will set the selected physical pin high or low depending on the value passed in.</p>
<p>The initial value of the pin at deploy time can also be set to 0 or 1.</p>
<p>When using PWM mode - expects an input value of a number 0 - 100.</p>
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
</script>
<script type="text/javascript">
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio out',{
category: 'advanced-output',
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.number() },
set: { value:"" },
level: { value:"0" },
out: { value:"out" }
},
inputs:1,
outputs:0,
icon: "rpi.png",
info: function() {
if ( Object.keys(pinsInUse).length !== 0 ) {
return "**Pins in use** : "+Object.keys(pinsInUse);
}
else { return ""; }
},
align: "right",
label: function() {
return this.name||"Pin: "+this.pin;
if (this.out === "pwm") { return this.name || "PWM: "+this.pin; }
else { return this.name||"Pin: "+this.pin ; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var pinnow = this.pin;
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if (data.type === "Model B+") {
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"));
@@ -175,8 +249,106 @@
$('#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').val(pinnow);
}
});
$.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data));
});
$("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn");
}
pinnow = pinnew;
}
});
$("#node-input-out").change(function() {
var newtype = $("#node-input-out option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error");
}
});
var hidestate = function () {
if ($("#node-input-out").val() === "pwm") {
$('#node-set-tick').hide();
$('#node-set-state').hide();
$('#node-input-set').prop('checked', false);
$("#dig-tip").hide();
$("#pwm-tip").show();
}
else {
$('#node-set-tick').show();
$("#dig-tip").show();
$("#pwm-tip").hide();
}
};
$("#node-input-out").change(function () { hidestate(); });
hidestate();
var setstate = function () {
if ($('#node-input-set').is(":checked")) {
$("#node-set-state").show();
} else {
$("#node-set-state").hide();
}
};
$("#node-input-set").change(function () { setstate(); });
setstate();
}
});
</script>
<script type="text/x-red" data-template-name="rpi-mouse">
<div class="form-row">
<label for="node-input-butt"><i class="fa fa-circle"></i> Button</label>
<select type="text" id="node-input-butt" style="width: 250px;">
<option value="1">left</option>
<option value="2">right</option>
<option value="4">middle</option>
<option value="7">any</option>
</select>
</div>
<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="rpi-mouse">
<p>Raspberry Pi mouse button node. Generates a <b>msg.payload</b> with
either a 1 or 0 when the selected mouse button is pressed and released</p>
<p>Also sets <b>msg.button</b> to the code value, 1 = left, 2 = right, 4 = middle,
so you can work out which button or combination was pressed.</p>
<p>And sets <b>msg.topic</b> to <i>pi/mouse</i>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-mouse',{
category: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
butt: { value:"1",required:true }
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
var na = "Pi Mouse";
if (this.butt === "1") { na += " Left"; }
if (this.butt === "2") { na += " Right"; }
if (this.butt === "4") { na += " Middle"; }
return this.name||na;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,168 +18,267 @@ 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');
var gpioCommand = '/usr/local/bin/gpio';
var gpioCommand = __dirname+'/nrgpio';
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
//util.log("Info : Ignoring Raspberry Pi specific node.");
throw "Info : Ignoring Raspberry Pi specific node.";
}
if (!fs.existsSync(gpioCommand)) { // gpio command not installed
throw "Info : Can't find Raspberry Pi wiringPi gpio command.";
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
util.log("[rpi-gpio] Info : Can't find Pi RPi.GPIO python library.");
throw "Warning : Can't find Pi RPi.GPIO python library.";
}
// Map physical P1 pins to Gordon's Wiring-Pi Pins (as they should be V1/V2 tolerant)
var pintable = {
// Physical : WiringPi
"11":"0",
"12":"1",
"13":"2",
"15":"3",
"16":"4",
"18":"5",
"22":"6",
"7":"7",
"3":"8",
"5":"9",
"24":"10",
"26":"11",
"19":"12",
"21":"13",
"23":"14",
"8":"15",
"10":"16",
"27":"30",
"28":"31",
"29":"21",
"31":"22",
"32":"26",
"33":"23",
"35":"24",
"36":"27",
"37":"25",
"38":"28",
"40":"29"
}
var tablepin = {
// WiringPi : Physical
"0":"11",
"1":"12",
"2":"13",
"3":"15",
"4":"16",
"5":"18",
"6":"22",
"7":"7",
"8":"3",
"9":"5",
"10":"24",
"11":"26",
"12":"19",
"13":"21",
"14":"23",
"15":"8",
"16":"10",
"30":"27",
"31":"28",
"21":"29",
"22":"31",
"26":"32",
"23":"33",
"24":"35",
"27":"36",
"25":"37",
"28":"38",
"29":"40"
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
util.log("[rpi-gpio] Error : "+gpioCommand+" needs to be executable.");
throw "Error : nrgpio must to be executable.";
}
// the magic to make python print stuff immediately
process.env.PYTHONUNBUFFERED = 1;
var pinsInUse = {};
var pinTypes = {"out":"digital output", "tri":"input", "up":"input with pull up", "down":"input with pull down", "pwm":"PWM output"};
function GPIOInNode(n) {
RED.nodes.createNode(this,n);
this.buttonState = -1;
this.pin = pintable[n.pin];
this.pin = n.pin;
this.intype = n.intype;
this.read = n.read || false;
if (this.read) { this.buttonState = -2; }
var node = this;
if (node.pin !== undefined) {
exec(gpioCommand+" mode "+node.pin+" "+node.intype, function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
node._interval = setInterval( function() {
exec(gpioCommand+" read "+node.pin, function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
if (node.buttonState !== Number(stdout)) {
var previousState = node.buttonState;
node.buttonState = Number(stdout);
if (previousState !== -1) {
var msg = {topic:"pi/"+tablepin[node.pin], payload:node.buttonState};
node.send(msg);
}
}
}
});
}, 250);
}
});
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.intype;
}
else {
node.error("Invalid GPIO pin: "+node.pin);
if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
}
}
node.on("close", function() {
clearInterval(node._interval);
if (node.pin !== undefined) {
if (node.intype === "tri") {
node.child = spawn(gpioCommand, ["in",node.pin]);
} else {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
node.child.stdout.on('data', function (data) {
data = data.toString().trim();
if (data.length > 0) {
if (node.buttonState !== -1) {
node.send({ topic:"pi/"+node.pin, payload:Number(data) });
}
node.buttonState = data;
node.status({fill:"green",shape:"dot",text:data});
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
}
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
});
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.warn("Invalid GPIO pin: "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write(" close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
function GPIOOutNode(n) {
RED.nodes.createNode(this,n);
this.pin = pintable[n.pin];
this.pin = n.pin;
this.set = n.set || false;
this.level = n.level || 0;
this.out = n.out || "out";
var node = this;
if (node.pin !== undefined) {
process.nextTick(function() {
exec(gpioCommand+" mode "+node.pin+" out", function(err,stdout,stderr) {
if (err) { node.error(err); }
else {
node.on("input", function(msg) {
if (msg.payload === "true") { msg.payload = true; }
if (msg.payload === "false") { msg.payload = false; }
var out = Number(msg.payload);
if ((out === 0)|(out === 1)) {
exec(gpioCommand+" write "+node.pin+" "+out, function(err,stdout,stderr) {
if (err) { node.error(err); }
});
}
else { node.warn("Invalid input - not 0 or 1"); }
});
}
});
});
if (!pinsInUse.hasOwnProperty(this.pin)) {
pinsInUse[this.pin] = this.out;
}
else {
node.error("Invalid GPIO pin: "+node.pin);
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]);
}
}
node.on("close", function() {
exec(gpioCommand+" mode "+node.pin+" in");
function inputlistener(msg) {
if (msg.payload === "true") { msg.payload = true; }
if (msg.payload === "false") { msg.payload = false; }
var out = Number(msg.payload);
var limit = 1;
if (node.out === "pwm") { limit = 100; }
if ((out >= 0) && (out <= limit)) {
if (RED.settings.verbose) { node.log("out: "+msg.payload); }
if (node.child !== null) {
node.child.stdin.write(msg.payload+"\n");
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
}
else {
node.error("nrpgio python command not running");
node.status({fill:"red",shape:"ring",text:"not running"});
}
}
else { node.warn("Invalid input: "+out); }
}
if (node.pin !== undefined) {
if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
} else {
node.child = spawn(gpioCommand, [node.out,node.pin]);
}
node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"});
node.on("input", inputlistener);
node.child.stdout.on('data', function (data) {
if (RED.settings.verbose) { node.log("out: "+data+" :"); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
});
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.warn("Invalid GPIO pin: "+node.pin);
}
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
delete pinsInUse[node.pin];
if (node.child != null) {
node.done = done;
node.child.stdin.write(" close "+node.pin);
node.child.kill('SIGKILL');
}
else { done(); }
});
}
var pitype = { type:"" };
exec(gpioCommand+" -v | grep Type", function(err,stdout,stderr) {
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
if (err) {
util.log('[36-rpi-gpio.js] Error: "'+gpioCommand+' -v" command failed for some reason.');
console.log('[rpi-gpio] Version command failed for some reason.');
}
else {
pitype = { type:(stdout.split(","))[0].split(": ")[1], rev:(stdout.split(","))[1].split(": ")[1] };
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()); }
}
});
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
RED.httpAdmin.get('/rpi-gpio/:id',function(req,res) {
res.send( JSON.stringify(pitype) );
function PiMouseNode(n) {
RED.nodes.createNode(this,n);
this.butt = n.butt || 7;
var node = this;
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"OK"});
node.child.stdout.on('data', function (data) {
data = Number(data);
if (data === 0) { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
else { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
});
node.child.stderr.on('data', function (data) {
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
});
node.child.on('close', function (code) {
node.child = null;
node.running = false;
if (RED.settings.verbose) { node.log("closed"); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"stopped"}); }
});
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); }
});
node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');
node.child = null;
}
else { done(); }
});
}
RED.nodes.registerType("rpi-mouse",PiMouseNode);
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pitype);
});
RED.httpAdmin.get('/rpi-pins/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pinsInUse);
});
}

16
nodes/core/hardware/nrgpio Executable file
View File

@@ -0,0 +1,16 @@
#
# Copyright 2014 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.
#
BASEDIR=$(dirname $0)
sudo python -u $BASEDIR/nrgpio.py $@

203
nodes/core/hardware/nrgpio.py Executable file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/python
#
# Copyright 2014 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.
#
# Import library functions we need
import RPi.GPIO as GPIO
import sys
bounce = 20 # bounce time in mS to apply
if sys.version_info >= (3,0):
print("Sorry - currently only configured to work with python 2.x")
sys.exit(1)
if len(sys.argv) > 1:
cmd = sys.argv[1].lower()
pin = int(sys.argv[2])
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
if cmd == "pwm":
#print "Initialised pin "+str(pin)+" to PWM"
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.start(0)
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
sys.exit(0)
p.ChangeDutyCycle(float(data))
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
elif cmd == "buzz":
#print "Initialised pin "+str(pin)+" to Buzz"
GPIO.setup(pin,GPIO.OUT)
p = GPIO.PWM(pin, 100)
p.stop()
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
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
GPIO.cleanup(pin)
sys.exit(0)
except Exception as ex:
print "bad data: "+data
elif cmd == "out":
#print "Initialised pin "+str(pin)+" to OUT"
GPIO.setup(pin,GPIO.OUT)
if len(sys.argv) == 4:
GPIO.output(pin,int(sys.argv[3]))
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
sys.exit(0)
data = int(data)
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
except:
data = 0
if data != 0:
data = 1
GPIO.output(pin,data)
elif cmd == "in":
#print "Initialised pin "+str(pin)+" to IN"
def handle_callback(chan):
print GPIO.input(chan)
if len(sys.argv) == 4:
if sys.argv[3].lower() == "up":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
elif sys.argv[3].lower() == "down":
GPIO.setup(pin,GPIO.IN,GPIO.PUD_DOWN)
else:
GPIO.setup(pin,GPIO.IN)
else:
GPIO.setup(pin,GPIO.IN)
print GPIO.input(pin)
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=bounce)
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup(pin)
sys.exit(0)
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup(pin)
sys.exit(0)
elif cmd == "byte":
#print "Initialised BYTE mode - "+str(pin)+
list = [7,11,13,12,15,16,18,22]
GPIO.setup(list,GPIO.OUT)
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup()
sys.exit(0)
data = int(data)
except EOFError: # hopefully always caused by us sigint'ing the program
GPIO.cleanup()
sys.exit(0)
except:
data = 0
for bit in range(8):
if pin == 1:
mask = 1 << (7 - bit)
else:
mask = 1 << bit
GPIO.output(list[bit], data & mask)
elif cmd == "borg":
#print "Initialised BORG mode - "+str(pin)+
GPIO.setup(11,GPIO.OUT)
GPIO.setup(13,GPIO.OUT)
GPIO.setup(15,GPIO.OUT)
r = GPIO.PWM(11, 100)
g = GPIO.PWM(13, 100)
b = GPIO.PWM(15, 100)
r.start(0)
g.start(0)
b.start(0)
while True:
try:
data = raw_input()
if data == "close":
GPIO.cleanup()
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
GPIO.cleanup()
sys.exit(0)
except:
data = 0
elif cmd == "rev":
print GPIO.RPI_REVISION
elif cmd == "ver":
print GPIO.VERSION
elif cmd == "mouse": # catch mice button events
file = open( "/dev/input/mice", "rb" )
oldbutt = 0
def getMouseEvent():
global oldbutt
global pin
buf = file.read(3)
pin = pin & 0x07
button = ord( buf[0] ) & pin # mask out just the required button(s)
if button != oldbutt: # only send if changed
oldbutt = button
print button
while True:
try:
getMouseEvent()
except:
file.close()
sys.exit(0)
else:
print "Bad parameters - {in|out|pwm} {pin} {value|up|down}"

View File

@@ -17,6 +17,7 @@
module.exports = function(RED) {
"use strict";
var connectionPool = require("./lib/mqttConnectionPool");
var isUtf8 = require('is-utf8');
function MQTTBrokerNode(n) {
RED.nodes.createNode(this,n);
@@ -45,11 +46,12 @@ module.exports = function(RED) {
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) {
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);
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"});
@@ -78,7 +80,7 @@ module.exports = function(RED) {
this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) {
this.status({fill:"red",shape:"ring",text:"disconnected"},true);
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.on("input",function(msg) {

View File

@@ -91,6 +91,7 @@
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="use">- set by msg.method -</option>
</select>
</div>
<div class="form-row">
@@ -100,7 +101,7 @@
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useAuth" style="width: 70%;">Use basic authentication?</label>
<label for="node-input-useAuth" style="width: 70%;">Use basic authentication ?</label>
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-user"><i class="fa fa-user"></i> Username</label>
@@ -110,20 +111,28 @@
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-input-password">
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> Return</label>
<select type="text" id="node-input-ret" style="width:72%;">
<option value="txt">a UTF-8 string</option>
<option value="bin">a binary buffer</option>
<option value="obj">a parsed JSON object</option>
</select>
</div>
<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-tips" id="tip-json" hidden>Tip: If the JSON parse fails the fetched string is returned as-is.</div>
</script>
<script type="text/x-red" data-help-name="http request">
<p>Provides a node for making http requests.</p>
<p>The URL and HTTP method can be configured in the node, but also
overridden by the incoming message:
<p>The URL and HTTP method can be configured in the node, if they are left blank they should be set in an incoming message on <code>msg.url</code> and <code>msg.method</code>:</p>
<ul>
<li><code>url</code>, if set, is used as the url of the request. Must start with http: or https:</li>
<li><code>method</code>, if set, is used as the HTTP method of the request.
Must be one of <code>GET</code>, <code>PUT</code>, <code>POST</code> or <code>DELETE</code> (default: GET)</li>
Must be one of <code>GET</code>, <code>PUT</code>, <code>POST</code>, <code>PATCH</code> or <code>DELETE</code> (default: GET)</li>
<li><code>headers</code>, if set, should be an object containing field/value
pairs to be added as request headers</li>
<li><code>payload</code> is sent as the body of the request</li>
@@ -157,7 +166,7 @@
return this.name;
} else if (this.url) {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) != "/") {
if (root.slice(-1) != "/") {
root = root+"/";
}
if (this.url.charAt(0) == "/") {
@@ -175,7 +184,7 @@
},
oneditprepare: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) == "/") {
if (root.slice(-1) == "/") {
root = root.slice(0,-1);
}
if (root == "") {
@@ -213,6 +222,7 @@
defaults: {
name: {value:""},
method:{value:"GET"},
ret: {value:"txt"},
url:{value:""},
//user -> credentials
//pass -> credentials
@@ -249,6 +259,14 @@
$('#node-input-password').val('');
}
});
},
$("#node-input-ret").change(function() {
if ($("#node-input-ret").val() === "obj") {
$("#tip-json").show();
} else {
$("#tip-json").hide();
}
});
}
});
</script>

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.
@@ -23,10 +23,10 @@ module.exports = function(RED) {
var getBody = require('raw-body');
var mustache = require("mustache");
var querystring = require("querystring");
var cors = require('cors');
var jsonParser = express.json();
var urlencParser = express.urlencoded();
var onHeaders = require('on-headers');
function rawBodyParser(req, res, next) {
if (req._body) { return next(); }
@@ -66,7 +66,7 @@ module.exports = function(RED) {
} else {
node.send({req:req,res:res});
}
}
};
var corsHandler = function(req,res,next) { next(); }
@@ -74,15 +74,35 @@ 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()) {
metricsHandler = function(req, res, next) {
var startAt = process.hrtime();
onHeaders(res, function() {
if(res._msgId) {
var diff = process.hrtime(startAt);
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
var metricResponseTime = ms.toFixed(3);
var metricContentLength = res._headers["content-length"];
//assuming that _id has been set for res._metrics in HttpOut node!
node.metric("response.time.millis", {_id:res._msgId} , metricResponseTime);
node.metric("response.content-length.bytes", {_id:res._msgId} , metricContentLength);
}
});
next();
};
}
if (this.method == "get") {
RED.httpNode.get(this.url,corsHandler,this.callback,this.errorHandler);
RED.httpNode.get(this.url,corsHandler,metricsHandler,this.callback,this.errorHandler);
} else if (this.method == "post") {
RED.httpNode.post(this.url,corsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
RED.httpNode.post(this.url,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
} else if (this.method == "put") {
RED.httpNode.put(this.url,corsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
RED.httpNode.put(this.url,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
} else if (this.method == "delete") {
RED.httpNode.delete(this.url,corsHandler,this.callback,this.errorHandler);
RED.httpNode.delete(this.url,corsHandler,metricsHandler,this.callback,this.errorHandler);
}
this.on("close",function() {
@@ -94,11 +114,13 @@ module.exports = function(RED) {
}
}
if (RED.settings.httpNodeCors) {
var route = RED.httpNode.route['options'];
for (var j = 0; j<route.length; j++) {
if (route[j].path == this.url) {
route.splice(j,1);
//break;
var routes = RED.httpNode.routes['options'];
if (routes) {
for (var j = 0; j<routes.length; j++) {
if (routes[j].path == this.url) {
routes.splice(j,1);
//break;
}
}
}
}
@@ -133,6 +155,8 @@ module.exports = function(RED) {
}
msg.res.set('content-length', len);
}
msg.res._msgId = msg._id;
msg.res.send(statusCode,msg.payload);
}
} else {
@@ -142,16 +166,22 @@ module.exports = function(RED) {
}
RED.nodes.registerType("http response",HTTPOut);
function HTTPRequest(n) {
RED.nodes.createNode(this,n);
var nodeUrl = n.url;
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
var nodeMethod = n.method || "GET";
this.ret = n.ret || "txt";
var node = this;
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) {
url = mustache.render(nodeUrl,msg);
@@ -163,8 +193,19 @@ module.exports = function(RED) {
url = "http://"+url;
}
var method = (msg.method||nodeMethod).toUpperCase();
//node.log(method+" : "+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 opts = urllib.parse(url);
opts.method = method;
opts.headers = {};
@@ -186,7 +227,7 @@ module.exports = function(RED) {
}
var payload = null;
if (msg.payload && (method == "POST" || method == "PUT") ) {
if (msg.payload && (method == "POST" || method == "PUT" || method == "PATCH" ) ) {
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
payload = msg.payload;
} else if (typeof msg.payload == "number") {
@@ -205,9 +246,8 @@ module.exports = function(RED) {
opts.headers['content-length'] = Buffer.byteLength(payload);
}
}
var req = ((/^https/.test(url))?https:http).request(opts,function(res) {
res.setEncoding('utf8');
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.payload = "";
@@ -215,6 +255,23 @@ module.exports = function(RED) {
msg.payload += chunk;
});
res.on('end',function() {
if (node.metric()) {
// Calculate request time
var diff = process.hrtime(preRequestTimestamp);
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
var metricRequestDurationMillis = ms.toFixed(3);
node.metric("duration.millis", msg, metricRequestDurationMillis);
if(res.client && res.client.bytesRead) {
node.metric("size.bytes", msg, res.client.bytesRead);
}
}
if (node.ret === "bin") {
msg.payload = new Buffer(msg.payload,"binary");
}
else if (node.ret === "obj") {
try { msg.payload = JSON.parse(msg.payload); }
catch(e) { node.warn("JSON parse error"); }
}
node.send(msg);
node.status({});
});

View File

@@ -13,13 +13,80 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/javascript">
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
$("#websocket-client-row").show();
}
else {
$("#websocket-server-row").show();
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
else {
$("#node-input-mode").val('server').change();
}
}
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') {
$("#node-input-server").append('<option value="">Dummy</option>');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
}
}
function ws_label() {
var nodeid = (this.client)?this.client:this.server;
var wsNode = RED.nodes.node(nodeid);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
}
function ws_validateserver() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return true;
}
else {
return RED.nodes.node(this.server) != null;
}
}
function ws_validateclient() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return RED.nodes.node(this.client) != null;
}
else {
return true;
}
}
</script>
<!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<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">
@@ -29,7 +96,7 @@
<script type="text/x-red" data-help-name="websocket in">
<p>WebSocket input node.</p>
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
The listener can be configured to expect a properly formed JSON string, in which
The socket can be configured to expect a properly formed JSON string, in which
case it will parse the JSON and send on the resulting object as the entire message.</p>
</script>
@@ -38,28 +105,39 @@
category: 'input',
defaults: {
name: {value:""},
server: {type:"websocket-listener"}
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:0,
outputs:1,
icon: "white-globe.png",
label: function() {
var wsNode = RED.nodes.node(this.server);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
</script>
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<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">
@@ -68,7 +146,7 @@
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket out node.</p>
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The listener
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
can be configured to encode the entire message object as a JSON string and send that
over the WebSocket.</p>
@@ -84,20 +162,20 @@
category: 'output',
defaults: {
name: {value:""},
server: {type:"websocket-listener", required:true}
server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
},
color:"rgb(215, 215, 160)",
inputs:1,
outputs:0,
icon: "white-globe.png",
align: "right",
label: function() {
var wsNode = RED.nodes.node(this.server);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
},
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
</script>
@@ -115,7 +193,7 @@
</select>
</div>
<div class="form-tips">
Be default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
The listener can be configured to send or receive the entire message object as a JSON formatted string.
<p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p>
</div>
@@ -157,7 +235,45 @@
$("#node-config-ws-path").html(root);
$("#node-config-ws-tip").show();
}
//document.getElementById("node-config-wsdocpath").innerHTML=
}
});
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false">Send/Receive payload</option>
<option value="true">Send/Receive entire message</option>
</select>
</div>
<div class="form-tips">
<p>URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.</p>
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
The client can be configured to send or receive the entire message object as a JSON formatted string.
</div>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>This configuration node connects a WebSocket client to the specified URL.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-client',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
return this.path;
}
});
</script>

View File

@@ -31,32 +31,9 @@ module.exports = function(RED) {
node.wholemsg = (n.wholemsg === "true");
node._inputNodes = []; // collection of nodes that want to receive events
var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
// Workaround https://github.com/einaros/ws/pull/253
// Listen for 'newListener' events from RED.server
node._serverListeners = {};
var storeListener = function(/*String*/event,/*function*/listener){
if(event == "error" || event == "upgrade" || event == "listening"){
node._serverListeners[event] = listener;
}
}
node._clients = {};
RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server
node.server = new ws.Server({server:RED.server,path:path});
// Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events
RED.server.removeListener('newListener',storeListener);
node.server.on('connection', function(socket){
function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16);
node._clients[id] = socket;
socket.on('close',function() {
@@ -68,7 +45,43 @@ module.exports = function(RED) {
socket.on('error', function(err) {
node.warn("An error occured on the ws connection: "+inspect(err));
});
});
}
// match absolute url
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
if(node.isServer)
{
var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
// Workaround https://github.com/einaros/ws/pull/253
// Listen for 'newListener' events from RED.server
node._serverListeners = {};
var storeListener = function(/*String*/event,/*function*/listener){
if(event == "error" || event == "upgrade" || event == "listening"){
node._serverListeners[event] = listener;
}
}
RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server
node.server = new ws.Server({server:RED.server,path:path});
// Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events
RED.server.removeListener('newListener',storeListener);
node.server.on('connection', handleConnection);
}
else
{
// Connect to remote endpoint
var socket = new ws(node.path);
node.server = socket; // keep for closing
handleConnection(socket);
}
node.on("close", function() {
// Workaround https://github.com/einaros/ws/pull/253
@@ -88,6 +101,7 @@ module.exports = function(RED) {
});
}
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler){
this._inputNodes.push(handler);
@@ -116,8 +130,13 @@ module.exports = function(RED) {
WebSocketListenerNode.prototype.broadcast = function(data){
try {
for (var i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data);
if(this.isServer) {
for (var i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data);
}
}
else {
this.server.send(data);
}
}
catch(e) { // swallow any errors
@@ -125,7 +144,7 @@ module.exports = function(RED) {
}
}
WebSocketListenerNode.prototype.send = function(id,data) {
WebSocketListenerNode.prototype.reply = function(id,data) {
var session = this._clients[id];
if (session) {
try {
@@ -138,7 +157,7 @@ module.exports = function(RED) {
function WebSocketInNode(n) {
RED.nodes.createNode(this,n);
this.server = n.server;
this.server = (n.client)?n.client:n.server;
var node = this;
this.serverConfig = RED.nodes.getNode(this.server);
if (this.serverConfig) {
@@ -152,7 +171,7 @@ module.exports = function(RED) {
function WebSocketOutNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.server = n.server;
this.server = (n.client)?n.client:n.server;
this.serverConfig = RED.nodes.getNode(this.server);
if (!this.serverConfig) {
this.error("Missing server configuration");
@@ -171,7 +190,7 @@ module.exports = function(RED) {
}
}
if (msg._session && msg._session.type == "websocket") {
node.serverConfig.send(msg._session.id,payload);
node.serverConfig.reply(msg._session.id,payload);
} else {
node.serverConfig.broadcast(payload,function(error){
if (!!error) {

View File

@@ -23,17 +23,20 @@
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div id="node-input-tip" class="form-tips">On Windows you must use double slashes \\ in any directory names.</div>
<div id="node-input-tip" class="form-tips">On Windows you must use double back-slashes \\ in any directory names.</div>
</script>
<script type="text/x-red" data-help-name="watch">
<p>Watches a directory or file for any changes.</p>
<p>You can enter a list of comma separated directories or files if you like. You will need to put " around any that have spaces in.</p>
<p>On Windows you must use double 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 watched criteria is returned in <b>msg.topic</b>.</p>
<p>Watches a directory or file for changes.</p>
<p>You can enter a list of comma separated directories and/or files. You will
need to put quotes "..." around any that have spaces in.</p>
<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>Of course in Linux, <i>everything</i> could be a file and thus 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>
<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>
</script>
<script type="text/javascript">

View File

@@ -103,11 +103,11 @@
</div>
<div class="form-row">
<table><tr>
<td width = "102px"><i class="fa fa-wrench"></i> Settings</td>
<td width = "100px">Baud Rate</td>
<td width = "80px">Data Bits</td>
<td width = "80px">Parity</td>
<td width = "80px">Stop Bits</td>
<td width="102px"><i class="fa fa-wrench"></i> Settings</td>
<td width="100px">Baud Rate</td>
<td width="80px">Data Bits</td>
<td width="80px">Parity</td>
<td width="80px">Stop Bits</td>
</tr><tr><td>&nbsp;</td>
<td>
<select type="text" id="node-config-input-serialbaud" style="width: 100px;">
@@ -153,63 +153,36 @@
</tr></table><br/>
<div class="form-row">
<label for="node-config-input-out"><i class="fa fa-cut"></i> Split input</label>
<select type="text" id="node-config-input-out" style="width:52%;">
<option value="char">when character received is</option>
<option value="time">after a fixed timeout of</option>
<option value="count">a fixed number of characters</option>
<label><i class="fa fa-sign-in"></i> Input</label>
</div>
<div class="form-row" style="padding-left: 10px;">
Split input
<select type="text" id="node-config-input-out" style="margin-left: 5px; width:200px;">
<option value="char">on the character</option>
<option value="time">after a timeout of</option>
<option value="count">into fixed lengths of</option>
</select>
<input type="text" id="node-config-input-newline" style="width:50px;">
<span id="node-units"></span>
</div>
<div class="form-row">
<label for="node-config-input-bin"><i class="fa fa-sign-in"></i> and deliver</label>
<select type="text" id="node-config-input-bin" style="width: 77%;">
<div class="form-row" style="padding-left: 10px;">
and deliver
<select type="text" id="node-config-input-bin" style="margin-left: 5px; width: 150px;">
<option value="false">ascii strings</option>
<option value="bin">binary buffers</option>
</select>
</div>
<br/>
<div class="form-row" id="node-config-addchar">
<label for="node-config-input-addchar"><i class="fa fa-sign-out"></i> On output</label>
<select type="text" id="node-config-input-addchar" style="width: 77%;">
<option value="false">don't add 'split' character to output messages</option>
<option value="true">add 'split' character to output messages</option>
</select>
<div id="node-config-addchar">
<div class="form-row">
<label><i class="fa fa-sign-out"></i> Output</label>
</div>
<div class="form-row">
<input style="width: 30px;margin-left: 10px; vertical-align: top;" type="checkbox" id="node-config-input-addchar"><label style="width: auto;" for="node-config-input-addchar">add split character to output messages</label>
</div>
</div>
<div class="form-tips" id="tip-split">Tip: the "Split on" character is used to split the input into separate messages. It can also be added to every message sent out to the serial port.</div>
<div class="form-tips" id="tip-bin" hidden>Tip: In timeout mode timeout starts from arrival of first character.</div>
<script>
var previous = null;
$("#node-config-input-out").on('focus', function () { previous = this.value; }).change(function() {
if (previous == null) { previous = $("#node-config-input-out").val(); }
if ($("#node-config-input-out").val() == "char") {
if (previous != "char") { $("#node-config-input-newline").val("\\n"); }
$("#node-units").text("");
$("#node-config-addchar").show();
$("#tip-split").show();
$("#tip-bin").hide();
}
else if ($("#node-config-input-out").val() == "time") {
if (previous != "time") { $("#node-config-input-newline").val("0"); }
$("#node-units").text("ms");
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").show();
}
else {
if (previous != "count") { $("#node-config-input-newline").val("12"); }
$("#node-units").text("chars");
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").hide();
}
});
</script>
</script>
<script type="text/javascript">
@@ -223,8 +196,8 @@
parity: {value:"none",required:true},
stopbits: {value:1,required:true},
newline: {value:"\\n"},
bin: {value:""},
out: {value:""},
bin: {value:"false"},
out: {value:"char"},
addchar: {value:false}
},
label: function() {
@@ -235,6 +208,34 @@
return this.serialport+":"+this.serialbaud+"-"+this.databits+this.parity.charAt(0).toUpperCase()+this.stopbits;
},
oneditprepare: function() {
var previous = null;
$("#node-config-input-out").on('focus', function () { previous = this.value; }).change(function() {
if (previous == null) { previous = $("#node-config-input-out").val(); }
if ($("#node-config-input-out").val() == "char") {
if (previous != "char") { $("#node-config-input-newline").val("\\n"); }
$("#node-units").text("");
$("#node-config-addchar").show();
$("#tip-split").show();
$("#tip-bin").hide();
}
else if ($("#node-config-input-out").val() == "time") {
if (previous != "time") { $("#node-config-input-newline").val("0"); }
$("#node-units").text("ms");
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").show();
}
else {
if (previous != "count") { $("#node-config-input-newline").val(""); }
$("#node-units").text("chars");
$("#node-config-addchar").hide();
$("#node-config-input-addchar").val("false");
$("#tip-split").hide();
$("#tip-bin").hide();
}
});
try {
$("#node-config-input-serialport").autocomplete( "destroy" );
} catch(err) {

View File

@@ -299,12 +299,9 @@ module.exports = function(RED) {
}
}();
RED.httpAdmin.get("/serialports",function(req,res) {
RED.httpAdmin.get("/serialports", RED.auth.needsPermission('serial.read'), function(req,res) {
serialp.list(function (err, ports) {
//console.log(JSON.stringify(ports));
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(JSON.stringify(ports));
res.end();
res.json(ports);
});
});
}

View File

@@ -213,7 +213,6 @@
else {
$("#fin-tip2").hide();
}
};
updateOptions();
$("#node-input-beserver").change(updateOptions);
@@ -225,8 +224,8 @@
<script type="text/x-red" data-template-name="tcp request">
<div class="form-row">
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label>
<input type="text" id="node-input-server" placeholder="ip.address" style="width:50%">
&nbsp;port <input type="text" id="node-input-port" style="width:50px">
<input type="text" id="node-input-server" placeholder="ip.address" style="width:45%">
&nbsp;port <input type="text" id="node-input-port" placeholder="number" style="width:60px">
</div>
<div class="form-row">
<label for="node-input-out"><i class="fa fa-sign-out"></i> Return</label>
@@ -243,7 +242,8 @@
<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"><b>Tip:</b> outputs a binary <b>Buffer</b>, so you may want to .toString() it.</div>
<div class="form-tips"><b>Tip:</b> Outputs a binary <b>Buffer</b>, so you may want to .toString() it.</br/>
<b>Tip:</b> Leave host and port blank if you want to overide with msg.host and msg.port properties.</div>
<script>
var previous = null;
$("#node-input-out").on('focus', function () { previous = this.value; }).change(function() {
@@ -273,6 +273,7 @@
returned characters into a fixed buffer, match a specified character before returning,
wait a fixed timeout from first reply and then return, or just sit and wait for data.</p>
<p>The response will be output in <b>msg.payload</b> as a buffer, so you may want to .toString() it.</p>
<p>If you leave tcp host or port blank they must be set by using the <b>msg.host</b> and <b>msg.port</b> properties.</p>
</script>
<script type="text/javascript">
@@ -280,8 +281,8 @@
category: 'function',
color:"Silver",
defaults: {
server: {value:"",required:true},
port: {value:"",required:true,validate:RED.validators.number()},
server: {value:""},
port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)},
out: {value:"time",required:true},
splitc: {value:"0",required:true},
name: {value:""}

View File

@@ -33,6 +33,7 @@ module.exports = function(RED) {
this.base64 = n.base64;
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
this.closing = false;
this.connected = false;
var node = this;
var count = 0;
@@ -47,6 +48,7 @@ module.exports = function(RED) {
var id = (1+Math.random()*4294967295).toString(16);
client = net.connect(node.port, node.host, function() {
buffer = (node.datatype == 'buffer')? new Buffer(0):"";
node.connected = true;
node.log("connected to "+node.host+":"+node.port);
node.status({fill:"green",shape:"dot",text:"connected"});
});
@@ -92,6 +94,7 @@ module.exports = function(RED) {
});
client.on('close', function() {
delete connectionPool[id];
node.connected = false;
node.status({fill:"red",shape:"ring",text:"disconnected"});
if (!node.closing) {
if (end) { // if we were asked to close then try to reconnect once very quick.
@@ -102,6 +105,8 @@ module.exports = function(RED) {
node.log("connection lost to "+node.host+":"+node.port);
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
}
} else {
if (node.done) { node.done(); }
}
});
client.on('error', function(err) {
@@ -110,10 +115,12 @@ module.exports = function(RED) {
}
setupTcpClient();
this.on('close', function() {
this.on('close', function(done) {
node.done = done;
this.closing = true;
client.end();
clearTimeout(reconnectTimeout);
if (!node.connected) { done(); }
});
} else {
var server = net.createServer(function (socket) {
@@ -182,8 +189,11 @@ module.exports = function(RED) {
node.error('unable to listen on port '+node.port+' : '+err);
} else {
node.log('listening on port '+node.port);
node.on('close', function() {
for (var c in connectionPool) {
connectionPool[c].end();
connectionPool[c].unref();
}
node.closing = true;
server.close();
node.log('stopped listening on port '+node.port);
@@ -204,19 +214,19 @@ module.exports = function(RED) {
this.beserver = n.beserver;
this.name = n.name;
this.closing = false;
this.connected = false;
var node = this;
if (!node.beserver||node.beserver=="client") {
var reconnectTimeout;
var client = null;
var connected = false;
var end = false;
var setupTcpClient = function() {
node.log("connecting to "+node.host+":"+node.port);
node.status({fill:"grey",shape:"dot",text:"connecting"});
client = net.connect(node.port, node.host, function() {
connected = true;
node.connected = true;
node.log("connected to "+node.host+":"+node.port);
node.status({fill:"green",shape:"dot",text:"connected"});
});
@@ -227,7 +237,7 @@ module.exports = function(RED) {
});
client.on('close', function() {
node.status({fill:"red",shape:"ring",text:"disconnected"});
connected = false;
node.connected = false;
client.destroy();
if (!node.closing) {
if (end) {
@@ -238,13 +248,15 @@ module.exports = function(RED) {
node.log("connection lost to "+node.host+":"+node.port);
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
}
} else {
if (node.done) { node.done(); }
}
});
}
setupTcpClient();
node.on("input", function(msg) {
if (connected && msg.payload != null) {
if (node.connected && msg.payload != null) {
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
@@ -259,10 +271,12 @@ module.exports = function(RED) {
}
});
node.on("close", function() {
node.on("close", function(done) {
node.done = done;
this.closing = true;
client.end();
clearTimeout(reconnectTimeout);
if (!node.connected) { done(); }
});
} else if (node.beserver == "reply") {
@@ -334,6 +348,10 @@ module.exports = function(RED) {
} else {
node.log('listening on port '+node.port);
node.on('close', function() {
for (var c in connectedSockets) {
connectedSockets[c].end();
connectedSockets[c].unref();
}
server.close();
node.log('stopped listening on port '+node.port);
});
@@ -370,12 +388,19 @@ module.exports = function(RED) {
client = net.Socket();
client.setTimeout(socketTimeout);
node.status({});
client.connect(node.port, node.server, function() {
//node.log('client connected');
node.status({fill:"green",shape:"dot",text:"connected"});
node.connected = true;
client.write(msg.payload);
});
var host = node.server || msg.host;
var port = node.port || msg.port;
if (host && port) {
client.connect(port, host, function() {
//node.log('client connected');
node.status({fill:"green",shape:"dot",text:"connected"});
node.connected = true;
client.write(msg.payload);
});
}
else {
node.warn("Host and/or port not set");
}
client.on('data', function(data) {
//node.log("data:"+ data.length+":"+ data);
@@ -440,6 +465,10 @@ module.exports = function(RED) {
client = null;
});
client.on('close', function() {
if (node.done) { node.done(); }
});
client.on('error', function() {
node.log('connect failed');
node.status({fill:"red",shape:"ring",text:"error"});
@@ -451,7 +480,7 @@ module.exports = function(RED) {
if (client) {
client.end();
setTimeout(function() {
client.connect(node.port, node.server, function() {
client.connect(port, host, function() {
//node.log('client connected');
node.connected = true;
client.write(msg.payload);
@@ -463,8 +492,13 @@ module.exports = function(RED) {
else { client.write(msg.payload); }
});
this.on("close", function() {
if (client) { buf = null; client.end(); }
this.on("close", function(done) {
node.done = done;
if (client) {
buf = null;
client.end();
}
if (!node.connected) { done(); }
});
}

View File

@@ -19,7 +19,7 @@ var events = require("events");
//var inspect = require("sys").inspect;
//var Client = module.exports.Client = function(
var port = 1883;
var host = "localhost";
@@ -32,7 +32,7 @@ function MQTTClient(port,host) {
this.lastOutbound = (new Date()).getTime();
this.lastInbound = (new Date()).getTime();
this.connected = false;
this._nextMessageId = function() {
this.messageId += 1;
if (this.messageId > 0xFFFF) {
@@ -53,7 +53,7 @@ MQTTClient.prototype.connect = function(options) {
self.options.clean = self.options.clean||true;
self.options.protocolId = 'MQIsdp';
self.options.protocolVersion = 3;
self.client = mqtt.createConnection(this.port,this.host,function(err,client) {
if (err) {
self.connected = false;
@@ -87,9 +87,9 @@ MQTTClient.prototype.connect = function(options) {
if (packet.returnCode == 0) {
self.watchdog = setInterval(function(self) {
var now = (new Date()).getTime();
//util.log('[mqtt] ['+self.uid+'] watchdog '+inspect({connected:self.connected,connectionError:self.connectionError,pingOutstanding:self.pingOutstanding,now:now,lastOutbound:self.lastOutbound,lastInbound:self.lastInbound}));
if (now - self.lastOutbound > self.options.keepalive*500 || now - self.lastInbound > self.options.keepalive*500) {
if (self.pingOutstanding) {
//util.log('[mqtt] ['+self.uid+'] watchdog pingOustanding - disconnect');
@@ -105,7 +105,7 @@ MQTTClient.prototype.connect = function(options) {
self.client.pingreq();
}
}
},self.options.keepalive*500,self);
self.pingOutstanding = false;
self.lastInbound = (new Date()).getTime()
@@ -131,7 +131,7 @@ MQTTClient.prototype.connect = function(options) {
delete self.pendingSubscriptions[packet.messageId];
});
client.on('publish',function(packet) {
self.lastInbound = (new Date()).getTime()
self.lastInbound = (new Date()).getTime();
if (packet.qos < 2) {
var p = packet;
self.emit('message',p.topic,p.payload,p.qos,p.retain);
@@ -145,7 +145,7 @@ MQTTClient.prototype.connect = function(options) {
self.client.puback(packet);
}
});
client.on('pubrel',function(packet) {
self.lastInbound = (new Date()).getTime()
var p = self.inboundMessages[packet.messageId];
@@ -156,12 +156,12 @@ MQTTClient.prototype.connect = function(options) {
self.lastOutbound = (new Date()).getTime()
self.client.pubcomp(packet);
});
client.on('puback',function(packet) {
self.lastInbound = (new Date()).getTime()
// outbound qos-1 complete
});
client.on('pubrec',function(packet) {
self.lastInbound = (new Date()).getTime()
self.lastOutbound = (new Date()).getTime()
@@ -176,7 +176,7 @@ MQTTClient.prototype.connect = function(options) {
self.lastInbound = (new Date()).getTime()
self.pingOutstanding = false;
});
this.lastOutbound = (new Date()).getTime()
this.connectionError = false;
client.connect(self.options);
@@ -192,8 +192,9 @@ MQTTClient.prototype.subscribe = function(topic,qos) {
messageId: self._nextMessageId()
};
this.pendingSubscriptions[options.messageId] = topic;
this.lastOutbound = (new Date()).getTime()
this.lastOutbound = (new Date()).getTime();
self.client.subscribe(options);
self.client.setPacketEncoding('binary');
}
}
MQTTClient.prototype.unsubscribe = function(topic) {
@@ -212,7 +213,7 @@ MQTTClient.prototype.unsubscribe = function(topic) {
MQTTClient.prototype.publish = function(topic,payload,qos,retain) {
var self = this;
if (self.connected) {
if (!Buffer.isBuffer(payload)) {
if (typeof payload === "object") {
payload = JSON.stringify(payload);
@@ -251,4 +252,3 @@ module.exports.createClient = function(port,host) {
var mqtt_client = new MQTTClient(port,host);
return mqtt_client;
}

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,17 +19,18 @@
<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="padding-top:10px;">
<div class="form-row">
If msg.<input type="text" id="node-input-property" style="width: 200px;"/>
</div>
<div class="form-row">
<div id="node-input-rule-container-div" style="border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
<ol id="node-input-rule-container" style=" list-style-type:none; margin: 0;">
</ol>
<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: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
<ol id="node-input-rule-container" style=" list-style-type:none; margin: 0;"></ol>
</div>
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> Add</a>
</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>
<div class="form-row">
<select id="node-input-checkall" style="width:100%; margin-right:5px;">
<option value="true">checking all rules</option>
<option value="false">stopping after first match</option>
@@ -82,9 +83,10 @@
];
function generateRule(i,rule) {
var container = $('<li/>',{style:"margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
var row = $('<div/>').appendTo(container);
var row2 = $('<div/>',{style:"padding-top: 5px; text-align: right;"}).appendTo(container);
$('<i style="color: #eee; cursor: move;" class="node-input-rule-handle fa fa-bars"></i>').appendTo(row);
var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row);
for (var d in operators) {
@@ -98,7 +100,10 @@
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 50px;margin-left:2px;"}).appendTo(btwnField);
var finalspan = $('<span/>',{style:"float: right; margin-top: 3px;margin-right: 10px;"}).appendTo(row);
finalspan.append(' send to <span class="node-input-rule-index">'+i+'</span> ');
finalspan.append(' &#8594; <span class="node-input-rule-index">'+i+'</span> ');
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
selectField.change(function() {
var type = selectField.children("option:selected").val();
@@ -122,8 +127,6 @@
}
});
var deleteButton = $('<a/>',{href:"#",class:"btn btn-mini", style:"margin-left: 5px;"}).appendTo(finalspan);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
deleteButton.click(function() {
container.css({"background":"#fee"});
@@ -158,15 +161,37 @@
generateRule(i+1,rule);
}
function switchDialogResize(ev,ui) {
$("#node-input-rule-container-div").css("height",(ui.size.height-260)+"px");
function switchDialogResize() {
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");
};
$( "#node-input-rule-container" ).sortable({
axis: "y",
update: function( event, ui ) {
var rules = $("#node-input-rule-container").children();
rules.each(function(i) {
$(this).find(".node-input-rule-index").html(i+1);
});
},
handle:".node-input-rule-handle",
cursor: "move"
});
$( "#node-input-rule-container .node-input-rule-handle" ).disableSelection();
$( "#dialog" ).on("dialogresize", switchDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-switch');
if (size) {
switchDialogResize(null,{size:size});
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
switchDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {

View File

@@ -36,7 +36,7 @@
<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>
<label for="node-input-reg" style="width: 70%;">Use regular expressions</label>
</div>
<div class="form-tips" id="node-tip"></div>
<br/>
@@ -47,10 +47,10 @@
</script>
<script type="text/x-red" data-help-name="change">
<p>A simple function node to change, replace, add or delete properties of a message.</p>
<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> Replace only operates on <b>strings</b>. Anything else will be passed straight through.</p>
<p><b>Note:</b> Set and replace only operate using <b>strings</b>. Anything else will be passed straight through.</p>
</script>
<script type="text/javascript">
@@ -61,7 +61,9 @@
action: {value:"replace",required:true},
property: {value:"payload",required:true},
from: {value:"",validate: function(v) {
if (this.action == "change" && this.reg) {
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;
@@ -82,8 +84,10 @@
if (this.name) {
return this.name;
}
if (this.action == "replace") {
if (this.action === "replace") {
return "set msg."+this.property;
} else if (this.action === "change") {
return "replace msg."+this.property;
} else {
return this.action+" msg."+this.property
}

View File

@@ -16,59 +16,75 @@
module.exports = function(RED) {
"use strict";
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.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, "\\$&");
}
var makeNew = function( stem, path, value ) {
var lastPart = (arguments.length === 3) ? path.pop() : false;
for (var i = 0; i < path.length; i++) {
stem = stem[path[i]] = stem[path[i]] || {};
}
if (lastPart) { stem = stem[lastPart] = value; }
return stem;
};
this.on('input', function (msg) {
if (node.action == "change") {
this.on('input', function(msg) {
var propertyParts;
var depth = 0;
if (node.action === "change") {
try {
node.re = new RegExp(this.from, "g");
} catch (e) {
node.error(e.message);
}
if (typeof msg[node.property] === "string") {
msg[node.property] = (msg[node.property]).replace(node.re, node.to);
}
}
//else if (node.action == "replace") {
//if (node.to.indexOf("msg.") == 0) {
//msg[node.property] = eval(node.to);
//}
//else {
//msg[node.property] = node.to;
//}
//}
else if (node.action == "replace") {
if (node.to.indexOf("msg.") === 0) {
makeNew( msg, node.property.split("."), eval(node.to) );
}
else {
makeNew( msg, node.property.split("."), node.to );
}
//makeNew( msg, node.property.split("."), node.to );
}
else if (node.action == "delete") {
delete(msg[node.property]);
}
propertyParts = node.property.split(".");
try {
propertyParts.reduce(function(obj, i) {
var to = node.to;
// Set msg from property to another msg property
if (node.action === "replace" && node.to.indexOf("msg.") === 0) {
var parts = to.substring(4);
var msgPropParts = parts.split(".");
try {
msgPropParts.reduce(function(ob, j) {
to = (typeof ob[j] !== "undefined" ? ob[j] : undefined);
return to;
}, msg);
} catch (err) {}
}
if (++depth === propertyParts.length) {
if (node.action === "change") {
if (typeof obj[i] === "string") {
obj[i] = obj[i].replace(node.re, node.to);
}
} else if (node.action === "replace") {
if (typeof to === "undefined") {
delete(obj[i]);
} else {
obj[i] = to;
}
} else if (node.action === "delete") {
delete(obj[i]);
}
} else {
// to property doesn't exist, don't create empty object
if (typeof to === "undefined") {
return;
// setting a non-existent multilevel object, create empty parent
} else if (!obj[i]) {
obj[i] = {};
}
return obj[i];
}
}, msg);
} catch (err) {}
node.send(msg);
});
}
RED.nodes.registerType("change", ChangeNode);
}
};

View File

@@ -28,6 +28,7 @@
<option value=" ">space</option>
<option value=";">semicolon</option>
<option value=":">colon</option>
<option value="#">hashtag</option>
<option value="">other...</option>
</select>
<input style="width: 40px;" type="text" id="node-input-sep" pattern=".">
@@ -100,7 +101,7 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " ") {
if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " " || this.sep == "#") {
$("#node-input-select-sep").val(this.sep);
$("#node-input-sep").hide();
} else {

View File

@@ -102,11 +102,12 @@ module.exports = function(RED) {
if (msg.payload[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]; }
}
else if ((msg.payload[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 (!isNaN(Number(k[j]))) { k[j] = Number(k[j]); }
if ( (k[j].charAt(0) !== "+") && !isNaN(Number(k[j])) ) { k[j] = Number(k[j]); }
o[node.template[j]] = k[j];
}
j += 1;
@@ -115,7 +116,7 @@ module.exports = function(RED) {
else if (f && ((msg.payload[i] === "\n") || (msg.payload[i] === "\r"))) { // handle multiple lines
//console.log(j,k,o,k[j]);
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
if (!isNaN(Number(k[j]))) { k[j] = Number(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];
}
@@ -136,7 +137,7 @@ module.exports = function(RED) {
//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 (!isNaN(Number(k[j]))) { k[j] = Number(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];
}

View File

@@ -40,13 +40,14 @@
<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: The <b>Select</b> value is a <a href="http://api.jquery.com/category/selectors/" target="_new"><i><u>jQuery</u></i></a> style selector.</div>
<div class="form-tips">Tip: The <b>Select</b> value is a <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_new"><i><u>CSS Selector</u></i></a>, similar to a jQuery selector.</div>
</script>
<script type="text/x-red" data-help-name="html">
<p>Extracts elements from an html document held in <b>msg.payload</b> using a selector.</p>
<p>The selector uses the <a href="http://api.jquery.com/category/selectors/" target="_new">jQuery syntax</a>.</p>
<p>The result is either a single message with a payload containing an array of the matched elements, or multiple
<p>The selector uses <a href=="https://github.com/cheeriojs/cheerio/blob/master/Readme.md" target="_new">Cheerio</a>
which uses the <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_new">CSS selector</a> syntax.</p>
<p>The result can be either a single message with a payload containing an array of the matched elements, or multiple
messages that each contain a matched element.</p>
</script>
@@ -55,10 +56,10 @@
category: 'function',
color:"#DEBD5C",
defaults: {
name: {value:""},
tag: {value:""},
ret: {value:"html"},
as: {value:"single"},
name: {value:""}
as: {value:"single"}
},
inputs:1,
outputs:1,

View File

@@ -17,8 +17,36 @@
<script type="text/x-red" data-template-name="xml">
<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">
<input type="text" id="node-input-name" placeholder="Name" style="width:280px !important">
</div>
<div class="form-row" id="advanced">
</div>
<div id="advanced-options">
<div class="form-row">
<i class="fa fa-key"></i> Represent XML tag attributes as a property named <input type="text" id="node-input-attr" style="width:20px !important">
</div>
<div class="form-row">
<i class="fa fa-key"></i> Prefix to access character content <input type="text" id="node-input-chr" style="width:20px !important">
</div>
<div class="form-tips">There is no simple way to convert XML attributes to JSON
so the approach taken here is to add a property, named $ by default, to the JSON structure.</div>
</div>
<script> {
var showadvanced = showadvanced || true;
var showall = function() {
showadvanced = !showadvanced;
if (showadvanced) {
$("#advanced-options").show();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-minus-square"></i> Advanced options</label>');
}
else {
$("#advanced-options").hide();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-plus-square"></i> Advanced options ...</label>');
}
};
showall();
$("#advanced").click( function() { showall(); });
} </script>
</script>
<script type="text/x-red" data-help-name="xml">
@@ -33,7 +61,9 @@
category: 'function',
color:"#DEBD5C",
defaults: {
name: {value:""}
name: {value:""},
attr: {value:'$',required:true},
chr: {value:'_',required:true}
},
inputs:1,
outputs:1,

View File

@@ -22,6 +22,8 @@ module.exports = function(RED) {
function XMLNode(n) {
RED.nodes.createNode(this,n);
this.attrkey = n.attr || '$';
this.charkey = n.chr || '_';
var node = this;
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
@@ -30,7 +32,7 @@ module.exports = function(RED) {
node.send(msg);
}
else if (typeof msg.payload == "string") {
parseString(msg.payload, {strict:true,async:true}, function (err, result) {
parseString(msg.payload, {strict:true,async:true,attrkey:node.attrkey,charkey:node.charkey}, function (err, result) {
if (err) { node.error(err); }
else {
msg.payload = result;

View File

@@ -200,6 +200,7 @@
<script type="text/x-red" data-help-name="twitter out">
<p>Twitter out node. Tweets the <b>msg.payload</b>.</p>
<p>To send a Direct Message (DM) - use a payload like "D {username} {message}"</p>
<p>If <b>msg.media</b> exists and is a Buffer object, this node will treat it
as an image and attach it to the tweet.</p>
</script>

View File

@@ -19,7 +19,7 @@ module.exports = function(RED) {
var ntwitter = require('twitter-ng');
var OAuth= require('oauth').OAuth;
var request = require('request');
function TwitterNode(n) {
RED.nodes.createNode(this,n);
this.screen_name = n.screen_name;
@@ -32,6 +32,32 @@ module.exports = function(RED) {
}
});
/**
* Populate msg.location based on data found in msg.tweet.
*/
function addLocationToTweet(msg) {
if(msg.tweet) {
if(msg.tweet.geo) { // if geo is set, always set location from geo
if(msg.tweet.geo.coordinates && msg.tweet.geo.coordinates.length === 2) {
if (!msg.location) { msg.location = {}; }
// coordinates[0] is lat, coordinates[1] is lon
msg.location.lat = msg.tweet.geo.coordinates[0];
msg.location.lon = msg.tweet.geo.coordinates[1];
msg.location.icon = "twitter";
}
} else if(msg.tweet.coordinates) { // otherwise attempt go get it from coordinates
if(msg.tweet.coordinates.coordinates && msg.tweet.coordinates.coordinates.length === 2) {
if (!msg.location) { msg.location = {}; }
// WARNING! coordinates[1] is lat, coordinates[0] is lon!!!
msg.location.lat = msg.tweet.coordinates.coordinates[1];
msg.location.lon = msg.tweet.coordinates.coordinates[0];
msg.location.icon = "twitter";
}
} // if none of these found then just do nothing
} // if no msg.tweet then just do nothing
}
function TwitterInNode(n) {
RED.nodes.createNode(this,n);
this.active = true;
@@ -51,7 +77,6 @@ module.exports = function(RED) {
access_token_secret: credentials.access_token_secret
});
//setInterval(function() {
// twit.get("/application/rate_limit_status.json",null,function(err,cb) {
// console.log("direct_messages:",cb["resources"]["direct_messages"]);
@@ -91,10 +116,13 @@ module.exports = function(RED) {
if (cb) {
for (var t=cb.length-1;t>=0;t-=1) {
var tweet = cb[t];
var where = tweet.user.location||"";
var where = tweet.user.location;
var la = tweet.lang || tweet.user.lang;
//console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, lang:la, tweet:tweet };
if (where) {
msg.location = {place:where};
addLocationToTweet(msg);
}
node.send(msg);
if (t == 0) {
node.since_ids[u] = tweet.id_str;
@@ -134,7 +162,13 @@ module.exports = function(RED) {
if (cb) {
for (var t=cb.length-1;t>=0;t-=1) {
var tweet = cb[t];
var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, tweet:tweet };
var where = tweet.sender.location;
var la = tweet.lang || tweet.sender.lang;
var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, lang:la, tweet:tweet };
if (where) {
msg.location = {place:where};
addLocationToTweet(msg);
}
node.send(msg);
if (t == 0) {
node.since_id = tweet.id_str;
@@ -154,7 +188,7 @@ module.exports = function(RED) {
if (this.user === "true") { thing = 'user'; }
var st = { track: [node.tags] };
var bits = node.tags.split(",");
if ((bits.length > 0) && (bits.length % 4 == 0)) {
if (bits.length == 4) {
if ((Number(bits[0]) < Number(bits[2])) && (Number(bits[1]) < Number(bits[3]))) {
st = { locations: node.tags };
}
@@ -172,12 +206,14 @@ module.exports = function(RED) {
//twit.stream('statuses/filter', { track: [node.tags] }, function(stream) {
node.stream = stream;
stream.on('data', function(tweet) {
//console.log(tweet.user);
if (tweet.user !== undefined) {
var where = tweet.user.location||"";
var where = tweet.user.location;
var la = tweet.lang || tweet.user.lang;
//console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, lang:la, tweet:tweet };
if (where) {
msg.location = {place:where};
addLocationToTweet(msg);
}
node.send(msg);
}
});
@@ -264,8 +300,8 @@ module.exports = function(RED) {
node.status({fill:"red",shape:"ring",text:"failed"});
} else {
var response = JSON.parse(body);
if (body.errors) {
var errorList = body.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
if (response.errors) {
var errorList = response.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
node.error("tweet failed: "+errorList);
node.status({fill:"red",shape:"ring",text:"failed"});
} else {

View File

@@ -31,7 +31,7 @@
</script>
<script type="text/x-red" data-help-name="feedparse">
<p>Monitors an RSS/atom feed for new entries.</p>
<p>Monitors an RSS/atom feed for new entries.</p>
</script>
<script type="text/javascript">
@@ -53,5 +53,4 @@
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013,2014 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,50 +22,58 @@ module.exports = function(RED) {
function FeedParseNode(n) {
RED.nodes.createNode(this,n);
this.url = n.url;
this.interval = (parseInt(n.interval)||15)*60000;
this.interval = (parseInt(n.interval)||15) * 60000;
var node = this;
this.interval_id = null;
this.seen = {};
if (this.url !== "") {
var getFeed = function() {
request(node.url,function(err) {
if (err) node.error(err);
})
.pipe(new FeedParser({feedurl:node.url}))
.on('error', function(error) {
node.error(error);
})
.on('meta', function (meta) {})
.on('readable', function () {
var stream = this, article;
while (article = stream.read()) {
if (!(article.guid in node.seen) || ( node.seen[article.guid] != 0 && node.seen[article.guid] != article.date.getTime())) {
node.seen[article.guid] = article.date?article.date.getTime():0;
var msg = {
topic:article.origlink||article.link,
payload: article.description,
article: article
};
node.send(msg);
}
}
})
.on('end', function () {
});
};
this.interval_id = setInterval(getFeed,node.interval);
getFeed();
var req = request(node.url, {timeout: 10000, pool: false});
//req.setMaxListeners(50);
//req.setHeader('user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36');
req.setHeader('accept', 'text/html,application/xhtml+xml');
var feedparser = new FeedParser();
req.on('error', function(err) { node.error(err); });
req.on('response', function(res) {
if (res.statusCode != 200) { node.warn('error - Bad status code'); }
else { res.pipe(feedparser); }
});
feedparser.on('error', function(error) { node.error(error); });
feedparser.on('readable', function () {
var stream = this, article;
while (article = stream.read()) {
if (!(article.guid in node.seen) || ( node.seen[article.guid] !== 0 && node.seen[article.guid] != article.date.getTime())) {
node.seen[article.guid] = article.date?article.date.getTime():0;
var msg = {
topic: article.origlink || article.link,
payload: article.description,
article: article
};
node.send(msg);
}
}
});
feedparser.on('meta', function (meta) {});
feedparser.on('end', function () {});
};
this.interval_id = setInterval(function() { getFeed(); }, node.interval);
getFeed();
} else {
this.error("Invalid url");
}
this.on("close", function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
}
});
}
RED.nodes.registerType("feedparse",FeedParseNode);
FeedParseNode.prototype.close = function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
}
}
}

View File

@@ -70,9 +70,13 @@
<script type="text/x-red" data-help-name="e-mail">
<p>Sends the <b>msg.payload</b> as an email, with a subject of <b>msg.topic</b>.</p>
<!-- <p>It sends the message to the configured recipient <i>only</i>.</p> -->
<p>You may dynamically overide the default recipient by setting a <b>msg.to</b> property.</p>
<!-- <p><b>msg.topic</b> is used to set the subject of the email, and <b>msg.payload</b> is the body text.</p> -->
<p>The default message recipient can be configured in the node, if it is left
blank it should be set using the <b>msg.to</b> property of the incoming message.</p>
<p>The payload can be html format.</p>
<p>If the payload is a binary buffer then it will be converted to an attachment.
The filename should be set using <b>msg.filename</b>. Optionally <b>msg.description</b> can be added for the body text.</p>
<p>Alternatively you may provide <b>msg.attachments</b> which should contain an array of one or
more attachments in <a href="https://www.npmjs.com/package/nodemailer#attachments" target="_new">nodemailer</a> format.</p>
</script>
<script type="text/javascript">

View File

@@ -71,13 +71,27 @@ module.exports = function(RED) {
this.on("input", function(msg) {
if (smtpTransport) {
node.status({fill:"blue",shape:"dot",text:"sending"});
var payload = RED.util.ensureString(msg.payload);
smtpTransport.sendMail({
from: node.userid, // sender address
to: msg.to || node.name, // comma separated list of addressees
subject: msg.topic, // subject line
text: payload // plaintext body
}, function(error, info) {
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"];
}
// 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"});
@@ -243,4 +257,4 @@ module.exports = function(RED) {
global: { type:"boolean"}
}
});
}
};

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013,2014 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013,2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,36 +44,66 @@ module.exports = function(RED) {
this.channel = n.channel || this.serverConfig.channel;
var node = this;
if (node.serverConfig.ircclient === null) {
node.log("Connecting to "+node.serverConfig.server);
node.log("CONNECT: "+node.serverConfig.server);
node.status({fill:"grey",shape:"dot",text:"connecting"});
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:false,retryDelay:20000});
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:true,autoRejoin:false,floodProtection:true,retryDelay:20000});
node.serverConfig.ircclient.setMaxListeners(0);
node.serverConfig.ircclient.addListener('error', function(message) {
node.log(JSON.stringify(message));
if (RED.settings.verbose) { node.log("ERR: "+JSON.stringify(message)); }
});
node.serverConfig.ircclient.addListener('netError', function(message) {
node.log(JSON.stringify("NET "+message));
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("NET: "+JSON.stringify(message)); }
node.status({fill:"red",shape:"ring",text:"net error"});
});
node.serverConfig.ircclient.addListener('connect', function() {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("CONNECTED "); }
});
node.serverConfig.ircclient.addListener('registered', function(message) {
node.serverConfig.lastseen = Date.now();
node.log(node.serverConfig.ircclient.nick+" ONLINE: "+message.server);
node.status({fill:"yellow",shape:"dot",text:"connected"});
node.serverConfig.ircclient.join( node.channel, function(data) {
node.log(data+" JOINED: "+node.channel);
node.status({fill:"green",shape:"dot",text:"joined"});
});
});
node.serverConfig.ircclient.addListener('ping', function(server) {
node.serverConfig.lastseen = Date.now();
//node.log("PING "+JSON.stringify(server));
if (RED.settings.verbose) { node.log("PING from "+JSON.stringify(server)); }
node.status({fill:"green",shape:"dot",text:"ok"});
});
node.serverConfig.ircclient.addListener('quit', function(nick, reason, channels, message) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("QUIT: "+nick+" "+reason+" "+channels+" "+JSON.stringify(message)); }
node.status({fill:"grey",shape:"ring",text:"quit"});
//node.serverConfig.ircclient.disconnect( function() {
// node.serverConfig.ircclient.connect();
//});
//if (RED.settings.verbose) { node.log("restart"); } // then retry
});
node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive
//console.log("RAW:"+JSON.stringify(message));
if (message.commandType === "reply") {
//console.log("RAW:"+JSON.stringify(message));
node.serverConfig.lastseen = Date.now();
}
});
node.recon = setInterval( function() {
//console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // if more than 5 mins since last seen
node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
if ((Date.now()-node.serverConfig.lastseen) > 240000) { // if more than 4 mins since last seen
node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}
if ((Date.now()-node.serverConfig.lastseen) > 400000) { // If more than 6.5 mins
node.serverConfig.ircclient.disconnect();
node.serverConfig.ircclient.connect();
node.log("reconnect"); // then retry
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // If more than 5 mins
//node.serverConfig.ircclient.disconnect();
//node.serverConfig.ircclient.connect();
node.status({fill:"grey",shape:"ring",text:"no connection"});
if (RED.settings.verbose) { node.log("CONNECTION LOST ?"); }
}
node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
//node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}, 60000); // check every 1 min
//node.serverConfig.ircclient.connect();
}
else { node.status({text:""}); }
node.ircclient = node.serverConfig.ircclient;
@@ -148,64 +178,74 @@ module.exports = function(RED) {
this.channel = n.channel || this.serverConfig.channel;
var node = this;
if (node.serverConfig.ircclient === null) {
node.log("connecting to "+node.serverConfig.server);
node.log("CONNECT: "+node.serverConfig.server);
node.status({fill:"grey",shape:"dot",text:"connecting"});
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:false,retryDelay:20000});
node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:true,autoRejoin:false,floodProtection:true,retryDelay:20000});
node.serverConfig.ircclient.setMaxListeners(0);
node.serverConfig.ircclient.addListener('error', function(message) {
node.log(JSON.stringify(message));
if (RED.settings.verbose) { node.log("ERR: "+JSON.stringify(message)); }
});
node.serverConfig.ircclient.addListener('netError', function(message) {
node.log(JSON.stringify("NET "+message));
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("NET: "+JSON.stringify(message)); }
node.status({fill:"red",shape:"ring",text:"net error"});
});
node.serverConfig.ircclient.addListener('connect', function() {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("CONNECTED "); }
});
node.serverConfig.ircclient.addListener('registered', function(message) {
node.serverConfig.lastseen = Date.now();
node.log(node.serverConfig.ircclient.nick+" ONLINE: "+message.server);
node.status({fill:"yellow",shape:"dot",text:"connected"});
node.serverConfig.ircclient.join( node.channel, function(data) {
node.log(data+" JOINED: "+node.channel);
node.status({fill:"green",shape:"dot",text:"joined"});
});
});
node.serverConfig.ircclient.addListener('ping', function(server) {
node.serverConfig.lastseen = Date.now();
//node.log("PING "+JSON.stringify(server));
if (RED.settings.verbose) { node.log("PING from "+JSON.stringify(server)); }
node.status({fill:"green",shape:"dot",text:"ok"});
});
node.serverConfig.ircclient.addListener('quit', function(nick, reason, channels, message) {
node.serverConfig.lastseen = Date.now();
if (RED.settings.verbose) { node.log("QUIT: "+nick+" "+reason+" "+channels+" "+JSON.stringify(message)); }
node.status({fill:"grey",shape:"ring",text:"quit"});
//node.serverConfig.ircclient.disconnect( function() {
// node.serverConfig.ircclient.connect();
//});
//if (RED.settings.verbose) { node.log("restart"); } // then retry
});
node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive
if (message.commandType === "reply") { node.serverConfig.lastseen = Date.now(); }
//console.log("RAW:"+JSON.stringify(message));
if (message.commandType === "reply") {
//console.log("RAW:"+JSON.stringify(message));
node.serverConfig.lastseen = Date.now();
}
});
node.recon = setInterval( function() {
//console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000);
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // if more than 5 mins since last seen
node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
if ((Date.now()-node.serverConfig.lastseen) > 240000) { // if more than 4 mins since last seen
node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}
if ((Date.now()-node.serverConfig.lastseen) > 400000) { // If more than 6.5 mins
node.serverConfig.ircclient.disconnect();
node.serverConfig.ircclient.connect();
node.log("reconnect"); // then retry
if ((Date.now()-node.serverConfig.lastseen) > 300000) { // If more than 5 mins
//node.serverConfig.ircclient.disconnect();
//node.serverConfig.ircclient.connect();
node.status({fill:"grey",shape:"ring",text:"no connection"});
if (RED.settings.verbose) { node.log("CONNECTION LOST ?"); }
}
node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link
//node.serverConfig.ircclient.send.apply(node.serverConfig.ircclient,["TIME"]); // request time to check link
}, 60000); // check every 1 min
node.serverConfig.ircclient.connect();
//node.serverConfig.ircclient.connect();
}
else { node.status({text:""}); }
node.ircclient = node.serverConfig.ircclient;
node.ircclient.addListener('registered', function(message) {
node.log(node.ircclient.nick+" ONLINE");
node.status({fill:"yellow",shape:"dot",text:"connected"});
node.ircclient.join( node.channel, function(data) {
//node.log(data+" JOINED "+node.channel);
node.status({fill:"green",shape:"dot",text:"joined"});
});
});
node.on("input", function(msg) {
if (Object.prototype.toString.call( msg.raw ) === '[object Array]') {
node.log("RAW command:"+msg.raw);
if (RED.settings.verbose) { node.log("RAW command:"+msg.raw); }
node.ircclient.send.apply(node.ircclient,msg.raw);
//var m = msg.raw;
//for (var i = 0; i < 10; i++) {
//if (typeof m[i] !== "string") { m[i] = ""; }
//m[i] = m[i].replace(/"/g, "");
//}
//node.log("RAW command:"+m);
//node.ircclient.send(m[0],m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[9]);
}
else {
if (msg._topic) { delete msg._topic; }

View File

@@ -20,14 +20,17 @@
<input type="text" id="node-input-filename" placeholder="Filename">
</div>
<div class="form-row">
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> Action</label>
<select type="text" id="node-input-overwriteFile" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">append to file</option>
<option value="true">overwrite file</option>
<option value="delete">delete file</option>
</select>
</div>
<div class="form-row" id="node-appline">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-appendNewline" style="width: 70%;">Append newline ?</label>
</div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-overwriteFile" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-overwriteFile" style="width: 70%;">Overwrite complete file ?</label>
<label for="node-input-appendNewline" style="width: 70%;">Add newline (\n) to each payload ?</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
@@ -37,10 +40,10 @@
<script type="text/x-red" data-help-name="file">
<p>Writes <b>msg.payload</b> to the file specified, e.g. to create a log.</p>
<p>The filename can be overridden by the <b>msg.filename</b> property of the incoming message.</p>
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
<p>A newline is added to every message. But this can be turned off if required, for example, to allow binary files to be written.</p>
<p>The default behaviour is to append to the file. This can be changed to overwrite the file each time, for example if you want to output a "static" web page or report.</p>
<p>If a <b>msg.delete</b> property exists then the file will be deleted instead.</p>
<p>This node can also be configured to delete a file if required. <i>Note:</i> Using msg.delete is now deprecated.</p>
</script>
<script type="text/x-red" data-template-name="file in">
@@ -63,7 +66,7 @@
<script type="text/x-red" data-help-name="file in">
<p>Reads the specified file and sends the content as <b>msg.payload</b>, and the filename as <b>msg.filename</b>.</p>
<p>The filename can be overridden by the <b>msg.filename</b> property of the incoming message.</p>
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
</script>
<script type="text/javascript">
@@ -73,7 +76,7 @@
name: {value:""},
filename: {value:""},
appendNewline: {value:true},
overwriteFile: {value:false}
overwriteFile: {value:"false"}
},
color:"BurlyWood",
inputs:1,
@@ -81,10 +84,17 @@
icon: "file.png",
align: "right",
label: function() {
return this.name||this.filename;
if (this.overwriteFile === "delete") { return this.name||"delete "+this.filename; }
else { return this.name||this.filename; }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-overwriteFile").on("change",function() {
if (this.value === "delete") { $("#node-appline").hide(); }
else { $("#node-appline").show(); }
});
}
});

View File

@@ -22,38 +22,53 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
this.filename = n.filename || "";
this.appendNewline = n.appendNewline;
this.overwriteFile = n.overwriteFile;
this.overwriteFile = n.overwriteFile.toString();
var node = this;
this.on("input",function(msg) {
var filename = msg.filename || this.filename;
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;
}
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); }
//console.log('Deleted file",filename);
});
} else if (typeof msg.payload != "undefined") {
var data = msg.payload;
if (typeof data === "object") {
if (!Buffer.isBuffer(data)) {
data = JSON.stringify(data);
}
if ((typeof data === "object")&&(!Buffer.isBuffer(data))) {
data = JSON.stringify(data);
}
if (typeof data === "boolean") { data = data.toString(); }
if ((this.appendNewline)&&(!Buffer.isBuffer(data))) { data += "\n"; }
if (this.overwriteFile) {
if (this.overwriteFile === "true") {
// 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); }
//console.log('Message written to file',filename);
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); }
else if (RED.settings.verbose) { node.log("deleted file: "+filename); }
});
}
else {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
// 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); }
//console.log('Message appended to file',filename);
else if (RED.settings.verbose) { node.log('appended to file: '+filename); }
});
}
}
@@ -61,6 +76,7 @@ module.exports = function(RED) {
}
RED.nodes.registerType("file",FileNode);
function FileInNode(n) {
RED.nodes.createNode(this,n);
@@ -72,17 +88,27 @@ module.exports = function(RED) {
options['encoding'] = this.format;
}
this.on("input",function(msg) {
var filename = msg.filename || this.filename;
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;
}
if (filename === "") {
node.warn('No filename specified');
} else {
msg.filename = filename;
fs.readFile(filename,options,function(err,data) {
if (err) {
node.warn(err);
msg.error = err;
delete msg.payload;
} else {
msg.filename = filename;
msg.payload = data;
delete msg.error;
}
node.send(msg);
});

View File

@@ -40,7 +40,7 @@
</div>
<div class="form-tips">
If key is blank, the topic will be used as the key.<br>
If type is hash, payload should be field=value.
If type is hash, payload should be an object or field=value string.
</div>
</script>

View File

@@ -84,11 +84,15 @@ module.exports = function(RED) {
if (this.structtype == "string") {
this.client.set(k,RED.util.ensureString(msg.payload));
} else if (this.structtype == "hash") {
var r = hashFieldRE.exec(msg.payload);
if (r) {
this.client.hset(k,r[1],r[2]);
if (typeof msg.payload == "object") {
this.client.hmset(k,msg.payload);
} else {
this.warn("Invalid payload for redis hash");
var r = hashFieldRE.exec(msg.payload);
if (r) {
this.client.hset(k,r[1],r[2]);
} else {
this.warn("Invalid payload for redis hash");
}
}
} else if (this.structtype == "set") {
this.client.sadd(k,msg.payload);

View File

@@ -197,10 +197,10 @@
<script type="text/x-red" data-help-name="mongodb in">
<p>Calls a MongoDB collection method based on the selected operator.</p>
<p>Find queries a collection using the <b>msg.payload</b> as the query statement as per the .find() function. Optionally, you may also (via a function) set a <b>msg.projection</b> object to constrain the returned fields, a <b>msg.sort</b> object and a <b>msg.limit</b> object.</p>
<p>Find queries a collection using the <b>msg.payload</b> as the query statement as per the .find() function. Optionally, you may also (via a function) set a <b>msg.projection</b> object to constrain the returned fields, a <b>msg.sort</b> object, a <b>msg.limit</b> number and a <b>msg.skip</b> number.</p>
<p>Count returns a count of the number of documents in a collection or matching a query using the <b>msg.payload</b> as the query statement.</p>
<p>Aggregate provides access to the aggregation pipeline using the <b>msg.payload</b> as the pipeline array.</p>
<p>You can override the collection the method is performed on by setting <b>msg.collection</b> to the desired collection name.</p>
<p>You can either set the collection method in the node config or on <b>msg.collection</b>. Setting it in the node will override <b>msg.collection</b>.</p>
<p>See the <a href="http://docs.mongodb.org/manual/reference/method/db.collection.find/" target="new"><i>MongoDB collection methods docs</i></a> for examples.</p>
<p>The result is returned in <b>msg.payload</b>.</p>
</script>

View File

@@ -48,8 +48,8 @@ module.exports = function(RED) {
}
return selector;
}
function MongoOutNode(n) {
RED.nodes.createNode(this,n);
this.collection = n.collection;
@@ -72,7 +72,7 @@ module.exports = function(RED) {
coll = db.collection(node.collection);
}
node.on("input",function(msg) {
if (!coll) {
if (!node.collection) {
if (msg.collection) {
coll = db.collection(msg.collection);
} else {
@@ -173,7 +173,7 @@ module.exports = function(RED) {
coll = db.collection(node.collection);
}
node.on("input", function(msg) {
if (!coll) {
if (!node.collection) {
if (msg.collection) {
coll = db.collection(msg.collection);
} else {
@@ -184,7 +184,7 @@ 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).toArray(function(err, items) {
coll.find(selector,msg.projection).sort(msg.sort).limit(msg.limit).skip(msg.skip).toArray(function(err, items) {
if (err) {
node.error(err);
} else {
@@ -192,6 +192,7 @@ module.exports = function(RED) {
delete msg.projection;
delete msg.sort;
delete msg.limit;
delete msg.skip;
node.send(msg);
}
});
@@ -206,7 +207,7 @@ module.exports = function(RED) {
}
});
} else if (node.operation === "aggregate") {
msg.payload = (msg.payload instanceof Array) ? msg.payload : [];
msg.payload = (Array.isArray(msg.payload)) ? msg.payload : [];
coll.aggregate(msg.payload, function(err, result) {
if (err) {
node.error(err);

View File

@@ -1,6 +1,6 @@
{
"name" : "node-red",
"version" : "0.9.1",
"version" : "0.10.2",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache",
@@ -13,7 +13,7 @@
"start": "node red.js",
"test": "./node_modules/.bin/grunt"
},
"contributors": [
"contributors": [
{"name": "Nick O'Leary"},
{"name": "Dave Conway-Jones"}
],
@@ -22,39 +22,48 @@
],
"dependencies": {
"express": "3.17.2",
"when": "3.4.6",
"when": "3.7.2",
"bcryptjs": "2.1.0",
"nopt": "3.0.1",
"mqtt": "0.3.x",
"ws": "0.4.32",
"fs-extra": "0.11.1",
"clone": "0.1.18",
"mustache": "0.8.2",
"cron":"1.0.4",
"raw-body":"1.3.0",
"ws": "0.7.1",
"fs-extra": "0.16.3",
"clone": "0.2.0",
"mustache": "1.0.0",
"cron":"1.0.6",
"raw-body":"1.3.2",
"twitter-ng":"0.6.2",
"oauth":"0.9.12",
"xml2js":"0.4.4",
"sentiment":"0.2.3",
"irc":"0.3.7",
"irc":"0.3.9",
"follow-redirects":"0.0.3",
"cors":"2.4.2",
"cors":"2.5.3",
"mkdirp":"0.5.0",
"cheerio":"0.17.0",
"uglify-js":"2.4.15",
"cheerio":"0.18.0",
"uglify-js":"2.4.16",
"nodemailer":"1.3.0",
"imap":"0.8.13",
"imap":"0.8.14",
"request":"2.42.0",
"colors":"0.6.2"
"on-headers":"1.0.0",
"is-utf8":"0.2.0",
"serialport":"1.4.10",
"feedparser":"0.19.2",
"fs.notify":"0.0.4",
"passport":"0.2.1",
"passport-http-bearer":"1.0.1",
"passport-oauth2-client-password":"0.1.2",
"oauth2orize":"1.0.1"
},
"devDependencies": {
"grunt": "0.4.5",
"grunt-cli": "0.1.13",
"grunt-simple-mocha": "0.4.0",
"grunt-contrib-jshint": "0.10.0",
"mocha": "1.21.4",
"should": "4.0.4",
"sinon": "1.10.3",
"supertest": "^0.13.0"
"grunt-contrib-jshint": "0.11.0",
"mocha": "2.1.0",
"should": "4.6.5",
"sinon": "1.12.2",
"supertest": "0.15.0"
},
"engines": {
"node": ">=0.8"

File diff suppressed because one or more lines are too long

BIN
public/font-awesome/fonts/fontawesome-webfont.eot Executable file → Normal file

Binary file not shown.

61
public/font-awesome/fonts/fontawesome-webfont.svg Executable file → Normal file
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<metadata></metadata>
<defs>
<font id="fontawesomeregular" horiz-adv-x="1536" >
@@ -147,14 +147,14 @@
<glyph unicode="&#xf077;" horiz-adv-x="1792" d="M1683 205l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5t19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5z" />
<glyph unicode="&#xf078;" horiz-adv-x="1792" d="M1683 728l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5t19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5z" />
<glyph unicode="&#xf079;" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
<glyph unicode="&#xf07a;" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf07a;" horiz-adv-x="1664" d="M640 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1536 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1664 1088v-512q0 -24 -16.5 -42.5t-40.5 -21.5l-1044 -122q13 -60 13 -70q0 -16 -24 -64h920q26 0 45 -19t19 -45 t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 11 8 31.5t16 36t21.5 40t15.5 29.5l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t19.5 -15.5t13 -24.5t8 -26t5.5 -29.5t4.5 -26h1201q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf07b;" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
<glyph unicode="&#xf07c;" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
<glyph unicode="&#xf07d;" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
<glyph unicode="&#xf07e;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
<glyph unicode="&#xf080;" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" />
<glyph unicode="&#xf081;" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf082;" d="M1536 160q0 -119 -84.5 -203.5t-203.5 -84.5h-192v608h203l30 224h-233v143q0 54 28 83t96 29l132 1v207q-96 9 -180 9q-136 0 -218 -80.5t-82 -225.5v-166h-224v-224h224v-608h-544q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5v-960z" />
<glyph unicode="&#xf082;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-188v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-532q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960z" />
<glyph unicode="&#xf083;" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
<glyph unicode="&#xf084;" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
<glyph unicode="&#xf085;" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
@@ -275,7 +275,7 @@
<glyph unicode="&#xf10c;" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf10d;" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
<glyph unicode="&#xf10e;" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
<glyph unicode="&#xf110;" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" />
<glyph unicode="&#xf110;" horiz-adv-x="1792" d="M526 142q0 -53 -37.5 -90.5t-90.5 -37.5q-52 0 -90 38t-38 90q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1522 142q0 -52 -38 -90t-90 -38q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM558 1138q0 -66 -47 -113t-113 -47t-113 47t-47 113t47 113t113 47t113 -47t47 -113z M1728 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1088 1344q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1618 1138q0 -93 -66 -158.5t-158 -65.5q-93 0 -158.5 65.5t-65.5 158.5 q0 92 65.5 158t158.5 66q92 0 158 -66t66 -158z" />
<glyph unicode="&#xf111;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf112;" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
<glyph unicode="&#xf113;" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
@@ -411,7 +411,7 @@
<glyph unicode="&#xf19d;" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" />
<glyph unicode="&#xf19e;" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" />
<glyph unicode="&#xf1a0;" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" />
<glyph unicode="&#xf1a1;" horiz-adv-x="1984" d="M831 572q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41t96.5 -41t40.5 -98zM1292 711q56 0 96.5 -41t40.5 -98q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41zM1984 722q0 -62 -31 -114t-83 -82q5 -33 5 -61 q0 -121 -68.5 -230.5t-197.5 -193.5q-125 -82 -285.5 -125.5t-335.5 -43.5q-176 0 -336.5 43.5t-284.5 125.5q-129 84 -197.5 193t-68.5 231q0 29 5 66q-48 31 -77 81.5t-29 109.5q0 94 66 160t160 66q83 0 148 -55q248 158 592 164l134 423q4 14 17.5 21.5t28.5 4.5 l347 -82q22 50 68.5 81t102.5 31q77 0 131.5 -54.5t54.5 -131.5t-54.5 -132t-131.5 -55q-76 0 -130.5 54t-55.5 131l-315 74l-116 -366q327 -14 560 -166q64 58 151 58q94 0 160 -66t66 -160zM1664 1459q-45 0 -77 -32t-32 -77t32 -77t77 -32t77 32t32 77t-32 77t-77 32z M77 722q0 -67 51 -111q49 131 180 235q-36 25 -82 25q-62 0 -105.5 -43.5t-43.5 -105.5zM1567 105q112 73 171.5 166t59.5 194t-59.5 193.5t-171.5 165.5q-116 75 -265.5 115.5t-313.5 40.5t-313.5 -40.5t-265.5 -115.5q-112 -73 -171.5 -165.5t-59.5 -193.5t59.5 -194 t171.5 -166q116 -75 265.5 -115.5t313.5 -40.5t313.5 40.5t265.5 115.5zM1850 605q57 46 57 117q0 62 -43.5 105.5t-105.5 43.5q-49 0 -86 -28q131 -105 178 -238zM1258 237q11 11 27 11t27 -11t11 -27.5t-11 -27.5q-99 -99 -319 -99h-2q-220 0 -319 99q-11 11 -11 27.5 t11 27.5t27 11t27 -11q77 -77 265 -77h2q188 0 265 77z" />
<glyph unicode="&#xf1a1;" horiz-adv-x="2304" d="M1509 107q0 -14 -12 -29q-52 -59 -147.5 -83t-196.5 -24q-252 0 -346 107q-12 15 -12 29q0 17 12 29.5t29 12.5q15 0 30 -12q58 -49 125.5 -66t159.5 -17t160 17t127 66q15 12 30 12q17 0 29 -12.5t12 -29.5zM978 498q0 -61 -43 -104t-104 -43q-60 0 -104.5 43.5 t-44.5 103.5q0 61 44 105t105 44t104 -44t43 -105zM1622 498q0 -61 -43 -104t-104 -43q-60 0 -104.5 43.5t-44.5 103.5q0 61 44 105t105 44t104 -44t43 -105zM415 793q-39 27 -88 27q-66 0 -113 -47t-47 -113q0 -72 54 -121q53 141 194 254zM2020 382q0 222 -249 387 q-128 85 -291.5 126.5t-331.5 41.5t-331.5 -41.5t-292.5 -126.5q-249 -165 -249 -387t249 -387q129 -85 292.5 -126.5t331.5 -41.5t331.5 41.5t291.5 126.5q249 165 249 387zM2137 660q0 66 -47 113t-113 47q-50 0 -93 -30q140 -114 192 -256q61 48 61 126zM1993 1335 q0 49 -34.5 83.5t-82.5 34.5q-49 0 -83.5 -34.5t-34.5 -83.5q0 -48 34.5 -82.5t83.5 -34.5q48 0 82.5 34.5t34.5 82.5zM2220 660q0 -65 -33 -122t-89 -90q5 -35 5 -66q0 -139 -79 -255.5t-208 -201.5q-140 -92 -313.5 -136.5t-354.5 -44.5t-355 44.5t-314 136.5 q-129 85 -208 201.5t-79 255.5q0 36 6 71q-53 33 -83.5 88.5t-30.5 118.5q0 100 71 171.5t172 71.5q91 0 159 -60q265 170 638 177l144 456q10 29 40 29q24 0 384 -90q24 55 74 88t110 33q82 0 141 -59t59 -142t-59 -141.5t-141 -58.5q-83 0 -141.5 58.5t-59.5 140.5 l-339 80l-125 -395q349 -15 603 -179q71 63 163 63q101 0 172 -71.5t71 -171.5z" />
<glyph unicode="&#xf1a2;" d="M950 393q7 7 17.5 7t17.5 -7t7 -18t-7 -18q-65 -64 -208 -64h-1h-1q-143 0 -207 64q-8 7 -8 18t8 18q7 7 17.5 7t17.5 -7q49 -51 172 -51h1h1q122 0 173 51zM671 613q0 -37 -26 -64t-63 -27t-63 27t-26 64t26 63t63 26t63 -26t26 -63zM1214 1049q-29 0 -50 21t-21 50 q0 30 21 51t50 21q30 0 51 -21t21 -51q0 -29 -21 -50t-51 -21zM1216 1408q132 0 226 -94t94 -227v-894q0 -133 -94 -227t-226 -94h-896q-132 0 -226 94t-94 227v894q0 133 94 227t226 94h896zM1321 596q35 14 57 45.5t22 70.5q0 51 -36 87.5t-87 36.5q-60 0 -98 -48 q-151 107 -375 115l83 265l206 -49q1 -50 36.5 -85t84.5 -35q50 0 86 35.5t36 85.5t-36 86t-86 36q-36 0 -66 -20.5t-45 -53.5l-227 54q-9 2 -17.5 -2.5t-11.5 -14.5l-95 -302q-224 -4 -381 -113q-36 43 -93 43q-51 0 -87 -36.5t-36 -87.5q0 -37 19.5 -67.5t52.5 -45.5 q-7 -25 -7 -54q0 -98 74 -181.5t201.5 -132t278.5 -48.5q150 0 277.5 48.5t201.5 132t74 181.5q0 27 -6 54zM971 702q37 0 63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64t26 63t63 26z" />
<glyph unicode="&#xf1a3;" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf1a4;" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" />
@@ -438,7 +438,7 @@
<glyph unicode="&#xf1ba;" horiz-adv-x="2048" d="M1824 640q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-96v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-1024v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5 t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h128q98 0 179 -63.5t104 -157.5l105 -419h28zM320 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM516 640h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5z M1728 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47z" />
<glyph unicode="&#xf1bb;" d="M1504 64q0 -26 -19 -45t-45 -19h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45t19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384 q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45z" />
<glyph unicode="&#xf1bc;" d="M1127 326q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5zM1223 541q0 40 -35 61q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64 q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5zM1331 789q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37 q159 0 309.5 -34t253.5 -95q21 -12 40 -12q29 0 50.5 20.5t21.5 51.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf1bd;" d="M1397 1408q58 0 98.5 -40.5t40.5 -98.5v-1258q0 -58 -40.5 -98.5t-98.5 -40.5h-1258q-58 0 -98.5 40.5t-40.5 98.5v1258q0 58 40.5 98.5t98.5 40.5h1258zM1465 11v1258q0 28 -20 48t-48 20h-1258q-28 0 -48 -20t-20 -48v-1258q0 -28 20 -48t48 -20h1258q28 0 48 20t20 48 zM694 749l188 -387l533 145v-496q0 -7 -5.5 -12.5t-12.5 -5.5h-1258q-7 0 -12.5 5.5t-5.5 12.5v141l711 195l-212 439q4 1 12 2.5t12 1.5q170 32 303.5 21.5t221 -46t143.5 -94.5q27 -28 -25 -42q-64 -16 -256 -62l-97 198q-111 7 -240 -16zM1397 1287q7 0 12.5 -5.5 t5.5 -12.5v-428q-85 30 -188 52q-294 64 -645 12l-18 -3l-65 134h-233l85 -190q-132 -51 -230 -137v560q0 7 5.5 12.5t12.5 5.5h1258zM286 387q-14 -3 -26 4.5t-14 21.5q-24 203 166 305l129 -270z" />
<glyph unicode="&#xf1bd;" horiz-adv-x="1024" d="M1024 1233l-303 -582l24 -31h279v-415h-507l-44 -30l-142 -273l-30 -30h-301v303l303 583l-24 30h-279v415h507l44 30l142 273l30 30h301v-303z" />
<glyph unicode="&#xf1be;" horiz-adv-x="2304" d="M784 164l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17l-14 -523l14 -241q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23zM1080 193l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6l-10 -579q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11 q11 0 20 9q9 7 9 20zM35 533l20 -128l-20 -126q-2 -9 -9 -9t-9 9l-17 126l17 128q2 9 9 9t9 -9zM121 612l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10l-23 202l23 207q0 9 9 9q8 0 10 -9zM401 159zM213 650l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11l-21 237l21 245 q2 12 12 12q11 0 11 -12zM307 657l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13l-21 244l21 252q0 13 13 13q12 0 14 -13zM401 639l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5l-20 246l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15zM784 164zM495 785 l21 -380l-21 -246q0 -7 -5 -12.5t-12 -5.5q-16 0 -18 18l-18 246l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5zM589 871l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19l-16 244l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5zM687 911l18 -506l-18 -242 q-2 -21 -22 -21q-19 0 -21 21l-16 242l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5zM1079 169v0v0zM881 915l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18l-14 239l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18zM980 896l14 -492l-14 -236q0 -11 -8 -19 t-19 -8t-19 8t-9 19l-12 236l12 492q1 12 9 20t19 8t18.5 -8t8.5 -20zM1192 404l-14 -231v0q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114l-6 117l12 636v3q2 15 12 24q9 7 20 7q8 0 15 -5q14 -8 16 -26zM2304 423q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22v899 q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201z" />
<glyph unicode="&#xf1c0;" d="M768 768q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 0q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127 t443 -43zM768 384q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 1536q208 0 385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128v128q0 69 103 128t280 93.5t385 34.5z" />
<glyph unicode="&#xf1c1;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M894 465q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4q52 85 107 197 q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM716 974q-15 -42 -2 -132q1 7 7 44q0 3 7 43q1 4 4 8 q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83zM1238 329q-24 24 -140 24q76 -28 124 -28q14 0 18 1q0 1 -2 3z" />
@@ -513,8 +513,53 @@
<glyph unicode="&#xf20a;" horiz-adv-x="2048" d="M785 528h207q-14 -158 -98.5 -248.5t-214.5 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-203q-5 64 -35.5 99t-81.5 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t40 -51.5t66 -18q95 0 109 139zM1497 528h206 q-14 -158 -98 -248.5t-214 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-204q-4 64 -35 99t-81 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t39.5 -51.5t65.5 -18q49 0 76.5 38t33.5 101zM1856 647q0 207 -15.5 307 t-60.5 161q-6 8 -13.5 14t-21.5 15t-16 11q-86 63 -697 63q-625 0 -710 -63q-5 -4 -17.5 -11.5t-21 -14t-14.5 -14.5q-45 -60 -60 -159.5t-15 -308.5q0 -208 15 -307.5t60 -160.5q6 -8 15 -15t20.5 -14t17.5 -12q44 -33 239.5 -49t470.5 -16q610 0 697 65q5 4 17 11t20.5 14 t13.5 16q46 60 61 159t15 309zM2048 1408v-1536h-2048v1536h2048z" />
<glyph unicode="&#xf20b;" d="M992 912v-496q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v496q0 112 -80 192t-192 80h-272v-1152q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v1344q0 14 9 23t23 9h464q135 0 249 -66.5t180.5 -180.5t66.5 -249zM1376 1376v-880q0 -135 -66.5 -249t-180.5 -180.5 t-249 -66.5h-464q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-768h272q112 0 192 80t80 192v880q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
<glyph unicode="&#xf20c;" d="M1311 694v-114q0 -24 -13.5 -38t-37.5 -14h-202q-24 0 -38 14t-14 38v114q0 24 14 38t38 14h202q24 0 37.5 -14t13.5 -38zM821 464v250q0 53 -32.5 85.5t-85.5 32.5h-133q-68 0 -96 -52q-28 52 -96 52h-130q-53 0 -85.5 -32.5t-32.5 -85.5v-250q0 -22 21 -22h55 q22 0 22 22v230q0 24 13.5 38t38.5 14h94q24 0 38 -14t14 -38v-230q0 -22 21 -22h54q22 0 22 22v230q0 24 14 38t38 14h97q24 0 37.5 -14t13.5 -38v-230q0 -22 22 -22h55q21 0 21 22zM1410 560v154q0 53 -33 85.5t-86 32.5h-264q-53 0 -86 -32.5t-33 -85.5v-410 q0 -21 22 -21h55q21 0 21 21v180q31 -42 94 -42h191q53 0 86 32.5t33 85.5zM1536 1176v-1072q0 -96 -68 -164t-164 -68h-1072q-96 0 -164 68t-68 164v1072q0 96 68 164t164 68h1072q96 0 164 -68t68 -164z" />
<glyph unicode="&#xf20d;" horiz-adv-x="1792" />
<glyph unicode="&#xf20e;" horiz-adv-x="1792" />
<glyph unicode="&#xf20d;" d="M915 450h-294l147 551zM1001 128h311l-324 1024h-440l-324 -1024h311l383 314zM1536 1120v-960q0 -118 -85 -203t-203 -85h-960q-118 0 -203 85t-85 203v960q0 118 85 203t203 85h960q118 0 203 -85t85 -203z" />
<glyph unicode="&#xf20e;" horiz-adv-x="2048" d="M2048 641q0 -21 -13 -36.5t-33 -19.5l-205 -356q3 -9 3 -18q0 -20 -12.5 -35.5t-32.5 -19.5l-193 -337q3 -8 3 -16q0 -23 -16.5 -40t-40.5 -17q-25 0 -41 18h-400q-17 -20 -43 -20t-43 20h-399q-17 -20 -43 -20q-23 0 -40 16.5t-17 40.5q0 8 4 20l-193 335 q-20 4 -32.5 19.5t-12.5 35.5q0 9 3 18l-206 356q-20 5 -32.5 20.5t-12.5 35.5q0 21 13.5 36.5t33.5 19.5l199 344q0 1 -0.5 3t-0.5 3q0 36 34 51l209 363q-4 10 -4 18q0 24 17 40.5t40 16.5q26 0 44 -21h396q16 21 43 21t43 -21h398q18 21 44 21q23 0 40 -16.5t17 -40.5 q0 -6 -4 -18l207 -358q23 -1 39 -17.5t16 -38.5q0 -13 -7 -27l187 -324q19 -4 31.5 -19.5t12.5 -35.5zM1063 -158h389l-342 354h-143l-342 -354h360q18 16 39 16t39 -16zM112 654q1 -4 1 -13q0 -10 -2 -15l208 -360q2 0 4.5 -1t5.5 -2.5l5 -2.5l188 199v347l-187 194 q-13 -8 -29 -10zM986 1438h-388l190 -200l554 200h-280q-16 -16 -38 -16t-38 16zM1689 226q1 6 5 11l-64 68l-17 -79h76zM1583 226l22 105l-252 266l-296 -307l63 -64h463zM1495 -142l16 28l65 310h-427l333 -343q8 4 13 5zM578 -158h5l342 354h-373v-335l4 -6q14 -5 22 -13 zM552 226h402l64 66l-309 321l-157 -166v-221zM359 226h163v189l-168 -177q4 -8 5 -12zM358 1051q0 -1 0.5 -2t0.5 -2q0 -16 -8 -29l171 -177v269zM552 1121v-311l153 -157l297 314l-223 236zM556 1425l-4 -8v-264l205 74l-191 201q-6 -2 -10 -3zM1447 1438h-16l-621 -224 l213 -225zM1023 946l-297 -315l311 -319l296 307zM688 634l-136 141v-284zM1038 270l-42 -44h85zM1374 618l238 -251l132 624l-3 5l-1 1zM1718 1018q-8 13 -8 29v2l-216 376q-5 1 -13 5l-437 -463l310 -327zM522 1142v223l-163 -282zM522 196h-163l163 -283v283zM1607 196 l-48 -227l130 227h-82zM1729 266l207 361q-2 10 -2 14q0 1 3 16l-171 296l-129 -612l77 -82q5 3 15 7z" />
<glyph unicode="&#xf210;" d="M0 856q0 131 91.5 226.5t222.5 95.5h742l352 358v-1470q0 -132 -91.5 -227t-222.5 -95h-780q-131 0 -222.5 95t-91.5 227v790zM1232 102l-176 180v425q0 46 -32 79t-78 33h-484q-46 0 -78 -33t-32 -79v-492q0 -46 32.5 -79.5t77.5 -33.5h770z" />
<glyph unicode="&#xf211;" d="M934 1386q-317 -121 -556 -362.5t-358 -560.5q-20 89 -20 176q0 208 102.5 384.5t278.5 279t384 102.5q82 0 169 -19zM1203 1267q93 -65 164 -155q-389 -113 -674.5 -400.5t-396.5 -676.5q-93 72 -155 162q112 386 395 671t667 399zM470 -67q115 356 379.5 622t619.5 384 q40 -92 54 -195q-292 -120 -516 -345t-343 -518q-103 14 -194 52zM1536 -125q-193 50 -367 115q-135 -84 -290 -107q109 205 274 370.5t369 275.5q-21 -152 -101 -284q65 -175 115 -370z" />
<glyph unicode="&#xf212;" horiz-adv-x="2048" d="M1893 1144l155 -1272q-131 0 -257 57q-200 91 -393 91q-226 0 -374 -148q-148 148 -374 148q-193 0 -393 -91q-128 -57 -252 -57h-5l155 1272q224 127 482 127q233 0 387 -106q154 106 387 106q258 0 482 -127zM1398 157q129 0 232 -28.5t260 -93.5l-124 1021 q-171 78 -368 78q-224 0 -374 -141q-150 141 -374 141q-197 0 -368 -78l-124 -1021q105 43 165.5 65t148.5 39.5t178 17.5q202 0 374 -108q172 108 374 108zM1438 191l-55 907q-211 -4 -359 -155q-152 155 -374 155q-176 0 -336 -66l-114 -941q124 51 228.5 76t221.5 25 q209 0 374 -102q172 107 374 102z" />
<glyph unicode="&#xf213;" horiz-adv-x="2048" d="M1500 165v733q0 21 -15 36t-35 15h-93q-20 0 -35 -15t-15 -36v-733q0 -20 15 -35t35 -15h93q20 0 35 15t15 35zM1216 165v531q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-531q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM924 165v429q0 20 -15 35t-35 15h-101 q-20 0 -35 -15t-15 -35v-429q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM632 165v362q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-362q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM2048 311q0 -166 -118 -284t-284 -118h-1244q-166 0 -284 118t-118 284 q0 116 63 214.5t168 148.5q-10 34 -10 73q0 113 80.5 193.5t193.5 80.5q102 0 180 -67q45 183 194 300t338 117q149 0 275 -73.5t199.5 -199.5t73.5 -275q0 -66 -14 -122q135 -33 221 -142.5t86 -247.5z" />
<glyph unicode="&#xf214;" d="M0 1536h1536v-1392l-776 -338l-760 338v1392zM1436 209v926h-1336v-926l661 -294zM1436 1235v201h-1336v-201h1336zM181 937v-115h-37v115h37zM181 789v-115h-37v115h37zM181 641v-115h-37v115h37zM181 493v-115h-37v115h37zM181 345v-115h-37v115h37zM207 202l15 34 l105 -47l-15 -33zM343 142l15 34l105 -46l-15 -34zM478 82l15 34l105 -46l-15 -34zM614 23l15 33l104 -46l-15 -34zM797 10l105 46l15 -33l-105 -47zM932 70l105 46l15 -34l-105 -46zM1068 130l105 46l15 -34l-105 -46zM1203 189l105 47l15 -34l-105 -46zM259 1389v-36h-114 v36h114zM421 1389v-36h-115v36h115zM583 1389v-36h-115v36h115zM744 1389v-36h-114v36h114zM906 1389v-36h-114v36h114zM1068 1389v-36h-115v36h115zM1230 1389v-36h-115v36h115zM1391 1389v-36h-114v36h114zM181 1049v-79h-37v115h115v-36h-78zM421 1085v-36h-115v36h115z M583 1085v-36h-115v36h115zM744 1085v-36h-114v36h114zM906 1085v-36h-114v36h114zM1068 1085v-36h-115v36h115zM1230 1085v-36h-115v36h115zM1355 970v79h-78v36h115v-115h-37zM1355 822v115h37v-115h-37zM1355 674v115h37v-115h-37zM1355 526v115h37v-115h-37zM1355 378 v115h37v-115h-37zM1355 230v115h37v-115h-37zM760 265q-129 0 -221 91.5t-92 221.5q0 129 92 221t221 92q130 0 221.5 -92t91.5 -221q0 -130 -91.5 -221.5t-221.5 -91.5zM595 646q0 -36 19.5 -56.5t49.5 -25t64 -7t64 -2t49.5 -9t19.5 -30.5q0 -49 -112 -49q-97 0 -123 51 h-3l-31 -63q67 -42 162 -42q29 0 56.5 5t55.5 16t45.5 33t17.5 53q0 46 -27.5 69.5t-67.5 27t-79.5 3t-67 5t-27.5 25.5q0 21 20.5 33t40.5 15t41 3q34 0 70.5 -11t51.5 -34h3l30 58q-3 1 -21 8.5t-22.5 9t-19.5 7t-22 7t-20 4.5t-24 4t-23 1q-29 0 -56.5 -5t-54 -16.5 t-43 -34t-16.5 -53.5z" />
<glyph unicode="&#xf215;" horiz-adv-x="2048" d="M863 504q0 112 -79.5 191.5t-191.5 79.5t-191 -79.5t-79 -191.5t79 -191t191 -79t191.5 79t79.5 191zM1726 505q0 112 -79 191t-191 79t-191.5 -79t-79.5 -191q0 -113 79.5 -192t191.5 -79t191 79.5t79 191.5zM2048 1314v-1348q0 -44 -31.5 -75.5t-76.5 -31.5h-1832 q-45 0 -76.5 31.5t-31.5 75.5v1348q0 44 31.5 75.5t76.5 31.5h431q44 0 76 -31.5t32 -75.5v-161h754v161q0 44 32 75.5t76 31.5h431q45 0 76.5 -31.5t31.5 -75.5z" />
<glyph unicode="&#xf216;" horiz-adv-x="2048" d="M1430 953zM1690 749q148 0 253 -98.5t105 -244.5q0 -157 -109 -261.5t-267 -104.5q-85 0 -162 27.5t-138 73.5t-118 106t-109 126.5t-103.5 132.5t-108.5 126t-117 106t-136 73.5t-159 27.5q-154 0 -251.5 -91.5t-97.5 -244.5q0 -157 104 -250t263 -93q100 0 208 37.5 t193 98.5q5 4 21 18.5t30 24t22 9.5q14 0 24.5 -10.5t10.5 -24.5q0 -24 -60 -77q-101 -88 -234.5 -142t-260.5 -54q-133 0 -245.5 58t-180 165t-67.5 241q0 205 141.5 341t347.5 136q120 0 226.5 -43.5t185.5 -113t151.5 -153t139 -167.5t133.5 -153.5t149.5 -113 t172.5 -43.5q102 0 168.5 61.5t66.5 162.5q0 95 -64.5 159t-159.5 64q-30 0 -81.5 -18.5t-68.5 -18.5q-20 0 -35.5 15t-15.5 35q0 18 8.5 57t8.5 59q0 159 -107.5 263t-266.5 104q-58 0 -111.5 -18.5t-84 -40.5t-55.5 -40.5t-33 -18.5q-15 0 -25.5 10.5t-10.5 25.5 q0 19 25 46q59 67 147 103.5t182 36.5q191 0 318 -125.5t127 -315.5q0 -37 -4 -66q57 15 115 15z" />
<glyph unicode="&#xf217;" horiz-adv-x="1664" d="M1216 832q0 26 -19 45t-45 19h-128v128q0 26 -19 45t-45 19t-45 -19t-19 -45v-128h-128q-26 0 -45 -19t-19 -45t19 -45t45 -19h128v-128q0 -26 19 -45t45 -19t45 19t19 45v128h128q26 0 45 19t19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf218;" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19t-45 -19l-147 -146v293q0 26 -19 45t-45 19t-45 -19t-19 -45v-293l-147 146q-19 19 -45 19t-45 -19t-19 -45t19 -45l256 -256q19 -19 45 -19t45 19l256 256q19 19 19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf219;" horiz-adv-x="2048" d="M212 768l623 -665l-300 665h-323zM1024 -4l349 772h-698zM538 896l204 384h-262l-288 -384h346zM1213 103l623 665h-323zM683 896h682l-204 384h-274zM1510 896h346l-288 384h-262zM1651 1382l384 -512q14 -18 13 -41.5t-17 -40.5l-960 -1024q-18 -20 -47 -20t-47 20 l-960 1024q-16 17 -17 40.5t13 41.5l384 512q18 26 51 26h1152q33 0 51 -26z" />
<glyph unicode="&#xf21a;" horiz-adv-x="2048" d="M1811 -19q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83 q19 19 45 19t45 -19l83 -83zM237 19q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -82l83 82q19 19 45 19t45 -19l83 -82l64 64v293l-210 314q-17 26 -7 56.5t40 40.5l177 58v299h128v128h256v128h256v-128h256v-128h128v-299l177 -58q30 -10 40 -40.5t-7 -56.5l-210 -314 v-293l19 18q19 19 45 19t45 -19l83 -82l83 82q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83zM640 1152v-128l384 128l384 -128v128h-128v128h-512v-128h-128z" />
<glyph unicode="&#xf21b;" d="M576 0l96 448l-96 128l-128 64zM832 0l128 640l-128 -64l-96 -128zM992 1010q-2 4 -4 6q-10 8 -96 8q-70 0 -167 -19q-7 -2 -21 -2t-21 2q-97 19 -167 19q-86 0 -96 -8q-2 -2 -4 -6q2 -18 4 -27q2 -3 7.5 -6.5t7.5 -10.5q2 -4 7.5 -20.5t7 -20.5t7.5 -17t8.5 -17t9 -14 t12 -13.5t14 -9.5t17.5 -8t20.5 -4t24.5 -2q36 0 59 12.5t32.5 30t14.5 34.5t11.5 29.5t17.5 12.5h12q11 0 17.5 -12.5t11.5 -29.5t14.5 -34.5t32.5 -30t59 -12.5q13 0 24.5 2t20.5 4t17.5 8t14 9.5t12 13.5t9 14t8.5 17t7.5 17t7 20.5t7.5 20.5q2 7 7.5 10.5t7.5 6.5 q2 9 4 27zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 61 4.5 118t19 125.5t37.5 123.5t63.5 103.5t93.5 74.5l-90 220h214q-22 64 -22 128q0 12 2 32q-194 40 -194 96q0 57 210 99q17 62 51.5 134t70.5 114q32 37 76 37q30 0 84 -31t84 -31t84 31 t84 31q44 0 76 -37q36 -42 70.5 -114t51.5 -134q210 -42 210 -99q0 -56 -194 -96q7 -81 -20 -160h214l-82 -225q63 -33 107.5 -96.5t65.5 -143.5t29 -151.5t8 -148.5z" />
<glyph unicode="&#xf21c;" horiz-adv-x="2304" d="M2301 500q12 -103 -22 -198.5t-99 -163.5t-158.5 -106t-196.5 -31q-161 11 -279.5 125t-134.5 274q-12 111 27.5 210.5t118.5 170.5l-71 107q-96 -80 -151 -194t-55 -244q0 -27 -18.5 -46.5t-45.5 -19.5h-256h-69q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5 t-131.5 316.5t131.5 316.5t316.5 131.5q76 0 152 -27l24 45q-123 110 -304 110h-64q-26 0 -45 19t-19 45t19 45t45 19h128q78 0 145 -13.5t116.5 -38.5t71.5 -39.5t51 -36.5h512h115l-85 128h-222q-30 0 -49 22.5t-14 52.5q4 23 23 38t43 15h253q33 0 53 -28l70 -105 l114 114q19 19 46 19h101q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-179l115 -172q131 63 275 36q143 -26 244 -134.5t118 -253.5zM448 128q115 0 203 72.5t111 183.5h-314q-35 0 -55 31q-18 32 -1 63l147 277q-47 13 -91 13q-132 0 -226 -94t-94 -226t94 -226 t226 -94zM1856 128q132 0 226 94t94 226t-94 226t-226 94q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94z" />
<glyph unicode="&#xf21d;" d="M1408 0q0 -63 -61.5 -113.5t-164 -81t-225 -46t-253.5 -15.5t-253.5 15.5t-225 46t-164 81t-61.5 113.5q0 49 33 88.5t91 66.5t118 44.5t131 29.5q26 5 48 -10.5t26 -41.5q5 -26 -10.5 -48t-41.5 -26q-58 -10 -106 -23.5t-76.5 -25.5t-48.5 -23.5t-27.5 -19.5t-8.5 -12 q3 -11 27 -26.5t73 -33t114 -32.5t160.5 -25t201.5 -10t201.5 10t160.5 25t114 33t73 33.5t27 27.5q-1 4 -8.5 11t-27.5 19t-48.5 23.5t-76.5 25t-106 23.5q-26 4 -41.5 26t-10.5 48q4 26 26 41.5t48 10.5q71 -12 131 -29.5t118 -44.5t91 -66.5t33 -88.5zM1024 896v-384 q0 -26 -19 -45t-45 -19h-64v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384h-64q-26 0 -45 19t-19 45v384q0 53 37.5 90.5t90.5 37.5h384q53 0 90.5 -37.5t37.5 -90.5zM928 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5 t158.5 -65.5t65.5 -158.5z" />
<glyph unicode="&#xf21e;" horiz-adv-x="1792" d="M1280 512h305q-5 -6 -10 -10.5t-9 -7.5l-3 -4l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-5 2 -21 20h369q22 0 39.5 13.5t22.5 34.5l70 281l190 -667q6 -20 23 -33t39 -13q21 0 38 13t23 33l146 485l56 -112q18 -35 57 -35zM1792 940q0 -145 -103 -300h-369l-111 221 q-8 17 -25.5 27t-36.5 8q-45 -5 -56 -46l-129 -430l-196 686q-6 20 -23.5 33t-39.5 13t-39 -13.5t-22 -34.5l-116 -464h-423q-103 155 -103 300q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124 t127 -344z" />
<glyph unicode="&#xf221;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292 q11 134 80.5 249t182 188t245.5 88q170 19 319 -54t236 -212t87 -306zM128 960q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5z" />
<glyph unicode="&#xf222;" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h416q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-419 -420q87 -104 129.5 -236.5t30.5 -276.5q-22 -250 -200.5 -431t-428.5 -206q-163 -17 -314 39.5t-256.5 162t-162 256.5t-39.5 314q25 250 206 428.5 t431 200.5q144 12 276.5 -30.5t236.5 -129.5l419 419h-261q-14 0 -23 9t-9 23v64zM704 -128q117 0 223.5 45.5t184 123t123 184t45.5 223.5t-45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123 t223.5 -45.5z" />
<glyph unicode="&#xf223;" horiz-adv-x="1280" d="M830 1220q145 -72 233.5 -210.5t88.5 -305.5q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5 t-147.5 384.5q0 167 88.5 305.5t233.5 210.5q-165 96 -228 273q-6 16 3.5 29.5t26.5 13.5h69q21 0 29 -20q44 -106 140 -171t214 -65t214 65t140 171q8 20 37 20h61q17 0 26.5 -13.5t3.5 -29.5q-63 -177 -228 -273zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf224;" d="M1024 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-149 16 -270.5 103t-186.5 223.5t-53 291.5q16 204 160 353.5t347 172.5q118 14 228 -19t198 -103l255 254h-134q-14 0 -23 9t-9 23v64zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf225;" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5t-147.5 384.5q0 201 126 359l-52 53l-101 -111q-9 -10 -22 -10.5t-23 7.5l-48 44q-10 8 -10.5 21.5t8.5 23.5l105 115l-111 112v-134q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9 t-9 23v288q0 26 19 45t45 19h288q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-133l106 -107l86 94q9 10 22 10.5t23 -7.5l48 -44q10 -8 10.5 -21.5t-8.5 -23.5l-90 -99l57 -56q158 126 359 126t359 -126l255 254h-134q-14 0 -23 9t-9 23v64zM832 256q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf226;" horiz-adv-x="1792" d="M1790 1007q12 -155 -52.5 -292t-186 -224t-271.5 -103v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-512v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23 t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292q17 206 164.5 356.5t352.5 169.5q206 21 377 -94q171 115 377 94q205 -19 352.5 -169.5t164.5 -356.5zM896 647q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM576 512q115 0 218 57q-154 165 -154 391 q0 224 154 391q-103 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5zM1152 128v260q-137 15 -256 94q-119 -79 -256 -94v-260h512zM1216 512q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5q-115 0 -218 -57q154 -167 154 -391 q0 -226 -154 -391q103 -57 218 -57z" />
<glyph unicode="&#xf227;" horiz-adv-x="1920" d="M1536 1120q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-31 -182 -166 -312t-318 -156q-210 -29 -384.5 80t-241.5 300q-117 6 -221 57.5t-177.5 133t-113.5 192.5t-32 230 q9 135 78 252t182 191.5t248 89.5q118 14 227.5 -19t198.5 -103l255 254h-134q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q59 -74 93 -169q182 -9 328 -124l255 254h-134q-14 0 -23 9 t-9 23v64zM1024 704q0 20 -4 58q-162 -25 -271 -150t-109 -292q0 -20 4 -58q162 25 271 150t109 292zM128 704q0 -168 111 -294t276 -149q-3 29 -3 59q0 210 135 369.5t338 196.5q-53 120 -163.5 193t-245.5 73q-185 0 -316.5 -131.5t-131.5 -316.5zM1088 -128 q185 0 316.5 131.5t131.5 316.5q0 168 -111 294t-276 149q3 -29 3 -59q0 -210 -135 -369.5t-338 -196.5q53 -120 163.5 -193t245.5 -73z" />
<glyph unicode="&#xf228;" horiz-adv-x="2048" d="M1664 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-32 -180 -164.5 -310t-313.5 -157q-223 -34 -409 90q-117 -78 -256 -93v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23 t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-155 17 -279.5 109.5t-187 237.5t-39.5 307q25 187 159.5 322.5t320.5 164.5q224 34 410 -90q146 97 320 97q201 0 359 -126l255 254h-134q-14 0 -23 9 t-9 23v64zM896 391q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM128 704q0 -185 131.5 -316.5t316.5 -131.5q117 0 218 57q-154 167 -154 391t154 391q-101 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5zM1216 256q185 0 316.5 131.5t131.5 316.5 t-131.5 316.5t-316.5 131.5q-117 0 -218 -57q154 -167 154 -391t-154 -391q101 -57 218 -57z" />
<glyph unicode="&#xf229;" horiz-adv-x="1792" d="M1728 1536q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-229 -230l156 -156q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-156 157l-99 -100q87 -104 129.5 -236.5t30.5 -276.5q-22 -250 -200.5 -431t-428.5 -206q-163 -17 -314 39.5 t-256.5 162t-162 256.5t-39.5 314q25 250 206 428.5t431 200.5q144 12 276.5 -30.5t236.5 -129.5l99 99l-156 156q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l156 -156l229 229h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM1280 448q0 117 -45.5 223.5t-123 184t-184 123 t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5z" />
<glyph unicode="&#xf22a;" horiz-adv-x="1280" d="M640 892q217 -24 364.5 -187.5t147.5 -384.5q0 -167 -87 -306t-236 -212t-319 -54q-133 15 -245.5 88t-182 188t-80.5 249q-12 155 52.5 292t186 224t271.5 103v132h-160q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h160v165l-92 -92q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22 t9 23l202 201q19 19 45 19t45 -19l202 -201q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-92 92v-165h160q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-160v-132zM576 -128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5 t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf22b;" horiz-adv-x="2048" d="M2029 685q19 -19 19 -45t-19 -45l-294 -294q-9 -10 -22.5 -10t-22.5 10l-45 45q-10 9 -10 22.5t10 22.5l185 185h-294v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-131q-12 -119 -67 -226t-139 -183.5t-196.5 -121.5t-234.5 -45q-180 0 -330.5 91t-234.5 247 t-74 337q8 162 94 300t226.5 219.5t302.5 85.5q166 4 310.5 -71.5t235.5 -208.5t107 -296h131v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h294l-185 185q-10 9 -10 22.5t10 22.5l45 45q9 10 22.5 10t22.5 -10zM640 128q104 0 198.5 40.5t163.5 109.5t109.5 163.5 t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" />
<glyph unicode="&#xf22c;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-612q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v612q-217 24 -364.5 187.5t-147.5 384.5q0 117 45.5 223.5t123 184t184 123t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5zM576 512q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf22d;" horiz-adv-x="1792" />
<glyph unicode="&#xf22e;" horiz-adv-x="1792" />
<glyph unicode="&#xf22f;" horiz-adv-x="1792" />
<glyph unicode="&#xf230;" d="M1451 1408q35 0 60 -25t25 -60v-1366q0 -35 -25 -60t-60 -25h-391v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-735q-35 0 -60 25t-25 60v1366q0 35 25 60t60 25h1366z" />
<glyph unicode="&#xf231;" horiz-adv-x="1280" d="M0 939q0 108 37.5 203.5t103.5 166.5t152 123t185 78t202 26q158 0 294 -66.5t221 -193.5t85 -287q0 -96 -19 -188t-60 -177t-100 -149.5t-145 -103t-189 -38.5q-68 0 -135 32t-96 88q-10 -39 -28 -112.5t-23.5 -95t-20.5 -71t-26 -71t-32 -62.5t-46 -77.5t-62 -86.5 l-14 -5l-9 10q-15 157 -15 188q0 92 21.5 206.5t66.5 287.5t52 203q-32 65 -32 169q0 83 52 156t132 73q61 0 95 -40.5t34 -102.5q0 -66 -44 -191t-44 -187q0 -63 45 -104.5t109 -41.5q55 0 102 25t78.5 68t56 95t38 110.5t20 111t6.5 99.5q0 173 -109.5 269.5t-285.5 96.5 q-200 0 -334 -129.5t-134 -328.5q0 -44 12.5 -85t27 -65t27 -45.5t12.5 -30.5q0 -28 -15 -73t-37 -45q-2 0 -17 3q-51 15 -90.5 56t-61 94.5t-32.5 108t-11 106.5z" />
<glyph unicode="&#xf232;" d="M985 562q13 0 97.5 -44t89.5 -53q2 -5 2 -15q0 -33 -17 -76q-16 -39 -71 -65.5t-102 -26.5q-57 0 -190 62q-98 45 -170 118t-148 185q-72 107 -71 194v8q3 91 74 158q24 22 52 22q6 0 18 -1.5t19 -1.5q19 0 26.5 -6.5t15.5 -27.5q8 -20 33 -88t25 -75q0 -21 -34.5 -57.5 t-34.5 -46.5q0 -7 5 -15q34 -73 102 -137q56 -53 151 -101q12 -7 22 -7q15 0 54 48.5t52 48.5zM782 32q127 0 243.5 50t200.5 134t134 200.5t50 243.5t-50 243.5t-134 200.5t-200.5 134t-243.5 50t-243.5 -50t-200.5 -134t-134 -200.5t-50 -243.5q0 -203 120 -368l-79 -233 l242 77q158 -104 345 -104zM782 1414q153 0 292.5 -60t240.5 -161t161 -240.5t60 -292.5t-60 -292.5t-161 -240.5t-240.5 -161t-292.5 -60q-195 0 -365 94l-417 -134l136 405q-108 178 -108 389q0 153 60 292.5t161 240.5t240.5 161t292.5 60z" />
<glyph unicode="&#xf233;" horiz-adv-x="1792" d="M128 128h1024v128h-1024v-128zM128 640h1024v128h-1024v-128zM1696 192q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM128 1152h1024v128h-1024v-128zM1696 704q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1696 1216 q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1792 384v-384h-1792v384h1792zM1792 896v-384h-1792v384h1792zM1792 1408v-384h-1792v384h1792z" />
<glyph unicode="&#xf234;" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1664 512h352q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-352q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5 t-9.5 22.5v352h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v352q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-352zM928 288q0 -52 38 -90t90 -38h256v-238q-68 -50 -171 -50h-874q-121 0 -194 69t-73 190q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q79 -61 154.5 -91.5t164.5 -30.5t164.5 30.5t154.5 91.5q20 17 39 17q132 0 217 -96h-223q-52 0 -90 -38t-38 -90v-192z" />
<glyph unicode="&#xf235;" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1781 320l249 -249q9 -9 9 -23q0 -13 -9 -22l-136 -136q-9 -9 -22 -9q-14 0 -23 9l-249 249l-249 -249q-9 -9 -23 -9q-13 0 -22 9l-136 136 q-9 9 -9 22q0 14 9 23l249 249l-249 249q-9 9 -9 23q0 13 9 22l136 136q9 9 22 9q14 0 23 -9l249 -249l249 249q9 9 23 9q13 0 22 -9l136 -136q9 -9 9 -22q0 -14 -9 -23zM1283 320l-181 -181q-37 -37 -37 -91q0 -53 37 -90l83 -83q-21 -3 -44 -3h-874q-121 0 -194 69 t-73 190q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q154 -122 319 -122t319 122q20 17 39 17q28 0 57 -6q-28 -27 -41 -50t-13 -56q0 -54 37 -91z" />
<glyph unicode="&#xf236;" horiz-adv-x="2048" d="M256 512h1728q26 0 45 -19t19 -45v-448h-256v256h-1536v-256h-256v1216q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-704zM832 832q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM2048 576v64q0 159 -112.5 271.5t-271.5 112.5h-704 q-26 0 -45 -19t-19 -45v-384h1152z" />
<glyph unicode="&#xf237;" d="M1536 1536l-192 -448h192v-192h-274l-55 -128h329v-192h-411l-357 -832l-357 832h-411v192h329l-55 128h-274v192h192l-192 448h256l323 -768h378l323 768h256zM768 320l108 256h-216z" />
<glyph unicode="&#xf238;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM768 192q80 0 136 56t56 136t-56 136t-136 56 t-136 -56t-56 -136t56 -136t136 -56zM1344 768v512h-1152v-512h1152z" />
<glyph unicode="&#xf239;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM288 224q66 0 113 47t47 113t-47 113t-113 47 t-113 -47t-47 -113t47 -113t113 -47zM704 768v512h-544v-512h544zM1248 224q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM1408 768v512h-576v-512h576z" />
<glyph unicode="&#xf23a;" horiz-adv-x="1792" d="M1792 204v-209h-642v209h134v926h-6l-314 -1135h-243l-310 1135h-8v-926h135v-209h-538v209h69q21 0 43 19.5t22 37.5v881q0 18 -22 40t-43 22h-69v209h672l221 -821h6l223 821h670v-209h-71q-19 0 -41 -22t-22 -40v-881q0 -18 21.5 -37.5t41.5 -19.5h71z" />
<glyph unicode="&#xf23b;" horiz-adv-x="1792" />
<glyph unicode="&#xf23c;" horiz-adv-x="1792" />
<glyph unicode="&#xf23d;" horiz-adv-x="1792" />
<glyph unicode="&#xf23e;" horiz-adv-x="1792" />
<glyph unicode="&#xf500;" horiz-adv-x="1792" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 306 KiB

BIN
public/font-awesome/fonts/fontawesome-webfont.ttf Executable file → Normal file

Binary file not shown.

BIN
public/font-awesome/fonts/fontawesome-webfont.woff Executable file → Normal file

Binary file not shown.

Binary file not shown.

BIN
public/icons/mouse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

BIN
public/icons/subflow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -31,14 +31,18 @@
<body spellcheck="false">
<div id="header">
<span class="logo"><img src="node-red.png"> <span>Node-RED</span></span>
<div class="pull-right">
<a id="btn-deploy" class="button action-deploy disabled" href="#"><i id="btn-icn-deploy" class="fa fa-download"></i> Deploy</a>
<a id="btn-sidemenu" class="button dropdown-toggle" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a>
</div>
<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>
<div id="main-container" class="sidebar-closed">
<div id="main-container" class="sidebar-closed hide">
<div id="palette">
<img src="spin.svg" class="palette-spinner"/>
<img src="spin.svg" class="palette-spinner hide"/>
<div id="palette-container" class="palette-scroll">
</div>
<div id="palette-search">
@@ -51,11 +55,10 @@
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
<div id="chart"></div>
<div id="workspace-toolbar">
<div class="btn-group">
<a class="btn btn-small" href="#"><i class="fa fa-search-minus"></i></a>
<a class="btn btn-small" href="#"><i class="fa fa-dot-circle-o"></i></a>
<a class="btn btn-small" href="#"><i class="fa fa-search-plus"></i></a>
</div>
<a class="button" id="workspace-subflow-edit" href="#"><i class="fa fa-pencil"></i> edit name</a>
<a class="button disabled" id="workspace-subflow-add-input" href="#"><i class="fa fa-plus"></i> input</a>
<a class="button" id="workspace-subflow-add-output" href="#"><i class="fa fa-plus"></i> output</a>
<a class="button" id="workspace-subflow-delete" href="#"><i class="fa fa-trash"></i> delete subflow</a>
</div>
</div>
@@ -78,9 +81,18 @@
<div id="notifications"></div>
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
<div id="shade"></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
<div id="subflow-dialog" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label>Name</label><input type="text" id="subflow-input-name">
</div>
</form>
<div class="form-tips" id="subflow-dialog-user-count"></div>
</div>
<div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal">
@@ -97,7 +109,7 @@
<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://node-red.github.io/docs" target="_blank">Open help in new window &raquo;</a></span></h5>
<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>
@@ -136,6 +148,9 @@
<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">
@@ -196,7 +211,6 @@
</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>
@@ -219,13 +233,23 @@
</div>
</script>
<script type="text/x-red" data-template-name="subflow">
<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 src="jquery/js/jquery-1.11.1.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
<script src="jquery/js/jquery-ui-1.10.3.custom.min.js"></script>
<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="d3.v3.min.js"></script>
<script src="red/main.js"></script>
<script src="red/settings.js"></script>
<script src="red/user.js"></script>
<script src="red/comms.js"></script>
<script src="red/ui/state.js"></script>
<script src="red/nodes.js"></script>
@@ -243,5 +267,6 @@
<script src="red/ui/library.js"></script>
<script src="red/ui/notifications.js"></script>
<script src="red/ui/touch/radialMenu.js"></script>
</body>
</html>

6
public/marked/marked.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
public/node-red-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,12 +1,12 @@
.textview {
background-color: white;
font-family: "Consolas", "Monaco", "Vera Mono", "monospace";
font-size: 10pt;
font-family: "Consolas", "Monaco", "Vera Mono", monospace;
font-size: 12px;
min-width: 50px;
min-height: 50px;
}
.textviewScroll {
padding: 1px 2px;
padding: 4px 2px 4px 2px;
}
.textviewContent {
cursor: auto;
@@ -17,6 +17,10 @@
.textviewRightRuler {
border-left: 1px solid #eaeaea;
}
.textviewInnerRightRuler {
border-left: 1px solid #eaeaea;
background-color: white;
}
.textviewMarginRuler {
border-left: 1px solid #eaeaea;
}
@@ -38,12 +42,41 @@
.ruler.overview {
width: 14px;
}
.ruler.zoom {
width: 100px;
height: 100%;
}
.rulerLines {
color: silver;
}
.rulerLines.even
.rulerLines.odd {
}
.rulerZoomWindow {
background-color: rgba(0, 0, 0, 0.1);
margin-left: 1px;
border: 1px solid #eee;
position: absolute;
width: calc(100% - 4px);
border-radius: 5px;
z-index: 100;
}
.textviewZoom {
font-size: 2px !important;
cursor: pointer;
}
.textviewZoom .textviewContent {
cursor: pointer;
}
.textviewZoom .textviewScroll {
padding: 0;
}
.textviewZoom .punctuation.separator.tab {
background-image: none;
}
.textviewZoom .punctuation.separator.space {
background-image: none;
}
.tooltipTheme.textview {
background-color: InfoBackground !important;
color: InfoText !important;
@@ -52,8 +85,8 @@
padding: 0px;
}
.textviewTooltip {
font-family: "Consolas", "Monaco", "Vera Mono", "monospace";
font-size: 10pt;
font-family: "Consolas", "Monaco", "Vera Mono", monospace;
font-size: 12px;
background-color: InfoBackground;
color: InfoText;
padding: 2px;
@@ -79,8 +112,57 @@
.tooltipTheme .annotationLine.currentLine {
background-color: transparent !important;
}
.textViewFind {
background-color: #ddd;
position: absolute;
top: -50px;
right: -1000px;
border: 1px solid #aaa;
border-top: 0;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
padding: 2px;
z-index: 100;
}
.textViewFind.show {
top: 0;
right: 40px;
transition: top 0.3s ease-out;
-ms-transition: top 0.3s ease-out;
-moz-transition: top 0.3s ease-out;
-webkit-transition: top 0.3s ease-out;
-o-transition: top 0.3s ease-out;
}
.textViewFindButton {
margin-right: 1px;
margin-left: 0;
}
.textViewFindButton:first-child {
margin-left: 5px;
}
.textViewFindButton:last-child {
margin-right: 5px;
}
.textViewFindButton.checked {
color: blue;
text-decoration: underline;
}
.textViewReplaceInput {
}
.textViewFindInput {
}
.textViewFindCloseButton {
width: 16px;
height: 16px;
border-width: 0;
background-color: transparent;
vertical-align: baseline;
background-position: center;
background-repeat: no-repeat;
background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///4CAgP///yH5BAEAAAMALAAAAAAQABAAAAIdnI+py+1vhECSyTluu9px+HkctnSdUh0pxLYuVAAAOw==);
}
.contentassist {
font-size:9pt;
font-size:12px;
display: none;
background-color: white;
position: fixed;
@@ -89,13 +171,14 @@
z-index:100;
cursor: default;
min-width: 70px;
max-width: 350px;
max-height: 170px;
width: 350px;
height: 170px;
overflow: hidden;
white-space: nowrap;
border-radius: 5px;
box-shadow: rgba(0, 0, 0, 0.3) 2px 2px 10px;
line-height: 18px;
resize: both;
}
.contentassist:focus {
outline: none;
@@ -108,9 +191,9 @@
}
.contentassist hr{
border: 0;
height: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
height: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
.contentassist .proposal-noemphasis-keyword {
background-color: aliceblue;
@@ -124,11 +207,11 @@
}
.contentassist .proposal-noemphasis-title-keywords {
background-color: aliceblue;
color: grey;
color: gray;
}
.contentassist .proposal-noemphasis-title {
background-color: aliceblue;
color: grey;
color: gray;
padding-top: 5px;
}
.contentassist .proposal-noemphasis-title::before {
@@ -161,6 +244,7 @@
.contentassist .cloneProposal {
box-shadow: rgba(0, 0, 0, 0.9) 2px 2px 8px;
position: fixed;
visibility: visible;
z-index: 1000;
}
.contentassist>div {
@@ -168,77 +252,43 @@
}
.cloneWrapper {
display: block;
height: 0;
overflow: visible;
visibility: hidden;
width: 0;
z-index: 1000;
}
.contentassist.cloneWrapper:hover {
overflow: visible;
}
.comment.block.documentation, .comment-block-documentation {
color: #00008F;
}
.comment {
color: #3C802C;
}
.constant.character.entity, .constant-character-entity {
font-style: normal;
}
.constant {
color: blue;
}
.entity.name.function, .entity.name.type, .entity-name-function, .entity-name-type {
font-weight: bold;
}
.entity.name.tag, .entity-name-tag {
color: #CC4C07;
}
.entity.other.attribute.name, .entity-other-attribute-name {
color: #3C802C;
}
.entity {
color: #3f7f7f;
}
.invalid.illegal, .invalid-illegal {
color: white;
background-color: red;
}
.invalid.deprecated, .invalid-deprecated {
text-decoration: line-through;
}
.invalid {
color: red;
font-weight: bold;
}
.keyword.other.documentation.markup {
color: #7F7F9F;
}
.keyword.other.documentation {
color: #7F9FBF;
}
.keyword.operator, .keyword-operator {
color: #ddd;
}
.keyword {
color: #CC4C07;
color: #9F4177;
font-weight: bold;
}
.markup.heading, .markup-heading {
font-weight: bold;
.storage {
color: #7F0055;
}
.markup.quote, .markup-quote {
font-style: italic;
.string {
color: #446fbd;
}
.meta.annotation.currentLine {
background-color: #EAF2FE;
.support {
color: #21439c;
}
.meta.tag {
color: #3f7f7f;
}
.punctuation.definition.comment, .punctuation-definition-comment {
color: #3f5fbf;
}
.punctuation.definition.string, .punctuation-definition-string {
color: blue;
.variable {
color: #0000c0;
}
.punctuation.separator.space {
@@ -252,24 +302,51 @@
background-repeat: no-repeat;
background-position: left center;
}
.storage {
color: #7F0055;
.comment-block-documentation {
color: #00008F;
}
.string {
color: #446fbd;
.constant-character-entity {
font-style: normal;
}
.support {
color: #21439c;
.entity-name-function, .entity-name-type {
font-weight: bold;
color: #67BBB8;
}
.variable.parameter, .variable-parameter {
color: black;
.entity-name-tag {
color: #98937B;
}
.variable.language, .variable-language {
color: #7F0055;
.entity-other-attribute-name {
color: #3C802C;
}
.invalid-illegal {
color: white;
background-color: red;
}
.invalid-deprecated {
text-decoration: line-through;
}
.keyword-operator {
color: #CC4C07;
font-weight: bold;
}
.variable {
color: #0000c0;
.meta.annotation.currentLine {
background-color: #EAF2FE;
}
.meta.tag {
color: #3f7f7f;
}
.punctuation-definition-comment {
color: #3f5fbf;
}
.punctuation-definition-string {
color: blue;
}
.variable-parameter {
color: #D1416F;
}
.variable-language {
color: #7F0055;
font-weight: bold;
}
.cm-meta { color: #00008F; }
.cm-keyword { font-weight: bold; color: #7F0055; }
@@ -446,12 +523,12 @@
border: 1px solid #ff3232;
}
.annotationOverview.currentLine {
background-color: #f8a852;
border: 1px solid #f79327;
background-color: #EAF2FE;
border: 1px solid black;
}
.annotationOverview.matchingSearch {
background-color: #C3E1FF;
border: 1px solid #afcae5;
background-color: #C3E1FF;
border: 1px solid #afcae5;
}
.annotationOverview.currentSearch {
background-color: #53D1FF;
@@ -524,3 +601,71 @@ border: 1px solid #afcae5;
.annotationLine.currentLine {
background-color: #EAF2FE;
}
.comment.block.documentation {
color: #00008F;
}
.constant.character.entity {
font-style: normal;
}
.entity.name.function, .entity.name.type {
font-weight: bold;
color: #67BBB8;
}
.entity.name {
color: #98937B;
}
.entity.other.attribute.name {
color: #3C802C;
}
.meta.documentation.tag {
color: #7F7F9F;
}
.meta.documentation.annotation {
color: #7F9FBF;
}
.markup.bold {
font-weight: bold;
}
.markup.heading, .markup-heading {
color: blue;
}
.markup.italic {
font-style: italic;
}
.markup.list {
color: #CC4C07;
}
.markup.other.separator {
color: #00008F;
}
.markup.other.strikethrough {
text-decoration: line-through;
}
.markup.other.table {
color: #3C802C;
}
.markup.quote, .markup-quote {
color: #446fbd;
}
.markup.raw {
font-family: monospace
}
.markup.underline.link {
text-decoration: underline;
}
.punctuation.definition.comment {
color: #3f5fbf;
}
.punctuation.definition.string {
color: blue;
}
.variable.parameter {
color: #D1416F;
}
.variable.language {
color: #7F0055;
font-weight: bold;
}
.variable.other {
color: #E038AD;
}

File diff suppressed because it is too large Load Diff

View File

@@ -17,27 +17,47 @@
RED.comms = (function() {
var errornotification = null;
var clearErrorTimer = null;
var subscriptions = {};
var ws;
var pendingAuth = false;
function connectWS() {
var path = location.hostname+":"+location.port+document.location.pathname;
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
ws = new WebSocket(path);
ws.onopen = function() {
if (errornotification) {
errornotification.close();
errornotification = null;
}
var auth_tokens = RED.settings.get("auth-tokens");
pendingAuth = (auth_tokens!=null);
function completeConnection() {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
ws.send(JSON.stringify({subscribe:t}));
}
}
}
ws = new WebSocket(path);
ws.onopen = function() {
if (errornotification) {
clearErrorTimer = setTimeout(function() {
errornotification.close();
errornotification = null;
},1000);
}
if (pendingAuth) {
ws.send(JSON.stringify({auth:auth_tokens.access_token}));
} else {
completeConnection();
}
}
ws.onmessage = function(event) {
var msg = JSON.parse(event.data);
if (msg.topic) {
if (pendingAuth && msg.auth == "ok") {
pendingAuth = false;
completeConnection();
} else if (msg.topic) {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
@@ -56,6 +76,9 @@ RED.comms = (function() {
ws.onclose = function() {
if (errornotification == null) {
errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true);
} else if (clearErrorTimer) {
clearTimeout(clearErrorTimer);
clearErrorTimer = null;
}
setTimeout(connectWS,1000);
}
@@ -72,15 +95,15 @@ RED.comms = (function() {
}
function unsubscribe(topic,callback) {
if (subscriptions.topic) {
for (var i=0;i<subscriptions.topic.length;i++) {
if (subscriptions.topic[i] === callback) {
subscriptions.topic.splice(i,1);
if (subscriptions[topic]) {
for (var i=0;i<subscriptions[topic].length;i++) {
if (subscriptions[topic][i] === callback) {
subscriptions[topic].splice(i,1);
break;
}
}
if (subscriptions.topic.length === 0) {
delete subscriptions.topic;
if (subscriptions[topic].length === 0) {
delete subscriptions[topic];
}
}
}

View File

@@ -50,6 +50,12 @@ RED.history = (function() {
RED.view.removeWorkspace(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]);
}
}
} else if (ev.t == "delete") {
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
@@ -57,6 +63,48 @@ RED.history = (function() {
RED.view.addWorkspace(ev.workspaces[i]);
}
}
if (ev.subflow) {
RED.nodes.addSubflow(ev.subflow);
}
var subflow;
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
subflow = RED.nodes.subflow(ev.subflowInputs[0].z);
subflow.in.push(ev.subflowInputs[0]);
subflow.in[0].dirty = true;
}
if (ev.subflowOutputs && ev.subflowOutputs.length > 0) {
subflow = RED.nodes.subflow(ev.subflowOutputs[0].z);
ev.subflowOutputs.sort(function(a,b) { return a.i-b.i});
for (i=0;i<ev.subflowOutputs.length;i++) {
var output = ev.subflowOutputs[i];
subflow.out.splice(output.i,0,output);
for (var j=output.i+1;j<subflow.out.length;j++) {
subflow.out[j].i++;
subflow.out[j].dirty = true;
}
RED.nodes.eachLink(function(l) {
if (l.source.type == "subflow:"+subflow.id) {
if (l.sourcePort >= output.i) {
l.sourcePort++;
}
}
});
}
}
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;
}
});
}
if (ev.nodes) {
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.add(ev.nodes[i]);
@@ -80,15 +128,68 @@ RED.history = (function() {
ev.node[i] = ev.changes[i];
}
}
RED.editor.updateNodeProperties(ev.node);
if (ev.subflow) {
if (ev.subflow.hasOwnProperty('inputCount')) {
if (ev.node.in.length > ev.subflow.inputCount) {
ev.node.in.splice(ev.subflow.inputCount);
} else if (ev.subflow.inputs.length > 0) {
ev.node.in = ev.node.in.concat(ev.subflow.inputs);
}
}
if (ev.subflow.hasOwnProperty('outputCount')) {
if (ev.node.out.length > ev.subflow.outputCount) {
ev.node.out.splice(ev.subflow.outputCount);
} else if (ev.subflow.outputs.length > 0) {
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.palette.refresh();
} else {
RED.editor.updateNodeProperties(ev.node);
RED.editor.validateNode(ev.node);
}
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.addLink(ev.links[i]);
}
}
RED.editor.validateNode(ev.node);
ev.node.dirty = true;
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;
}
});
for (i=0;i<ev.nodes.length;i++) {
RED.nodes.remove(ev.nodes[i]);
}
}
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.removeLink(ev.links[i]);
}
}
RED.nodes.removeSubflow(ev.subflow);
RED.view.removeWorkspace(ev.subflow);
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
}
RED.view.dirty(ev.dirty);
RED.view.redraw();

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,33 +15,13 @@
**/
var RED = (function() {
function hideDropTarget() {
$("#dropTarget").hide();
RED.keyboard.remove(/* ESCAPE */ 27);
var deploymentTypes = {
"full":{img:"images/deploy-full-o.png"},
"nodes":{img:"images/deploy-nodes-o.png"},
"flows":{img:"images/deploy-flows-o.png"}
}
$('#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();
});
var deploymentType = "full";
function save(force) {
if (RED.view.dirty()) {
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
@@ -82,7 +62,10 @@ var RED = (function() {
url:"flows",
type: "POST",
data: JSON.stringify(nns),
contentType: "application/json; charset=utf-8"
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) {
@@ -142,12 +125,9 @@ var RED = (function() {
});
function loadSettings() {
$.get('settings', function(data) {
RED.settings = data;
console.log("Node-RED: "+data.version);
loadNodeList();
});
RED.settings.init(loadNodeList);
}
function loadNodeList() {
$.ajax({
headers: {
@@ -205,7 +185,7 @@ var RED = (function() {
var i,m;
var typeList;
var info;
if (topic == "node/added") {
var addedTypes = [];
for (i=0;i<msg.length;i++) {
@@ -245,7 +225,7 @@ var RED = (function() {
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success");
});
}
}
}
} else if (topic == "node/disabled") {
if (msg.types) {
@@ -282,42 +262,117 @@ var RED = (function() {
dialog.modal();
}
function changeDeploymentType(type) {
deploymentType = type;
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
}
$(function() {
function loadEditor() {
RED.menu.init({id:"btn-sidemenu",
options: [
{id:"btn-sidebar",icon:"fa fa-columns",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar},
{id:"btn-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
{id:"btn-node-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"btn-node-status",icon:"fa fa-info",label:"Node Status",toggle:true,onselect:toggleStatus},
null,
{id:"btn-import-menu",icon:"fa fa-sign-in",label:"Import...",options:[
{id:"btn-import-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",onselect:RED.view.showImportNodesDialog},
{id:"btn-import-library",icon:"fa fa-book",label:"Library",options:[]}
{id:"btn-import-menu",label:"Import",options:[
{id:"btn-import-clipboard",label:"Clipboard",onselect:RED.view.showImportNodesDialog},
{id:"btn-import-library",label:"Library",options:[]}
]},
{id:"btn-export-menu",icon:"fa fa-sign-out",label:"Export...",disabled:true,options:[
{id:"btn-export-clipboard",icon:"fa fa-clipboard",label:"Clipboard...",disabled:true,onselect:RED.view.showExportNodesDialog},
{id:"btn-export-library",icon:"fa fa-book",label:"Library...",disabled:true,onselect:RED.view.showExportNodesLibraryDialog}
{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}
]},
null,
{id:"btn-config-nodes",icon:"fa fa-th-list",label:"Configuration nodes...",onselect:RED.sidebar.config.show},
{id:"btn-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show},
null,
{id:"btn-workspace-menu",icon:"fa fa-th-large",label:"Workspaces",options:[
{id:"btn-workspace-add",icon:"fa fa-plus",label:"Add"},
{id:"btn-workspace-edit",icon:"fa fa-pencil",label:"Rename"},
{id:"btn-workspace-delete",icon:"fa fa-minus",label:"Delete"},
{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},
]},
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"},
null
]},
null,
{id:"btn-keyboard-shortcuts",icon:"fa fa-keyboard-o",label:"Keyboard Shortcuts",onselect:showHelp},
{id:"btn-help",icon:"fa fa-question",label:"Help...", href:"http://nodered.org/docs"}
{id:"btn-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect: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.library.init();
RED.palette.init();
RED.sidebar.init();
RED.view.init();
RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();});
loadSettings();
RED.comms.connect();
loadNodeList();
}
$(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = "Node-RED : "+window.location.hostname;
}
RED.settings.init(loadEditor);
});
return {
};
})();

View File

@@ -21,6 +21,7 @@ RED.nodes = (function() {
var links = [];
var defaultWorkspace;
var workspaces = {};
var subflows = {};
var registry = (function() {
var nodeList = [];
@@ -98,13 +99,22 @@ RED.nodes = (function() {
},
registerNodeType: function(nt,def) {
nodeDefinitions[nt] = def;
nodeSets[typeToId[nt]].added = true;
// TODO: too tightly coupled into palette UI
if (def.category != "subflows") {
nodeSets[typeToId[nt]].added = true;
// TODO: too tightly coupled into palette UI
}
RED.palette.add(nt,def);
if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
def.onpaletteadd.call(def);
}
},
removeNodeType: function(nt) {
if (nt.substring(0,8) != "subflow:") {
throw new Error("this api is subflow only. called with:",nt);
}
delete nodeDefinitions[nt];
RED.palette.remove(nt);
},
getNodeType: function(nt) {
return nodeDefinitions[nt];
}
@@ -122,7 +132,6 @@ RED.nodes = (function() {
RED.sidebar.config.refresh();
} else {
n.dirty = true;
nodes.push(n);
var updatedConfigNode = false;
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
@@ -142,6 +151,14 @@ RED.nodes = (function() {
if (updatedConfigNode) {
RED.sidebar.config.refresh();
}
if (n._def.category == "subflows" && typeof n.i === "undefined") {
var nextId = 0;
RED.nodes.eachNode(function(node) {
nextId = Math.max(nextId,node.i||0);
});
n.i = nextId+1;
}
nodes.push(n);
}
}
function addLink(l) {
@@ -174,27 +191,27 @@ RED.nodes = (function() {
if (node) {
nodes.splice(nodes.indexOf(node),1);
removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
removedLinks.map(function(l) {links.splice(links.indexOf(l), 1); });
}
var updatedConfigNode = false;
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
var property = node._def.defaults[d];
if (property.type) {
var type = registry.getNodeType(property.type);
if (type && type.category == "config") {
var configNode = configNodes[node[d]];
if (configNode) {
updatedConfigNode = true;
var users = configNode.users;
users.splice(users.indexOf(node),1);
removedLinks.forEach(function(l) {links.splice(links.indexOf(l), 1); });
var updatedConfigNode = false;
for (var d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d)) {
var property = node._def.defaults[d];
if (property.type) {
var type = registry.getNodeType(property.type);
if (type && type.category == "config") {
var configNode = configNodes[node[d]];
if (configNode) {
updatedConfigNode = true;
var users = configNode.users;
users.splice(users.indexOf(node),1);
}
}
}
}
}
}
if (updatedConfigNode) {
RED.sidebar.config.refresh();
if (updatedConfigNode) {
RED.sidebar.config.refresh();
}
}
}
return removedLinks;
@@ -237,6 +254,50 @@ RED.nodes = (function() {
return {nodes:removedNodes,links:removedLinks};
}
function addSubflow(sf) {
subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
icon:"subflow.png",
category: "subflows",
inputs: sf.in.length,
outputs: sf.out.length,
color: "#da9",
label: function() { return this.name||RED.nodes.subflow(sf.id).name },
labelStyle: function() { return this.name?"node_label_italic":""; },
paletteLabel: function() { return RED.nodes.subflow(sf.id).name }
});
}
function getSubflow(id) {
return subflows[id];
}
function removeSubflow(sf) {
delete subflows[sf.id];
registry.removeNodeType("subflow:"+sf.id);
}
function subflowContains(sfid,nodeid) {
for (var i=0;i<nodes.length;i++) {
var node = nodes[i];
if (node.z === sfid) {
var m = /^subflow:(.+)$/.exec(node.type);
if (m) {
if (m[1] === nodeid) {
return true;
} else {
var result = subflowContains(m[1],nodeid);
if (result) {
return true;
}
}
}
}
}
return false;
}
function getAllFlowNodes(node) {
var visited = {};
visited[node.id] = true;
@@ -247,8 +308,12 @@ RED.nodes = (function() {
var childLinks = links.filter(function(d) { return (d.source === n) || (d.target === n);});
for (var i=0;i<childLinks.length;i++) {
var child = (childLinks[i].source === n)?childLinks[i].target:childLinks[i].source;
if (!visited[child.id]) {
visited[child.id] = true;
var id = child.id;
if (!id) {
id = child.direction+":"+child.i;
}
if (!visited[id]) {
visited[id] = true;
nns.push(child);
stack.push(child);
}
@@ -265,19 +330,36 @@ RED.nodes = (function() {
var node = {};
node.id = n.id;
node.type = n.type;
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
node[d] = n[d];
if (node.type == "unknown") {
for (var p in n._orig) {
if (n._orig.hasOwnProperty(p)) {
node[p] = n._orig[p];
}
}
}
if(exportCreds && n.credentials) {
node.credentials = {};
for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) {
if (n.credentials[cred] != null) {
node.credentials[cred] = n.credentials[cred];
} else {
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
node[d] = n[d];
}
}
if(exportCreds && n.credentials) {
var credentialSet = {};
node.credentials = {};
for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) {
if (n._def.credentials[cred].type == 'password') {
if (n.credentials["has_"+cred] != n.credentials._["has_"+cred] ||
(n.credentials["has_"+cred] && n.credentials[cred])) {
credentialSet[cred] = n.credentials[cred];
}
} else if (n.credentials[cred] != null && n.credentials[cred] != n.credentials._[cred]) {
credentialSet[cred] = n.credentials[cred];
}
}
}
if (Object.keys(credentialSet).length > 0) {
node.credentials = credentialSet;
}
}
}
if (n._def.category != "config") {
@@ -291,37 +373,94 @@ RED.nodes = (function() {
var wires = links.filter(function(d){return d.source === n;});
for (var j=0;j<wires.length;j++) {
var w = wires[j];
node.wires[w.sourcePort].push(w.target.id);
if (w.target.type != "subflow") {
node.wires[w.sourcePort].push(w.target.id);
}
}
}
return node;
}
function convertSubflow(n) {
var node = {};
node.id = n.id;
node.type = n.type;
node.name = n.name;
node.in = [];
node.out = [];
n.in.forEach(function(p) {
var nIn = {x:p.x,y:p.y,wires:[]};
var wires = links.filter(function(d) { return d.source === p });
for (var i=0;i<wires.length;i++) {
var w = wires[i];
if (w.target.type != "subflow") {
nIn.wires.push({id:w.target.id})
}
}
node.in.push(nIn);
});
n.out.forEach(function(p,c) {
var nOut = {x:p.x,y:p.y,wires:[]};
var wires = links.filter(function(d) { return d.target === p });
for (i=0;i<wires.length;i++) {
if (wires[i].source.type != "subflow") {
nOut.wires.push({id:wires[i].source.id,port:wires[i].sourcePort})
} else {
nOut.wires.push({id:n.id,port:0})
}
}
node.out.push(nOut);
});
return node;
}
/**
* Converts the current node selection to an exportable JSON Object
**/
function createExportableNodeSet(set) {
var nns = [];
var exportedConfigNodes = {};
var exportedSubflows = {};
for (var n=0;n<set.length;n++) {
var node = set[n].n;
var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) {
if (node._def.defaults[d].type && node[d] in configNodes) {
var confNode = configNodes[node[d]];
var exportable = registry.getNodeType(node._def.defaults[d].type).exportable;
if ((exportable == null || exportable)) {
if (!(node[d] in exportedConfigNodes)) {
exportedConfigNodes[node[d]] = true;
nns.unshift(RED.nodes.convertNode(confNode));
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}];
RED.nodes.eachNode(function(n) {
if (n.z == subflowId) {
subflowSet.push({n:n});
}
} else {
convertedNode[d] = "";
}
});
var exportableSubflow = createExportableNodeSet(subflowSet);
nns = exportableSubflow.concat(nns);
}
}
nns.push(convertedNode);
if (node.type != "subflow") {
var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) {
if (node._def.defaults[d].type && node[d] in configNodes) {
var confNode = configNodes[node[d]];
var exportable = registry.getNodeType(node._def.defaults[d].type).exportable;
if ((exportable == null || exportable)) {
if (!(node[d] in exportedConfigNodes)) {
exportedConfigNodes[node[d]] = true;
nns.unshift(RED.nodes.convertNode(confNode));
}
} else {
convertedNode[d] = "";
}
}
}
nns.push(convertedNode);
} else {
var convertedSubflow = convertSubflow(node);
nns.push(convertedSubflow);
}
}
return nns;
}
@@ -332,7 +471,14 @@ RED.nodes = (function() {
var i;
for (i in workspaces) {
if (workspaces.hasOwnProperty(i)) {
nns.push(workspaces[i]);
if (workspaces[i].type == "tab") {
nns.push(workspaces[i]);
}
}
}
for (i in subflows) {
if (subflows.hasOwnProperty(i)) {
nns.push(convertSubflow(subflows[i]));
}
}
for (i in configNodes) {
@@ -368,18 +514,21 @@ RED.nodes = (function() {
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
if (n.type != "workspace" && n.type != "tab" && !registry.getNodeType(n.type)) {
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)
n.name = n.type;
n.type = "unknown";
if (unknownTypes.indexOf(n.name)==-1) {
unknownTypes.push(n.name);
}
if (n.x == null && n.y == null) {
// config node - remove it
newNodes.splice(i,1);
i--;
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) {
@@ -389,9 +538,35 @@ RED.nodes = (function() {
//"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;
}
}
}
}
var new_workspaces = [];
var workspace_map = {};
var new_subflows = [];
var subflow_map = {};
var nid;
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
@@ -403,13 +578,36 @@ RED.nodes = (function() {
defaultWorkspace = n;
}
if (createNewIds) {
var nid = getID();
nid = getID();
workspace_map[n.id] = nid;
n.id = nid;
}
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) {
@@ -417,6 +615,7 @@ RED.nodes = (function() {
addWorkspace(defaultWorkspace);
RED.view.addWorkspace(defaultWorkspace);
new_workspaces.push(defaultWorkspace);
activeWorkspace = RED.view.getWorkspace();
}
var node_map = {};
@@ -426,7 +625,7 @@ RED.nodes = (function() {
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" && n.type !== "subflow") {
var def = registry.getNodeType(n.type);
if (def && def.category == "config") {
if (!RED.nodes.node(n.id)) {
@@ -443,40 +642,77 @@ RED.nodes = (function() {
} else {
var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
if (createNewIds) {
node.z = workspace_map[node.z];
if (!workspaces[node.z]) {
node.z = RED.view.getWorkspace();
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();
} else {
node.id = n.id;
if (node.z == null || !workspaces[node.z]) {
node.z = RED.view.getWorkspace();
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
node.z = activeWorkspace;
}
}
node.type = n.type;
node._def = def;
if (!node._def) {
node._def = {
color:"#fee",
defaults: {},
label: "unknown: "+n.type,
labelStyle: "node_label_italic",
outputs: n.outputs||n.wires.length
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.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;
new_nodes.push(node);
if (node._def.category != "config") {
new_nodes.push(node);
}
}
}
}
@@ -494,10 +730,38 @@ RED.nodes = (function() {
}
delete n.wires;
}
return [new_nodes,new_links,new_workspaces];
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) {
//TODO: get this UI thing out of here! (see above as well)
RED.notify("<strong>Error</strong>: "+error,"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;
}
@@ -516,13 +780,22 @@ RED.nodes = (function() {
registerType: registry.registerNodeType,
getType: registry.getNodeType,
convertNode: convertNode,
add: addNode,
addLink: addLink,
remove: removeNode,
addLink: addLink,
removeLink: removeLink,
addWorkspace: addWorkspace,
removeWorkspace: removeWorkspace,
workspace: getWorkspace,
addSubflow: addSubflow,
removeSubflow: removeSubflow,
subflow: getSubflow,
subflowContains: subflowContains,
eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) {
cb(nodes[n]);
@@ -540,6 +813,13 @@ RED.nodes = (function() {
}
}
},
eachSubflow: function(cb) {
for (var id in subflows) {
if (subflows.hasOwnProperty(id)) {
cb(subflows[id]);
}
}
},
node: getNode,
import: importNodes,
refreshValidation: refreshValidation,

122
public/red/settings.js Normal file
View File

@@ -0,0 +1,122 @@
/**
* Copyright 2014 IBM, Antoine Aflalo
*
* 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.settings = (function () {
var loadedSettings = {};
var hasLocalStorage = function () {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
};
var set = function (key, value) {
if (!hasLocalStorage()) {
return;
}
localStorage.setItem(key, JSON.stringify(value));
};
/**
* If the key is not set in the localStorage it returns <i>undefined</i>
* Else return the JSON parsed value
* @param key
* @returns {*}
*/
var get = function (key) {
if (!hasLocalStorage()) {
return undefined;
}
return JSON.parse(localStorage.getItem(key));
};
var remove = function (key) {
if (!hasLocalStorage()) {
return;
}
localStorage.removeItem(key);
};
var setProperties = function(data) {
for (var prop in loadedSettings) {
if (loadedSettings.hasOwnProperty(prop) && RED.settings.hasOwnProperty(prop)) {
delete RED.settings[prop];
}
}
for (prop in data) {
if (data.hasOwnProperty(prop)) {
RED.settings[prop] = data[prop];
}
}
loadedSettings = data;
};
var init = function (done) {
$.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);
}
}
}
});
load(done);
}
var load = function(done) {
$.ajax({
headers: {
"Accept": "application/json"
},
dataType: "json",
cache: false,
url: 'settings',
success: function (data) {
setProperties(data);
if (RED.settings.user && RED.settings.user.anonymous) {
RED.settings.remove("auth-tokens");
}
console.log("Node-RED: " + data.version);
done();
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status === 401) {
RED.user.login(function() { load(done); });
} else {
console.log("Unexpected error:",jqXHR.status,textStatus);
}
}
});
};
return {
init: init,
load: load,
set: set,
get: get,
remove: remove
}
})
();

View File

@@ -95,22 +95,31 @@ RED.editor = (function() {
node.resize = true;
node.dirty = true;
var removedLinks = [];
if (node.outputs < node.ports.length) {
while (node.outputs < node.ports.length) {
node.ports.pop();
if (node.ports) {
if (node.outputs < node.ports.length) {
while (node.outputs < node.ports.length) {
node.ports.pop();
}
RED.nodes.eachLink(function(l) {
if (l.source === node && l.sourcePort >= node.outputs) {
removedLinks.push(l);
}
});
} else if (node.outputs > node.ports.length) {
while (node.outputs > node.ports.length) {
node.ports.push(node.ports.length);
}
}
}
if (node.inputs === 0) {
RED.nodes.eachLink(function(l) {
if (l.source === node && l.sourcePort >= node.outputs) {
removedLinks.push(l);
}
if (l.target === node) {
removedLinks.push(l);
}
});
for (var l=0;l<removedLinks.length;l++) {
RED.nodes.removeLink(removedLinks[l]);
}
} else if (node.outputs > node.ports.length) {
while (node.outputs > node.ports.length) {
node.ports.push(node.ports.length);
}
}
for (var l=0;l<removedLinks.length;l++) {
RED.nodes.removeLink(removedLinks[l]);
}
return removedLinks;
}
@@ -227,7 +236,12 @@ RED.editor = (function() {
//TODO: move this to RED.library
var flowName = $("#node-input-filename").val();
if (!/^\s*$/.test(flowName)) {
$.post('library/flows/'+flowName,$("#node-input-filename").attr('nodes'),function() {
$.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");
});
@@ -243,6 +257,11 @@ RED.editor = (function() {
id: "node-dialog-cancel",
text: "Cancel",
click: function() {
if (editing_node._def) {
if (editing_node._def.oneditcancel) {
editing_node._def.oneditcancel.call(editing_node);
}
}
$( this ).dialog( "close" );
}
}
@@ -253,6 +272,7 @@ RED.editor = (function() {
}
},
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
if (editing_node) {
var size = $(this).dialog('option','sizeCache-'+editing_node.type);
@@ -274,6 +294,11 @@ RED.editor = (function() {
RED.sidebar.info.refresh(editing_node);
}
RED.sidebar.config.refresh();
var buttons = $( this ).dialog("option","buttons");
if (buttons.length == 3) {
$( this ).dialog("option","buttons",buttons.splice(1));
}
editing_node = null;
}
});
@@ -459,10 +484,30 @@ RED.editor = (function() {
function showEditDialog(node) {
editing_node = node;
RED.view.state(RED.state.EDITING);
$("#dialog-form").html($("script[data-template-name='"+node.type+"']").html());
var type = node.type;
if (node.type.substring(0,8) == "subflow:") {
type = "subflow";
var id = editing_node.type.substring(8);
var buttons = $( "#dialog" ).dialog("option","buttons");
buttons.unshift({
class: 'leftButton',
text: "Edit flow",
click: function() {
RED.view.showSubflow(id);
$("#node-dialog-ok").click();
}
});
$( "#dialog" ).dialog("option","buttons",buttons);
}
$("#dialog-form").html($("script[data-template-name='"+type+"']").html());
$('<input type="text" style="display: none;" />').appendTo("#dialog-form");
prepareEditDialog(node,node._def,"node-input");
$( "#dialog" ).dialog("option","title","Edit "+node.type+" node").dialog( "open" );
$( "#dialog" ).dialog("option","title","Edit "+type+" node").dialog( "open" );
}
function showEditConfigNodeDialog(name,type,id) {
@@ -508,6 +553,9 @@ RED.editor = (function() {
if (configTypeDef.ondelete) {
configTypeDef.ondelete.call(RED.nodes.node(configId));
}
if (configTypeDef.oneditdelete) {
configTypeDef.oneditdelete.call(RED.nodes.node(configId));
}
RED.nodes.remove(configId);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
@@ -641,7 +689,8 @@ RED.editor = (function() {
],
resize: function(e,ui) {
},
open: function(e) {
open: function(e,ui) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.disable();
}
@@ -655,10 +704,106 @@ RED.editor = (function() {
}
});
$( "#subflow-dialog" ).dialog({
modal: true,
autoOpen: false,
closeOnEscape: false,
width: 500,
buttons: [
{
id: "subflow-dialog-ok",
text: "Ok",
click: function() {
if (editing_node) {
var i;
var changes = {};
var changed = false;
var wasDirty = RED.view.dirty();
var newName = $("#subflow-input-name").val();
if (newName != editing_node.name) {
changes['name'] = editing_node.name;
editing_node.name = newName;
changed = true;
$("#btn-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName);
}
RED.palette.refresh();
if (changed) {
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
n.changed = true;
updateNodeProperties(n);
}
});
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.view.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.view.redraw();
}
$( this ).dialog( "close" );
}
},
{
id: "subflow-dialog-cancel",
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
editing_node = null;
}
}
],
open: function(e,ui) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
RED.sidebar.info.refresh(editing_node);
editing_node = null;
}
});
function showEditSubflowDialog(subflow) {
editing_node = subflow;
RED.view.state(RED.state.EDITING);
$("#subflow-input-name").val(subflow.name);
var userCount = 0;
var subflowType = "subflow:"+editing_node.id;
RED.nodes.eachNode(function(n) {
if (n.type === subflowType) {
userCount++;
}
});
$("#subflow-dialog-user-count").html("There "+(userCount==1?"is":"are")+" "+userCount+" instance"+(userCount==1?" ":"s")+" of this subflow").show();
$("#subflow-dialog").dialog("option","title","Edit flow "+subflow.name).dialog( "open" );
}
return {
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties // TODO: only exposed for edit-undo
}

View File

@@ -24,20 +24,21 @@ RED.keyboard = (function() {
if (handler && handler.ondown) {
if (!handler.modifiers ||
((!handler.modifiers.shift || d3.event.shiftKey) &&
(!handler.modifiers.ctrl || d3.event.ctrlKey ) &&
(!handler.modifiers.alt || d3.event.altKey ) )) {
(!handler.modifiers.ctrl || d3.event.ctrlKey || d3.event.metaKey) &&
(!handler.modifiers.alt || d3.event.altKey) )) {
handler.ondown();
}
}
});
d3.select(window).on("keyup",function() {
if (!active) { return; }
var handler = handlers[d3.event.keyCode];
if (handler && handler.onup) {
if (!handler.modifiers ||
((!handler.modifiers.shift || d3.event.shiftKey) &&
(!handler.modifiers.ctrl || d3.event.ctrlKey ) &&
(!handler.modifiers.alt || d3.event.altKey ) )) {
(!handler.modifiers.ctrl || d3.event.ctrlKey || d3.event.metaKey) &&
(!handler.modifiers.alt || d3.event.altKey) )) {
handler.onup();
}
}
@@ -46,7 +47,6 @@ RED.keyboard = (function() {
var mod = modifiers;
var cbdown = ondown;
var cbup = onup;
if (typeof modifiers == "function") {
mod = {};
cbdown = modifiers;

View File

@@ -66,9 +66,6 @@ RED.library = (function() {
$("#btn-import-library-submenu").replaceWith(menu);
});
}
loadFlowLibrary();
function createUI(options) {
var libraryData = {};
@@ -360,6 +357,9 @@ RED.library = (function() {
}
return {
init: function() {
loadFlowLibrary();
},
create: createUI,
loadFlowLibrary: loadFlowLibrary
}

View File

@@ -13,45 +13,105 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.menu = (function() {
var menuItems = {};
function createMenuItem(opt) {
var item;
function setState() {
var savedStateActive = isSavedStateActive(opt.id);
if (savedStateActive) {
link.addClass("active");
opt.onselect.call(opt, true);
} else if (savedStateActive === false) {
link.removeClass("active");
opt.onselect.call(opt, false);
} else if (opt.hasOwnProperty("selected")) {
if (opt.selected) {
link.addClass("active");
} else {
link.removeClass("active");
}
opt.onselect.call(opt, opt.selected);
}
}
if (opt === null) {
item = $('<li class="divider"></li>');
} else {
item = $('<li></li>');
var link = $('<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">'+
(opt.toggle?'<i class="fa fa-check pull-right"></i>':'')+
(opt.icon?'<i class="'+opt.icon+'"></i> ':'')+
opt.label+
'</a>').appendTo(item);
menuItems[opt.id] = opt;
var linkContent = '<a '+(opt.id?'id="'+opt.id+'" ':'')+'tabindex="-1" href="#">';
if (opt.toggle) {
linkContent += '<i class="fa fa-square pull-left"></i>';
linkContent += '<i class="fa fa-check-square pull-left"></i>';
}
if (opt.icon !== undefined) {
if (/\.png/.test(opt.icon)) {
linkContent += '<img src="'+opt.icon+'"/> ';
} else {
linkContent += '<i class="'+(opt.icon?opt.icon:'" style="display: inline-block;"')+'"></i> ';
}
}
if (opt.sublabel) {
linkContent += '<span class="menu-label-container"><span class="menu-label">'+opt.label+'</span>'+
'<span class="menu-sublabel">'+opt.sublabel+'</span></span>'
} else {
linkContent += '<span class="menu-label">'+opt.label+'</span>'
}
linkContent += '</a>';
var link = $(linkContent).appendTo(item);
menuItems[opt.id] = opt;
if (opt.onselect) {
link.click(function() {
if ($(this).parent().hasClass("disabled")) {
return;
}
if (opt.toggle) {
setSelected(opt.id,!isSelected(opt.id));
if ($(this).parent().hasClass("disabled")) {
return;
}
if (opt.toggle) {
var selected = isSelected(opt.id);
if (typeof opt.toggle === "string") {
if (!selected) {
for (var m in menuItems) {
if (menuItems.hasOwnProperty(m)) {
var mi = menuItems[m];
if (mi.id != opt.id && opt.toggle == mi.toggle) {
setSelected(mi.id,false);
}
}
}
setSelected(opt.id,true);
}
} else {
opt.onselect.call(opt);
setSelected(opt.id, !selected);
}
})
} else {
opt.onselect.call(opt);
}
});
setState();
} else if (opt.href) {
link.attr("target","_blank").attr("href",opt.href);
} else if (!opt.options) {
item.addClass("disabled");
link.click(function(event) {
event.preventDefault();
});
}
if (opt.options) {
item.addClass("dropdown-submenu pull-left");
var submenu = $('<ul id="'+opt.id+'-submenu" class="dropdown-menu"></ul>').appendTo(item);
for (var i=0;i<opt.options.length;i++) {
createMenuItem(opt.options[i]).appendTo(submenu);
}
@@ -59,27 +119,53 @@ RED.menu = (function() {
if (opt.disabled) {
item.addClass("disabled");
}
if (opt.tip) {
item.popover({
placement:"left",
trigger: "hover",
delay: { show: 350, hide: 20 },
html: true,
container:'body',
content: opt.tip
});
}
}
return item;
}
function createMenu(options) {
var button = $("#"+options.id);
//button.click(function(event) {
// $("#"+options.id+"-submenu").show();
// event.preventDefault();
//});
var topMenu = $("<ul/>",{class:"dropdown-menu"}).insertAfter(button);
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
for (var i=0;i<options.options.length;i++) {
var opt = options.options[i];
createMenuItem(opt).appendTo(topMenu);
}
}
function isSelected(id) {
return $("#"+id).hasClass("active");
function isSavedStateActive(id) {
return RED.settings.get("menu-" + id);
}
function isSelected(id) {
return $("#" + id).hasClass("active");
}
function setSavedState(id, state) {
RED.settings.set("menu-" + id, state);
}
function setSelected(id,state) {
if (isSelected(id) == state) {
return;
@@ -93,8 +179,9 @@ RED.menu = (function() {
if (opt.onselect) {
opt.onselect.call(opt,state);
}
setSavedState(id, state);
}
function setDisabled(id,state) {
if (state) {
$("#"+id).parent().addClass("disabled");
@@ -102,21 +189,36 @@ RED.menu = (function() {
$("#"+id).parent().removeClass("disabled");
}
}
function addItem(id,opt) {
createMenuItem(opt).appendTo("#"+id+"-submenu");
}
function removeItem(id) {
$("#"+id).parent().remove();
}
function setAction(id,action) {
menuItems[id].onselect = action;
$("#"+id).click(function() {
if ($(this).parent().hasClass("disabled")) {
return;
}
if (menuItems[id].toggle) {
setSelected(id,!isSelected(id));
} else {
menuItems[id].onselect.call(menuItems[id]);
}
});
}
return {
init: createMenu,
setSelected: setSelected,
isSelected: isSelected,
setDisabled: setDisabled,
addItem: addItem,
removeItem: removeItem
removeItem: removeItem,
setAction: setAction
//TODO: add an api for replacing a submenu - see library.js:loadFlowLibrary
}
})();

View File

@@ -17,12 +17,12 @@
RED.palette = (function() {
var exclusion = ['config','unknown','deprecated'];
var core = ['input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
var core = ['input', 'output', 'function', 'subflows', 'social', 'storage', 'analysis', 'advanced'];
function createCategoryContainer(category){
var escapedCategory = category.replace(" ","_");
$("#palette-container").append('<div class="palette-category">'+
'<div id="header-'+category+'" class="palette-header"><i class="expanded fa fa-caret-down"></i><span>'+category.replace("_"," ")+'</span></div>'+
var catDiv = $("#palette-container").append('<div id="palette-container-'+category+'" class="palette-category hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-caret-down"></i><span>'+category.replace("_"," ")+'</span></div>'+
'<div class="palette-content" id="palette-base-category-'+category+'">'+
'<div id="palette-'+category+'-input"></div>'+
'<div id="palette-'+category+'-output"></div>'+
@@ -30,18 +30,76 @@ RED.palette = (function() {
'</div>'+
'</div>');
$("#header-"+category).on('click', function(e) {
$("#palette-header-"+category).on('click', function(e) {
$(this).next().slideToggle();
$(this).children("i").toggleClass("expanded");
});
}
core.forEach(createCategoryContainer);
function setLabel(type, el,label) {
var nodeWidth = 80;
var nodeHeight = 25;
var lineHeight = 20;
var portHeight = 10;
var words = label.split(" ");
var displayLines = [];
var currentLine = words[0];
var currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
for (var i=1;i<words.length;i++) {
var newWidth = RED.view.calculateTextWidth(currentLine+" "+words[i], "palette_label", 0);
if (newWidth < nodeWidth) {
currentLine += " "+words[i];
currentLineWidth = newWidth;
} else {
displayLines.push(currentLine);
currentLine = words[i];
currentLineWidth = RED.view.calculateTextWidth(currentLine, "palette_label", 0);
}
}
displayLines.push(currentLine);
var lines = displayLines.join("<br/>");
var multiLineNodeHeight = 8+(lineHeight*displayLines.length);
el.css({height:multiLineNodeHeight+"px"});
var labelElement = el.find(".palette_label");
labelElement.html(lines);
el.find(".palette_port").css({top:(multiLineNodeHeight/2-5)+"px"});
var popOverContent;
try {
var l = "<p><b>"+label+"</b></p>";
if (label != type) {
l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>";
}
popOverContent = $(l+($("script[data-help-name|='"+type+"']").html()||"<p>no information available</p>").trim())
.filter(function(n) {
return this.nodeType == 1 || (this.nodeType == 3 && this.textContent.trim().length > 0)
}).slice(0,2);
} catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break
console.log("Error generating pop-over label for '"+type+"'.");
console.log(err.toString());
popOverContent = "<p><b>"+label+"</b></p><p>no information available</p>";
}
el.data('popover').options.content = popOverContent;
}
function escapeNodeType(nt) {
return nt.replace(" ","_").replace(".","_").replace(":","_");
}
function addNodeType(nt,def) {
var nodeTypeId = nt.replace(" ","_");
var nodeTypeId = escapeNodeType(nt);
if ($("#palette_node_"+nodeTypeId).length) {
return;
}
@@ -55,45 +113,20 @@ RED.palette = (function() {
d.id = "palette_node_"+nodeTypeId;
d.type = nt;
// calculate width of label text
$.fn.textWidth = function(text, font) {
if (!$.fn.textWidth.fakeEl) {
$.fn.textWidth.fakeEl = $('<span>').hide().appendTo(document.body);
}
$.fn.textWidth.fakeEl.text(text || this.val() || this.text()).css('font', font || this.css('font'));
return $.fn.textWidth.fakeEl.width();
};
var label;
if (typeof def.paletteLabel === "undefined") {
label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
} else {
} else {
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
}
var pixels = $.fn.textWidth(label, '13px helvetica');
var nodeWidth = 90;
var labelWidth = nodeWidth - 10;
var numLines = Math.ceil(pixels / nodeWidth);
var multiLine = numLines > 1;
// styles matching with style.css
var nodeHeight = 25;
var lineHeight = 16;
var portHeight = 10;
var multiLineNodeHeight = lineHeight * numLines + (nodeHeight - lineHeight);
d.innerHTML = '<div class="palette_label"></div>';
d.innerHTML = '<div class="palette_label"'+
(multiLine ? 'style="line-height: '+
lineHeight + 'px; margin-top: 5px"' : '')+
'>'+label+"</div>";
d.className="palette_node";
if (def.icon) {
d.style.backgroundImage = "url(icons/"+def.icon+")";
if (multiLine) {
d.style.backgroundSize = "18px 27px";
}
d.style.backgroundSize = "18px 27px";
if (def.align == "right") {
d.style.backgroundPosition = "95% 50%";
} else if (def.inputs > 0) {
@@ -102,29 +135,23 @@ RED.palette = (function() {
}
d.style.backgroundColor = def.color;
d.style.height = multiLineNodeHeight + "px";
if (def.outputs > 0) {
var portOut = document.createElement("div");
portOut.className = "palette_port palette_port_output";
if (multiLine) {
portOut.style.top = ((multiLineNodeHeight - portHeight) / 2) + "px";
}
d.appendChild(portOut);
}
if (def.inputs > 0) {
var portIn = document.createElement("div");
portIn.className = "palette_port";
if (multiLine) {
portIn.style.top = ((multiLineNodeHeight - portHeight) / 2) + "px";
}
portIn.className = "palette_port palette_port_input";
d.appendChild(portIn);
}
if ($("#palette-base-category-"+rootCategory).length === 0) {
createCategoryContainer(rootCategory);
}
$("#palette-container-"+rootCategory).show();
if ($("#palette-"+category).length === 0) {
$("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
@@ -133,23 +160,13 @@ RED.palette = (function() {
$("#palette-"+category).append(d);
d.onmousedown = function(e) { e.preventDefault(); };
var popOverContent;
try {
popOverContent = $("<p><b>"+label+"</b></p>"+($("script[data-help-name|='"+nt+"']").html().trim()||"<p>no information available</p>")).slice(0,2);
} catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break
console.log("Error generating pop-over label for '"+nt+"'.");
console.log(err.toString());
popOverContent = "<p><b>"+label+"</b></p><p>no information available</p>";
}
$(d).popover({
title:d.type,
placement:"right",
trigger: "hover",
delay: { show: 750, hide: 50 },
html: true,
container:'body',
content: popOverContent
container:'body'
});
$(d).click(function() {
var help = '<div class="node-help">'+($("script[data-help-name|='"+d.type+"']").html()||"")+"</div>";
@@ -161,23 +178,50 @@ RED.palette = (function() {
revert: true,
revertDuration: 50
});
setLabel(nt,$(d),label);
}
}
function removeNodeType(nt) {
var nodeTypeId = nt.replace(" ","_");
var nodeTypeId = escapeNodeType(nt);
$("#palette_node_"+nodeTypeId).remove();
}
function hideNodeType(nt) {
var nodeTypeId = nt.replace(" ","_");
var nodeTypeId = escapeNodeType(nt);
$("#palette_node_"+nodeTypeId).hide();
}
function showNodeType(nt) {
var nodeTypeId = nt.replace(" ","_");
var nodeTypeId = escapeNodeType(nt);
$("#palette_node_"+nodeTypeId).show();
}
function refreshNodeTypes() {
RED.nodes.eachSubflow(function(sf) {
var paletteNode = $("#palette_node_subflow_"+sf.id.replace(".","_"));
var portInput = paletteNode.find(".palette_port_input");
var portOutput = paletteNode.find(".palette_port_output");
if (portInput.length === 0 && sf.in.length > 0) {
var portIn = document.createElement("div");
portIn.className = "palette_port palette_port_input";
paletteNode.append(portIn);
} else if (portInput.length !== 0 && sf.in.length === 0) {
portInput.remove();
}
if (portOutput.length === 0 && sf.out.length > 0) {
var portOut = document.createElement("div");
portOut.className = "palette_port palette_port_output";
paletteNode.append(portOut);
} else if (portOutput.length !== 0 && sf.out.length === 0) {
portOutput.remove();
}
setLabel(sf.type+":"+sf.id,paletteNode,sf.name);
});
}
function filterChange() {
var val = $("#palette-search-input").val();
if (val === "") {
@@ -186,7 +230,7 @@ RED.palette = (function() {
$("#palette-search-clear").show();
}
var re = new RegExp(val);
var re = new RegExp(val,'i');
$(".palette_node").each(function(i,el) {
if (val === "" || re.test(el.id)) {
$(this).show();
@@ -196,35 +240,46 @@ RED.palette = (function() {
});
}
$("#palette-search-input").focus(function(e) {
RED.keyboard.disable();
});
$("#palette-search-input").blur(function(e) {
RED.keyboard.enable();
});
$("#palette-search-clear").on("click",function(e) {
e.preventDefault();
$("#palette-search-input").val("");
filterChange();
$("#palette-search-input").focus();
});
$("#palette-search-input").val("");
$("#palette-search-input").on("keyup",function() {
filterChange();
});
$("#palette-search-input").on("focus",function() {
$("body").one("mousedown",function() {
$("#palette-search-input").blur();
function init() {
$(".palette-spinner").show();
if (RED.settings.paletteCategories) {
RED.settings.paletteCategories.forEach(createCategoryContainer);
} else {
core.forEach(createCategoryContainer);
}
$("#palette-search-input").focus(function(e) {
RED.keyboard.disable();
});
});
$("#palette-search-input").blur(function(e) {
RED.keyboard.enable();
});
$("#palette-search-clear").on("click",function(e) {
e.preventDefault();
$("#palette-search-input").val("");
filterChange();
$("#palette-search-input").focus();
});
$("#palette-search-input").val("");
$("#palette-search-input").on("keyup",function() {
filterChange();
});
$("#palette-search-input").on("focus",function() {
$("body").one("mousedown",function() {
$("#palette-search-input").blur();
});
});
}
return {
init: init,
add:addNodeType,
remove:removeNodeType,
hide:hideNodeType,
show:showNodeType
show:showNodeType,
refresh:refreshNodeTypes
};
})();

View File

@@ -26,6 +26,7 @@ RED.sidebar = (function() {
$("#"+tab.id).remove();
}
});
function addTab(title,content,closeable) {
$("#sidebar-content").append(content);
$(content).hide();
@@ -124,26 +125,28 @@ RED.sidebar = (function() {
$("#main-container").addClass("sidebar-closed");
} else {
$("#main-container").removeClass("sidebar-closed");
sidebar_tabs.resize();
}
}
function showSidebar(id) {
RED.menu.setSelected("btn-sidebar",true);
sidebar_tabs.activateTab("tab-"+id);
if (id) {
sidebar_tabs.activateTab("tab-"+id);
}
}
function containsTab(id) {
return sidebar_tabs.contains("tab-"+id);
}
$(function() {
function init () {
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("btn-sidebar",!RED.menu.isSelected("btn-sidebar"));d3.event.preventDefault();});
showSidebar("info");
});
showSidebar();
RED.sidebar.info.show();
}
return {
init: init,
addTab: addTab,
removeTab: removeTab,
show: showSidebar,

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.
@@ -14,15 +14,31 @@
* limitations under the License.
**/
RED.sidebar.info = (function() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var content = document.createElement("div");
content.id = "tab-info";
content.style.paddingTop = "4px";
content.style.paddingLeft = "4px";
content.style.paddingRight = "4px";
RED.sidebar.addTab("info",content);
function show() {
if (!RED.sidebar.containsTab("info")) {
RED.sidebar.addTab("info",content,false);
}
RED.sidebar.show("info");
}
function jsonFilter(key,value) {
if (key === "") {
return value;
@@ -39,49 +55,86 @@ RED.sidebar.info = (function() {
}
return value;
}
function refresh(node) {
var table = '<table class="node-info"><tbody>';
table += '<tr class="blank"><td colspan="2">Node</td></tr>';
table += "<tr><td>Type</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>ID</td><td>&nbsp;"+node.id+"</td></tr>";
table += '<tr class="blank"><td colspan="2">Properties</td></tr>';
for (var n in node._def.defaults) {
if (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)+" ...";
}
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
} else if (type === "number") {
val = val.toString();
} else if ($.isArray(val)) {
val = "[<br/>";
for (var i=0;i<Math.min(node[n].length,10);i++) {
var vv = JSON.stringify(node[n][i],jsonFilter," ").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
val += "&nbsp;"+i+": "+vv+"<br/>";
}
if (node[n].length > 10) {
val += "&nbsp;... "+node[n].length+" items<br/>";
}
val += "]";
} else {
val = JSON.stringify(val,jsonFilter," ");
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
var m = /^subflow(:(.+))?$/.exec(node.type);
if (m) {
var subflowNode;
if (m[2]) {
subflowNode = RED.nodes.subflow(m[2]);
} else {
subflowNode = node;
}
table += '<tr class="blank"><td colspan="2">Subflow</td></tr>';
var userCount = 0;
var subflowType = "subflow:"+subflowNode.id;
RED.nodes.eachNode(function(n) {
if (n.type === subflowType) {
userCount++;
}
});
table += "<tr><td>name</td><td>"+subflowNode.name+"</td></tr>";
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 (node._def) {
for (var n in node._def.defaults) {
if (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)+" ...";
}
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
} else if (type === "number") {
val = val.toString();
} else if ($.isArray(val)) {
val = "[<br/>";
for (var i=0;i<Math.min(node[n].length,10);i++) {
var vv = JSON.stringify(node[n][i],jsonFilter," ").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
val += "&nbsp;"+i+": "+vv+"<br/>";
}
if (node[n].length > 10) {
val += "&nbsp;... "+node[n].length+" items<br/>";
}
val += "]";
} else {
val = JSON.stringify(val,jsonFilter," ");
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
}
table += "<tr><td>"+n+"</td><td>"+val+"</td></tr>";
}
}
table += "<tr><td>&nbsp;"+n+"</td><td>"+val+"</td></tr>";
}
}
table += "</tbody></table><br/>";
table += '<div class="node-help">'+($("script[data-help-name|='"+node.type+"']").html()||"")+"</div>";
if (node.type != "comment") {
var helpText = $("script[data-help-name|='"+node.type+"']").html()||"";
table += '<div class="node-help">'+helpText+"</div>";
}
if (node._def && node._def.info) {
var info = node._def.info;
table += '<div class="node-help">'+marked(typeof info === "function" ? info.call(node) : info)+'</div>';
//table += '<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
}
$("#tab-info").html(table);
}
return {
show: show,
refresh:refresh,
clear: function() {
$("#tab-info").html("");

View File

@@ -116,6 +116,13 @@ RED.tabs = (function() {
},
contains: function(id) {
return ul.find("a[href='#"+id+"']").length > 0;
},
renameTab: function(id,label) {
tabs[id].label = label;
var tab = ul.find("a[href='#"+id+"']");
tab.attr("title",label);
tab.text(label);
updateTabWidths();
}
}

File diff suppressed because it is too large Load Diff

117
public/red/user.js Normal file
View File

@@ -0,0 +1,117 @@
/**
* Copyright 2014 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.user = (function() {
function login(opts,done) {
if (typeof opts == 'function') {
done = opts;
opts = {};
}
var dialog = $('<div id="node-dialog-login" class="hide">'+
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img src="node-red-256.png"/></div>'+
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
'<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+
'</div>'+
'</div>');
dialog.dialog({
autoOpen: false,
dialogClass: "ui-dialog-no-close",
modal: true,
closeOnEscape: false,
width: 600,
resizable: false,
draggable: false
});
$("#node-dialog-login-fields").empty();
$.ajax({
dataType: "json",
url: "auth/login",
success: function(data) {
if (data.type == "credentials") {
var i=0;
for (;i<data.prompts.length;i++) {
var field = data.prompts[i];
var row = $("<div/>",{class:"form-row"});
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
$('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
row.appendTo("#node-dialog-login-fields");
}
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">Login failed</span><img src="spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">Cancel</a>':'')+
'<a href="#" id="node-dialog-login-submit" tabIndex="'+(i+2)+'">Login</a></div>').appendTo("#node-dialog-login-fields");
$("#node-dialog-login-submit").button().click(function( event ) {
$("#node-dialog-login-submit").button("option","disabled",true);
$("#node-dialog-login-failed").hide();
$(".login-spinner").show();
var body = {
client_id: "node-red-editor",
grant_type: "password",
scope:"*"
}
for (var i=0;i<data.prompts.length;i++) {
var field = data.prompts[i];
body[field.id] = $("#node-dialog-login-"+field.id).val();
}
$.ajax({
url:"auth/token",
type: "POST",
data: body
}).done(function(data,textStatus,xhr) {
RED.settings.set("auth-tokens",data);
$("#node-dialog-login").dialog('destroy').remove();
done();
}).fail(function(jqXHR,textStatus,errorThrown) {
RED.settings.remove("auth-tokens");
$("#node-dialog-login-failed").show();
}).always(function() {
$("#node-dialog-login-submit").button("option","disabled",false);
$(".login-spinner").hide();
});
event.preventDefault();
});
if (opts.cancelable) {
$("#node-dialog-login-cancel").button().click(function( event ) {
$("#node-dialog-login").dialog('destroy').remove();
});
}
}
dialog.dialog("open");
}
});
}
function logout() {
$.ajax({
url: "auth/revoke",
type: "POST",
data: {token:RED.settings.get("auth-tokens").access_token},
success: function() {
RED.settings.remove("auth-tokens");
document.location.reload(true);
}
})
}
return {
login: login,
logout: logout
}
})();

Some files were not shown because too many files have changed in this diff Show More