Compare commits

..

160 Commits

Author SHA1 Message Date
Nick O'Leary
859a7538e1 Fix crash on repeated inject of invalid json payload 2016-02-26 10:35:15 +00:00
Dave Conway-Jones
0d1543ee8a Add tail node binary mode test 2016-02-25 08:52:43 +00:00
Dave Conway-Jones
d3a98dd355 Add binary mode to tail node 2016-02-24 23:06:27 +00:00
Dave Conway-Jones
ad10125303 revert Cheerio to somewhat smaller version 2016-02-23 09:47:49 +00:00
Dave Conway-Jones
b89e866d39 Add os/platform info to default debug 2016-02-22 17:47:16 +00:00
Dave Conway-Jones
afbaf1cfe0 make sample node info consistent <code> 2016-02-22 17:46:14 +00:00
Dave Conway-Jones
b3be8b30e7 remove direction flag from delay node 2016-02-22 17:45:28 +00:00
Nick O'Leary
c6ad2c9ad2 Don't force reconnect mqtt client if message arrives
Fixes the annoying mqtt connect/disconnect cycle
2016-02-19 22:52:43 +00:00
Nick O'Leary
3b44d9972e Bump package version 2016-02-19 21:21:22 +00:00
Nick O'Leary
af736c98f2 Add -p/--port option to override listening port 2016-02-19 21:18:50 +00:00
Nick O'Leary
f1377fa217 Invert toggle button colours so state is more obvious 2016-02-18 10:28:22 +00:00
Dave Conway-Jones
2ba146b9ff Add timeout to httprequest node
and override 2 min default in settings.js.
to Close #801
2016-02-15 09:45:58 +00:00
Dave Conway-Jones
2361607aa3 file node info to same style as others 2016-02-14 13:45:14 +00:00
Nick O'Leary
86ffc80098 Tidy up spinner css 2016-02-14 11:52:33 +00:00
Dave Conway-Jones
7f6915eb59 tcp node add reply (to all) capability
if no _session present.
2016-02-12 13:17:50 +00:00
Dave Conway-Jones
d69bcad028 hardware, logic, storage nodes info updates
(and udp)
2016-02-12 13:17:21 +00:00
Dave Conway-Jones
4cb45e2712 parser node info updates 2016-02-12 13:16:28 +00:00
Dave Conway-Jones
b7a0ad703a io and analysis nodes info updates 2016-02-12 13:15:53 +00:00
Dave Conway-Jones
7610b9a975 core nodes info updates 2016-02-12 13:15:05 +00:00
Dave Conway-Jones
7d95f621df update UDP node info to be more correct. 2016-02-11 22:21:12 +00:00
Nick O'Leary
bba210e112 Allow the template node to be treated as plain text 2016-02-11 13:16:15 +00:00
Nick O'Leary
3a97e20bde Validate MQTT In topics
Fixes #792
2016-02-10 22:38:59 +00:00
Nick O'Leary
4fe7ea00b0 httpNodeAuth should not block http options requests
Fixes #793#793#793
2016-02-10 21:57:46 +00:00
Nick O'Leary
3ec8ecd4de Disable perMessageDeflate on WS servers
Workaround for this issue: https://github.com/websockets/ws/pull/632
as it has been fixed in the 1.x release that drops support for
node 0.10...
2016-02-10 21:43:37 +00:00
Dave Conway-Jones
401e65e852 Merge pull request #799 from natcl/patch-1
Fix typo in delay node: replaced ramdom to random
Thanks @natcl
2016-02-09 23:09:10 +00:00
Nathanaël Lécaudé
e7c5b691a0 More ramdom --> random 2016-02-09 18:03:27 -05:00
Nathanaël Lécaudé
9f3ea8da67 Fix typo in delay node: replaced ramdom to random 2016-02-09 17:57:50 -05:00
Dave Conway-Jones
4d84d624b1 clear trigger status icon on re-deploy 2016-02-04 22:13:08 +00:00
Nick O'Leary
633a6a0ee6 Fix inject test to use a proper type 2016-02-04 21:52:27 +00:00
Nick O'Leary
c7bcd3f438 Don't default inject payload to blank string 2016-02-04 21:43:18 +00:00
Dave Conway-Jones
d3a29a6f16 fix trigger tests 2016-02-04 21:22:52 +00:00
Dave Conway-Jones
827711ca89 Fix util jshint as we need the behaviour. 2016-02-04 21:21:57 +00:00
Dave Conway-Jones
76e98f74fa let new typed-inputs return correctly
0 now returns correct type for boolean and number when required.
2016-02-04 21:06:20 +00:00
Dave Conway-Jones
fb09f4b22d trigger node, add configurable reset
and make it do strings when it says so, and numbers if you want.
2016-02-04 21:05:15 +00:00
Dave Conway-Jones
bb06585748 another tidy up on Pi GPIO node 2016-02-03 21:10:44 +00:00
Nick O'Leary
c76ba1dcc7 Allow function properties in settings
Fixes #790
2016-01-29 11:56:16 +00:00
Nick O'Leary
a115301b04 Fix order of config dialog calls to save/creds/validate 2016-01-29 11:56:16 +00:00
Dave Conway-Jones
72917117a9 Add debounce to Pi GPIO node 2016-01-25 09:56:35 +00:00
Nick O'Leary
6567739236 Bump version 2016-01-21 22:20:18 +00:00
Nick O'Leary
4aa6b47c0e Revert wrapping of http requestion object
Fixes #787
2016-01-21 22:15:25 +00:00
Nick O'Leary
03558b012c Bump version and dependencies 2016-01-18 11:09:52 +00:00
Nick O'Leary
3288efdad6 Remove unimplemented flow.enable/disable functions 2016-01-18 10:53:50 +00:00
Dave Conway-Jones
3902a343f3 Add ports in use warning to udp node
to close #786
Thanks @hugobox
2016-01-17 10:34:40 +00:00
Dave Conway-Jones
882b7d0391 change settings.js example to octalbonescript 2016-01-16 13:58:24 +00:00
Nick O'Leary
81f082825d Add 'previous value' option to Switch node 2016-01-15 11:35:59 +00:00
Nick O'Leary
392fd6fed3 Allow existing nodes to splice into links on drag 2016-01-14 15:59:45 +00:00
Nick O'Leary
51afed4041 Ensure config list refreshes properly on tab delete 2016-01-14 15:22:00 +00:00
Nick O'Leary
17e3b71d9c Allow update of global flow 2016-01-14 14:57:13 +00:00
Nick O'Leary
6e75089f3a CORS not properly configured on multiple http routes
Fixes #783
2016-01-13 12:54:34 +00:00
Nick O'Leary
6dc640b129 Add hidden count when config node filtered 2016-01-13 10:30:24 +00:00
Nick O'Leary
27cbaac343 Restore shift-drag to snap/unsnap to grid 2016-01-13 09:16:24 +00:00
Nick O'Leary
fa4006619e Make debug/config sidebar headers consistent 2016-01-12 23:55:18 +00:00
Nick O'Leary
cb8fe8462a Moving nodes with keyboard should flag workspace dirty 2016-01-12 23:08:13 +00:00
Nick O'Leary
abd51a5511 Notifications flagged as fixed should not be click-closable 2016-01-12 23:06:18 +00:00
Nick O'Leary
a0cc1e6b0c Add config node filter 2016-01-12 23:03:33 +00:00
Nick O'Leary
50399c6bfa Rework config sidebar and deploy warning 2016-01-12 17:54:53 +00:00
Nick O'Leary
de48c1be44 Wrap http request object to match http response object 2016-01-11 22:35:31 +00:00
Nick O'Leary
0786ec4b66 Move typedInput icons and update boolean 2016-01-11 21:24:35 +00:00
Nick O'Leary
db319e0ebc Ensure global context is seeded properly 2016-01-11 11:28:01 +00:00
Nick O'Leary
4fc568856a Clear link_splice style on drag end 2016-01-11 11:00:54 +00:00
Nick O'Leary
4c6771669b Fix palette node link splicing on Firefox 2016-01-10 22:25:20 +00:00
Nick O'Leary
9bca2a91c9 Tidy up view menu 2016-01-10 21:25:05 +00:00
Nick O'Leary
66eaaf5a48 Add 'view' menu and reorganise a few things 2016-01-09 20:39:03 +00:00
Nick O'Leary
9837f0e2e1 Highlight node port when dragging wires and undash the wires 2016-01-09 13:47:05 +00:00
Nick O'Leary
6b8ffb4c68 Fix lint issues in view 2016-01-09 00:31:05 +00:00
Nick O'Leary
f35dd34da9 Allow shift-click to detach existing wires 2016-01-09 00:29:04 +00:00
Nick O'Leary
ed19e4fa08 Splice nodes dragged from palette into links 2016-01-08 22:34:10 +00:00
Dave Conway-Jones
661e1a4f90 try to trim imported/dragged flows to [ ] 2016-01-08 19:54:16 +00:00
Nick O'Leary
5826de76ca Make dragging nodes from the palette line up better 2016-01-08 14:42:05 +00:00
Nick O'Leary
05888740e5 Update jquery version to 0.11.3 2016-01-08 14:41:37 +00:00
Nick O'Leary
41f3b0c333 Fix variable leak in theme.js 2016-01-08 13:41:33 +00:00
Nick O'Leary
70f3e72a20 Move version number as title of NR logo 2016-01-08 13:36:49 +00:00
Nick O'Leary
e873afd40b Moving nodes mark workspace as dirty 2016-01-08 11:08:48 +00:00
Nick O'Leary
2777c2937a Make version number slightly darker 2016-01-07 22:29:20 +00:00
Nick O'Leary
798903e4cc Move layout menu down one position 2016-01-07 22:28:03 +00:00
Nick O'Leary
58622ba18f Attach dialog close handlers to dialog parents 2016-01-07 20:08:31 +00:00
Nick O'Leary
c368dcd5b7 Ok/Cancel edit dialogs with Ctrl-Enter/Escape 2016-01-07 17:10:59 +00:00
Nick O'Leary
0b4c652ce7 Add version number of sidebar footer 2016-01-07 16:42:10 +00:00
Nick O'Leary
dbaacc411a Handle OSX Meta key when selecting nodes 2016-01-07 15:09:14 +00:00
Nick O'Leary
1850185d1e Add grid-alignment options 2016-01-07 14:39:01 +00:00
Nick O'Leary
2e9d445d36 Add oneditresize function definition 2016-01-06 17:01:14 +00:00
Nick O'Leary
aed89d82fb Fix template test 2016-01-06 17:01:14 +00:00
Nick O'Leary
231adac6d8 Rename typedInput.options 2016-01-06 17:01:14 +00:00
Nick O'Leary
587c4e5915 Update template node to use typedInput 2016-01-06 17:01:14 +00:00
Nick O'Leary
55f1cbf18f Ensure inject payload exists 2016-01-06 17:01:13 +00:00
Nick O'Leary
38168a545b Update Inject node to use typedInput 2016-01-06 17:01:13 +00:00
Nick O'Leary
43c6df49d7 Update typedInput nls 2016-01-06 17:01:13 +00:00
Nick O'Leary
f1c59faf72 Rename propertySelect to typedInput and add boolean opt 2016-01-06 17:01:13 +00:00
Nick O'Leary
5f7019325c Update switch/change help text to reflect updates 2016-01-06 17:01:13 +00:00
Nick O'Leary
fe4dae8518 Add propertySelect to switch node 2016-01-06 17:01:13 +00:00
Nick O'Leary
1f848b205b Add propertySelect support to Change node 2016-01-06 17:01:13 +00:00
Nick O'Leary
742c470d81 Add context/flow/global support to Function node 2016-01-06 17:01:13 +00:00
Nick O'Leary
5ead3342cc Add node context/flow/global 2016-01-06 17:01:13 +00:00
Nick O'Leary
b95dc2ecce Add propertySelect jquery widget 2016-01-06 17:01:13 +00:00
Nick O'Leary
4d0950215f Don't allow tabs or subflows to be added with new flow 2016-01-06 17:01:13 +00:00
Nick O'Leary
da0ce9fe0d Simplify flow api implementation and add logging messages 2016-01-06 17:01:13 +00:00
Nick O'Leary
ca62e720b5 Add missing spec file 2016-01-06 17:01:13 +00:00
Nick O'Leary
c4b1795396 Add add/update/delete flow apis 2016-01-06 17:01:13 +00:00
Nick O'Leary
fd2e47ed73 WIP: add flow api 2016-01-06 17:01:12 +00:00
Nick O'Leary
d5f2255a68 Handle null coreNodesPath 2016-01-06 17:01:12 +00:00
Nick O'Leary
05b58e9263 Allow core nodes dir to be provided to runtime via settings 2016-01-06 17:01:12 +00:00
Nick O'Leary
4a91c27e4b Allow server to be option on red.init 2016-01-06 17:01:12 +00:00
Nick O'Leary
3a03d46d8d Fix lint error in registry.js 2016-01-06 17:01:12 +00:00
Nick O'Leary
f03aff7006 Tidy up API passed to node modules 2016-01-06 17:01:12 +00:00
Nick O'Leary
043b8a3105 Register node message catalog directly, not via event 2016-01-06 17:01:12 +00:00
Nick O'Leary
1dd9984521 Pickup default language from i18n module 2016-01-06 17:01:12 +00:00
Nick O'Leary
d2be7f8c8f Move locale files under api/runtime components 2016-01-06 17:01:12 +00:00
Nick O'Leary
88dc202db2 Fix node test helper for api/runtime changes 2016-01-06 17:01:12 +00:00
Nick O'Leary
083d54b008 Add unit test for flow reload api 2016-01-06 17:01:11 +00:00
Nick O'Leary
87d77efa57 Add flow reload admin api 2016-01-06 17:01:11 +00:00
Nick O'Leary
35c4a41d7b Node id generation should only be done in runtime/util 2016-01-06 17:01:11 +00:00
Nick O'Leary
1ca3ca07d5 api/nodes accessing comms module incorrectly 2016-01-06 17:01:11 +00:00
Nick O'Leary
d673846e3d WIP: runtime api for node modules 2016-01-06 17:01:11 +00:00
Nick O'Leary
f62b7afede Remove all uses of fs.exists as it is deprecated
The tests still use it in places - particular localfilesystem tests,
but those tests need to be redone with sinon stubbing in place and
not rely on real fs operations.
2016-01-06 17:01:11 +00:00
Nick O'Leary
e65770a53a Add missing test resources
They were ignored as they have node_modules in the path...
2016-01-06 17:01:11 +00:00
Nick O'Leary
a92a741932 Fix incorrect async test completion 2016-01-06 17:01:11 +00:00
Nick O'Leary
45f67191ba Improve node registry test coverage 2016-01-06 17:01:11 +00:00
Nick O'Leary
93f5da325b Fix node test helper for runtime/api changes 2016-01-06 17:01:11 +00:00
Nick O'Leary
8fb955e182 Move comms from runtime to api component 2016-01-06 17:01:11 +00:00
Nick O'Leary
9f5e6a4b37 Update tests for runtime/api separation 2016-01-06 17:01:11 +00:00
Nick O'Leary
f43738446e WIP: separate runtime and api components 2016-01-06 17:01:11 +00:00
Nick O'Leary
923a46d304 Bump version 0.12.5 2016-01-06 16:55:38 +00:00
Dave Conway-Jones
b9b5eaccae better handle utf8 file output chars 2016-01-06 12:27:47 +00:00
Dave Conway-Jones
cda11491c2 bump sentiment node npm prereq 2016-01-06 12:27:47 +00:00
Nick O'Leary
98c539f662 Refresh active nodes when node properties change 2016-01-04 22:05:17 +00:00
Dave Conway-Jones
9fb958b302 close tcp node connection properly when required. 2015-12-23 20:01:05 +00:00
Dave Conway-Jones
8e25e76439 Add hint re servos to Pi GPIO node info 2015-12-23 11:50:47 +00:00
Nick O'Leary
62694da7e6 Ensure last mqtt node turns off the lights before closing
The mqtt-broker node disconnects when the last node using it
is closed. But that node-close was not waiting for the disconnect
to complete. This led to a race-condition where the using node
was recreated and started trying to use the broker node whilst it
was still disconnecting.
2015-12-22 23:31:22 +00:00
Dave Conway-Jones
86064651af Add Pi Keyboard code node 2015-12-21 10:27:58 +00:00
Dave Conway-Jones
65daaeb617 add attribute test to HTML parser node tests 2015-12-19 14:30:43 +00:00
Dave Conway-Jones
08b39f50b3 Add attribute capability to HTML parser node 2015-12-19 12:44:11 +00:00
Nick O'Leary
d0f7e5ca4d Bump version 0.12.4 2015-12-14 10:01:10 +00:00
Nick O'Leary
4eb5058e68 Add readOnly setting to prevent file writes in localfilesystem storage 2015-12-13 22:45:44 +00:00
Nick O'Leary
1054193298 Update example httpNodeAuth setting to be bcrypt 2015-12-13 21:27:57 +00:00
Nick O'Leary
38c6cf0450 Support bcrypt for httpNodeAuth 2015-12-13 20:46:27 +00:00
Dave Conway-Jones
5b04b86867 remove extraneous s from GPIO node... 2015-12-12 15:18:17 +00:00
Dave Conway-Jones
a074bcfd56 Pi no longer needs root workaround to access gpio
(stops PAM logging in Node-RED log under systemd)
2015-12-12 15:13:15 +00:00
Nick O'Leary
f93179d946 Rename library filename field to avoid id clash
Fixed #767
2015-12-12 12:57:33 +00:00
Nick O'Leary
2c347bc092 Bump version for 0.12.3 2015-12-11 22:02:37 +00:00
Dave Conway-Jones
0f7119f468 TCPget don't send nun msg on disconnect
(as we now send status anyway)
2015-12-11 14:17:50 +00:00
Dave Conway-Jones
2685a24705 Let TCPget node pass through other msg properties 2015-12-11 14:07:20 +00:00
Nick O'Leary
371f72f4f1 Skip delay node burst test 2015-12-11 14:04:24 +00:00
Nick O'Leary
c70c00043b Attached admin route before node route security 2015-12-11 13:42:44 +00:00
Nick O'Leary
50d0a88276 Ensure tabs are removed from runtime on partial deploy 2015-12-10 15:47:15 +00:00
Dave Conway-Jones
5bbf576dae set fa-icons perms 644 not 755... 2015-12-10 13:57:54 +00:00
Nick O'Leary
5d334e9619 Clarify auth settings in default settings.js 2015-12-10 13:20:58 +00:00
Dave Conway-Jones
98f9353338 bump serial port package dependency version. 2015-12-10 13:11:14 +00:00
Nick O'Leary
d3de7037e5 Move HTTPRequest node to its own file 2015-12-10 12:58:50 +00:00
Nick O'Leary
64431c6711 Ensure node.ports is properly intialised
Fixes #766
2015-12-10 10:46:12 +00:00
Dave Conway-Jones
d4ce193dc8 Fix trigger to block properly until reset
Fix to Close #764
2015-12-09 16:42:16 +00:00
Dave Conway-Jones
606305aec4 Bump FA-Icons to v4.5 2015-12-09 15:38:37 +00:00
Dave Conway-Jones
a95f44d68b remove annoying comma from comm.js
OCD reasons only
2015-12-09 13:37:20 +00:00
Dave Conway-Jones
ef2dc4b9e1 One more tidy up for tcp node 2015-12-07 22:39:42 +00:00
Dave Conway-Jones
9baca1772b Close tcp port for tcpin node (same as previous fix but for input) 2015-12-07 22:39:42 +00:00
Nick O'Leary
04cd19349d Don't reuse node-edit dialog for library export ui
Fixes #762
2015-12-07 22:15:14 +00:00
Dave Conway-Jones
1280e5bc8b Close tcp out node more forcibly. (and update status) on redeploy. 2015-12-07 17:41:51 +00:00
Dave Conway-Jones
dda90f956d Clear delay node status on re-deploy. (rate limit path) 2015-12-02 15:37:36 +00:00
Dave Conway-Jones
bc4b599513 Fix udp socket creation error on node v0.10 2015-12-01 14:52:15 +00:00
Dave Conway-Jones
090d52d678 narrowing in on tcpget fix, reconnect but don't resend.
to address issue #759
2015-12-01 13:41:39 +00:00
Dave Conway-Jones
a47ad4842a Clean up tcpget node connected status.
to address #759
2015-11-30 22:05:26 +00:00
217 changed files with 6909 additions and 4012 deletions

View File

@@ -124,14 +124,15 @@ module.exports = function(grunt) {
"editor/js/ui/library.js",
"editor/js/ui/notifications.js",
"editor/js/ui/subflow.js",
"editor/js/ui/touch/radialMenu.js"
"editor/js/ui/touch/radialMenu.js",
"editor/js/ui/typedInput.js"
],
dest: "public/red/red.js"
},
vendor: {
files: {
"public/vendor/vendor.js": [
"editor/vendor/jquery/js/jquery-1.11.1.min.js",
"editor/vendor/jquery/js/jquery-1.11.3.min.js",
"editor/vendor/bootstrap/js/bootstrap.min.js",
"editor/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js",
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js",
@@ -174,8 +175,8 @@ module.exports = function(grunt) {
messages: {
src: [
'nodes/core/locales/en-US/messages.json',
'locales/en-US/editor.json',
'locales/en-US/runtime.json'
'red/api/locales/en-US/editor.json',
'red/runtime/locales/en-US/runtime.json'
]
}
},
@@ -223,8 +224,8 @@ module.exports = function(grunt) {
json: {
files: [
'nodes/core/locales/en-US/messages.json',
'locales/en-US/editor.json',
'locales/en-US/runtime.json'
'red/api/locales/en-US/editor.json',
'red/runtime/locales/en-US/runtime.json'
],
tasks: ['jsonlint:messages']
}
@@ -238,7 +239,7 @@ module.exports = function(grunt) {
args: nodemonArgs,
ext: 'js,html,json',
watch: [
'red','nodes','locales'
'red','nodes'
]
}
}
@@ -302,8 +303,7 @@ module.exports = function(grunt) {
'red/**',
'public/**',
'editor/templates/**',
'bin/**',
'locales/**'
'bin/**'
],
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
}]

View File

@@ -66,4 +66,4 @@ For more open-source projects from IBM, head over [here](http://ibm.github.io).
## Copyright and license
Copyright 2013, 2015 IBM Corp. under [the Apache 2.0 license](LICENSE).
Copyright 2013, 2016 IBM Corp. under [the Apache 2.0 license](LICENSE).

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,6 +80,12 @@ RED.history = (function() {
}
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "delete") {
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
@@ -168,6 +174,17 @@ RED.history = (function() {
n.n.y = n.oy;
n.n.dirty = true;
}
// A move could have caused a link splice
if (ev.links) {
for (i=0;i<ev.links.length;i++) {
RED.nodes.removeLink(ev.links[i]);
}
}
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
}
}
} else if (ev.t == "edit") {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
@@ -265,6 +282,7 @@ RED.history = (function() {
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
}
}
}

View File

@@ -156,11 +156,13 @@ var RED = (function() {
function loadEditor() {
RED.menu.init({id:"btn-sidemenu",
options: [
{id:"menu-item-sidebar-menu",label:RED._("menu.label.sidebar.sidebar"),options:[
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
null
{id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
{id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid},
{id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid},
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}
]},
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
null,
{id:"menu-item-import",label:RED._("menu.label.import"),options:[
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
@@ -171,23 +173,24 @@ var RED = (function() {
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
]},
null,
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
]},
null,
{id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function(){}},
{id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove},
null
]},
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
]},
null,
{id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp},
{id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
}
},
{id:"menu-item-node-red-version", label:"v"+RED.settings.version}
]
});

View File

@@ -157,6 +157,12 @@ RED.nodes = (function() {
if (n._def.category == "config") {
configNodes[n.id] = n;
} else {
n.ports = [];
if (n.outputs) {
for (var i=0;i<n.outputs;i++) {
n.ports.push(i);
}
}
n.dirty = true;
var updatedConfigNode = false;
for (var d in n._def.defaults) {

View File

@@ -16,9 +16,9 @@
RED.settings = (function () {
var loadedSettings = {};
var hasLocalStorage = function () {
try {
return 'localStorage' in window && window['localStorage'] !== null;
@@ -74,7 +74,7 @@ RED.settings = (function () {
RED.settings.set("auth-tokens",{access_token: accessToken});
window.location.search = "";
}
$.ajaxSetup({
beforeSend: function(jqXHR,settings) {
// Only attach auth header for requests to relative paths
@@ -89,7 +89,7 @@ RED.settings = (function () {
load(done);
}
var load = function(done) {
$.ajax({
headers: {
@@ -141,7 +141,7 @@ RED.settings = (function () {
set: set,
get: get,
remove: remove,
theme: theme
}
})

View File

@@ -22,7 +22,7 @@ RED.clipboard = (function() {
var exportNodesDialog;
var importNodesDialog;
function setupDialogs(){
function setupDialogs() {
dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
@@ -35,9 +35,7 @@ RED.clipboard = (function() {
id: "clipboard-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
if (/Import/.test(dialog.dialog("option","title"))) {
RED.view.importNodes($("#clipboard-import").val());
}
RED.view.importNodes($("#clipboard-import").val());
$( this ).dialog( "close" );
}
},
@@ -63,7 +61,7 @@ RED.clipboard = (function() {
close: function(e) {
RED.keyboard.enable();
}
});
});
dialogContainer = dialog.children(".dialog-form");
@@ -85,9 +83,11 @@ RED.clipboard = (function() {
function validateImport() {
var importInput = $("#clipboard-import");
var v = importInput.val();
v = v.substring(v.indexOf('['),v.lastIndexOf(']')+1);
try {
JSON.parse(v);
importInput.removeClass("input-error");
importInput.val(v);
$("#clipboard-dialog-ok").button("enable");
} catch(err) {
if (v !== "") {
@@ -155,8 +155,6 @@ RED.clipboard = (function() {
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
$('#chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
$("#dropTarget").css({display:'table'});
@@ -175,18 +173,13 @@ RED.clipboard = (function() {
.on("drop",function(event) {
var data = event.originalEvent.dataTransfer.getData("text/plain");
hideDropTarget();
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
RED.view.importNodes(data);
event.preventDefault();
});
},
import: importNodes,
export: exportNodes
}
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2015 IBM Corp.
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -208,13 +208,13 @@ RED.deploy = (function() {
.html("<li>"+invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
} else if (hasUnusedConfig && !ignoreDeployWarnings.unusedConfig) {
showWarning = true;
$( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
$( "#node-dialog-confirm-deploy-unused" ).show();
unusedConfigNodes.sort(sortNodeInfo);
$( "#node-dialog-confirm-deploy-unused-list" )
.html("<li>"+unusedConfigNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
// showWarning = true;
// $( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
// $( "#node-dialog-confirm-deploy-unused" ).show();
//
// unusedConfigNodes.sort(sortNodeInfo);
// $( "#node-dialog-confirm-deploy-unused-list" )
// .html("<li>"+unusedConfigNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
}
if (showWarning) {
$( "#node-dialog-confirm-deploy-hide" ).prop("checked",false);
@@ -241,7 +241,13 @@ RED.deploy = (function() {
"Node-RED-Deployment-Type":deploymentType
}
}).done(function(data,textStatus,xhr) {
RED.notify(RED._("deploy.successfulDeploy"),"success");
if (hasUnusedConfig) {
RED.notify(
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
} else {
RED.notify(RED._("deploy.successfulDeploy"),"success");
}
RED.nodes.eachNode(function(node) {
if (node.changed) {
node.dirty = true;

View File

@@ -150,9 +150,9 @@ RED.editor = (function() {
node.ports.pop();
}
RED.nodes.eachLink(function(l) {
if (l.source === node && l.sourcePort >= node.outputs) {
removedLinks.push(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) {
@@ -305,23 +305,7 @@ RED.editor = (function() {
}
editing_node.dirty = true;
validateNode(editing_node);
RED.view.redraw();
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
//TODO: move this to RED.library
var flowName = $("#node-input-filename").val();
if (!/^\s*$/.test(flowName)) {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",
data: $("#node-input-filename").attr('nodes'),
contentType: "application/json; charset=utf-8"
}).done(function() {
RED.library.loadFlowLibrary();
RED.notify(RED._("library.savedNodes"),"success");
}).fail(function(xhr,textStatus,err) {
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
});
}
RED.view.redraw(true);
}
$( this ).dialog( "close" );
}
@@ -364,6 +348,10 @@ RED.editor = (function() {
resize: function(e,ui) {
if (editing_node) {
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
if (editing_node._def.oneditresize) {
var form = $("#dialog-form");
editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
}
}
},
open: function(e) {
@@ -380,6 +368,12 @@ RED.editor = (function() {
$(this).dialog('option','width',size.width);
$(this).dialog('option','height',size.height);
}
if (editing_node._def.oneditresize) {
setTimeout(function() {
var form = $("#dialog-form");
editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
},0);
}
}
},
close: function(e) {
@@ -401,6 +395,12 @@ RED.editor = (function() {
}
editing_node = null;
}
}).parent().on('keydown', function(evt) {
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
$("#node-dialog-cancel").click();
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
$("#node-dialog-ok").click();
}
});
}
@@ -951,20 +951,20 @@ RED.editor = (function() {
RED.nodes.add(editing_config_node);
}
updateConfigNodeSelect(configProperty,configType,editing_config_node.id);
if (configTypeDef.credentials) {
updateNodeCredentials(editing_config_node,configTypeDef.credentials,"node-config-input");
}
if (configTypeDef.oneditsave) {
configTypeDef.oneditsave.call(editing_config_node);
}
if (configTypeDef.credentials) {
updateNodeCredentials(editing_config_node,configTypeDef.credentials,"node-config-input");
}
validateNode(editing_config_node);
for (var i=0;i<editing_config_node.users.length;i++) {
var user = editing_config_node.users[i];
validateNode(user);
}
updateConfigNodeSelect(configProperty,configType,editing_config_node.id);
RED.nodes.dirty(true);
RED.view.redraw(true);
$(this).dialog("close");
@@ -1024,6 +1024,12 @@ RED.editor = (function() {
cancel: '.ui-dialog-content, .ui-dialog-titlebar-close, #node-config-dialog-scope-container'
});
}
}).parent().on('keydown', function(evt) {
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
$("#node-config-dialog-cancel").click();
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
$("#node-config-dialog-ok").click();
}
});
}
@@ -1095,7 +1101,7 @@ RED.editor = (function() {
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.view.redraw();
RED.view.redraw(true);
}
$( this ).dialog( "close" );
}
@@ -1145,6 +1151,12 @@ RED.editor = (function() {
$(".node-text-editor").css("height",height+"px");
subflowEditor.resize();
}
}).parent().on('keydown', function(evt) {
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
$("#subflow-dialog-cancel").click();
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
$("#subflow-dialog-ok").click();
}
});
}

View File

@@ -16,6 +16,8 @@
RED.library = (function() {
var exportToLibraryDialog;
function loadFlowLibrary() {
$.getJSON("library/flows",function(data) {
//console.log(data);
@@ -382,10 +384,8 @@ RED.library = (function() {
function exportFlow() {
//TODO: don't rely on the main dialog
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
$("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
$("#node-input-filename").attr('nodes',JSON.stringify(nns));
$("#dialog").i18n();
$("#dialog").dialog("option","title",RED._("library.exportToLibrary")).dialog( "open" );
$("#node-input-library-filename").attr('nodes',JSON.stringify(nns));
exportToLibraryDialog.dialog( "open" );
}
return {
@@ -405,6 +405,61 @@ RED.library = (function() {
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
loadFlowLibrary();
}
exportToLibraryDialog = $('<div id="library-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
title: RED._("library.exportToLibrary"),
buttons: [
{
id: "library-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
//TODO: move this to RED.library
var flowName = $("#node-input-library-filename").val();
if (!/^\s*$/.test(flowName)) {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",
data: $("#node-input-library-filename").attr('nodes'),
contentType: "application/json; charset=utf-8"
}).done(function() {
RED.library.loadFlowLibrary();
RED.notify(RED._("library.savedNodes"),"success");
}).fail(function(xhr,textStatus,err) {
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
});
}
$( this ).dialog( "close" );
}
},
{
id: "library-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
exportToLibraryDialog.children(".dialog-form").append($(
'<div class="form-row">'+
'<label for="node-input-library-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>'+
'<input type="text" id="node-input-library-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">'+
'<input type="text" style="display: none;" />'+ // Second hidden input to prevent submit on Enter
'</div>'
));
},
create: createUI,
loadFlowLibrary: loadFlowLibrary,

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,6 +49,13 @@ RED.notify = (function() {
};
})();
if (!fixed) {
$(n).click((function() {
var nn = n;
return function() {
nn.close();
window.clearTimeout(nn.timeoutid);
};
})());
n.timeoutid = window.setTimeout(n.close,timeout||3000);
}
currentNotifications.push(n);

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -210,12 +210,83 @@ RED.palette = (function() {
var help = '<div class="node-help">'+helpText+"</div>";
RED.sidebar.info.set(help);
});
var chart = $("#chart");
var chartOffset = chart.offset();
var chartSVG = $("#chart>svg").get(0);
var activeSpliceLink;
var mouseX;
var mouseY;
var spliceTimer;
$(d).draggable({
helper: 'clone',
appendTo: 'body',
revert: true,
revertDuration: 50,
start: function() {RED.view.focus();}
start: function() {RED.view.focus();},
stop: function() { d3.select('.link_splice').classed('link_splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}},
drag: function(e,ui) {
// TODO: this is the margin-left of palette node. Hard coding
// it here makes me sad
ui.position.left += 17.5;
if (def.inputs > 0 && def.outputs > 0) {
mouseX = e.clientX - chartOffset.left+chart.scrollLeft();
mouseY = e.clientY-chartOffset.top +chart.scrollTop();
if (!spliceTimer) {
spliceTimer = setTimeout(function() {
var nodes = [];
var bestDistance = Infinity;
var bestLink = null;
if (chartSVG.getIntersectionList) {
var svgRect = chartSVG.createSVGRect();
svgRect.x = mouseX;
svgRect.y = mouseY;
svgRect.width = 1;
svgRect.height = 1;
nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
mouseX /= RED.view.scale();
mouseY /= RED.view.scale();
} else {
// Firefox doesn't do getIntersectionList and that
// makes us sad
mouseX /= RED.view.scale();
mouseY /= RED.view.scale();
nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
}
for (var i=0;i<nodes.length;i++) {
if (d3.select(nodes[i]).classed('link_background')) {
var length = nodes[i].getTotalLength();
for (var j=0;j<length;j+=10) {
var p = nodes[i].getPointAtLength(j);
var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
if (d2 < 200 && d2 < bestDistance) {
bestDistance = d2;
bestLink = nodes[i];
}
}
}
}
if (activeSpliceLink && activeSpliceLink !== bestLink) {
d3.select(activeSpliceLink.parentNode).classed('link_splice',false);
}
if (bestLink) {
d3.select(bestLink.parentNode).classed('link_splice',true)
} else {
d3.select('.link_splice').classed('link_splice',false);
}
if (activeSpliceLink !== bestLink) {
if (bestLink) {
$(ui.helper).data('splice',d3.select(bestLink).data()[0]);
} else {
$(ui.helper).removeData('splice');
}
}
activeSpliceLink = bestLink;
spliceTimer = null;
},200);
}
}
}
});
var nodeInfo = null;
@@ -292,7 +363,7 @@ RED.palette = (function() {
}
var re = new RegExp(val,'i');
$(".palette_node").each(function(i,el) {
$("#palette-container .palette_node").each(function(i,el) {
var currentLabel = $(el).find(".palette_label").text();
if (val === "" || re.test(el.id) || re.test(currentLabel)) {
$(this).show();

View File

@@ -20,10 +20,14 @@ RED.sidebar = (function() {
id:"sidebar-tabs",
onchange:function(tab) {
$("#sidebar-content").children().hide();
$("#sidebar-footer").children().hide();
if (tab.onchange) {
tab.onchange.call(tab);
}
$(tab.content).show();
if (tab.toolbar) {
$(tab.toolbar).show();
}
},
onremove: function(tab) {
$(tab.content).hide();
@@ -58,10 +62,15 @@ RED.sidebar = (function() {
$("#sidebar-content").append(options.content);
$(options.content).hide();
if (options.toolbar) {
$("#sidebar-footer").append(options.toolbar);
$(options.toolbar).hide();
}
$(options.content).hide();
var id = options.id;
RED.menu.addItem("menu-item-sidebar-menu",{
id:"menu-item-sidebar-menu-"+options.id,
RED.menu.addItem("menu-item-view-menu",{
id:"menu-item-view-menu-"+options.id,
label:options.name,
onselect:function() {
showSidebar(options.id);
@@ -80,7 +89,7 @@ RED.sidebar = (function() {
sidebar_tabs.removeTab(id);
$(knownTabs[id].content).remove();
delete knownTabs[id];
RED.menu.removeItem("menu-item-sidebar-menu-"+id);
RED.menu.removeItem("menu-item-view-menu-"+id);
}
var sidebarSeparator = {};

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 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,24 +19,115 @@ RED.sidebar.config = (function() {
var content = document.createElement("div");
content.className = "sidebar-node-config"
$('<div class="palette-category">'+
'<div class="workspace-config-node-tray-header palette-header"><i class="fa fa-angle-down expanded"></i><span data-i18n="sidebar.config.local"></span></div>'+
'<ul id="workspace-config-node-tray-locals" class="palette-content config-node-list"></ul>'+
'</div>'+
'<div class="palette-category">'+
'<div class="workspace-config-node-tray-header palette-header"><i class="fa fa-angle-down expanded"></i><span data-i18n="sidebar.config.global"></span></div>'+
'<ul id="workspace-config-node-tray-globals" class="palette-content config-node-list"></ul>'+
'</div>').appendTo(content);
$('<div class="button-group sidebar-header">'+
'<a class="sidebar-header-button-toggle selected" id="workspace-config-node-filter-all" href="#"><span data-i18n="sidebar.config.filterAll"></span></a>'+
'<a class="sidebar-header-button-toggle" id="workspace-config-node-filter-unused" href="#"><span data-i18n="sidebar.config.filterUnused"></span></a> '+
'</div>'
).appendTo(content);
var toolbar = $('<div>'+
'<a class="sidebar-footer-button" id="workspace-config-node-collapse-all" href="#"><i class="fa fa-angle-double-up"></i></a> '+
'<a class="sidebar-footer-button" id="workspace-config-node-expand-all" href="#"><i class="fa fa-angle-double-down"></i></a>'+
'</div>');
var globalCategories = $("<div>").appendTo(content);
var flowCategories = $("<div>").appendTo(content);
var subflowCategories = $("<div>").appendTo(content);
var showUnusedOnly = false;
var categories = {};
function getOrCreateCategory(name,parent,label) {
name = name.replace(/\./i,"-");
if (!categories[name]) {
var container = $('<div class="palette-category workspace-config-node-category" id="workspace-config-node-category-'+name+'"></div>').appendTo(parent);
var header = $('<div class="workspace-config-node-tray-header palette-header"><i class="fa fa-angle-down expanded"></i></div>').appendTo(container);
if (label) {
$('<span class="config-node-label"/>').text(label).appendTo(header);
} else {
$('<span class="config-node-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
}
$('<span class="config-node-filter-info"></span>').appendTo(header);
category = $('<ul class="palette-content config-node-list"></ul>').appendTo(container);
container.i18n();
var icon = header.find("i");
var result = {
label: label,
list: category,
size: function() {
return result.list.find("li:not(.config_node_none)").length
},
open: function(snap) {
if (!icon.hasClass("expanded")) {
icon.addClass("expanded");
if (snap) {
result.list.show();
} else {
result.list.slideDown();
}
}
},
close: function(snap) {
if (icon.hasClass("expanded")) {
icon.removeClass("expanded");
if (snap) {
result.list.hide();
} else {
result.list.slideUp();
}
}
},
isOpen: function() {
return icon.hasClass("expanded");
}
};
header.on('click', function(e) {
if (result.isOpen()) {
result.close();
} else {
result.open();
}
});
categories[name] = result;
} else {
if (categories[name].label !== label) {
categories[name].list.parent().find('.config-node-label').text(label);
categories[name].label = label;
}
}
return categories[name];
}
function createConfigNodeList(id,nodes) {
var category = getOrCreateCategory(id.replace(/\./i,"-"))
var list = category.list;
function createConfigNodeList(nodes,list) {
nodes.sort(function(A,B) {
if (A.type < B.type) { return -1;}
if (A.type > B.type) { return 1;}
return 0;
});
if (showUnusedOnly) {
var hiddenCount = nodes.length;
nodes = nodes.filter(function(n) {
return n.users.length === 0;
})
hiddenCount = hiddenCount - nodes.length;
if (hiddenCount > 0) {
list.parent().find('.config-node-filter-info').text(RED._('sidebar.config.filtered',{count:hiddenCount})).show();
} else {
list.parent().find('.config-node-filter-info').hide();
}
} else {
list.parent().find('.config-node-filter-info').hide();
}
list.empty();
if (nodes.length === 0) {
$('<li class="config_node_none" data-i18n="sidebar.config.none">NONE</li>').i18n().appendTo(list);
category.close(true);
} else {
var currentType = "";
nodes.forEach(function(node) {
@@ -86,23 +177,46 @@ RED.sidebar.config = (function() {
RED.view.redraw();
});
});
category.open(true);
}
}
function refreshConfigNodeList() {
var validList = {"global":true};
var localConfigNodes = [];
getOrCreateCategory("global",globalCategories);
RED.nodes.eachWorkspace(function(ws) {
validList[ws.id.replace(/\./g,"-")] = true;
getOrCreateCategory(ws.id,flowCategories,ws.label);
})
RED.nodes.eachSubflow(function(sf) {
validList[sf.id.replace(/\./g,"-")] = true;
getOrCreateCategory(sf.id,subflowCategories,sf.name);
})
$(".workspace-config-node-category").each(function() {
var id = $(this).attr('id').substring("workspace-config-node-category-".length);
if (!validList[id]) {
$(this).remove();
delete categories[id];
}
})
var globalConfigNodes = [];
var configList = {};
RED.nodes.eachConfig(function(cn) {
if (cn.z == RED.workspaces.active()) {
localConfigNodes.push(cn);
if (cn.z) {//} == RED.workspaces.active()) {
configList[cn.z.replace(/\./g,"-")] = configList[cn.z.replace(/\./g,"-")]||[];
configList[cn.z.replace(/\./g,"-")].push(cn);
} else if (!cn.z) {
globalConfigNodes.push(cn);
}
});
createConfigNodeList(localConfigNodes,$("#workspace-config-node-tray-locals"));
createConfigNodeList(globalConfigNodes,$("#workspace-config-node-tray-globals"));
for (var id in validList) {
if (validList.hasOwnProperty(id)) {
createConfigNodeList(id,configList[id]||[]);
}
}
createConfigNodeList('global',globalConfigNodes);
}
function init() {
@@ -111,25 +225,63 @@ RED.sidebar.config = (function() {
label: RED._("sidebar.config.label"),
name: RED._("sidebar.config.name"),
content: content,
toolbar: toolbar,
closeable: true,
visible: false,
onchange: function() { refreshConfigNodeList(); }
});
$(".workspace-config-node-tray-header").on('click', function(e) {
var icon = $(this).find("i");
if (icon.hasClass("expanded")) {
icon.removeClass("expanded");
$(this).next().slideUp();
} else {
icon.addClass("expanded");
$(this).next().slideDown();
}
RED.menu.setAction('menu-item-config-nodes',function() {
RED.sidebar.show('config');
})
$("#workspace-config-node-collapse-all").on("click", function(e) {
e.preventDefault();
for (var cat in categories) {
if (categories.hasOwnProperty(cat)) {
categories[cat].close();
}
}
});
$("#workspace-config-node-expand-all").on("click", function(e) {
e.preventDefault();
for (var cat in categories) {
if (categories.hasOwnProperty(cat)) {
if (categories[cat].size() > 0) {
categories[cat].open();
}
}
}
});
$('#workspace-config-node-filter-all').on("click",function(e) {
e.preventDefault();
if (showUnusedOnly) {
$(this).addClass('selected');
$('#workspace-config-node-filter-unused').removeClass('selected');
showUnusedOnly = !showUnusedOnly;
refreshConfigNodeList();
}
});
$('#workspace-config-node-filter-unused').on("click",function(e) {
e.preventDefault();
if (!showUnusedOnly) {
$(this).addClass('selected');
$('#workspace-config-node-filter-all').removeClass('selected');
showUnusedOnly = !showUnusedOnly;
refreshConfigNodeList();
}
});
}
function show() {
function show(unused) {
if (unused !== undefined) {
if (unused) {
$('#workspace-config-node-filter-unused').click();
} else {
$('#workspace-config-node-filter-all').click();
}
}
refreshConfigNodeList();
RED.sidebar.show("config");
}

306
editor/js/ui/typedInput.js Normal file
View File

@@ -0,0 +1,306 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
(function($) {
var allOptions = {
msg: {value:"msg",label:"msg.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i},
flow: {value:"flow",label:"flow.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i},
global: {value:"global",label:"global.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
json: {value:"json",label:"JSON",icon:"red/images/typedInput/json.png", validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}},
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"}
};
var nlsd = false;
$.widget( "nodered.typedInput", {
_create: function() {
if (!nlsd && RED && RED._) {
for (var i in allOptions) {
if (allOptions.hasOwnProperty(i)) {
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
}
}
}
nlsd = true;
var that = this;
this.disarmClick = false;
this.element.addClass('red-ui-typedInput');
this.uiWidth = this.element.width();
this.uiSelect = this.element
.wrap( "<div>" )
.parent();
["Right","Left"].forEach(function(d) {
var m = that.element.css("margin"+d);
that.uiSelect.css("margin"+d,m);
that.element.css("margin"+d,0);
});
this.uiSelect.addClass("red-ui-typedInput-container");
this.options.types = this.options.types||Object.keys(allOptions);
var hasSubOptions = false;
this.typeMap = {};
this.types = this.options.types.map(function(opt) {
var result;
if (typeof opt === 'string') {
result = allOptions[opt];
} else {
result = opt;
}
that.typeMap[result.value] = result;
if (result.options) {
hasSubOptions = true;
}
return result;
});
if (this.options.typeField) {
this.typeField = $(this.options.typeField).hide();
var t = this.typeField.val();
if (t && this.typeMap[t]) {
this.options.default = t;
}
} else {
this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect);
}
this.selectTrigger = $('<a href="#"><i class="fa fa-sort-desc"></i></a>').prependTo(this.uiSelect);
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
this.element.on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
});
this.element.on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus');
});
this.element.on('change', function() {
that.validate();
})
this.selectTrigger.click(function(event) {
event.preventDefault();
that._showMenu(that.menu,that.selectTrigger);
});
if (hasSubOptions) {
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<a href="#" class="red-ui-typedInput-option-trigger" style="display:inline-block"><i class="fa fa-sort-desc"></i></a>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span></span>').prependTo(this.optionSelectTrigger);
this.optionSelectTrigger.click(function(event) {
event.preventDefault();
if (that.optionMenu) {
that.optionMenu.css({
minWidth:that.optionSelectLabel.width()
});
that._showMenu(that.optionMenu,that.optionSelectLabel)
}
});
}
this.menu = this._createMenu(this.types, function(v) { that.type(v) });
this.type(this.options.default||this.types[0].value);
},
_hideMenu: function(menu) {
$(document).off("mousedown.close-property-select");
menu.hide();
this.element.focus();
},
_createMenu: function(opts,callback) {
var that = this;
var menu = $("<div>").addClass("red-ui-typedInput-options");
opts.forEach(function(opt) {
if (typeof opt === 'string') {
opt = {value:opt,label:opt};
}
var op = $('<a href="#">').attr("value",opt.value).appendTo(menu);
if (opt.label) {
op.text(opt.label);
}
if (opt.icon) {
$('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op);
} else {
op.css({paddingLeft: "18px"});
}
op.click(function(event) {
event.preventDefault();
callback(opt.value);
that._hideMenu(menu);
});
});
menu.css({
display: "none",
});
menu.appendTo(document.body);
return menu;
},
_showMenu: function(menu,relativeTo) {
if (this.disarmClick) {
this.disarmClick = false;
return
}
var that = this;
var pos = relativeTo.offset();
var height = relativeTo.height();
menu.css({
top: (height+pos.top-3)+"px",
left: (2+pos.left)+"px",
});
menu.slideDown(100);
this._delay(function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
$(document).on("mousedown.close-property-select", function(event) {
if(!$(event.target).closest(menu).length) {
that._hideMenu(menu);
}
if ($(event.target).closest(relativeTo).length) {
that.disarmClick = true;
event.preventDefault();
}
})
});
},
_getLabelWidth: function(label) {
var labelWidth = label.width();
if (labelWidth === 0) {
var newTrigger = label.clone();
newTrigger.css({
position:"absolute",
top:0,
left:-1000
}).appendTo(document.body);
labelWidth = newTrigger.width()+4;
newTrigger.remove();
}
return labelWidth;
},
_resize: function() {
if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) {
this.selectTrigger.width(this.uiWidth+5);
} else {
this.selectTrigger.width('auto');
var labelWidth = this._getLabelWidth(this.selectTrigger);
var newWidth = this.uiWidth-labelWidth+4;
this.element.width(newWidth);
if (this.optionSelectTrigger) {
var triggerWidth = this._getLabelWidth(this.optionSelectTrigger);
labelWidth = this._getLabelWidth(this.optionSelectLabel)-4;
this.optionSelectLabel.width(labelWidth+(newWidth-triggerWidth));
}
}
},
_destroy: function() {
this.menu.remove();
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
this._resize();
},
value: function(value) {
if (!arguments.length) {
return this.element.val();
} else {
if (this.typeMap[this.propertyType].options) {
if (this.typeMap[this.propertyType].options.indexOf(value) === -1) {
value = "";
}
this.optionSelectLabel.text(value);
}
this.element.val(value);
this.element.trigger('change');
}
},
type: function(type) {
if (!arguments.length) {
return this.propertyType;
} else {
var opt = this.typeMap[type];
if (opt && this.propertyType !== type) {
this.propertyType = type;
this.typeField.val(type);
this.selectLabel.empty();
if (opt.icon) {
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
} else {
this.selectLabel.text(opt.label);
}
if (opt.options) {
if (this.optionSelectTrigger) {
this.optionSelectTrigger.show();
this.element.hide();
var that = this;
this.optionMenu = this._createMenu(opt.options,function(v){
that.optionSelectLabel.text(v);
that.value(v);
});
var currentVal = this.element.val();
if (opt.options.indexOf(currentVal) !== -1) {
this.optionSelectLabel.text(currentVal);
} else {
this.value(opt.options[0]);
}
}
} else {
if (this.optionMenu) {
this.optionMenu.remove();
this.optionMenu = null;
}
if (this.optionSelectTrigger) {
this.optionSelectTrigger.hide();
}
if (opt.hasValue === false) {
this.element.val("");
this.element.hide();
} else {
this.element.show();
}
this.element.trigger('change');
}
this._resize();
}
}
},
validate: function() {
var result;
var value = this.value();
var type = this.type();
if (this.typeMap[type] && this.typeMap[type].validate) {
var val = this.typeMap[type].validate;
if (typeof val === 'function') {
result = val(value);
} else {
result = val.test(value);
}
} else {
result = true;
}
if (result) {
this.uiSelect.removeClass('input-error');
} else {
this.uiSelect.addClass('input-error');
}
return result;
}
});
})(jQuery);

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,12 @@ RED.view = (function() {
var workspaceScrollPositions = {};
var gridSize = 20;
var snapGrid = false;
var activeSpliceLink;
var spliceActive = false;
var spliceTimer;
var activeSubflow = null;
var activeNodes = [];
@@ -196,40 +202,54 @@ RED.view = (function() {
.attr('height', space_height)
.attr('fill','#fff');
//var gridScale = d3.scale.linear().range([0,2000]).domain([0,2000]);
//var grid = vis.append('g');
//
//grid.selectAll("line.horizontal").data(gridScale.ticks(100)).enter()
// .append("line")
// .attr(
// {
// "class":"horizontal",
// "x1" : 0,
// "x2" : 2000,
// "y1" : function(d){ return gridScale(d);},
// "y2" : function(d){ return gridScale(d);},
// "fill" : "none",
// "shape-rendering" : "crispEdges",
// "stroke" : "#eee",
// "stroke-width" : "1px"
// });
//grid.selectAll("line.vertical").data(gridScale.ticks(100)).enter()
// .append("line")
// .attr(
// {
// "class":"vertical",
// "y1" : 0,
// "y2" : 2000,
// "x1" : function(d){ return gridScale(d);},
// "x2" : function(d){ return gridScale(d);},
// "fill" : "none",
// "shape-rendering" : "crispEdges",
// "stroke" : "#eee",
// "stroke-width" : "1px"
// });
var gridScale = d3.scale.linear().range([0,space_width]).domain([0,space_width]);
var grid = vis.append('g');
grid.selectAll("line.horizontal").data(gridScale.ticks(space_width/gridSize)).enter()
.append("line")
.attr(
{
"class":"horizontal",
"x1" : 0,
"x2" : space_width,
"y1" : function(d){ return gridScale(d);},
"y2" : function(d){ return gridScale(d);},
"fill" : "none",
"shape-rendering" : "crispEdges",
"stroke" : "#eee",
"stroke-width" : "1px"
});
grid.selectAll("line.vertical").data(gridScale.ticks(space_width/gridSize)).enter()
.append("line")
.attr(
{
"class":"vertical",
"y1" : 0,
"y2" : space_width,
"x1" : function(d){ return gridScale(d);},
"x2" : function(d){ return gridScale(d);},
"fill" : "none",
"shape-rendering" : "crispEdges",
"stroke" : "#eee",
"stroke-width" : "1px"
});
grid.style("visibility","hidden");
var drag_line = vis.append("svg:path").attr("class", "drag_line");
var dragGroup = vis.append('g');
var drag_lines = [];
function showDragLines(nodes) {
for (var i=0;i<nodes.length;i++) {
var node = nodes[i];
node.el = dragGroup.append("svg:path").attr("class", "drag_line");
drag_lines.push(node);
}
}
function hideDragLines() {
while(drag_lines.length) {
(drag_lines.pop()).el.remove();
}
}
function updateActiveNodes() {
var activeWorkspace = RED.workspaces.active();
@@ -300,7 +320,6 @@ RED.view = (function() {
drop: function( event, ui ) {
d3.event = event;
var selected_tool = ui.draggable[0].type;
var m = /^subflow:(.+)$/.exec(selected_tool);
if (activeSubflow && m) {
@@ -313,16 +332,9 @@ RED.view = (function() {
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
return;
}
}
var mousePos = d3.touches(this)[0]||d3.mouse(this);
mousePos[1] += this.scrollTop;
mousePos[0] += this.scrollLeft;
mousePos[1] /= scaleFactor;
mousePos[0] /= scaleFactor;
var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:RED.workspaces.active()};
var nn = { id:(1+Math.random()*4294967295).toString(16),z:RED.workspaces.active()};
nn.type = selected_tool;
nn._def = RED.nodes.getType(nn.type);
@@ -347,7 +359,10 @@ RED.view = (function() {
}
nn.changed = true;
nn.w = node_width;
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
var historyEvent = {
t:'add',
nodes:[nn.id],
@@ -364,6 +379,41 @@ RED.view = (function() {
}
}
var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
var mousePos = d3.touches(this)[0]||d3.mouse(this);
mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]);
mousePos[0] += this.scrollLeft + ((nn.w/2)-helperOffset[0]);
mousePos[1] /= scaleFactor;
mousePos[0] /= scaleFactor;
if (snapGrid) {
mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize));
mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize));
}
nn.x = mousePos[0];
nn.y = mousePos[1];
var spliceLink = $(ui.helper).data('splice');
if (spliceLink) {
// TODO: DRY - canvasMouseUp
RED.nodes.removeLink(spliceLink);
var link1 = {
source:spliceLink.source,
sourcePort:spliceLink.sourcePort,
target: nn
};
var link2 = {
source:nn,
sourcePort:0,
target: spliceLink.target
};
RED.nodes.addLink(link1);
RED.nodes.addLink(link2);
historyEvent.links = [link1,link2];
historyEvent.removedLinks = [spliceLink];
}
RED.history.push(historyEvent);
RED.nodes.add(nn);
RED.editor.validateNode(nn);
@@ -420,6 +470,8 @@ RED.view = (function() {
}
function canvasMouseMove() {
var i;
var node;
mouse_position = d3.touches(this)[0]||d3.mouse(this);
// Prevent touch scrolling...
//if (d3.touches(this)[0]) {
@@ -466,36 +518,73 @@ RED.view = (function() {
var mousePos;
if (mouse_mode == RED.state.JOINING) {
// update drag line
drag_line.attr("class", "drag_line");
mousePos = mouse_position;
var numOutputs = (mousedown_port_type === 0)?(mousedown_node.outputs || 1):1;
var sourcePort = mousedown_port_index;
var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
var sc = (mousedown_port_type === 0)?1:-1;
var dy = mousePos[1]-(mousedown_node.y+portY);
var dx = mousePos[0]-(mousedown_node.x+sc*mousedown_node.w/2);
var delta = Math.sqrt(dy*dy+dx*dx);
var scale = lineCurveScale;
var scaleY = 0;
if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width);
}
if (dx*sc < 0) {
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
if (Math.abs(dy) < 3*node_height) {
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
if (drag_lines.length === 0) {
if (d3.event.shiftKey) {
// Get all the wires we need to detach.
var links = [];
var filter;
if (mousedown_port_type === 0) {
filter = {
source:mousedown_node,
sourcePort: mousedown_port_index
}
} else {
filter = {
target: mousedown_node
}
}
var existingLinks = RED.nodes.filterLinks(filter);
for (i=0;i<existingLinks.length;i++) {
var link = existingLinks[i];
RED.nodes.removeLink(link);
links.push({
link:link,
node: (mousedown_port_type===0)?link.target:link.source,
port: (mousedown_port_type===0)?0:link.sourcePort,
portType: (mousedown_port_type===0)?1:0
})
}
showDragLines(links);
mouse_mode = 0;
updateActiveNodes();
redraw();
mouse_mode = RED.state.JOINING;
} else {
showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
}
}
mousePos = mouse_position;
for (i=0;i<drag_lines.length;i++) {
var drag_line = drag_lines[i];
var numOutputs = (drag_line.portType === 0)?(drag_line.node.outputs || 1):1;
var sourcePort = drag_line.port;
var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
drag_line.attr("d",
"M "+(mousedown_node.x+sc*mousedown_node.w/2)+" "+(mousedown_node.y+portY)+
" C "+(mousedown_node.x+sc*(mousedown_node.w/2+node_width*scale))+" "+(mousedown_node.y+portY+scaleY*node_height)+" "+
(mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+
mousePos[0]+" "+mousePos[1]
);
var sc = (drag_line.portType === 0)?1:-1;
var dy = mousePos[1]-(drag_line.node.y+portY);
var dx = mousePos[0]-(drag_line.node.x+sc*drag_line.node.w/2);
var delta = Math.sqrt(dy*dy+dx*dx);
var scale = lineCurveScale;
var scaleY = 0;
if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width);
}
if (dx*sc < 0) {
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
if (Math.abs(dy) < 3*node_height) {
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
}
}
drag_line.el.attr("d",
"M "+(drag_line.node.x+sc*drag_line.node.w/2)+" "+(drag_line.node.y+portY)+
" C "+(drag_line.node.x+sc*(drag_line.node.w/2+node_width*scale))+" "+(drag_line.node.y+portY+scaleY*node_height)+" "+
(mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+
mousePos[0]+" "+mousePos[1]
);
}
d3.event.preventDefault();
} else if (mouse_mode == RED.state.MOVING) {
mousePos = d3.mouse(document.body);
@@ -506,11 +595,17 @@ RED.view = (function() {
if (d > 3) {
mouse_mode = RED.state.MOVING_ACTIVE;
clickElapsed = 0;
spliceActive = false;
if (moving_set.length === 1) {
node = moving_set[0];
spliceActive = node.n._def.inputs > 0 &&
node.n._def.outputs > 0 &&
RED.nodes.filterLinks({ source: node.n }).length === 0 &&
RED.nodes.filterLinks({ target: node.n }).length === 0;
}
}
} else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) {
mousePos = mouse_position;
var node;
var i;
var minX = 0;
var minY = 0;
for (var n = 0; n<moving_set.length; n++) {
@@ -532,11 +627,11 @@ RED.view = (function() {
node.n.y -= minY;
}
}
if (d3.event.shiftKey && moving_set.length > 0) {
var gridOffset = [0,0];
if (snapGrid != d3.event.shiftKey && moving_set.length > 0) {
var gridOffset = [0,0];
node = moving_set[0];
gridOffset[0] = node.n.x-(20*Math.floor((node.n.x-node.n.w/2)/20)+node.n.w/2);
gridOffset[1] = node.n.y-(20*Math.floor(node.n.y/20));
gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize));
if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
for (i = 0; i<moving_set.length; i++) {
node = moving_set[i];
@@ -548,6 +643,57 @@ RED.view = (function() {
}
}
}
if (mouse_mode == RED.state.MOVING_ACTIVE && moving_set.length === 1) {
node = moving_set[0];
if (spliceActive) {
if (!spliceTimer) {
spliceTimer = setTimeout(function() {
var nodes = [];
var bestDistance = Infinity;
var bestLink = null;
var mouseX = mousePos[0];
var mouseY = mousePos[1];
if (outer[0][0].getIntersectionList) {
var svgRect = outer[0][0].createSVGRect();
svgRect.x = mouseX;
svgRect.y = mouseY;
svgRect.width = 1;
svgRect.height = 1;
nodes = outer[0][0].getIntersectionList(svgRect, outer[0][0]);
} else {
// Firefox doesn't do getIntersectionList and that
// makes us sad
nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
}
for (var i=0;i<nodes.length;i++) {
if (d3.select(nodes[i]).classed('link_background')) {
var length = nodes[i].getTotalLength();
for (var j=0;j<length;j+=10) {
var p = nodes[i].getPointAtLength(j);
var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
if (d2 < 200 && d2 < bestDistance) {
bestDistance = d2;
bestLink = nodes[i];
}
}
}
}
if (activeSpliceLink && activeSpliceLink !== bestLink) {
d3.select(activeSpliceLink.parentNode).classed('link_splice',false);
}
if (bestLink) {
d3.select(bestLink.parentNode).classed('link_splice',true)
} else {
d3.select('.link_splice').classed('link_splice',false);
}
activeSpliceLink = bestLink;
spliceTimer = null;
},100);
}
}
}
}
if (mouse_mode !== 0) {
redraw();
@@ -555,8 +701,22 @@ RED.view = (function() {
}
function canvasMouseUp() {
var i;
var historyEvent;
if (mousedown_node && mouse_mode == RED.state.JOINING) {
drag_line.attr("class", "drag_line_hidden");
var removedLinks = [];
for (i=0;i<drag_lines.length;i++) {
if (drag_lines[i].link) {
removedLinks.push(drag_lines[i].link)
}
}
historyEvent = {
t:'delete',
links: removedLinks,
dirty:RED.nodes.dirty()
};
RED.history.push(historyEvent);
hideDragLines();
}
if (lasso) {
var x = parseInt(lasso.attr("x"));
@@ -594,7 +754,7 @@ RED.view = (function() {
updateSelection();
lasso.remove();
lasso = null;
} else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey ) {
} else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey&& !d3.event.metaKey ) {
clearSelection();
updateSelection();
}
@@ -604,11 +764,33 @@ RED.view = (function() {
for (var j=0;j<moving_set.length;j++) {
ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy});
}
RED.history.push({t:'move',nodes:ns,dirty:RED.nodes.dirty()});
historyEvent = {t:'move',nodes:ns,dirty:RED.nodes.dirty()};
if (activeSpliceLink) {
// TODO: DRY - droppable
var spliceLink = d3.select(activeSpliceLink).data()[0];
RED.nodes.removeLink(spliceLink);
var link1 = {
source:spliceLink.source,
sourcePort:spliceLink.sourcePort,
target: moving_set[0].n
};
var link2 = {
source:moving_set[0].n,
sourcePort:0,
target: spliceLink.target
};
RED.nodes.addLink(link1);
RED.nodes.addLink(link2);
historyEvent.links = [link1,link2];
historyEvent.removedLinks = [spliceLink];
updateActiveNodes();
}
RED.nodes.dirty(true);
RED.history.push(historyEvent);
}
}
if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
for (var i=0;i<moving_set.length;i++) {
for (i=0;i<moving_set.length;i++) {
delete moving_set[i].ox;
delete moving_set[i].oy;
}
@@ -724,6 +906,7 @@ RED.view = (function() {
delete moving_set[i].oy;
}
RED.history.push({t:'move',nodes:ns,dirty:RED.nodes.dirty()});
RED.nodes.dirty(true);
}
function moveSelection(dx,dy) {
var minX = 0;
@@ -876,6 +1059,13 @@ RED.view = (function() {
mousedown_link = null;
mouse_mode = 0;
mousedown_port_type = 0;
activeSpliceLink = null;
spliceActive = false;
d3.select('.link_splice').classed('link_splice',false);
if (spliceTimer) {
clearTimeout(spliceTimer);
spliceTimer = null;
}
}
function portMouseDown(d,portType,portIndex) {
@@ -892,46 +1082,59 @@ RED.view = (function() {
}
function portMouseUp(d,portType,portIndex) {
var i;
document.body.style.cursor = "";
if (mouse_mode == RED.state.JOINING && mousedown_node) {
if (mouse_mode == RED.state.JOINING && drag_lines.length > 0) {
if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) {
RED.nodes.eachNode(function(n) {
if (n.z == RED.workspaces.active()) {
var hw = n.w/2;
var hh = n.h/2;
if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
mouseup_node = n;
portType = mouseup_node.inputs>0?1:0;
portIndex = 0;
}
if (n.z == RED.workspaces.active()) {
var hw = n.w/2;
var hh = n.h/2;
if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
mouseup_node = n;
portType = mouseup_node.inputs>0?1:0;
portIndex = 0;
}
}
});
} else {
mouseup_node = d;
}
if (portType == mousedown_port_type || mouseup_node === mousedown_node) {
drag_line.attr("class", "drag_line_hidden");
resetMouseVars();
return;
var addedLinks = [];
var removedLinks = [];
for (i=0;i<drag_lines.length;i++) {
if (drag_lines[i].link) {
removedLinks.push(drag_lines[i].link)
}
}
var src,dst,src_port;
if (mousedown_port_type === 0) {
src = mousedown_node;
src_port = mousedown_port_index;
dst = mouseup_node;
} else if (mousedown_port_type == 1) {
src = mouseup_node;
dst = mousedown_node;
src_port = portIndex;
for (i=0;i<drag_lines.length;i++) {
if (portType != drag_lines[i].portType && mouseup_node !== drag_lines[i].node) {
var drag_line = drag_lines[i];
var src,dst,src_port;
if (drag_line.portType === 0) {
src = drag_line.node;
src_port = drag_line.port;
dst = mouseup_node;
} else if (drag_line.portType == 1) {
src = mouseup_node;
dst = drag_line.node;
src_port = portIndex;
}
var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
if (!existingLink) {
var link = {source: src, sourcePort:src_port, target: dst};
RED.nodes.addLink(link);
addedLinks.push(link);
}
}
}
var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
if (!existingLink) {
var link = {source: src, sourcePort:src_port, target: dst};
RED.nodes.addLink(link);
if (addedLinks.length > 0 || removedLinks.length > 0) {
var historyEvent = {
t:'add',
links:[link],
links:addedLinks,
removedLinks: removedLinks,
dirty:RED.nodes.dirty()
};
if (activeSubflow) {
@@ -947,8 +1150,9 @@ RED.view = (function() {
RED.history.push(historyEvent);
updateActiveNodes();
RED.nodes.dirty(true);
} else {
}
resetMouseVars();
hideDragLines();
selected_link = null;
redraw();
}
@@ -994,10 +1198,10 @@ RED.view = (function() {
var i;
if (d.selected && d3.event.ctrlKey) {
d.selected = false;
if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
mousedown_node.selected = false;
for (i=0;i<moving_set.length;i+=1) {
if (moving_set[i].n === d) {
if (moving_set[i].n === mousedown_node) {
moving_set.splice(i,1);
break;
}
@@ -1012,7 +1216,7 @@ RED.view = (function() {
moving_set.push({n:cnodes[n]});
}
} else if (!d.selected) {
if (!d3.event.ctrlKey) {
if (!d3.event.ctrlKey && !d3.event.metaKey) {
clearSelection();
}
mousedown_node.selected = true;
@@ -1123,7 +1327,7 @@ RED.view = (function() {
.on("touchstart", function(d,i){portMouseDown(d,1,0);} )
.on("mouseup", function(d,i){portMouseUp(d,1,0);})
.on("touchend",function(d,i){portMouseUp(d,1,0);} )
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 1)));})
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
outGroup.append("svg:text").attr('class','port_label').attr('x',20).attr('y',8).style("font-size","10px").text("output");
@@ -1166,7 +1370,7 @@ RED.view = (function() {
.on("touchstart", function(d,i){portMouseDown(d,0,i);} )
.on("mouseup", function(d,i){portMouseUp(d,0,i);})
.on("touchend",function(d,i){portMouseUp(d,0,i);} )
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 0) ));})
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
inGroup.append("svg:text").attr('class','port_label').attr('x',18).attr('y',20).style("font-size","10px").text("input");
@@ -1205,7 +1409,7 @@ RED.view = (function() {
node.attr("id",d.id);
var l = d._def.label;
l = (typeof l === "function" ? l.call(d) : l)||"";
d.w = Math.max(node_width,calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0) );
d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) );
d.h = Math.max(node_height,(d.outputs||0) * 15);
if (d._def.badge) {
@@ -1388,8 +1592,10 @@ RED.view = (function() {
if (d.resize) {
var l = d._def.label;
l = (typeof l === "function" ? l.call(d) : l)||"";
d.w = Math.max(node_width,calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0) );
var ow = d.w;
d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) );
d.h = Math.max(node_height,(d.outputs||0) * 15);
d.x += (d.w-ow)/2;
d.resize = false;
}
var thisNode = d3.select(this);
@@ -1423,7 +1629,7 @@ RED.view = (function() {
.on("touchstart",function(d){portMouseDown(d,1,0);})
.on("mouseup",function(d){portMouseUp(d,1,0);} )
.on("touchend",function(d){portMouseUp(d,1,0);} )
.on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));})
.on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 1) ));})
.on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
}
@@ -1438,7 +1644,7 @@ RED.view = (function() {
.on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
.on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
.on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 0) ));})
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
d._ports.exit().remove();
@@ -1618,7 +1824,7 @@ RED.view = (function() {
touchStartTime = null;
showTouchMenu(obj,pos);
},touchLongPressTimeout);
});
})
l.append("svg:path").attr("class","link_outline link_path");
l.append("svg:path").attr("class","link_line link_path")
.classed("link_subflow", function(d) { return activeSubflow && (d.source.type === "subflow" || d.target.type === "subflow") });
@@ -1844,6 +2050,31 @@ RED.view = (function() {
selection.link = selected_link;
}
return selection;
},
toggleShowGrid: function(state) {
if (state) {
grid.style("visibility","visible");
} else {
grid.style("visibility","hidden");
}
},
toggleSnapGrid: function(state) {
snapGrid = state;
redraw();
},
scale: function() {
return scaleFactor;
},
getLinksAtPoint: function(x,y) {
var result = [];
var links = outer.selectAll(".link_background")[0];
for (var i=0;i<links.length;i++) {
var bb = links[i].getBBox();
if (x >= bb.x && y >= bb.y && x <= bb.x+bb.width && y <= bb.y+bb.height) {
result.push(links[i])
}
}
return result;
}
};
})();

View File

@@ -28,7 +28,6 @@ RED.workspaces = (function() {
var tabId = RED.nodes.id();
do {
workspaceIndex += 1;
//TODO: nls of Sheet
} while($("#workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
ws = {type:"tab",id:tabId,label:RED._('workspace.defaultName',{number:workspaceIndex})};
@@ -55,6 +54,7 @@ RED.workspaces = (function() {
historyEvent.workspaces = [ws];
RED.history.push(historyEvent);
RED.nodes.dirty(true);
RED.sidebar.config.refresh();
} else {
$( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
$( "#node-dialog-delete-workspace-content" ).text(RED._("workspace.delete",{label:ws.label}));
@@ -141,8 +141,8 @@ RED.workspaces = (function() {
if (workspace.label != label) {
workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true);
RED.sidebar.config.refresh();
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
// TODO: update entry in menu
}
$( this ).dialog( "close" );
}

View File

@@ -43,3 +43,8 @@ $workspace-button-color-disabled: #ccc;
$workspace-button-color-focus: #999;
$workspace-button-color-hover: #666;
$workspace-button-color-active: #666;
$workspace-button-color-selected: #AAA;
$typedInput-button-background: #efefef;
$typedInput-button-background-hover: #ddd;
$typedInput-button-background-active: #e3e3e3;

View File

@@ -186,7 +186,7 @@
.drag_line {
stroke: $node-selected-color;
stroke-width: 4;
stroke-width: 3;
fill: none;
pointer-events: none;
}
@@ -225,6 +225,9 @@
cursor: crosshair;
fill: none;
}
.link_splice > .link_line {
stroke-dasharray: 15,8;
}
g.link_selected path.link_line {
stroke: $node-selected-color;

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2015 IBM Corp.
* Copyright 2015, 2016 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,6 +22,9 @@
font-size: 14px !important;
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
}
.ui-widget input {
box-shadow: none;
}
/* jQuery Theme overrides */
.ui-tabs .ui-tabs-panel {
@@ -73,3 +76,23 @@
.ui-dialog .ui-dialog-buttonpane {
padding: .3em 1em .5em 1em;
}
.ui-spinner {
border-radius: 4px;
padding: 0;
border: 1px solid $form-input-border-color;
}
.ui-spinner input {
margin: 0 17px 0 0;
padding: 6px;
border: none;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
&:focus {
outline: none;
}
}

View File

@@ -30,9 +30,9 @@
@mixin workspace-button {
@include disable-selection;
color: $workspace-button-color;
box-sizing: border-box;
display: inline-block;
color: $workspace-button-color;
background: $workspace-button-background;
border: 1px solid $secondary-border-color;
text-align: center;
@@ -57,7 +57,28 @@
background: $workspace-button-background-active;
text-decoration: none;
}
&.selected:not(.disabled) {
color: $workspace-button-color-selected;
background: $workspace-button-background-active;
cursor: default;
}
.button-group &:not(:first-child) {
border-left: none;
}
}
@mixin workspace-button-toggle {
@include workspace-button;
color: $workspace-button-color-selected;
background: $workspace-button-background-active;
&.selected:not(.disabled) {
color: $workspace-button-color;
background: $workspace-button-background;
}
}
@mixin component-footer {
border-top: 1px solid $primary-border-color;
background: #f3f3f3;

View File

@@ -33,6 +33,12 @@
border: 1px solid #325C80;
border-left-width: 16px;
}
.notification a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.notification-success {
border-color: #4B8400;

View File

@@ -114,11 +114,16 @@
}
.palette-header {
position: relative;
background: $palette-header-background;
cursor: pointer;
text-align: left;
padding: 9px;
font-weight: bold;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.palette-header i {
margin: 3px 10px 3px 3px;

View File

@@ -37,7 +37,6 @@
right: 0;
bottom: 25px;
left: 0px;
padding-top: 3px;
overflow-y: auto;
}
@@ -63,6 +62,33 @@
padding: 2px 8px;
}
.sidebar-header {
color: #666;
text-align: right;
padding: 8px 10px;
background: #f3f3f3;
border-bottom: 1px solid $secondary-border-color;
}
#sidebar-footer {
@include component-footer;
}
.sidebar-footer-button {
@include component-footer-button;
}
.sidebar-header-button {
@include workspace-button;
font-size: 13px;
line-height: 13px;
padding: 5px 8px;
}
.sidebar-header-button-toggle {
@include workspace-button-toggle;
font-size: 13px;
line-height: 13px;
padding: 5px 8px;
}
.sidebar-header-button:not(:first-child) {
border-left: none;
}

View File

@@ -41,6 +41,8 @@
@import "popover";
@import "flow";
@import "typedInput";
@import "dragdrop";
@import "keyboard";

View File

@@ -47,23 +47,35 @@
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
.config_node_type {
color: #999;
text-align: right;
padding-right: 3px;
&:not(:first-child) {
margin-top: 20px;
}
}
.config_node_none {
color: #ddd;
text-align:right;
padding-right: 3px;
}
.config_node_unused {
border-color: #aaa;
background: #f9f9f9;
border-style: dashed;
color: #aaa;
}
}
.config_node_type {
color: #999;
text-align: right;
padding-right: 3px;
&:not(:first-child) {
margin-top: 20px;
}
}
.config_node_none {
color: #ddd;
text-align:right;
padding-right: 3px;
}
.config_node_unused {
border-color: #aaa;
background: #f9f9f9;
border-style: dashed;
color: #aaa;
}
.config-node-filter-info {
position: absolute;
top: 0;
right:0;
height: 38px;
line-height: 38px;
padding: 0 8px;
background: $palette-header-background;
font-size: 0.8em;
color: #999;
font-weight: normal;
}

122
editor/sass/typedInput.scss Normal file
View File

@@ -0,0 +1,122 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
.red-ui-typedInput-container {
border: 1px solid $form-input-border-color;
border-radius: 4px;
height: 34px;
display: inline-block;
padding: 0;
margin: 0;
vertical-align: middle;
box-sizing: border-box;
overflow:hidden;
input {
padding: 0 0 0 1px;
margin:0;
height: 32px;
border:none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
box-shadow: none;
vertical-align: middle;
}
&.red-ui-typedInput-focus:not(.input-error) {
border-color: $form-input-focus-color !important;
}
a {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
padding: 0 1px 0 5px;
display:inline-block;
background: $typedInput-button-background;
height: 32px;
line-height: 32px;
vertical-align: middle;
color: #555;
i {
position:relative;
top:-3px;
margin-right:4px;
margin-top: 1px;
vertical-align: middle;
}
span {
display: inline-block;
height: 100%;
padding: 0 1px 0 5px;
}
&:hover {
text-decoration: none;
background: $typedInput-button-background-hover;
}
&:focus {
text-decoration: none;
}
&:active {
background: $typedInput-button-background-active;
text-decoration: none;
}
}
a.red-ui-typedInput-option-trigger {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
padding: 0 5px 0 0;
i {
margin-right: 0;
margin-left: 4px;
}
span {
background:#fff;
padding: 0 5px 0 5px;
}
}
}
.red-ui-typedInput-options {
@include component-shadow;
position: absolute;
border: 1px solid $primary-border-color;
background: #fff;
z-index: 2000;
a {
padding: 6px 18px 6px 6px;
display: block;
border-bottom: 1px solid $secondary-border-color;
color: #333;
&:hover {
text-decoration: none;
background: $typedInput-button-background-hover;
}
&:focus {
text-decoration: none;
}
&:active {
background: $typedInput-button-background-active;
text-decoration: none;
}
}
}

View File

@@ -35,7 +35,7 @@
</head>
<body spellcheck="false">
<div id="header">
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</span>
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}" title="{{version}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</span>
<ul class="header-toolbar hide">
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
<ul>
@@ -104,9 +104,6 @@
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
</div>
<div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unusedConfig;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unused-list"></ul>
</div>
</form>
</div>
@@ -162,13 +159,6 @@
</form>
</div>
<script type="text/x-red" data-template-name="export-library-dialog">
<div class="form-row">
<label for="node-input-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>
<input type="text" id="node-input-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">
</div>
</script>
<script type="text/x-red" data-template-name="subflow">
<div class="form-row">
<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>

4
editor/vendor/font-awesome/css/font-awesome.min.css vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

BIN
editor/vendor/font-awesome/fonts/FontAwesome.otf vendored Executable file → Normal file

Binary file not shown.

BIN
editor/vendor/font-awesome/fonts/fontawesome-webfont.eot vendored Executable file → Normal file

Binary file not shown.

63
editor/vendor/font-awesome/fonts/fontawesome-webfont.svg vendored 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" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="fontawesomeregular" horiz-adv-x="1536" >
@@ -219,8 +219,8 @@
<glyph unicode="&#xf0d1;" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf0d2;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf0d3;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
<glyph unicode="&#xf0d4;" d="M829 318q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5zM755 863q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5 t-57.5 96.5t-17.5 106q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107zM861 1120l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95 q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83zM1152 672h128v64h-128v128h-64v-128 h-128v-64h128v-160h64v160zM1536 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.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf0d5;" horiz-adv-x="1664" d="M735 740q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4 q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65t-59.5 -61.5t-24.5 -66zM589 836q38 0 78 16.5t66 43.5q53 57 53 159q0 58 -17 125t-48.5 129.5 t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26zM591 -37q58 0 111.5 13t99 39t73 73t27.5 109q0 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 -48 2 q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5zM1401 839h213v-108h-213v-219h-105v219h-212v108h212v217h105v-217z" />
<glyph unicode="&#xf0d4;" d="M917 631q0 26 -6 64h-362v-132h217q-3 -24 -16.5 -50t-37.5 -53t-66.5 -44.5t-96.5 -17.5q-99 0 -169 71t-70 171t70 171t169 71q92 0 153 -59l104 101q-108 100 -257 100q-160 0 -272 -112.5t-112 -271.5t112 -271.5t272 -112.5q165 0 266.5 105t101.5 270zM1262 585 h109v110h-109v110h-110v-110h-110v-110h110v-110h110v110zM1536 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.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf0d5;" horiz-adv-x="2304" d="M1437 623q0 -208 -87 -370.5t-248 -254t-369 -91.5q-149 0 -285 58t-234 156t-156 234t-58 285t58 285t156 234t234 156t285 58q286 0 491 -192l-199 -191q-117 113 -292 113q-123 0 -227.5 -62t-165.5 -168.5t-61 -232.5t61 -232.5t165.5 -168.5t227.5 -62 q83 0 152.5 23t114.5 57.5t78.5 78.5t49 83t21.5 74h-416v252h692q12 -63 12 -122zM2304 745v-210h-209v-209h-210v209h-209v210h209v209h210v-209h209z" />
<glyph unicode="&#xf0d6;" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf0d7;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf0d8;" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
@@ -362,7 +362,7 @@
<glyph unicode="&#xf169;" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 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.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf16a;" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
<glyph unicode="&#xf16b;" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
<glyph unicode="&#xf16c;" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" />
<glyph unicode="&#xf16c;" d="M1289 -96h-1118v480h-160v-640h1438v640h-160v-480zM347 428l33 157l783 -165l-33 -156zM450 802l67 146l725 -339l-67 -145zM651 1158l102 123l614 -513l-102 -123zM1048 1536l477 -641l-128 -96l-477 641zM330 65v159h800v-159h-800z" />
<glyph unicode="&#xf16d;" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
<glyph unicode="&#xf16e;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-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.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
<glyph unicode="&#xf170;" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 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" />
@@ -410,7 +410,7 @@
<glyph unicode="&#xf19c;" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" />
<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="&#xf1a0;" d="M768 750h725q12 -67 12 -128q0 -217 -91 -387.5t-259.5 -266.5t-386.5 -96q-157 0 -299 60.5t-245 163.5t-163.5 245t-60.5 299t60.5 299t163.5 245t245 163.5t299 60.5q300 0 515 -201l-209 -201q-123 119 -306 119q-129 0 -238.5 -65t-173.5 -176.5t-64 -243.5 t64 -243.5t173.5 -176.5t238.5 -65q87 0 160 24t120 60t82 82t51.5 87t22.5 78h-436v264z" />
<glyph unicode="&#xf1a1;" horiz-adv-x="1792" d="M1095 369q16 -16 0 -31q-62 -62 -199 -62t-199 62q-16 15 0 31q6 6 15 6t15 -6q48 -49 169 -49q120 0 169 49q6 6 15 6t15 -6zM788 550q0 -37 -26 -63t-63 -26t-63.5 26t-26.5 63q0 38 26.5 64t63.5 26t63 -26.5t26 -63.5zM1183 550q0 -37 -26.5 -63t-63.5 -26t-63 26 t-26 63t26 63.5t63 26.5t63.5 -26t26.5 -64zM1434 670q0 49 -35 84t-85 35t-86 -36q-130 90 -311 96l63 283l200 -45q0 -37 26 -63t63 -26t63.5 26.5t26.5 63.5t-26.5 63.5t-63.5 26.5q-54 0 -80 -50l-221 49q-19 5 -25 -16l-69 -312q-180 -7 -309 -97q-35 37 -87 37 q-50 0 -85 -35t-35 -84q0 -35 18.5 -64t49.5 -44q-6 -27 -6 -56q0 -142 140 -243t337 -101q198 0 338 101t140 243q0 32 -7 57q30 15 48 43.5t18 63.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191 t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf1a2;" d="M939 407q13 -13 0 -26q-53 -53 -171 -53t-171 53q-13 13 0 26q5 6 13 6t13 -6q42 -42 145 -42t145 42q5 6 13 6t13 -6zM676 563q0 -31 -23 -54t-54 -23t-54 23t-23 54q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1014 563q0 -31 -23 -54t-54 -23t-54 23t-23 54 q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1229 666q0 42 -30 72t-73 30q-42 0 -73 -31q-113 78 -267 82l54 243l171 -39q1 -32 23.5 -54t53.5 -22q32 0 54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5q-48 0 -69 -43l-189 42q-17 5 -21 -13l-60 -268q-154 -6 -265 -83 q-30 32 -74 32q-43 0 -73 -30t-30 -72q0 -30 16 -55t42 -38q-5 -25 -5 -48q0 -122 120 -208.5t289 -86.5q170 0 290 86.5t120 208.5q0 25 -6 49q25 13 40.5 37.5t15.5 54.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<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" />
@@ -454,7 +454,7 @@
<glyph unicode="&#xf1cb;" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" />
<glyph unicode="&#xf1cc;" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" />
<glyph unicode="&#xf1cd;" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" />
<glyph unicode="&#xf1ce;" horiz-adv-x="1792" d="M1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5z" />
<glyph unicode="&#xf1ce;" horiz-adv-x="1792" d="M1760 640q0 -176 -68.5 -336t-184 -275.5t-275.5 -184t-336 -68.5t-336 68.5t-275.5 184t-184 275.5t-68.5 336q0 213 97 398.5t265 305.5t374 151v-228q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5 t136.5 204t51 248.5q0 230 -145.5 406t-366.5 221v228q206 -31 374 -151t265 -305.5t97 -398.5z" />
<glyph unicode="&#xf1d0;" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" />
<glyph unicode="&#xf1d1;" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf1d2;" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 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.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
@@ -555,7 +555,7 @@
<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="&#xf23a;" horiz-adv-x="1792" d="M597 1115v-1173q0 -25 -12.5 -42.5t-36.5 -17.5q-17 0 -33 8l-465 233q-21 10 -35.5 33.5t-14.5 46.5v1140q0 20 10 34t29 14q14 0 44 -15l511 -256q3 -3 3 -5zM661 1014l534 -866l-534 266v600zM1792 996v-1054q0 -25 -14 -40.5t-38 -15.5t-47 13l-441 220zM1789 1116 q0 -3 -256.5 -419.5t-300.5 -487.5l-390 634l324 527q17 28 52 28q14 0 26 -6l541 -270q4 -2 4 -6z" />
<glyph unicode="&#xf23b;" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1408v-1536h-1536v1536h1536z" />
<glyph unicode="&#xf23c;" horiz-adv-x="2296" d="M478 -139q-8 -16 -27 -34.5t-37 -25.5q-25 -9 -51.5 3.5t-28.5 31.5q-1 22 40 55t68 38q23 4 34 -21.5t2 -46.5zM1819 -139q7 -16 26 -34.5t38 -25.5q25 -9 51.5 3.5t27.5 31.5q2 22 -39.5 55t-68.5 38q-22 4 -33 -21.5t-2 -46.5zM1867 -30q13 -27 56.5 -59.5t77.5 -41.5 q45 -13 82 4.5t37 50.5q0 46 -67.5 100.5t-115.5 59.5q-40 5 -63.5 -37.5t-6.5 -76.5zM428 -30q-13 -27 -56 -59.5t-77 -41.5q-45 -13 -82 4.5t-37 50.5q0 46 67.5 100.5t115.5 59.5q40 5 63 -37.5t6 -76.5zM1158 1094h1q-41 0 -76 -15q27 -8 44 -30.5t17 -49.5 q0 -35 -27 -60t-65 -25q-52 0 -80 43q-5 -23 -5 -42q0 -74 56 -126.5t135 -52.5q80 0 136 52.5t56 126.5t-56 126.5t-136 52.5zM1462 1312q-99 109 -220.5 131.5t-245.5 -44.5q27 60 82.5 96.5t118 39.5t121.5 -17t99.5 -74.5t44.5 -131.5zM2212 73q8 -11 -11 -42 q7 -23 7 -40q1 -56 -44.5 -112.5t-109.5 -91.5t-118 -37q-48 -2 -92 21.5t-66 65.5q-687 -25 -1259 0q-23 -41 -66.5 -65t-92.5 -22q-86 3 -179.5 80.5t-92.5 160.5q2 22 7 40q-19 31 -11 42q6 10 31 1q14 22 41 51q-7 29 2 38q11 10 39 -4q29 20 59 34q0 29 13 37 q23 12 51 -16q35 5 61 -2q18 -4 38 -19v73q-11 0 -18 2q-53 10 -97 44.5t-55 87.5q-9 38 0 81q15 62 93 95q2 17 19 35.5t36 23.5t33 -7.5t19 -30.5h13q46 -5 60 -23q3 -3 5 -7q10 1 30.5 3.5t30.5 3.5q-15 11 -30 17q-23 40 -91 43q0 6 1 10q-62 2 -118.5 18.5t-84.5 47.5 q-32 36 -42.5 92t-2.5 112q16 126 90 179q23 16 52 4.5t32 -40.5q0 -1 1.5 -14t2.5 -21t3 -20t5.5 -19t8.5 -10q27 -14 76 -12q48 46 98 74q-40 4 -162 -14l47 46q61 58 163 111q145 73 282 86q-20 8 -41 15.5t-47 14t-42.5 10.5t-47.5 11t-43 10q595 126 904 -139 q98 -84 158 -222q85 -10 121 9h1q5 3 8.5 10t5.5 19t3 19.5t3 21.5l1 14q3 28 32 40t52 -5q73 -52 91 -178q7 -57 -3.5 -113t-42.5 -91q-28 -32 -83.5 -48.5t-115.5 -18.5v-10q-71 -2 -95 -43q-14 -5 -31 -17q11 -1 32 -3.5t30 -3.5q1 4 5 8q16 18 60 23h13q5 18 19 30t33 8 t36 -23t19 -36q79 -32 93 -95q9 -40 1 -81q-12 -53 -56 -88t-97 -44q-10 -2 -17 -2q0 -49 -1 -73q20 15 38 19q26 7 61 2q28 28 51 16q14 -9 14 -37q33 -16 59 -34q27 13 38 4q10 -10 2 -38q28 -30 41 -51q23 8 31 -1zM1937 1025q0 -29 -9 -54q82 -32 112 -132 q4 37 -9.5 98.5t-41.5 90.5q-20 19 -36 17t-16 -20zM1859 925q35 -42 47.5 -108.5t-0.5 -124.5q67 13 97 45q13 14 18 28q-3 64 -31 114.5t-79 66.5q-15 -15 -52 -21zM1822 921q-30 0 -44 1q42 -115 53 -239q21 0 43 3q16 68 1 135t-53 100zM258 839q30 100 112 132 q-9 25 -9 54q0 18 -16.5 20t-35.5 -17q-28 -29 -41.5 -90.5t-9.5 -98.5zM294 737q29 -31 97 -45q-13 58 -0.5 124.5t47.5 108.5v0q-37 6 -52 21q-51 -16 -78.5 -66t-31.5 -115q9 -17 18 -28zM471 683q14 124 73 235q-19 -4 -55 -18l-45 -19v1q-46 -89 -20 -196q25 -3 47 -3z M1434 644q8 -38 16.5 -108.5t11.5 -89.5q3 -18 9.5 -21.5t23.5 4.5q40 20 62 85.5t23 125.5q-24 2 -146 4zM1152 1285q-116 0 -199 -82.5t-83 -198.5q0 -117 83 -199.5t199 -82.5t199 82.5t83 199.5q0 116 -83 198.5t-199 82.5zM1380 646q-106 2 -211 0v1q-1 -27 2.5 -86 t13.5 -66q29 -14 93.5 -14.5t95.5 10.5q9 3 11 39t-0.5 69.5t-4.5 46.5zM1112 447q8 4 9.5 48t-0.5 88t-4 63v1q-212 -3 -214 -3q-4 -20 -7 -62t0 -83t14 -46q34 -15 101 -16t101 10zM718 636q-16 -59 4.5 -118.5t77.5 -84.5q15 -8 24 -5t12 21q3 16 8 90t10 103 q-69 -2 -136 -6zM591 510q3 -23 -34 -36q132 -141 271.5 -240t305.5 -154q172 49 310.5 146t293.5 250q-33 13 -30 34l3 9v1v-1q-17 2 -50 5.5t-48 4.5q-26 -90 -82 -132q-51 -38 -82 1q-5 6 -9 14q-7 13 -17 62q-2 -5 -5 -9t-7.5 -7t-8 -5.5t-9.5 -4l-10 -2.5t-12 -2 l-12 -1.5t-13.5 -1t-13.5 -0.5q-106 -9 -163 11q-4 -17 -10 -26.5t-21 -15t-23 -7t-36 -3.5q-2 0 -3 -0.5t-3 -0.5h-3q-179 -17 -203 40q-2 -63 -56 -54q-47 8 -91 54q-12 13 -20 26q-17 29 -26 65q-58 -6 -87 -10q1 -2 4 -10zM507 -118q3 14 3 30q-17 71 -51 130t-73 70 q-41 12 -101.5 -14.5t-104.5 -80t-39 -107.5q35 -53 100 -93t119 -42q51 -2 94 28t53 79zM510 53q23 -63 27 -119q195 113 392 174q-98 52 -180.5 120t-179.5 165q-6 -4 -29 -13q0 -2 -1 -5t-1 -4q31 -18 22 -37q-12 -23 -56 -34q-10 -13 -29 -24h-1q-2 -83 1 -150 q19 -34 35 -73zM579 -113q532 -21 1145 0q-254 147 -428 196q-76 -35 -156 -57q-8 -3 -16 0q-65 21 -129 49q-208 -60 -416 -188h-1v-1q1 0 1 1zM1763 -67q4 54 28 120q14 38 33 71l-1 -1q3 77 3 153q-15 8 -30 25q-42 9 -56 33q-9 20 22 38q-2 4 -2 9q-16 4 -28 12 q-204 -190 -383 -284q198 -59 414 -176zM2155 -90q5 54 -39 107.5t-104 80t-102 14.5q-38 -11 -72.5 -70.5t-51.5 -129.5q0 -16 3 -30q10 -49 53 -79t94 -28q54 2 119 42t100 93z" />
<glyph unicode="&#xf23d;" horiz-adv-x="2304" d="M1524 -25q0 -68 -48 -116t-116 -48t-116.5 48t-48.5 116t48.5 116.5t116.5 48.5t116 -48.5t48 -116.5zM775 -25q0 -68 -48.5 -116t-116.5 -48t-116 48t-48 116t48 116.5t116 48.5t116.5 -48.5t48.5 -116.5zM0 1469q57 -60 110.5 -104.5t121 -82t136 -63t166 -45.5 t200 -31.5t250 -18.5t304 -9.5t372.5 -2.5q139 0 244.5 -5t181 -16.5t124 -27.5t71 -39.5t24 -51.5t-19.5 -64t-56.5 -76.5t-89.5 -91t-116 -104.5t-139 -119q-185 -157 -286 -247q29 51 76.5 109t94 105.5t94.5 98.5t83 91.5t54 80.5t13 70t-45.5 55.5t-116.5 41t-204 23.5 t-304 5q-168 -2 -314 6t-256 23t-204.5 41t-159.5 51.5t-122.5 62.5t-91.5 66.5t-68 71.5t-50.5 69.5t-40 68t-36.5 59.5z" />
@@ -600,11 +600,11 @@
<glyph unicode="&#xf267;" horiz-adv-x="1792" d="M949 643q0 -26 -16.5 -45t-41.5 -19q-26 0 -45 16.5t-19 41.5q0 26 17 45t42 19t44 -16.5t19 -41.5zM964 585l350 581q-9 -8 -67.5 -62.5t-125.5 -116.5t-136.5 -127t-117 -110.5t-50.5 -51.5l-349 -580q7 7 67 62t126 116.5t136 127t117 111t50 50.5zM1611 640 q0 -201 -104 -371q-3 2 -17 11t-26.5 16.5t-16.5 7.5q-13 0 -13 -13q0 -10 59 -44q-74 -112 -184.5 -190.5t-241.5 -110.5l-16 67q-1 10 -15 10q-5 0 -8 -5.5t-2 -9.5l16 -68q-72 -15 -146 -15q-199 0 -372 105q1 2 13 20.5t21.5 33.5t9.5 19q0 13 -13 13q-6 0 -17 -14.5 t-22.5 -34.5t-13.5 -23q-113 75 -192 187.5t-110 244.5l69 15q10 3 10 15q0 5 -5.5 8t-10.5 2l-68 -15q-14 72 -14 139q0 206 109 379q2 -1 18.5 -12t30 -19t17.5 -8q13 0 13 12q0 6 -12.5 15.5t-32.5 21.5l-20 12q77 112 189 189t244 107l15 -67q2 -10 15 -10q5 0 8 5.5 t2 10.5l-15 66q71 13 134 13q204 0 379 -109q-39 -56 -39 -65q0 -13 12 -13q11 0 48 64q111 -75 187.5 -186t107.5 -241l-56 -12q-10 -2 -10 -16q0 -5 5.5 -8t9.5 -2l57 13q14 -72 14 -140zM1696 640q0 163 -63.5 311t-170.5 255t-255 170.5t-311 63.5t-311 -63.5 t-255 -170.5t-170.5 -255t-63.5 -311t63.5 -311t170.5 -255t255 -170.5t311 -63.5t311 63.5t255 170.5t170.5 255t63.5 311zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191 t191 -286t71 -348z" />
<glyph unicode="&#xf268;" horiz-adv-x="1792" d="M893 1536q240 2 451 -120q232 -134 352 -372l-742 39q-160 9 -294 -74.5t-185 -229.5l-276 424q128 159 311 245.5t383 87.5zM146 1131l337 -663q72 -143 211 -217t293 -45l-230 -451q-212 33 -385 157.5t-272.5 316t-99.5 411.5q0 267 146 491zM1732 962 q58 -150 59.5 -310.5t-48.5 -306t-153 -272t-246 -209.5q-230 -133 -498 -119l405 623q88 131 82.5 290.5t-106.5 277.5zM896 942q125 0 213.5 -88.5t88.5 -213.5t-88.5 -213.5t-213.5 -88.5t-213.5 88.5t-88.5 213.5t88.5 213.5t213.5 88.5z" />
<glyph unicode="&#xf269;" horiz-adv-x="1792" d="M903 -256q-283 0 -504.5 150.5t-329.5 398.5q-58 131 -67 301t26 332.5t111 312t179 242.5l-11 -281q11 14 68 15.5t70 -15.5q42 81 160.5 138t234.5 59q-54 -45 -119.5 -148.5t-58.5 -163.5q25 -8 62.5 -13.5t63 -7.5t68 -4t50.5 -3q15 -5 9.5 -45.5t-30.5 -75.5 q-5 -7 -16.5 -18.5t-56.5 -35.5t-101 -34l15 -189l-139 67q-18 -43 -7.5 -81.5t36 -66.5t65.5 -41.5t81 -6.5q51 9 98 34.5t83.5 45t73.5 17.5q61 -4 89.5 -33t19.5 -65q-1 -2 -2.5 -5.5t-8.5 -12.5t-18 -15.5t-31.5 -10.5t-46.5 -1q-60 -95 -144.5 -135.5t-209.5 -29.5 q74 -61 162.5 -82.5t168.5 -6t154.5 52t128 87.5t80.5 104q43 91 39 192.5t-37.5 188.5t-78.5 125q87 -38 137 -79.5t77 -112.5q15 170 -57.5 343t-209.5 284q265 -77 412 -279.5t151 -517.5q2 -127 -40.5 -255t-123.5 -238t-189 -196t-247.5 -135.5t-288.5 -49.5z" />
<glyph unicode="&#xf26a;" d="M768 -92q77 0 139.5 63t100.5 166t59 234.5t21 268.5t-21 268.5t-59 234.5t-100.5 166t-139.5 63t-139.5 -63t-100.5 -166t-59 -234.5t-21 -268.5t21 -268.5t59 -234.5t100.5 -166t139.5 -63zM768 -256q-184 0 -333 77t-240 203t-141 287t-50 329t50 329t141 287t240 203 t333 77q148 0 274 -50t214.5 -136t151.5 -201t92.5 -244t29.5 -265t-29.5 -265t-92.5 -244t-151.5 -201t-214.5 -136t-274 -50z" />
<glyph unicode="&#xf26b;" horiz-adv-x="1792" d="M716 -69q-143 35 -261.5 114t-197.5 191q-139 -300 -17 -398q26 -21 85 -24.5t127.5 9.5t141 41.5t122.5 66.5zM693 762h452q0 108 -61.5 169t-168.5 61q-103 0 -162.5 -62.5t-59.5 -167.5zM1724 1137h-34q26 102 22.5 170t-25 110t-63.5 57t-93.5 11t-115 -26.5 t-128.5 -56.5t-134 -79q129 -37 238.5 -113.5t185 -179t110 -231.5t15.5 -262h-1005q0 -60 10 -106t34 -85t69.5 -60t112.5 -21q87 0 142.5 44t72.5 122h540q-71 -230 -281.5 -377t-477.5 -147q-83 0 -159 15q-35 -40 -151 -94t-248 -78t-219 35q-78 60 -100 159t7 214 t88 242t143.5 248t173.5 226.5t177.5 183.5t156.5 112v24q-120 -37 -258.5 -137.5t-240.5 -207t-159 -195.5q4 106 34 201t80 169t118 135.5t147.5 100.5t168 65.5t180.5 29.5t185 -8q310 186 503 189h7q57 0 103 -18q80 -30 98 -132.5t-30 -248.5z" />
<glyph unicode="&#xf26a;" horiz-adv-x="1792" d="M1493 1308q-165 110 -359 110q-155 0 -293 -73t-240 -200q-75 -93 -119.5 -218t-48.5 -266v-42q4 -141 48.5 -266t119.5 -218q102 -127 240 -200t293 -73q194 0 359 110q-121 -108 -274.5 -168t-322.5 -60q-29 0 -43 1q-175 8 -333 82t-272 193t-181 281t-67 339 q0 182 71 348t191 286t286 191t348 71h3q168 -1 320.5 -60.5t273.5 -167.5zM1792 640q0 -192 -77 -362.5t-213 -296.5q-104 -63 -222 -63q-137 0 -255 84q154 56 253.5 233t99.5 405q0 227 -99 404t-253 234q119 83 254 83q119 0 226 -65q135 -125 210.5 -295t75.5 -361z " />
<glyph unicode="&#xf26b;" horiz-adv-x="1792" d="M1792 599q0 -56 -7 -104h-1151q0 -146 109.5 -244.5t257.5 -98.5q99 0 185.5 46.5t136.5 130.5h423q-56 -159 -170.5 -281t-267.5 -188.5t-321 -66.5q-187 0 -356 83q-228 -116 -394 -116q-237 0 -237 263q0 115 45 275q17 60 109 229q199 360 475 606 q-184 -79 -427 -354q63 274 283.5 449.5t501.5 175.5q30 0 45 -1q255 117 433 117q64 0 116 -13t94.5 -40.5t66.5 -76.5t24 -115q0 -116 -75 -286q101 -182 101 -390zM1722 1239q0 83 -53 132t-137 49q-108 0 -254 -70q121 -47 222.5 -131.5t170.5 -195.5q51 135 51 216z M128 2q0 -86 48.5 -132.5t134.5 -46.5q115 0 266 83q-122 72 -213.5 183t-137.5 245q-98 -205 -98 -332zM632 715h728q-5 142 -113 237t-251 95q-144 0 -251.5 -95t-112.5 -237z" />
<glyph unicode="&#xf26c;" horiz-adv-x="2048" d="M1792 288v960q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1248v-960q0 -66 -47 -113t-113 -47h-736v-128h352q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23 v64q0 14 9 23t23 9h352v128h-736q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
<glyph unicode="&#xf26d;" horiz-adv-x="1792" d="M138 1408h197q-70 -64 -126 -149q-36 -56 -59 -115t-30 -125.5t-8.5 -120t10.5 -132t21 -126t28 -136.5q4 -19 6 -28q51 -238 81 -329q57 -171 152 -275h-272q-48 0 -82 34t-34 82v1304q0 48 34 82t82 34zM1346 1408h308q48 0 82 -34t34 -82v-1304q0 -48 -34 -82t-82 -34 h-178q212 210 196 565l-469 -101q-2 -45 -12 -82t-31 -72t-59.5 -59.5t-93.5 -36.5q-123 -26 -199 40q-32 27 -53 61t-51.5 129t-64.5 258q-35 163 -45.5 263t-5.5 139t23 77q20 41 62.5 73t102.5 45q45 12 83.5 6.5t67 -17t54 -35t43 -48t34.5 -56.5l468 100 q-68 175 -180 287z" />
<glyph unicode="&#xf26e;" horiz-adv-x="2304" d="M1391 390v0l-1 1q-15 18 -34.5 37.5t-62.5 57.5t-93.5 62t-95.5 24q-48 0 -83 -21.5t-51 -54t-23 -59t-7 -47.5v0v0q0 -21 7 -48t23 -59t51 -53.5t83 -21.5q45 0 95.5 24t94 62.5t62 57t34.5 37.5zM2103 390q0 21 -7 47.5t-23 59t-51 54t-83 21.5q-45 0 -95.5 -24 t-94 -62.5t-62 -57t-34.5 -37.5l-1 -1v0v0l1 -1q15 -18 34.5 -37.5t62.5 -57.5t93.5 -62t95.5 -24q48 0 83 21.5t51 53.5t23 59t7 48zM2304 393q0 -69 -24 -137.5t-68 -126t-116 -93.5t-159 -36q-68 0 -134 24t-113.5 58.5t-84.5 69.5t-59.5 59t-25.5 24t-22.5 -24 t-54.5 -58.5t-81.5 -69.5t-115 -59t-143.5 -24q-65 0 -123.5 22.5t-96.5 54t-66.5 66.5t-41 59.5t-12.5 32.5q0 -8 -8.5 -26.5t-25 -45.5t-47 -55t-69 -52.5t-96.5 -40t-125 -15.5q-71 0 -130 15.5t-98.5 39.5t-70.5 56.5t-48 63.5t-27.5 63.5t-14 54t-3.5 36.5h217 q0 -55 49 -107.5t126 -52.5q79 0 134.5 67t55.5 148q0 80 -52 136.5t-138 56.5q-5 0 -13 -0.5t-31 -5t-43 -12t-42 -24.5t-34 -40h-195l102 583h602v-174h-445q-27 -159 -41 -248q4 0 16.5 13t31.5 28.5t65 28.5t108 13t114 -20.5t82.5 -49.5t51.5 -58.5t31 -50t11 -20.5 t13 25t36.5 60.5t60.5 71.5t97 61t133 25t140.5 -25t115.5 -60.5t83.5 -71.5t56.5 -61t21 -25q2 0 22 25t56 60.5t83.5 71.5t115.5 61t140 25q92 0 164.5 -35t115.5 -93t65 -125t22 -137z" />
<glyph unicode="&#xf26e;" d="M1401 -11l-6 -6q-113 -114 -259 -175q-154 -64 -317 -64q-165 0 -317 64q-148 63 -259 175q-113 112 -175 258q-42 103 -54 189q-4 28 48 36q51 8 56 -20q1 -1 1 -4q18 -90 46 -159q50 -124 152 -226q98 -98 226 -152q132 -56 276 -56q143 0 276 56q128 55 225 152l6 6 q10 10 25 6q12 -3 33 -22q36 -37 17 -58zM929 604l-66 -66l63 -63q21 -21 -7 -49q-17 -17 -32 -17q-10 0 -19 10l-62 61l-66 -66q-5 -5 -15 -5q-15 0 -31 16l-2 2q-18 15 -18 29q0 7 8 17l66 65l-66 66q-16 16 14 45q18 18 31 18q6 0 13 -5l65 -66l65 65q18 17 48 -13 q27 -27 11 -44zM1400 547q0 -118 -46 -228q-45 -105 -126 -186q-80 -80 -187 -126t-228 -46t-228 46t-187 126q-82 82 -125 186q-15 32 -15 40h-1q-9 27 43 44q50 16 60 -12q37 -99 97 -167h1v339v2q3 136 102 232q105 103 253 103q147 0 251 -103t104 -249 q0 -147 -104.5 -251t-250.5 -104q-58 0 -112 16q-28 11 -13 61q16 51 44 43l14 -3q14 -3 32.5 -6t30.5 -3q104 0 176 71.5t72 174.5q0 101 -72 171q-71 71 -175 71q-107 0 -178 -80q-64 -72 -64 -160v-413q110 -67 242 -67q96 0 185 36.5t156 103.5t103.5 155t36.5 183 q0 198 -141 339q-140 140 -339 140q-200 0 -340 -140q-53 -53 -77 -87l-2 -2q-8 -11 -13 -15.5t-21.5 -9.5t-38.5 3q-21 5 -36.5 16.5t-15.5 26.5v680q0 15 10.5 26.5t27.5 11.5h877q30 0 30 -55t-30 -55h-811v-483h1q40 42 102 84t108 61q109 46 231 46q121 0 228 -46 t187 -126q81 -81 126 -186q46 -112 46 -229zM1369 1128q9 -8 9 -18t-5.5 -18t-16.5 -21q-26 -26 -39 -26q-9 0 -16 7q-106 91 -207 133q-128 56 -276 56q-133 0 -262 -49q-27 -10 -45 37q-9 25 -8 38q3 16 16 20q130 57 299 57q164 0 316 -64q137 -58 235 -152z" />
<glyph unicode="&#xf270;" horiz-adv-x="1792" d="M1551 60q15 6 26 3t11 -17.5t-15 -33.5q-13 -16 -44 -43.5t-95.5 -68t-141 -74t-188 -58t-229.5 -24.5q-119 0 -238 31t-209 76.5t-172.5 104t-132.5 105t-84 87.5q-8 9 -10 16.5t1 12t8 7t11.5 2t11.5 -4.5q192 -117 300 -166q389 -176 799 -90q190 40 391 135z M1758 175q11 -16 2.5 -69.5t-28.5 -102.5q-34 -83 -85 -124q-17 -14 -26 -9t0 24q21 45 44.5 121.5t6.5 98.5q-5 7 -15.5 11.5t-27 6t-29.5 2.5t-35 0t-31.5 -2t-31 -3t-22.5 -2q-6 -1 -13 -1.5t-11 -1t-8.5 -1t-7 -0.5h-5.5h-4.5t-3 0.5t-2 1.5l-1.5 3q-6 16 47 40t103 30 q46 7 108 1t76 -24zM1364 618q0 -31 13.5 -64t32 -58t37.5 -46t33 -32l13 -11l-227 -224q-40 37 -79 75.5t-58 58.5l-19 20q-11 11 -25 33q-38 -59 -97.5 -102.5t-127.5 -63.5t-140 -23t-137.5 21t-117.5 65.5t-83 113t-31 162.5q0 84 28 154t72 116.5t106.5 83t122.5 57 t130 34.5t119.5 18.5t99.5 6.5v127q0 65 -21 97q-34 53 -121 53q-6 0 -16.5 -1t-40.5 -12t-56 -29.5t-56 -59.5t-48 -96l-294 27q0 60 22 119t67 113t108 95t151.5 65.5t190.5 24.5q100 0 181 -25t129.5 -61.5t81 -83t45 -86t12.5 -73.5v-589zM692 597q0 -86 70 -133 q66 -44 139 -22q84 25 114 123q14 45 14 101v162q-59 -2 -111 -12t-106.5 -33.5t-87 -71t-32.5 -114.5z" />
<glyph unicode="&#xf271;" horiz-adv-x="1792" d="M1536 1280q52 0 90 -38t38 -90v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128zM1152 1376v-288q0 -14 9 -23t23 -9 h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 1376v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM1536 -128v1024h-1408v-1024h1408zM896 448h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224 v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224z" />
<glyph unicode="&#xf272;" horiz-adv-x="1792" d="M1152 416v-64q0 -14 -9 -23t-23 -9h-576q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h576q14 0 23 -9t9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23 t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h128q52 0 90 -38t38 -90z" />
@@ -621,20 +621,35 @@
<glyph unicode="&#xf27d;" horiz-adv-x="1792" d="M1709 1018q-10 -236 -332 -651q-333 -431 -562 -431q-142 0 -240 263q-44 160 -132 482q-72 262 -157 262q-18 0 -127 -76l-77 98q24 21 108 96.5t130 115.5q156 138 241 146q95 9 153 -55.5t81 -203.5q44 -287 66 -373q55 -249 120 -249q51 0 154 161q101 161 109 246 q13 139 -109 139q-57 0 -121 -26q120 393 459 382q251 -8 236 -326z" />
<glyph unicode="&#xf27e;" d="M0 1408h1536v-1536h-1536v1536zM1085 293l-221 631l221 297h-634l221 -297l-221 -631l317 -304z" />
<glyph unicode="&#xf280;" d="M0 1408h1536v-1536h-1536v1536zM908 1088l-12 -33l75 -83l-31 -114l25 -25l107 57l107 -57l25 25l-31 114l75 83l-12 33h-95l-53 96h-32l-53 -96h-95zM641 925q32 0 44.5 -16t11.5 -63l174 21q0 55 -17.5 92.5t-50.5 56t-69 25.5t-85 7q-133 0 -199 -57.5t-66 -182.5v-72 h-96v-128h76q20 0 20 -8v-382q0 -14 -5 -20t-18 -7l-73 -7v-88h448v86l-149 14q-6 1 -8.5 1.5t-3.5 2.5t-0.5 4t1 7t0.5 10v387h191l38 128h-231q-6 0 -2 6t4 9v80q0 27 1.5 40.5t7.5 28t19.5 20t36.5 5.5zM1248 96v86l-54 9q-7 1 -9.5 2.5t-2.5 3t1 7.5t1 12v520h-275 l-23 -101l83 -22q23 -7 23 -27v-370q0 -14 -6 -18.5t-20 -6.5l-70 -9v-86h352z" />
<glyph unicode="&#xf281;" horiz-adv-x="1792" />
<glyph unicode="&#xf282;" horiz-adv-x="1792" />
<glyph unicode="&#xf283;" horiz-adv-x="1792" />
<glyph unicode="&#xf284;" horiz-adv-x="1792" />
<glyph unicode="&#xf285;" horiz-adv-x="1792" />
<glyph unicode="&#xf286;" horiz-adv-x="1792" />
<glyph unicode="&#xf287;" horiz-adv-x="1792" />
<glyph unicode="&#xf288;" horiz-adv-x="1792" />
<glyph unicode="&#xf289;" horiz-adv-x="1792" />
<glyph unicode="&#xf28a;" horiz-adv-x="1792" />
<glyph unicode="&#xf28b;" horiz-adv-x="1792" />
<glyph unicode="&#xf28c;" horiz-adv-x="1792" />
<glyph unicode="&#xf28d;" horiz-adv-x="1792" />
<glyph unicode="&#xf28e;" horiz-adv-x="1792" />
<glyph unicode="&#xf281;" horiz-adv-x="1792" d="M1792 690q0 -58 -29.5 -105.5t-79.5 -72.5q12 -46 12 -96q0 -155 -106.5 -287t-290.5 -208.5t-400 -76.5t-399.5 76.5t-290 208.5t-106.5 287q0 47 11 94q-51 25 -82 73.5t-31 106.5q0 82 58 140.5t141 58.5q85 0 145 -63q218 152 515 162l116 521q3 13 15 21t26 5 l369 -81q18 37 54 59.5t79 22.5q62 0 106 -43.5t44 -105.5t-44 -106t-106 -44t-105.5 43.5t-43.5 105.5l-334 74l-104 -472q300 -9 519 -160q58 61 143 61q83 0 141 -58.5t58 -140.5zM418 491q0 -62 43.5 -106t105.5 -44t106 44t44 106t-44 105.5t-106 43.5q-61 0 -105 -44 t-44 -105zM1228 136q11 11 11 26t-11 26q-10 10 -25 10t-26 -10q-41 -42 -121 -62t-160 -20t-160 20t-121 62q-11 10 -26 10t-25 -10q-11 -10 -11 -25.5t11 -26.5q43 -43 118.5 -68t122.5 -29.5t91 -4.5t91 4.5t122.5 29.5t118.5 68zM1225 341q62 0 105.5 44t43.5 106 q0 61 -44 105t-105 44q-62 0 -106 -43.5t-44 -105.5t44 -106t106 -44z" />
<glyph unicode="&#xf282;" horiz-adv-x="1792" d="M69 741h1q16 126 58.5 241.5t115 217t167.5 176t223.5 117.5t276.5 43q231 0 414 -105.5t294 -303.5q104 -187 104 -442v-188h-1125q1 -111 53.5 -192.5t136.5 -122.5t189.5 -57t213 -3t208 46.5t173.5 84.5v-377q-92 -55 -229.5 -92t-312.5 -38t-316 53 q-189 73 -311.5 249t-124.5 372q-3 242 111 412t325 268q-48 -60 -78 -125.5t-46 -159.5h635q8 77 -8 140t-47 101.5t-70.5 66.5t-80.5 41t-75 20.5t-56 8.5l-22 1q-135 -5 -259.5 -44.5t-223.5 -104.5t-176 -140.5t-138 -163.5z" />
<glyph unicode="&#xf283;" horiz-adv-x="2304" d="M0 32v608h2304v-608q0 -66 -47 -113t-113 -47h-1984q-66 0 -113 47t-47 113zM640 256v-128h384v128h-384zM256 256v-128h256v128h-256zM2144 1408q66 0 113 -47t47 -113v-224h-2304v224q0 66 47 113t113 47h1984z" />
<glyph unicode="&#xf284;" horiz-adv-x="1792" d="M1549 857q55 0 85.5 -28.5t30.5 -83.5t-34 -82t-91 -27h-136v-177h-25v398h170zM1710 267l-4 -11l-5 -10q-113 -230 -330.5 -366t-474.5 -136q-182 0 -348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71q244 0 454.5 -124t329.5 -338l2 -4l8 -16 q-30 -15 -136.5 -68.5t-163.5 -84.5q-6 -3 -479 -268q384 -183 799 -366zM896 -234q250 0 462.5 132.5t322.5 357.5l-287 129q-72 -140 -206 -222t-292 -82q-151 0 -280 75t-204 204t-75 280t75 280t204 204t280 75t280 -73.5t204 -204.5l280 143q-116 208 -321 329 t-443 121q-119 0 -232.5 -31.5t-209 -87.5t-176.5 -137t-137 -176.5t-87.5 -209t-31.5 -232.5t31.5 -232.5t87.5 -209t137 -176.5t176.5 -137t209 -87.5t232.5 -31.5z" />
<glyph unicode="&#xf285;" horiz-adv-x="1792" d="M1427 827l-614 386l92 151h855zM405 562l-184 116v858l1183 -743zM1424 697l147 -95v-858l-532 335zM1387 718l-500 -802h-855l356 571z" />
<glyph unicode="&#xf286;" horiz-adv-x="1792" d="M640 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1152 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1664 496v-752h-640v320q0 80 -56 136t-136 56t-136 -56t-56 -136v-320h-640v752q0 16 16 16h96 q16 0 16 -16v-112h128v624q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h16v393q-32 19 -32 55q0 26 19 45t45 19t45 -19t19 -45q0 -36 -32 -55v-9h272q16 0 16 -16v-224q0 -16 -16 -16h-272v-128h16q16 0 16 -16v-112h128 v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-624h128v112q0 16 16 16h96q16 0 16 -16z" />
<glyph unicode="&#xf287;" horiz-adv-x="2304" d="M2288 731q16 -8 16 -27t-16 -27l-320 -192q-8 -5 -16 -5q-9 0 -16 4q-16 10 -16 28v128h-858q37 -58 83 -165q16 -37 24.5 -55t24 -49t27 -47t27 -34t31.5 -26t33 -8h96v96q0 14 9 23t23 9h320q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v96h-96 q-32 0 -61 10t-51 23.5t-45 40.5t-37 46t-33.5 57t-28.5 57.5t-28 60.5q-23 53 -37 81.5t-36 65t-44.5 53.5t-46.5 17h-360q-22 -84 -91 -138t-157 -54q-106 0 -181 75t-75 181t75 181t181 75q88 0 157 -54t91 -138h104q24 0 46.5 17t44.5 53.5t36 65t37 81.5q19 41 28 60.5 t28.5 57.5t33.5 57t37 46t45 40.5t51 23.5t61 10h107q21 57 70 92.5t111 35.5q80 0 136 -56t56 -136t-56 -136t-136 -56q-62 0 -111 35.5t-70 92.5h-107q-17 0 -33 -8t-31.5 -26t-27 -34t-27 -47t-24 -49t-24.5 -55q-46 -107 -83 -165h1114v128q0 18 16 28t32 -1z" />
<glyph unicode="&#xf288;" horiz-adv-x="1792" d="M1150 774q0 -56 -39.5 -95t-95.5 -39h-253v269h253q56 0 95.5 -39.5t39.5 -95.5zM1329 774q0 130 -91.5 222t-222.5 92h-433v-896h180v269h253q130 0 222 91.5t92 221.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348 t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf289;" horiz-adv-x="2304" d="M1645 438q0 59 -34 106.5t-87 68.5q-7 -45 -23 -92q-7 -24 -27.5 -38t-44.5 -14q-12 0 -24 3q-31 10 -45 38.5t-4 58.5q23 71 23 143q0 123 -61 227.5t-166 165.5t-228 61q-134 0 -247 -73t-167 -194q108 -28 188 -106q22 -23 22 -55t-22 -54t-54 -22t-55 22 q-75 75 -180 75q-106 0 -181 -74.5t-75 -180.5t75 -180.5t181 -74.5h1046q79 0 134.5 55.5t55.5 133.5zM1798 438q0 -142 -100.5 -242t-242.5 -100h-1046q-169 0 -289 119.5t-120 288.5q0 153 100 267t249 136q62 184 221 298t354 114q235 0 408.5 -158.5t196.5 -389.5 q116 -25 192.5 -118.5t76.5 -214.5zM2048 438q0 -175 -97 -319q-23 -33 -64 -33q-24 0 -43 13q-26 17 -32 48.5t12 57.5q71 104 71 233t-71 233q-18 26 -12 57t32 49t57.5 11.5t49.5 -32.5q97 -142 97 -318zM2304 438q0 -244 -134 -443q-23 -34 -64 -34q-23 0 -42 13 q-26 18 -32.5 49t11.5 57q108 164 108 358q0 195 -108 357q-18 26 -11.5 57.5t32.5 48.5q26 18 57 12t49 -33q134 -198 134 -442z" />
<glyph unicode="&#xf28a;" d="M1500 -13q0 -89 -63 -152.5t-153 -63.5t-153.5 63.5t-63.5 152.5q0 90 63.5 153.5t153.5 63.5t153 -63.5t63 -153.5zM1267 268q-115 -15 -192.5 -102.5t-77.5 -205.5q0 -74 33 -138q-146 -78 -379 -78q-109 0 -201 21t-153.5 54.5t-110.5 76.5t-76 85t-44.5 83 t-23.5 66.5t-6 39.5q0 19 4.5 42.5t18.5 56t36.5 58t64 43.5t94.5 18t94 -17.5t63 -41t35.5 -53t17.5 -49t4 -33.5q0 -34 -23 -81q28 -27 82 -42t93 -17l40 -1q115 0 190 51t75 133q0 26 -9 48.5t-31.5 44.5t-49.5 41t-74 44t-93.5 47.5t-119.5 56.5q-28 13 -43 20 q-116 55 -187 100t-122.5 102t-72 125.5t-20.5 162.5q0 78 20.5 150t66 137.5t112.5 114t166.5 77t221.5 28.5q120 0 220 -26t164.5 -67t109.5 -94t64 -105.5t19 -103.5q0 -46 -15 -82.5t-36.5 -58t-48.5 -36t-49 -19.5t-39 -5h-8h-32t-39 5t-44 14t-41 28t-37 46t-24 70.5 t-10 97.5q-15 16 -59 25.5t-81 10.5l-37 1q-68 0 -117.5 -31t-70.5 -70t-21 -76q0 -24 5 -43t24 -46t53 -51t97 -53.5t150 -58.5q76 -25 138.5 -53.5t109 -55.5t83 -59t60.5 -59.5t41 -62.5t26.5 -62t14.5 -63.5t6 -62t1 -62.5z" />
<glyph unicode="&#xf28b;" d="M704 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1152 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103 t-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="&#xf28c;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-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 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM864 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192z" />
<glyph unicode="&#xf28d;" d="M1088 352v576q0 14 -9 23t-23 9h-576q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h576q14 0 23 9t9 23zM1536 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.5 t103 -385.5z" />
<glyph unicode="&#xf28e;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-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 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h576q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-576z" />
<glyph unicode="&#xf290;" horiz-adv-x="1792" d="M1757 128l35 -313q3 -28 -16 -50q-19 -21 -48 -21h-1664q-29 0 -48 21q-19 22 -16 50l35 313h1722zM1664 967l86 -775h-1708l86 775q3 24 21 40.5t43 16.5h256v-128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5v128h384v-128q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5v128h256q25 0 43 -16.5t21 -40.5zM1280 1152v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
<glyph unicode="&#xf291;" horiz-adv-x="2048" d="M1920 768q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-15l-115 -662q-8 -46 -44 -76t-82 -30h-1280q-46 0 -82 30t-44 76l-115 662h-15q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5h1792zM485 -32q26 2 43.5 22.5t15.5 46.5l-32 416q-2 26 -22.5 43.5 t-46.5 15.5t-43.5 -22.5t-15.5 -46.5l32 -416q2 -25 20.5 -42t43.5 -17h5zM896 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1280 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1632 27l32 416 q2 26 -15.5 46.5t-43.5 22.5t-46.5 -15.5t-22.5 -43.5l-32 -416q-2 -26 15.5 -46.5t43.5 -22.5h5q25 0 43.5 17t20.5 42zM476 1244l-93 -412h-132l101 441q19 88 89 143.5t160 55.5h167q0 26 19 45t45 19h384q26 0 45 -19t19 -45h167q90 0 160 -55.5t89 -143.5l101 -441 h-132l-93 412q-11 44 -45.5 72t-79.5 28h-167q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45h-167q-45 0 -79.5 -28t-45.5 -72z" />
<glyph unicode="&#xf292;" horiz-adv-x="1792" d="M991 512l64 256h-254l-64 -256h254zM1759 1016l-56 -224q-7 -24 -31 -24h-327l-64 -256h311q15 0 25 -12q10 -14 6 -28l-56 -224q-5 -24 -31 -24h-327l-81 -328q-7 -24 -31 -24h-224q-16 0 -26 12q-9 12 -6 28l78 312h-254l-81 -328q-7 -24 -31 -24h-225q-15 0 -25 12 q-9 12 -6 28l78 312h-311q-15 0 -25 12q-9 12 -6 28l56 224q7 24 31 24h327l64 256h-311q-15 0 -25 12q-10 14 -6 28l56 224q5 24 31 24h327l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h254l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h311 q15 0 25 -12q9 -12 6 -28z" />
<glyph unicode="&#xf293;" d="M841 483l148 -148l-149 -149zM840 1094l149 -149l-148 -148zM710 -130l464 464l-306 306l306 306l-464 464v-611l-255 255l-93 -93l320 -321l-320 -321l93 -93l255 255v-611zM1429 640q0 -209 -32 -365.5t-87.5 -257t-140.5 -162.5t-181.5 -86.5t-219.5 -24.5 t-219.5 24.5t-181.5 86.5t-140.5 162.5t-87.5 257t-32 365.5t32 365.5t87.5 257t140.5 162.5t181.5 86.5t219.5 24.5t219.5 -24.5t181.5 -86.5t140.5 -162.5t87.5 -257t32 -365.5z" />
<glyph unicode="&#xf294;" horiz-adv-x="1024" d="M596 113l173 172l-173 172v-344zM596 823l173 172l-173 172v-344zM628 640l356 -356l-539 -540v711l-297 -296l-108 108l372 373l-372 373l108 108l297 -296v711l539 -540z" />
<glyph unicode="&#xf295;" d="M1280 256q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM512 1024q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5 t112.5 -271.5zM1440 1344q0 -20 -13 -38l-1056 -1408q-19 -26 -51 -26h-160q-26 0 -45 19t-19 45q0 20 13 38l1056 1408q19 26 51 26h160q26 0 45 -19t19 -45zM768 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
<glyph unicode="&#xf296;" horiz-adv-x="1792" />
<glyph unicode="&#xf297;" horiz-adv-x="1792" />
<glyph unicode="&#xf298;" horiz-adv-x="1792" />
<glyph unicode="&#xf299;" horiz-adv-x="1792" />
<glyph unicode="&#xf29a;" horiz-adv-x="1792" />
<glyph unicode="&#xf29b;" horiz-adv-x="1792" />
<glyph unicode="&#xf29c;" horiz-adv-x="1792" />
<glyph unicode="&#xf29d;" horiz-adv-x="1792" />
<glyph unicode="&#xf29e;" horiz-adv-x="1792" />
<glyph unicode="&#xf500;" horiz-adv-x="1792" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 357 KiB

BIN
editor/vendor/font-awesome/fonts/fontawesome-webfont.ttf vendored Executable file → Normal file

Binary file not shown.

BIN
editor/vendor/font-awesome/fonts/fontawesome-webfont.woff vendored Executable file → Normal file

Binary file not shown.

BIN
editor/vendor/font-awesome/fonts/fontawesome-webfont.woff2 vendored Executable file → Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -52,8 +52,8 @@
<!-- node in the palette. -->
<p>Simple sample input node. Just sends a single message when it starts up.
This is not very useful.</p>
<p>Outputs an object called <b>msg</b> containing <b>msg.topic</b> and
<b>msg.payload</b>. msg.payload is a String.</p>
<p>Outputs an object called <code>msg</code> containing <code>msg.topic</code> and
<code>msg.payload</code>. msg.payload is a String.</p>
</script>
<!-- Finally, the node type is registered along with all of its properties -->

View File

@@ -22,10 +22,11 @@
</script>
<script type="text/x-red" data-help-name="sentiment">
<p>Analyses the <b>msg.payload</b> and adds a <b>msg.sentiment</b> object that contains the resulting AFINN-111 sentiment score as <b>msg.sentiment.score</b>.</p>
<p>Analyses the <code>msg.payload</code> and adds a <code>msg.sentiment</code> object
that contains the resulting AFINN-111 sentiment score as <code>msg.sentiment.score</code>.</p>
<p>A score greater than zero is positive and less than zero is negative.</p>
<p>The score typically ranges from -5 to +5, but can go higher and lower.</p>
<p>An object of word score overrides can be supplied as <b>msg.overrides</b>.</p>
<p>An object of word score overrides can be supplied as <code>msg.overrides</code>.</p>
<p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_new">the Sentiment docs here</a>.</p>
</script>

View File

@@ -15,18 +15,10 @@
-->
<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> <span data-i18n="common.label.payload"></span></label>
<select id="node-input-payloadType" style="width:73%">
<option value="date" data-i18n="inject.timestamp"></option>
<option value="string" data-i18n="inject.string"></option>
<option value="none" data-i18n="inject.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" style="width:70%">
<div class="form-row">
<label for="node-input-payload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-input-payload" style="width:300px">
<input type="hidden" id="node-input-payloadType">
</div>
<div class="form-row">
@@ -150,13 +142,7 @@
.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 {
@@ -165,11 +151,11 @@
</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>
to be injected into the flow.</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>
also be set to various other javascript types.</p>
<p>The repeat function allows the payload to be sent on the required schedule.</p>
<p>The <i>Fire once at start</i> option actually waits a short interval before firing
<p>The <i>Inject once at start</i> 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
@@ -186,7 +172,22 @@
defaults: {
name: {value:""},
topic: {value:""},
payload: {value:""},
payload: {value:"", validate:function(v) {
var ptype = $("#node-input-payloadType").val() || this.payloadType;
if (ptype === 'json') {
try {
JSON.parse(v);
return true;
} catch(err) {
return false;
}
} else if (ptype === 'flow' || ptype === 'global' ) {
return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i.test(v);
} else if (ptype === 'num') {
return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
}
return true;
}},
payloadType: {value:"date"},
repeat: {value:""},
crontab: {value:""},
@@ -196,23 +197,51 @@
outputs:1,
icon: "inject.png",
label: function() {
if (this.payloadType === "string") {
if (this.name) {
return this.name;
} else if (this.payloadType === "string" ||
this.payloadType === "str" ||
this.payloadType === "num" ||
this.payloadType === "bool" ||
this.payloadType === "json") {
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;
return this.topic + ":" + this.payload;
} else if (this.payload.length > 0 && this.payload.length < 24) {
return this.payload;
} else {
return this._("inject.inject");
}
} else if (this.payloadType === 'date') {
return this._("inject.timestamp")
} else if (this.payloadType === 'flow' && this.payload.length < 19) {
return 'flow.'+this.payload;
} else if (this.payloadType === 'global' && this.payload.length < 17) {
return 'global.'+this.payload;
} else {
return this._("inject.inject");
}
if ((this.topic.length < 24) && (this.topic.length > 0)) {
return this.name||this.topic;
}
else { return this.name||(this.payloadType==="date"?this._("inject.timestamp"):null)||this._("inject.inject"); }
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.payloadType == null) {
if (this.payload == "") {
this.payloadType = "date";
} else {
this.payloadType = "str";
}
} else if (this.payloadType === 'string' || this.payloadType === 'none') {
this.payloadType = "str";
}
$("#node-input-payloadType").val(this.payloadType);
$("#node-input-payload").typedInput({
default: 'str',
typeField: $("#node-input-payloadType"),
types:['flow','global','str','num','bool','json',{value:"date",label:this._("inject.timestamp"),hasValue:false}]
});
$("#inject-time-type-select").change(function() {
$("#node-input-crontab").val('');
var id = $("#inject-time-type-select option:selected").val();
@@ -373,24 +402,8 @@
$("#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-payload").typedInput('type',this.payloadType);
$("#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();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,16 +50,22 @@ module.exports = function(RED) {
}
this.on("input",function(msg) {
var msg = {topic:this.topic};
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null || this.payloadType === "string") {
msg.payload = this.payload;
} else {
msg.payload = "";
try {
msg = {topic:this.topic};
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null) {
msg.payload = this.payload;
} else if (this.payloadType == 'none') {
msg.payload = "";
} else {
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
}
this.send(msg);
msg = null;
} catch(err) {
this.error(err,msg);
}
this.send(msg);
msg = null;
});
}

View File

@@ -22,7 +22,7 @@
<option value="target" data-i18n="catch.scope.selected"></options>
</select>
</div>
<div class="form-row node-input-target-row" style="display: none;">
<div class="form-row node-input-target-row" style="display: none;min-height: 100px;">
<div id="node-input-catch-target-container-div" style="position: relative; box-sizing: border-box; border-radius: 2px; height: 180px; border: 1px solid #ccc;overflow:hidden; ">
<div style="box-sizing: border-box; line-height: 20px; font-size: 0.8em; border-bottom: 1px solid #ddd; height: 20px;">
<input type="checkbox" data-i18n="[title]catch.label.selectAll" id="node-input-target-node-checkbox-all" style="width: 30px; margin: 0 2px 1px 2px;">
@@ -114,6 +114,17 @@
oneditprepare: function() {
var nodeList = $("#node-input-catch-target-container");
var node = this;
this.resize = function() {
var rows = $("#dialog-form>div:not(.node-input-target-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-target-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-catch-target-container-div").css("height",height+"px");
}
function createNodeList() {
var scope = node.scope || [];
nodeList.empty();
@@ -238,6 +249,7 @@
} else {
$(".node-input-target-row").hide();
}
node.resize();
});
if (this.scope == null) {
$("#node-input-scope-select").val("all");
@@ -245,31 +257,6 @@
$("#node-input-scope-select").val("target");
}
$("#node-input-scope-select").change();
function dialogResize() {
var rows = $("#dialog-form>div:not(.node-input-target-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-target-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-catch-target-container-div").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", dialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-catch');
if (size) {
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
dialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
$( "#dialog" ).off("dialogresize",dialogResize);
});
},
oneditsave: function() {
var scope = $("#node-input-scope-select").children("option:selected").val();
@@ -283,8 +270,10 @@
node.scope.push($(this).data('node-id'));
}
})
}
},
oneditresize: function(size) {
this.resize();
}
});
</script>

View File

@@ -22,7 +22,7 @@
<option value="target" data-i18n="status.scope.selected"></options>
</select>
</div>
<div class="form-row node-input-target-row" style="display: none;">
<div class="form-row node-input-target-row" style="display: none; min-height: 100px;">
<div id="node-input-status-target-container-div" style="position: relative; box-sizing: border-box; border-radius: 2px; height: 180px; border: 1px solid #ccc;overflow:hidden; ">
<div style="box-sizing: border-box; line-height: 20px; font-size: 0.8em; border-bottom: 1px solid #ddd; height: 20px;">
<input type="checkbox" data-i18n="[title]status.label.selectAll" id="node-input-target-node-checkbox-all" style="width: 30px; margin: 0 2px 1px 2px;">
@@ -105,6 +105,17 @@
oneditprepare: function() {
var nodeList = $("#node-input-status-target-container");
var node = this;
this.resize = function() {
var rows = $("#dialog-form>div:not(.node-input-target-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-target-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-status-target-container-div").css("height",height+"px");
}
function createNodeList() {
var scope = node.scope || [];
nodeList.empty();
@@ -219,8 +230,6 @@
$(".node-input-target-node-checkbox").prop('checked',this.checked);
})
$("#node-input-scope-select").change(function(e) {
var scope = $(this).children("option:selected").val();
if (scope === "target") {
@@ -229,6 +238,7 @@
} else {
$(".node-input-target-row").hide();
}
node.resize();
});
if (this.scope == null) {
$("#node-input-scope-select").val("all");
@@ -236,31 +246,6 @@
$("#node-input-scope-select").val("target");
}
$("#node-input-scope-select").change();
function dialogResize() {
var rows = $("#dialog-form>div:not(.node-input-target-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-target-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-status-target-container-div").css("height",height+"px");
};
$( "#dialog" ).on("dialogresize", dialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-status');
if (size) {
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
dialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
$( "#dialog" ).off("dialogresize",dialogResize);
});
},
oneditsave: function() {
var scope = $("#node-input-scope-select").children("option:selected").val();
@@ -276,6 +261,9 @@
})
}
},
oneditresize: function(size) {
this.resize();
}
});
</script>

View File

@@ -39,13 +39,14 @@
</script>
<script type="text/x-red" data-help-name="debug">
<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 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 <code>msg.payload</code>.</p>
<p>Each message will also display the timestamp, <code>msg.topic</code> and the type of 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 <b>msg</b> object.</p>
<p>Optionally can show the complete <code>msg</code> object, and send messages to the console log.</p>
<p>In addition any calls to node.warn or node.error will appear here.</p>
</script>
@@ -127,17 +128,10 @@
}
},
onpaletteadd: function() {
var content = document.createElement("div");
$(content).css({"position":"relative","height":"100%"});
var toolbar = document.createElement("div");
toolbar.id = "debug-toolbar";
content.appendChild(toolbar);
var content = $("<div>").css({"position":"relative","height":"100%"});
var toolbar = $('<div class="sidebar-header"><a id="debug-tab-clear" title="clear log" class="button" href="#"><i class="fa fa-trash"></i></a></div>').appendTo(content);
toolbar.innerHTML = '<div class="pull-right"><a id="debug-tab-clear" title="clear log" class="button" href="#"><i class="fa fa-trash"></i></a></div> ';
var messages = document.createElement("div");
messages.id = "debug-content";
content.appendChild(messages);
var messages = $('<div id="debug-content"/>').appendTo(content);
RED.sidebar.addTab({
id: "debug",
@@ -221,9 +215,9 @@
'</span>';
}
msg.innerHTML += '<span class="debug-message-payload">'+ payload+ '</span>';
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
var atBottom = (sbc.scrollHeight-messages.height()-sbc.scrollTop) < 5;
messageCount++;
$(messages).append(msg);
messages.append(msg);
if (messageCount > 200) {
$("#debug-content .debug-message:first").remove();
@@ -257,17 +251,12 @@
<style>
#debug-content {
position: absolute;
top: 30px;
top: 43px;
bottom: 0px;
left:0px;
right: 0px;
overflow-y: scroll;
}
#debug-toolbar {
padding: 3px 10px;
height: 24px;
background: #f3f3f3;
}
.debug-message {
cursor: pointer;
border-bottom: 1px solid #eee;

View File

@@ -124,7 +124,6 @@ module.exports = function(RED) {
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substr(0,debuglength) +" ....";
}
RED.comms.publish("debug",msg);
}

View File

@@ -44,7 +44,7 @@
<p>Provides 3 outputs... stdout, stderr, and return code.</p>
<p>By default uses exec() which calls the command, then gets a callback on completion, returning the complete result in one message, along with any errors.</p>
<p>Optionally can use spawn() instead, which returns the output from stdout and stderr as the command runs (usually one line at a time). On completion it then returns a return code (on the 3rd output).</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>The optional append gets added to the command after the <code>msg.payload</code> - so you can do things like pipe the result to another command.</p>
<p>Parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p>
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
<p>The blue status icon will be visible while the node is active.</p>

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013, 2015 IBM Corp.
Copyright 2013, 2016 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
</div>
<div class="form-row">
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
<input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1">
<input id="node-input-outputs" style="width: 60px;" value="1">
</div>
<div class="form-tips"><span data-i18n="function.tip"></span></div>
</script>
@@ -88,31 +88,6 @@
min:1
});
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");
that.editor.resize();
}
$( "#dialog" ).on("dialogresize", functionDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-function');
if (size) {
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
functionDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
var height = $( "#dialog" ).dialog('option','height');
$( "#dialog" ).off("dialogresize",functionDialogResize);
});
this.editor = RED.editor.createEditor({
id: 'node-input-func-editor',
mode: 'ace/mode/javascript',
@@ -141,6 +116,17 @@
}
$("#node-input-func").val(this.editor.getValue());
delete this.editor;
},
oneditresize: function(size) {
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");
this.editor.resize();
}
});
</script>

View File

@@ -90,7 +90,34 @@ module.exports = function(RED) {
}
},
context: {
global:RED.settings.functionGlobalContext || {}
set: function() {
node.context().set.apply(node,arguments);
},
get: function() {
return node.context().get.apply(node,arguments);
},
get global() {
return node.context().global;
},
get flow() {
return node.context().flow;
}
},
flow: {
set: function() {
node.context().flow.set.apply(node,arguments);
},
get: function() {
return node.context().flow.get.apply(node,arguments);
}
},
global: {
set: function() {
node.context().global.set.apply(node,arguments);
},
get: function() {
return node.context().global.get.apply(node,arguments);
}
},
setTimeout: function () {
var func = arguments[0];

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013, 2015 IBM Corp.
Copyright 2013, 2016 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,29 +19,41 @@
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<div class="form-row">
<label for="node-input-field"><i class="fa fa-edit"></i> <span data-i18n="template.label.property"></span></label>
<input type="text" id="node-input-field" placeholder="payload" style="width:250px;">
<input type="hidden" id="node-input-fieldType">
</div>
<div class="form-row" style="position: relative; margin-bottom: 0px;">
<label for="node-input-template"><i class="fa fa-file-code-o"></i> <span data-i18n="template.label.template"></span></label>
<input type="hidden" id="node-input-template" autofocus="autofocus">
<select id="node-input-format" style=" font-size: 0.8em; margin-bottom: 3px; width:110px; float:right;">
<option value="handlebars">mustache</option>
<option value="html">HTML</option>
<option value="json">JSON</option>
<option value="markdown">Markdown</option>
<option value="text">none</option>
</select>
<div style="position: absolute; right:0;display:inline-block; text-align: right; font-size: 0.8em;">
<span data-i18n="template.label.format"></span>:
<select id="node-input-format" style="width:110px; font-size: 10px !important; height: 24px; padding:0;">
<option value="handlebars">mustache</option>
<option value="html">HTML</option>
<option value="json">JSON</option>
<option value="markdown">Markdown</option>
<option value="text">none</option>
</select>
</div>
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
</div>
<div class="form-row">
<label for="node-input-field"><i class="fa fa-edit"></i> <span data-i18n="template.label.property"></span></label>
msg.<input type="text" id="node-input-field" placeholder="payload" style="width:170px;">
<label for="node-input-syntax"><i class="fa fa-code"></i> <span data-i18n="template.label.syntax"></span></label>
<select id="node-input-syntax" style="width:180px;">
<option value="mustache" data-i18n="template.label.mustache"></option>
<option value="plain" data-i18n="template.label.plain"></option>
</select>
</div>
</script>
<script type="text/x-red" data-help-name="template">
<p>Creates a new message based on the provided template.</p>
<p>This uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">mustache</a></i> format.</p>
<p>Sets a property based on the provided template.</p>
<p>By default this uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">mustache</a></i>
format, but this can be switched off if required.</p>
<p>For example, when a template of:
<pre>Hello {{name}}. Today is {{date}}</pre>
<p>receives a message containing:
@@ -50,8 +62,10 @@
date: "Monday"
payload: ...
}</pre>
<p>The resulting payload will be:
<p>The resulting property will be:
<pre>Hello Fred. Today is Monday</pre>
<p>By default, mustache will escape any HTML entities in the values it substitutes.
To prevent this, use <code>{{{triple}}}</code> braces.
</script>
<script type="text/javascript">
@@ -61,7 +75,9 @@
defaults: {
name: {value:""},
field: {value:"payload"},
fieldType: {value:"msg"},
format: {value:"handlebars"},
syntax: {value:"mustache"},
template: {value:"This is the payload: {{payload}} !"},
},
inputs:1,
@@ -72,30 +88,19 @@
},
oneditprepare: function() {
var that = this;
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");
that.editor.resize();
};
$( "#dialog" ).on("dialogresize", templateDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-template');
if (size) {
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
templateDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
var height = $( "#dialog" ).dialog('option','height');
$( "#dialog" ).off("dialogresize",templateDialogResize);
if (!this.fieldType) {
this.fieldType = 'msg';
}
if (!this.syntax) {
this.syntax = 'mustache';
$("#node-input-syntax").val(this.syntax);
}
$("#node-input-field").typedInput({
default: 'msg',
types: ['msg','flow','global'],
typeField: $("#node-input-fieldType")
});
this.editor = RED.editor.createEditor({
id: 'node-input-template-editor',
mode: 'ace/mode/html',
@@ -108,7 +113,7 @@
fields:['name','outputs']
});
this.editor.focus();
$("#node-input-format").change(function() {
var mod = "ace/mode/"+$("#node-input-format").val();
that.editor.getSession().setMode({
@@ -120,6 +125,17 @@
oneditsave: function() {
$("#node-input-template").val(this.editor.getValue())
delete this.editor;
},
oneditresize: function(size) {
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");
this.editor.resize();
}
});
</script>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2016 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,33 +23,26 @@ module.exports = function(RED) {
this.name = n.name;
this.field = n.field || "payload";
this.template = n.template;
this.syntax = n.syntax || "mustache";
this.fieldType = n.fieldType || "msg";
var node = this;
var b = node.field.split(".");
var i = 0;
var m = null;
var rec = function(obj) {
i += 1;
if ((i < b.length) && (typeof obj[b[i-1]] === "object")) {
rec(obj[b[i-1]]); // not there yet - carry on digging
}
else {
if (i === b.length) { // we've finished so assign the value
obj[b[i-1]] = mustache.render(node.template,m);
node.send(m);
}
else {
obj[b[i-1]] = {}; // needs to be a new object so create it
rec(obj[b[i-1]]); // and carry on digging
}
}
}
node.on("input", function(msg) {
try {
m = msg;
i = 0;
rec(msg);
var value;
if (node.syntax === "mustache") {
value = mustache.render(node.template,msg);
} else {
value = node.template;
}
if (node.fieldType === 'msg') {
RED.util.setMessageProperty(msg,node.field,value);
} else if (node.fieldType === 'flow') {
node.context().flow.set(node.field,value);
} else if (node.fieldType === 'global') {
node.context().global.set(node.field,value);
}
node.send(msg);
} catch(err) {
node.error(err.message);
}

View File

@@ -14,21 +14,20 @@
limitations under the License.
-->
<!-- First, the content of the edit dialog is defined. -->
<script type="text/x-red" data-template-name="delay">
<div class="form-row">
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
<select id="node-input-pauseType" style="width:270px !important">
<option value="delay" data-i18n="delay.delaymsg"></option>
<option value="random" data-i18n="delay.ramdomdelay"></option>
<option value="random" data-i18n="delay.randomdelay"></option>
<option value="rate" data-i18n="delay.limitrate"></option>
<option value="queue" data-i18n="delay.fairqueue"></option>
</select>
</div>
<div id="delay-details" class="form-row">
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="delay.for"></span></label>
<input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
<input type="text" id="node-input-timeout" placeholder="Time" style="text-align:right; width:50px !important">
<select id="node-input-timeoutUnits" style="width:200px !important">
<option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds" data-i18n="delay.secs"></option>
@@ -40,7 +39,7 @@
<div id="rate-details" class="form-row">
<label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="delay.rate"></span></label>
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
<input type="text" id="node-input-rate" placeholder="1" style="text-align:right; width:30px !important">
<label for="node-input-rateUnits"><span data-i18n="delay.msgper"></span></label>
<select id="node-input-rateUnits" style="width:140px !important">
<option value="second" data-i18n="delay.sec"></option>
@@ -55,9 +54,9 @@
<div id="random-details" class="form-row">
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
<input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
<label for="node-input-randomLast" style="width:20px"> &amp; </label>
<input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:right; width:30px !important">
&nbsp;&nbsp;&amp;&nbsp;&nbsp;
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:right; width:30px !important">
<select id="node-input-randomUnits" style="width:140px !important">
<option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds" data-i18n="delay.secs"></option>
@@ -74,25 +73,23 @@
</script>
<!-- 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 limits messages.</p>
<p>The default delay is 5 seconds and rate limit of 1 msg/second, but both can be configured.</p>
<p>When set to rate limit messages, they are spread across the configured time period. It can
also be set to 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.
<p>The "topic based fair queue" adds messages to a release queue tagged by their <code>msg.topic</code> 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 -->
<script type="text/javascript">
RED.nodes.registerType('delay',{
category: 'function', // the palette category
category: 'function',
color:"#E6E0F8",
defaults: { // defines the editable properties of the node
name: {value:""}, // along with default values.
defaults: {
name: {value:""},
pauseType: {value:"delay", required:true},
timeout: {value:"5", required:true, validate:RED.validators.number()},
timeoutUnits: {value:"seconds"},
@@ -103,10 +100,10 @@
randomUnits: {value: "seconds"},
drop: {value:false}
},
inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n
icon: "timer.png", // set the icon (held in public/icons)
label: function() { // sets the default label contents
inputs:1,
outputs:1,
icon: "timer.png",
label: function() {
if (this.pauseType == "delay") {
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
@@ -122,7 +119,7 @@
return this.name || this._("delay.label.queue")+" "+this.rate+" msg/"+units;
}
},
labelStyle: function() { // sets the class to apply to the label
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {

View File

@@ -144,6 +144,7 @@ module.exports = function(RED) {
this.on("close", function() {
clearInterval(this.intervalID);
this.buffer = [];
node.status({});
});
} else if (this.pauseType === "queue") {

View File

@@ -19,6 +19,7 @@
<span data-i18n="trigger.send"></span>
<select id="node-input-op1type" style="width:200px !important">
<option value="val" data-i18n="trigger.output.string"></option>
<option value="num" data-i18n="trigger.output.number"></option>
<option value="pay" data-i18n="trigger.output.existing"></option>
<option value="nul" data-i18n="trigger.output.nothing"></option>
</select>
@@ -44,13 +45,17 @@
<span data-i18n="trigger.then-send"></span>
<select id="node-input-op2type" style="width:200px !important">
<option value="val" data-i18n="trigger.output.string"></option>
<option value="num" data-i18n="trigger.output.number"></option>
<option value="pay" data-i18n="trigger.output.existing"></option>
<option value="nul" data-i18n="trigger.output.nothing"></option>
</select>
<input style="width: 145px !important" type="text" id="node-input-op2">
</div>
<div class="form-row node-type-wait">
<input type="checkbox" id="node-input-extend" style="margin-left: 5px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
<input type="checkbox" id="node-input-extend" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
</div>
<div class="form-row">
<span data-i18n="trigger.label.reset"></span><input type="text" id="node-input-reset" style="width:240px" data-i18n="[placeholder]trigger.label.resetprompt">
</div>
<br/>
<div class="form-row">
@@ -61,18 +66,20 @@
</script>
<script type="text/x-red" data-help-name="trigger">
<p>Creates two messages on the output separated by a timeout whenever ANY <b>msg</b> arrives on the input.</p>
<p>Creates two messages on the output separated by a timeout whenever <i>any</i> <code>msg</code> arrives on the input.</p>
<p>For example, this can be used to toggle a Raspberry PI GPIO pin on and off.</p>
<p>The two output states can be specified as can the duration of the timer.
Either output can be set to a value, or templated from the inbound
<b>msg</b> using mustache syntax. <pre>The payload is {{payload}}</pre></p>
<p>If the payload is an object then setting the output to <i>existing payload</i> will pass the complete payload object through.</p>
<code>msg</code> using mustache syntax. <pre>The payload is {{payload}}</pre></p>
<p>If the <code>msg.payload</code> is an object then setting the output to
<i>existing payload</i> will pass the complete payload object through.</p>
<p>Optionally the timer can be extended by being retriggered... or not.</p>
<p>By setting the first output to <i>nothing</i>, and selecting extend timer - a watchdog timer can be created.
No output will happen as long as repeated inputs occur within the timeout period.</p>
<p>Setting the timer to 0 creates an "infinite" timeout - the first output will happen but the second
never will, and neither can the first be retriggered - so a true one shot.</p>
<p>If a <b>msg.reset</b> property is present any timeout currently in progress
<p>If a <code>msg.reset</code> property is present, or the <code>msg.payload</code>
matches the optional reset value, any timeout currently in progress
will be cleared and the second output will not happen.</p>
<p>The blue status icon will be visible while the node is active.</p>
</script>
@@ -88,7 +95,8 @@
op2type: {value:"val"},
duration: {value:"250",required:true,validate:RED.validators.number()},
extend: {value:"false"},
units: {value: "ms"},
units: {value:"ms"},
reset: {value:""},
name: {value:""}
},
inputs:1,
@@ -116,14 +124,14 @@
}
});
$("#node-input-op1type").change(function() {
if ($(this).val() == "val") {
if (($(this).val() == "val")||($(this).val() == "num")) {
$("#node-input-op1").show();
} else {
$("#node-input-op1").hide();
}
});
$("#node-input-op2type").change(function() {
if ($(this).val() == "val") {
if (($(this).val() == "val")||($(this).val() == "num")) {
$("#node-input-op2").show();
} else {
$("#node-input-op2").hide();

View File

@@ -25,6 +25,7 @@ module.exports = function(RED) {
this.op2type = n.op2type || "val";
this.extend = n.extend || "false";
this.units = n.units || "ms";
this.reset = n.reset || '';
this.duration = n.duration || 250;
if (this.duration <= 0) { this.duration = 0; }
else {
@@ -34,30 +35,30 @@ module.exports = function(RED) {
}
this.op1Templated = this.op1.indexOf("{{") != -1;
this.op2Templated = this.op2.indexOf("{{") != -1;
if (!isNaN(this.op1)) { this.op1 = Number(this.op1); }
if (!isNaN(this.op2)) { this.op2 = Number(this.op2); }
if ((this.op1type === "num") && (!isNaN(this.op1))) { this.op1 = Number(this.op1); }
if ((this.op2type === "num") && (!isNaN(this.op2))) { this.op2 = Number(this.op2); }
if (this.op1 == "true") { this.op1 = true; }
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.op2 = null; }
try { this.op1 = JSON.parse(this.op1); }
catch(e) { this.op1 = this.op1; }
try { this.op2 = JSON.parse(this.op2); }
catch(e) { this.op2 = this.op2; }
//try { this.op1 = JSON.parse(this.op1); }
//catch(e) { this.op1 = this.op1; }
//try { this.op2 = JSON.parse(this.op2); }
//catch(e) { this.op2 = this.op2; }
var node = this;
var tout = null;
var m2;
this.on("input", function(msg) {
if (msg.hasOwnProperty("reset")) {
if (msg.hasOwnProperty("reset") || ((node.reset !== '')&&(msg.payload == node.reset)) ) {
clearTimeout(tout);
tout = null;
node.status({});
}
else {
if (!tout) {
if ((!tout) && (tout !== 0)) {
if (node.op2type === "pay") { m2 = msg.payload; }
else if (node.op2Templated) { m2 = mustache.render(node.op2,msg); }
else { m2 = node.op2; }
@@ -90,8 +91,8 @@ module.exports = function(RED) {
this.on("close", function() {
if (tout) {
clearTimeout(tout);
node.status({});
}
node.status({});
});
}
RED.nodes.registerType("trigger",TriggerNode);

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013, 2015 IBM Corp.
Copyright 2013, 2016 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -55,33 +55,6 @@
},
oneditprepare: function() {
var that = this;
$( "#node-input-outputs" ).spinner({
min:1
});
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");
that.editor.resize();
};
$( "#dialog" ).on("dialogresize", functionDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-function');
if (size) {
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
functionDialogResize();
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
var height = $( "#dialog" ).dialog('option','height');
$( "#dialog" ).off("dialogresize",functionDialogResize);
});
this.editor = RED.editor.createEditor({
id: 'node-input-info-editor',
mode: 'ace/mode/markdown',
@@ -92,6 +65,17 @@
oneditsave: function() {
$("#node-input-info").val(this.editor.getValue());
delete this.editor;
},
oneditresize: function(size) {
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");
this.editor.resize();
}
});
</script>

View File

@@ -41,16 +41,18 @@
</div>
<div class="form-row">
<label for="node-input-intype"><i class="fa fa-level-up"></i> <span data-i18n="rpi-gpio.label.resistor"></span></label>
<select type="text" id="node-input-intype" style="width: 150px;">
<select type="text" id="node-input-intype" style="width:100px;">
<option value="tri" data-i18n="rpi-gpio.resistor.none"></option>
<option value="up" data-i18n="rpi-gpio.resistor.pullup"></option>
<option value="down" data-i18n="rpi-gpio.resistor.pulldown"></option>
</select>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span data-i18n="rpi-gpio.label.debounce"></span>
<input type="text" id="node-input-debounce" style="width:47px; text-align:right"/>&nbsp;mS
</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%;"><span data-i18n="rpi-gpio.label.readinitial"></span></label>
<label for="node-input-read" style="width:70%;"><span data-i18n="rpi-gpio.label.readinitial"></span></label>
</div>
<br/>
<div class="form-row">
@@ -62,10 +64,11 @@
</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>Raspberry Pi input node. Generates a <code>msg.payload</code> with either a
0 or 1 depending on the state of the input pin.</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>The <code>msg.topic</code> is set to <i>pi/{the pin number}</i></p>
<p>Requires the RPi.GPIO python library version 0.5.10 (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>
@@ -77,8 +80,9 @@
color:"#c6dbef",
defaults: {
name: { value:"" },
pin: { value:"",required:true,validate:RED.validators.number() },
intype: { value: "in" },
pin: { value:"tri",required:true,validate:RED.validators.number() },
intype: { value:"in" },
debounce: { value:"25" },
read: { value:false }
},
inputs:0,
@@ -104,7 +108,7 @@
var alreadyset = this._("rpi-gpio.alreadyset");
$.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type);
if ((data.type === "Model B+") || (data.type === "Model A+")) {
if ((data.type !== "Model B") && (data.type !== "Model A")) {
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0 - BCM0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0 - BCM1"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21 - BCM5"));
@@ -200,11 +204,14 @@
</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>Raspberry Pi output node. Can be used in Digital, PWM or Servo modes.
<p>Digital mode expects a <code>msg.payload</code> with either a 0 or 1 (or true or false),
and 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>When using PWM mode - expects an input value of a number 0 - 100. It can be floating point.</p>
<p>PWM mode can be used to drive a servo using input values between 10 and 20 only.
The GPIO2 pin is best for this as it uses hardware to do the PWM.</p>
<p>Requires the RPi.GPIO python library version 0.5.10 (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>
@@ -247,7 +254,7 @@
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+") || (data.type === "Model A+")) {
if ((data.type !== "Model B") && (data.type !== "Model A")) {
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0 - BCM0"));
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0 - BCM1"));
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21 - BCM5"));
@@ -325,6 +332,7 @@
<option value="7" data-i18n="rpi-gpio.any"></option>
</select>
</div>
<br/>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -333,12 +341,12 @@
<script type="text/x-red" data-help-name="rpi-mouse">
<p>Raspberry Pi mouse button node. Requires a USB mouse.</p>
<p>Generates a <b>msg.payload</b> with
<p>Generates a <code>msg.payload</code> 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
<p>Also sets <code>msg.button</code> to the code value
<ul><li>1 = left</li><li>2 = right</li><li>4 = middle</li></ul>
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>
<p>And sets <code>msg.topic</code> to <i>pi/mouse</i>.</p>
</script>
<script type="text/javascript">
@@ -365,3 +373,37 @@
}
});
</script>
<script type="text/x-red" data-template-name="rpi-keyboard">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="rpi-keyboard">
<p>Raspberry Pi keyboard handling node. Requires a USB keyboard.</p>
<p>Generates a <code>msg.payload</code> with a keycode, and <code>msg.action</code> set to
<i>up, down</i> or <i>repeat</i> whenever a key is pressed or released.</p>
<p>It also sets <code>msg.topic</code> to <i>pi/key</i>.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-keyboard',{
category: 'Raspberry Pi',
label: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" }
},
inputs:0,
outputs:1,
icon: "rpi.png",
label: function() {
return this.name||this._("rpi-gpio.label.pikeyboard");;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013,2014 IBM Corp.
* Copyright 2013,2016 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,21 +18,25 @@ module.exports = function(RED) {
"use strict";
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var fs = require('fs');
var fs = require('fs');
var gpioCommand = __dirname+'/nrgpio';
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
try {
fs.statSync("/dev/ttyAMA0"); // unlikely if not on a Pi
} catch(err) {
//RED.log.info(RED._("rpi-gpio.errors.ignorenode"));
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
}
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
try {
fs.statSync("/usr/share/doc/python-rpi.gpio");
} catch(err) {
RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
}
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
}
@@ -49,6 +53,7 @@ module.exports = function(RED) {
this.pin = n.pin;
this.intype = n.intype;
this.read = n.read || false;
this.debounce = Number(n.debounce || 25);
if (this.read) { this.buttonState = -2; }
var node = this;
if (!pinsInUse.hasOwnProperty(this.pin)) {
@@ -61,11 +66,7 @@ module.exports = function(RED) {
}
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.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
node.running = true;
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
@@ -120,7 +121,6 @@ module.exports = function(RED) {
}
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
function GPIOOutNode(n) {
RED.nodes.createNode(this,n);
this.pin = n.pin;
@@ -160,7 +160,7 @@ module.exports = function(RED) {
if (node.pin !== undefined) {
if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
} else {
} else {
node.child = spawn(gpioCommand, [node.out,node.pin]);
}
node.running = true;
@@ -210,20 +210,6 @@ module.exports = function(RED) {
});
}
var pitype = { type:"" };
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
if (err) {
RED.log.info(RED._("rpi-gpio.errors.version"));
}
else {
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
else { RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim()); }
}
});
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
function PiMouseNode(n) {
@@ -273,6 +259,70 @@ module.exports = function(RED) {
}
RED.nodes.registerType("rpi-mouse",PiMouseNode);
function PiKeyboardNode(n) {
RED.nodes.createNode(this,n);
var node = this;
node.child = spawn(gpioCommand+".py", ["kbd","0"]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
var b = data.toString().trim().split(",");
var act = "up";
if (b[1] === "1") { act = "down"; }
if (b[1] === "2") { act = "repeat"; }
node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
});
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(RED._("rpi-gpio.status.closed")); }
if (node.done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done();
}
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
});
node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
node.on("close", function(done) {
node.status({});
if (node.child != null) {
node.done = done;
node.child.kill('SIGINT');
node.child = null;
}
else { done(); }
});
}
RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);
var pitype = { type:"" };
exec(gpioCommand+" info", function(err,stdout,stderr) {
if (err) {
RED.log.info(RED._("rpi-gpio.errors.version"));
}
else {
try {
var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
pitype.type = info["TYPE"];
}
catch(e) {
RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
}
}
});
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pitype);
});

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
#
# Copyright 2014 IBM Corp.
# Copyright 2014,2016 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,15 +15,19 @@
# Import library functions we need
import RPi.GPIO as GPIO
import struct
import sys
import os
import subprocess
from time import sleep
bounce = 20 # bounce time in mS to apply
bounce = 25;
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:
if len(sys.argv) > 2:
cmd = sys.argv[1].lower()
pin = int(sys.argv[2])
GPIO.setmode(GPIO.BOARD)
@@ -92,18 +96,18 @@ if len(sys.argv) > 1:
elif cmd == "in":
#print "Initialised pin "+str(pin)+" to IN"
bounce = int(sys.argv[4])
def handle_callback(chan):
sleep(bounce/1000)
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)
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)
print GPIO.input(pin)
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=bounce)
@@ -166,12 +170,6 @@ if len(sys.argv) > 1:
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
@@ -193,5 +191,40 @@ if len(sys.argv) > 1:
file.close()
sys.exit(0)
elif cmd == "kbd": # catch keyboard button events
try:
while not os.path.isdir("/dev/input/by-path"):
time.sleep(10)
infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip()
infile_path = "/dev/input/by-path/" + infile
EVENT_SIZE = struct.calcsize('llHHI')
file = open(infile_path, "rb")
event = file.read(EVENT_SIZE)
while event:
(tv_sec, tv_usec, type, code, value) = struct.unpack('llHHI', event)
#if type != 0 or code != 0 or value != 0:
if type == 1:
# type,code,value
print("%u,%u" % (code, value))
event = file.read(EVENT_SIZE)
print "0,0"
file.close()
sys.exit(0)
except:
file.close()
sys.exit(0)
elif len(sys.argv) > 1:
cmd = sys.argv[1].lower()
if cmd == "rev":
print GPIO.RPI_REVISION
elif cmd == "ver":
print GPIO.VERSION
elif cmd == "info":
print GPIO.RPI_INFO
else:
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
print " only ver (gpio version) and info (board information) accept no pin parameter."
else:
print "Bad parameters - {in|out|pwm} {pin} {value|up|down}"
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013,2015 IBM Corp.
Copyright 2013, 2016 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
@@ -27,9 +27,15 @@
</script>
<script type="text/x-red" data-help-name="mqtt in">
<p>MQTT input node. Connects to a broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>.</p>
<p><b>msg.payload</b> is usually a string, but can also be a binary buffer.</p>
<p>Connects to a broker and subscribes to the specified topic.</p>
<p>Outputs a message with the properties:</p>
<ul>
<li><code>msg.topic</code></li>
<li><code>msg.payload</code></li>
<li><code>msg.qos</code></li>
<li><code>msg.retain</code></li>
</ul>
<p><code>msg.payload</code> will be a String, unless it is detected as a binary buffer.</p>
</script>
<script type="text/javascript">
@@ -37,7 +43,7 @@
category: 'input',
defaults: {
name: {value:""},
topic: {value:"",required:true},
topic: {value:"",required:true,validate: RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)},
broker: {type:"mqtt-broker", required:true}
},
color:"#d8bfd8",
@@ -84,9 +90,14 @@
</script>
<script type="text/x-red" data-help-name="mqtt out">
<p>Connects to a MQTT broker and publishes <b>msg.payload</b> either to the <b>msg.topic</b> or to the topic specified in the edit window. The value in the edit window has precedence.</p>
<p>Likewise QoS and/or retain values in the edit panel will overwrite any <b>msg.qos</b> and <b>msg.retain</b> properties. If nothing is set they default to <i>0</i> and <i>false</i> respectively.</p>
<p>If <b>msg.payload</b> contains an object it will be stringified before being sent.</p>
<p>Connects to a MQTT broker and publishes messages.</p>
<p>The topic used can be configured in the node or, if left blank, can be set
by <code>msg.topic</code>.</p>
<p>Likewise the QoS and retain values can be configured in the node or, if left
blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.
By default, messages are published at QoS 0 with the retain flag set to false.</p>
<p>If <code>msg.payload</code> contains an object it will be converted to JSON
before being sent.</p>
</script>
<script type="text/javascript">
@@ -226,7 +237,7 @@
usetls: {value: false},
verifyservercert: { value: false},
compatmode: { value: true},
keepalive: {value:15,validate:RED.validators.number()},
keepalive: {value:60,validate:RED.validators.number()},
cleansession: {value: true},
willTopic: {value:""},
willQos: {value:"0"},

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013,2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ module.exports = function(RED) {
this.brokerurl = "";
this.connected = false;
this.connecting = false;
this.closing = false;
this.options = {};
this.queue = [];
this.subscriptions = {};
@@ -75,7 +76,7 @@ module.exports = function(RED) {
this.verifyservercert = false;
}
if (typeof this.keepalive === 'undefined'){
this.keepalive = 15;
this.keepalive = 60;
} else if (typeof this.keepalive === 'string') {
this.keepalive = Number(this.keepalive);
}
@@ -136,11 +137,17 @@ module.exports = function(RED) {
}
};
this.deregister = function(mqttNode){
this.deregister = function(mqttNode,done){
delete node.users[mqttNode.id];
if (Object.keys(node.users).length === 0) {
node.client.end();
if (node.closing) {
return done();
}
if (Object.keys(node.users).length === 0) {
if (node.client) {
return node.client.end(done);
}
}
done();
};
this.connect = function () {
@@ -177,15 +184,14 @@ module.exports = function(RED) {
if (node.birthMessage) {
node.publish(node.birthMessage);
}
// Send any queued messages
while(node.queue.length) {
var msg = node.queue.shift();
//console.log(msg);
node.publish(msg);
}
});
node.client.on("reconnect", function() {
for (var id in node.users) {
if (node.users.hasOwnProperty(id)) {
node.users[id].status({fill:"yellow",shape:"ring",text:"common.status.connecting"});
}
}
})
// Register disconnect handlers
node.client.on('close', function () {
if (node.connected) {
@@ -265,17 +271,13 @@ module.exports = function(RED) {
retain: msg.retain || false
};
node.client.publish(msg.topic, msg.payload, options, function (err){return});
} else {
if (!node.connecting) {
node.connect();
}
node.queue.push(msg);
}
};
this.on('close', function(done) {
this.closing = true;
if (this.connected) {
this.client.on('close', function() {
this.client.once('close', function() {
done();
});
this.client.end();
@@ -298,10 +300,14 @@ module.exports = function(RED) {
this.topic = n.topic;
this.broker = n.broker;
this.brokerConn = RED.nodes.getNode(this.broker);
if (!/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(this.topic)) {
return this.warn(RED._("mqtt.errors.invalid-topic"));
}
var node = this;
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
if (this.topic) {
node.brokerConn.register(this);
this.brokerConn.subscribe(this.topic,2,function(topic,payload,packet) {
if (isUtf8(payload)) { payload = payload.toString(); }
var msg = {topic:topic,payload:payload, qos: packet.qos, retain: packet.retain};
@@ -313,15 +319,14 @@ module.exports = function(RED) {
if (this.brokerConn.connected) {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
}
node.brokerConn.register(this);
}
else {
this.error(RED._("mqtt.errors.not-defined"));
}
this.on('close', function() {
this.on('close', function(done) {
if (node.brokerConn) {
node.brokerConn.unsubscribe(node.topic,node.id);
node.brokerConn.deregister(node);
node.brokerConn.deregister(node,done);
}
});
} else {
@@ -365,8 +370,8 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
}
node.brokerConn.register(node);
this.on('close', function() {
node.brokerConn.deregister(node);
this.on('close', function(done) {
node.brokerConn.deregister(node,done);
});
} else {
this.error(RED._("mqtt.errors.missing-config"));

View File

@@ -84,74 +84,6 @@
</ul>
</script>
<script type="text/x-red" data-template-name="http request">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:72%;">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="use" data-i18n="httpin.setby"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="http://">
</div>
<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%;"><span data-i18n="httpin.basicauth"></span></label>
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-input-user">
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></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> <span data-i18n="httpin.label.return"></span></label>
<select type="text" id="node-input-ret" style="width:72%;">
<option value="txt" data-i18n="httpin.utf8"></option>
<option value="bin" data-i18n="httpin.binary"></option>
<option value="obj" data-i18n="httpin.json"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></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, 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>, <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>
</ul>
<p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_new">mustache-style</a> tags. These allow the
url to be constructed using values of the incoming message. For example, if the url is set to
<code>example.com/{{{topic}}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.
Using {{{...}}} prevents mustache from escaping characters like / & etc.</p>
<p>
The output message contains the following properties:
<ul>
<li><code>payload</code> is the body of the response</li>
<li><code>statusCode</code> is the status code of the response, or the error code if the request could not be completed</li>
<li><code>headers</code> is an object containing the response headers</li>
</ul>
<p><b>Note</b>: If you need to configure a proxy please add <b>http_proxy=...</b> to your environment variables and restart Node-RED.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('http in',{
category: 'input',
@@ -221,57 +153,4 @@
return this.name?"node_label_italic":"";
}
});
RED.nodes.registerType('http request',{
category: 'function',
color:"rgb(231, 231, 174)",
defaults: {
name: {value:""},
method:{value:"GET"},
ret: {value:"txt"},
url:{value:""},
//user -> credentials
//pass -> credentials
},
credentials: {
user: {type:"text"},
password: {type: "password"}
},
inputs:1,
outputs:1,
icon: "white-globe.png",
label: function() {
return this.name||this._("httpin.httpreq");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.credentials.user || this.credentials.has_password) {
$('#node-input-useAuth').prop('checked', true);
$(".node-input-useAuth-row").show();
} else {
$('#node-input-useAuth').prop('checked', false);
$(".node-input-useAuth-row").hide();
}
$("#node-input-useAuth").change(function() {
if ($(this).is(":checked")) {
$(".node-input-useAuth-row").show();
} else {
$(".node-input-useAuth-row").hide();
$('#node-input-user').val('');
$('#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,2015 IBM Corp.
* Copyright 2013, 2016 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,13 +16,8 @@
module.exports = function(RED) {
"use strict";
var http = require("follow-redirects").http;
var https = require("follow-redirects").https;
var urllib = require("url");
var bodyParser = require("body-parser");
var getBody = require('raw-body');
var mustache = require("mustache");
var querystring = require("querystring");
var cors = require('cors');
var jsonParser = bodyParser.json();
var urlencParser = bodyParser.urlencoded({extended:true});
@@ -67,8 +62,9 @@ module.exports = function(RED) {
var corsSetup = false;
function createRequestWrapper(node,req) {
// This misses a bunch of properties (eg headers). Before we use this function
// need to ensure it captures everything documented by Express and HTTP modules.
var wrapper = {
_req: req
};
@@ -104,7 +100,7 @@ module.exports = function(RED) {
wrapper[f] = function() {
node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.req."+f}));
var result = req[f].apply(req,arguments);
if (result === res) {
if (result === req) {
return wrapper;
} else {
return result;
@@ -160,6 +156,13 @@ module.exports = function(RED) {
return wrapper;
}
var corsHandler = function(req,res,next) { next(); }
if (RED.settings.httpNodeCors) {
corsHandler = cors(RED.settings.httpNodeCors);
RED.httpNode.options("*",corsHandler);
}
function HTTPIn(n) {
RED.nodes.createNode(this,n);
if (RED.settings.httpNodeRoot !== false) {
@@ -191,14 +194,6 @@ module.exports = function(RED) {
}
};
var corsHandler = function(req,res,next) { next(); }
if (RED.settings.httpNodeCors && !corsSetup) {
corsHandler = cors(RED.settings.httpNodeCors);
RED.httpNode.options("*",corsHandler);
corsSetup = true;
}
var httpMiddleware = function(req,res,next) { next(); }
if (RED.settings.httpNodeMiddleware) {
@@ -286,165 +281,4 @@ 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;
var prox, noprox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY != null) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy != null) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY != null) { noprox = process.env.NO_PROXY.split(","); }
this.on("input",function(msg) {
var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
var url = nodeUrl || msg.url;
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
node.warn(RED._("common.errors.nooverride"));
}
if (isTemplatedUrl) {
url = mustache.render(nodeUrl,msg);
}
if (!url) {
node.error(RED._("httpin.errors.no-url"),msg);
return;
}
// url must start http:// or https:// so assume http:// if not set
if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) {
url = "http://"+url;
}
var method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
node.warn(RED._("common.errors.nooverride"));
}
if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter
}
var opts = urllib.parse(url);
opts.method = method;
opts.headers = {};
if (msg.headers) {
for (var v in msg.headers) {
if (msg.headers.hasOwnProperty(v)) {
var name = v.toLowerCase();
if (name !== "content-type" && name !== "content-length") {
// only normalise the known headers used later in this
// function. Otherwise leave them alone.
name = v;
}
opts.headers[name] = msg.headers[v];
}
}
}
if (this.credentials && this.credentials.user) {
opts.auth = this.credentials.user+":"+(this.credentials.password||"");
}
var payload = null;
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") {
payload = msg.payload+"";
} else {
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
payload = querystring.stringify(msg.payload);
} else {
payload = JSON.stringify(msg.payload);
if (opts.headers['content-type'] == null) {
opts.headers['content-type'] = "application/json";
}
}
}
if (opts.headers['content-length'] == null) {
if (Buffer.isBuffer(payload)) {
opts.headers['content-length'] = payload.length;
} else {
opts.headers['content-length'] = Buffer.byteLength(payload);
}
}
}
var urltotest = url;
var noproxy;
if (noprox) {
for (var i in noprox) {
if (url.indexOf(noprox[i]) !== -1) { noproxy=true; }
}
}
if (prox && !noproxy) {
var match = prox.match(/^(http:\/\/)?(.+)?:([0-9]+)?/i);
if (match) {
//opts.protocol = "http:";
//opts.host = opts.hostname = match[2];
//opts.port = (match[3] != null ? match[3] : 80);
opts.headers['Host'] = opts.host;
var heads = opts.headers;
var path = opts.pathname = opts.href;
opts = urllib.parse(prox);
opts.path = opts.pathname = path;
opts.headers = heads;
opts.method = method;
//console.log(opts);
urltotest = match[0];
}
else { node.warn("Bad proxy url: "+process.env.http_proxy); }
}
var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) {
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.payload = "";
// msg.url = url; // revert when warning above finally removed
res.on('data',function(chunk) {
msg.payload += chunk;
});
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(RED._("httpin.errors.json-error")); }
}
node.send(msg);
node.status({});
});
});
req.on('error',function(err) {
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.send(msg);
node.status({fill:"red",shape:"ring",text:err.code});
});
if (payload) {
req.write(payload);
}
req.end();
});
}
RED.nodes.registerType("http request",HTTPRequest,{
credentials: {
user: {type:"text"},
password: {type: "password"}
}
});
}

View File

@@ -0,0 +1,138 @@
<!--
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="http request">
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:72%;">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="use" data-i18n="httpin.setby"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="http://">
</div>
<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%;"><span data-i18n="httpin.basicauth"></span></label>
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-input-user">
</div>
<div class="form-row node-input-useAuth-row">
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></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> <span data-i18n="httpin.label.return"></span></label>
<select type="text" id="node-input-ret" style="width:72%;">
<option value="txt" data-i18n="httpin.utf8"></option>
<option value="bin" data-i18n="httpin.binary"></option>
<option value="obj" data-i18n="httpin.json"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></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, 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>, <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>
</ul>
<p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_new">mustache-style</a> tags. These allow the
url to be constructed using values of the incoming message. For example, if the url is set to
<code>example.com/{{{topic}}}</code>, it will have the value of <code>msg.topic</code> automatically inserted.
Using {{{...}}} prevents mustache from escaping characters like / & etc.</p>
<p>
The output message contains the following properties:
<ul>
<li><code>payload</code> is the body of the response</li>
<li><code>statusCode</code> is the status code of the response, or the error code if the request could not be completed</li>
<li><code>headers</code> is an object containing the response headers</li>
</ul>
<p><b>Note</b>: If you need to configure a proxy please add <b>http_proxy=...</b> to your environment variables and restart Node-RED.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('http request',{
category: 'function',
color:"rgb(231, 231, 174)",
defaults: {
name: {value:""},
method:{value:"GET"},
ret: {value:"txt"},
url:{value:""},
//user -> credentials
//pass -> credentials
},
credentials: {
user: {type:"text"},
password: {type: "password"}
},
inputs:1,
outputs:1,
icon: "white-globe.png",
label: function() {
return this.name||this._("httpin.httpreq");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.credentials.user || this.credentials.has_password) {
$('#node-input-useAuth').prop('checked', true);
$(".node-input-useAuth-row").show();
} else {
$('#node-input-useAuth').prop('checked', false);
$(".node-input-useAuth-row").hide();
}
$("#node-input-useAuth").change(function() {
if ($(this).is(":checked")) {
$(".node-input-useAuth-row").show();
} else {
$(".node-input-useAuth-row").hide();
$('#node-input-user').val('');
$('#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

@@ -0,0 +1,193 @@
/**
* 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.
**/
module.exports = function(RED) {
"use strict";
var http = require("follow-redirects").http;
var https = require("follow-redirects").https;
var urllib = require("url");
var mustache = require("mustache");
var querystring = require("querystring");
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";
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
else { this.reqTimeout = 120000; }
var node = this;
var prox, noprox;
if (process.env.http_proxy != null) { prox = process.env.http_proxy; }
if (process.env.HTTP_PROXY != null) { prox = process.env.HTTP_PROXY; }
if (process.env.no_proxy != null) { noprox = process.env.no_proxy.split(","); }
if (process.env.NO_PROXY != null) { noprox = process.env.NO_PROXY.split(","); }
this.on("input",function(msg) {
var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
var url = nodeUrl || msg.url;
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
node.warn(RED._("common.errors.nooverride"));
}
if (isTemplatedUrl) {
url = mustache.render(nodeUrl,msg);
}
if (!url) {
node.error(RED._("httpin.errors.no-url"),msg);
return;
}
// url must start http:// or https:// so assume http:// if not set
if (!((url.indexOf("http://") === 0) || (url.indexOf("https://") === 0))) {
url = "http://"+url;
}
var method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
node.warn(RED._("common.errors.nooverride"));
}
if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter
}
var opts = urllib.parse(url);
opts.method = method;
opts.headers = {};
if (msg.headers) {
for (var v in msg.headers) {
if (msg.headers.hasOwnProperty(v)) {
var name = v.toLowerCase();
if (name !== "content-type" && name !== "content-length") {
// only normalise the known headers used later in this
// function. Otherwise leave them alone.
name = v;
}
opts.headers[name] = msg.headers[v];
}
}
}
if (this.credentials && this.credentials.user) {
opts.auth = this.credentials.user+":"+(this.credentials.password||"");
}
var payload = null;
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") {
payload = msg.payload+"";
} else {
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
payload = querystring.stringify(msg.payload);
} else {
payload = JSON.stringify(msg.payload);
if (opts.headers['content-type'] == null) {
opts.headers['content-type'] = "application/json";
}
}
}
if (opts.headers['content-length'] == null) {
if (Buffer.isBuffer(payload)) {
opts.headers['content-length'] = payload.length;
} else {
opts.headers['content-length'] = Buffer.byteLength(payload);
}
}
}
var urltotest = url;
var noproxy;
if (noprox) {
for (var i in noprox) {
if (url.indexOf(noprox[i]) !== -1) { noproxy=true; }
}
}
if (prox && !noproxy) {
var match = prox.match(/^(http:\/\/)?(.+)?:([0-9]+)?/i);
if (match) {
//opts.protocol = "http:";
//opts.host = opts.hostname = match[2];
//opts.port = (match[3] != null ? match[3] : 80);
opts.headers['Host'] = opts.host;
var heads = opts.headers;
var path = opts.pathname = opts.href;
opts = urllib.parse(prox);
opts.path = opts.pathname = path;
opts.headers = heads;
opts.method = method;
//console.log(opts);
urltotest = match[0];
}
else { node.warn("Bad proxy url: "+process.env.http_proxy); }
}
var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) {
(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.payload = "";
// msg.url = url; // revert when warning above finally removed
res.on('data',function(chunk) {
msg.payload += chunk;
});
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(RED._("httpin.errors.json-error")); }
}
node.send(msg);
node.status({});
});
});
req.setTimeout(node.reqTimeout, function() {
node.error(RED._("common.notification.errors.no-response"),msg);
setTimeout(function() {
node.status({fill:"red",shape:"ring",text:"common.notification.errors.no-response"});
},10);
req.abort();
});
req.on('error',function(err) {
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.send(msg);
node.status({fill:"red",shape:"ring",text:err.code});
});
if (payload) {
req.write(payload);
}
req.end();
});
}
RED.nodes.registerType("http request",HTTPRequest,{
credentials: {
user: {type:"text"},
password: {type: "password"}
}
});
}

View File

@@ -38,7 +38,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>.
<p>By default, the data received from the WebSocket will be in <code>msg.payload</code>.
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>
@@ -48,7 +48,7 @@
(function() {
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
@@ -59,7 +59,7 @@
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
@@ -67,24 +67,24 @@
$("#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('');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
$("#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;
@@ -93,7 +93,7 @@
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;
@@ -121,7 +121,7 @@
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
RED.nodes.registerType('websocket out',{
category: 'output',
defaults: {
@@ -141,7 +141,7 @@
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
});
RED.nodes.registerType('websocket-listener',{
category: 'config',
defaults: {
@@ -152,7 +152,7 @@
outputs:0,
label: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) != "/") {
if (root.slice(-1) != "/") {
root = root+"/";
}
if (this.path.charAt(0) == "/") {
@@ -164,7 +164,7 @@
},
oneditprepare: function() {
var root = RED.settings.httpNodeRoot;
if (root.slice(-1) == "/") {
if (root.slice(-1) == "/") {
root = root.slice(0,-1);
}
if (root == "") {
@@ -188,7 +188,7 @@
return this.path;
}
});
})();
</script>
@@ -217,15 +217,15 @@
<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 socket
can be configured to encode the entire message object as a JSON string and send that
<p>By default, <code>msg.payload</code> will be sent over the WebSocket. The socket
can be configured to encode the entire <code>msg</code> object as a JSON string and send that
over the WebSocket.</p>
<p>If the message arriving at this node started at a WebSocket In node, the message
will be sent back to the client that triggered the flow. Otherwise, the message
will be broadcast to all connected clients.</p>
<p>If you want to broadcast a message that started at a WebSocket In node, you
should delete the <b>msg._session</b> property within the flow</p>.
should delete the <code>msg._session</code> property within the flow.</p>
</script>
<!-- WebSocket Server configuration node -->

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013,2015 IBM Corp.
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,7 +82,15 @@ module.exports = function(RED) {
RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server
node.server = new ws.Server({server:RED.server,path:path});
node.server = new ws.Server({
server:RED.server,
path:path,
// Disable the deflate option due to this issue
// https://github.com/websockets/ws/pull/632
// that is fixed in the 1.x release of the ws module
// that we cannot currently pickup as it drops node 0.10 support
perMessageDeflate: false
});
// Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events

View File

@@ -31,11 +31,11 @@
<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.
<b>msg.type</b> has the type of thing changed, usually <i>file</i> or <i>directory</i>,
while <b>msg.size</b> holds the file size in bytes.</p>
<p>The full filename of the file that actually changed is put into <code>msg.payload</code>,
while a stringified version of the watch list is returned in <code>msg.topic</code>.</p>
<p><code>msg.file</code> contains just the short filename of the file that changed.
<code>msg.type</code> has the type of thing changed, usually <i>file</i> or <i>directory</i>,
while <code>msg.size</code> holds the file size in bytes.</p>
<p>Of course in Linux, <i>everything</i> is a file and thus can be watched...</p>
<p><b>Note: </b>The directory or file must exist in order to be watched. If the file
or directory gets deleted it may no longer be monitored even if it gets re-created.</p>

View File

@@ -56,7 +56,7 @@
</script>
<script type="text/x-red" data-help-name="tcp in">
<p>Provides a choice of tcp inputs. Can either connect to a remote tcp port,
<p>Provides a choice of TCP inputs. Can either connect to a remote TCP port,
or accept incoming connections.</p>
</script>
@@ -147,12 +147,14 @@
</script>
<script type="text/x-red" data-help-name="tcp out">
<p>Provides a choice of tcp outputs. Can either connect to a remote tcp port,
<p>Provides a choice of TCP outputs. Can either connect to a remote TCP port,
accept incoming connections, or reply to messages received from a TCP In node.</p>
<p>Only <b>msg.payload</b> is sent.</p>
<p>If <b>msg.payload</b> is a string containing a Base64 encoding of binary
<p>Only <code>msg.payload</code> is sent.</p>
<p>If <code>msg.payload</code> is a string containing a Base64 encoding of binary
data, the Base64 decoding option will cause it to be converted back to binary
before being sent.</p>
<p>If <code>msg._session</code> is not present the payload is
sent to <b>all</b> connected clients.</p>
</script>
<script type="text/javascript">
@@ -247,12 +249,12 @@
</script>
<script type="text/x-red" data-help-name="tcp request">
<p>A simple TCP request node - sends the <b>msg.payload</b> to a server tcp port and expects a response.</p>
<p>Connects, sends the "request", reads the "response". It can either count a number of
<p>A simple TCP request node - sends the <code>msg.payload</code> to a server tcp port and expects a response.</p>
<p>Connects, sends the "request", and reads the "response". It can either count a number of
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>
<p>The response will be output in <code>msg.payload</code> 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 <code>msg.host</code> and <code>msg.port</code> properties.</p>
</script>
<script type="text/javascript">

View File

@@ -119,7 +119,7 @@ module.exports = function(RED) {
this.on('close', function(done) {
node.done = done;
this.closing = true;
if (client) { client.end(); }
if (client) { client.destroy(); }
clearTimeout(reconnectTimeout);
if (!node.connected) { done(); }
});
@@ -242,6 +242,8 @@ module.exports = function(RED) {
node.log(RED._("tcpin.errors.error",{error:err.toString()}));
});
client.on('end', function (err) {
node.status({});
node.connected = false;
});
client.on('close', function() {
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
@@ -274,7 +276,7 @@ module.exports = function(RED) {
}
if (node.doend === true) {
end = true;
if (client) { client.end(); }
if (client) { node.status({}); client.destroy(); }
}
}
});
@@ -282,7 +284,7 @@ module.exports = function(RED) {
node.on("close", function(done) {
node.done = done;
this.closing = true;
if (client) { client.end(); }
if (client) { client.destroy(); }
clearTimeout(reconnectTimeout);
if (!node.connected) { done(); }
});
@@ -301,6 +303,17 @@ module.exports = function(RED) {
}
}
}
else {
for (var i in connectionPool) {
if (Buffer.isBuffer(msg.payload)) {
connectionPool[i].write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
connectionPool[i].write(new Buffer(msg.payload,'base64'));
} else {
connectionPool[i].write(new Buffer(""+msg.payload));
}
}
}
});
} else {
var connectedSockets = [];
@@ -397,11 +410,9 @@ module.exports = function(RED) {
}
if (!node.connected) {
client = net.Socket();
if (socketTimeout) { client.setTimeout(socketTimeout); }
//node.status({});
if (socketTimeout !== null) { client.setTimeout(socketTimeout); }
var host = node.server || msg.host;
var port = node.port || msg.port;
var m;
if (host && port) {
client.connect(port, host, function() {
@@ -417,10 +428,12 @@ module.exports = function(RED) {
client.on('data', function(data) {
if (node.out == "sit") { // if we are staying connected just send the buffer
node.send({"payload": data});
msg.payload = data;
node.send(msg);
}
else if (node.splitc === 0) {
node.send({"payload": data});
msg.payload = data;
node.send(msg);
}
else {
for (var j = 0; j < data.length; j++ ) {
@@ -433,10 +446,10 @@ module.exports = function(RED) {
else {
node.tout = setTimeout(function () {
node.tout = null;
m = new Buffer(i+1);
buf.copy(m,0,0,i+1);
node.send({"payload":m});
//if (client) { client.end(); }
msg.payload = new Buffer(i+1);
buf.copy(msg.payload,0,0,i+1);
node.send(msg);
if (client) { node.status({}); client.destroy(); }
}, node.splitc);
i = 0;
buf[0] = data[j];
@@ -447,10 +460,10 @@ module.exports = function(RED) {
buf[i] = data[j];
i += 1;
if ( i >= node.splitc) {
m = new Buffer(i);
buf.copy(m,0,0,i);
node.send({"payload":m});
//if (client) { client.end(); }
msg.payload = new Buffer(i);
buf.copy(msg.payload,0,0,i);
node.send(msg);
if (client) { node.status({}); client.destroy(); }
i = 0;
}
}
@@ -459,10 +472,10 @@ module.exports = function(RED) {
buf[i] = data[j];
i += 1;
if (data[j] == node.splitc) {
m = new Buffer(i);
buf.copy(m,0,0,i);
node.send({"payload":m});
//if (client) { client.end(); }
msg.payload = new Buffer(i);
buf.copy(msg.payload,0,0,i);
node.send(msg);
if (client) { node.status({}); client.destroy(); }
i = 0;
}
}
@@ -471,34 +484,36 @@ module.exports = function(RED) {
});
client.on('end', function() {
//console.log("END");
node.connected = false;
node.status({});
node.send({"payload":m});
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
client = null;
});
client.on('close', function() {
node.status({});
//console.log("CLOSE");
node.connected = false;
if (node.done) { node.done(); }
});
client.on('error', function() {
node.error(RED._("tcpin.errors.connect-fail"),msg);
//console.log("ERROR");
node.connected = false;
node.status({fill:"red",shape:"ring",text:"common.status.error"});
if (client) { client.end(); }
node.error(RED._("tcpin.errors.connect-fail"),msg);
if (client) { client.destroy(); }
});
client.on('timeout',function() {
node.warn(RED._("tcpin.errors.connect-timeout"));
//console.log("TIMEOUT");
node.connected = false;
node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
//node.warn(RED._("tcpin.errors.connect-timeout"));
if (client) {
client.end();
setTimeout(function() {
client.connect(port, host, function() {
node.connected = true;
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
client.write(msg.payload);
});
},reconnectTime);
client.connect(port, host, function() {
node.connected = true;
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
}
});
}
@@ -511,6 +526,7 @@ module.exports = function(RED) {
buf = null;
client.destroy();
}
node.status({});
if (!node.connected) { done(); }
});

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013,2016 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
</div>
<div class="form-row node-input-iface">
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interface">
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interfaceprompt">
</div>
<div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>
@@ -52,12 +52,15 @@
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips"><span data-i18n="udp.tip.in"></span></div>
<div class="form-tips" id="udpporttip"><span data-i18n="[html]udp.tip.port"></span></div>
</script>
<script type="text/x-red" data-help-name="udp in">
<p>A udp input node, that produces a <b>msg.payload</b> containing a <i>BUFFER</i>, string, or base64 encoded string. Supports multicast.</p>
<p>It also provides <b>msg.ip</b> and <b>msg.port</b> to the ip address and port from which the message was received.</p>
<p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
<p>A UDP input node, that produces a <code>msg.payload</code> containing a
Buffer, string, or base64 encoded string. Supports multicast.</p>
<p>It also provides <code>msg.ip</code> and <code>msg.port</code> set to the
ip address and port from which the message was received.</p>
<p><b>Note</b>: On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
</script>
<script type="text/javascript">
@@ -98,6 +101,21 @@
}
});
$("#node-input-multicast").change();
var porttip = this._("udp.tip.port");
var alreadyused = this._("udp.errors.alreadyused");
var portsInUse = {};
$.getJSON('udp-ports/'+this.id,function(data) {
portsInUse = data || {};
$('#udpporttip').html(porttip + Object.keys(data||{}));
});
$("#node-input-port").change(function() {
var portnew = $("#node-input-port").val();
if (portsInUse.hasOwnProperty($("#node-input-port").val())) {
console.log($("#node-input-port").val());
RED.notify(alreadyused+" "+$("#node-input-port").val(),"warn");
}
});
}
});
</script>
@@ -147,10 +165,10 @@
</script>
<script type="text/x-red" data-help-name="udp out">
<p>This node sends <b>msg.payload</b> to the designated udp host and port. Supports multicast.</p>
<p>You may also use <b>msg.ip</b> and <b>msg.port</b> to set the destination values.<br/><b>Note</b>: the statically configured values have precedence.</p>
<p>This node sends <code>msg.payload</code> to the designated UDP host and port. Supports multicast.</p>
<p>You may also use <code>msg.ip</code> and <code>msg.port</code> to set the destination values, but the statically configured values have precedence.</p>
<p>If you select broadcast either set the address to the local broadcast ip address, or maybe try 255.255.255.255, which is the global broadcast address.</p>
<p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
<p><b>Note</b>: On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
</script>
<script type="text/javascript">

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013,2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
module.exports = function(RED) {
"use strict";
var dgram = require('dgram');
var udpInputPortsInUse = {};
// The Input Node
function UDPin(n) {
@@ -28,8 +29,16 @@ module.exports = function(RED) {
this.multicast = n.multicast;
this.ipv = n.ipv || "udp4";
var node = this;
if (!udpInputPortsInUse.hasOwnProperty(this.port)) {
udpInputPortsInUse[this.port] = n.id;
}
else {
node.warn(RED._("udp.errors.alreadyused",node.port));
}
var server = dgram.createSocket({type:node.ipv, reuseAddr:true}); // default to ipv4
var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var server = dgram.createSocket(opts); // default to udp4
server.on("error", function (err) {
if ((err.code == "EACCES") && (node.port < 1024)) {
@@ -74,6 +83,10 @@ module.exports = function(RED) {
});
node.on("close", function() {
console.log("ID=",node.id);
if (udpInputPortsInUse[node.port] === node.id) {
delete udpInputPortsInUse[node.port];
}
try {
server.close();
node.log(RED._("udp.status.listener-stopped"));
@@ -84,9 +97,11 @@ module.exports = function(RED) {
server.bind(node.port,node.iface);
}
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-in.read'), function(req,res) {
res.json(udpInputPortsInUse);
});
RED.nodes.registerType("udp in",UDPin);
// The Output Node
function UDPout(n) {
RED.nodes.createNode(this,n);
@@ -100,7 +115,9 @@ module.exports = function(RED) {
this.ipv = n.ipv || "udp4";
var node = this;
var sock = dgram.createSocket({type:node.ipv, reuseAddr:true}); // default to ipv4
var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var sock = dgram.createSocket(opts); // default to udp4
sock.on("error", function(err) {
// Any async error will also get reported in the sock.send call.

View File

@@ -37,8 +37,6 @@
"repeat": "Repeat"
},
"timestamp": "timestamp",
"string": "string",
"blank": "blank",
"none": "none",
"interval": "interval",
"interval-time": "interval between times",
@@ -47,6 +45,7 @@
"minutes": "minutes",
"hours": "hours",
"between": "between",
"previous": "previous value",
"at": "at",
"and": "and",
"every": "every",
@@ -138,7 +137,11 @@
"template": {
"label": {
"template": "Template",
"property": "Property"
"property": "Set property",
"format": "Syntax Highlight",
"syntax": "Format",
"mustache": "Mustache template",
"plain": "Plain text"
},
"templatevalue": "This is the payload: {{payload}} !"
},
@@ -146,7 +149,7 @@
"action": "Action",
"for": "For",
"delaymsg": "Delay message",
"ramdomdelay": "Random delay",
"randomdelay": "Random delay",
"limitrate": "Limit rate to",
"fairqueue": "Topic based fair queue",
"milisecs": "Miliseconds",
@@ -165,7 +168,7 @@
"label": {
"delay": "delay",
"limit": "limit",
"random": "ramdom",
"random": "random",
"queue": "queue"
},
"error": {
@@ -177,8 +180,9 @@
"then": "then",
"then-send": "then send",
"output": {
"string": "the string payload",
"existing": "the existing message",
"string": "the string",
"number": "the number",
"existing": "the existing msg.payload",
"nothing": "nothing"
},
"wait-reset": "wait to be reset",
@@ -189,11 +193,13 @@
"m": "Minutes",
"h": "Hours"
},
"extend": "extend delay if new message arrives",
"tip": "The node can be reset by sending a message with the <b>msg.reset</b> property set",
"extend": " extend delay if new message arrives",
"tip": "The node can also be reset by sending a message with the <code>msg.reset</code> property set to any value.",
"label": {
"trigger": "trigger",
"trigger-block": "trigger & block"
"trigger-block": "trigger & block",
"reset": "and reset if msg.payload == ",
"resetprompt": "(optional reset value)"
}
},
"comment": {
@@ -371,7 +377,8 @@
"using": "using",
"output": "Output",
"group": "Group",
"interface": "Interface",
"interface": "Local IP",
"interfaceprompt": "(optional) local ip address to bind to",
"send": "Send a",
"toport": "to port",
"address": "Address",
@@ -398,7 +405,8 @@
},
"tip": {
"in": "Tip: Make sure your firewall will allow the data in.",
"out": "Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>."
"out": "Tip: leave address and port blank if you want to set using <code>msg.ip</code> and <code>msg.port</code>.",
"port": "Ports already in use: "
},
"status": {
"listener-at": "udp listener at __host__:__port__",
@@ -417,7 +425,8 @@
"interface": "Must be ip address of the required interface",
"ip-notset": "udp: ip address not set",
"port-notset": "udp: port not set",
"port-invalid": "udp: port number not valid"
"port-invalid": "udp: port number not valid",
"alreadyused": "udp: port already in use"
}
},
"switch": {
@@ -459,7 +468,8 @@
"replace": "Replace with"
},
"errors": {
"invalid-from": "Invalid 'from' property: __error__"
"invalid-from": "Invalid 'from' property: __error__",
"invalid-json": "Invalid 'to' JSON property"
}
},
"range": {
@@ -531,7 +541,8 @@
},
"output": {
"html": "the html content of the elements",
"text": "only the text content of the elements"
"text": "only the text content of the elements",
"attr": "an object of any attributes of the elements"
},
"format": {
"single": "as a single message containing an array",
@@ -565,8 +576,10 @@
"readinitial": "Read initial state of pin on deploy/restart?",
"type": "Type",
"initpin": "Initialise pin state?",
"debounce": "Debounce",
"button": "Button",
"pimouse": "Pi Mouse",
"pikeyboard": "Pi Keyboard",
"left": "Left",
"right": "Right",
"middle": "Middle"
@@ -624,8 +637,13 @@
"tail": {
"label": {
"filename": "Filename",
"type": "File type",
"splitlines": "Split lines on \\n?"
},
"action": {
"text": "Text - returns String",
"binary": "Binary - returns Buffer"
},
"errors": {
"windowsnotsupport": "Not currently supported on Windows."
}

View File

@@ -20,7 +20,7 @@
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row">
<span data-i18n="switch.label.property"></span> msg.<input type="text" id="node-input-property" style="width: 200px;"/>
<span data-i18n="switch.label.property"></span> <input type="text" id="node-input-property" style="width: 300px;"/>
</div>
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
@@ -39,11 +39,11 @@
</script>
<script type="text/x-red" data-help-name="switch">
<p>A simple function node to route messages based on its properties.</p>
<p>A node to route messages based on property values.</p>
<p>When a message arrives, the selected property is evaluated against each
of the defined rules. The message is then sent to the output of <i>all</i>
rules that pass.</p>
<p>Note: the <i>otherwise</i> rule applies as a "not any of" the rules preceding it.</p>
<p><b>Note</b>: the <i>otherwise</i> rule applies as a "not any of" the rules preceding it.</p>
</script>
<script type="text/javascript">
@@ -53,6 +53,7 @@
defaults: {
name: {value:""},
property: {value:"payload", required:true},
propertyType: { value:"msg" },
rules: {value:[{t:"eq", v:""}]},
checkall: {value:"true", required:true},
outputs: {value:1}
@@ -64,7 +65,10 @@
return this.name||"switch";
},
oneditprepare: function() {
var node = this;
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false};
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global']});
var operators = [
{v:"eq",t:"=="},
{v:"neq",t:"!="},
@@ -85,7 +89,7 @@
var andLabel = this._("switch.and");
var caseLabel = this._("switch.ignorecase");
function resizeRule(rule,width) {
this.resizeRule = function(rule,newWidth) {
var selectField = rule.find("select");
var type = selectField.children("option:selected").val();
var valueField = rule.find(".node-input-rule-value");
@@ -102,14 +106,14 @@
selectField.width(selectWidth);
if (type === "btwn") {
var labelWidth = rule.find(".node-input-tule-btwn-label").width();
btwnField1.width((width-256-labelWidth)/2);
btwnField2.width((width-256-labelWidth)/2);
var labelWidth = rule.find(".node-input-rule-btwn-label").width();
btwnField1.typedInput("width",(newWidth-selectWidth-120));
btwnField2.typedInput("width",(newWidth-selectWidth-120));
} else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
// valueField.hide();
} else {
valueField.width(width-selectWidth-120);
valueField.typedInput("width",(newWidth-selectWidth-120));
}
}
}
@@ -118,6 +122,8 @@
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; padding-left: 175px;"}).appendTo(container);
var row3 = $('<div/>',{style:"padding-top: 5px; padding-left: 120px;"}).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);
@@ -125,11 +131,10 @@
selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t));
}
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px; width: 145px;"}).appendTo(row);
var btwnField = $('<span/>').appendTo(row);
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px; width: 50px;"}).appendTo(btwnField);
var btwnAndLabel = $('<span/>',{class:"node-input-tule-btwn-label"}).text(" "+andLabel+" ").appendTo(btwnField);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 50px;margin-left:2px;"}).appendTo(btwnField);
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px; width: 145px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num',previousValueType]});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]});
var finalspan = $('<span/>',{style:"float: right;margin-right: 10px;"}).appendTo(row);
finalspan.append(' &#8594; <span class="node-input-rule-index">'+i+'</span> ');
@@ -141,24 +146,28 @@
$('<label/>',{for:"node-input-rule-case-"+i,style:"margin-left: 3px;"}).text(caseLabel).appendTo(row2);
selectField.change(function() {
var width = $("#node-input-rule-container").width();
resizeRule(container,width);
var type = selectField.children("option:selected").val();
node.resizeRule(container,width);
if (type === "btwn") {
valueField.hide();
btwnField.show();
valueField.parent().hide();
btwnValueField.parent().show();
} else {
btwnField.hide();
btwnValueField.parent().hide();
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
valueField.hide();
valueField.parent().hide();
} else {
valueField.show();
valueField.parent().show();
}
}
if (type === "regex") {
row2.show();
row3.hide();
} else if (type === "btwn"){
row2.hide();
row3.show();
} else {
row2.hide();
row3.hide();
}
});
@@ -177,10 +186,13 @@
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
if (rule.t == "btwn") {
btwnValueField.val(rule.v);
btwnValue2Field.val(rule.v2);
btwnValueField.typedInput('value',rule.v);
btwnValueField.typedInput('type',rule.vt||'num');
btwnValue2Field.typedInput('value',rule.v2);
btwnValue2Field.typedInput('type',rule.v2t||'num');
} else if (typeof rule.v != "undefined") {
valueField.val(rule.v);
valueField.typedInput('value',rule.v);
valueField.typedInput('type',rule.vt||'str');
}
if (rule.case) {
caseSensitive.prop('checked',true);
@@ -200,23 +212,6 @@
generateRule(i+1,rule);
}
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");
var rules = $("#node-input-rule-container").children();
var newWidth = $("#node-input-rule-container").width();
rules.each(function(i) {
resizeRule($(this),newWidth);
})
};
$( "#node-input-rule-container" ).sortable({
axis: "y",
update: function( event, ui ) {
@@ -229,21 +224,6 @@
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) {
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
switchDialogResize();
} else {
setTimeout(switchDialogResize,10);
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
$( "#dialog" ).off("dialogresize",switchDialogResize);
});
},
oneditsave: function() {
var rules = $("#node-input-rule-container").children();
@@ -256,10 +236,13 @@
var r = {t:type};
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
if (type === "btwn") {
r.v = rule.find(".node-input-rule-btwn-value").val();
r.v2 = rule.find(".node-input-rule-btwn-value2").val();
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');
r.v2 = rule.find(".node-input-rule-btwn-value2").typedInput('value');
r.v2t = rule.find(".node-input-rule-btwn-value2").typedInput('type');
} else {
r.v = rule.find(".node-input-rule-value").val();
r.v = rule.find(".node-input-rule-value").typedInput('value');
r.vt = rule.find(".node-input-rule-value").typedInput('type');
}
if (type === "regex") {
r.case = rule.find(".node-input-rule-case").prop("checked");
@@ -267,7 +250,25 @@
}
node.rules.push(r);
});
node.outputs = node.rules.length;
this.outputs = node.rules.length;
this.propertyType = $("#node-input-property").typedInput('type');
},
oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
var height = size.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");
var rules = $("#node-input-rule-container").children();
var newWidth = $("#node-input-rule-container").width();
var node = this;
rules.each(function(i) {
node.resizeRule($(this),newWidth);
})
}
});
</script>

View File

@@ -37,30 +37,55 @@ module.exports = function(RED) {
RED.nodes.createNode(this, n);
this.rules = n.rules || [];
this.property = n.property;
this.propertyType = n.propertyType || "msg";
this.checkall = n.checkall || "true";
var propertyParts = (n.property || "payload").split(".");
this.previousValue = null;
var node = this;
for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i];
if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v);
rule.v2 = Number(rule.v2);
if (!rule.vt) {
rule.vt = 'str';
}
if (rule.vt === 'str' || rule.vt === 'num') {
if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v);
}
}
if (typeof rule.v2 !== 'undefined') {
if (!rule.v2t) {
rule.v2t = 'str';
}
if (rule.v2t === 'str' || rule.v2t === 'num') {
if (!isNaN(Number(rule.v2))) {
rule.v2 = Number(rule.v2);
}
}
}
}
this.on('input', function (msg) {
var onward = [];
try {
var prop = propertyParts.reduce(function (obj, i) {
return obj[i]
}, msg);
var prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
var elseflag = true;
for (var i=0; i<node.rules.length; i+=1) {
var rule = node.rules[i];
var test = prop;
var v1,v2;
if (rule.vt === 'prev') {
v1 = node.previousValue;
} else {
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
}
v2 = rule.v2;
if (rule.v2t === 'prev') {
v2 = node.previousValue;
} else if (typeof v2 !== 'undefined') {
v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
}
node.previousValue = prop;
if (rule.t == "else") { test = elseflag; elseflag = true; }
if (operators[rule.t](test,rule.v, rule.v2, rule.case)) {
if (operators[rule.t](test,v1,v2,rule.case)) {
onward.push(msg);
elseflag = false;
if (node.checkall == "false") { break; }

View File

@@ -17,7 +17,7 @@
<script type="text/x-red" data-template-name="change">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
<input type="text" id="node-input-name" style="width: 370px;" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
@@ -33,12 +33,12 @@
</script>
<script type="text/x-red" data-help-name="change">
<p>Set, change or delete properties of a message.</p>
<p>The node can specify multiple rules that will be applied to the message in turn.</p>
<p>Set, change or delete properties of a message, flow context or global context.</p>
<p>The node can specify multiple rules that will be applied in turn.</p>
<p>The available operations are:</p>
<ul>
<li><b>Set</b> - set a property. The <b>to</b> property can either be a string value, or reference
another message property by name, for example: <code>msg.topic</code>.</li>
<li><b>Set</b> - set a property. The value can be a variety of different types, or
can be taken from an existing message or context property.</li>
<li><b>Change</b> - search &amp; replace parts of the property. If regular expressions
are enabled, the <b>replace with</b> property can include capture groups, for example <code>$1</code></li>
<li><b>Delete</b> - delete a property.</li>
@@ -51,7 +51,7 @@
category: 'function',
defaults: {
name: {value:""},
rules:{value:[{t:"set",p:"payload",to:""}]},
rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}]},
// legacy
action: {value:""},
property: {value:""},
@@ -77,11 +77,11 @@
} else {
if (this.rules.length == 1) {
if (this.rules[0].t === "set") {
return this._("change.label.set",{property:"msg."+this.rules[0].p});
return this._("change.label.set",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
} else if (this.rules[0].t === "change") {
return this._("change.label.change",{property:"msg."+this.rules[0].p});
return this._("change.label.change",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
} else {
return this._("change.label.delete",{property:"msg."+this.rules[0].p});
return this._("change.label.delete",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
}
} else {
return this._("change.label.changeCount",{count:this.rules.length});
@@ -99,14 +99,22 @@
var search = this._("change.action.search");
var replace = this._("change.action.replace");
var regex = this._("change.label.regex");
if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
$("#node-input-action").change();
function resizeRule(rule,width) {
rule.find('input[type="text"]').width(width-220);
}
function generateRule(rule) {
if (rule.t === "change" && rule.re) {
rule.fromt = 're';
delete rule.re;
}
if (rule.t === "set" && !rule.tot) {
if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
rule.to = rule.to.substring(4);
rule.tot = "msg";
} else {
rule.tot = "str";
}
}
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
var row1 = $('<div/>').appendTo(container);
@@ -114,41 +122,30 @@
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 100px"}).appendTo(row1);
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 110px; margin-right: 10px;"}).appendTo(row1);
var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del}];
for (var i=0;i<3;i++) {
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
}
$('<div/>',{style:"display:inline-block; width: 50px; text-align: right;"}).text("msg.").appendTo(row1);
var propertyName = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-name",type:"text"}).appendTo(row1);
var propertyName = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-name",type:"text"}).appendTo(row1).typedInput({types:['msg','flow','global']});
var finalspan = $('<span/>',{style:"float: right; margin-right: 10px;"}).appendTo(row1);
var deleteButton = $('<a/>',{href:"#",class:"editor-button editor-button-small", style:"margin-top: 7px; margin-left: 5px;"}).appendTo(finalspan);
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(to).appendTo(row2);
var propertyValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2);
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"}).text(to).appendTo(row2);
var propertyValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2).typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']});
var row3_1 = $('<div/>').appendTo(row3);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(search).appendTo(row3_1);
var fromValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1);
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"}).text(search).appendTo(row3_1);
var fromValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1).typedInput({default:'str',types:['msg','flow','global','str','re']});
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(replace).appendTo(row3_2);
var toValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2);
var row3_3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
var id = "node-input-rule-property-regex-"+Math.floor(Math.random()*10000);
var useRegExp = $('<input/>',{id:id,class:"node-input-rule-property-re",type:"checkbox", style:"margin-left: 150px; margin-right: 10px; display: inline-block; width: auto; vertical-align: top;"}).appendTo(row3_3);
$('<label/>',{for:id,style:"width: auto;"}).text(regex).appendTo(row3_3);
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"}).text(replace).appendTo(row3_2);
var toValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2).typedInput({default:'str',types:['msg','flow','global','str','num','json']});
selectField.change(function() {
var width = $("#node-input-rule-container").width();
resizeRule(container,width);
var type = $(this).val();
if (type == "set") {
row2.show();
@@ -169,11 +166,14 @@
});
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
propertyName.val(rule.p);
propertyValue.val(rule.to);
fromValue.val(rule.from);
toValue.val(rule.to);
useRegExp.prop('checked', rule.re);
propertyName.typedInput('value',rule.p);
propertyName.typedInput('type',rule.pt)
propertyValue.typedInput('value',rule.to);
propertyValue.typedInput('type',rule.tot)
fromValue.typedInput('value',rule.from);
fromValue.typedInput('type',rule.fromt)
toValue.typedInput('value',rule.to);
toValue.typedInput('type',rule.tot)
selectField.change();
$("#node-input-rule-container").append(container);
@@ -185,7 +185,8 @@
if (!this.rules) {
var rule = {
t:(this.action=="replace"?"set":this.action),
p:this.property
p:this.property,
pt:"msg"
}
if (rule.t === "set") {
@@ -208,37 +209,6 @@
for (var i=0;i<this.rules.length;i++) {
generateRule(this.rules[i]);
}
function changeDialogResize() {
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-rule-container-div").css("height",height+"px");
var rules = $("#node-input-rule-container").children();
var newWidth = $("#node-input-rule-container").width();
rules.each(function(i) {
resizeRule($(this),newWidth);
})
};
$( "#dialog" ).on("dialogresize", changeDialogResize);
$( "#dialog" ).one("dialogopen", function(ev) {
var size = $( "#dialog" ).dialog('option','sizeCache-change');
if (size) {
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
changeDialogResize();
} else {
setTimeout(changeDialogResize,10);
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
$( "#dialog" ).off("dialogresize",changeDialogResize);
});
},
oneditsave: function() {
var rules = $("#node-input-rule-container").children();
@@ -250,17 +220,37 @@
var type = rule.find(".node-input-rule-type option:selected").val();
var r = {
t:type,
p:rule.find(".node-input-rule-property-name").val()
p:rule.find(".node-input-rule-property-name").typedInput('value'),
pt:rule.find(".node-input-rule-property-name").typedInput('type')
};
if (type === "set") {
r.to = rule.find(".node-input-rule-property-value").val();
r.to = rule.find(".node-input-rule-property-value").typedInput('value');
r.tot = rule.find(".node-input-rule-property-value").typedInput('type');
} else if (type === "change") {
r.from = rule.find(".node-input-rule-property-search-value").val();
r.to = rule.find(".node-input-rule-property-replace-value").val();
r.re = rule.find(".node-input-rule-property-re").prop('checked');
r.from = rule.find(".node-input-rule-property-search-value").typedInput('value');
r.fromt = rule.find(".node-input-rule-property-search-value").typedInput('type');
r.to = rule.find(".node-input-rule-property-replace-value").typedInput('value');
r.tot = rule.find(".node-input-rule-property-replace-value").typedInput('type');
}
node.rules.push(r);
});
},
oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
var height = size.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");
var rules = $("#node-input-rule-container").children();
var newWidth = $("#node-input-rule-container").width();
rules.each(function(i) {
$(this).find('.red-ui-typedInput').typedInput("width",newWidth-180);
})
$("#node-input-name").width(newWidth-130);
}
});
</script>

View File

@@ -19,15 +19,15 @@ module.exports = function(RED) {
function ChangeNode(n) {
RED.nodes.createNode(this, n);
this.rules = n.rules;
if (!this.rules) {
var rule = {
t:(n.action=="replace"?"set":n.action),
p:n.property||""
}
if (rule.t === "set") {
rule.to = n.to||"";
} else if (rule.t === "change") {
@@ -37,15 +37,35 @@ module.exports = function(RED) {
}
this.rules = [rule];
}
this.actions = [];
var valid = true;
for (var i=0;i<this.rules.length;i++) {
var rule = this.rules[i];
// Migrate to type-aware rules
if (!rule.pt) {
rule.pt = "msg";
}
if (rule.t === "change" && rule.re) {
rule.fromt = 're';
delete rule.re;
}
if (rule.t === "set" && !rule.tot) {
if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
rule.to = rule.to.substring(4);
rule.tot = "msg";
}
}
if (!rule.tot) {
rule.tot = "str";
}
if (!rule.fromt) {
rule.fromt = "str";
}
if (rule.t === "change") {
if (rule.re === false) {
if (rule.fromt !== 're') {
rule.from = rule.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
try {
@@ -55,57 +75,68 @@ module.exports = function(RED) {
this.error(RED._("change.errors.invalid-from",{error:e.message}));
}
}
if (rule.tot === 'num') {
rule.to = Number(rule.to);
} else if (rule.tot === 'json') {
try {
rule.to = JSON.parse(rule.to);
} catch(e2) {
valid = false;
this.error(RED._("change.errors.invalid-json"));
}
} else if (rule.tot === 'bool') {
rule.to = /^true$/i.test(rule.to);
}
}
function applyRule(msg,rule) {
var propertyParts;
var depth = 0;
propertyParts = rule.p.split(".");
try {
propertyParts.reduce(function(obj, i) {
var to = rule.to;
// Set msg from property to another msg property
if (rule.t === "set" && rule.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) {}
var property = rule.p;
var value = rule.to;
if (rule.tot === "msg") {
value = RED.util.getMessageProperty(msg,rule.to);
} else if (rule.tot === 'flow') {
value = node.context().flow.get(rule.to);
} else if (rule.tot === 'global') {
value = node.context().global.get(rule.to);
}
if (rule.pt === 'msg') {
if (rule.t === 'delete') {
RED.util.setMessageProperty(msg,property,undefined);
} else if (rule.t === 'set') {
RED.util.setMessageProperty(msg,property,value);
} else if (rule.t === 'change') {
var current = RED.util.getMessageProperty(msg,property);
if (typeof current === 'string') {
current = current.replace(rule.from,value);
RED.util.setMessageProperty(msg,property,current);
}
}
} else {
var target;
if (rule.pt === 'flow') {
target = node.context().flow;
} else if (rule.pt === 'global') {
target = node.context().global;
}
if (target) {
if (rule.t === 'delete') {
target.set(property,undefined);
} else if (rule.t === 'set') {
target.set(property,value);
} else if (rule.t === 'change') {
var current = target.get(msg,property);
if (typeof current === 'string') {
current = current.replace(rule.from,value);
target.set(property,current);
}
}
}
if (++depth === propertyParts.length) {
if (rule.t === "change") {
if (typeof obj[i] === "string") {
obj[i] = obj[i].replace(rule.from, rule.to);
}
} else if (rule.t === "set") {
if (typeof to === "undefined") {
delete(obj[i]);
} else {
obj[i] = to;
}
} else if (rule.t === "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) {}
}
} catch(err) {console.log(err.stack)}
return msg;
}
if (valid) {
var node = this;
this.on('input', function(msg) {

View File

@@ -68,12 +68,12 @@
</script>
<script type="text/x-red" data-help-name="csv">
<p>A function that parses the <b>msg.payload</b> to convert CSV to/from a javascript object.
<p>A function that parses the <code>msg.payload</code> to convert CSV to/from a javascript object.
Places the result in the payload.</p>
<p>If the input is a string it tries to parse it as CSV and creates a javascript object.</p>
<p>If the input is a javascript object it tries to build a CSV string.</p>
<p>If the input is a simple array the output is just a CSV generated from that array.</p>
<p>If the input is an array of arrays or an array of objects a multiple-line CSV is created.</p>
<p>If the input is an array of arrays, or an array of objects, a multiple-line CSV is created.</p>
<p>The columns template should contain an ordered list of column headers. For CSV input these become the property names.
For CSV output these specify the properties to extract from the object and the order for the CSV.</p>
<p>If the input is an array then the columns template does not matter, but can be used to generate a row of column titles.</p>

View File

@@ -23,8 +23,8 @@
<label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="html.label.output"></span></label>
<select id="node-input-ret" style="width:76% !important">
<option value="html" data-i18n="html.output.html"></option>
<option value="text"data-i18n="html.output.text"></option>
<!-- <option value="attr">an object of any attributes</option> -->
<option value="text" data-i18n="html.output.text"></option>
<option value="attr" data-i18n="html.output.attr"></option>
<!-- <option value="val">return the value from a form element</option> -->
</select>
</div>
@@ -44,7 +44,7 @@
</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>Extracts elements from an html document held in <code>msg.payload</code> using a selector.</p>
<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

View File

@@ -32,9 +32,9 @@ module.exports = function(RED) {
$(node.tag).each(function() {
if (node.as === "multi") {
var pay2 = null;
if (node.ret === "html") { pay2 = $(this).html(); }
if (node.ret === "html") { pay2 = cheerio.load($(this).html().trim()).xml(); }
if (node.ret === "text") { pay2 = $(this).text(); }
//if (node.ret === "attr") { pay2 = $(this)[0]["attribs"]; }
if (node.ret === "attr") { pay2 = this.attribs; }
//if (node.ret === "val") { pay2 = $(this).val(); }
/* istanbul ignore else */
if (pay2) {
@@ -43,9 +43,9 @@ module.exports = function(RED) {
}
}
if (node.as === "single") {
if (node.ret === "html") { pay.push( $(this).html() ); }
if (node.ret === "html") { pay.push( cheerio.load($(this).html().trim()).xml() ); }
if (node.ret === "text") { pay.push( $(this).text() ); }
//if (node.ret === "attr") { pay.push( $(this)[0]["attribs"] ); }
if (node.ret === "attr") { pay.push( this.attribs ); }
//if (node.ret === "val") { pay.push( $(this).val() ); }
}
});

View File

@@ -22,7 +22,7 @@
</script>
<script type="text/x-red" data-help-name="json">
<p>A function that parses the <b>msg.payload</b> to convert a JSON string to/from a javascript object. Places the result back into the payload.</p>
<p>A function that parses the <code>msg.payload</code> to convert a JSON string to/from a javascript object. Places the result back into the payload.</p>
<p>If the input is a JSON string it tries to parse it to a javascript object.</p>
<p>If the input is a javascript object it creates a JSON string.</p>
</script>

View File

@@ -33,11 +33,11 @@
</script>
<script type="text/x-red" data-help-name="xml">
<p>A function that parses the <b>msg.payload</b> to convert xml to/from a javascript object. Places the result in the payload.</p>
<p>A function that parses the <code>msg.payload</code> to convert xml to/from a javascript object. Places the result in the payload.</p>
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
<p>If the input is a javascript object it tries to build an XML string.</p>
<p>You can also pass in a <b>msg.options</b> object to overide all the multitude of parameters. See
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a>
<p>You can also pass in a <code>msg.options</code> object to overide all the multitude of parameters. See
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options" target="_new">the xml2js docs</a>
for more information.</p>
<p>If set, options in the edit dialogue override those passed in on the msg.options object.</p>
</script>

View File

@@ -1,5 +1,5 @@
<!--
Copyright 2013 IBM Corp.
Copyright 2013,2016 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,11 +15,18 @@
-->
<script type="text/x-red" data-template-name="tail">
<div class="form-row node-input-filename">
<div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="tail.label.filename"></span></label>
<input type="text" id="node-input-filename">
</div>
<div class="form-row">
<label for="node-input-filetype"><i class="fa fa-file-text-o"></i> <span data-i18n="tail.label.type"></span></label>
<select type="text" id="node-input-filetype">
<option value="text" data-i18n="tail.action.text"></option>
<option value="binary" data-i18n="tail.action.binary"></option>
</select>
</div>
<div class="form-row" id="node-tail-split">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-split" style="width: 70%;"><span data-i18n="tail.label.splitlines"></span></label>
@@ -28,12 +35,12 @@
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<!-- <div class="form-tips">WON'T work on Windows.</div> -->
</script>
<script type="text/x-red" data-help-name="tail">
<p>Tails (watches for things to be added) to the configured file. (Linux/Mac ONLY)</p>
<p>This won't work on Windows filesystems, as it relies on the tail -F command.</p>
<p>This will not work on Windows filesystems, as it relies on the <b>tail -F</b> command.</p>
<p>Text (UTF-8) files will be returned as strings. Binary files will be returned as a Buffer object.</p>
</script>
<script type="text/javascript">
@@ -41,6 +48,7 @@
category: 'storage-input',
defaults: {
name: {value:""},
filetype: {value:"text"},
split: {value:false},
filename: {value:"",required:true}
},
@@ -53,6 +61,12 @@
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-filetype").on("change",function() {
if (this.value === "text") { $("#node-tail-split").show(); }
else { $("#node-tail-split").hide(); }
});
}
});
</script>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013, 2014 IBM Corp.
* Copyright 2013,2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,31 +27,36 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
this.filename = n.filename;
this.split = n.split;
this.filetype = n.filetype || "text";
this.split = n.split || false;
var node = this;
var err = "";
// TODO: rewrite to use node-tail
var tail = spawn("tail", ["-F", "-n", "0", this.filename]);
tail.stdout.on("data", function (data) {
if (node.split) {
// TODO: allow customisation of the line break - as we do elsewhere
var strings = data.toString().split("\n");
for (var s in strings) {
//TODO: should we really filter blanks? Is that expected?
if (strings[s] !== "") {
node.send({
topic: node.filename,
payload: strings[s]
});
var msg = { topic:node.filename };
if (node.filetype === "text") {
if (node.split) {
// TODO: allow customisation of the line break - as we do elsewhere
var strings = data.toString().split("\n");
for (var s in strings) {
//TODO: should we really filter blanks? Is that expected?
if (strings[s] !== "") {
node.send({
topic: node.filename,
payload: strings[s]
});
}
}
}
else {
msg.payload = data.toString();
node.send(msg);
}
}
else {
var msg = {
topic:node.filename,
payload: data.toString()
};
msg.payload = data;
node.send(msg);
}
});

View File

@@ -44,11 +44,12 @@
</script>
<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 configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
<p>Writes <code>msg.payload</code> to the file specified, for example to create a log.</p>
<p>The filename can be configured in the node. If left blank it should be
set by <code>msg.filename</code> on the incoming message.</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>This node can also be configured to delete a file if required. <i>Note:</i> Using msg.delete is now deprecated.</p>
<p>This node can also be configured to delete a file if required.</p>
</script>
<script type="text/x-red" data-template-name="file in">
@@ -70,8 +71,10 @@
</script>
<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 configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
<p>Reads the specified file and sends the content as <code>msg.payload</code>,
and the filename as <code>msg.filename</code>.</p>
<p>The filename can be configured in the node. If left blank it should be
set by <code>msg.filename</code> on the incoming message.</p>
</script>
<script type="text/javascript">

View File

@@ -42,6 +42,7 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((this.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
data = new Buffer(data);
if (this.overwriteFile === "true") {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
fs.writeFile(filename, data, "binary", function (err) {

View File

@@ -1,6 +1,6 @@
{
"name" : "node-red",
"version" : "0.12.2",
"version" : "0.13.3",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache-2.0",
@@ -22,67 +22,67 @@
{"name": "Dave Conway-Jones"}
],
"keywords": [
"editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm", "flow"
"editor", "messaging", "iot", "ibm", "flow"
],
"dependencies": {
"basic-auth": "1.0.3",
"bcryptjs": "2.3.0",
"body-parser": "1.14.1",
"body-parser": "1.15.0",
"cheerio":"0.19.0",
"clone": "1.0.2",
"cors":"2.7.1",
"cron":"1.0.9",
"express": "4.13.3",
"cron":"1.1.0",
"express": "4.13.4",
"follow-redirects":"0.0.7",
"fs-extra": "0.26.0",
"fs-extra": "0.26.5",
"fs.notify":"0.0.4",
"i18next":"1.10.5",
"is-utf8":"0.2.0",
"i18next":"1.10.6",
"is-utf8":"0.2.1",
"media-typer": "0.3.0",
"mqtt": "1.4.3",
"mustache": "2.2.0",
"nopt": "3.0.4",
"oauth2orize":"1.1.0",
"mqtt": "1.7.2",
"mustache": "2.2.1",
"nopt": "3.0.6",
"oauth2orize":"1.2.2",
"on-headers":"1.0.1",
"passport":"0.3.0",
"passport":"0.3.2",
"passport-http-bearer":"1.0.1",
"passport-oauth2-client-password":"0.1.2",
"raw-body":"2.1.4",
"semver": "5.0.3",
"sentiment":"0.2.3",
"uglify-js":"2.5.0",
"when": "3.7.4",
"ws": "0.8.0",
"xml2js":"0.4.13",
"raw-body":"2.1.5",
"semver": "5.1.0",
"sentiment":"1.0.6",
"uglify-js":"2.6.1",
"when": "3.7.7",
"ws": "0.8.1",
"xml2js":"0.4.16",
"node-red-node-feedparser":"0.1.*",
"node-red-node-email":"0.1.*",
"node-red-node-twitter":"0.1.*",
"node-red-node-rbe":"0.1.*"
},
"optionalDependencies": {
"node-red-node-serialport":"0.0.*",
"node-red-node-serialport":"0.1.*",
"bcrypt":"0.8.5"
},
"devDependencies": {
"grunt": "0.4.5",
"grunt-chmod": "1.1.1",
"grunt-cli": "0.1.13",
"grunt-concurrent":"2.0.3",
"grunt-contrib-clean":"0.6.0",
"grunt-concurrent":"2.2.1",
"grunt-contrib-clean":"0.7.0",
"grunt-contrib-compress": "0.14.0",
"grunt-contrib-concat":"0.5.1",
"grunt-contrib-copy": "0.8.2",
"grunt-contrib-jshint": "0.11.3",
"grunt-contrib-uglify": "0.9.2",
"grunt-contrib-jshint": "0.12.0",
"grunt-contrib-uglify": "0.11.1",
"grunt-contrib-watch":"0.6.1",
"grunt-jsonlint":"1.0.5",
"grunt-nodemon":"0.4.0",
"grunt-jsonlint":"1.0.7",
"grunt-nodemon":"0.4.1",
"grunt-sass":"1.1.0",
"grunt-simple-mocha": "0.4.0",
"mocha": "2.3.3",
"grunt-simple-mocha": "0.4.1",
"mocha": "2.4.5",
"should": "6.0.3",
"sinon": "1.17.2",
"supertest": "1.1.0"
"sinon": "1.17.3",
"supertest": "1.2.0"
},
"engines": {
"node": ">=0.10"

52
red.js
View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* Copyright 2013, 2015 IBM Corp.
* Copyright 2013, 2016 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,11 +19,12 @@ var https = require('https');
var util = require("util");
var express = require("express");
var crypto = require("crypto");
try { bcrypt = require('bcrypt'); }
catch(e) { bcrypt = require('bcryptjs'); }
var nopt = require("nopt");
var path = require("path");
var fs = require("fs-extra");
var RED = require("./red/red.js");
var log = require("./red/log");
var server;
var app = express();
@@ -34,12 +35,14 @@ var flowFile;
var knownOpts = {
"settings":[path],
"userDir":[path],
"port": Number,
"v": Boolean,
"help": Boolean
};
var shortHands = {
"s":["--settings"],
"u":["--userDir"],
"p":["--port"],
"?":["--help"]
};
nopt.invalidHandler = function(k,v,t) {
@@ -50,11 +53,13 @@ var parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
if (parsedArgs.help) {
console.log("Node-RED v"+RED.version());
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR] [flows.json]");
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]");
console.log(" [--port PORT] [flows.json]");
console.log("");
console.log("Options:");
console.log(" -s, --settings FILE use specified settings file");
console.log(" -u, --userDir DIR use specified user directory");
console.log(" -p, --port PORT port to listen on");
console.log(" -v enable verbose output");
console.log(" -?, --help show usage");
console.log("");
@@ -152,7 +157,7 @@ if (settings.httpNodeRoot !== false) {
settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth;
}
settings.uiPort = settings.uiPort||1880;
settings.uiPort = parsedArgs.port||settings.uiPort||1880;
settings.uiHost = settings.uiHost||"0.0.0.0";
if (flowFile) {
@@ -180,9 +185,24 @@ try {
function basicAuthMiddleware(user,pass) {
var basicAuth = require('basic-auth');
var checkPassword;
if (pass.length == "32") {
// Assume its a legacy md5 password
checkPassword = function(p) {
return crypto.createHash('md5').update(p,'utf8').digest('hex') === pass;
}
} else {
checkPassword = function(p) {
return bcrypt.compareSync(p,pass);
}
}
return function(req,res,next) {
if (req.method === 'OPTIONS') {
return next();
}
var requestUser = basicAuth(req);
if (!requestUser || requestUser.name !== user || crypto.createHash('md5').update(requestUser.pass,'utf8').digest('hex') !== pass) {
if (!requestUser || requestUser.name !== user || !checkPassword(requestUser.pass)) {
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.sendStatus(401);
}
@@ -191,16 +211,16 @@ function basicAuthMiddleware(user,pass) {
}
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
RED.log.warn(log._("server.httpadminauth-deprecated"));
RED.log.warn(RED.log._("server.httpadminauth-deprecated"));
app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass));
}
if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
app.use(settings.httpNodeRoot,basicAuthMiddleware(settings.httpNodeAuth.user,settings.httpNodeAuth.pass));
}
if (settings.httpAdminRoot !== false) {
app.use(settings.httpAdminRoot,RED.httpAdmin);
}
if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
app.use(settings.httpNodeRoot,basicAuthMiddleware(settings.httpNodeAuth.user,settings.httpNodeAuth.pass));
}
if (settings.httpNodeRoot !== false) {
app.use(settings.httpNodeRoot,RED.httpNode);
}
@@ -229,10 +249,10 @@ RED.start().then(function() {
if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
server.on('error', function(err) {
if (err.errno === "EADDRINUSE") {
RED.log.error(log._("server.unable-to-listen", {listenpath:getListenPath()}));
RED.log.error(log._("server.port-in-use"));
RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
RED.log.error(RED.log._("server.port-in-use"));
} else {
RED.log.error(log._("server.uncaught-exception"));
RED.log.error(RED.log._("server.uncaught-exception"));
if (err.stack) {
RED.log.error(err.stack);
} else {
@@ -243,16 +263,16 @@ RED.start().then(function() {
});
server.listen(settings.uiPort,settings.uiHost,function() {
if (settings.httpAdminRoot === false) {
RED.log.info(log._("server.admin-ui-disabled"));
RED.log.info(RED.log._("server.admin-ui-disabled"));
}
process.title = 'node-red';
RED.log.info(log._("server.now-running", {listenpath:getListenPath()}));
RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
});
} else {
RED.log.info(log._("server.headless-mode"));
RED.log.info(RED.log._("server.headless-mode"));
}
}).otherwise(function(err) {
RED.log.error(log._("server.failed-to-start"));
RED.log.error(RED.log._("server.failed-to-start"));
if (err.stack) {
RED.log.error(err.stack);
} else {

View File

@@ -25,7 +25,7 @@ var permissions = require("./permissions");
var theme = require("../theme");
var settings = null;
var log = require("../../log");
var log = null
passport.use(strategies.bearerStrategy.BearerStrategy);
@@ -36,11 +36,13 @@ var server = oauth2orize.createServer();
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
function init(_settings,storage) {
settings = _settings;
function init(runtime) {
settings = runtime.settings;
log = runtime.log;
if (settings.adminAuth) {
Users.init(settings.adminAuth);
Tokens.init(settings.adminAuth,storage);
Tokens.init(settings.adminAuth,runtime.storage);
strategies.init(runtime);
}
}

View File

@@ -26,7 +26,7 @@ var Users = require("./users");
var Clients = require("./clients");
var permissions = require("./permissions");
var log = require("../../log");
var log;
var bearerStrategy = function (accessToken, done) {
// is this a valid token?
@@ -124,6 +124,9 @@ AnonymousStrategy.prototype.authenticate = function(req) {
}
module.exports = {
init: function(runtime) {
log = runtime.log;
},
bearerStrategy: bearerStrategy,
clientPasswordStrategy: clientPasswordStrategy,
passwordTokenExchange: passwordTokenExchange,

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2014, 2015 IBM Corp.
* Copyright 2014, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
**/
var ws = require("ws");
var log = require("./log");
var log;
var server;
var settings;
@@ -29,23 +29,37 @@ var retained = {};
var heartbeatTimer;
var lastSentTime;
function init(_server,_settings) {
server = _server;
settings = _settings;
function handleStatus(event) {
publish("status/"+event.id,event.status,true);
}
function init(_server,runtime) {
server = _server;
settings = runtime.settings;
log = runtime.log;
runtime.events.removeListener("node-status",handleStatus);
runtime.events.on("node-status",handleStatus);
}
function start() {
var Tokens = require("./api/auth/tokens");
var Users = require("./api/auth/users");
var Permissions = require("./api/auth/permissions");
var Tokens = require("./auth/tokens");
var Users = require("./auth/users");
var Permissions = require("./auth/permissions");
if (!settings.disableEditor) {
Users.default().then(function(anonymousUser) {
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
var path = settings.httpAdminRoot || "/";
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({server:server,path:path});
wsServer = new ws.Server({
server:server,
path:path,
// Disable the deflate option due to this issue
// https://github.com/websockets/ws/pull/632
// that is fixed in the 1.x release of the ws module
// that we cannot currently pickup as it drops node 0.10 support
perMessageDeflate: false
});
wsServer.on('connection',function(ws) {
log.audit({event: "comms.open"});
@@ -151,15 +165,17 @@ function stop() {
}
function publish(topic,data,retain) {
if (retain) {
retained[topic] = data;
} else {
delete retained[topic];
if (server) {
if (retain) {
retained[topic] = data;
} else {
delete retained[topic];
}
lastSentTime = Date.now();
activeConnections.forEach(function(conn) {
publishTo(conn,topic,data);
});
}
lastSentTime = Date.now();
activeConnections.forEach(function(conn) {
publishTo(conn,topic,data);
});
}
function publishTo(ws,topic,data) {
@@ -203,5 +219,5 @@ module.exports = {
init:init,
start:start,
stop:stop,
publish:publish,
publish:publish
}

View File

@@ -14,10 +14,14 @@
* limitations under the License.
**/
var log = require("../log");
var api = require("../nodes");
var log;
var api;
module.exports = {
init: function(runtime) {
log = runtime.log;
api = runtime.nodes;
},
get: function (req, res) {
// TODO: It should verify the given node id is of the type specified -
// but that would add a dependency from this module to the

89
red/api/flow.js Normal file
View File

@@ -0,0 +1,89 @@
/**
* Copyright 2014, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 log;
var redNodes;
var settings;
module.exports = {
init: function(runtime) {
settings = runtime.settings;
redNodes = runtime.nodes;
log = runtime.log;
},
get: function(req,res) {
var id = req.params.id;
var flow = redNodes.getFlow(id);
if (flow) {
log.audit({event: "flow.get",id:id},req);
res.json(flow);
} else {
log.audit({event: "flow.get",id:id,error:"not_found"},req);
res.status(404).end();
}
},
post: function(req,res) {
var flow = req.body;
redNodes.addFlow(flow).then(function(id) {
log.audit({event: "flow.add",id:id},req);
res.json({id:id});
}).otherwise(function(err) {
log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
})
},
put: function(req,res) {
var id = req.params.id;
var flow = req.body;
try {
redNodes.updateFlow(id,flow).then(function() {
log.audit({event: "flow.update",id:id},req);
res.json({id:id});
}).otherwise(function(err) {
console.log(err.stack);
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
})
} catch(err) {
if (err.code === 404) {
log.audit({event: "flow.update",id:id,error:"not_found"},req);
res.status(404).end();
} else {
console.log(err.stack);
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}
},
delete: function(req,res) {
var id = req.params.id;
try {
redNodes.removeFlow(id).then(function() {
log.audit({event: "flow.remove",id:id},req);
res.status(204).end();
})
} catch(err) {
if (err.code === 404) {
log.audit({event: "flow.remove",id:id,error:"not_found"},req);
res.status(404).end();
} else {
log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}
}
}

View File

@@ -14,12 +14,16 @@
* limitations under the License.
**/
var log = require("../log");
var redNodes = require("../nodes");
var settings = require("../settings");
var log;
var redNodes;
var settings;
module.exports = {
init: function(runtime) {
settings = runtime.settings;
redNodes = runtime.nodes;
log = runtime.log;
},
get: function(req,res) {
log.audit({event: "flows.get"},req);
res.json(redNodes.getFlows());
@@ -28,12 +32,22 @@ module.exports = {
var flows = req.body;
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
log.audit({event: "flows.set",type:deploymentType},req);
redNodes.setFlows(flows,deploymentType).then(function() {
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack);
res.status(500).json({error:"unexpected_error", message:err.message});
});
if (deploymentType === 'reload') {
redNodes.loadFlows().then(function() {
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.flows.error-reload",{message:err.message}));
log.warn(err.stack);
res.status(500).json({error:"unexpected_error", message:err.message});
});
} else {
redNodes.setFlows(flows,deploymentType).then(function() {
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack);
res.status(500).json({error:"unexpected_error", message:err.message});
});
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2014 IBM Corp.
* Copyright 2014, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,22 +19,27 @@ var bodyParser = require("body-parser");
var util = require('util');
var path = require('path');
var passport = require('passport');
var when = require('when');
var ui = require("./ui");
var nodes = require("./nodes");
var flows = require("./flows");
var flow = require("./flow");
var library = require("./library");
var info = require("./info");
var theme = require("./theme");
var locales = require("./locales");
var credentials = require("./credentials");
var log = require("../log");
var comms = require("./comms");
var auth = require("./auth");
var needsPermission = auth.needsPermission;
var settings = require("../settings");
var i18n;
var log;
var adminApp;
var nodeApp;
var server;
var errorHandler = function(err,req,res,next) {
if (err.message === "request entity too large") {
@@ -46,71 +51,116 @@ var errorHandler = function(err,req,res,next) {
res.status(400).json({error:"unexpected_error", message:err.toString()});
};
function init(adminApp,storage) {
function init(_server,runtime) {
server = _server;
var settings = runtime.settings;
i18n = runtime.i18n;
log = runtime.log;
if (settings.httpNodeRoot !== false) {
nodeApp = express();
}
if (settings.httpAdminRoot !== false) {
comms.init(server,runtime);
adminApp = express();
auth.init(runtime);
credentials.init(runtime);
flows.init(runtime);
flow.init(runtime);
info.init(runtime);
library.init(adminApp,runtime);
locales.init(runtime);
nodes.init(runtime);
auth.init(settings,storage);
// Editor
if (!settings.disableEditor) {
ui.init(settings);
var editorApp = express();
editorApp.get("/",ui.ensureSlash,ui.editor);
editorApp.get("/icons/:icon",ui.icon);
if (settings.editorTheme) {
editorApp.use("/theme",theme.init(settings));
// Editor
if (!settings.disableEditor) {
ui.init(runtime);
var editorApp = express();
editorApp.get("/",ui.ensureSlash,ui.editor);
editorApp.get("/icons/:icon",ui.icon);
theme.init(runtime);
if (settings.editorTheme) {
editorApp.use("/theme",theme.app());
}
editorApp.use("/",ui.editorResources);
adminApp.use(editorApp);
}
editorApp.use("/",ui.editorResources);
adminApp.use(editorApp);
var maxApiRequestSize = settings.apiMaxLength || '1mb';
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
adminApp.get("/auth/login",auth.login,errorHandler);
if (settings.adminAuth) {
//TODO: all passport references ought to be in ./auth
adminApp.use(passport.initialize());
adminApp.post("/auth/token",
auth.ensureClientSecret,
auth.authenticateClient,
auth.getToken,
auth.errorHandler
);
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke,errorHandler);
}
// Flows
adminApp.get("/flows",needsPermission("flows.read"),flows.get,errorHandler);
adminApp.post("/flows",needsPermission("flows.write"),flows.post,errorHandler);
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,errorHandler);
adminApp.post("/flow",needsPermission("flows.write"),flow.post,errorHandler);
adminApp.delete("/flow/:id",needsPermission("flows.write"),flow.delete,errorHandler);
adminApp.put("/flow/:id",needsPermission("flows.write"),flow.put,errorHandler);
// Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,errorHandler);
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,errorHandler);
adminApp.get("/nodes/:mod",needsPermission("nodes.read"),nodes.getModule,errorHandler);
adminApp.put("/nodes/:mod",needsPermission("nodes.write"),nodes.putModule,errorHandler);
adminApp.delete("/nodes/:mod",needsPermission("nodes.write"),nodes.delete,errorHandler);
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet,errorHandler);
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet,errorHandler);
adminApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,errorHandler);
adminApp.get(/locales\/(.+)\/?$/,locales.get,errorHandler);
// Library
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,errorHandler);
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll,errorHandler);
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,errorHandler);
// Settings
adminApp.get("/settings",needsPermission("settings.read"),info.settings,errorHandler);
// Error Handler
//adminApp.use(errorHandler);
}
var maxApiRequestSize = settings.apiMaxLength || '1mb';
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
adminApp.get("/auth/login",auth.login);
if (settings.adminAuth) {
//TODO: all passport references ought to be in ./auth
adminApp.use(passport.initialize());
adminApp.post("/auth/token",
auth.ensureClientSecret,
auth.authenticateClient,
auth.getToken,
auth.errorHandler
);
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke);
}
// Flows
adminApp.get("/flows",needsPermission("flows.read"),flows.get);
adminApp.post("/flows",needsPermission("flows.write"),flows.post);
// Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll);
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post);
adminApp.get("/nodes/:mod",needsPermission("nodes.read"),nodes.getModule);
adminApp.put("/nodes/:mod",needsPermission("nodes.write"),nodes.putModule);
adminApp.delete("/nodes/:mod",needsPermission("nodes.write"),nodes.delete);
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet);
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet);
adminApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get);
adminApp.get(/locales\/(.+)\/?$/,locales.get);
// Library
library.init(adminApp);
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post);
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll);
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get);
// Settings
adminApp.get("/settings",needsPermission("settings.read"),info.settings);
// Error Handler
adminApp.use(errorHandler);
}
function start() {
return i18n.registerMessageCatalog("editor",path.resolve(path.join(__dirname,"locales")),"editor.json").then(function(){
comms.start();
});
}
function stop() {
comms.stop();
return when.resolve();
}
module.exports = {
init: init
init: init,
start: start,
stop: stop,
library: {
register: library.register
},
auth: {
needsPermission: auth.needsPermission
},
comms: {
publish: comms.publish
},
get adminApp() { return adminApp; },
get nodeApp() { return nodeApp; },
get server() { return server; }
};

View File

@@ -13,28 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var settings = require('../settings');
var theme = require("./theme");
var util = require('util');
var settings;
module.exports = {
init: function(runtime) {
settings = runtime.settings;
},
settings: function(req,res) {
var safeSettings = {
httpNodeRoot: settings.httpNodeRoot,
version: settings.version,
user: req.user
}
var themeSettings = theme.settings();
if (themeSettings) {
safeSettings.editorTheme = themeSettings;
}
if (util.isArray(settings.paletteCategories)) {
safeSettings.paletteCategories = settings.paletteCategories;
}
res.json(safeSettings);
}
}

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