Compare commits

...

121 Commits

Author SHA1 Message Date
Nick O'Leary
34537180c3 Fix basic authentication on httpNode/Admin/Static 2015-11-10 15:44:48 +00:00
Nick O'Leary
cb01920ee6 Allow nodes to be installed by path name 2015-11-09 16:52:14 +00:00
Nick O'Leary
437b01a0ff Move node installer to its own module 2015-11-09 11:29:48 +00:00
Nick O'Leary
075a2abf71 Use child_process.execFile to prevent command injection 2015-11-08 14:26:11 +00:00
Nick O'Leary
985875cc75 Move node install/remove from server component to node engine 2015-11-08 14:06:36 +00:00
Nick O'Leary
1c45bc615f Enable touch-menu for links so they can be deleted 2015-11-06 16:46:07 +00:00
Nick O'Leary
fa7f2606fb bump version 2015-11-06 13:42:00 +00:00
Nick O'Leary
12b95f1c72 Enable config node dialog to have editor ui 2015-11-06 11:08:07 +00:00
Nick O'Leary
a0aee2021d Tidy up sheets/flows labelling 2015-11-05 14:22:15 +00:00
Nick O'Leary
c90fd1e6d8 Move credential http API handling to api component 2015-11-04 11:13:43 +00:00
Dave Conway-Jones
3b769fd2de add bash directive to nrgpio script 2015-11-04 08:42:00 +00:00
Nick O'Leary
71ecb89abc Merge branch 'flowengine' 2015-11-03 20:24:55 +00:00
Nick O'Leary
7b6bc1d3bc Increase default api request limit to 1mb
- expose it via settings.apiMaxLength
 - audit log api errors
2015-11-03 11:23:37 +00:00
Nick O'Leary
9c3be40fbe Automatically clear timeouts/intervals set in Function node
closes #744
2015-11-03 10:47:29 +00:00
Nick O'Leary
ab87fa9ce4 Ensure status/errors from global config nodes propagate properly 2015-11-03 10:20:00 +00:00
Nick O'Leary
d1940a023a Complete test coverage on flow engine refactor 2015-11-02 15:58:40 +00:00
Nick O'Leary
5a176a037c Update test helper for refactored flow engine 2015-11-02 15:58:40 +00:00
Nick O'Leary
ec25191c98 Flow Engine refactor
Each flow/tab now exists as its own logical object. This is the ground
work for allowing flows to be added/removed/updated independently.
2015-11-02 15:58:29 +00:00
Nick O'Leary
20b321f928 Handle null subflow info property 2015-10-26 11:12:49 +00:00
Dave Conway-Jones
425b016d63 Add missing space to label in delay node 2015-10-26 09:42:16 +00:00
Nick O'Leary
b2c7189ce4 Update package dependencies 2015-10-25 21:36:20 +00:00
Nick O'Leary
f66886dbdb Websocket input node not unsubscribing properly
Fixes #739
2015-10-23 23:01:25 +01:00
Nick O'Leary
712f8b4680 Add description field to Subflows 2015-10-23 22:14:39 +01:00
Nick O'Leary
f626ee060a Allow properties to be specified by environment variables
A property set to $(ABC) will be substituted with the environment
variable ABC - if it exists. If the property doesn't exist, the property
is left unchanged.
2015-10-23 22:14:39 +01:00
Dave Conway-Jones
86aa7c97be Make exec node status indication consistent with others 2015-10-22 19:02:22 +01:00
Dave Conway-Jones
30e3525987 Add status icon to trigger node while it is active 2015-10-22 16:27:07 +01:00
Dave Conway-Jones
ad44f838da update sample to refer to fa-icons 2015-10-20 19:30:42 +01:00
Dave Conway-Jones
2569a35b6c fix closing web socket timeout
to close #738 - thanks to @GSeva for spotting it.
2015-10-20 18:58:37 +01:00
Dave Conway-Jones
1ee5e50d50 Turn on tcp node socket keepalive when in server mode 2015-10-20 10:13:48 +01:00
Dave Conway-Jones
1dbec5eca8 slight tidy of Pi Mouse node 2015-10-20 10:02:57 +01:00
Nick O'Leary
2bc8db308c Add missing tab-config file 2015-10-17 19:05:23 +01:00
Nick O'Leary
f196740426 Restore config node tab 2015-10-16 21:56:20 +01:00
Dave Conway-Jones
20121b79c5 Let CSV node handle null columns in input array. array-> csv 2015-10-16 20:42:25 +01:00
Nick O'Leary
741a4cfe53 Don't assume def.align is set to 'right'
Fixes #734
2015-10-15 14:27:30 +01:00
Dave Conway-Jones
0343de9f34 Add status capability to function node 2015-10-15 13:33:05 +01:00
Nick O'Leary
4772bca14a Undo 'selection to subflow' not restoring nodes 2015-10-15 11:34:17 +01:00
Dave Conway-Jones
6ae1a5ba0d let CSV obj->css allow blank columns in template 2015-10-15 08:31:42 +01:00
Dave Conway-Jones
217c9718e4 catch missing template error in CSV parser node
and update help info
2015-10-14 23:38:29 +01:00
Dave Conway-Jones
61d7893467 Enhance CSV node to accept simple arrays -> css 2015-10-14 23:07:25 +01:00
Dave Conway-Jones
8f26c01f4b fix escaping of quotes in CSV node 2015-10-14 11:02:31 +01:00
Dave Conway-Jones
61045ddd7f try to fix debug node non-standard object type handling. 2015-10-10 22:41:07 +01:00
Dave Conway-Jones
1bf72a0bc3 correct TCP node server status message 2015-10-09 15:44:22 +01:00
Dave Conway-Jones
6d84b1bb8d update inspect to come from correct package (util)
(works from v0.10 onwards - but now deprecated in v4)
2015-10-09 13:36:35 +01:00
Dave Conway-Jones
8abd0b1fdf Detect/set correct method for http proxy
to close #732 — thanks @utaani
2015-10-09 09:19:09 +01:00
Dave Conway-Jones
81e125b7ba add bit more help to exec node
Point out that parameters with spaces should be quoted
2015-10-05 21:41:53 +01:00
Dave Conway-Jones
d5e1468718 Normalise spawn behaviour in exec node to accept identical parameters
Can now just tick or untick spawn and command still works.
2015-10-05 21:17:35 +01:00
Nick O'Leary
c232bf5ed6 Avoid unnecessary isUtf8 check on HTTP payloads 2015-10-05 21:10:32 +01:00
Nick O'Leary
21b25ffaee Merge pull request #730 from jthomas/master
Allow HTTP nodes to handle non-UTF8 content.
2015-10-05 20:52:35 +01:00
Dave Conway-Jones
ca0a93df08 Add Node-RED sag icon to default images 2015-10-05 13:46:42 +01:00
James Thomas
699a22c757 Allow HTTP nodes to handle non-UTF8 content.
Setting UTF-8 as the default body encoding stops us sending binary
content, e.g. audio, which can be decoded into a Buffer.

Use "Content-Type" header to decide whether to decode as UTF-8 or a
Buffer.
2015-10-05 11:53:29 +01:00
Nick O'Leary
8b2b1669b5 Add ignore-case flag to Switch regex rule
closes #366
2015-10-04 22:14:49 +01:00
Dave Conway-Jones
c1e8370916 Lets spawn msg.payload contain comma separated parameters
(like rest of parameters passed in to spawn via edit dialogue)
Will also accept an array…
2015-10-04 19:22:33 +01:00
Nick O'Leary
ddedea8b90 Resize change/switch rules with the dialog 2015-10-03 22:30:28 +01:00
Nick O'Leary
8f414ce458 Update auth revoke test for express 4 2015-10-03 20:32:24 +01:00
Nick O'Leary
453f23da20 Move to node 4.x.x in travis 2015-10-03 09:15:11 +01:00
Nick O'Leary
9e91e42a1b Increase delay burst test timeout
The test was occasionally timing out, despite it succeeding. Increased
the timeout to give it a bit more safety margin to complete
2015-10-03 09:06:51 +01:00
Nick O'Leary
b666734c79 Update dependency versions 2015-10-02 22:13:15 +01:00
Nick O'Leary
a2297f303d Update config node user list when node edits are undone 2015-10-02 21:27:31 +01:00
Nick O'Leary
ecde942255 Wrap msg.req/res objects and add deprecated warnings to functions 2015-10-02 21:27:31 +01:00
Nick O'Leary
d668d43a0a Move to express 4.x 2015-10-02 21:27:31 +01:00
Nick O'Leary
ca91a5dd95 Rename flow menu ids back to workspace 2015-10-02 21:27:31 +01:00
Nick O'Leary
5f9780d71c Improve error message if settings file fails to load 2015-10-02 21:27:31 +01:00
Nick O'Leary
ee37464741 Copy settings file to userDir on start-up if it makes sense
It will only copy the file if:
 - a custom file hasn't been specified (-s file)
 - there isn't an existing settings.js file in userDir
 - the default settings file (in install dir) hasn't been
   modified (mtime < ctime)
2015-10-02 21:27:31 +01:00
Nick O'Leary
8d73f927db Debug output should use overridden toString method if set
Closes #690
2015-10-02 21:27:31 +01:00
Nick O'Leary
4a0222bd1c Warn if a node references an unknown config node type
Closes #709
2015-10-02 21:27:31 +01:00
Nick O'Leary
c0b8f5e3e1 Add tab info to deploy error messages 2015-10-02 21:27:31 +01:00
Nick O'Leary
a9a0b263dc Add undo support for config-node delete 2015-10-02 21:27:30 +01:00
Nick O'Leary
f2b73187d8 Name flows back to sheets in the ui 2015-10-02 21:27:30 +01:00
Nick O'Leary
ef10ade0cc Improve handling of imported config nodes
Avoid creating unnecessary duplicates of config nodes, whilst
honouring the scope of the any existing node.

Also, 'undo' now removes any added config node
2015-10-02 21:27:30 +01:00
Nick O'Leary
719bb4263e Ensure config nodes are deleted when owning subflow is deleted 2015-10-02 21:27:30 +01:00
Nick O'Leary
b3602b268e Have grunt dev restart on message catalog changes 2015-10-02 21:27:30 +01:00
Nick O'Leary
66ec9bae27 Add warning if HTTP In node is missing path config 2015-10-02 21:27:30 +01:00
Nick O'Leary
d96b6e77c0 Allow config nodes to be scoped to an individual Flow 2015-10-02 21:27:30 +01:00
Nick O'Leary
da64c018ac Make the comms link be more tolerant to temporarily blips 2015-10-02 21:27:30 +01:00
Nick O'Leary
f9fb97adf2 Undo subflow rename not updating corresponding tab/menu labels 2015-10-02 21:27:30 +01:00
Nick O'Leary
8316bc6480 Move config node users tip into body of dialog 2015-10-02 21:27:30 +01:00
Nick O'Leary
08021e039a Better favicon 2015-10-02 21:27:30 +01:00
Dave Conway-Jones
cc6e0937a0 initialise status.text to be blank string if not set.
(and change test to use .text rather than .message)
(not that it matters at this level of test)
2015-10-02 21:27:30 +01:00
Nick O'Leary
c1d694a97c Add birth message to MQTT node 2015-10-02 21:27:30 +01:00
Dave Conway-Jones
fcf4f40c36 add basic status node test 2015-10-02 21:27:30 +01:00
Nick O'Leary
380b03399c Update to latest MQTT module version 2015-10-02 21:27:29 +01:00
Nick O'Leary
c33d02c53f Ensure MQTT node cleansession/keepalive defaults are used 2015-10-02 21:27:29 +01:00
Nick O'Leary
fa5e37993e Update mqtt node options to include will/cleansession/keepalive 2015-10-02 21:27:29 +01:00
Richard Ruston
437b2d506b Update MQTT node to use MQTT.js 1.2 and enable secure connections 2015-10-02 21:27:29 +01:00
Dave Conway-Jones
4ed09f6431 update FA icons to v4.4.0 2015-10-02 21:27:29 +01:00
Nick O'Leary
0b98a6acf8 Ensure catch/status nodes marked changed when edited 2015-10-02 21:27:29 +01:00
Nick O'Leary
1d73c86cb2 Add scope to status node 2015-10-02 21:27:29 +01:00
Nick O'Leary
40fe0f3239 Test Node.status calls through to Flow.handleStatus 2015-10-02 21:27:29 +01:00
Nick O'Leary
d1ea689999 Update catch node help text 2015-10-02 21:27:29 +01:00
Nick O'Leary
a6644ad5ff Add status node 2015-10-02 21:27:29 +01:00
Nick O'Leary
658746d2a3 Allow flow file to be specified with grunt dev
For example: grunt dev --flowFile=flows.json
2015-10-02 21:27:29 +01:00
Nick O'Leary
cbdd4de630 Errors in subflows propagate up to nearest catch node 2015-10-02 21:27:29 +01:00
Nick O'Leary
00c612485b Fix jshint errors 2015-10-02 21:27:29 +01:00
Nick O'Leary
3a6192bf73 Catch node can target specific nodes 2015-10-02 21:27:28 +01:00
Nick O'Leary
c64b5c2850 Add subflow tab image 2015-10-02 21:27:28 +01:00
Nick O'Leary
fdbf079896 Clean up subflow editor
- new appearance of subflow tabs
 - input/output buttons now counters
 - allow multiple input wires to the same node when converting to subflow
 - ensure edit history is propagated properly to instance nodes
2015-10-02 21:27:28 +01:00
Dave Conway-Jones
d1a5395727 update test for JSON parser to include array 2015-10-02 19:46:29 +01:00
Dave Conway-Jones
83a3642c0e Allow JSON node to handle array type 2015-10-02 17:30:23 +01:00
Nick O'Leary
9932d34304 Fix XML parse test
Workaround to https://github.com/Leonidas-from-XIV/node-xml2js/issues/239
2015-09-29 14:39:07 +01:00
Nick O'Leary
7aa37a1976 Bump version 2015-09-29 10:17:45 +01:00
Dave Conway-Jones
fa42fbdab8 Let XML node options be set
let msg.options to set a lot more options if required
2015-09-26 13:47:14 +01:00
Nick O'Leary
caa83ac830 Merge pull request #724 from vielmetti/travis-node-4
Support for Node 4.0.0 and Travis CI testing for same
2015-09-25 23:02:49 +01:00
Nick O'Leary
3963fa9738 Allow a language catalog to be a partial catalog 2015-09-24 21:56:45 +01:00
Edward Vielmetti
005a98d020 Update for node 4 testing.
Changes to .travis.yml
Patch from @dceejay to make a test portable
Patch from @dceejay to correct a case where an int was expected
2015-09-23 15:27:45 -04:00
Dave Conway-Jones
9560dc9408 remove delay spinner upper limit
e.g. 65 secs is perfectly valid…
close #728
2015-09-22 15:06:58 +01:00
Nick O'Leary
4ac9a5edf0 Merge pull request #726 from vielmetti/patch-2
Bump uglify to 2.4.24 to address security advisory
2015-09-22 10:15:54 +01:00
Edward Vielmetti
37e62597ae Bump uglify to 2.4.24 to address security advisory
Noted in https://nodesecurity.io/advisories/uglifyjs_incorrectly_handles_non-boolean_comparisons
2015-09-20 01:21:21 -04:00
Dave Conway-Jones
90bfe378d0 Add mobile category to palette order 2015-09-16 22:35:17 +01:00
Dave Conway-Jones
ce22b494ec Update README to use svg badges
to close #720
Thanks to @scherwi for the tip
2015-09-08 21:47:57 +01:00
Nick O'Leary
f9e0420647 Fix http node method-override nls message id 2015-08-31 16:06:00 +01:00
Dave Conway-Jones
2fe568d9ba Fix TCP node sending data
(it’s for the children)
2015-08-17 17:15:44 +01:00
Dave Conway-Jones
2d4979df4d only set tcp timeout if needs setting. 2015-08-15 22:16:48 +01:00
Dave Conway-Jones
b555b014b8 Update debug test to check lengths... 2015-08-12 21:56:19 +01:00
Dave Conway-Jones
999b888c54 debug nodes - show length of strings, buffers or size of arrays 2015-08-11 19:39:37 +01:00
Nick O'Leary
5193d7bddb Stop sending messages to ws connections that have errored
Fixes #708
2015-07-30 22:09:01 +01:00
Nick O'Leary
6b03379e4e Ensure exclusive conf node is removed on edit cancel
- If an exclusive conf node was added to a node, but the
   node's own edit dialog was canceled, the conf node remained
   but not associated with the node - effectively orphaning it
2015-07-22 22:28:30 +01:00
Nick O'Leary
08d687ad60 Update to travis container builds 2015-07-22 13:06:14 +01:00
Nick O'Leary
eb57089f06 Add flag to disable build check for tests 2015-07-22 11:41:58 +01:00
Nick O'Leary
a76e4fede1 Handle null acceptedLanguages
Closes #704
2015-07-19 22:11:25 +01:00
Nick O'Leary
705d043540 Replace bootstrap popover 2015-07-18 21:27:16 +01:00
Nick O'Leary
5462e251f8 Fix exclusive config node check when type not registered 2015-07-15 13:23:05 +01:00
131 changed files with 7508 additions and 4279 deletions

View File

@@ -1,6 +1,5 @@
/Gruntfile.js
/.git/*
*.json
/lib/*
*.backup
/public/*

View File

@@ -1,7 +1,21 @@
sudo: false
language: node_js
env:
- CXX="g++-4.8"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- gcc-4.8
matrix:
allow_failures:
- node_js: "4"
before_install:
- npm install -g npm@~1.4.18
node_js:
- "4"
- "0.12"
- "0.10"
script:

View File

@@ -18,6 +18,12 @@ var path = require("path");
module.exports = function(grunt) {
var nodemonArgs = ["-v"];
var flowFile = grunt.option('flowFile');
if (flowFile) {
nodemonArgs.push(flowFile);
}
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
paths: {
@@ -106,6 +112,7 @@ module.exports = function(grunt) {
"editor/js/ui/menu.js",
"editor/js/ui/keyboard.js",
"editor/js/ui/tabs.js",
"editor/js/ui/popover.js",
"editor/js/ui/workspaces.js",
"editor/js/ui/view.js",
"editor/js/ui/sidebar.js",
@@ -228,10 +235,10 @@ module.exports = function(grunt) {
dev: {
script: 'red.js',
options: {
args:['-v'],
args: nodemonArgs,
ext: 'js,html,json',
watch: [
'red','nodes'
'red','nodes','locales'
]
}
}

View File

@@ -2,8 +2,8 @@
http://nodered.org
[![Build Status](https://travis-ci.org/node-red/node-red.png)](https://travis-ci.org/node-red/node-red)
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.png?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
[![Build Status](https://travis-ci.org/node-red/node-red.svg)](https://travis-ci.org/node-red/node-red)
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.svg?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
A visual tool for wiring the Internet of Things.
@@ -67,4 +67,3 @@ 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).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="480" width="480" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 480.00002 479.99999">
<title>Node-RED Icon</title>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title>Node-RED Icon</dc:title>
<cc:license rdf:resource="http://creativecommons.org/licenses/by/3.0/"/>
<dc:creator>
<cc:Agent>
<dc:title>Nick O&apos;Leary</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
</cc:License>
</rdf:RDF>
</metadata>
<g transform="translate(0 -572.36)">
<rect style="color-rendering:auto;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;image-rendering:auto" ry="56" height="448" width="448" y="588.36" x="16" fill="#8f0000"/>
<g transform="matrix(8.545 0 0 8.545 -786.19 -1949.8)">
<path style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m104.41 321.21c0.0138-2.3846-1.905-4.2806-4.2896-4.2806h-6.243v2.9257h6.243c0.80513 0 1.4808 0.58383 1.4808 1.389v4.2422c0 0.80513-0.67566 1.5075-1.4808 1.5075h-6.243v2.8086h6.243c2.3846 0 4.2895-1.9315 4.2895-4.3162l-0.00005-1.9812c9.8659 0.14125 12.737 2.7065 15.877 5.4519 3.0241 2.6446 6.4153 5.4869 15.252 5.557l0.00046 0.97238c0.001 2.3846 1.9543 4.3803 4.3389 4.3803h6.4273v-3.0427h-6.4273c-0.80514 0-1.4135-0.53255-1.4135-1.3377v-4.2422c0-0.80513 0.60835-1.4418 1.4135-1.4418h6.4273v-2.8086h-6.4273c-2.3846 0-4.3379 1.8658-4.3389 4.2504l-0.00045 1.005c-8.351-0.0276-10.723-2.3434-13.76-4.9992-2.5914-2.2662-5.6368-4.7578-12.346-5.6642 0.0583-0.0501 0.11211-0.0987 0.16838-0.15027 1.2918-1.1846 1.9884-2.6158 2.6699-3.8516 0.68148-1.2357 1.3227-2.267 2.373-2.9879 0.85207-0.58483 2.0639-1.0208 3.926-1.1017l0.00018 0.99192c0.00043 2.3846 1.9236 4.4325 4.3083 4.4325h17.242c2.3846 0 4.3127-2.0479 4.3127-4.4325v-4.2422c0-2.3846-1.9281-4.3153-4.3127-4.3153h-17.242c-2.3846 0-4.3095 1.9306-4.3083 4.3153l0.00051 0.98395c-2.2474 0.0903-3.9508 0.6357-5.2079 1.4985-1.5245 1.0464-2.3662 2.4764-3.0762 3.7637-0.70992 1.2873-1.3108 2.4408-2.2188 3.2734-0.79034 0.72475-1.8834 1.2844-3.658 1.493zm18.468-12.356h17.242c0.80514 0 1.387 0.58455 1.387 1.3897v4.2422c0 0.80514-0.5819 1.3898-1.387 1.3898h-17.242c-0.80514 0-1.4994-0.58462-1.4994-1.3898v-4.2422c0-0.80513 0.69431-1.3897 1.4994-1.3897z" fill="#fff"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

View File

@@ -22,6 +22,7 @@ RED.comms = (function() {
var subscriptions = {};
var ws;
var pendingAuth = false;
var reconnectAttempts = 0;
function connectWS() {
var path = location.hostname;
@@ -46,6 +47,7 @@ RED.comms = (function() {
ws = new WebSocket(path);
ws.onopen = function() {
reconnectAttempts = 0;
if (errornotification) {
clearErrorTimer = setTimeout(function() {
errornotification.close();
@@ -80,12 +82,13 @@ RED.comms = (function() {
}
};
ws.onclose = function() {
if (errornotification == null) {
if (reconnectAttempts > 5 && errornotification == null) {
errornotification = RED.notify(RED._("notification.error",{message:RED._("notification.errors.lostConnection")}),"error",true);
} else if (clearErrorTimer) {
clearTimeout(clearErrorTimer);
clearErrorTimer = null;
}
reconnectAttempts++;
setTimeout(connectWS,1000);
}
}

View File

@@ -15,7 +15,7 @@
**/
RED.history = (function() {
var undo_history = [];
return {
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
markAllDirty: function() {
@@ -33,6 +33,7 @@ RED.history = (function() {
var ev = undo_history.pop();
var i;
var node;
var subflow;
var modifiedTabs = {};
if (ev) {
if (ev.t == 'add') {
@@ -62,6 +63,23 @@ RED.history = (function() {
RED.workspaces.remove(ev.subflows[i]);
}
}
if (ev.subflow) {
if (ev.subflow.instances) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (ev.subflow.hasOwnProperty('changed')) {
subflow = RED.nodes.subflow(ev.subflow.id);
if (subflow) {
subflow.changed = ev.subflow.changed;
}
}
}
} else if (ev.t == "delete") {
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
@@ -69,10 +87,9 @@ RED.history = (function() {
RED.workspaces.add(ev.workspaces[i]);
}
}
if (ev.subflow) {
RED.nodes.addSubflow(ev.subflow);
if (ev.subflow && ev.subflow.subflow) {
RED.nodes.addSubflow(ev.subflow.subflow);
}
var subflow;
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
subflow = RED.nodes.subflow(ev.subflowInputs[0].z);
subflow.in.push(ev.subflowInputs[0]);
@@ -97,9 +114,17 @@ RED.history = (function() {
});
}
}
if (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
if (subflow) {
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
n.changed = true;
n.inputs = subflow.in.length;
n.outputs = subflow.out.length;
while (n.outputs > n.ports.length) {
@@ -120,6 +145,22 @@ RED.history = (function() {
RED.nodes.addLink(ev.links[i]);
}
}
if (ev.changes) {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
node = RED.nodes.node(i);
if (node) {
for (var d in ev.changes[i]) {
if (ev.changes[i].hasOwnProperty(d)) {
node[d] = ev.changes[i][d];
}
}
node.dirty = true;
}
}
}
}
} else if (ev.t == "move") {
for (i=0;i<ev.nodes.length;i++) {
var n = ev.nodes[i];
@@ -130,6 +171,17 @@ RED.history = (function() {
} else if (ev.t == "edit") {
for (i in ev.changes) {
if (ev.changes.hasOwnProperty(i)) {
if (ev.node._def.defaults[i].type) {
// This is a config node property
var currentConfigNode = RED.nodes.node(ev.node[i]);
if (currentConfigNode) {
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
}
var newConfigNode = RED.nodes.node(ev.changes[i]);
if (newConfigNode) {
newConfigNode.users.push(ev.node);
}
}
ev.node[i] = ev.changes[i];
}
}
@@ -148,14 +200,24 @@ RED.history = (function() {
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
}
}
if (ev.subflow.hasOwnProperty('instances')) {
ev.subflow.instances.forEach(function(n) {
var node = RED.nodes.node(n.id);
if (node) {
node.changed = n.changed;
node.dirty = true;
}
});
}
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.changed = ev.changed;
n.inputs = ev.node.in.length;
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
});
RED.palette.refresh();
if (ev.node.type === 'subflow') {
$("#menu-item-workspace-menu-"+ev.node.id.replace(".","-")).text(ev.node.name);
}
} else {
RED.editor.updateNodeProperties(ev.node);
RED.editor.validateNode(ev.node);
@@ -169,7 +231,7 @@ RED.history = (function() {
ev.node.changed = ev.changed;
} else if (ev.t == "createSubflow") {
if (ev.nodes) {
RED.nodes.filterNodes({z:ev.subflow.id}).forEach(function(n) {
RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) {
n.z = ev.activeWorkspace;
n.dirty = true;
});
@@ -182,10 +244,10 @@ RED.history = (function() {
RED.nodes.removeLink(ev.links[i]);
}
}
RED.nodes.removeSubflow(ev.subflow);
RED.workspaces.remove(ev.subflow);
RED.nodes.removeSubflow(ev.subflow.subflow);
RED.workspaces.remove(ev.subflow.subflow);
if (ev.removedLinks) {
for (i=0;i<ev.removedLinks.length;i++) {
RED.nodes.addLink(ev.removedLinks[i]);
@@ -199,10 +261,10 @@ RED.history = (function() {
}
});
RED.nodes.dirty(ev.dirty);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
}
}
}

View File

@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.i18n = (function() {
return {
init: function(done) {
i18n.init({
@@ -26,7 +26,8 @@ RED.i18n = (function() {
namespaces: ["editor","node-red"],
defaultNs: "editor"
},
fallbackLng: ['en-US']
fallbackLng: ['en-US'],
useCookie: false
},function() {
done();
});
@@ -40,6 +41,3 @@ RED.i18n = (function() {
}
}
})();

View File

@@ -176,7 +176,7 @@ var RED = (function() {
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
]},
null,
{id:"menu-item-workspace",label:RED._("menu.label.workspaces"),options:[
{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},

View File

@@ -156,7 +156,6 @@ RED.nodes = (function() {
}
if (n._def.category == "config") {
configNodes[n.id] = n;
RED.sidebar.config.refresh();
} else {
n.dirty = true;
var updatedConfigNode = false;
@@ -176,7 +175,7 @@ RED.nodes = (function() {
}
}
if (updatedConfigNode) {
RED.sidebar.config.refresh();
// TODO: refresh config tab?
}
if (n._def.category == "subflows" && typeof n.i === "undefined") {
var nextId = 0;
@@ -215,7 +214,7 @@ RED.nodes = (function() {
if (id in configNodes) {
node = configNodes[id];
delete configNodes[id];
RED.sidebar.config.refresh();
RED.workspaces.refresh();
} else {
node = getNode(id);
if (node) {
@@ -245,11 +244,11 @@ RED.nodes = (function() {
}
}
if (updatedConfigNode) {
RED.sidebar.config.refresh();
RED.workspaces.refresh();
}
}
}
if (node._def.onremove) {
if (node && node._def.onremove) {
node._def.onremove.call(n);
}
return {links:removedLinks,nodes:removedNodes};
@@ -273,15 +272,24 @@ RED.nodes = (function() {
var removedNodes = [];
var removedLinks = [];
var n;
var node;
for (n=0;n<nodes.length;n++) {
var node = nodes[n];
node = nodes[n];
if (node.z == id) {
removedNodes.push(node);
}
}
for(n in configNodes) {
if (configNodes.hasOwnProperty(n)) {
node = configNodes[n];
if (node.z == id) {
removedNodes.push(node);
}
}
}
for (n=0;n<removedNodes.length;n++) {
var rmlinks = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(rmlinks);
var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links);
}
return {nodes:removedNodes,links:removedLinks};
}
@@ -307,6 +315,7 @@ RED.nodes = (function() {
subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
info: sf.info,
icon:"subflow.png",
category: "subflows",
inputs: sf.in.length,
@@ -382,6 +391,7 @@ RED.nodes = (function() {
var node = {};
node.id = n.id;
node.type = n.type;
node.z = n.z;
if (node.type == "unknown") {
for (var p in n._orig) {
if (n._orig.hasOwnProperty(p)) {
@@ -417,7 +427,6 @@ RED.nodes = (function() {
if (n._def.category != "config") {
node.x = n.x;
node.y = n.y;
node.z = n.z;
node.wires = [];
for(var i=0;i<n.outputs;i++) {
node.wires.push([]);
@@ -438,6 +447,7 @@ RED.nodes = (function() {
node.id = n.id;
node.type = n.type;
node.name = n.name;
node.info = n.info;
node.in = [];
node.out = [];
@@ -545,8 +555,11 @@ RED.nodes = (function() {
return nns;
}
function compareNodes(nodeA,nodeB) {
if (nodeA.id != nodeB.id || nodeA.type != nodeB.type) {
function compareNodes(nodeA,nodeB,idMustMatch) {
if (idMustMatch && nodeA.id != nodeB.id) {
return false;
}
if (nodeA.type != nodeB.type) {
return false;
}
var def = nodeA._def;
@@ -685,26 +698,6 @@ RED.nodes = (function() {
});
new_subflows.push(n);
addSubflow(n,createNewIds);
} else {
def = registry.getNodeType(n.type);
if (def && def.category == "config") {
var existingConfigNode = RED.nodes.node(n.id);
if (!existingConfigNode || !compareNodes(existingConfigNode,n) || existingConfigNode._def.exclusive) {
var configNode = {id:n.id,type:n.type,users:[]};
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d];
}
}
configNode.label = def.label;
configNode._def = def;
if (existingConfigNode || createNewIds) {
configNode.id = getID();
}
node_map[n.id] = configNode;
RED.nodes.add(configNode);
}
}
}
}
if (defaultWorkspace == null) {
@@ -715,6 +708,60 @@ RED.nodes = (function() {
activeWorkspace = RED.workspaces.active();
}
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
def = registry.getNodeType(n.type);
if (def && def.category == "config") {
var existingConfigNode = null;
if (createNewIds) {
if (n.z) {
if (subflow_map[n.z]) {
n.z = subflow_map[n.z].id;
} else {
n.z = workspace_map[n.z];
if (!workspaces[n.z]) {
n.z = activeWorkspace;
}
}
}
existingConfigNode = RED.nodes.node(n.id);
if (existingConfigNode) {
if (n.z && existingConfigNode.z !== n.z) {
existingConfigNode = null;
// Check the config nodes on n.z
for (var cn in configNodes) {
if (configNodes.hasOwnProperty(cn)) {
if (configNodes[cn].z === n.z && compareNodes(configNodes[cn],n,false)) {
existingConfigNode = configNodes[cn];
node_map[n.id] = configNodes[cn];
break;
}
}
}
}
}
}
if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) {
var configNode = {id:n.id, z:n.z, type:n.type, users:[]};
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
configNode[d] = n[d];
}
}
configNode.label = def.label;
configNode._def = def;
if (createNewIds) {
configNode.id = getID();
}
node_map[n.id] = configNode;
new_nodes.push(configNode);
RED.nodes.add(configNode);
}
}
}
for (i=0;i<newNodes.length;i++) {
n = newNodes[i];
// TODO: remove workspace in next release+1
@@ -809,17 +856,19 @@ RED.nodes = (function() {
}
for (i=0;i<new_nodes.length;i++) {
n = new_nodes[i];
for (var w1=0;w1<n.wires.length;w1++) {
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
for (var w2=0;w2<wires.length;w2++) {
if (wires[w2] in node_map) {
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
addLink(link);
new_links.push(link);
if (n.wires) {
for (var w1=0;w1<n.wires.length;w1++) {
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
for (var w2=0;w2<wires.length;w2++) {
if (wires[w2] in node_map) {
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
addLink(link);
new_links.push(link);
}
}
}
delete n.wires;
}
delete n.wires;
}
for (i=0;i<new_subflows.length;i++) {
n = new_subflows[i];
@@ -846,6 +895,7 @@ RED.nodes = (function() {
});
}
RED.workspaces.refresh();
return [new_nodes,new_links,new_workspaces,new_subflows];
}
@@ -947,6 +997,13 @@ RED.nodes = (function() {
}
}
},
eachWorkspace: function(cb) {
for (var id in workspaces) {
if (workspaces.hasOwnProperty(id)) {
cb(workspaces[id]);
}
}
},
node: getNode,

View File

@@ -103,7 +103,7 @@ RED.deploy = (function() {
],
create: function() {
$("#node-dialog-confirm-deploy").parent().find("div.ui-dialog-buttonpane")
.append('<div style="height:0; vertical-align: middle; display:inline-block;">'+
.prepend('<div style="height:0; vertical-align: middle; display:inline-block; margin-top: 13px; float:left;">'+
'<input style="vertical-align:top;" type="checkbox" id="node-dialog-confirm-deploy-hide">'+
'<label style="display:inline;" for="node-dialog-confirm-deploy-hide"> do not warn about this again</label>'+
'<input type="hidden" id="node-dialog-confirm-deploy-type">'+
@@ -124,6 +124,36 @@ RED.deploy = (function() {
});
}
function getNodeInfo(node) {
var tabLabel = "";
if (node.z) {
var tab = RED.nodes.workspace(node.z);
if (!tab) {
tab = RED.nodes.subflow(node.z);
tabLabel = tab.name;
} else {
tabLabel = tab.label;
}
}
var label = "";
if (typeof node._def.label == "function") {
label = node._def.label.call(node);
} else {
label = node._def.label;
}
label = label || node.id;
return {tab:tabLabel,type:node.type,label:label};
}
function sortNodeInfo(A,B) {
if (A.tab < B.tab) { return -1;}
if (A.tab > B.tab) { return 1;}
if (A.type < B.type) { return -1;}
if (A.type > B.type) { return 1;}
if (A.name < B.name) { return -1;}
if (A.name > B.name) { return 1;}
return 0;
}
function save(force) {
if (RED.nodes.dirty()) {
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
@@ -134,8 +164,13 @@ RED.deploy = (function() {
var hasUnusedConfig = false;
var unknownNodes = [];
var invalidNodes = [];
RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid;
if (!node.valid) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
@@ -144,18 +179,10 @@ RED.deploy = (function() {
});
hasUnknown = unknownNodes.length > 0;
var unusedConfigNodes = {};
var unusedConfigNodes = [];
RED.nodes.eachConfig(function(node) {
if (node.users.length === 0) {
var label = "";
if (typeof node._def.label == "function") {
label = node._def.label.call(node);
} else {
label = node._def.label;
}
label = label || node.id;
unusedConfigNodes[node.type] = unusedConfigNodes[node.type] || [];
unusedConfigNodes[node.type].push(label);
unusedConfigNodes.push(getNodeInfo(node));
hasUnusedConfig = true;
}
});
@@ -176,19 +203,18 @@ RED.deploy = (function() {
showWarning = true;
$( "#node-dialog-confirm-deploy-type" ).val("invalid");
$( "#node-dialog-confirm-deploy-config" ).show();
invalidNodes.sort(sortNodeInfo);
$( "#node-dialog-confirm-deploy-invalid-list" )
.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();
var unusedNodeLabels = [];
var unusedTypes = Object.keys(unusedConfigNodes).sort();
unusedTypes.forEach(function(type) {
unusedConfigNodes[type].forEach(function(label) {
unusedNodeLabels.push(type+": "+label);
});
});
unusedConfigNodes.sort(sortNodeInfo);
$( "#node-dialog-confirm-deploy-unused-list" )
.html("<li>"+unusedNodeLabels.join("</li><li>")+"</li>");
.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);

View File

@@ -15,6 +15,8 @@
**/
RED.editor = (function() {
var editing_node = null;
var editing_config_node = null;
var subflowEditor;
function getCredentialsURL(nodeType, nodeID) {
var dashedType = nodeType.replace(/\s+/g, '-');
@@ -33,7 +35,6 @@ RED.editor = (function() {
var subflow;
var isValid;
var hasChanged;
if (node.type.indexOf("subflow:")===0) {
subflow = RED.nodes.subflow(node.type.substring(8));
isValid = subflow.valid;
@@ -43,7 +44,7 @@ RED.editor = (function() {
hasChanged = subflow.changed;
}
node.valid = isValid;
node.changed = hasChanged;
node.changed = node.changed || hasChanged;
} else if (node._def) {
node.valid = validateNodeProperties(node, node._def.defaults, node);
if (node._def._creds) {
@@ -65,7 +66,7 @@ RED.editor = (function() {
var modifiedTabs = {};
for (i=0;i<subflowInstances.length;i++) {
subflowInstances[i].valid = node.valid;
subflowInstances[i].changed = node.changed;
subflowInstances[i].changed = subflowInstances[i].changed || node.changed;
subflowInstances[i].dirty = true;
modifiedTabs[subflowInstances[i].z] = true;
}
@@ -270,7 +271,37 @@ RED.editor = (function() {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
var subflowInstances = null;
if (activeSubflow) {
subflowInstances = [];
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+RED.workspaces.active()) {
subflowInstances.push({
id:n.id,
changed:n.changed
});
n.changed = true;
n.dirty = true;
updateNodeProperties(n);
}
});
}
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
links:removedLinks,
dirty:wasDirty,
changed:wasChanged
};
if (subflowInstances) {
historyEvent.subflow = {
instances:subflowInstances
}
}
RED.history.push(historyEvent);
}
editing_node.dirty = true;
validateNode(editing_node);
@@ -303,6 +334,28 @@ RED.editor = (function() {
if (editing_node._def.oneditcancel) {
editing_node._def.oneditcancel.call(editing_node);
}
for (var d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var def = editing_node._def.defaults[d];
if (def.type) {
var configTypeDef = RED.nodes.getType(def.type);
if (configTypeDef && configTypeDef.exclusive) {
var input = $("#node-input-"+d).val()||"";
if (input !== "" && !editing_node[d]) {
// This node has an exclusive config node that
// has just been added. As the user is cancelling
// the edit, need to delete the just-added config
// node so that it doesn't get orphaned.
RED.nodes.remove(input);
}
}
}
}
}
}
$( this ).dialog( "close" );
}
@@ -340,7 +393,7 @@ RED.editor = (function() {
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
}
RED.sidebar.config.refresh();
RED.workspaces.refresh();
var buttons = $( this ).dialog("option","buttons");
if (buttons.length == 3) {
@@ -521,10 +574,15 @@ RED.editor = (function() {
if (definition.defaults.hasOwnProperty(d)) {
if (definition.defaults[d].type) {
var configTypeDef = RED.nodes.getType(definition.defaults[d].type);
if (configTypeDef.exclusive) {
prepareConfigNodeButton(node,d,definition.defaults[d].type);
if (configTypeDef) {
if (configTypeDef.exclusive) {
prepareConfigNodeButton(node,d,definition.defaults[d].type);
} else {
prepareConfigNodeSelect(node,d,definition.defaults[d].type);
}
} else {
prepareConfigNodeSelect(node,d,definition.defaults[d].type);
console.log("Unknown type:", definition.defaults[d].type);
preparePropertyEditor(node,d,prefix);
}
} else {
preparePropertyEditor(node,d,prefix);
@@ -587,16 +645,20 @@ RED.editor = (function() {
}
$("#dialog-form").find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
if (current.indexOf(":") === -1) {
var prefix = "";
if (current.indexOf("[")===0) {
var parts = current.split("]");
prefix = parts[0]+"]";
current = parts[1];
var keys = current.split(";");
for (var i=0;i<keys.length;i++) {
var key = keys[i];
if (key.indexOf(":") === -1) {
var prefix = "";
if (key.indexOf("[")===0) {
var parts = key.split("]");
prefix = parts[0]+"]";
key = parts[1];
}
keys[i] = prefix+ns+":"+key;
}
$(this).attr("data-i18n",prefix+ns+":"+current);
}
$(this).attr("data-i18n",keys.join(";"));
});
$('<input type="text" style="display: none;" />').appendTo("#dialog-form");
prepareEditDialog(node,node._def,"node-input");
@@ -607,7 +669,7 @@ RED.editor = (function() {
function showEditConfigNodeDialog(name,type,id) {
var adding = (id == "_ADD_");
var node_def = RED.nodes.getType(type);
var configNode = RED.nodes.node(id);
editing_config_node = RED.nodes.node(id);
var ns;
if (node_def.set.module === "node-red") {
@@ -616,22 +678,28 @@ RED.editor = (function() {
ns = node_def.set.id;
}
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
if (!activeWorkspace) {
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
}
if (configNode == null) {
configNode = {
if (editing_config_node == null) {
editing_config_node = {
id: (1+Math.random()*4294967295).toString(16),
_def: node_def,
type: type
type: type,
z: activeWorkspace.id,
users: []
}
for (var d in node_def.defaults) {
if (node_def.defaults[d].value) {
configNode[d] = node_def.defaults[d].value;
editing_config_node[d] = node_def.defaults[d].value;
}
}
configNode["_"] = node_def._;
editing_config_node["_"] = node_def._;
}
$("#dialog-config-form").html($("script[data-template-name='"+type+"']").html());
$("#node-config-dialog-edit-form").html($("script[data-template-name='"+type+"']").html());
$("#dialog-config-form").find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
@@ -647,7 +715,7 @@ RED.editor = (function() {
});
prepareEditDialog(configNode,node_def,"node-config-input");
prepareEditDialog(editing_config_node,node_def,"node-config-input");
var buttons = $( "#node-config-dialog" ).dialog("option","buttons");
if (adding) {
@@ -655,7 +723,7 @@ RED.editor = (function() {
buttons = buttons.splice(1);
}
buttons[0].text = "Add";
$("#node-config-dialog-user-count").html("").hide();
$("#node-config-dialog-user-count").find("span").html("").parent().hide();
} else {
if (buttons.length == 2) {
buttons.unshift({
@@ -665,21 +733,34 @@ RED.editor = (function() {
var configProperty = $(this).dialog('option','node-property');
var configId = $(this).dialog('option','node-id');
var configType = $(this).dialog('option','node-type');
var configNode = RED.nodes.node(configId);
var configTypeDef = RED.nodes.getType(configType);
if (configTypeDef.ondelete) {
configTypeDef.ondelete.call(RED.nodes.node(configId));
configTypeDef.ondelete.call(editing_config_node);
}
if (configTypeDef.oneditdelete) {
configTypeDef.oneditdelete.call(RED.nodes.node(configId));
configTypeDef.oneditdelete.call(editing_config_node);
}
var historyEvent = {
t:'delete',
nodes:[editing_config_node],
changes: {},
dirty: RED.nodes.dirty()
}
RED.nodes.remove(configId);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
for (var i=0;i<editing_config_node.users.length;i++) {
var user = editing_config_node.users[i];
historyEvent.changes[user.id] = {
changed: user.changed,
valid: user.valid
};
for (var d in user._def.defaults) {
if (user._def.defaults.hasOwnProperty(d) && user[d] == configId) {
historyEvent.changes[user.id][d] = configId
user[d] = "";
user.changed = true;
user.dirty = true;
}
}
validateNode(user);
@@ -688,12 +769,65 @@ RED.editor = (function() {
RED.nodes.dirty(true);
$( this ).dialog( "close" );
RED.view.redraw();
RED.history.push(historyEvent);
}
});
}
buttons[1].text = "Update";
$("#node-config-dialog-user-count").html(RED._("editor.nodesUse", {count:configNode.users.length})).show();
$("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show();
}
if (editing_config_node._def.exclusive) {
$("#node-config-dialog-scope").hide();
} else {
$("#node-config-dialog-scope").show();
}
$("#node-config-dialog-scope-warning").hide();
var nodeUserFlows = {};
editing_config_node.users.forEach(function(n) {
nodeUserFlows[n.z] = true;
});
var flowCount = Object.keys(nodeUserFlows).length;
var tabSelect = $("#node-config-dialog-scope").empty();
tabSelect.off("change");
tabSelect.append('<option value=""'+(!editing_config_node.z?" selected":"")+' data-i18n="sidebar.config.global"></option>');
tabSelect.append('<option disabled data-i18n="sidebar.config.flows"></option>');
RED.nodes.eachWorkspace(function(ws) {
var workspaceLabel = ws.label;
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
});
tabSelect.append('<option disabled data-i18n="sidebar.config.subflows"></option>');
RED.nodes.eachSubflow(function(ws) {
var workspaceLabel = ws.name;
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
});
if (flowCount > 0) {
tabSelect.on('change',function() {
var newScope = $(this).val();
if (newScope === '') {
// global scope - everyone can use it
$("#node-config-dialog-scope-warning").hide();
} else if (!nodeUserFlows[newScope] || flowCount > 1) {
// a user will loose access to it
$("#node-config-dialog-scope-warning").show();
} else {
$("#node-config-dialog-scope-warning").hide();
}
});
}
//tabSelect.append('<option value="'+activeWorkspace.id+'"'+(activeWorkspace.id==configNode.z?" selected":"")+'>'+workspaceLabel+'</option>');
tabSelect.i18n();
$( "#node-config-dialog" ).dialog("option","buttons",buttons);
$("#node-config-dialog").i18n();
@@ -701,7 +835,7 @@ RED.editor = (function() {
$( "#node-config-dialog" )
.dialog("option","node-adding",adding)
.dialog("option","node-property",name)
.dialog("option","node-id",configNode.id)
.dialog("option","node-id",editing_config_node.id)
.dialog("option","node-type",type)
.dialog("option","title",(adding?RED._("editor.addNewConfig", {type:type}):RED._("editor.editConfig", {type:type})))
.dialog( "open" );
@@ -721,17 +855,39 @@ RED.editor = (function() {
var select = $("#node-input-"+name);
var node_def = RED.nodes.getType(type);
select.children().remove();
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
if (!activeWorkspace) {
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
}
var configNodes = [];
RED.nodes.eachConfig(function(config) {
if (config.type == type) {
if (config.type == type && (!config.z || config.z === activeWorkspace.id)) {
var label = "";
if (typeof node_def.label == "function") {
label = node_def.label.call(config);
} else {
label = node_def.label;
}
select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
configNodes.push({id:config.id,label:label});
}
});
configNodes.sort(function(A,B) {
if (A.label < B.label) {
return -1;
} else if (A.label > B.label) {
return 1;
}
return 0;
});
configNodes.forEach(function(cn) {
select.append('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'>'+cn.label+'</option>');
});
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:type})+'</option>');
window.setTimeout(function() { select.change();},50);
}
@@ -755,53 +911,62 @@ RED.editor = (function() {
var configType = $(this).dialog('option','node-type');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
var configNode;
var d;
var input;
var scope = $("#node-config-dialog-scope").val();
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
editing_config_node[d] = input.prop('checked');
} else {
editing_config_node[d] = input.val();
}
}
}
editing_config_node.label = configTypeDef.label;
editing_config_node.z = scope;
if (scope) {
editing_config_node.users = editing_config_node.users.filter(function(n) {
var keep = true;
for (var d in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d)) {
if (n._def.defaults[d].type === editing_config_node.type &&
n[d] === editing_config_node.id &&
n.z !== scope) {
keep = false;
n[d] = null;
n.dirty = true;
n.changed = true;
validateNode(n);
}
}
}
return keep;
});
}
if (configAdding) {
configNode = {type:configType,id:configId,users:[]};
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
configNode.label = configTypeDef.label;
configNode._def = configTypeDef;
RED.nodes.add(configNode);
updateConfigNodeSelect(configProperty,configType,configNode.id);
} else {
configNode = RED.nodes.node(configId);
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
updateConfigNodeSelect(configProperty,configType,configId);
RED.nodes.add(editing_config_node);
}
updateConfigNodeSelect(configProperty,configType,editing_config_node.id);
if (configTypeDef.credentials) {
updateNodeCredentials(configNode,configTypeDef.credentials,"node-config-input");
updateNodeCredentials(editing_config_node,configTypeDef.credentials,"node-config-input");
}
if (configTypeDef.oneditsave) {
configTypeDef.oneditsave.call(RED.nodes.node(configId));
configTypeDef.oneditsave.call(editing_config_node);
}
validateNode(configNode);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
validateNode(editing_config_node);
for (var i=0;i<editing_config_node.users.length;i++) {
var user = editing_config_node.users[i];
validateNode(user);
}
RED.nodes.dirty(true);
RED.view.redraw(true);
$(this).dialog("close");
}
@@ -844,11 +1009,20 @@ RED.editor = (function() {
close: function(e) {
$(this).dialog('option','width','auto');
$(this).dialog('option','height','auto');
$("#dialog-config-form").html("");
$("#node-config-dialog-edit-form").html("");
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.enable();
}
RED.sidebar.config.refresh();
RED.workspaces.refresh();
},
create: function() {
$("#node-config-dialog").parent().find("div.ui-dialog-buttonpane")
.prepend('<div id="node-config-dialog-user-count"><i class="fa fa-info-circle"></i> <span></span></div>');
$("#node-config-dialog").parent().find('.ui-dialog-titlebar').append('<span id="node-config-dialog-scope-container"><span id="node-config-dialog-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="node-config-dialog-scope"></select></span>');
$("#node-config-dialog").parent().draggable({
cancel: '.ui-dialog-content, .ui-dialog-titlebar-close, #node-config-dialog-scope-container'
});
}
});
}
@@ -878,15 +1052,29 @@ RED.editor = (function() {
changes['name'] = editing_node.name;
editing_node.name = newName;
changed = true;
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text(RED._("subflow.tabLabel",{name:newName}));
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text(newName);
}
var newDescription = subflowEditor.getValue();
if (newDescription != editing_node.info) {
changes['info'] = editing_node.info;
editing_node.info = newDescription;
changed = true;
}
RED.palette.refresh();
if (changed) {
var subflowInstances = [];
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
subflowInstances.push({
id:n.id,
changed:n.changed
})
n.changed = true;
n.dirty = true;
updateNodeProperties(n);
}
});
@@ -898,7 +1086,10 @@ RED.editor = (function() {
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
changed:wasChanged,
subflow: {
instances:subflowInstances
}
};
RED.history.push(historyEvent);
@@ -918,6 +1109,14 @@ RED.editor = (function() {
}
}
],
create: function(e) {
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
subflowEditor = RED.editor.createEditor({
id: 'subflow-input-info-editor',
mode: 'ace/mode/markdown',
value: ""
});
},
open: function(e) {
RED.keyboard.disable();
var minWidth = $(this).dialog('option','minWidth');
@@ -932,17 +1131,30 @@ RED.editor = (function() {
RED.view.state(RED.state.DEFAULT);
}
RED.sidebar.info.refresh(editing_node);
RED.workspaces.refresh();
editing_node = null;
},
resize: function(e) {
var rows = $("#subflow-dialog>form>div:not(.node-text-editor-row)");
var editorRow = $("#subflow-dialog>form>div.node-text-editor-row");
var height = $("#subflow-dialog").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#subflow-dialog>form").css("marginTop"))+parseInt($("#subflow-dialog>form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
subflowEditor.resize();
}
});
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
}
function showEditSubflowDialog(subflow) {
editing_node = subflow;
RED.view.state(RED.state.EDITING);
$("#subflow-input-name").val(subflow.name);
subflowEditor.getSession().setValue(subflow.info,-1);
var userCount = 0;
var subflowType = "subflow:"+editing_node.id;

View File

@@ -133,17 +133,6 @@ RED.menu = (function() {
if (opt.disabled) {
item.addClass("disabled");
}
if (opt.tip) {
item.popover({
placement:"left",
trigger: "hover",
delay: { show: 350, hide: 20 },
html: true,
container:'body',
content: opt.tip
});
}
}

View File

@@ -17,7 +17,7 @@
RED.palette = (function() {
var exclusion = ['config','unknown','deprecated'];
var core = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
var core = ['subflows', 'input', 'output', 'function', 'social', 'mobile', 'storage', 'analysis', 'advanced'];
var categoryContainers = {};
@@ -60,8 +60,8 @@ RED.palette = (function() {
});
}
function setLabel(type, el,label) {
var nodeWidth = 80;
function setLabel(type, el,label, info) {
var nodeWidth = 82;
var nodeHeight = 25;
var lineHeight = 20;
var portHeight = 10;
@@ -101,10 +101,9 @@ RED.palette = (function() {
if (label != type) {
l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>";
}
popOverContent = $(l+($("script[data-help-name|='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
popOverContent = $(l+(info?info:$("script[data-help-name|='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
.filter(function(n) {
return this.nodeType == 1 || (this.nodeType == 3 && this.textContent.trim().length > 0)
return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0)
}).slice(0,2);
} catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break
@@ -114,8 +113,7 @@ RED.palette = (function() {
popOverContent = "<p><b>"+label+"</b></p><p>"+RED._("palette.noInfo")+"</p>";
}
el.data('popover').options.content = popOverContent;
el.data('popover').setContent(popOverContent);
}
function escapeNodeType(nt) {
@@ -127,7 +125,6 @@ RED.palette = (function() {
if ($("#palette_node_"+nodeTypeId).length) {
return;
}
if (exclusion.indexOf(def.category)===-1) {
var category = def.category.replace(" ","_");
@@ -188,18 +185,30 @@ RED.palette = (function() {
$("#palette-"+category).append(d);
d.onmousedown = function(e) { e.preventDefault(); };
$(d).popover({
title:d.type,
placement:"right",
trigger: "hover",
delay: { show: 750, hide: 50 },
html: true,
container:'body'
RED.popover.create({
target:$(d),
content: "hi",
delay: { show: 750, hide: 50 }
});
// $(d).popover({
// title:d.type,
// placement:"right",
// trigger: "hover",
// delay: { show: 750, hide: 50 },
// html: true,
// container:'body'
// });
$(d).click(function() {
RED.view.focus();
var help = '<div class="node-help">'+($("script[data-help-name|='"+d.type+"']").html()||"")+"</div>";
$("#tab-info").html(help);
var helpText;
if (nt.indexOf("subflow:") === 0) {
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"");
} else {
helpText = $("script[data-help-name|='"+d.type+"']").html()||"";
}
var help = '<div class="node-help">'+helpText+"</div>";
RED.sidebar.info.set(help);
});
$(d).draggable({
helper: 'clone',
@@ -209,14 +218,15 @@ RED.palette = (function() {
start: function() {RED.view.focus();}
});
var nodeInfo = null;
if (def.category == "subflows") {
$(d).dblclick(function(e) {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
});
nodeInfo = marked(def.info||"");
}
setLabel(nt,$(d),label);
setLabel(nt,$(d),label,nodeInfo);
var categoryNode = $("#palette-container-"+category);
if (categoryNode.find(".palette_node").length === 1) {
@@ -269,7 +279,7 @@ RED.palette = (function() {
} else if (portOutput.length !== 0 && sf.out.length === 0) {
portOutput.remove();
}
setLabel(sf.type+":"+sf.id,paletteNode,sf.name);
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
});
}

79
editor/js/ui/popover.js Normal file
View File

@@ -0,0 +1,79 @@
/**
* 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.popover = (function() {
function createPopover(options) {
var target = options.target;
var content = options.content;
var delay = options.delay;
var timer = null;
var active;
var div;
var openPopup = function() {
if (active) {
div = $('<div class="red-ui-popover"></div>').html(content).appendTo("body");
var targetPos = target.offset();
var targetWidth = target.width();
var targetHeight = target.height();
var divHeight = div.height();
div.css({top: targetPos.top+targetHeight/2-divHeight/2-10,left:targetPos.left+targetWidth+17});
div.fadeIn("fast");
}
}
var closePopup = function() {
if (!active) {
if (div) {
div.fadeOut("fast",function() {
$(this).remove();
});
div = null;
}
}
}
target.on('mouseenter',function(e) {
clearTimeout(timer);
active = true;
timer = setTimeout(openPopup,delay.show);
});
target.on('mouseleave', function(e) {
if (timer) {
clearTimeout(timer);
}
active = false;
setTimeout(closePopup,delay.hide);
});
var res = {
setContent: function(_content) {
content = _content;
}
}
target.data('popover',res);
return res;
}
return {
create: createPopover
}
})();

View File

@@ -21,8 +21,11 @@ RED.subflow = (function() {
return RED.nodes.subflow(RED.workspaces.active());
}
function findAvailableSubflowIOPosition(subflow) {
var pos = {x:70,y:70};
function findAvailableSubflowIOPosition(subflow,isInput) {
var pos = {x:50,y:30};
if (!isInput) {
pos.x += 110;
}
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
var port;
if (i < subflow.out.length) {
@@ -40,7 +43,10 @@ RED.subflow = (function() {
function addSubflowInput() {
var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow);
if (subflow.in.length === 1) {
return;
}
var position = findAvailableSubflowIOPosition(subflow,true);
var newInput = {
type:"subflow",
direction:"in",
@@ -56,31 +62,50 @@ RED.subflow = (function() {
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.inputs = subflow.in.length;
RED.editor.updateNodeProperties(n);
}
});
var result = refresh(true);
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
inputCount: oldInCount
inputCount: oldInCount,
instances: result.instances
}
};
RED.history.push(historyEvent);
$("#workspace-subflow-add-input").toggleClass("disabled",true);
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw();
$("#workspace-subflow-input-add").addClass("active");
$("#workspace-subflow-input-remove").removeClass("active");
}
function removeSubflowInput() {
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
if (activeSubflow.in.length === 0) {
return;
}
var removedInput = activeSubflow.in[0];
var removedInputLinks = [];
RED.nodes.eachLink(function(l) {
if (l.source.type == "subflow" && l.source.z == activeSubflow.id && l.source.i == removedInput.i) {
removedInputLinks.push(l);
} else if (l.target.type == "subflow:"+activeSubflow.id) {
removedInputLinks.push(l);
}
});
removedInputLinks.forEach(function(l) { RED.nodes.removeLink(l)});
activeSubflow.in = [];
$("#workspace-subflow-input-add").removeClass("active");
$("#workspace-subflow-input-remove").addClass("active");
activeSubflow.changed = true;
return {subflowInputs: [ removedInput ], links:removedInputLinks};
}
function addSubflowOutput(id) {
var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow);
var position = findAvailableSubflowIOPosition(subflow,false);
var newOutput = {
type:"subflow",
@@ -98,45 +123,186 @@ RED.subflow = (function() {
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.outputs = subflow.out.length;
RED.editor.updateNodeProperties(n);
}
});
var result = refresh(true);
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
outputCount: oldOutCount
outputCount: oldOutCount,
instances: result.instances
}
};
RED.history.push(historyEvent);
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw();
$("#workspace-subflow-output .spinner-value").html(subflow.out.length);
}
function init() {
function removeSubflowOutput(removedSubflowOutputs) {
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
if (activeSubflow.out.length === 0) {
return;
}
if (typeof removedSubflowOutputs === "undefined") {
removedSubflowOutputs = [activeSubflow.out[activeSubflow.out.length-1]];
}
var removedLinks = [];
removedSubflowOutputs.sort(function(a,b) { return b.i-a.i});
for (i=0;i<removedSubflowOutputs.length;i++) {
var output = removedSubflowOutputs[i];
activeSubflow.out.splice(output.i,1);
var subflowRemovedLinks = [];
var subflowMovedLinks = [];
RED.nodes.eachLink(function(l) {
if (l.target.type == "subflow" && l.target.z == activeSubflow.id && l.target.i == output.i) {
subflowRemovedLinks.push(l);
}
if (l.source.type == "subflow:"+activeSubflow.id) {
if (l.sourcePort == output.i) {
subflowRemovedLinks.push(l);
} else if (l.sourcePort > output.i) {
subflowMovedLinks.push(l);
}
}
});
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
removedLinks = removedLinks.concat(subflowRemovedLinks);
for (var j=output.i;j<activeSubflow.out.length;j++) {
activeSubflow.out[j].i--;
activeSubflow.out[j].dirty = true;
}
}
activeSubflow.changed = true;
return {subflowOutputs: removedSubflowOutputs, links: removedLinks}
}
function refresh(markChange) {
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
refreshToolbar(activeSubflow);
var subflowInstances = [];
if (activeSubflow) {
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
subflowInstances.push({
id: n.id,
changed: n.changed
});
if (markChange) {
n.changed = true;
}
n.inputs = activeSubflow.in.length;
n.outputs = activeSubflow.out.length;
while (n.outputs < n.ports.length) {
n.ports.pop();
}
n.resize = true;
n.dirty = true;
RED.editor.updateNodeProperties(n);
});
RED.editor.validateNode(activeSubflow);
return {
instances: subflowInstances
}
}
}
function refreshToolbar(activeSubflow) {
if (activeSubflow) {
$("#workspace-subflow-input-add").toggleClass("active", activeSubflow.in.length !== 0);
$("#workspace-subflow-input-remove").toggleClass("active",activeSubflow.in.length === 0);
$("#workspace-subflow-output .spinner-value").html(activeSubflow.out.length);
}
}
function showWorkspaceToolbar(activeSubflow) {
var toolbar = $("#workspace-toolbar");
toolbar.empty();
$('<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowProperties"><i class="fa fa-pencil"></i> </a>').appendTo(toolbar);
$('<span style="margin-left: 5px;" data-i18n="subflow.input"></span> '+
'<div style="display: inline-block;" class="button-group">'+
'<a id="workspace-subflow-input-remove" class="button active" href="#">0</a>'+
'<a id="workspace-subflow-input-add" class="button" href="#">1</a>'+
'</div>').appendTo(toolbar);
$('<span style="margin-left: 5px;" data-i18n="subflow.output"></span> <div id="workspace-subflow-output" style="display: inline-block;" class="button-group spinner-group">'+
'<a id="workspace-subflow-output-remove" class="button" href="#"><i class="fa fa-minus"></i></a>'+
'<div class="spinner-value">3</div>'+
'<a id="workspace-subflow-output-add" class="button" href="#"><i class="fa fa-plus"></i></a>'+
'</div>').appendTo(toolbar);
// $('<a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
// $('<a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
$('<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>').appendTo(toolbar);
toolbar.i18n();
$("#workspace-subflow-output-remove").click(function(event) {
event.preventDefault();
var wasDirty = RED.nodes.dirty();
var wasChanged = activeSubflow.changed;
var result = removeSubflowOutput();
if (result) {
var inst = refresh(true);
RED.history.push({
t:'delete',
links:result.links,
subflowOutputs: result.subflowOutputs,
changed: wasChanged,
dirty:wasDirty,
subflow: {
instances: inst.instances
}
});
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw(true);
}
});
$("#workspace-subflow-output-add").click(function(event) {
event.preventDefault();
addSubflowOutput();
});
$("#workspace-subflow-input-add").click(function(event) {
event.preventDefault();
addSubflowInput();
});
$("#workspace-subflow-input-remove").click(function(event) {
event.preventDefault();
var wasDirty = RED.nodes.dirty();
var wasChanged = activeSubflow.changed;
activeSubflow.changed = true;
var result = removeSubflowInput();
if (result) {
var inst = refresh(true);
RED.history.push({
t:'delete',
links:result.links,
changed: wasChanged,
subflowInputs: result.subflowInputs,
dirty:wasDirty,
subflow: {
instances: inst.instances
}
});
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw(true);
}
});
$("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
event.preventDefault();
});
$("#workspace-subflow-add-input").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowInput();
});
$("#workspace-subflow-add-output").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowOutput();
});
$("#workspace-subflow-delete").click(function(event) {
event.preventDefault();
@@ -144,11 +310,18 @@ RED.subflow = (function() {
var removedLinks = [];
var startDirty = RED.nodes.dirty();
var activeSubflow = getSubflow();
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+getSubflow().id) {
if (n.type == "subflow:"+activeSubflow.id) {
removedNodes.push(n);
}
if (n.z == getSubflow().id) {
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
RED.nodes.eachConfig(function(n) {
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
@@ -162,15 +335,15 @@ RED.subflow = (function() {
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
removedNodes = removedNodes.concat(removedConfigNodes);
var activeSubflow = getSubflow();
RED.nodes.removeSubflow(activeSubflow);
RED.history.push({
t:'delete',
nodes:removedNodes,
links:removedLinks,
subflow: activeSubflow,
subflow: {
subflow: activeSubflow
},
dirty:startDirty
});
@@ -179,6 +352,26 @@ RED.subflow = (function() {
RED.view.redraw();
});
refreshToolbar(activeSubflow);
$("#chart").css({"margin-top": "40px"});
$("#workspace-toolbar").show();
}
function hideWorkspaceToolbar() {
$("#workspace-toolbar").hide().empty();
$("#chart").css({"margin-top": "0"});
}
function init() {
RED.events.on("workspace:change",function(event) {
var activeSubflow = RED.nodes.subflow(event.workspace);
if (activeSubflow) {
showWorkspaceToolbar(activeSubflow);
} else {
hideWorkspaceToolbar();
}
});
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-subflow-convert",true);
@@ -205,16 +398,20 @@ RED.subflow = (function() {
type:"subflow",
id:subflowId,
name:name,
info:"",
in: [],
out: []
};
RED.nodes.addSubflow(subflow);
RED.history.push({
t:'createSubflow',
subflow: subflow,
subflow: {
subflow:subflow
},
dirty:RED.nodes.dirty()
});
RED.workspaces.show(subflowId);
RED.nodes.dirty(true);
}
function convertToSubflow() {
@@ -230,6 +427,8 @@ RED.subflow = (function() {
var candidateInputs = [];
var candidateOutputs = [];
var candidateInputNodes = {};
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
@@ -262,6 +461,7 @@ RED.subflow = (function() {
if (!nodes[link.source.id] && nodes[link.target.id]) {
// An inbound link
candidateInputs.push(link);
candidateInputNodes[link.target.id] = link.target;
removedLinks.push(link);
}
});
@@ -279,15 +479,10 @@ RED.subflow = (function() {
});
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
if (candidateInputs.length > 1) {
if (Object.keys(candidateInputNodes).length > 1) {
RED.notify(RED._("subflow.errors.multipleInputsToSelection"),"error");
return;
}
//if (candidateInputs.length == 0) {
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
// return;
//}
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
@@ -304,15 +499,16 @@ RED.subflow = (function() {
type:"subflow",
id:subflowId,
name:name,
in: candidateInputs.map(function(v,i) { var index = i; return {
info:"",
in: Object.keys(candidateInputNodes).map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.target.x-(v.target.w/2)-80,
y:v.target.y,
x:candidateInputNodes[v].x-(candidateInputNodes[v].w/2)-80,
y:candidateInputNodes[v].y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.target.id}]
wires:[{id:candidateInputNodes[v].id}]
}}),
out: candidateOutputs.map(function(v,i) { var index = i; return {
type:"subflow",
@@ -325,6 +521,7 @@ RED.subflow = (function() {
wires:[{id:v.source.id,port:v.sourcePort}]
}})
};
RED.nodes.addSubflow(subflow);
var subflowInstance = {
@@ -383,7 +580,9 @@ RED.subflow = (function() {
t:'createSubflow',
nodes:[subflowInstance.id],
links:new_links,
subflow: subflow,
subflow: {
subflow: subflow
},
activeWorkspace: RED.workspaces.active(),
removedLinks: removedLinks,
@@ -401,6 +600,9 @@ RED.subflow = (function() {
return {
init: init,
createSubflow: createSubflow,
convertToSubflow: convertToSubflow
convertToSubflow: convertToSubflow,
refresh: refresh,
removeInput: removeSubflowInput,
removeOutput: removeSubflowOutput
}
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +15,95 @@
**/
RED.sidebar.config = (function() {
var content = document.createElement("div");
content.style.paddingTop = "4px";
content.style.paddingLeft = "4px";
content.style.paddingRight = "4px";
var list = $("<ul>",{class:"tab-config-list"}).appendTo(content);
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);
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;
});
list.empty();
if (nodes.length === 0) {
$('<li class="config_node_none" data-i18n="sidebar.config.none">NONE</li>').i18n().appendTo(list);
} else {
var currentType = "";
nodes.forEach(function(node) {
var label = "";
if (typeof node._def.label == "function") {
label = node._def.label.call(node);
} else {
label = node._def.label;
}
label = label || node.id;
if (node.type != currentType) {
$('<li class="config_node_type">'+node.type+'</li>').appendTo(list);
currentType = node.type;
}
var entry = $('<li class="palette_node config_node"></li>').appendTo(list);
$('<div class="palette_label"></div>').text(label).appendTo(entry);
var iconContainer = $('<div/>',{class:"palette_icon_container palette_icon_container_right"}).text(node.users.length).appendTo(entry);
if (node.users.length === 0) {
entry.addClass("config_node_unused");
}
entry.on('click',function(e) {
RED.sidebar.info.refresh(node);
});
entry.on('dblclick',function(e) {
RED.editor.editConfig("", node.type, node.id);
});
var userArray = node.users.map(function(n) { return n.id });
entry.on('mouseover',function(e) {
RED.nodes.eachNode(function(node) {
if( userArray.indexOf(node.id) != -1) {
node.highlighted = true;
node.dirty = true;
}
});
RED.view.redraw();
});
entry.on('mouseout',function(e) {
RED.nodes.eachNode(function(node) {
if(node.highlighted) {
node.highlighted = false;
node.dirty = true;
}
});
RED.view.redraw();
});
});
}
}
function refreshConfigNodeList() {
var localConfigNodes = [];
var globalConfigNodes = [];
RED.nodes.eachConfig(function(cn) {
if (cn.z == RED.workspaces.active()) {
localConfigNodes.push(cn);
} else if (!cn.z) {
globalConfigNodes.push(cn);
}
});
createConfigNodeList(localConfigNodes,$("#workspace-config-node-tray-locals"));
createConfigNodeList(globalConfigNodes,$("#workspace-config-node-tray-globals"));
}
function init() {
RED.sidebar.addTab({
@@ -30,63 +113,29 @@ RED.sidebar.config = (function() {
content: content,
closeable: true,
visible: false,
onchange: function() { refresh(); }
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();
}
});
}
function show() {
refresh();
refreshConfigNodeList();
RED.sidebar.show("config");
}
function refresh() {
list.empty();
RED.nodes.eachConfig(function(node) {
var li = list.find("#tab-config-list-type-"+node.type);
if (li.length === 0) {
li = $("<li>",{id:"tab-config-list-type-"+node.type}).appendTo(list);
$('<div class="tab-config-list-type">'+node.type+'</div>').appendTo(li);
}
var label = "";
if (typeof node._def.label == "function") {
label = node._def.label.call(node);
} else {
label = node._def.label;
}
label = label || "&nbsp;";
var entry = $('<div class="tab-config-list-entry"></div>').appendTo(li);
entry.on('dblclick',function(e) {
RED.editor.editConfig("", node.type, node.id);
});
var userArray = node.users.map(function(n) { return n.id });
entry.on('mouseover',function(e) {
RED.nodes.eachNode(function(node) {
if( userArray.indexOf(node.id) != -1) {
node.highlighted = true;
node.dirty = true;
}
});
RED.view.redraw();
});
entry.on('mouseout',function(e) {
RED.nodes.eachNode(function(node) {
if(node.highlighted) {
node.highlighted = false;
node.dirty = true;
}
});
RED.view.redraw();
});
$('<div class="tab-config-list-label">'+label+'</div>').appendTo(entry);
$('<div class="tab-config-list-users">'+node.users.length+'</div>').appendTo(entry);
});
}
return {
init:init,
show:show,
refresh:refresh
refresh:refreshConfigNodeList
}
})();

View File

@@ -75,8 +75,8 @@ RED.sidebar.info = (function() {
table += "<tr><td>"+RED._("sidebar.info.id")+"</td><td>&nbsp;"+node.id+"</td></tr>";
var m = /^subflow(:(.+))?$/.exec(node.type);
var subflowNode;
if (m) {
var subflowNode;
if (m[2]) {
subflowNode = RED.nodes.subflow(m[2]);
} else {
@@ -135,12 +135,13 @@ RED.sidebar.info = (function() {
}
}
table += "</tbody></table><hr/>";
if (node.type != "comment") {
if (!subflowNode && node.type != "comment") {
var helpText = $("script[data-help-name|='"+node.type+"']").html()||"";
table += '<div class="node-help">'+helpText+"</div>";
}
if (node._def && node._def.info) {
if (subflowNode) {
table += '<div class="node-help">'+marked(subflowNode.info||"")+'</div>';
} else if (node._def && node._def.info) {
var info = node._def.info;
table += '<div class="node-help">'+marked(typeof info === "function" ? info.call(node) : info)+'</div>';
//table += '<div class="node-help">'+(typeof info === "function" ? info.call(node) : info)+'</div>';
@@ -167,7 +168,11 @@ RED.sidebar.info = (function() {
}
function clear() {
$("#tab-info").html("");
$(content).html("");
}
function set(html) {
$(content).html(html);
}
RED.events.on("view:selection-changed",function(selection) {
@@ -194,6 +199,7 @@ RED.sidebar.info = (function() {
init: init,
show: show,
refresh:refresh,
clear: clear
clear: clear,
set: set
}
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,12 +80,15 @@ RED.tabs = (function() {
tabs.css({width:currentTabWidth+"%"});
if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide();
ul.find(".red-ui-tab-icon").hide();
} else {
ul.find(".red-ui-tab-close").show();
ul.find(".red-ui-tab-icon").show();
}
if (currentActiveTabWidth !== 0) {
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
}
}
@@ -116,13 +119,16 @@ RED.tabs = (function() {
tabs[tab.id] = tab;
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
link.html(tab.label);
if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
}
$('<span/>').text(tab.label).appendTo(link);
link.on("click",onTabClick);
link.on("dblclick",onTabDblClick);
if (tab.closeable) {
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
closeLink.html('<i class="fa fa-times" />');
closeLink.append('<i class="fa fa-times" />');
closeLink.on("click",function(event) {
removeTab(tab.id);
@@ -150,7 +156,7 @@ RED.tabs = (function() {
tabs[id].label = label;
var tab = ul.find("a[href='#"+id+"']");
tab.attr("title",label);
tab.text(label);
tab.find("span").text(label);
updateTabWidths();
}

View File

@@ -255,9 +255,6 @@ RED.view = (function() {
var scrollStartTop = chart.scrollTop();
activeSubflow = RED.nodes.subflow(event.workspace);
if (activeSubflow) {
$("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0);
}
RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
@@ -351,7 +348,23 @@ RED.view = (function() {
nn.changed = true;
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
RED.history.push({t:'add',nodes:[nn.id],dirty:RED.nodes.dirty()});
var historyEvent = {
t:'add',
nodes:[nn.id],
dirty:RED.nodes.dirty()
}
if (activeSubflow) {
var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) {
historyEvent.subflow = {
id:activeSubflow.id,
changed: activeSubflow.changed,
instances: subflowRefresh.instances
}
}
}
RED.history.push(historyEvent);
RED.nodes.add(nn);
RED.editor.validateNode(nn);
RED.nodes.dirty(true);
@@ -394,8 +407,8 @@ RED.view = (function() {
lasso = vis.append('rect')
.attr("ox",point[0])
.attr("oy",point[1])
.attr("rx",2)
.attr("ry",2)
.attr("rx",1)
.attr("ry",1)
.attr("x",point[0])
.attr("y",point[1])
.attr("width",0)
@@ -741,12 +754,15 @@ RED.view = (function() {
redraw();
}
function deleteSelection() {
var result;
var removedNodes = [];
var removedLinks = [];
var removedSubflowOutputs = [];
var removedSubflowInputs = [];
var subflowInstances = [];
var startDirty = RED.nodes.dirty();
var startChanged = false;
if (moving_set.length > 0) {
for (var i=0;i<moving_set.length;i++) {
var node = moving_set[i].n;
@@ -769,65 +785,22 @@ RED.view = (function() {
}
}
if (removedSubflowOutputs.length > 0) {
removedSubflowOutputs.sort(function(a,b) { return b.i-a.i});
for (i=0;i<removedSubflowOutputs.length;i++) {
var output = removedSubflowOutputs[i];
activeSubflow.out.splice(output.i,1);
var subflowRemovedLinks = [];
var subflowMovedLinks = [];
RED.nodes.eachLink(function(l) {
if (l.target.type == "subflow" && l.target.z == activeSubflow.id && l.target.i == output.i) {
subflowRemovedLinks.push(l);
}
if (l.source.type == "subflow:"+activeSubflow.id) {
if (l.sourcePort == output.i) {
subflowRemovedLinks.push(l);
} else if (l.sourcePort > output.i) {
subflowMovedLinks.push(l);
}
}
});
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
removedLinks = removedLinks.concat(subflowRemovedLinks);
for (var j=output.i;j<activeSubflow.out.length;j++) {
activeSubflow.out[j].i--;
activeSubflow.out[j].dirty = true;
}
result = RED.subflow.removeOutput(removedSubflowOutputs);
if (result) {
removedLinks = removedLinks.concat(result.links);
}
}
// Assume 0/1 inputs
if (removedSubflowInputs.length == 1) {
var input = removedSubflowInputs[0];
var subflowRemovedInputLinks = [];
RED.nodes.eachLink(function(l) {
if (l.source.type == "subflow" && l.source.z == activeSubflow.id && l.source.i == input.i) {
subflowRemovedInputLinks.push(l);
} else if (l.target.type == "subflow:"+activeSubflow.id) {
subflowRemovedInputLinks.push(l);
}
});
subflowRemovedInputLinks.forEach(function(l) { RED.nodes.removeLink(l)});
removedLinks = removedLinks.concat(subflowRemovedInputLinks);
activeSubflow.in = [];
$("#workspace-subflow-add-input").toggleClass("disabled",false);
result = RED.subflow.removeInput();
if (result) {
removedLinks = removedLinks.concat(result.links);
}
}
if (activeSubflow) {
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
n.changed = true;
n.inputs = activeSubflow.in.length;
n.outputs = activeSubflow.out.length;
while (n.outputs < n.ports.length) {
n.ports.pop();
}
n.resize = true;
n.dirty = true;
});
RED.editor.validateNode(activeSubflow);
var instances = RED.subflow.refresh(true);
if (instances) {
subflowInstances = instances.instances;
}
moving_set = [];
if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) {
RED.nodes.dirty(true);
@@ -838,7 +811,18 @@ RED.view = (function() {
removedLinks.push(selected_link);
RED.nodes.dirty(true);
}
RED.history.push({t:'delete',nodes:removedNodes,links:removedLinks,subflowOutputs:removedSubflowOutputs,subflowInputs:removedSubflowInputs,dirty:startDirty});
var historyEvent = {
t:'delete',
nodes:removedNodes,
links:removedLinks,
subflowOutputs:removedSubflowOutputs,
subflowInputs:removedSubflowInputs,
subflow: {
instances: subflowInstances
},
dirty:startDirty
};
RED.history.push(historyEvent);
selected_link = null;
updateActiveNodes();
@@ -945,7 +929,22 @@ RED.view = (function() {
if (!existingLink) {
var link = {source: src, sourcePort:src_port, target: dst};
RED.nodes.addLink(link);
RED.history.push({t:'add',links:[link],dirty:RED.nodes.dirty()});
var historyEvent = {
t:'add',
links:[link],
dirty:RED.nodes.dirty()
};
if (activeSubflow) {
var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) {
historyEvent.subflow = {
id:activeSubflow.id,
changed: activeSubflow.changed,
instances: subflowRefresh.instances
}
}
}
RED.history.push(historyEvent);
updateActiveNodes();
RED.nodes.dirty(true);
} else {
@@ -1066,7 +1065,7 @@ RED.view = (function() {
function showTouchMenu(obj,pos) {
var mdn = mousedown_node;
var options = [];
options.push({name:"delete",disabled:(moving_set.length===0),onselect:function() {deleteSelection();}});
options.push({name:"delete",disabled:(moving_set.length===0 && selected_link === null),onselect:function() {deleteSelection();}});
options.push({name:"cut",disabled:(moving_set.length===0),onselect:function() {copySelection();deleteSelection();}});
options.push({name:"copy",disabled:(moving_set.length===0),onselect:function() {copySelection();}});
options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,true);}});
@@ -1360,7 +1359,9 @@ RED.view = (function() {
var text = node.append('svg:text').attr('class','node_label').attr('x', 38).attr('dy', '.35em').attr('text-anchor','start');
if (d._def.align) {
text.attr('class','node_label node_label_'+d._def.align);
text.attr('text-anchor','end');
if (d._def.align === "right") {
text.attr('text-anchor','end');
}
}
var status = node.append("svg:g").attr("class","node_status_group").style("display","none");
@@ -1371,14 +1372,7 @@ RED.view = (function() {
var statusLabel = status.append("svg:text")
.attr("class","node_status_label")
.attr('x',20).attr('y',9)
.style({
'stroke-width': 0,
'fill': '#888',
'font-size':'9pt',
'stroke':'#000',
'text-anchor':'start'
});
.attr('x',20).attr('y',9);
//node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5});
@@ -1616,6 +1610,14 @@ RED.view = (function() {
redraw();
focusView();
d3.event.stopPropagation();
var obj = d3.select(document.body);
var touch0 = d3.event.touches.item(0);
var pos = [touch0.pageX,touch0.pageY];
touchStartTime = setTimeout(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")
@@ -1695,6 +1697,10 @@ RED.view = (function() {
*/
function importNodes(newNodesStr,touchImport) {
try {
var activeSubflowChanged;
if (activeSubflow) {
activeSubflowChanged = activeSubflow.changed;
}
var result = RED.nodes.import(newNodesStr,true);
if (result) {
var new_nodes = result[0];
@@ -1702,7 +1708,7 @@ RED.view = (function() {
var new_workspaces = result[2];
var new_subflows = result[3];
var new_ms = new_nodes.filter(function(n) { return n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty('x') && n.hasOwnProperty('y') && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
var new_node_ids = new_nodes.map(function(n){ return n.id; });
// TODO: pick a more sensible root node
@@ -1752,14 +1758,25 @@ RED.view = (function() {
moving_set = new_ms;
}
RED.history.push({
var historyEvent = {
t:'add',
nodes:new_node_ids,
links:new_links,
workspaces:new_workspaces,
subflows:new_subflows,
dirty:RED.nodes.dirty()
});
};
if (activeSubflow) {
var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) {
historyEvent.subflow = {
id:activeSubflow.id,
changed: activeSubflowChanged,
instances: subflowRefresh.instances
}
}
}
RED.history.push(historyEvent);
updateActiveNodes();
redraw();
@@ -1788,7 +1805,6 @@ RED.view = (function() {
if (updateActive) {
updateActiveNodes();
}
RED.workspaces.refresh();
redraw();
},
focus: focusView,

View File

@@ -84,19 +84,13 @@ RED.workspaces = (function() {
workspace_tabs = RED.tabs.create({
id: "workspace-tabs",
onchange: function(tab) {
if (tab.type == "subflow") {
$("#chart").css({"margin-top": "40px"});
$("#workspace-toolbar").show();
} else {
$("#workspace-toolbar").hide();
$("#chart").css({"margin-top": "0"});
}
var event = {
old: activeWorkspace
}
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
RED.events.emit("workspace:change",event);
RED.sidebar.config.refresh();
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
@@ -221,6 +215,7 @@ RED.workspaces = (function() {
}
}
}
return {
init: init,
add: addWorkspace,
@@ -242,7 +237,7 @@ RED.workspaces = (function() {
if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id);
if (sf) {
addWorkspace({type:"subflow",id:id,label:RED._("subflow.tabLabel",{name:sf.name}), closeable: true});
addWorkspace({type:"subflow",id:id,icon:"red/images/subflow_tab.png",label:sf.name, closeable: true});
}
}
workspace_tabs.activateTab(id);
@@ -250,9 +245,10 @@ RED.workspaces = (function() {
refresh: function() {
RED.nodes.eachSubflow(function(sf) {
if (workspace_tabs.contains(sf.id)) {
workspace_tabs.renameTab(sf.id,RED._("subflow.tabLabel",{name:sf.name}));
workspace_tabs.renameTab(sf.id,sf.name);
}
});
RED.sidebar.config.refresh();
},
resize: function() {
workspace_tabs.resize();

View File

@@ -23,8 +23,8 @@
border-color: rgb(214, 97, 95) !important;
}
.leftButton {
margin-right: 200px !important;
.ui-dialog .ui-dialog-buttonpane button.leftButton {
margin-right: 40px;
}
.form-row {
@@ -42,7 +42,6 @@
.form-tips {
background: #ffe;
font-size: 12px;
padding: 8px;
border-radius: 2px;
border: 1px solid $secondary-border-color;
@@ -67,7 +66,7 @@
.editor-button {
@include workspace-button;
height: 34px;
line-height: 30px;
line-height: 32px;
font-size: 13px;
border-radius: 4px;
padding: 0 10px;
@@ -79,3 +78,29 @@
border-radius: 2px;
padding: 0 5px;
}
#node-config-dialog-scope-container {
cursor: auto;
float: right;
font-size: 12px !important;
}
#node-config-dialog-scope-warning {
display: inline-block;
margin-right: 5px;
color: #AD1625;
vertical-align: middle;
}
#node-config-dialog-scope {
margin: 1px 0 0 0;
padding: 0;
height: 22px;
width: 110px;
}
#node-config-dialog-user-count {
vertical-align: middle;
display:inline-block;
margin-top: 10px;
margin-right: 20px;
float:left;
font-size: 12px;
}

View File

@@ -19,7 +19,7 @@
}
.lasso {
stroke-width: 2px;
stroke-width: 1px;
stroke: #ff7f0e;
fill: rgba(20,125,255,0.1);
stroke-dasharray: 10 5;
@@ -165,6 +165,15 @@
.node_hovered {
}
.node_status_label {
@include disable-selection;
stroke-width: 0;
fill: #888;
font-size:9pt;
stroke:#000;
text-anchor:start;
}
.port_hovered {
stroke: $port-selected-color;
fill: $port-selected-color;

View File

@@ -47,7 +47,7 @@ span.logo {
font-size: 30px;
line-height: 30px;
text-decoration: none;
span {
vertical-align: middle;
font-size: 16px !important;
@@ -55,14 +55,14 @@ span.logo {
img {
height: 18px;
}
a {
color: inherit;
&:hover {
text-decoration: none;
}
}
}
.header-toolbar {
@@ -70,13 +70,13 @@ span.logo {
margin: 0;
list-style: none;
float: right;
> li {
display: inline-block;
padding: 0;
margin: 0;
position: relative;
}
}
@@ -97,19 +97,19 @@ span.logo {
vertical-align: middle;
border-left: 2px solid #000;
border-right: 2px solid #000;
&:hover {
border-color: $headerMenuItemHover;
}
}
.button-group {
#header .button-group {
display: inline-block;
margin: auto 15px;
vertical-align: middle;
clear: both;
}
.button-group > a {
#header .button-group > a {
display: inline-block;
float: left;
line-height: 22px;
@@ -122,11 +122,11 @@ span.logo {
.deploy-button {
background: $deployButton;
color: #eee !important;
&:hover {
background: $deployButtonHover;
}
&:active {
background: $deployButtonActive;
color: #ccc !important;
@@ -134,18 +134,18 @@ span.logo {
}
#btn-deploy {
padding: 4px 12px;
&.disabled {
cursor: default;
background: $deployDisabledButton;
color: #999 !important;
img {
opacity: 0.3;
}
&+ #btn-deploy-options {
background: $deployDisabledButton;
color: #ddd;
@@ -157,7 +157,7 @@ span.logo {
background: $deployDisabledButton;
}
}
img {
margin-right: 8px;
}
@@ -264,4 +264,3 @@ span.logo {
font-size: 16px;
color: #fff;
}

View File

@@ -14,6 +14,15 @@
* limitations under the License.
**/
.ui-widget {
font-size: 14px !important;
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
}
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button {
font-size: 14px !important;
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
}
/* jQuery Theme overrides */
.ui-tabs .ui-tabs-panel {
padding: 0px;
@@ -27,14 +36,16 @@
.ui-dialog {
border-radius: 1px;
border: 1px solid #eee;
background: #fff;
padding: 0;
box-shadow: 2px 2px 12px rgba(0,0,0,0.2);
@include component-shadow;
}
.ui-dialog .ui-dialog-content {
padding: 25px 25px 10px 25px;
}
.ui-dialog .ui-dialog-title {
width: auto;
}
.ui-dialog .ui-dialog-titlebar {
padding: 10px;
background: #f3f3f3;
@@ -51,3 +62,14 @@
.ui-dialog-no-close .ui-dialog-titlebar-close {
display: none;
}
.ui-dialog-buttonset {
text-align: right;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: none;
}
.ui-dialog .ui-dialog-buttonpane {
padding: .3em 1em .5em 1em;
}

View File

@@ -50,10 +50,12 @@
}
&:not(.disabled):focus {
color: $workspace-button-color-focus;
text-decoration: none;
}
&:not(.disabled):active {
color: $workspace-button-color-active;
background: $workspace-button-background-active;
text-decoration: none;
}
}
@mixin component-footer {
@@ -75,4 +77,14 @@
line-height: 17px;
width: 18px;
height: 18px;
&.text-button {
width: auto;
padding: 0 5px;
}
}
@mixin component-shadow {
border: 1px solid $secondary-border-color;
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
}

View File

@@ -200,7 +200,7 @@
left: auto;
right: 0;
border-right: none;
border-left: 2px solid rgba(0,0,0,0.1);
border-left: 1px solid rgba(0,0,0,0.1);
}
.palette_icon {
display: inline-block;

53
editor/sass/popover.scss Normal file
View File

@@ -0,0 +1,53 @@
/**
* 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-popover {
display: none;
position: absolute;
width: 300px;
padding: 10px;
height: auto;
background: #fff;
z-index: 1000;
font-size: 14px;
line-height: 1.4em;
@include component-shadow;
}
.red-ui-popover:after, .red-ui-popover:before {
right: 100%;
top: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.red-ui-popover:after {
border-color: rgba(136, 183, 213, 0);
border-right-color: #fff;
border-width: 10px;
margin-top: -10px;
}
.red-ui-popover:before {
border-color: rgba(194, 225, 245, 0);
border-right-color: $primary-border-color;
border-width: 11px;
margin-top: -11px;
}

View File

@@ -38,7 +38,7 @@
@import "tabs";
@import "tab-config";
@import "tab-info";
@import "popover";
@import "flow";
@import "dragdrop";
@@ -47,9 +47,10 @@
body {
font: 14px "Helvetica" !important;
padding-top: 100px;
background: #f3f3f3;
font-size: 14px;
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;
padding-top: 100px;
background: #f3f3f3;
}
#main-container {

View File

@@ -13,51 +13,57 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
ul.tab-config-list {
list-style-type: none;
padding: 3px;
margin: 0;
.sidebar-node-config {
background: #f3f3f3;
height: 100%;
overflow-y:auto;
@include disable-selection;
}
ul.tab-config-list li {
max-width: 400px;
font-size: 13px;
background: #f3f3f3;
margin: 10px auto;
border-radius: 3px;
border: 1px solid #ccc;
padding: 3px 8px;
}
div.tab-config-list-type {
}
.config-node-list {
margin: 0;
list-style-type: none;
}
.config_node {
width: 160px;
height: 30px;
background: #f3f3f3;
color: #666;
cursor: pointer;
div.tab-config-list-entry {
position: relative;
margin: 4px 0;
padding: 8px 4px 8px 10px;
background: #fff;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
div.tab-config-list-entry:hover {
background: #f6f6f6;
}
div.tab-config-list-label {
}
div.tab-config-list-users {
position: absolute;
right: 3px;
top: 3px;
bottom: 3px;
line-height: 27px;
font-size: 11px;
background: #f6f6f6;
float: right;
border: 1px solid #eee;
border-radius: 3px;
padding: 1px 5px;
}
.palette_label {
margin-left: 8px;
line-height: 24px;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.palette_icon_container {
font-size: 12px;
line-height: 30px;
background-color: #e8e8e8;
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;
}

View File

@@ -21,13 +21,10 @@ ul.red-ui-tabs {
display: block;
height: 35px;
box-sizing:border-box;
white-space: nowrap;
border-bottom: 1px solid $primary-border-color;
background: #fff;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
@include disable-selection;
}
ul.red-ui-tabs li {
@@ -49,6 +46,7 @@ ul.red-ui-tabs li {
ul.red-ui-tabs li a.red-ui-tab-label {
display: block;
font-size: 14px;
padding-left: 12px;
width: 100%;
height: 100%;
@@ -110,3 +108,16 @@ ul.red-ui-tabs li.active {
ul.red-ui-tabs li.active a {
color: #333;
}
ul.red-ui-tabs li img {
margin-left: -8px;
margin-right: 3px;
margin-top: -2px;
opacity: 0.1;
width: 20px;
height: 20px;
vertical-align: middle;
}
ul.red-ui-tabs li.active img {
opacity: 0.2;
}

View File

@@ -24,6 +24,7 @@
left:0px;
right:0px;
box-sizing:border-box;
transition: right 0.2s ease;
}
#chart svg:focus {

View File

@@ -17,21 +17,60 @@
#workspace-toolbar {
display: none;
color: $workspace-button-color;
font-size: 12px;
line-height: 18px;
position: absolute;
top: 35px;
left:0;
width: 100%;
right: 0;
padding: 7px;
height: 40px;
box-sizing: border-box;
background: #fff;
border-bottom: 1px solid $secondary-border-color;
}
white-space: nowrap;
transition: right 0.2s ease;
overflow: hidden;
.button {
@include workspace-button;
margin-right: 10px;
padding: 2px 8px;
}
.button-group {
@include disable-selection;
.button:first-child {
margin-right: 0;
}
&:not(.spinner-group) {
.button:not(:first-child) {
border-left: none;
}
}
.button.active {
background: $workspace-button-background-active;
cursor: default;
}
}
.spinner-value {
width: 25px;
color: $workspace-button-color;
padding: 0 5px;
display: inline-block;
text-align: center;
border-top: 1px solid $secondary-border-color;
border-bottom: 1px solid $secondary-border-color;
margin: 0;
height: 24px;
font-size: 12px;
background: #f9f9f9;
line-height: 22px;
box-sizing: border-box;
}
#workspace-toolbar .button {
@include workspace-button;
line-height: 18px;
font-size: 12px;
margin-right: 5px;
padding: 2px 8px;
}

View File

@@ -58,12 +58,7 @@
<ul id="workspace-tabs"></ul>
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
<div id="chart"></div>
<div id="workspace-toolbar">
<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowName"><i class="fa fa-pencil"></i> </a>
<a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>
<a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>
<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>
</div>
<div id="workspace-toolbar"></div>
<div id="workspace-footer">
<a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a>
<a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a>
@@ -84,24 +79,33 @@
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"><div id="node-config-dialog-edit-form"></div><!--<div id="node-config-dialog-toolbar" class="form-row"><label><span>Node scope</span></label><select id="node-config-dialog-scope"></select></div>--></form></div>
<div id="subflow-dialog" class="hide">
<form class="form-horizontal">
<div class="form-row">
<label data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
<label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="subflow-input-info" data-i18n="subflow.info"></label>
<a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a>
</div>
<div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="subflow-input-info-editor"></div>
</div>
<div class="form-row form-tips" id="subflow-dialog-user-count"></div>
</form>
<div class="form-tips" id="subflow-dialog-user-count"></div>
</div>
<div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm"> </div>
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-invalid-list"></ul>
</div>
<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>
<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>
<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>

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

File diff suppressed because one or more lines are too long

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

Binary file not shown.

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

Binary file not shown.

105
editor/vendor/font-awesome/fonts/fontawesome-webfont.svg vendored Normal file → Executable file
View File

@@ -399,7 +399,7 @@
<glyph unicode="&#xf191;" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 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="&#xf192;" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf193;" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" />
<glyph unicode="&#xf194;" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.5zM1536 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="&#xf194;" d="M1292 898q10 216 -161 222q-231 8 -312 -261q44 19 82 19q85 0 74 -96q-4 -57 -74 -167t-105 -110q-43 0 -82 169q-13 54 -45 255q-30 189 -160 177q-59 -7 -164 -100l-81 -72l-81 -72l52 -67q76 52 87 52q57 0 107 -179q15 -55 45 -164.5t45 -164.5q68 -179 164 -179 q157 0 383 294q220 283 226 444zM1536 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="&#xf195;" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
<glyph unicode="&#xf196;" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf197;" horiz-adv-x="2176" d="M620 416q-110 -64 -268 -64h-128v64h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5t38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40 t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113zM1739 668q53 -36 53 -92t-53 -92l81 -30q68 48 68 122t-68 122zM625 400h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96l-93 464h29 q157 0 273 64zM352 816h-29l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64z" />
@@ -411,8 +411,8 @@
<glyph unicode="&#xf19d;" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" />
<glyph unicode="&#xf19e;" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" />
<glyph unicode="&#xf1a0;" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" />
<glyph unicode="&#xf1a1;" horiz-adv-x="2304" d="M1509 107q0 -14 -12 -29q-52 -59 -147.5 -83t-196.5 -24q-252 0 -346 107q-12 15 -12 29q0 17 12 29.5t29 12.5q15 0 30 -12q58 -49 125.5 -66t159.5 -17t160 17t127 66q15 12 30 12q17 0 29 -12.5t12 -29.5zM978 498q0 -61 -43 -104t-104 -43q-60 0 -104.5 43.5 t-44.5 103.5q0 61 44 105t105 44t104 -44t43 -105zM1622 498q0 -61 -43 -104t-104 -43q-60 0 -104.5 43.5t-44.5 103.5q0 61 44 105t105 44t104 -44t43 -105zM415 793q-39 27 -88 27q-66 0 -113 -47t-47 -113q0 -72 54 -121q53 141 194 254zM2020 382q0 222 -249 387 q-128 85 -291.5 126.5t-331.5 41.5t-331.5 -41.5t-292.5 -126.5q-249 -165 -249 -387t249 -387q129 -85 292.5 -126.5t331.5 -41.5t331.5 41.5t291.5 126.5q249 165 249 387zM2137 660q0 66 -47 113t-113 47q-50 0 -93 -30q140 -114 192 -256q61 48 61 126zM1993 1335 q0 49 -34.5 83.5t-82.5 34.5q-49 0 -83.5 -34.5t-34.5 -83.5q0 -48 34.5 -82.5t83.5 -34.5q48 0 82.5 34.5t34.5 82.5zM2220 660q0 -65 -33 -122t-89 -90q5 -35 5 -66q0 -139 -79 -255.5t-208 -201.5q-140 -92 -313.5 -136.5t-354.5 -44.5t-355 44.5t-314 136.5 q-129 85 -208 201.5t-79 255.5q0 36 6 71q-53 33 -83.5 88.5t-30.5 118.5q0 100 71 171.5t172 71.5q91 0 159 -60q265 170 638 177l144 456q10 29 40 29q24 0 384 -90q24 55 74 88t110 33q82 0 141 -59t59 -142t-59 -141.5t-141 -58.5q-83 0 -141.5 58.5t-59.5 140.5 l-339 80l-125 -395q349 -15 603 -179q71 63 163 63q101 0 172 -71.5t71 -171.5z" />
<glyph unicode="&#xf1a2;" d="M950 393q7 7 17.5 7t17.5 -7t7 -18t-7 -18q-65 -64 -208 -64h-1h-1q-143 0 -207 64q-8 7 -8 18t8 18q7 7 17.5 7t17.5 -7q49 -51 172 -51h1h1q122 0 173 51zM671 613q0 -37 -26 -64t-63 -27t-63 27t-26 64t26 63t63 26t63 -26t26 -63zM1214 1049q-29 0 -50 21t-21 50 q0 30 21 51t50 21q30 0 51 -21t21 -51q0 -29 -21 -50t-51 -21zM1216 1408q132 0 226 -94t94 -227v-894q0 -133 -94 -227t-226 -94h-896q-132 0 -226 94t-94 227v894q0 133 94 227t226 94h896zM1321 596q35 14 57 45.5t22 70.5q0 51 -36 87.5t-87 36.5q-60 0 -98 -48 q-151 107 -375 115l83 265l206 -49q1 -50 36.5 -85t84.5 -35q50 0 86 35.5t36 85.5t-36 86t-86 36q-36 0 -66 -20.5t-45 -53.5l-227 54q-9 2 -17.5 -2.5t-11.5 -14.5l-95 -302q-224 -4 -381 -113q-36 43 -93 43q-51 0 -87 -36.5t-36 -87.5q0 -37 19.5 -67.5t52.5 -45.5 q-7 -25 -7 -54q0 -98 74 -181.5t201.5 -132t278.5 -48.5q150 0 277.5 48.5t201.5 132t74 181.5q0 27 -6 54zM971 702q37 0 63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64t26 63t63 26z" />
<glyph unicode="&#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" />
<glyph unicode="&#xf1a4;" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" />
<glyph unicode="&#xf1a5;" d="M1472 160v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480h704v-704h480q93 0 158.5 65.5t65.5 158.5zM1536 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.5 t84.5 -203.5z" />
@@ -459,7 +459,7 @@
<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" />
<glyph unicode="&#xf1d3;" horiz-adv-x="1792" d="M595 22q0 100 -165 100q-158 0 -158 -104q0 -101 172 -101q151 0 151 105zM536 777q0 61 -30 102t-89 41q-124 0 -124 -145q0 -135 124 -135q119 0 119 137zM805 1101v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58 q0 -31 22.5 -51.5t58 -32t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47 q98 0 218 47zM1123 220h-222q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134zM1724 442v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6v190h96v76q0 54 -6 89h227q-6 -41 -6 -165h171 v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33zM1148 1389q0 -58 -39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5z" />
<glyph unicode="&#xf1d4;" d="M825 547l343 588h-150q-21 -39 -63.5 -118.5t-68 -128.5t-59.5 -118.5t-60 -128.5h-3q-21 48 -44.5 97t-52 105.5t-46.5 92t-54 104.5t-49 95h-150l323 -589v-435h134v436zM1536 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="&#xf1d4;" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 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="&#xf1d5;" horiz-adv-x="1280" d="M842 964q0 -80 -57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5t29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5 t136.5 -56.5t56.5 -136.5zM1223 953q0 -158 -78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153t-153 102t-186 38t-186 -38t-153 -102t-102 -153 t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5z" />
<glyph unicode="&#xf1d6;" horiz-adv-x="1792" d="M270 730q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5t3.5 -5t2 -3.5 q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9t-98 20 t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20q-18 -41 -54.5 -74.5 t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100q0 275 252 466z" />
<glyph unicode="&#xf1d7;" horiz-adv-x="2048" d="M580 1075q0 41 -25 66t-66 25q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5zM1323 568q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51zM1087 1075q0 41 -24.5 66t-65.5 25 q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5zM1722 568q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51zM1456 965q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5 q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5zM2048 404q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109 q-150 -37 -218 -37q-169 0 -311 70.5t-223.5 191.5t-81.5 264t81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5z" />
@@ -483,13 +483,13 @@
<glyph unicode="&#xf1ea;" horiz-adv-x="2048" d="M1024 1024h-384v-384h384v384zM1152 384v-128h-640v128h640zM1152 1152v-640h-640v640h640zM1792 384v-128h-512v128h512zM1792 640v-128h-512v128h512zM1792 896v-128h-512v128h512zM1792 1152v-128h-512v128h512zM256 192v960h-128v-960q0 -26 19 -45t45 -19t45 19 t19 45zM1920 192v1088h-1536v-1088q0 -33 -11 -64h1483q26 0 45 19t19 45zM2048 1408v-1216q0 -80 -56 -136t-136 -56h-1664q-80 0 -136 56t-56 136v1088h256v128h1792z" />
<glyph unicode="&#xf1eb;" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" />
<glyph unicode="&#xf1ec;" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1ed;" horiz-adv-x="1792" d="M1112 1090q0 159 -237 159h-70q-32 0 -59.5 -21.5t-34.5 -52.5l-63 -276q-2 -5 -2 -16q0 -24 17 -39.5t41 -15.5h53q69 0 128.5 13t112.5 41t83.5 81.5t30.5 126.5zM1716 938q0 -265 -220 -428q-219 -161 -612 -161h-61q-32 0 -59 -21.5t-34 -52.5l-73 -316 q-8 -36 -40.5 -61.5t-69.5 -25.5h-213q-31 0 -53 20t-22 51q0 10 13 65h151q34 0 64 23.5t38 56.5l73 316q8 33 37.5 57t63.5 24h61q390 0 607 160t217 421q0 129 -51 207q183 -92 183 -335zM1533 1123q0 -264 -221 -428q-218 -161 -612 -161h-60q-32 0 -59.5 -22t-34.5 -53 l-73 -315q-8 -36 -40 -61.5t-69 -25.5h-214q-31 0 -52.5 19.5t-21.5 51.5q0 8 2 20l300 1301q8 36 40.5 61.5t69.5 25.5h444q68 0 125 -4t120.5 -15t113.5 -30t96.5 -50.5t77.5 -74t49.5 -103.5t18.5 -136z" />
<glyph unicode="&#xf1ed;" d="M1519 890q18 -84 -4 -204q-87 -444 -565 -444h-44q-25 0 -44 -16.5t-24 -42.5l-4 -19l-55 -346l-2 -15q-5 -26 -24.5 -42.5t-44.5 -16.5h-251q-21 0 -33 15t-9 36q9 56 26.5 168t26.5 168t27 167.5t27 167.5q5 37 43 37h131q133 -2 236 21q175 39 287 144q102 95 155 246 q24 70 35 133q1 6 2.5 7.5t3.5 1t6 -3.5q79 -59 98 -162zM1347 1172q0 -107 -46 -236q-80 -233 -302 -315q-113 -40 -252 -42q0 -1 -90 -1l-90 1q-100 0 -118 -96q-2 -8 -85 -530q-1 -10 -12 -10h-295q-22 0 -36.5 16.5t-11.5 38.5l232 1471q5 29 27.5 48t51.5 19h598 q34 0 97.5 -13t111.5 -32q107 -41 163.5 -123t56.5 -196z" />
<glyph unicode="&#xf1ee;" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" />
<glyph unicode="&#xf1f0;" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1f1;" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1f2;" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" />
<glyph unicode="&#xf1f3;" horiz-adv-x="2304" d="M119 854h89l-45 108zM740 328l74 79l-70 79h-163v-49h142v-55h-142v-54h159zM898 406l99 -110v217zM1186 453q0 33 -40 33h-84v-69h83q41 0 41 36zM1475 457q0 29 -42 29h-82v-61h81q43 0 43 32zM1197 923q0 29 -42 29h-82v-60h81q43 0 43 31zM1656 854h89l-44 108z M699 1009v-271h-66v212l-94 -212h-57l-94 212v-212h-132l-25 60h-135l-25 -60h-70l116 271h96l110 -257v257h106l85 -184l77 184h108zM1255 453q0 -20 -5.5 -35t-14 -25t-22.5 -16.5t-26 -10t-31.5 -4.5t-31.5 -1t-32.5 0.5t-29.5 0.5v-91h-126l-80 90l-83 -90h-256v271h260 l80 -89l82 89h207q109 0 109 -89zM964 794v-56h-217v271h217v-57h-152v-49h148v-55h-148v-54h152zM2304 235v-229q0 -55 -38.5 -94.5t-93.5 -39.5h-2040q-55 0 -93.5 39.5t-38.5 94.5v678h111l25 61h55l25 -61h218v46l19 -46h113l20 47v-47h541v99l10 1q10 0 10 -14v-86h279 v23q23 -12 55 -18t52.5 -6.5t63 0.5t51.5 1l25 61h56l25 -61h227v58l34 -58h182v378h-180v-44l-25 44h-185v-44l-23 44h-249q-69 0 -109 -22v22h-172v-22q-24 22 -73 22h-628l-43 -97l-43 97h-198v-44l-22 44h-169l-78 -179v391q0 55 38.5 94.5t93.5 39.5h2040 q55 0 93.5 -39.5t38.5 -94.5v-678h-120q-51 0 -81 -22v22h-177q-55 0 -78 -22v22h-316v-22q-31 22 -87 22h-209v-22q-23 22 -91 22h-234l-54 -58l-50 58h-349v-378h343l55 59l52 -59h211v89h21q59 0 90 13v-102h174v99h8q8 0 10 -2t2 -10v-87h529q57 0 88 24v-24h168 q60 0 95 17zM1546 469q0 -23 -12 -43t-34 -29q25 -9 34 -26t9 -46v-54h-65v45q0 33 -12 43.5t-46 10.5h-69v-99h-65v271h154q48 0 77 -15t29 -58zM1269 936q0 -24 -12.5 -44t-33.5 -29q26 -9 34.5 -25.5t8.5 -46.5v-53h-65q0 9 0.5 26.5t0 25t-3 18.5t-8.5 16t-17.5 8.5 t-29.5 3.5h-70v-98h-64v271l153 -1q49 0 78 -14.5t29 -57.5zM1798 327v-56h-216v271h216v-56h-151v-49h148v-55h-148v-54zM1372 1009v-271h-66v271h66zM2065 357q0 -86 -102 -86h-126v58h126q34 0 34 25q0 16 -17 21t-41.5 5t-49.5 3.5t-42 22.5t-17 55q0 39 26 60t66 21 h130v-57h-119q-36 0 -36 -25q0 -16 17.5 -20.5t42 -4t49 -2.5t42 -21.5t17.5 -54.5zM2304 407v-101q-24 -35 -88 -35h-125v58h125q33 0 33 25q0 13 -12.5 19t-31 5.5t-40 2t-40 8t-31 24t-12.5 48.5q0 39 26.5 60t66.5 21h129v-57h-118q-36 0 -36 -25q0 -20 29 -22t68.5 -5 t56.5 -26zM2139 1008v-270h-92l-122 203v-203h-132l-26 60h-134l-25 -60h-75q-129 0 -129 133q0 138 133 138h63v-59q-7 0 -28 1t-28.5 0.5t-23 -2t-21.5 -6.5t-14.5 -13.5t-11.5 -23t-3 -33.5q0 -38 13.5 -58t49.5 -20h29l92 213h97l109 -256v256h99l114 -188v188h66z" />
<glyph unicode="&#xf1f4;" horiz-adv-x="2304" d="M322 689h-15q-19 0 -19 18q0 28 19 85q5 15 15 19.5t28 4.5q77 0 77 -49q0 -41 -30.5 -59.5t-74.5 -18.5zM664 528q-47 0 -47 29q0 62 123 62l3 -3q-5 -88 -79 -88zM1438 687h-15q-19 0 -19 19q0 28 19 85q5 15 14.5 19t28.5 4q77 0 77 -49q0 -41 -30.5 -59.5 t-74.5 -18.5zM1780 527q-47 0 -47 30q0 62 123 62l3 -3q-5 -89 -79 -89zM373 894h-128q-8 0 -14.5 -4t-8.5 -7.5t-7 -12.5q-3 -7 -45 -190t-42 -192q0 -7 5.5 -12.5t13.5 -5.5h62q25 0 32.5 34.5l15 69t32.5 34.5q47 0 87.5 7.5t80.5 24.5t63.5 52.5t23.5 84.5 q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM719 798q-38 0 -74 -6q-2 0 -8.5 -1t-9 -1.5l-7.5 -1.5t-7.5 -2t-6.5 -3t-6.5 -4t-5 -5t-4.5 -7t-4 -9q-9 -29 -9 -39t9 -10q5 0 21.5 5t19.5 6q30 8 58 8q74 0 74 -36q0 -11 -10 -14q-8 -2 -18 -3t-21.5 -1.5t-17.5 -1.5 q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5q0 -38 26 -59.5t64 -21.5q24 0 45.5 6.5t33 13t38.5 23.5q-3 -7 -3 -15t5.5 -13.5t12.5 -5.5h56q1 1 7 3.5t7.5 3.5t5 3.5t5 5.5t2.5 8l45 194q4 13 4 30q0 81 -145 81zM1247 793h-74q-22 0 -39 -23q-5 -7 -29.5 -51 t-46.5 -81.5t-26 -38.5l-5 4q0 77 -27 166q-1 5 -3.5 8.5t-6 6.5t-6.5 5t-8.5 3t-8.5 1.5t-9.5 1t-9 0.5h-10h-8.5q-38 0 -38 -21l1 -5q5 -53 25 -151t25 -143q2 -16 2 -24q0 -19 -30.5 -61.5t-30.5 -58.5q0 -13 40 -13q61 0 76 25l245 415q10 20 10 26q0 9 -8 9zM1489 892 h-129q-18 0 -29 -23q-6 -13 -46.5 -191.5t-40.5 -190.5q0 -20 43 -20h7.5h9h9t9.5 1t8.5 2t8.5 3t6.5 4.5t5.5 6t3 8.5l21 91q2 10 10.5 17t19.5 7q47 0 87.5 7t80.5 24.5t63.5 52.5t23.5 84q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM1835 798q-26 0 -74 -6 q-38 -6 -48 -16q-7 -8 -11 -19q-8 -24 -8 -39q0 -10 8 -10q1 0 41 12q30 8 58 8q74 0 74 -36q0 -12 -10 -14q-4 -1 -57 -7q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5t26 -58.5t64 -21.5q24 0 45 6t34 13t38 24q-3 -15 -3 -16q0 -5 2 -8.5t6.5 -5.5t8 -3.5 t10.5 -2t9.5 -0.5h9.5h8q42 0 48 25l45 194q3 15 3 31q0 81 -145 81zM2157 889h-55q-25 0 -33 -40q-10 -44 -36.5 -167t-42.5 -190v-5q0 -16 16 -18h1h57q10 0 18.5 6.5t10.5 16.5l83 374h-1l1 5q0 7 -5.5 12.5t-13.5 5.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048 q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1f4;" horiz-adv-x="2304" d="M745 630q0 -37 -25.5 -61.5t-62.5 -24.5q-29 0 -46.5 16t-17.5 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM1530 779q0 -42 -22 -57t-66 -15l-32 -1l17 107q2 11 13 11h18q22 0 35 -2t25 -12.5t12 -30.5zM1881 630q0 -36 -25.5 -61t-61.5 -25q-29 0 -47 16 t-18 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM513 801q0 59 -38.5 85.5t-100.5 26.5h-160q-19 0 -21 -19l-65 -408q-1 -6 3 -11t10 -5h76q20 0 22 19l18 110q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM822 489l41 261q1 6 -3 11t-10 5h-76 q-14 0 -17 -33q-27 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q28 0 58 12t48 32q-4 -12 -4 -21q0 -16 13 -16h69q19 0 22 19zM1269 752q0 5 -4 9.5t-9 4.5h-77q-11 0 -18 -10l-106 -156l-44 150q-5 16 -22 16h-75q-5 0 -9 -4.5t-4 -9.5q0 -2 19.5 -59 t42 -123t23.5 -70q-82 -112 -82 -120q0 -13 13 -13h77q11 0 18 10l255 368q2 2 2 7zM1649 801q0 59 -38.5 85.5t-100.5 26.5h-159q-20 0 -22 -19l-65 -408q-1 -6 3 -11t10 -5h82q12 0 16 13l18 116q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM1958 489 l41 261q1 6 -3 11t-10 5h-76q-14 0 -17 -33q-26 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q29 0 59 12t47 32q0 -1 -2 -9t-2 -12q0 -16 13 -16h69q19 0 22 19zM2176 898v1q0 14 -13 14h-74q-11 0 -13 -11l-65 -416l-1 -2q0 -5 4 -9.5t10 -4.5h66 q19 0 21 19zM392 764q-5 -35 -26 -46t-60 -11l-33 -1l17 107q2 11 13 11h19q40 0 58 -11.5t12 -48.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1f5;" horiz-adv-x="2304" d="M1597 633q0 -69 -21 -106q-19 -35 -52 -35q-23 0 -41 9v224q29 30 57 30q57 0 57 -122zM2035 669h-110q6 98 56 98q51 0 54 -98zM476 534q0 59 -33 91.5t-101 57.5q-36 13 -52 24t-16 25q0 26 38 26q58 0 124 -33l18 112q-67 32 -149 32q-77 0 -123 -38q-48 -39 -48 -109 q0 -58 32.5 -90.5t99.5 -56.5q39 -14 54.5 -25.5t15.5 -27.5q0 -31 -48 -31q-29 0 -70 12.5t-72 30.5l-18 -113q72 -41 168 -41q81 0 129 37q51 41 51 117zM771 749l19 111h-96v135l-129 -21l-18 -114l-46 -8l-17 -103h62v-219q0 -84 44 -120q38 -30 111 -30q32 0 79 11v118 q-32 -7 -44 -7q-42 0 -42 50v197h77zM1087 724v139q-15 3 -28 3q-32 0 -55.5 -16t-33.5 -46l-10 56h-131v-471h150v306q26 31 82 31q16 0 26 -2zM1124 389h150v471h-150v-471zM1746 638q0 122 -45 179q-40 52 -111 52q-64 0 -117 -56l-8 47h-132v-645l150 25v151 q36 -11 68 -11q83 0 134 56q61 65 61 202zM1278 986q0 33 -23 56t-56 23t-56 -23t-23 -56t23 -56.5t56 -23.5t56 23.5t23 56.5zM2176 629q0 113 -48 176q-50 64 -144 64q-96 0 -151.5 -66t-55.5 -180q0 -128 63 -188q55 -55 161 -55q101 0 160 40l-16 103q-57 -31 -128 -31 q-43 0 -63 19q-23 19 -28 66h248q2 14 2 52zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf1f6;" horiz-adv-x="2048" d="M1558 684q61 -356 298 -556q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5zM1024 -176q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5zM2026 1424q8 -10 7.5 -23.5t-10.5 -22.5 l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5 l418 363q10 8 23.5 7t21.5 -11z" />
<glyph unicode="&#xf1f7;" horiz-adv-x="2048" d="M1040 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM503 315l877 760q-42 88 -132.5 146.5t-223.5 58.5q-93 0 -169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -384 -137 -645zM1856 128 q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5l149 129h757q-166 187 -227 459l111 97q61 -356 298 -556zM1942 1520l84 -96q8 -10 7.5 -23.5t-10.5 -22.5l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161 q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5l418 363q10 8 23.5 7t21.5 -11z" />
@@ -523,7 +523,7 @@
<glyph unicode="&#xf215;" horiz-adv-x="2048" d="M863 504q0 112 -79.5 191.5t-191.5 79.5t-191 -79.5t-79 -191.5t79 -191t191 -79t191.5 79t79.5 191zM1726 505q0 112 -79 191t-191 79t-191.5 -79t-79.5 -191q0 -113 79.5 -192t191.5 -79t191 79.5t79 191.5zM2048 1314v-1348q0 -44 -31.5 -75.5t-76.5 -31.5h-1832 q-45 0 -76.5 31.5t-31.5 75.5v1348q0 44 31.5 75.5t76.5 31.5h431q44 0 76 -31.5t32 -75.5v-161h754v161q0 44 32 75.5t76 31.5h431q45 0 76.5 -31.5t31.5 -75.5z" />
<glyph unicode="&#xf216;" horiz-adv-x="2048" d="M1430 953zM1690 749q148 0 253 -98.5t105 -244.5q0 -157 -109 -261.5t-267 -104.5q-85 0 -162 27.5t-138 73.5t-118 106t-109 126.5t-103.5 132.5t-108.5 126t-117 106t-136 73.5t-159 27.5q-154 0 -251.5 -91.5t-97.5 -244.5q0 -157 104 -250t263 -93q100 0 208 37.5 t193 98.5q5 4 21 18.5t30 24t22 9.5q14 0 24.5 -10.5t10.5 -24.5q0 -24 -60 -77q-101 -88 -234.5 -142t-260.5 -54q-133 0 -245.5 58t-180 165t-67.5 241q0 205 141.5 341t347.5 136q120 0 226.5 -43.5t185.5 -113t151.5 -153t139 -167.5t133.5 -153.5t149.5 -113 t172.5 -43.5q102 0 168.5 61.5t66.5 162.5q0 95 -64.5 159t-159.5 64q-30 0 -81.5 -18.5t-68.5 -18.5q-20 0 -35.5 15t-15.5 35q0 18 8.5 57t8.5 59q0 159 -107.5 263t-266.5 104q-58 0 -111.5 -18.5t-84 -40.5t-55.5 -40.5t-33 -18.5q-15 0 -25.5 10.5t-10.5 25.5 q0 19 25 46q59 67 147 103.5t182 36.5q191 0 318 -125.5t127 -315.5q0 -37 -4 -66q57 15 115 15z" />
<glyph unicode="&#xf217;" horiz-adv-x="1664" d="M1216 832q0 26 -19 45t-45 19h-128v128q0 26 -19 45t-45 19t-45 -19t-19 -45v-128h-128q-26 0 -45 -19t-19 -45t19 -45t45 -19h128v-128q0 -26 19 -45t45 -19t45 19t19 45v128h128q26 0 45 19t19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf218;" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19t-45 -19l-147 -146v293q0 26 -19 45t-45 19t-45 -19t-19 -45v-293l-147 146q-19 19 -45 19t-45 -19t-19 -45t19 -45l256 -256q19 -19 45 -19t45 19l256 256q19 19 19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf218;" horiz-adv-x="1664" d="M1280 832q0 26 -19 45t-45 19t-45 -19l-147 -146v293q0 26 -19 45t-45 19t-45 -19t-19 -45v-293l-147 146q-19 19 -45 19t-45 -19t-19 -45t19 -45l256 -256q19 -19 45 -19t45 19l256 256q19 19 19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf219;" horiz-adv-x="2048" d="M212 768l623 -665l-300 665h-323zM1024 -4l349 772h-698zM538 896l204 384h-262l-288 -384h346zM1213 103l623 665h-323zM683 896h682l-204 384h-274zM1510 896h346l-288 384h-262zM1651 1382l384 -512q14 -18 13 -41.5t-17 -40.5l-960 -1024q-18 -20 -47 -20t-47 20 l-960 1024q-16 17 -17 40.5t13 41.5l384 512q18 26 51 26h1152q33 0 51 -26z" />
<glyph unicode="&#xf21a;" horiz-adv-x="2048" d="M1811 -19q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83 q19 19 45 19t45 -19l83 -83zM237 19q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -82l83 82q19 19 45 19t45 -19l83 -82l64 64v293l-210 314q-17 26 -7 56.5t40 40.5l177 58v299h128v128h256v128h256v-128h256v-128h128v-299l177 -58q30 -10 40 -40.5t-7 -56.5l-210 -314 v-293l19 18q19 19 45 19t45 -19l83 -82l83 82q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83zM640 1152v-128l384 128l384 -128v128h-128v128h-512v-128h-128z" />
<glyph unicode="&#xf21b;" d="M576 0l96 448l-96 128l-128 64zM832 0l128 640l-128 -64l-96 -128zM992 1010q-2 4 -4 6q-10 8 -96 8q-70 0 -167 -19q-7 -2 -21 -2t-21 2q-97 19 -167 19q-86 0 -96 -8q-2 -2 -4 -6q2 -18 4 -27q2 -3 7.5 -6.5t7.5 -10.5q2 -4 7.5 -20.5t7 -20.5t7.5 -17t8.5 -17t9 -14 t12 -13.5t14 -9.5t17.5 -8t20.5 -4t24.5 -2q36 0 59 12.5t32.5 30t14.5 34.5t11.5 29.5t17.5 12.5h12q11 0 17.5 -12.5t11.5 -29.5t14.5 -34.5t32.5 -30t59 -12.5q13 0 24.5 2t20.5 4t17.5 8t14 9.5t12 13.5t9 14t8.5 17t7.5 17t7 20.5t7.5 20.5q2 7 7.5 10.5t7.5 6.5 q2 9 4 27zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 61 4.5 118t19 125.5t37.5 123.5t63.5 103.5t93.5 74.5l-90 220h214q-22 64 -22 128q0 12 2 32q-194 40 -194 96q0 57 210 99q17 62 51.5 134t70.5 114q32 37 76 37q30 0 84 -31t84 -31t84 31 t84 31q44 0 76 -37q36 -42 70.5 -114t51.5 -134q210 -42 210 -99q0 -56 -194 -96q7 -81 -20 -160h214l-82 -225q63 -33 107.5 -96.5t65.5 -143.5t29 -151.5t8 -148.5z" />
@@ -531,18 +531,18 @@
<glyph unicode="&#xf21d;" d="M1408 0q0 -63 -61.5 -113.5t-164 -81t-225 -46t-253.5 -15.5t-253.5 15.5t-225 46t-164 81t-61.5 113.5q0 49 33 88.5t91 66.5t118 44.5t131 29.5q26 5 48 -10.5t26 -41.5q5 -26 -10.5 -48t-41.5 -26q-58 -10 -106 -23.5t-76.5 -25.5t-48.5 -23.5t-27.5 -19.5t-8.5 -12 q3 -11 27 -26.5t73 -33t114 -32.5t160.5 -25t201.5 -10t201.5 10t160.5 25t114 33t73 33.5t27 27.5q-1 4 -8.5 11t-27.5 19t-48.5 23.5t-76.5 25t-106 23.5q-26 4 -41.5 26t-10.5 48q4 26 26 41.5t48 10.5q71 -12 131 -29.5t118 -44.5t91 -66.5t33 -88.5zM1024 896v-384 q0 -26 -19 -45t-45 -19h-64v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384h-64q-26 0 -45 19t-19 45v384q0 53 37.5 90.5t90.5 37.5h384q53 0 90.5 -37.5t37.5 -90.5zM928 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5 t158.5 -65.5t65.5 -158.5z" />
<glyph unicode="&#xf21e;" horiz-adv-x="1792" d="M1280 512h305q-5 -6 -10 -10.5t-9 -7.5l-3 -4l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-5 2 -21 20h369q22 0 39.5 13.5t22.5 34.5l70 281l190 -667q6 -20 23 -33t39 -13q21 0 38 13t23 33l146 485l56 -112q18 -35 57 -35zM1792 940q0 -145 -103 -300h-369l-111 221 q-8 17 -25.5 27t-36.5 8q-45 -5 -56 -46l-129 -430l-196 686q-6 20 -23.5 33t-39.5 13t-39 -13.5t-22 -34.5l-116 -464h-423q-103 155 -103 300q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124 t127 -344z" />
<glyph unicode="&#xf221;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292 q11 134 80.5 249t182 188t245.5 88q170 19 319 -54t236 -212t87 -306zM128 960q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5z" />
<glyph unicode="&#xf222;" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h416q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-419 -420q87 -104 129.5 -236.5t30.5 -276.5q-22 -250 -200.5 -431t-428.5 -206q-163 -17 -314 39.5t-256.5 162t-162 256.5t-39.5 314q25 250 206 428.5 t431 200.5q144 12 276.5 -30.5t236.5 -129.5l419 419h-261q-14 0 -23 9t-9 23v64zM704 -128q117 0 223.5 45.5t184 123t123 184t45.5 223.5t-45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123 t223.5 -45.5z" />
<glyph unicode="&#xf222;" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-382 -383q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5 q203 0 359 -126l382 382h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf223;" horiz-adv-x="1280" d="M830 1220q145 -72 233.5 -210.5t88.5 -305.5q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5 t-147.5 384.5q0 167 88.5 305.5t233.5 210.5q-165 96 -228 273q-6 16 3.5 29.5t26.5 13.5h69q21 0 29 -20q44 -106 140 -171t214 -65t214 65t140 171q8 20 37 20h61q17 0 26.5 -13.5t3.5 -29.5q-63 -177 -228 -273zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf224;" d="M1024 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-149 16 -270.5 103t-186.5 223.5t-53 291.5q16 204 160 353.5t347 172.5q118 14 228 -19t198 -103l255 254h-134q-14 0 -23 9t-9 23v64zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf225;" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5t-147.5 384.5q0 201 126 359l-52 53l-101 -111q-9 -10 -22 -10.5t-23 7.5l-48 44q-10 8 -10.5 21.5t8.5 23.5l105 115l-111 112v-134q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9 t-9 23v288q0 26 19 45t45 19h288q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-133l106 -107l86 94q9 10 22 10.5t23 -7.5l48 -44q10 -8 10.5 -21.5t-8.5 -23.5l-90 -99l57 -56q158 126 359 126t359 -126l255 254h-134q-14 0 -23 9t-9 23v64zM832 256q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf226;" horiz-adv-x="1792" d="M1790 1007q12 -155 -52.5 -292t-186 -224t-271.5 -103v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-512v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23 t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292q17 206 164.5 356.5t352.5 169.5q206 21 377 -94q171 115 377 94q205 -19 352.5 -169.5t164.5 -356.5zM896 647q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM576 512q115 0 218 57q-154 165 -154 391 q0 224 154 391q-103 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5zM1152 128v260q-137 15 -256 94q-119 -79 -256 -94v-260h512zM1216 512q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5q-115 0 -218 -57q154 -167 154 -391 q0 -226 -154 -391q103 -57 218 -57z" />
<glyph unicode="&#xf227;" horiz-adv-x="1920" d="M1536 1120q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-31 -182 -166 -312t-318 -156q-210 -29 -384.5 80t-241.5 300q-117 6 -221 57.5t-177.5 133t-113.5 192.5t-32 230 q9 135 78 252t182 191.5t248 89.5q118 14 227.5 -19t198.5 -103l255 254h-134q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q59 -74 93 -169q182 -9 328 -124l255 254h-134q-14 0 -23 9 t-9 23v64zM1024 704q0 20 -4 58q-162 -25 -271 -150t-109 -292q0 -20 4 -58q162 25 271 150t109 292zM128 704q0 -168 111 -294t276 -149q-3 29 -3 59q0 210 135 369.5t338 196.5q-53 120 -163.5 193t-245.5 73q-185 0 -316.5 -131.5t-131.5 -316.5zM1088 -128 q185 0 316.5 131.5t131.5 316.5q0 168 -111 294t-276 149q3 -29 3 -59q0 -210 -135 -369.5t-338 -196.5q53 -120 163.5 -193t245.5 -73z" />
<glyph unicode="&#xf228;" horiz-adv-x="2048" d="M1664 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-32 -180 -164.5 -310t-313.5 -157q-223 -34 -409 90q-117 -78 -256 -93v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23 t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-155 17 -279.5 109.5t-187 237.5t-39.5 307q25 187 159.5 322.5t320.5 164.5q224 34 410 -90q146 97 320 97q201 0 359 -126l255 254h-134q-14 0 -23 9 t-9 23v64zM896 391q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM128 704q0 -185 131.5 -316.5t316.5 -131.5q117 0 218 57q-154 167 -154 391t154 391q-101 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5zM1216 256q185 0 316.5 131.5t131.5 316.5 t-131.5 316.5t-316.5 131.5q-117 0 -218 -57q154 -167 154 -391t-154 -391q101 -57 218 -57z" />
<glyph unicode="&#xf229;" horiz-adv-x="1792" d="M1728 1536q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-229 -230l156 -156q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-156 157l-99 -100q87 -104 129.5 -236.5t30.5 -276.5q-22 -250 -200.5 -431t-428.5 -206q-163 -17 -314 39.5 t-256.5 162t-162 256.5t-39.5 314q25 250 206 428.5t431 200.5q144 12 276.5 -30.5t236.5 -129.5l99 99l-156 156q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l156 -156l229 229h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM1280 448q0 117 -45.5 223.5t-123 184t-184 123 t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5z" />
<glyph unicode="&#xf229;" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-213 -214l140 -140q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-140 141l-78 -79q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5 t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5q203 0 359 -126l78 78l-172 172q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l172 -172l213 213h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf22a;" horiz-adv-x="1280" d="M640 892q217 -24 364.5 -187.5t147.5 -384.5q0 -167 -87 -306t-236 -212t-319 -54q-133 15 -245.5 88t-182 188t-80.5 249q-12 155 52.5 292t186 224t271.5 103v132h-160q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h160v165l-92 -92q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22 t9 23l202 201q19 19 45 19t45 -19l202 -201q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-92 92v-165h160q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-160v-132zM576 -128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5 t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf22b;" horiz-adv-x="2048" d="M2029 685q19 -19 19 -45t-19 -45l-294 -294q-9 -10 -22.5 -10t-22.5 10l-45 45q-10 9 -10 22.5t10 22.5l185 185h-294v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-131q-12 -119 -67 -226t-139 -183.5t-196.5 -121.5t-234.5 -45q-180 0 -330.5 91t-234.5 247 t-74 337q8 162 94 300t226.5 219.5t302.5 85.5q166 4 310.5 -71.5t235.5 -208.5t107 -296h131v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h294l-185 185q-10 9 -10 22.5t10 22.5l45 45q9 10 22.5 10t22.5 -10zM640 128q104 0 198.5 40.5t163.5 109.5t109.5 163.5 t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" />
<glyph unicode="&#xf22b;" horiz-adv-x="2048" d="M1901 621q19 -19 19 -45t-19 -45l-294 -294q-9 -10 -22.5 -10t-22.5 10l-45 45q-10 9 -10 22.5t10 22.5l185 185h-294v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-132q-24 -217 -187.5 -364.5t-384.5 -147.5q-167 0 -306 87t-212 236t-54 319q15 133 88 245.5 t188 182t249 80.5q155 12 292 -52.5t224 -186t103 -271.5h132v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h294l-185 185q-10 9 -10 22.5t10 22.5l45 45q9 10 22.5 10t22.5 -10zM576 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5 t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf22c;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-612q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v612q-217 24 -364.5 187.5t-147.5 384.5q0 117 45.5 223.5t123 184t184 123t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5zM576 512q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
<glyph unicode="&#xf22d;" horiz-adv-x="1792" />
<glyph unicode="&#xf22d;" horiz-adv-x="1280" d="M1024 576q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1152 576q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123 t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5z" />
<glyph unicode="&#xf22e;" horiz-adv-x="1792" />
<glyph unicode="&#xf22f;" horiz-adv-x="1792" />
<glyph unicode="&#xf230;" d="M1451 1408q35 0 60 -25t25 -60v-1366q0 -35 -25 -60t-60 -25h-391v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-735q-35 0 -60 25t-25 60v1366q0 35 25 60t60 25h1366z" />
@@ -556,10 +556,85 @@
<glyph unicode="&#xf238;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM768 192q80 0 136 56t56 136t-56 136t-136 56 t-136 -56t-56 -136t56 -136t136 -56zM1344 768v512h-1152v-512h1152z" />
<glyph unicode="&#xf239;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM288 224q66 0 113 47t47 113t-47 113t-113 47 t-113 -47t-47 -113t47 -113t113 -47zM704 768v512h-544v-512h544zM1248 224q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM1408 768v512h-576v-512h576z" />
<glyph unicode="&#xf23a;" horiz-adv-x="1792" d="M1792 204v-209h-642v209h134v926h-6l-314 -1135h-243l-310 1135h-8v-926h135v-209h-538v209h69q21 0 43 19.5t22 37.5v881q0 18 -22 40t-43 22h-69v209h672l221 -821h6l223 821h670v-209h-71q-19 0 -41 -22t-22 -40v-881q0 -18 21.5 -37.5t41.5 -19.5h71z" />
<glyph unicode="&#xf23b;" horiz-adv-x="1792" />
<glyph unicode="&#xf23c;" horiz-adv-x="1792" />
<glyph unicode="&#xf23d;" horiz-adv-x="1792" />
<glyph unicode="&#xf23e;" horiz-adv-x="1792" />
<glyph unicode="&#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" />
<glyph unicode="&#xf23e;" horiz-adv-x="1792" d="M896 1472q-169 0 -323 -66t-265.5 -177.5t-177.5 -265.5t-66 -323t66 -323t177.5 -265.5t265.5 -177.5t323 -66t323 66t265.5 177.5t177.5 265.5t66 323t-66 323t-177.5 265.5t-265.5 177.5t-323 66zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348 t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM496 704q16 0 16 -16v-480q0 -16 -16 -16h-32q-16 0 -16 16v480q0 16 16 16h32zM896 640q53 0 90.5 -37.5t37.5 -90.5q0 -35 -17.5 -64t-46.5 -46v-114q0 -14 -9 -23 t-23 -9h-64q-14 0 -23 9t-9 23v114q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5zM896 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 103zM544 928v-96 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 93 65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5v-96q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 146 -103 249t-249 103t-249 -103t-103 -249zM1408 192v512q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-512 q0 -26 19 -45t45 -19h896q26 0 45 19t19 45z" />
<glyph unicode="&#xf240;" horiz-adv-x="2304" d="M1920 1024v-768h-1664v768h1664zM2048 448h128v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288zM2304 832v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113 v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160q53 0 90.5 -37.5t37.5 -90.5z" />
<glyph unicode="&#xf241;" horiz-adv-x="2304" d="M256 256v768h1280v-768h-1280zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
<glyph unicode="&#xf242;" horiz-adv-x="2304" d="M256 256v768h896v-768h-896zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
<glyph unicode="&#xf243;" horiz-adv-x="2304" d="M256 256v768h512v-768h-512zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
<glyph unicode="&#xf244;" horiz-adv-x="2304" d="M2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23 v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
<glyph unicode="&#xf245;" horiz-adv-x="1280" d="M1133 493q31 -30 14 -69q-17 -40 -59 -40h-382l201 -476q10 -25 0 -49t-34 -35l-177 -75q-25 -10 -49 0t-35 34l-191 452l-312 -312q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v1504q0 42 40 59q12 5 24 5q27 0 45 -19z" />
<glyph unicode="&#xf246;" horiz-adv-x="1024" d="M832 1408q-320 0 -320 -224v-416h128v-128h-128v-544q0 -224 320 -224h64v-128h-64q-272 0 -384 146q-112 -146 -384 -146h-64v128h64q320 0 320 224v544h-128v128h128v416q0 224 -320 224h-64v128h64q272 0 384 -146q112 146 384 146h64v-128h-64z" />
<glyph unicode="&#xf247;" horiz-adv-x="2048" d="M2048 1152h-128v-1024h128v-384h-384v128h-1280v-128h-384v384h128v1024h-128v384h384v-128h1280v128h384v-384zM1792 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 -128v128h-128v-128h128zM1664 0v128h128v1024h-128v128h-1280v-128h-128v-1024h128v-128 h1280zM1920 -128v128h-128v-128h128zM1280 896h384v-768h-896v256h-384v768h896v-256zM512 512h640v512h-640v-512zM1536 256v512h-256v-384h-384v-128h640z" />
<glyph unicode="&#xf248;" horiz-adv-x="2304" d="M2304 768h-128v-640h128v-384h-384v128h-896v-128h-384v384h128v128h-384v-128h-384v384h128v640h-128v384h384v-128h896v128h384v-384h-128v-128h384v128h384v-384zM2048 1024v-128h128v128h-128zM1408 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 256 v128h-128v-128h128zM1536 384h-128v-128h128v128zM384 384h896v128h128v640h-128v128h-896v-128h-128v-640h128v-128zM896 -128v128h-128v-128h128zM2176 -128v128h-128v-128h128zM2048 128v640h-128v128h-384v-384h128v-384h-384v128h-384v-128h128v-128h896v128h128z" />
<glyph unicode="&#xf249;" d="M1024 288v-416h-928q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68v-928h-416q-40 0 -68 -28t-28 -68zM1152 256h381q-15 -82 -65 -132l-184 -184q-50 -50 -132 -65v381z" />
<glyph unicode="&#xf24a;" d="M1400 256h-248v-248q29 10 41 22l185 185q12 12 22 41zM1120 384h288v896h-1280v-1280h896v288q0 40 28 68t68 28zM1536 1312v-1024q0 -40 -20 -88t-48 -76l-184 -184q-28 -28 -76 -48t-88 -20h-1024q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68 z" />
<glyph unicode="&#xf24b;" horiz-adv-x="2304" d="M1951 538q0 -26 -15.5 -44.5t-38.5 -23.5q-8 -2 -18 -2h-153v140h153q10 0 18 -2q23 -5 38.5 -23.5t15.5 -44.5zM1933 751q0 -25 -15 -42t-38 -21q-3 -1 -15 -1h-139v129h139q3 0 8.5 -0.5t6.5 -0.5q23 -4 38 -21.5t15 -42.5zM728 587v308h-228v-308q0 -58 -38 -94.5 t-105 -36.5q-108 0 -229 59v-112q53 -15 121 -23t109 -9l42 -1q328 0 328 217zM1442 403v113q-99 -52 -200 -59q-108 -8 -169 41t-61 142t61 142t169 41q101 -7 200 -58v112q-48 12 -100 19.5t-80 9.5l-28 2q-127 6 -218.5 -14t-140.5 -60t-71 -88t-22 -106t22 -106t71 -88 t140.5 -60t218.5 -14q101 4 208 31zM2176 518q0 54 -43 88.5t-109 39.5v3q57 8 89 41.5t32 79.5q0 55 -41 88t-107 36q-3 0 -12 0.5t-14 0.5h-455v-510h491q74 0 121.5 36.5t47.5 96.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90 t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf24c;" horiz-adv-x="2304" d="M858 295v693q-106 -41 -172 -135.5t-66 -211.5t66 -211.5t172 -134.5zM1362 641q0 117 -66 211.5t-172 135.5v-694q106 41 172 135.5t66 211.5zM1577 641q0 -159 -78.5 -294t-213.5 -213.5t-294 -78.5q-119 0 -227.5 46.5t-187 125t-125 187t-46.5 227.5q0 159 78.5 294 t213.5 213.5t294 78.5t294 -78.5t213.5 -213.5t78.5 -294zM1960 634q0 139 -55.5 261.5t-147.5 205.5t-213.5 131t-252.5 48h-301q-176 0 -323.5 -81t-235 -230t-87.5 -335q0 -171 87 -317.5t236 -231.5t323 -85h301q129 0 251.5 50.5t214.5 135t147.5 202.5t55.5 246z M2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf24d;" horiz-adv-x="1792" d="M1664 -96v1088q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5zM1792 992v-1088q0 -66 -47 -113t-113 -47h-1088q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113 zM1408 1376v-160h-128v160q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h160v-128h-160q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113z" />
<glyph unicode="&#xf24e;" horiz-adv-x="2304" d="M1728 1088l-384 -704h768zM448 1088l-384 -704h768zM1269 1280q-14 -40 -45.5 -71.5t-71.5 -45.5v-1291h608q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1344q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h608v1291q-40 14 -71.5 45.5t-45.5 71.5h-491q-14 0 -23 9t-9 23v64 q0 14 9 23t23 9h491q21 57 70 92.5t111 35.5t111 -35.5t70 -92.5h491q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-491zM1088 1264q33 0 56.5 23.5t23.5 56.5t-23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5zM2176 384q0 -73 -46.5 -131t-117.5 -91 t-144.5 -49.5t-139.5 -16.5t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81zM896 384q0 -73 -46.5 -131t-117.5 -91t-144.5 -49.5t-139.5 -16.5 t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81z" />
<glyph unicode="&#xf250;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-77 -29 -149 -92.5 t-129.5 -152.5t-92.5 -210t-35 -253h1024q0 132 -35 253t-92.5 210t-129.5 152.5t-149 92.5q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" />
<glyph unicode="&#xf251;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -66 9 -128h1006q9 61 9 128zM1280 -128q0 130 -34 249.5t-90.5 208t-126.5 152t-146 94.5h-230q-76 -31 -146 -94.5t-126.5 -152t-90.5 -208t-34 -249.5h1024z" />
<glyph unicode="&#xf252;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -206 85 -384h854q85 178 85 384zM1223 192q-54 141 -145.5 241.5t-194.5 142.5h-230q-103 -42 -194.5 -142.5t-145.5 -241.5h910z" />
<glyph unicode="&#xf253;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-137 -51 -244 -196 h700q-107 145 -244 196q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" />
<glyph unicode="&#xf254;" d="M1504 -64q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472zM130 0q3 55 16 107t30 95t46 87t53.5 76t64.5 69.5t66 60t70.5 55t66.5 47.5t65 43q-43 28 -65 43t-66.5 47.5t-70.5 55t-66 60t-64.5 69.5t-53.5 76t-46 87 t-30 95t-16 107h1276q-3 -55 -16 -107t-30 -95t-46 -87t-53.5 -76t-64.5 -69.5t-66 -60t-70.5 -55t-66.5 -47.5t-65 -43q43 -28 65 -43t66.5 -47.5t70.5 -55t66 -60t64.5 -69.5t53.5 -76t46 -87t30 -95t16 -107h-1276zM1504 1536q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9 h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472z" />
<glyph unicode="&#xf255;" d="M768 1152q-53 0 -90.5 -37.5t-37.5 -90.5v-128h-32v93q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-429l-32 30v172q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-224q0 -47 35 -82l310 -296q39 -39 39 -102q0 -26 19 -45t45 -19h640q26 0 45 19t19 45v25 q0 41 10 77l108 436q10 36 10 77v246q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-32h-32v125q0 40 -25 72.5t-64 40.5q-14 2 -23 2q-46 0 -79 -33t-33 -79v-128h-32v122q0 51 -32.5 89.5t-82.5 43.5q-5 1 -13 1zM768 1280q84 0 149 -50q57 34 123 34q59 0 111 -27 t86 -76q27 7 59 7q100 0 170 -71.5t70 -171.5v-246q0 -51 -13 -108l-109 -436q-6 -24 -6 -71q0 -80 -56 -136t-136 -56h-640q-84 0 -138 58.5t-54 142.5l-308 296q-76 73 -76 175v224q0 99 70.5 169.5t169.5 70.5q11 0 16 -1q6 95 75.5 160t164.5 65q52 0 98 -21 q72 69 174 69z" />
<glyph unicode="&#xf256;" horiz-adv-x="1792" d="M880 1408q-46 0 -79 -33t-33 -79v-656h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528v-256l-154 205q-38 51 -102 51q-53 0 -90.5 -37.5t-37.5 -90.5q0 -43 26 -77l384 -512q38 -51 102 -51h688q34 0 61 22t34 56l76 405q5 32 5 59v498q0 46 -33 79t-79 33t-79 -33 t-33 -79v-272h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528h-32v656q0 46 -33 79t-79 33zM880 1536q68 0 125.5 -35.5t88.5 -96.5q19 4 42 4q99 0 169.5 -70.5t70.5 -169.5v-17q105 6 180.5 -64t75.5 -175v-498q0 -40 -8 -83l-76 -404q-14 -79 -76.5 -131t-143.5 -52 h-688q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 106 75 181t181 75q78 0 128 -34v434q0 99 70.5 169.5t169.5 70.5q23 0 42 -4q31 61 88.5 96.5t125.5 35.5z" />
<glyph unicode="&#xf257;" horiz-adv-x="1792" d="M1073 -128h-177q-163 0 -226 141q-23 49 -23 102v5q-62 30 -98.5 88.5t-36.5 127.5q0 38 5 48h-261q-106 0 -181 75t-75 181t75 181t181 75h113l-44 17q-74 28 -119.5 93.5t-45.5 145.5q0 106 75 181t181 75q46 0 91 -17l628 -239h401q106 0 181 -75t75 -181v-668 q0 -88 -54 -157.5t-140 -90.5l-339 -85q-92 -23 -186 -23zM1024 583l-155 -71l-163 -74q-30 -14 -48 -41.5t-18 -60.5q0 -46 33 -79t79 -33q26 0 46 10l338 154q-49 10 -80.5 50t-31.5 90v55zM1344 272q0 46 -33 79t-79 33q-26 0 -46 -10l-290 -132q-28 -13 -37 -17 t-30.5 -17t-29.5 -23.5t-16 -29t-8 -40.5q0 -50 31.5 -82t81.5 -32q20 0 38 9l352 160q30 14 48 41.5t18 60.5zM1112 1024l-650 248q-24 8 -46 8q-53 0 -90.5 -37.5t-37.5 -90.5q0 -40 22.5 -73t59.5 -47l526 -200v-64h-640q-53 0 -90.5 -37.5t-37.5 -90.5t37.5 -90.5 t90.5 -37.5h535l233 106v198q0 63 46 106l111 102h-69zM1073 0q82 0 155 19l339 85q43 11 70 45.5t27 78.5v668q0 53 -37.5 90.5t-90.5 37.5h-308l-136 -126q-36 -33 -36 -82v-296q0 -46 33 -77t79 -31t79 35t33 81v208h32v-208q0 -70 -57 -114q52 -8 86.5 -48.5t34.5 -93.5 q0 -42 -23 -78t-61 -53l-310 -141h91z" />
<glyph unicode="&#xf258;" horiz-adv-x="2048" d="M1151 1536q61 0 116 -28t91 -77l572 -781q118 -159 118 -359v-355q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v177l-286 143h-546q-80 0 -136 56t-56 136v32q0 119 84.5 203.5t203.5 84.5h420l42 128h-686q-100 0 -173.5 67.5t-81.5 166.5q-65 79 -65 182v32 q0 80 56 136t136 56h959zM1920 -64v355q0 157 -93 284l-573 781q-39 52 -103 52h-959q-26 0 -45 -19t-19 -45q0 -32 1.5 -49.5t9.5 -40.5t25 -43q10 31 35.5 50t56.5 19h832v-32h-832q-26 0 -45 -19t-19 -45q0 -44 3 -58q8 -44 44 -73t81 -29h640h91q40 0 68 -28t28 -68 q0 -15 -5 -30l-64 -192q-10 -29 -35 -47.5t-56 -18.5h-443q-66 0 -113 -47t-47 -113v-32q0 -26 19 -45t45 -19h561q16 0 29 -7l317 -158q24 -13 38.5 -36t14.5 -50v-197q0 -26 19 -45t45 -19h384q26 0 45 19t19 45z" />
<glyph unicode="&#xf259;" horiz-adv-x="2048" d="M816 1408q-48 0 -79.5 -34t-31.5 -82q0 -14 3 -28l150 -624h-26l-116 482q-9 38 -39.5 62t-69.5 24q-47 0 -79 -34t-32 -81q0 -11 4 -29q3 -13 39 -161t68 -282t32 -138v-227l-307 230q-34 26 -77 26q-52 0 -89.5 -36.5t-37.5 -88.5q0 -67 56 -110l507 -379 q34 -26 76 -26h694q33 0 59 20.5t34 52.5l100 401q8 30 10 88t9 86l116 478q3 12 3 26q0 46 -33 79t-80 33q-38 0 -69 -25.5t-40 -62.5l-99 -408h-26l132 547q3 14 3 28q0 47 -32 80t-80 33q-38 0 -68.5 -24t-39.5 -62l-145 -602h-127l-164 682q-9 38 -39.5 62t-68.5 24z M1461 -256h-694q-85 0 -153 51l-507 380q-50 38 -78.5 94t-28.5 118q0 105 75 179t180 74q25 0 49.5 -5.5t41.5 -11t41 -20.5t35 -23t38.5 -29.5t37.5 -28.5l-123 512q-7 35 -7 59q0 93 60 162t152 79q14 87 80.5 144.5t155.5 57.5q83 0 148 -51.5t85 -132.5l103 -428 l83 348q20 81 85 132.5t148 51.5q87 0 152.5 -54t82.5 -139q93 -10 155 -78t62 -161q0 -30 -7 -57l-116 -477q-5 -22 -5 -67q0 -51 -13 -108l-101 -401q-19 -75 -79.5 -122.5t-137.5 -47.5z" />
<glyph unicode="&#xf25a;" horiz-adv-x="1792" d="M640 1408q-53 0 -90.5 -37.5t-37.5 -90.5v-512v-384l-151 202q-41 54 -107 54q-52 0 -89 -38t-37 -90q0 -43 26 -77l384 -512q38 -51 102 -51h718q22 0 39.5 13.5t22.5 34.5l92 368q24 96 24 194v217q0 41 -28 71t-68 30t-68 -28t-28 -68h-32v61q0 48 -32 81.5t-80 33.5 q-46 0 -79 -33t-33 -79v-64h-32v90q0 55 -37 94.5t-91 39.5q-53 0 -90.5 -37.5t-37.5 -90.5v-96h-32v570q0 55 -37 94.5t-91 39.5zM640 1536q107 0 181.5 -77.5t74.5 -184.5v-220q22 2 32 2q99 0 173 -69q47 21 99 21q113 0 184 -87q27 7 56 7q94 0 159 -67.5t65 -161.5 v-217q0 -116 -28 -225l-92 -368q-16 -64 -68 -104.5t-118 -40.5h-718q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 105 74.5 180.5t179.5 75.5q71 0 130 -35v547q0 106 75 181t181 75zM768 128v384h-32v-384h32zM1024 128v384h-32v-384h32zM1280 128v384h-32 v-384h32z" />
<glyph unicode="&#xf25b;" d="M1288 889q60 0 107 -23q141 -63 141 -226v-177q0 -94 -23 -186l-85 -339q-21 -86 -90.5 -140t-157.5 -54h-668q-106 0 -181 75t-75 181v401l-239 628q-17 45 -17 91q0 106 75 181t181 75q80 0 145.5 -45.5t93.5 -119.5l17 -44v113q0 106 75 181t181 75t181 -75t75 -181 v-261q27 5 48 5q69 0 127.5 -36.5t88.5 -98.5zM1072 896q-33 0 -60.5 -18t-41.5 -48l-74 -163l-71 -155h55q50 0 90 -31.5t50 -80.5l154 338q10 20 10 46q0 46 -33 79t-79 33zM1293 761q-22 0 -40.5 -8t-29 -16t-23.5 -29.5t-17 -30.5t-17 -37l-132 -290q-10 -20 -10 -46 q0 -46 33 -79t79 -33q33 0 60.5 18t41.5 48l160 352q9 18 9 38q0 50 -32 81.5t-82 31.5zM128 1120q0 -22 8 -46l248 -650v-69l102 111q43 46 106 46h198l106 233v535q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5v-640h-64l-200 526q-14 37 -47 59.5t-73 22.5 q-53 0 -90.5 -37.5t-37.5 -90.5zM1180 -128q44 0 78.5 27t45.5 70l85 339q19 73 19 155v91l-141 -310q-17 -38 -53 -61t-78 -23q-53 0 -93.5 34.5t-48.5 86.5q-44 -57 -114 -57h-208v32h208q46 0 81 33t35 79t-31 79t-77 33h-296q-49 0 -82 -36l-126 -136v-308 q0 -53 37.5 -90.5t90.5 -37.5h668z" />
<glyph unicode="&#xf25c;" horiz-adv-x="1973" d="M857 992v-117q0 -13 -9.5 -22t-22.5 -9h-298v-812q0 -13 -9 -22.5t-22 -9.5h-135q-13 0 -22.5 9t-9.5 23v812h-297q-13 0 -22.5 9t-9.5 22v117q0 14 9 23t23 9h793q13 0 22.5 -9.5t9.5 -22.5zM1895 995l77 -961q1 -13 -8 -24q-10 -10 -23 -10h-134q-12 0 -21 8.5 t-10 20.5l-46 588l-189 -425q-8 -19 -29 -19h-120q-20 0 -29 19l-188 427l-45 -590q-1 -12 -10 -20.5t-21 -8.5h-135q-13 0 -23 10q-9 10 -9 24l78 961q1 12 10 20.5t21 8.5h142q20 0 29 -19l220 -520q10 -24 20 -51q3 7 9.5 24.5t10.5 26.5l221 520q9 19 29 19h141 q13 0 22 -8.5t10 -20.5z" />
<glyph unicode="&#xf25d;" horiz-adv-x="1792" d="M1042 833q0 88 -60 121q-33 18 -117 18h-123v-281h162q66 0 102 37t36 105zM1094 548l205 -373q8 -17 -1 -31q-8 -16 -27 -16h-152q-20 0 -28 17l-194 365h-155v-350q0 -14 -9 -23t-23 -9h-134q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h294q128 0 190 -24q85 -31 134 -109 t49 -180q0 -92 -42.5 -165.5t-115.5 -109.5q6 -10 9 -16zM896 1376q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM1792 640 q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf25e;" horiz-adv-x="1792" d="M605 303q153 0 257 104q14 18 3 36l-45 82q-6 13 -24 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78 q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-148 0 -246 -96.5t-98 -240.5q0 -146 97 -241.5t247 -95.5zM1235 303q153 0 257 104q14 18 4 36l-45 82q-8 14 -25 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5 t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-147 0 -245.5 -96.5t-98.5 -240.5q0 -146 97 -241.5t247 -95.5zM896 1376 q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71z" />
<glyph unicode="&#xf260;" horiz-adv-x="2048" d="M736 736l384 -384l-384 -384l-672 672l672 672l168 -168l-96 -96l-72 72l-480 -480l480 -480l193 193l-289 287zM1312 1312l672 -672l-672 -672l-168 168l96 96l72 -72l480 480l-480 480l-193 -193l289 -287l-96 -96l-384 384z" />
<glyph unicode="&#xf261;" horiz-adv-x="1792" d="M717 182l271 271l-279 279l-88 -88l192 -191l-96 -96l-279 279l279 279l40 -40l87 87l-127 128l-454 -454zM1075 190l454 454l-454 454l-271 -271l279 -279l88 88l-192 191l96 96l279 -279l-279 -279l-40 40l-87 -88zM1792 640q0 -182 -71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf262;" horiz-adv-x="2304" d="M651 539q0 -39 -27.5 -66.5t-65.5 -27.5q-39 0 -66.5 27.5t-27.5 66.5q0 38 27.5 65.5t66.5 27.5q38 0 65.5 -27.5t27.5 -65.5zM1805 540q0 -39 -27.5 -66.5t-66.5 -27.5t-66.5 27.5t-27.5 66.5t27.5 66t66.5 27t66.5 -27t27.5 -66zM765 539q0 79 -56.5 136t-136.5 57 t-136.5 -56.5t-56.5 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM1918 540q0 80 -56.5 136.5t-136.5 56.5q-79 0 -136 -56.5t-57 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM850 539q0 -116 -81.5 -197.5t-196.5 -81.5q-116 0 -197.5 82t-81.5 197 t82 196.5t197 81.5t196.5 -81.5t81.5 -196.5zM2004 540q0 -115 -81.5 -196.5t-197.5 -81.5q-115 0 -196.5 81.5t-81.5 196.5t81.5 196.5t196.5 81.5q116 0 197.5 -81.5t81.5 -196.5zM1040 537q0 191 -135.5 326.5t-326.5 135.5q-125 0 -231 -62t-168 -168.5t-62 -231.5 t62 -231.5t168 -168.5t231 -62q191 0 326.5 135.5t135.5 326.5zM1708 1110q-254 111 -556 111q-319 0 -573 -110q117 0 223 -45.5t182.5 -122.5t122 -183t45.5 -223q0 115 43.5 219.5t118 180.5t177.5 123t217 50zM2187 537q0 191 -135 326.5t-326 135.5t-326.5 -135.5 t-135.5 -326.5t135.5 -326.5t326.5 -135.5t326 135.5t135 326.5zM1921 1103h383q-44 -51 -75 -114.5t-40 -114.5q110 -151 110 -337q0 -156 -77 -288t-209 -208.5t-287 -76.5q-133 0 -249 56t-196 155q-47 -56 -129 -179q-11 22 -53.5 82.5t-74.5 97.5 q-80 -99 -196.5 -155.5t-249.5 -56.5q-155 0 -287 76.5t-209 208.5t-77 288q0 186 110 337q-9 51 -40 114.5t-75 114.5h365q149 100 355 156.5t432 56.5q224 0 421 -56t348 -157z" />
<glyph unicode="&#xf263;" horiz-adv-x="1280" d="M640 629q-188 0 -321 133t-133 320q0 188 133 321t321 133t321 -133t133 -321q0 -187 -133 -320t-321 -133zM640 1306q-92 0 -157.5 -65.5t-65.5 -158.5q0 -92 65.5 -157.5t157.5 -65.5t157.5 65.5t65.5 157.5q0 93 -65.5 158.5t-157.5 65.5zM1163 574q13 -27 15 -49.5 t-4.5 -40.5t-26.5 -38.5t-42.5 -37t-61.5 -41.5q-115 -73 -315 -94l73 -72l267 -267q30 -31 30 -74t-30 -73l-12 -13q-31 -30 -74 -30t-74 30q-67 68 -267 268l-267 -268q-31 -30 -74 -30t-73 30l-12 13q-31 30 -31 73t31 74l267 267l72 72q-203 21 -317 94 q-39 25 -61.5 41.5t-42.5 37t-26.5 38.5t-4.5 40.5t15 49.5q10 20 28 35t42 22t56 -2t65 -35q5 -4 15 -11t43 -24.5t69 -30.5t92 -24t113 -11q91 0 174 25.5t120 50.5l38 25q33 26 65 35t56 2t42 -22t28 -35z" />
<glyph unicode="&#xf264;" d="M927 956q0 -66 -46.5 -112.5t-112.5 -46.5t-112.5 46.5t-46.5 112.5t46.5 112.5t112.5 46.5t112.5 -46.5t46.5 -112.5zM1141 593q-10 20 -28 32t-47.5 9.5t-60.5 -27.5q-10 -8 -29 -20t-81 -32t-127 -20t-124 18t-86 36l-27 18q-31 25 -60.5 27.5t-47.5 -9.5t-28 -32 q-22 -45 -2 -74.5t87 -73.5q83 -53 226 -67l-51 -52q-142 -142 -191 -190q-22 -22 -22 -52.5t22 -52.5l9 -9q22 -22 52.5 -22t52.5 22l191 191q114 -115 191 -191q22 -22 52.5 -22t52.5 22l9 9q22 22 22 52.5t-22 52.5l-191 190l-52 52q141 14 225 67q67 44 87 73.5t-2 74.5 zM1092 956q0 134 -95 229t-229 95t-229 -95t-95 -229t95 -229t229 -95t229 95t95 229zM1536 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="&#xf265;" horiz-adv-x="1720" d="M1565 1408q65 0 110 -45.5t45 -110.5v-519q0 -176 -68 -336t-182.5 -275t-274 -182.5t-334.5 -67.5q-176 0 -335.5 67.5t-274.5 182.5t-183 275t-68 336v519q0 64 46 110t110 46h1409zM861 344q47 0 82 33l404 388q37 35 37 85q0 49 -34.5 83.5t-83.5 34.5q-47 0 -82 -33 l-323 -310l-323 310q-35 33 -81 33q-49 0 -83.5 -34.5t-34.5 -83.5q0 -51 36 -85l405 -388q33 -33 81 -33z" />
<glyph unicode="&#xf266;" horiz-adv-x="2304" d="M1494 -103l-295 695q-25 -49 -158.5 -305.5t-198.5 -389.5q-1 -1 -27.5 -0.5t-26.5 1.5q-82 193 -255.5 587t-259.5 596q-21 50 -66.5 107.5t-103.5 100.5t-102 43q0 5 -0.5 24t-0.5 27h583v-50q-39 -2 -79.5 -16t-66.5 -43t-10 -64q26 -59 216.5 -499t235.5 -540 q31 61 140 266.5t131 247.5q-19 39 -126 281t-136 295q-38 69 -201 71v50l513 -1v-47q-60 -2 -93.5 -25t-12.5 -69q33 -70 87 -189.5t86 -187.5q110 214 173 363q24 55 -10 79.5t-129 26.5q1 7 1 25v24q64 0 170.5 0.5t180 1t92.5 0.5v-49q-62 -2 -119 -33t-90 -81 l-213 -442q13 -33 127.5 -290t121.5 -274l441 1017q-14 38 -49.5 62.5t-65 31.5t-55.5 8v50l460 -4l1 -2l-1 -44q-139 -4 -201 -145q-526 -1216 -559 -1291h-49z" />
<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="&#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="&#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" />
<glyph unicode="&#xf273;" horiz-adv-x="1792" d="M1111 151l-46 -46q-9 -9 -22 -9t-23 9l-188 189l-188 -189q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22t9 23l189 188l-189 188q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l188 -188l188 188q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23l-188 -188l188 -188q9 -10 9 -23t-9 -22z M128 -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 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -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-96h128q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf274;" horiz-adv-x="1792" d="M1303 572l-512 -512q-10 -9 -23 -9t-23 9l-288 288q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l220 -220l444 444q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23 t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-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 -47 t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
<glyph unicode="&#xf275;" horiz-adv-x="1792" d="M448 1536q26 0 45 -19t19 -45v-891l536 429q17 14 40 14q26 0 45 -19t19 -45v-379l536 429q17 14 40 14q26 0 45 -19t19 -45v-1152q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h384z" />
<glyph unicode="&#xf276;" horiz-adv-x="1024" d="M512 448q66 0 128 15v-655q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v655q61 -15 128 -15zM512 1536q212 0 362 -150t150 -362t-150 -362t-362 -150t-362 150t-150 362t150 362t362 150zM512 1312q14 0 23 9t9 23t-9 23t-23 9q-146 0 -249 -103t-103 -249 q0 -14 9 -23t23 -9t23 9t9 23q0 119 84.5 203.5t203.5 84.5z" />
<glyph unicode="&#xf277;" horiz-adv-x="1792" d="M1745 1239q10 -10 10 -23t-10 -23l-141 -141q-28 -28 -68 -28h-1344q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h576v64q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-64h512q40 0 68 -28zM768 320h256v-512q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v512zM1600 768 q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1344q-40 0 -68 28l-141 141q-10 10 -10 23t10 23l141 141q28 28 68 28h512v192h256v-192h576z" />
<glyph unicode="&#xf278;" horiz-adv-x="2048" d="M2020 1525q28 -20 28 -53v-1408q0 -20 -11 -36t-29 -23l-640 -256q-24 -11 -48 0l-616 246l-616 -246q-10 -5 -24 -5q-19 0 -36 11q-28 20 -28 53v1408q0 20 11 36t29 23l640 256q24 11 48 0l616 -246l616 246q32 13 60 -6zM736 1390v-1270l576 -230v1270zM128 1173 v-1270l544 217v1270zM1920 107v1270l-544 -217v-1270z" />
<glyph unicode="&#xf279;" horiz-adv-x="1792" d="M512 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472q0 20 17 28l480 256q7 4 15 4zM1760 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472 q0 20 17 28l480 256q7 4 15 4zM640 1536q8 0 14 -3l512 -256q18 -10 18 -29v-1472q0 -13 -9.5 -22.5t-22.5 -9.5q-8 0 -14 3l-512 256q-18 10 -18 29v1472q0 13 9.5 22.5t22.5 9.5z" />
<glyph unicode="&#xf27a;" horiz-adv-x="1792" d="M640 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 640q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-110 0 -211 18q-173 -173 -435 -229q-52 -10 -86 -13q-12 -1 -22 6t-13 18q-4 15 20 37q5 5 23.5 21.5t25.5 23.5t23.5 25.5t24 31.5t20.5 37 t20 48t14.5 57.5t12.5 72.5q-146 90 -229.5 216.5t-83.5 269.5q0 174 120 321.5t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
<glyph unicode="&#xf27b;" horiz-adv-x="1792" d="M640 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5 t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51 t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 130 71 248.5t191 204.5t286 136.5t348 50.5t348 -50.5t286 -136.5t191 -204.5t71 -248.5z" />
<glyph unicode="&#xf27c;" horiz-adv-x="1024" d="M512 345l512 295v-591l-512 -296v592zM0 640v-591l512 296zM512 1527v-591l-512 -296v591zM512 936l512 295v-591z" />
<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="&#xf500;" horiz-adv-x="1792" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 306 KiB

After

Width:  |  Height:  |  Size: 348 KiB

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@@ -22,6 +22,7 @@
"show": "Toggle Sidebar"
},
"displayStatus": "Display Node Status",
"displayConfig": "Configuration Nodes",
"import": "Import",
"export": "Export",
"clipboard": "Clipboard",
@@ -29,7 +30,7 @@
"subflows": "Subflows",
"createSubflow": "Create Subflow",
"selectionToSubflow": "Selection to Subflow",
"workspaces": "Workspaces",
"flows": "Tabs",
"add": "Add",
"rename": "Rename",
"delete": "Delete",
@@ -87,22 +88,23 @@
"cancel": "Cancel"
},
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
"improperlyConfigured": "Some of the nodes are not properly configured.",
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
"unknown": "The workspace contains some unknown node types:",
"unusedConfig": "The workspace contains some unused configuration nodes:",
"confirm": "Are you sure you want to deploy?"
}
},
"subflow": {
"tabLabel": "Subflow: __name__",
"editSubflow": "Edit flow __name__",
"edit": "Edit flow",
"subflowInstances": "There is __count__ instance of this subflow",
"subflowInstances_plural": "There are __count__ instances of this subflow",
"editSubflowName": "edit name",
"input": "input",
"output": "output",
"editSubflowProperties": "edit properties",
"input": "inputs:",
"output": "outputs:",
"deleteSubflow": "delete subflow",
"info": "Description",
"format":"markdown format",
"errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
@@ -116,7 +118,10 @@
"nodesUse_plural": "__count__ nodes use this config",
"addNewConfig": "Add new __type__ config node",
"editConfig": "Edit __type__ config node",
"addNewType": "Add new __type__..."
"addNewType": "Add new __type__...",
"errors": {
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it"
}
},
"keyboard": {
"selectAll": "Select all nodes",
@@ -191,7 +196,12 @@
},
"config": {
"name": "Configuration nodes",
"label": "config"
"label": "config",
"local": "on this flow",
"global": "on all flows",
"none": "none",
"subflows": "subflows",
"flows": "flows"
}
}
}

View File

@@ -28,7 +28,7 @@
<!-- Generally, there should be an input for each property of the node. -->
<!-- The for and id attributes identify the corresponding property -->
<!-- (with the 'node-input-' prefix). -->
<!-- The available icon classes are defined Twitter Bootstrap glyphicons -->
<!-- The available icon classes are defined Font Awesome Icons (FA Icons) -->
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">

View File

@@ -81,13 +81,13 @@ module.exports = function(RED) {
if (node != null) {
try {
node.receive();
res.send(200);
res.sendStatus(200);
} catch(err) {
res.send(500);
res.sendStatus(500);
node.error(RED._("inject.failed",{error:err.toString()}));
}
} else {
res.send(404);
res.sendStatus(404);
}
});
}

View File

@@ -15,6 +15,25 @@
-->
<script type="text/x-red" data-template-name="catch">
<div class="form-row">
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
<select id="node-input-scope-select">
<option value="all" data-i18n="catch.scope.all"></option>
<option value="target" data-i18n="catch.scope.selected"></options>
</select>
</div>
<div class="form-row node-input-target-row" style="display: none;">
<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;">
<div style="display: inline-block;"><a id="node-input-target-sort-label" href="#" data-i18n="[title]catch.label.sortByLabel"><span data-i18n="catch.label.node"></span> <i class="node-input-catch-sort-label-a fa fa-caret-down"></i><i class="node-input-catch-sort-label-d fa fa-caret-up"></i></a></div>
<div style="position: absolute; right: 10px; width: 50px; display: inline-block; text-align: right;"><a id="node-input-target-sort-type" href="#" data-i18n="[title]catch.label.sortByType"><i class="node-input-catch-sort-sublabel-a fa fa-caret-down"></i><i class="node-input-catch-sort-sublabel-d fa fa-caret-up"></i> <span data-i18n="catch.label.type"></span></a></div>
</div>
<div style="background: #fbfbfb; box-sizing: border-box; position:absolute; top:20px;bottom:0;left:0px;right:0px; overflow-y: scroll; overflow-x: hidden;">
<ul id="node-input-catch-target-container" style=" list-style-type:none; margin: 0;"></ul>
</div>
</div>
</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">
@@ -37,26 +56,235 @@
<li><code>message</code> : the error message</li>
<li><code>source.id</code> : the id of the node that threw the error</li>
<li><code>source.type</code> : the type of the node that threw the error</li>
<li><code>source.name</code> : the name, if set, of the node that threw the error</li>
</ul>
</p>
<p>If the message already had a <code>error</code> property, it is copied to <code>_error</code>.</p>
</script>
<style>
#node-input-catch-target-container {
position: relative;
}
#node-input-catch-target-container li {
padding: 2px 5px;
background: none;
font-size: 0.8em;
margin:0;
white-space: nowrap;
}
#node-input-catch-target-container li label {
margin-bottom: 0;
width: 100%;
}
#node-input-catch-target-container li label input {
vertical-align: top;
width:15px;
margin-right: 10px;
}
#node-input-catch-target-container li:hover,
#node-input-catch-target-container li:hover .node-input-target-node-sublabel {
background: #f0f0f0;
}
.node-input-target-node-sublabel {
position:absolute;
right: 0px;
padding-right: 10px;
padding-left: 10px;
font-size: 0.8em;
background: #fbfbfb;
}
</style>
<script type="text/javascript">
RED.nodes.registerType('catch',{
category: 'input',
color:"#e49191",
defaults: {
name: {value:""}
name: {value:""},
scope: {value:null}
},
inputs:0,
outputs:1,
icon: "alert.png",
label: function() {
return this.name||this._("catch.catch");
return this.name||this.scope?this._("catch.catchNodes",{number:this.scope.length}):this._("catch.catch");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var nodeList = $("#node-input-catch-target-container");
var node = this;
function createNodeList() {
var scope = node.scope || [];
nodeList.empty();
var candidateNodes = RED.nodes.filterNodes({z:node.z});
var allChecked = true;
candidateNodes.forEach(function(n) {
if (n.id === node.id) {
return;
}
var isChecked = scope.indexOf(n.id) !== -1;
allChecked = allChecked && isChecked;
var container = $('<li/>',{class:"node-input-target-node"});
var row = $('<label/>',{for:"node-input-target-node-"+n.id}).appendTo(container);
$('<input>',{type:"checkbox",class:"node-input-target-node-checkbox",id:"node-input-target-node-"+n.id})
.data('node-id',n.id)
.prop('checked', isChecked)
.appendTo(row);
container.on('mouseover',function(e) {
n.highlighted = true;
n.dirty = true;
RED.view.redraw();
});
container.on('mouseout',function(e) {
n.highlighted = false;
n.dirty = true;
RED.view.redraw();
});
var labelSpan = $('<span>');
var nodeDef = RED.nodes.getType(n.type);
var label;
var sublabel;
if (nodeDef) {
var l = nodeDef.label;
label = (typeof l === "function" ? l.call(n) : l)||"";
sublabel = n.type;
if (sublabel.indexOf("subflow:") === 0) {
var subflowId = sublabel.substring(8);
var subflow = RED.nodes.subflow(subflowId);
sublabel = "subflow : "+subflow.name;
}
}
if (!nodeDef || !label) {
label = n.type;
}
$('<span>',{class:"node-input-target-node-label",style:"white-space:nowrap"}).text(label).appendTo(row);
if (sublabel) {
$('<span>',{class:"node-input-target-node-sublabel"}).text(sublabel).appendTo(row);
}
container.appendTo(nodeList);
});
$(".node-input-target-node-checkbox").change(function() {
if (!this.checked) {
$("#node-input-target-node-checkbox-all").prop('checked',false);
}
});
$("#node-input-target-node-checkbox-all").prop('checked',allChecked);
sortNodeList('label');
}
function sortNodeList(sortOn) {
var currentSort = nodeList.data('currentSort');
var currentSortOrder = nodeList.data('currentSortOrder');
if (!currentSort) {
currentSort = sortOn;
currentSortOrder = 'a';
} else {
if (currentSort === sortOn) {
currentSortOrder = (currentSortOrder === 'a'?'d':'a');
} else {
currentSortOrder = 'a';
}
currentSort = sortOn;
}
nodeList.data('currentSort',currentSort);
nodeList.data('currentSortOrder',currentSortOrder);
$("#node-input-catch-target-container-div .fa").hide();
$(".node-input-catch-sort-"+currentSort+"-"+currentSortOrder).show();
var items = nodeList.find("li").get();
items.sort(function(a,b) {
var labelA = $(a).find(".node-input-target-node-"+currentSort).text().toLowerCase();
var labelB = $(b).find(".node-input-target-node-"+currentSort).text().toLowerCase();
if (labelA < labelB) { return currentSortOrder==='a'?-1:1; }
if (labelA > labelB) { return currentSortOrder==='a'?1:-1; }
return 0;
});
$.each(items, function(i, li){
nodeList.append(li);
});
}
$("#node-input-target-sort-label").click(function(e) {
e.preventDefault();
sortNodeList('label');
});
$("#node-input-target-sort-type").click(function(e) {
e.preventDefault();
sortNodeList('sublabel')
});
$("#node-input-target-node-checkbox-all").change(function() {
$(".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") {
createNodeList();
$(".node-input-target-row").show();
} else {
$(".node-input-target-row").hide();
}
});
if (this.scope == null) {
$("#node-input-scope-select").val("all");
} else {
$("#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();
if (scope === 'all') {
this.scope = null;
} else {
var node = this;
node.scope = [];
$(".node-input-target-node-checkbox").each(function(n) {
if ($(this).prop("checked")) {
node.scope.push($(this).data('node-id'));
}
})
}
}
});
</script>

View File

@@ -20,6 +20,7 @@ module.exports = function(RED) {
function CatchNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.scope = n.scope;
this.on("input",function(msg) {
this.send(msg);
});

View File

@@ -0,0 +1,281 @@
<!--
Copyright 2015 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="status">
<div class="form-row">
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
<select id="node-input-scope-select">
<option value="all" data-i18n="status.scope.all"></option>
<option value="target" data-i18n="status.scope.selected"></options>
</select>
</div>
<div class="form-row node-input-target-row" style="display: none;">
<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;">
<div style="display: inline-block;"><a id="node-input-target-sort-label" href="#" data-i18n="[title]status.label.sortByLabel"><span data-i18n="status.label.node"></span> <i class="node-input-status-sort-label-a fa fa-caret-down"></i><i class="node-input-status-sort-label-d fa fa-caret-up"></i></a></div>
<div style="position: absolute; right: 10px; width: 50px; display: inline-block; text-align: right;"><a id="node-input-target-sort-type" href="#" data-i18n="[title]status.label.sortByType"><i class="node-input-status-sort-sublabel-a fa fa-caret-down"></i><i class="node-input-status-sort-sublabel-d fa fa-caret-up"></i> <span data-i18n="status.label.type"></span></a></div>
</div>
<div style="background: #fbfbfb; box-sizing: border-box; position:absolute; top:20px;bottom:0;left:0px;right:0px; overflow-y: scroll; overflow-x: hidden;">
<ul id="node-input-status-target-container" style=" list-style-type:none; margin: 0;"></ul>
</div>
</div>
</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>
</script>
<script type="text/x-red" data-help-name="status">
<p>Send status messages from other nodes on the same tab.</p>
<p>The message sent by this node will have a <code>status</code> property
with the following attributes:
<ul>
<li><code>text</code> : the status text</li>
<li><code>source.type</code> : the type of the node that reported status</li>
<li><code>source.id</code> : the id of the node that reported status</li>
<li><code>source.name</code> : the name, if set, of the node that reported status</li>
</ul>
</p>
</script>
<style>
#node-input-status-target-container {
position: relative;
}
#node-input-status-target-container li {
padding: 2px 5px;
background: none;
font-size: 0.8em;
margin:0;
white-space: nowrap;
}
#node-input-status-target-container li label {
margin-bottom: 0;
width: 100%;
}
#node-input-status-target-container li label input {
vertical-align: top;
width:15px;
margin-right: 10px;
}
#node-input-status-target-container li:hover,
#node-input-status-target-container li:hover .node-input-target-node-sublabel {
background: #f0f0f0;
}
.node-input-target-node-sublabel {
position:absolute;
right: 0px;
padding-right: 10px;
padding-left: 10px;
font-size: 0.8em;
background: #fbfbfb;
}
</style>
<script type="text/javascript">
RED.nodes.registerType('status',{
category: 'input',
color:"#c0edc0",
defaults: {
name: {value:""},
scope: {value:null}
},
inputs:0,
outputs:1,
icon: "alert.png",
label: function() {
return this.name||this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var nodeList = $("#node-input-status-target-container");
var node = this;
function createNodeList() {
var scope = node.scope || [];
nodeList.empty();
var candidateNodes = RED.nodes.filterNodes({z:node.z});
var allChecked = true;
candidateNodes.forEach(function(n) {
if (n.id === node.id) {
return;
}
var isChecked = scope.indexOf(n.id) !== -1;
allChecked = allChecked && isChecked;
var container = $('<li/>',{class:"node-input-target-node"});
var row = $('<label/>',{for:"node-input-target-node-"+n.id}).appendTo(container);
$('<input>',{type:"checkbox",class:"node-input-target-node-checkbox",id:"node-input-target-node-"+n.id})
.data('node-id',n.id)
.prop('checked', isChecked)
.appendTo(row);
container.on('mouseover',function(e) {
n.highlighted = true;
n.dirty = true;
RED.view.redraw();
});
container.on('mouseout',function(e) {
n.highlighted = false;
n.dirty = true;
RED.view.redraw();
});
var labelSpan = $('<span>');
var nodeDef = RED.nodes.getType(n.type);
var label;
var sublabel;
if (nodeDef) {
var l = nodeDef.label;
label = (typeof l === "function" ? l.call(n) : l)||"";
sublabel = n.type;
if (sublabel.indexOf("subflow:") === 0) {
var subflowId = sublabel.substring(8);
var subflow = RED.nodes.subflow(subflowId);
sublabel = "subflow : "+subflow.name;
}
}
if (!nodeDef || !label) {
label = n.type;
}
$('<span>',{class:"node-input-target-node-label",style:"white-space:nowrap"}).text(label).appendTo(row);
if (sublabel) {
$('<span>',{class:"node-input-target-node-sublabel"}).text(sublabel).appendTo(row);
}
container.appendTo(nodeList);
});
$(".node-input-target-node-checkbox").change(function() {
if (!this.checked) {
$("#node-input-target-node-checkbox-all").prop('checked',false);
}
});
$("#node-input-target-node-checkbox-all").prop('checked',allChecked);
sortNodeList('label');
}
function sortNodeList(sortOn) {
var currentSort = nodeList.data('currentSort');
var currentSortOrder = nodeList.data('currentSortOrder');
if (!currentSort) {
currentSort = sortOn;
currentSortOrder = 'a';
} else {
if (currentSort === sortOn) {
currentSortOrder = (currentSortOrder === 'a'?'d':'a');
} else {
currentSortOrder = 'a';
}
currentSort = sortOn;
}
nodeList.data('currentSort',currentSort);
nodeList.data('currentSortOrder',currentSortOrder);
$("#node-input-status-target-container-div .fa").hide();
$(".node-input-status-sort-"+currentSort+"-"+currentSortOrder).show();
var items = nodeList.find("li").get();
items.sort(function(a,b) {
var labelA = $(a).find(".node-input-target-node-"+currentSort).text().toLowerCase();
var labelB = $(b).find(".node-input-target-node-"+currentSort).text().toLowerCase();
if (labelA < labelB) { return currentSortOrder==='a'?-1:1; }
if (labelA > labelB) { return currentSortOrder==='a'?1:-1; }
return 0;
});
$.each(items, function(i, li){
nodeList.append(li);
});
}
$("#node-input-target-sort-label").click(function(e) {
e.preventDefault();
sortNodeList('label');
});
$("#node-input-target-sort-type").click(function(e) {
e.preventDefault();
sortNodeList('sublabel')
});
$("#node-input-target-node-checkbox-all").change(function() {
$(".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") {
createNodeList();
$(".node-input-target-row").show();
} else {
$(".node-input-target-row").hide();
}
});
if (this.scope == null) {
$("#node-input-scope-select").val("all");
} else {
$("#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();
if (scope === 'all') {
this.scope = null;
} else {
var node = this;
node.scope = [];
$(".node-input-target-node-checkbox").each(function(n) {
if ($(this).prop("checked")) {
node.scope.push($(this).data('node-id'));
}
})
}
}
});
</script>

View File

@@ -0,0 +1,30 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
function StatusNode(n) {
RED.nodes.createNode(this,n);
var node = this;
this.scope = n.scope;
this.on("input", function(msg) {
this.send(msg);
});
}
RED.nodes.registerType("status",StatusNode);
}

View File

@@ -201,7 +201,7 @@
msg.className = 'debug-message'+(o.level?(' debug-message-level-'+o.level):'');
msg.innerHTML = '<span class="debug-message-date">'+
getTimestamp()+'</span>'+
(name?'<span class="debug-message-name">['+name+']':'')+
(name?'<span class="debug-message-name">'+name:'')+
'</span>';
// NOTE: relying on function error to have a "type" that all other msgs don't
if (o.hasOwnProperty("type") && (o.type === "function")) {
@@ -212,11 +212,11 @@
errorLvlType = 'warn';
}
msg.className = 'debug-message debug-message-level-' + errorLvl;
msg.innerHTML += '<span class="debug-message-topic">[function] : (' + errorLvlType + ')</span>';
msg.innerHTML += '<span class="debug-message-topic">function : (' + errorLvlType + ')</span>';
} else {
msg.innerHTML += '<span class="debug-message-topic">'+
(o.topic?topic+' : ':'')+
(o.property?'[msg.'+property+']':'[msg]')+" : "+format+
(o.property?'msg.'+property:'msg')+" : "+format+
'</span>';
}
@@ -284,7 +284,7 @@
.debug-message-topic {
display: block;
background: #fff;
padding: 1px 5px;
padding: 1px;
font-size: 10px;
color: #a66;
}

View File

@@ -82,21 +82,27 @@ module.exports = function(RED) {
msg.format = "error";
msg.msg = msg.msg.toString();
} else if (msg.msg instanceof Buffer) {
msg.format = "buffer";
msg.format = "buffer ["+msg.msg.length+"]";
msg.msg = msg.msg.toString('hex');
} else if (typeof msg.msg === 'object') {
var seen = [];
msg.format = "object";
if (util.isArray(msg.msg)) {
msg.format = "array";
msg.format = msg.msg.constructor.name || "Object";
var isArray = util.isArray(msg.msg);
if (isArray) {
msg.format = "array ["+msg.msg.length+"]";
}
if (isArray || (msg.format === "Object")) {
msg.msg = JSON.stringify(msg.msg, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.indexOf(value) !== -1) { return "[circular]"; }
seen.push(value);
}
return value;
}," ");
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]"; }
}
msg.msg = JSON.stringify(msg.msg, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.indexOf(value) !== -1) { return "[circular]"; }
seen.push(value);
}
return value;
}," ");
seen = null;
} else if (typeof msg.msg === "boolean") {
msg.format = "boolean";
@@ -111,7 +117,7 @@ module.exports = function(RED) {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string";
msg.format = "string ["+msg.msg.length+"]";
msg.msg = msg.msg;
}
@@ -136,15 +142,15 @@ module.exports = function(RED) {
if (node !== null && typeof node !== "undefined" ) {
if (state === "enable") {
node.active = true;
res.send(200);
res.sendStatus(200);
} else if (state === "disable") {
node.active = false;
res.send(201);
res.sendStatus(201);
} else {
res.send(404);
res.sendStatus(404);
}
} else {
res.send(404);
res.sendStatus(404);
}
});
};

View File

@@ -37,17 +37,17 @@
<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="spawnTip"><span data-i18n="[html]exec.tip"></span></div>
</script>
<script type="text/x-red" data-help-name="exec">
<p>Calls out to a system command.<br/></p>
<p>Provides 3 outputs... stdout, stderr, and return code.</p>
<p>By default uses exec() which calls the command, blocks while waiting for completion, and then returns the complete result in one go, along with any errors.</p>
<p>Optionally can use spawn() instead, which returns output from stdout and stderr as the command runs (ie one line at a time). On completion it then returns a return code (on the 3rd output).</p>
<p>Spawn only expects one command word, with all extra parameters to be comma separated and passed as the append.</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>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>
</script>
<script type="text/javascript">
@@ -70,15 +70,6 @@
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-useSpawn").on("change",function() {
if ($("#node-input-useSpawn").is(':checked')) {
$("#spawnTip").show();
} else {
$("#spawnTip").hide();
}
});
}
});
</script>

View File

@@ -30,17 +30,18 @@ module.exports = function(RED) {
var node = this;
this.on("input", function(msg) {
node.status({fill:"blue",shape:"dot"});
node.status({fill:"blue",shape:"dot",text:" "});
if (this.useSpawn === true) {
// make the extra args into an array
// then prepend with the msg.payload
if (typeof(msg.payload !== "string")) { msg.payload = (msg.payload || "").toString(); }
var arg = [];
if (node.append.length > 0) { arg = node.append.split(","); }
if ((node.addpay === true) && (msg.payload.toString().trim() !== "")) { arg.unshift(msg.payload); }
if (RED.settings.verbose) { node.log(node.cmd+" ["+arg+"]"); }
if (node.cmd.indexOf(" ") == -1) {
var ex = spawn(node.cmd,arg);
var arg = node.cmd+" "+msg.payload+" "+node.append;
// slice whole line by spaces (trying to honour quotes);
arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
var cmd = arg.shift();
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
if (cmd.indexOf(" ") == -1) {
var ex = spawn(cmd,arg);
ex.stdout.on('data', function (data) {
//console.log('[exec] stdout: ' + data);
if (isUtf8(data)) { msg.payload = data.toString(); }

View File

@@ -57,11 +57,14 @@ module.exports = function(RED) {
"error:__node__.error,"+
"warn:__node__.warn,"+
"on:__node__.on,"+
"status:__node__.status,"+
"send:function(msgs){ __node__.send(__msgid__,msgs);}"+
"};\n"+
this.func+"\n"+
"})(msg);";
this.topic = n.topic;
this.outstandingTimers = [];
this.outstandingIntervals = [];
var sandbox = {
console:console,
util:util,
@@ -70,24 +73,50 @@ module.exports = function(RED) {
log: function() {
node.log.apply(node, arguments);
},
error: function(){
error: function() {
node.error.apply(node, arguments);
},
warn: function() {
node.warn.apply(node, arguments);
},
send: function(id,msgs) {
sendResults(node,id,msgs);
send: function(id, msgs) {
sendResults(node, id, msgs);
},
on: function() {
node.on.apply(node,arguments);
node.on.apply(node, arguments);
},
status: function() {
node.status.apply(node, arguments);
}
},
context: {
global:RED.settings.functionGlobalContext || {}
},
setTimeout: setTimeout,
clearTimeout: clearTimeout
setTimeout: function (func,delay) {
var timerId = setTimeout(function() {
sandbox.clearTimeout(timerId);
func();
},delay);
node.outstandingTimers.push(timerId);
},
clearTimeout: function(id) {
clearTimeout(id);
var index = node.outstandingTimers.indexOf(id);
if (index > -1) {
node.outstandingTimers.splice(index,1);
}
},
setInterval: function(func,delay) {
var timerId = setInterval(func,delay);
node.outstandingIntervals.push(timerId);
},
clearInterval: function(id) {
clearInterval(id);
var index = node.outstandingIntervals.indexOf(id);
if (index > -1) {
node.outstandingIntervals.splice(index,1);
}
}
};
var context = vm.createContext(sandbox);
try {
@@ -100,7 +129,7 @@ module.exports = function(RED) {
sendResults(this,msg._msgid,context.results);
var duration = process.hrtime(start);
var converted = Math.floor((duration[0]* 1e9 + duration[1])/10000)/100;
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
this.metric("duration", msg, converted);
if (process.env.NODE_RED_FUNCTION_TIME) {
this.status({fill:"yellow",shape:"dot",text:""+converted});
@@ -111,7 +140,7 @@ module.exports = function(RED) {
var errorMessage;
var stack = err.stack.split(/\r?\n/);
if (stack.length > 0) {
while(line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++;
}
@@ -119,9 +148,9 @@ module.exports = function(RED) {
errorMessage = stack[line];
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
if (m) {
var line = Number(m[1])-1;
var lineno = Number(m[1])-1;
var cha = m[2];
errorMessage += " (line "+line+", col "+cha+")";
errorMessage += " (line "+lineno+", col "+cha+")";
}
}
}
@@ -131,6 +160,14 @@ module.exports = function(RED) {
this.error(errorMessage, msg);
}
});
this.on("close", function() {
while(node.outstandingTimers.length > 0) {
clearTimeout(node.outstandingTimers.pop())
}
while(node.outstandingIntervals.length > 0) {
clearInterval(node.outstandingIntervals.pop())
}
})
} catch(err) {
// eg SyntaxError - which v8 doesn't include line number information
// so we can't do better than this

View File

@@ -109,23 +109,23 @@
if (this.pauseType == "delay") {
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
return this.name||this._("delay.label.delay")+" "+this.timeout+" " + units;
return this.name||this._("delay.label.delay")+" "+this.timeout+" "+units;
} else if (this.pauseType == "rate") {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name||this._("delay.label.limit")+" "+this.rate+" msg/"+ units;
return this.name||this._("delay.label.limit")+" "+this.rate+" msg/"+units;
} else if (this.pauseType == "random") {
return this.name || this._("delay.label.random");
}
else {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name || this._("delay.label.queue") +this.rate+" msg/"+ units;
return this.name || this._("delay.label.queue")+" "+this.rate+" msg/"+units;
}
},
labelStyle: function() { // sets the class to apply to the label
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$( "#node-input-timeout" ).spinner({min:1,max:60});
$( "#node-input-timeout" ).spinner({min:1});
$( "#node-input-rate" ).spinner({min:1});
$( "#node-input-randomFirst" ).spinner({min:0});

View File

@@ -87,8 +87,8 @@ module.exports = function(RED) {
node.send(msg);
}, node.timeout);
this.idList.push(id);
if ((node.timeout > 1000) && (node.idList.length === 0)) {
node.status({fill:"blue",shape:"dot"});
if ((node.timeout > 1000) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:" "});
}
});

View File

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

View File

@@ -54,6 +54,7 @@ module.exports = function(RED) {
if (msg.hasOwnProperty("reset")) {
clearTimeout(tout);
tout = null;
node.status({});
}
else {
if (!tout) {
@@ -64,14 +65,16 @@ module.exports = function(RED) {
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
else { msg.payload = node.op1; }
if (node.op1type !== "nul") { node.send(msg); }
if (node.duration === 0) { tout = "infinite"; }
if (node.duration === 0) { tout = 0; }
else {
tout = setTimeout(function() {
msg.payload = m2;
if (node.op2type !== "nul") { node.send(msg); }
tout = null;
node.status({});
},node.duration);
}
node.status({fill:"blue",shape:"dot",text:" "});
}
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
clearTimeout(tout);
@@ -79,12 +82,16 @@ module.exports = function(RED) {
msg.payload = m2;
if (node.op2type !== "nul") { node.send(msg); }
tout = null;
node.status({});
},node.duration);
}
}
});
this.on("close", function() {
if (tout) { clearTimeout(tout); }
if (tout) {
clearTimeout(tout);
node.status({});
}
});
}
RED.nodes.registerType("trigger",TriggerNode);

View File

@@ -73,6 +73,7 @@
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio in',{
category: 'Raspberry Pi',
label: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
@@ -211,6 +212,7 @@
var pinsInUse = {};
RED.nodes.registerType('rpi-gpio out',{
category: 'Raspberry Pi',
label: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },
@@ -330,9 +332,11 @@
</script>
<script type="text/x-red" data-help-name="rpi-mouse">
<p>Raspberry Pi mouse button node. Generates a <b>msg.payload</b> with
<p>Raspberry Pi mouse button node. Requires a USB mouse.</p>
<p>Generates a <b>msg.payload</b> with
either a 1 or 0 when the selected mouse button is pressed and released</p>
<p>Also sets <b>msg.button</b> to the code value, 1 = left, 2 = right, 4 = middle,
<p>Also sets <b>msg.button</b> 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>
</script>
@@ -340,6 +344,7 @@
<script type="text/javascript">
RED.nodes.registerType('rpi-mouse',{
category: 'Raspberry Pi',
label: 'Raspberry Pi',
color:"#c6dbef",
defaults: {
name: { value:"" },

View File

@@ -1,3 +1,4 @@
#!/bin/bash
#
# Copyright 2014 IBM Corp.
#

View File

@@ -1,12 +1,9 @@
<!--
Copyright 2013,2014 IBM Corp.
Copyright 2013,2015 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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.
@@ -117,24 +114,99 @@
</script>
<script type="text/x-red" data-template-name="mqtt-broker">
<div class="form-row node-input-broker">
<label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" >
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> <span data-i18n="mqtt.label.port"></span></label>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:45px">
</div>
<div class="form-row">
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label>
<input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid">
<ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-config-mqtt-broker-tabs"></ul>
</div>
<div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-config-input-user">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-config-input-password">
<div id="node-config-mqtt-broker-tabs-content" style="min-height: 170px;">
<div id="mqtt-broker-tab-connection" style="display:none">
<div class="form-row node-input-broker">
<label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" >
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> <span data-i18n="mqtt.label.port"></span></label>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:45px">
</div>
<div class="form-row">
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label>
<input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid">
</div>
<div class="form-row">
<label for="node-config-input-keepalive" style="width: auto"><i class="fa fa-clock-o"></i> <span data-i18n="mqtt.label.keepalive"></span></label>
<input type="text" id="node-config-input-keepalive" style="width: 50px">
<input type="checkbox" id="node-config-input-cleansession" style="margin-left: 30px; height: 1em;display: inline-block; width: auto; vertical-align: middle;">
<label for="node-config-input-cleansession" style="width: auto;" data-i18n="mqtt.label.cleansession"></label>
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-compatmode" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-compatmode" style="width: auto;" data-i18n="mqtt.label.compatmode"></label>
</div>
</div>
<div id="mqtt-broker-tab-security" style="display:none">
<div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-config-input-user">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-config-input-password">
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: 70%;" data-i18n="mqtt.label.use-tls"></label>
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-verifyservercert" style="width: 70%;" data-i18n="mqtt.label.verify-server-cert"></label>
</div>
</div>
<div id="mqtt-broker-tab-birth" style="display:none">
<div class="form-row">
<label for="node-config-input-birthTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-config-input-birthTopic" data-i18n="[placeholder]mqtt.placeholder.birth-topic">
</div>
<div class="form-row">
<label for="node-config-input-birthQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-config-input-birthQos" style="width:125px !important">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;<span data-i18n="mqtt.retain"></span> &nbsp;<select id="node-config-input-birthRetain" style="width:125px !important">
<option value="false" data-i18n="mqtt.false"></option>
<option value="true" data-i18n="mqtt.true"></option>
</select>
</div>
<div class="form-row">
<label for="node-config-input-birthPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-config-input-birthPayload" data-i18n="[placeholder]common.label.payload">
</div>
</div>
<div id="mqtt-broker-tab-will" style="display:none">
<div class="form-row">
<label for="node-config-input-willTopic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-config-input-willTopic" data-i18n="[placeholder]mqtt.placeholder.will-topic">
</div>
<div class="form-row">
<label for="node-config-input-willQos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-config-input-willQos" style="width:125px !important">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;<span data-i18n="mqtt.retain"></span> &nbsp;<select id="node-config-input-willRetain" style="width:125px !important">
<option value="false" data-i18n="mqtt.false"></option>
<option value="true" data-i18n="mqtt.true"></option>
</select>
</div>
<div class="form-row">
<label for="node-config-input-willPayload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-config-input-willPayload" data-i18n="[placeholder]common.label.payload">
</div>
</div>
</div>
</script>
<script type="text/javascript">
@@ -143,7 +215,27 @@
defaults: {
broker: {value:"",required:true},
port: {value:1883,required:true,validate:RED.validators.number()},
clientid: { value:"" }
clientid: { value:"", validate: function(v) {
if ($("#node-config-input-clientid").length) {
// Currently editing the node
return $("#node-config-input-cleansession").is(":checked") || v.length > 0;
} else {
return this.cleansession || v.length > 0;
}
}},
usetls: {value: false},
verifyservercert: { value: false},
compatmode: { value: true},
keepalive: {value:15,validate:RED.validators.number()},
cleansession: {value: true},
willTopic: {value:""},
willQos: {value:"0"},
willRetain: {value:false},
willPayload: {value:""},
birthTopic: {value:""},
birthQos: {value:"0"},
birthRetain: {value:false},
birthPayload: {value:""}
},
credentials: {
user: {type:"text"},
@@ -152,6 +244,87 @@
label: function() {
if (this.broker == "") { this.broker = "localhost"; }
return (this.clientid?this.clientid+"@":"")+this.broker+":"+this.port;
},
oneditprepare: function () {
var tabs = RED.tabs.create({
id: "node-config-mqtt-broker-tabs",
onchange: function(tab) {
$("#node-config-mqtt-broker-tabs-content").children().hide();
$("#" + tab.id).show();
}
});
tabs.addTab({
id: "mqtt-broker-tab-connection",
label: this._("mqtt.tabs-label.connection")
});
tabs.addTab({
id: "mqtt-broker-tab-security",
label: this._("mqtt.tabs-label.security")
});
tabs.addTab({
id: "mqtt-broker-tab-birth",
label: this._("mqtt.tabs-label.birth")
});
tabs.addTab({
id: "mqtt-broker-tab-will",
label: this._("mqtt.tabs-label.will")
});
setTimeout(function() { tabs.resize()},0);
if (typeof this.cleansession === 'undefined') {
this.cleansession = true;
$("#node-config-input-cleansession").prop("checked",true);
}
if (typeof this.usetls === 'undefined'){
this.usetls = false;
$("#node-config-input-usetls").prop("checked",false);
}
if (typeof this.verifyservercert === 'undefined'){
this.verifyservercert = true;
$("#node-config-input-verifyservercert").prop("checked",true);
}
if (typeof this.compatmode === 'undefined'){
this.compatmode = true;
$("#node-config-input-compatmode").prop('checked', true);
}
if (typeof this.keepalive === 'undefined'){
this.keepalive = 15;
$("#node-config-input-keepalive").val(this.keepalive);
}
if (typeof this.willQos === 'undefined') {
this.willQos = "0";
$("#node-config-input-willQos").val("0");
}
if (typeof this.birthQos === 'undefined') {
this.birthQos = "0";
$("#node-config-input-birthQos").val("0");
}
function updateTLSOptions() {
if ($("#node-config-input-usetls").is(':checked')) {
$("#node-config-input-verifyservercert").prop("disabled", false);
$("#node-config-input-verifyservercert").next().css("color","");
} else {
$("#node-config-input-verifyservercert").prop("disabled", true);
$("#node-config-input-verifyservercert").next().css("color","#aaa");
}
}
updateTLSOptions();
$("#node-config-input-usetls").on("click",function() {
updateTLSOptions();
});
var node = this;
function updateClientId() {
if ($("#node-config-input-cleansession").is(":checked")) {
$("#node-config-input-clientid").attr("placeholder",node._("mqtt.placeholder.clientid"));
} else {
$("#node-config-input-clientid").attr("placeholder",node._("mqtt.placeholder.clientid-nonclean"));
}
$("#node-config-input-clientid").change();
}
setTimeout(updateClientId,0);
$("#node-config-input-cleansession").on("click",function() {
updateClientId();
});
}
});
</script>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013,2014 IBM Corp.
* Copyright 2013,2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,268 @@
module.exports = function(RED) {
"use strict";
var connectionPool = require("./lib/mqttConnectionPool");
var mqtt = require("mqtt");
var util = require("util");
var events = require("events");
var isUtf8 = require('is-utf8');
function matchTopic(ts,t) {
if (ts == "#") {
return true;
}
var re = new RegExp("^"+ts.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
return re.test(t);
}
function MQTTBrokerNode(n) {
RED.nodes.createNode(this,n);
// Configuration options passed by Node Red
this.broker = n.broker;
this.port = n.port;
this.clientid = n.clientid;
this.usetls = n.usetls;
this.verifyservercert = n.verifyservercert;
this.compatmode = n.compatmode;
this.keepalive = n.keepalive;
this.cleansession = n.cleansession;
// Config node state
this.brokerurl = "";
this.connected = false;
this.connecting = false;
this.usecount = 0;
this.options = {};
this.queue = [];
this.subscriptions = {};
if (n.birthTopic) {
this.birthMessage = {
topic: n.birthTopic,
payload: n.birthPayload || "",
qos: Number(n.birthQos||0),
retain: n.birthRetain=="true"|| n.birthRetain===true
};
}
events.EventEmitter.call(this);
this.setMaxListeners(0);
if (this.credentials) {
this.username = this.credentials.user;
this.password = this.credentials.password;
}
// If the config node is missing certain options (it was probably deployed prior to an update to the node code),
// select/generate sensible options for the new fields
if (typeof this.usetls === 'undefined'){
this.usetls = false;
}
if (typeof this.compatmode === 'undefined'){
this.compatmode = true;
}
if (typeof this.verifyservercert === 'undefined'){
this.verifyservercert = false;
}
if (typeof this.keepalive === 'undefined'){
this.keepalive = 15;
} else if (typeof this.keepalive === 'string') {
this.keepalive = Number(this.keepalive);
}
if (typeof this.cleansession === 'undefined') {
this.cleansession = true;
}
// Create the URL to pass in to the MQTT.js library
if (this.brokerurl == "") {
if (this.usetls) {
this.brokerurl="mqtts://";
} else {
this.brokerurl="mqtt://";
}
if (this.broker != "") {
this.brokerurl = this.brokerurl+this.broker+":"+this.port;
} else {
this.brokerurl = this.brokerurl+"localhost:1883";
}
}
if (!this.cleansession && !this.clientid) {
this.cleansession = true;
this.warn(RED._("mqtt.errors.nonclean-missingclientid"));
}
// Build options for passing to the MQTT.js API
this.options.clientId = this.clientid || 'mqtt_' + (1+Math.random()*4294967295).toString(16);
this.options.username = this.username;
this.options.password = this.password;
this.options.keepalive = this.keepalive;
this.options.clean = this.cleansession;
this.options.reconnectPeriod = RED.settings.mqttReconnectTime||5000;
if (this.compatmode == "true" || this.compatmode === true){
this.options.protocolId = 'MQIsdp';
this.options.protocolVersion = 3;
}
this.options.rejectUnauthorized = (this.verifyservercert == "true" || this.verifyservercert === true)
if (n.willTopic) {
this.options.will = {
topic: n.willTopic,
payload: n.willPayload || "",
qos: Number(n.willQos||0),
retain: n.willRetain=="true"|| n.willRetain===true
};
}
// Define functions called by MQTT in and out nodes
var node = this;
this.register = function(){
node.usecount += 1;
};
this.deregister = function(){
node.usecount -= 1;
if (node.usecount == 0) {
node.client.end();
}
};
this.connect = function () {
if (!node.connected && !node.connecting) {
node.connecting = true;
node.client = mqtt.connect(node.brokerurl ,node.options);
// Register successful connect or reconnect handler
node.client.on('connect', function () {
node.connected = true;
node.log(RED._("mqtt.state.connected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
node.emit('connected');
// Remove any existing listeners before resubscribing to avoid duplicates in the event of a re-connection
node.client.removeAllListeners('message');
// Re-subscribe to stored topics
for (var s in node.subscriptions) {
var topic = s;
var qos = 0;
for (var r in node.subscriptions[s]) {
qos = Math.max(qos,node.subscriptions[s][r].qos);
node.client.on('message',node.subscriptions[s][r].handler);
}
var options = {qos: qos};
node.client.subscribe(topic, options);
}
// Send any birth message
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);
}
});
// Register disconnect handlers
node.client.on('close', function () {
if (node.connected) {
node.connected = false;
node.log(RED._("mqtt.state.disconnected",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
node.emit('disconnected');
} else if (node.connecting) {
node.log(RED._("mqtt.state.connect-failed",{broker:(node.clientid?node.clientid+"@":"")+node.brokerurl}));
}
});
// Register connect error handler
node.client.on('error', function (error) {
console.log("ERROR",error);
if (node.connecting) {
node.client.end();
node.connecting = false;
}
});
}
};
this.subscribe = function (topic,qos,callback,ref) {
ref = ref||0;
node.subscriptions[topic] = node.subscriptions[topic]||{};
var sub = {
topic:topic,
qos:qos,
handler:function(mtopic,mpayload, mpacket) {
if (matchTopic(topic,mtopic)) {
callback(mtopic,mpayload, mpacket);
}
},
ref: ref
};
node.subscriptions[topic][ref] = sub;
if (node.connected) {
node.client.on('message',sub.handler);
var options = {};
options.qos = qos;
node.client.subscribe(topic, options);
}
};
this.unsubscribe = function (topic, ref) {
ref = ref||0;
var sub = node.subscriptions[topic];
if (sub) {
if (sub[ref]) {
node.client.removeListener('message',sub[ref].handler);
delete sub[ref];
}
if (Object.keys(sub).length == 0) {
delete node.subscriptions[topic];
if (node.connected){
node.client.unsubscribe(topic);
}
}
}
};
this.publish = function (msg) {
if (node.connected) {
if (!Buffer.isBuffer(msg.payload)) {
if (typeof msg.payload === "object") {
msg.payload = JSON.stringify(msg.payload);
} else if (typeof msg.payload !== "string") {
msg.payload = "" + msg.payload;
}
}
var options = {
qos: msg.qos || 0,
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(closecomplete) {
if (this.connected) {
this.on('disconnected', function() {
closecomplete();
});
this.client.end();
} else {
closecomplete();
}
});
}
util.inherits(MQTTBrokerNode, events.EventEmitter);
RED.nodes.registerType("mqtt-broker",MQTTBrokerNode,{
credentials: {
user: {type:"text"},
@@ -40,30 +289,30 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
this.topic = n.topic;
this.broker = n.broker;
this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) {
this.brokerConn = RED.nodes.getNode(this.broker);
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this;
node.brokerConn.register();
if (this.topic) {
this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
this.brokerConn.subscribe(this.topic,2,function(topic,payload,packet) {
if (isUtf8(payload)) { payload = payload.toString(); }
var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
if ((node.brokerConfig.broker === "localhost")||(node.brokerConfig.broker === "127.0.0.1")) {
var msg = {topic:topic,payload:payload, qos: packet.qos, retain: packet.retain};
if ((node.brokerConn.broker === "localhost")||(node.brokerConn.broker === "127.0.0.1")) {
msg._topic = topic;
}
node.send(msg);
}, this.id);
this.client.on("connectionlost",function() {
this.brokerConn.on("disconnected",function() {
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
});
this.client.on("connect",function() {
this.brokerConn.on("connected",function() {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
if (this.client.isConnected()) {
if (this.brokerConn.connected) {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
} else {
this.client.connect();
this.brokerConn.connect();
}
}
else {
@@ -73,9 +322,9 @@ module.exports = function(RED) {
this.error(RED._("mqtt.errors.missing-config"));
}
this.on('close', function() {
if (this.client) {
this.client.unsubscribe(this.topic,this.id);
this.client.disconnect();
if (this.brokerConn) {
this.brokerConn.unsubscribe(this.topic,this.id);
node.brokerConn.deregister();
}
});
}
@@ -87,12 +336,12 @@ module.exports = function(RED) {
this.qos = n.qos || null;
this.retain = n.retain;
this.broker = n.broker;
this.brokerConfig = RED.nodes.getNode(this.broker);
this.brokerConn = RED.nodes.getNode(this.broker);
if (this.brokerConfig) {
if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this;
node.brokerConn.register();
this.on("input",function(msg) {
if (msg.qos) {
msg.qos = parseInt(msg.qos);
@@ -108,30 +357,28 @@ module.exports = function(RED) {
}
if ( msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
this.client.publish(msg); // send the message
this.brokerConn.publish(msg); // send the message
}
else { node.warn(RED._("mqtt.errors.invalid-topic")); }
}
});
this.client.on("connectionlost",function() {
this.brokerConn.on("disconnected",function() {
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
});
this.client.on("connect",function() {
this.brokerConn.on("connected",function() {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
if (this.client.isConnected()) {
if (this.brokerConn.connected) {
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
} else {
this.client.connect();
this.brokerConn.connect();
}
} else {
this.error(RED._("mqtt.errors.missing-config"));
}
this.on('close', function() {
if (this.client) {
this.client.disconnect();
}
node.brokerConn.deregister();
});
}
RED.nodes.registerType("mqtt out",MQTTOutNode);
}
};

View File

@@ -33,7 +33,7 @@
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row row-swagger-doc">
<label for="node-input-swaggerDoc"><i class="fa fa-tag"></i> <span data-i18n="httpin.label.doc"></span></label>
<label for="node-input-swaggerDoc"><i class="fa fa-file-text-o"></i> <span data-i18n="httpin.label.doc"></span></label>
<input type="text" id="node-input-swaggerDoc">
</div>
<div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div>

View File

@@ -19,35 +19,155 @@ module.exports = function(RED) {
var http = require("follow-redirects").http;
var https = require("follow-redirects").https;
var urllib = require("url");
var express = require("express");
var bodyParser = require("body-parser");
var getBody = require('raw-body');
var mustache = require("mustache");
var querystring = require("querystring");
var cors = require('cors');
var jsonParser = express.json();
var urlencParser = express.urlencoded();
var jsonParser = bodyParser.json();
var urlencParser = bodyParser.urlencoded({extended:true});
var onHeaders = require('on-headers');
var typer = require('media-typer');
var isUtf8 = require('is-utf8');
function rawBodyParser(req, res, next) {
if (req._body) { return next(); }
req.body = "";
req._body = true;
var isText = true;
var checkUTF = false;
if (req.headers['content-type']) {
var parsedType = typer.parse(req.headers['content-type'])
if (parsedType.type === "text") {
isText = true;
} else if (parsedType.subtype === "xml" || parsedType.suffix === "xml") {
isText = true;
} else if (parsedType.type !== "application") {
isText = false;
} else if (parsedType.subtype !== "octet-stream") {
checkUTF = true;
}
}
getBody(req, {
limit: '1mb',
length: req.headers['content-length'],
encoding: 'utf8'
encoding: isText ? "utf8" : null
}, function (err, buf) {
if (err) { return next(err); }
if (!isText && checkUTF && isUtf8(buf)) {
buf = buf.toString()
}
req.body = buf;
next();
});
}
var corsSetup = false;
function createRequestWrapper(node,req) {
var wrapper = {
_req: req
};
var toWrap = [
"param",
"get",
"is",
"acceptsCharset",
"acceptsLanguage",
"app",
"baseUrl",
"body",
"cookies",
"fresh",
"hostname",
"ip",
"ips",
"originalUrl",
"params",
"path",
"protocol",
"query",
"route",
"secure",
"signedCookies",
"stale",
"subdomains",
"xhr",
"socket" // TODO: tidy this up
];
toWrap.forEach(function(f) {
if (typeof req[f] === "function") {
wrapper[f] = function() {
node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.req."+f}));
var result = req[f].apply(req,arguments);
if (result === res) {
return wrapper;
} else {
return result;
}
}
} else {
wrapper[f] = req[f];
}
});
return wrapper;
}
function createResponseWrapper(node,res) {
var wrapper = {
_res: res
};
var toWrap = [
"append",
"attachment",
"cookie",
"clearCookie",
"download",
"end",
"format",
"get",
"json",
"jsonp",
"links",
"location",
"redirect",
"render",
"send",
"sendfile",
"sendFile",
"sendStatus",
"set",
"status",
"type",
"vary"
];
toWrap.forEach(function(f) {
wrapper[f] = function() {
node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.res."+f}));
var result = res[f].apply(res,arguments);
if (result === res) {
return wrapper;
} else {
return result;
}
}
});
return wrapper;
}
function HTTPIn(n) {
RED.nodes.createNode(this,n);
if (RED.settings.httpNodeRoot !== false) {
if (!n.url) {
this.warn(RED._("httpin.errors.missing-path"));
return;
}
this.url = n.url;
this.method = n.method;
this.swaggerDoc = n.swaggerDoc;
@@ -56,26 +176,27 @@ module.exports = function(RED) {
this.errorHandler = function(err,req,res,next) {
node.warn(err);
res.send(500);
res.sendStatus(500);
};
this.callback = function(req,res) {
var msgid = RED.util.generateId();
res._msgid = msgid;
if (node.method.match(/(^post$|^delete$|^put$|^options$)/)) {
node.send({_msgid:msgid,req:req,res:res,payload:req.body});
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body});
} else if (node.method == "get") {
node.send({_msgid:msgid,req:req,res:res,payload:req.query});
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.query});
} else {
node.send({_msgid:msgid,req:req,res:res});
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res)});
}
};
var corsHandler = function(req,res,next) { next(); }
if (RED.settings.httpNodeCors) {
if (RED.settings.httpNodeCors && !corsSetup) {
corsHandler = cors(RED.settings.httpNodeCors);
RED.httpNode.options(this.url,corsHandler);
RED.httpNode.options("*",corsHandler);
corsSetup = true;
}
var httpMiddleware = function(req,res,next) { next(); }
@@ -117,24 +238,12 @@ module.exports = function(RED) {
}
this.on("close",function() {
var routes = RED.httpNode.routes[this.method];
for (var i = 0; i<routes.length; i++) {
if (routes[i].path == this.url) {
var node = this;
RED.httpNode._router.stack.forEach(function(route,i,routes) {
if (route.route && route.route.path === node.url && route.route.methods[node.method]) {
routes.splice(i,1);
//break;
}
}
if (RED.settings.httpNodeCors) {
var routes = RED.httpNode.routes['options'];
if (routes) {
for (var j = 0; j<routes.length; j++) {
if (routes[j].path == this.url) {
routes.splice(j,1);
//break;
}
}
}
}
});
});
} else {
this.warn(RED._("httpin.errors.not-created"));
@@ -149,13 +258,13 @@ module.exports = function(RED) {
this.on("input",function(msg) {
if (msg.res) {
if (msg.headers) {
msg.res.set(msg.headers);
msg.res._res.set(msg.headers);
}
var statusCode = msg.statusCode || 200;
if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
msg.res.jsonp(statusCode,msg.payload);
msg.res._res.status(statusCode).jsonp(msg.payload);
} else {
if (msg.res.get('content-length') == null) {
if (msg.res._res.get('content-length') == null) {
var len;
if (msg.payload == null) {
len = 0;
@@ -166,10 +275,10 @@ module.exports = function(RED) {
} else {
len = Buffer.byteLength(msg.payload);
}
msg.res.set('content-length', len);
msg.res._res.set('content-length', len);
}
msg.res.send(statusCode,msg.payload);
msg.res._res.status(statusCode).send(msg.payload);
}
} else {
node.warn(RED._("httpin.errors.no-response"));
@@ -214,7 +323,7 @@ module.exports = function(RED) {
var method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
node.warn(RED._("httpin.errors.not-overridden"));
node.warn(RED._("common.errors.nooverride"));
}
if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter
@@ -282,6 +391,7 @@ module.exports = function(RED) {
opts = urllib.parse(prox);
opts.path = opts.pathname = path;
opts.headers = heads;
opts.method = method;
//console.log(opts);
urltotest = match[0];
}

View File

@@ -17,7 +17,7 @@
module.exports = function(RED) {
"use strict";
var ws = require("ws");
var inspect = require("sys").inspect;
var inspect = require("util").inspect;
// A node red node that sets up a local websocket server
function WebSocketListenerNode(n) {
@@ -48,7 +48,7 @@ module.exports = function(RED) {
if (!node.isServer) { node.emit('opened',''); }
});
socket.on('close',function() {
if (node.isServer) { delete node._clients[id]; node.emit('opened',Object.keys(node._clients).length); }
if (node.isServer) { delete node._clients[id]; node.emit('closed',Object.keys(node._clients).length); }
else { node.emit('closed'); }
if (!node.closing && !node.isServer) {
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
@@ -115,7 +115,7 @@ module.exports = function(RED) {
else {
node.closing = true;
node.server.close();
if (node.tout) { clearTimeout(tout); }
if (node.tout) { clearTimeout(node.tout); }
}
});
}
@@ -126,6 +126,14 @@ module.exports = function(RED) {
this._inputNodes.push(handler);
}
WebSocketListenerNode.prototype.removeInputNode = function(/*Node*/handler) {
this._inputNodes.forEach(function(node, i, inputNodes) {
if (node === handler) {
inputNodes.splice(i, 1);
}
});
}
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
var msg;
if (this.wholemsg) {
@@ -187,6 +195,11 @@ module.exports = function(RED) {
} else {
this.error(RED._("websocket.errors.missing-conf"));
}
this.on('close', function() {
node.serverConfig.removeInputNode(node);
});
}
RED.nodes.registerType("websocket in",WebSocketInNode);

View File

@@ -125,10 +125,12 @@ module.exports = function(RED) {
});
} else {
var server = net.createServer(function (socket) {
socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var id = (1+Math.random()*4294967295).toString(16);
connectionPool[id] = socket;
node.status({text:RED._("tcpin.status.connections",{count:++count})});
count++;
node.status({text:RED._("tcpin.status.connections",{count:count})});
var buffer = (node.datatype == 'buffer')? new Buffer(0):"";
socket.on('data', function (data) {
@@ -175,7 +177,8 @@ module.exports = function(RED) {
});
socket.on('close', function() {
delete connectionPool[id];
node.status({text:RED._("tcpin.status.connections",{count:--count})});
count--;
node.status({text:RED._("tcpin.status.connections",{count:count})});
});
socket.on('error',function(err) {
node.log(err);
@@ -303,11 +306,12 @@ module.exports = function(RED) {
var connectedSockets = [];
node.status({text:RED._("tcpin.status.connections",{count:0})});
var server = net.createServer(function (socket) {
socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.push(socket);
node.status({text:connectedSockets.length+" "+"tcpin.status.connections"});
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
socket.on('timeout', function() {
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
socket.end();
@@ -393,7 +397,7 @@ module.exports = function(RED) {
}
if (!node.connected) {
client = net.Socket();
client.setTimeout(socketTimeout);
if (socketTimeout) { client.setTimeout(socketTimeout); }
//node.status({});
var host = node.server || msg.host;
var port = node.port || msg.port;
@@ -432,7 +436,7 @@ module.exports = function(RED) {
m = new Buffer(i+1);
buf.copy(m,0,0,i+1);
node.send({"payload":m});
if (client) { client.end(); }
//if (client) { client.end(); }
}, node.splitc);
i = 0;
buf[0] = data[j];
@@ -445,7 +449,8 @@ module.exports = function(RED) {
if ( i >= node.splitc) {
m = new Buffer(i);
buf.copy(m,0,0,i);
if (client) { client.end(); }
node.send({"payload":m});
//if (client) { client.end(); }
i = 0;
}
}
@@ -456,7 +461,8 @@ module.exports = function(RED) {
if (data[j] == node.splitc) {
m = new Buffer(i);
buf.copy(m,0,0,i);
if (client) { client.end(); }
node.send({"payload":m});
//if (client) { client.end(); }
i = 0;
}
}
@@ -472,6 +478,7 @@ module.exports = function(RED) {
});
client.on('close', function() {
node.status({});
if (node.done) { node.done(); }
});
@@ -501,7 +508,7 @@ module.exports = function(RED) {
node.done = done;
if (client) {
buf = null;
client.end();
client.destroy();
}
if (!node.connected) { done(); }
});

View File

@@ -16,7 +16,7 @@
var util = require("util");
var mqtt = require("mqtt");
var events = require("events");
//var inspect = require("sys").inspect;
//var inspect = require("util").inspect;
//var Client = module.exports.Client = function(

View File

@@ -68,7 +68,36 @@
}
},
"catch": {
"catch": "catch"
"catch": "catch all",
"catchNodes": "catch (__number__)",
"label": {
"source": "Catch errors from",
"node": "node",
"type": "type",
"selectAll": "select all",
"sortByLabel": "sort by label",
"sortByType": "sort by type"
},
"scope": {
"all": "all nodes",
"selected": "selected nodes"
}
},
"status": {
"status": "status (all)",
"statusNodes": "status (__number__)",
"label": {
"source": "Report status from",
"node": "node",
"type": "type",
"selectAll": "select all",
"sortByLabel": "sort by label",
"sortByType": "sort by type"
},
"scope": {
"all": "all nodes",
"selected": "selected nodes"
}
},
"debug": {
"output": "Output",
@@ -182,13 +211,32 @@
},
"mqtt": {
"label": {
"broker": "Broker",
"broker": "Server",
"qos": "QoS",
"clientid": "Client ID",
"port": "Port"
"port": "Port",
"keepalive": "Keep alive time (s)",
"cleansession": "Use clean session",
"use-tls": "Enable secure (SSL/TLS) connection",
"verify-server-cert":"Verify server certificate",
"compatmode": "Use legacy MQTT 3.1 support"
},
"tabs-label": {
"connection": "Connection",
"security": "Security",
"will": "Will Message",
"birth": "Birth Message"
},
"placeholder": {
"clientid": "Leave blank for auto generated"
"clientid": "Leave blank for auto generated",
"clientid-nonclean":"Must be set for non-clean sessions",
"will-topic": "Leave blank to disable will message",
"birth-topic": "Leave blank to disable birth message"
},
"state": {
"connected": "Connected to broker: __broker__",
"disconnected": "Disconnected from broker: __broker__",
"connect-failed": "Connection failed to broker: __broker__"
},
"retain": "Retain",
"true": "true",
@@ -197,7 +245,8 @@
"errors": {
"not-defined": "topic not defined",
"missing-config": "missing broker configuration",
"invalid-topic": "Invalid topic specified"
"invalid-topic": "Invalid topic specified",
"nonclean-missingclientid": "No client ID set, using clean session"
}
},
"httpin": {
@@ -220,9 +269,11 @@
"httpreq": "http request",
"errors": {
"not-created": "Cannot create http-in node when httpNodeRoot set to false",
"missing-path": "missing path",
"no-response": "No response object",
"json-error": "JSON parse error",
"no-url": "No url specified"
"no-url": "No url specified",
"deprecated-call":"Deprecated call to __method__"
},
"status": {
"requesting": "requesting"
@@ -374,8 +425,10 @@
"property": "Property",
"rule": "rule"
},
"and": "and",
"checkall": "checking all rules",
"stopfirst": "stopping after first match",
"ignorecase": "ignore case",
"rules": {
"btwn":"is between",
"cont":"contains",
@@ -467,7 +520,8 @@
"windows": "Windows (\\r\\n)"
},
"errors": {
"csv_js": "This node only handles csv strings or js objects."
"csv_js": "This node only handles csv strings or js objects.",
"obj_csv": "No columns template specified for object -> csv."
}
},
"html": {
@@ -488,7 +542,8 @@
"json": {
"errors": {
"dropped-object": "Ignored non-object payload",
"dropped": "Ignored unsupported payload type"
"dropped": "Ignored unsupported payload type",
"dropped-error": "Failed to convert payload"
}
},
"xml": {

View File

@@ -82,10 +82,42 @@
{v:"else",t:this._("switch.rules.else")}
];
var andLabel = this._("switch.and");
var caseLabel = this._("switch.ignorecase");
function resizeRule(rule,width) {
var selectField = rule.find("select");
var type = selectField.children("option:selected").val();
var valueField = rule.find(".node-input-rule-value");
var btwnField1 = rule.find(".node-input-rule-btwn-value");
var btwnField2 = rule.find(".node-input-rule-btwn-value2");
var selectWidth;
if (type.length < 4) {
selectWidth = 60;
} else if (type === "regex") {
selectWidth = 147;
} else {
selectWidth = 120;
}
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);
} else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
// valueField.hide();
} else {
valueField.width(width-selectWidth-120);
}
}
}
function generateRule(i,rule) {
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
var row = $('<div/>').appendTo(container);
var row2 = $('<div/>',{style:"padding-top: 5px; text-align: right;"}).appendTo(container);
var row2 = $('<div/>',{style:"padding-top: 5px; padding-left: 175px;"}).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);
@@ -96,7 +128,7 @@
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);
btwnField.append(" and ");
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 finalspan = $('<span/>',{style:"float: right;margin-right: 10px;"}).appendTo(row);
@@ -105,15 +137,13 @@
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);
var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2);
$('<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();
if (type.length < 4) {
selectField.css({"width":"60px"});
} else if (type === "regex") {
selectField.css({"width":"147px"});
} else {
selectField.css({"width":"120px"});
}
if (type === "btwn") {
valueField.hide();
btwnField.show();
@@ -125,9 +155,13 @@
valueField.show();
}
}
if (type === "regex") {
row2.show();
} else {
row2.hide();
}
});
deleteButton.click(function() {
container.css({"background":"#fee"});
container.fadeOut(300, function() {
@@ -148,6 +182,11 @@
} else if (typeof rule.v != "undefined") {
valueField.val(rule.v);
}
if (rule.case) {
caseSensitive.prop('checked',true);
} else {
caseSensitive.prop('checked',false);
}
selectField.change();
}
@@ -170,6 +209,12 @@
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({
@@ -192,6 +237,8 @@
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
switchDialogResize();
} else {
setTimeout(switchDialogResize,10);
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {
@@ -214,6 +261,9 @@
} else {
r.v = rule.find(".node-input-rule-value").val();
}
if (type === "regex") {
r.case = rule.find(".node-input-rule-case").prop("checked");
}
}
node.rules.push(r);
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2013 IBM Corp.
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ module.exports = function(RED) {
'gte': function(a, b) { return a >= b; },
'btwn': function(a, b, c) { return a >= b && a <= c; },
'cont': function(a, b) { return (a + "").indexOf(b) != -1; },
'regex': function(a, b) { return (a + "").match(new RegExp(b)); },
'regex': function(a, b, c, d) { return (a + "").match(new RegExp(b,d?'i':'')); },
'true': function(a) { return a === true; },
'false': function(a) { return a === false; },
'null': function(a) { return (typeof a == "undefined" || a === null); },
@@ -60,7 +60,7 @@ module.exports = function(RED) {
var rule = node.rules[i];
var test = prop;
if (rule.t == "else") { test = elseflag; elseflag = true; }
if (operators[rule.t](test,rule.v, rule.v2)) {
if (operators[rule.t](test,rule.v, rule.v2, rule.case)) {
onward.push(msg);
elseflag = false;
if (node.checkall == "false") { break; }

View File

@@ -102,6 +102,10 @@
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) {
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
@@ -143,8 +147,9 @@
selectField.change(function() {
var width = $("#node-input-rule-container").width();
resizeRule(container,width);
var type = $(this).val();
if (type == "set") {
row2.show();
row3.hide();
@@ -213,6 +218,12 @@
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) {
@@ -221,6 +232,8 @@
$("#dialog").dialog('option','width',size.width);
$("#dialog").dialog('option','height',size.height);
changeDialogResize();
} else {
setTimeout(changeDialogResize,10);
}
});
$( "#dialog" ).one("dialogclose", function(ev,ui) {

View File

@@ -1,6 +1,6 @@
<!--
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.
@@ -68,12 +68,15 @@
</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 <b>msg.payload</b> 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>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 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>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>
<p><b>Note:</b> the columns should always be specified comma separated - even if another separator is chosen for the data.</p>
</script>

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.
@@ -55,25 +55,50 @@ module.exports = function(RED) {
}
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
for (var s = 0; s < msg.payload.length; s++) {
for (var t=0; t < node.template.length; t++) {
// aaargh - resorting to eval here - but fairly contained front and back.
var p = RED.util.ensureString(eval("msg.payload[s]."+node.template[t]));
if (p === "undefined") { p = ""; }
if (p.indexOf(node.sep) != -1) { // add quotes if any "commas"
ou += node.quo + p + node.quo + node.sep;
if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
for (var t = 0; t < msg.payload[s].length; t++) {
if (!msg.payload[s][t]) { msg.payload[s][t] = ""; }
if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas"
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
}
ou += msg.payload[s].join(node.sep) + node.ret;
}
else {
if ((node.template.length === 1) && (node.template[0] === '')) {
node.warn(RED._("csv.errors.obj_csv"));
}
else {
for (var t=0; t < node.template.length; t++) {
if (node.template[t] === '') {
ou += node.sep;
}
else {
// aaargh - resorting to eval here - but fairly contained front and back.
var p = RED.util.ensureString(eval("msg.payload[s]."+node.template[t]));
if (p === "undefined") { p = ""; }
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
ou += node.quo + p + node.quo + node.sep;
}
else if (p.indexOf(node.sep) !== -1) { // add quotes if any "commas"
ou += node.quo + p + node.quo + node.sep;
}
else { ou += p + node.sep; } // otherwise just add
}
}
ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
}
else if (p.indexOf(node.quo) != -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
ou += node.quo + p + node.quo + node.sep;
}
else { ou += p + node.sep; } // otherwise just add
}
ou = ou.slice(0,-1) + node.ret; // remove final "comma" and add "newline"
}
msg.payload = ou;
node.send(msg);
if (msg.payload !== '') { node.send(msg); }
}
catch(e) { node.error(e,msg); }
}

View File

@@ -31,9 +31,14 @@ module.exports = function(RED) {
catch(e) { node.error(e.message,msg); }
}
else if (typeof msg.payload === "object") {
if ((!Buffer.isBuffer(msg.payload)) && (!util.isArray(msg.payload))) {
msg.payload = JSON.stringify(msg.payload);
node.send(msg);
if (!Buffer.isBuffer(msg.payload)) {
try {
msg.payload = JSON.stringify(msg.payload);
node.send(msg);
}
catch(e) {
node.error(RED._("json.errors.dropped-error"));
}
}
else { node.warn(RED._("json.errors.dropped-object")); }
}

View File

@@ -23,10 +23,10 @@
</div>
<div id="advanced-options">
<div class="form-row">
<i class="fa fa-key"></i> <span data-i18n="xml.label.represent"></span> <input type="text" id="node-input-attr" style="width:20px !important">
<i class="fa fa-key"></i> <span data-i18n="xml.label.represent"></span> <input type="text" id="node-input-attr" style="width:20px !important" placeholder="$">
</div>
<div class="form-row">
<i class="fa fa-key"></i> <span data-i18n="xml.label.prefix"></span> <input type="text" id="node-input-chr" style="width:20px !important">
<i class="fa fa-key"></i> <span data-i18n="xml.label.prefix"></span> <input type="text" id="node-input-chr" style="width:20px !important" placeholder="_">
</div>
<div class="form-tips"><span data-i18n="xml.tip"></span></div>
</div>
@@ -36,7 +36,10 @@
<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>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>See <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a> for more information.</p>
<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>
for more information.</p>
<p>If set, options in the edit dialogue override those passed in on the msg.options object.</p>
</script>
<script type="text/javascript">
@@ -45,8 +48,8 @@
color:"#DEBD5C",
defaults: {
name: {value:""},
attr: {value:'$',required:true},
chr: {value:'_',required:true}
attr: {value:""},
chr: {value:""}
},
inputs:1,
outputs:1,

View File

@@ -22,17 +22,25 @@ module.exports = function(RED) {
function XMLNode(n) {
RED.nodes.createNode(this,n);
this.attrkey = n.attr || '$';
this.charkey = n.chr || '_';
this.attrkey = n.attr;
this.charkey = n.chr;
var node = this;
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") {
msg.payload = builder.buildObject(msg.payload);
if (typeof msg.payload === "object") {
var options = {};
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
options.async = false;
msg.payload = builder.buildObject(msg.payload, options);
node.send(msg);
}
else if (typeof msg.payload == "string") {
parseString(msg.payload, {strict:true,async:true,attrkey:node.attrkey,charkey:node.charkey}, function (err, result) {
var options = {};
if (msg.hasOwnProperty("options") && typeof msg.options === "object") { options = msg.options; }
options.async = true;
options.attrkey = node.attrkey || options.attrkey || '$';
options.charkey = node.charkey || options.charkey || '_';
parseString(msg.payload, options, function (err, result) {
if (err) { node.error(err, msg); }
else {
msg.payload = result;

View File

@@ -1,6 +1,6 @@
{
"name" : "node-red",
"version" : "0.11.0",
"version" : "0.12.1",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache-2.0",
@@ -25,32 +25,35 @@
"editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm", "flow"
],
"dependencies": {
"express": "3.20.3",
"when": "3.7.3",
"bcryptjs": "2.2.0",
"nopt": "3.0.3",
"mqtt": "0.3.x",
"ws": "0.7.2",
"fs-extra": "0.22.1",
"express": "4.13.3",
"body-parser": "1.14.1",
"when": "3.7.4",
"bcryptjs": "2.3.0",
"nopt": "3.0.4",
"mqtt": "1.4.3",
"ws": "0.8.0",
"fs-extra": "0.26.0",
"clone": "1.0.2",
"mustache": "2.1.2",
"mustache": "2.2.0",
"cron":"1.0.9",
"raw-body":"2.1.2",
"xml2js":"0.4.9",
"raw-body":"2.1.4",
"xml2js":"0.4.13",
"sentiment":"0.2.3",
"follow-redirects":"0.0.6",
"follow-redirects":"0.0.7",
"cors":"2.7.1",
"cheerio":"0.19.0",
"uglify-js":"2.4.23",
"on-headers":"1.0.0",
"uglify-js":"2.5.0",
"on-headers":"1.0.1",
"is-utf8":"0.2.0",
"media-typer": "0.3.0",
"fs.notify":"0.0.4",
"passport":"0.2.2",
"passport":"0.3.0",
"passport-http-bearer":"1.0.1",
"passport-oauth2-client-password":"0.1.2",
"oauth2orize":"1.0.1",
"i18next":"1.10.2",
"semver": "5.0.1",
"oauth2orize":"1.1.0",
"i18next":"1.10.5",
"semver": "5.0.3",
"basic-auth": "1.0.3",
"node-red-node-feedparser":"0.1.*",
"node-red-node-email":"0.1.*",
"node-red-node-twitter":"0.1.*",
@@ -58,28 +61,28 @@
},
"optionalDependencies": {
"node-red-node-serialport":"0.0.*",
"bcrypt":"0.8.3"
"bcrypt":"0.8.5"
},
"devDependencies": {
"grunt": "0.4.5",
"grunt-chmod": "1.0.3",
"grunt-chmod": "1.1.1",
"grunt-cli": "0.1.13",
"grunt-concurrent":"2.0.0",
"grunt-concurrent":"2.0.3",
"grunt-contrib-clean":"0.6.0",
"grunt-contrib-compress": "0.13.0",
"grunt-contrib-compress": "0.14.0",
"grunt-contrib-concat":"0.5.1",
"grunt-contrib-copy": "0.8.0",
"grunt-contrib-jshint": "0.11.2",
"grunt-contrib-uglify": "0.9.1",
"grunt-contrib-copy": "0.8.2",
"grunt-contrib-jshint": "0.11.3",
"grunt-contrib-uglify": "0.9.2",
"grunt-contrib-watch":"0.6.1",
"grunt-jsonlint":"1.0.4",
"grunt-jsonlint":"1.0.5",
"grunt-nodemon":"0.4.0",
"grunt-sass":"1.0.0",
"grunt-sass":"1.1.0",
"grunt-simple-mocha": "0.4.0",
"mocha": "2.2.5",
"mocha": "2.3.3",
"should": "6.0.3",
"sinon": "1.15.4",
"supertest": "1.0.1"
"sinon": "1.17.2",
"supertest": "1.1.0"
},
"engines": {
"node": ">=0.8 <=0.12"

52
red.js
View File

@@ -21,7 +21,7 @@ var express = require("express");
var crypto = require("crypto");
var nopt = require("nopt");
var path = require("path");
var fs = require("fs");
var fs = require("fs-extra");
var RED = require("./red/red.js");
var log = require("./red/log");
@@ -76,13 +76,22 @@ if (parsedArgs.settings) {
// NODE_RED_HOME contains user data - use its settings.js
settingsFile = path.join(process.env.NODE_RED_HOME,"settings.js");
} else {
var userSettingsFile = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,".node-red","settings.js");
var userDir = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE,".node-red");
var userSettingsFile = path.join(userDir,"settings.js");
if (fs.existsSync(userSettingsFile)) {
// $HOME/.node-red/settings.js exists
settingsFile = userSettingsFile;
} else {
// Use default settings.js
settingsFile = __dirname+"/settings.js";
var defaultSettings = path.join(__dirname,"settings.js");
var settingsStat = fs.statSync(defaultSettings);
if (settingsStat.mtime.getTime() < settingsStat.ctime.getTime()) {
// Default settings file has not been modified - safe to copy
fs.copySync(defaultSettings,userSettingsFile);
settingsFile = userSettingsFile;
} else {
// Use default settings.js as it has been modified
settingsFile = defaultSettings;
}
}
}
}
@@ -91,8 +100,11 @@ try {
var settings = require(settingsFile);
settings.settingsFile = settingsFile;
} catch(err) {
console.log("Error loading settings file: "+settingsFile)
if (err.code == 'MODULE_NOT_FOUND') {
console.log("Unable to load settings file: "+settingsFile);
if (err.toString().indexOf(settingsFile) === -1) {
console.log(err.toString());
}
} else {
console.log(err);
}
@@ -166,21 +178,25 @@ try {
process.exit(1);
}
function basicAuthMiddleware(user,pass) {
var basicAuth = require('basic-auth');
return function(req,res,next) {
var requestUser = basicAuth(req);
if (!requestUser || requestUser.name !== user || crypto.createHash('md5').update(requestUser.pass,'utf8').digest('hex') !== pass) {
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.sendStatus(401);
}
next();
}
}
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
RED.log.warn(log._("server.httpadminauth-deprecated"));
app.use(settings.httpAdminRoot,
express.basicAuth(function(user, pass) {
return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass;
})
);
app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass));
}
if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
app.use(settings.httpNodeRoot,
express.basicAuth(function(user, pass) {
return user === settings.httpNodeAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpNodeAuth.pass;
})
);
app.use(settings.httpNodeRoot,basicAuthMiddleware(settings.httpNodeAuth.user,settings.httpNodeAuth.pass));
}
if (settings.httpAdminRoot !== false) {
app.use(settings.httpAdminRoot,RED.httpAdmin);
@@ -192,11 +208,7 @@ if (settings.httpNodeRoot !== false) {
if (settings.httpStatic) {
settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth;
if (settings.httpStaticAuth) {
app.use("/",
express.basicAuth(function(user, pass) {
return user === settings.httpStaticAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpStaticAuth.pass;
})
);
app.use("/",basicAuthMiddleware(settings.httpStaticAuth.user,settings.httpStaticAuth.pass));
}
app.use("/",express.static(settings.httpStatic));
}

View File

@@ -55,7 +55,7 @@ function needsPermission(permission) {
return next();
}
log.audit({event: "permission.fail"},req);
return res.send(401);
return res.status(401).end();
});
} else {
next();
@@ -95,7 +95,7 @@ function revoke(req,res) {
// TODO: audit log
Tokens.revoke(token).then(function() {
log.audit({event: "auth.login.revoke"},req);
res.send(200);
res.status(200).end();
});
}

48
red/api/credentials.js Normal file
View File

@@ -0,0 +1,48 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var log = require("../log");
var api = require("../nodes");
module.exports = {
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
// registry module that knows about node types.
var nodeType = req.params.type;
var nodeID = req.params.id;
log.audit({event: "credentials.get",type:nodeType,id:nodeID},req);
var credentials = api.getCredentials(nodeID);
if (!credentials) {
res.json({});
return;
}
var definition = api.getCredentialDefinition(nodeType);
var sendCredentials = {};
for (var cred in definition) {
if (definition.hasOwnProperty(cred)) {
if (definition[cred].type == "password") {
var key = 'has_' + cred;
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
continue;
}
sendCredentials[cred] = credentials[cred] || '';
}
}
res.json(sendCredentials);
}
}

View File

@@ -29,11 +29,11 @@ module.exports = {
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
log.audit({event: "flows.set",type:deploymentType},req);
redNodes.setFlows(flows,deploymentType).then(function() {
res.send(204);
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack);
res.json(500,{error:"unexpected_error", message:err.message});
res.status(500).json({error:"unexpected_error", message:err.message});
});
}
}

View File

@@ -15,6 +15,7 @@
**/
var express = require("express");
var bodyParser = require("body-parser");
var util = require('util');
var path = require('path');
var passport = require('passport');
@@ -26,6 +27,9 @@ 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 auth = require("./auth");
var needsPermission = auth.needsPermission;
@@ -33,14 +37,18 @@ var needsPermission = auth.needsPermission;
var settings = require("../settings");
var errorHandler = function(err,req,res,next) {
console.log(err.stack);
res.json(400,{error:"unexpected_error", message:err.toString()});
if (err.message === "request entity too large") {
log.error(err);
} else {
console.log(err.stack);
}
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:"unexpected_error", message:err.toString()});
};
function init(adminApp,storage) {
auth.init(settings,storage);
// Editor
if (!settings.disableEditor) {
ui.init(settings);
@@ -53,9 +61,9 @@ function init(adminApp,storage) {
editorApp.use("/",ui.editorResources);
adminApp.use(editorApp);
}
adminApp.use(express.json());
adminApp.use(express.urlencoded());
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);
@@ -86,6 +94,8 @@ function init(adminApp,storage) {
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

View File

@@ -29,7 +29,7 @@ function createLibrary(type) {
if (typeof result === "string") {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(result);
res.end();
res.end();
} else {
res.json(result);
}
@@ -38,33 +38,33 @@ function createLibrary(type) {
log.warn(log._("api.library.error-load-entry",{path:path,message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.get",type:type,error:"forbidden"},req);
res.send(403);
res.status(403).end();
return;
}
}
log.audit({event: "library.get",type:type,error:"not_found"},req);
res.send(404);
res.status(404).end();
});
});
redApp.post(new RegExp("/library/"+type+"\/(.*)"),needsPermission("library.write"),function(req,res) {
var path = req.params[0];
var meta = req.body;
var text = meta.text;
delete meta.text;
storage.saveLibraryEntry(type,path,meta,text).then(function() {
log.audit({event: "library.set",type:type},req);
res.send(204);
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.library.error-save-entry",{path:path,message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.set",type:type,error:"forbidden"},req);
res.send(403);
res.status(403).end();
return;
}
log.audit({event: "library.set",type:type,error:"unexpected_error",message:err.toString()},req);
res.json(500,{error:"unexpected_error", message:err.toString()});
res.status(500).json({error:"unexpected_error", message:err.toString()});
});
});
}
@@ -74,7 +74,7 @@ module.exports = {
redApp = app;
},
register: createLibrary,
getAll: function(req,res) {
storage.getAllFlows().then(function(flows) {
log.audit({event: "library.get.all",type:"flow"},req);
@@ -92,28 +92,28 @@ module.exports = {
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
res.send(403);
res.status(403).end();
return;
}
}
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
res.send(404);
res.status(404).end();
});
},
post: function(req,res) {
var flow = JSON.stringify(req.body);
storage.saveFlow(req.params[0],flow).then(function() {
log.audit({event: "library.set",type:"flow",path:req.params[0]},req);
res.send(204);
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
res.send(403);
res.status(403).end();
return;
}
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"unexpected_error",message:err.toString()},req);
res.send(500,{error:"unexpected_error", message:err.toString()});
res.status(500).send({error:"unexpected_error", message:err.toString()});
});
}
}

View File

@@ -19,7 +19,7 @@ module.exports = {
get: function(req,res) {
var namespace = req.params[0];
namespace = namespace.replace(/\.json$/,"");
var lang = i18n.determineLangFromHeaders(req.acceptedLanguages || []);
var lang = i18n.determineLangFromHeaders(req.acceptsLanguages() || []);
var prevLang = i18n.i.lng();
i18n.i.setLng(lang, function(){
var catalog = i18n.catalog(namespace,lang);

View File

@@ -15,7 +15,6 @@
**/
var redNodes = require("../nodes");
var comms = require("../comms");
var server = require("../server");
var log = require("../log");
var i18n = require("../i18n");
@@ -29,7 +28,7 @@ module.exports = {
log.audit({event: "nodes.list.get"},req);
res.json(redNodes.getNodeList());
} else {
var lang = i18n.determineLangFromHeaders(req.acceptedLanguages);
var lang = i18n.determineLangFromHeaders(req.acceptsLanguages());
log.audit({event: "nodes.configs.get"},req);
res.send(redNodes.getNodeConfigs(lang));
}
@@ -38,7 +37,7 @@ module.exports = {
post: function(req,res) {
if (!settings.available()) {
log.audit({event: "nodes.install",error:"settings_unavailable"},req);
res.json(400,{error:"settings_unavailable", message:"Settings unavailable"});
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
return;
}
var node = req.body;
@@ -47,35 +46,31 @@ module.exports = {
var module = redNodes.getModuleInfo(node.module);
if (module) {
log.audit({event: "nodes.install",module:node.module,error:"module_already_loaded"},req);
res.json(400,{error:"module_already_loaded", message:"Module already loaded"});
res.status(400).json({error:"module_already_loaded", message:"Module already loaded"});
return;
}
promise = server.installModule(node.module);
} else if (node.file) {
promise = server.installNode(node.file);
promise = redNodes.installModule(node.module);
} else {
log.audit({event: "nodes.install",module:node.module,error:"invalid_request"},req);
res.json(400,{error:"invalid_request", message:"Invalid request"});
res.status(400).json({error:"invalid_request", message:"Invalid request"});
return;
}
promise.then(function(info) {
comms.publish("node/added",info.nodes,false);
if (node.module) {
log.audit({event: "nodes.install",module:node.module},req);
res.json(redNodes.getModuleInfo(node.module));
} else if (node.file) {
log.audit({event: "nodes.install",file:node.file},req);
res.json(info.nodes[0]);
res.json(info);
}
}).otherwise(function(err) {
if (err.code === 404) {
log.audit({event: "nodes.install",module:node.module,file:node.file,error:"not_found"},req);
res.send(404);
log.audit({event: "nodes.install",module:node.module,error:"not_found"},req);
res.status(404).end();
} else if (err.code) {
log.audit({event: "nodes.install",module:node.module,error:err.code},req);
res.json(400,{error:err.code, message:err.message});
} else {
log.audit({event: "nodes.install",module:node.module,file:node.file,error:err.code||"unexpected_error",message:err.toString()},req);
res.json(400,{error:err.code||"unexpected_error", message:err.toString()});
res.status(400).json({error:err.code, message:err.message});
} else {
log.audit({event: "nodes.install",module:node.module,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
});
},
@@ -83,7 +78,7 @@ module.exports = {
delete: function(req,res) {
if (!settings.available()) {
log.audit({event: "nodes.remove",error:"settings_unavailable"},req);
res.json(400,{error:"settings_unavailable", message:"Settings unavailable"});
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
return;
}
var mod = req.params.mod;
@@ -92,22 +87,23 @@ module.exports = {
var module = redNodes.getModuleInfo(mod);
if (!module) {
log.audit({event: "nodes.remove",module:mod,error:"not_found"},req);
res.send(404);
res.status(404).end();
return;
} else {
promise = server.uninstallModule(mod);
promise = redNodes.uninstallModule(mod);
}
promise.then(function() {
promise.then(function(list) {
comms.publish("node/removed",list,false);
log.audit({event: "nodes.remove",module:mod},req);
res.send(204);
res.status(204).end();
}).otherwise(function(err) {
log.audit({event: "nodes.remove",module:mod,error:err.code||"unexpected_error",message:err.toString()},req);
res.json(400,{error:err.code||"unexpected_error", message:err.toString()});
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
});
} catch(err) {
log.audit({event: "nodes.remove",module:mod,error:err.code||"unexpected_error",message:err.toString()},req);
res.json(400,{error:err.code||"unexpected_error", message:err.toString()});
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
},
@@ -122,17 +118,17 @@ module.exports = {
res.send(result);
} else {
log.audit({event: "nodes.info.get",id:id,error:"not_found"},req);
res.send(404);
res.status(404).end();
}
} else {
var lang = i18n.determineLangFromHeaders(req.acceptedLanguages);
var lang = i18n.determineLangFromHeaders(req.acceptsLanguages());
result = redNodes.getNodeConfig(id,lang);
if (result) {
log.audit({event: "nodes.config.get",id:id},req);
res.send(result);
} else {
log.audit({event: "nodes.config.get",id:id,error:"not_found"},req);
res.send(404);
res.status(404).end();
}
}
},
@@ -145,20 +141,20 @@ module.exports = {
res.json(result);
} else {
log.audit({event: "nodes.module.get",module:module,error:"not_found"},req);
res.send(404);
res.status(404).end();
}
},
putSet: function(req,res) {
if (!settings.available()) {
log.audit({event: "nodes.info.set",error:"settings_unavailable"},req);
res.json(400,{error:"settings_unavailable", message:"Settings unavailable"});
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
return;
}
var body = req.body;
if (!body.hasOwnProperty("enabled")) {
log.audit({event: "nodes.info.set",error:"invalid_request"},req);
res.json(400,{error:"invalid_request", message:"Invalid request"});
res.status(400).json({error:"invalid_request", message:"Invalid request"});
return;
}
try {
@@ -167,7 +163,7 @@ module.exports = {
var info;
if (!node) {
log.audit({event: "nodes.info.set",id:id,error:"not_found"},req);
res.send(404);
res.status(404).end();
} else {
delete node.loaded;
putNode(node, body.enabled).then(function(result) {
@@ -177,20 +173,20 @@ module.exports = {
}
} catch(err) {
log.audit({event: "nodes.info.set",id:id,enabled:body.enabled,error:err.code||"unexpected_error",message:err.toString()},req);
res.json(400,{error:err.code||"unexpected_error", message:err.toString()});
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
},
putModule: function(req,res) {
if (!settings.available()) {
log.audit({event: "nodes.module.set",error:"settings_unavailable"},req);
res.json(400,{error:"settings_unavailable", message:"Settings unavailable"});
res.status(400).json({error:"settings_unavailable", message:"Settings unavailable"});
return;
}
var body = req.body;
if (!body.hasOwnProperty("enabled")) {
log.audit({event: "nodes.module.set",error:"invalid_request"},req);
res.json(400,{error:"invalid_request", message:"Invalid request"});
res.status(400).json({error:"invalid_request", message:"Invalid request"});
return;
}
try {
@@ -198,7 +194,7 @@ module.exports = {
var module = redNodes.getModuleInfo(mod);
if (!module) {
log.audit({event: "nodes.module.set",module:mod,error:"not_found"},req);
return res.send(404);
return res.status(404).end();
}
var nodes = module.nodes;
@@ -211,7 +207,7 @@ module.exports = {
});
} catch(err) {
log.audit({event: "nodes.module.set",module:mod,enabled:body.enabled,error:err.code||"unexpected_error",message:err.toString()},req);
res.json(400,{error:err.code||"unexpected_error", message:err.toString()});
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
}
};
@@ -227,7 +223,7 @@ function putNode(node, enabled) {
} else {
promise = redNodes.disableNode(node.id);
}
return promise.then(function(info) {
if (info.enabled === enabled && !info.err) {
comms.publish("node/"+(enabled?"enabled":"disabled"),info,false);

View File

@@ -43,7 +43,7 @@ function serveFile(app,baseUrl,file) {
var url = baseUrl+path.basename(file);
//console.log(url,"->",file);
app.get(url,function(req, res) {
res.sendfile(file);
res.sendFile(file);
});
return "theme"+url;
} catch(err) {
@@ -58,13 +58,13 @@ module.exports = {
var url;
themeContext = clone(defaultContext);
themeSettings = null;
if (settings.editorTheme) {
var theme = settings.editorTheme;
themeSettings = {};
var themeApp = express();
if (theme.page) {
if (theme.page.css) {
var styles = theme.page.css;
@@ -72,7 +72,7 @@ module.exports = {
styles = [styles];
}
themeContext.page.css = [];
for (i=0;i<styles.length;i++) {
url = serveFile(themeApp,"/css/",styles[i]);
if (url) {
@@ -80,25 +80,25 @@ module.exports = {
}
}
}
if (theme.page.favicon) {
url = serveFile(themeApp,"/favicon/",theme.page.favicon)
if (url) {
themeContext.page.favicon = url;
}
}
themeContext.page.title = theme.page.title || themeContext.page.title;
}
if (theme.header) {
themeContext.header.title = theme.header.title || themeContext.header.title;
if (theme.header.hasOwnProperty("url")) {
themeContext.header.url = theme.header.url;
}
if (theme.header.hasOwnProperty("image")) {
if (theme.header.image) {
url = serveFile(themeApp,"/header/",theme.header.image);
@@ -110,7 +110,7 @@ module.exports = {
}
}
}
if (theme.deployButton) {
if (theme.deployButton.type == "simple") {
themeSettings.deployButton = {
@@ -127,11 +127,11 @@ module.exports = {
}
}
}
if (theme.hasOwnProperty("userMenu")) {
themeSettings.userMenu = theme.userMenu;
}
if (theme.login) {
if (theme.login.image) {
url = serveFile(themeApp,"/login/",theme.login.image);
@@ -142,11 +142,11 @@ module.exports = {
}
}
}
if (theme.hasOwnProperty("menu")) {
themeSettings.menu = theme.menu;
}
return themeApp;
}
},

View File

@@ -42,7 +42,7 @@ module.exports = {
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8");
Mustache.parse(editorTemplate);
},
ensureSlash: function(req,res,next) {
var parts = req.originalUrl.split("?");
if (parts[0].slice(-1) != "/") {
@@ -55,17 +55,17 @@ module.exports = {
},
icon: function(req,res) {
if (iconCache[req.params.icon]) {
res.sendfile(iconCache[req.params.icon]); // if not found, express prints this to the console and serves 404
} else {
res.sendFile(iconCache[req.params.icon]); // if not found, express prints this to the console and serves 404
} else {
for (var p=0;p<icon_paths.length;p++) {
var iconPath = path.join(icon_paths[p],req.params.icon);
if (fs.existsSync(iconPath)) {
res.sendfile(iconPath);
res.sendFile(iconPath);
iconCache[req.params.icon] = iconPath;
return;
}
}
res.sendfile(defaultIcon);
res.sendFile(defaultIcon);
}
},
editor: function(req,res) {

View File

@@ -46,7 +46,7 @@ function start() {
var path = settings.httpAdminRoot || "/";
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({server:server,path:path});
wsServer.on('connection',function(ws) {
log.audit({event: "comms.open"});
var pendingAuth = (settings.adminAuth != null);
@@ -122,13 +122,13 @@ function start() {
log.warn(log._("comms.error",{message:err.toString()}));
});
});
wsServer.on('error', function(err) {
log.warn(log._("comms.error-server",{message:err.toString()}));
});
lastSentTime = Date.now();
heartbeatTimer = setInterval(function() {
var now = Date.now();
if (now-lastSentTime > webSocketKeepAliveTime) {
@@ -167,6 +167,8 @@ function publishTo(ws,topic,data) {
try {
ws.send(msg);
} catch(err) {
removeActiveConnection(ws);
removePendingConnection(ws);
log.warn(log._("comms.error-send",{message:err.toString()}));
}
}

View File

@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var i18n = require("i18next");
var when = require("when");
var path = require("path");
var fs = require("fs");
var defaultLang = "en-US";
var supportedLangs = null;
var supportedLangs = [];
var resourceMap = {
"runtime": {
@@ -56,6 +56,18 @@ var initSupportedLangs = function() {
});
}
function mergeCatalog(fallback,catalog) {
for (var k in fallback) {
if (fallback.hasOwnProperty(k)) {
if (!catalog[k]) {
catalog[k] = fallback[k];
} else if (typeof fallback[k] === 'object') {
mergeCatalog(fallback[k],catalog[k]);
}
}
}
}
var MessageFileLoader = {
fetchOne: function(lng, ns, callback) {
if (resourceMap[ns]) {
@@ -66,10 +78,12 @@ var MessageFileLoader = {
callback(err);
} else {
try {
//console.log(">>",ns,file,lng);
resourceCache[ns] = resourceCache[ns]||{};
resourceCache[ns][lng] = JSON.parse(content.replace(/^\uFEFF/, ''));
//console.log(resourceCache[ns][lng]);
if (lng !== defaultLang) {
mergeCatalog(resourceCache[ns][defaultLang],resourceCache[ns][lng]);
}
callback(null, resourceCache[ns][lng]);
} catch(e) {
callback(e);
@@ -80,7 +94,7 @@ var MessageFileLoader = {
callback(new Error("Unrecognised namespace"));
}
}
}
function init() {
@@ -109,7 +123,10 @@ function getCatalog(namespace,lang) {
if (!result) {
var langParts = lang.split("-");
if (langParts.length == 2) {
result = getCatalog(namespace,langParts[0]);
result = resourceCache[namespace][langParts[0]];
}
if (!result) {
return resourceCache[namespace][defaultLang];
}
}
}
@@ -119,7 +136,7 @@ function getCatalog(namespace,lang) {
function determineLangFromHeaders(acceptedLanguages){
var lang = "en-US";
acceptedLanguages = acceptedLanguages || [];
for (var i=0;i<acceptedLanguages.length;i++){
if (supportedLangs.indexOf(acceptedLanguages[i]) !== -1){
lang = acceptedLanguages[i];

View File

@@ -1,790 +0,0 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require("when");
var clone = require("clone");
var typeRegistry = require("./registry");
var credentials = require("./credentials");
var redUtil = require("../util");
var events = require("../events");
var Log = require("../log");
function getID() {
return (1+Math.random()*4294967295).toString(16);
}
function createNode(type,config) {
var nn = null;
var nt = typeRegistry.get(type);
if (nt) {
try {
nn = new nt(clone(config));
}
catch (err) {
Log.log({
level: Log.ERROR,
id:config.id,
type: type,
msg: err
});
}
} else {
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
}
return nn;
}
function createSubflow(sf,sfn,subflows) {
//console.log("CREATE SUBFLOW",sf.config.id,sfn.id);
var nodes = [];
var node_map = {};
var newNodes = [];
var node;
var wires;
var i,j,k;
// Clone all of the subflow node definitions and give them new IDs
for (i=0;i<sf.nodes.length;i++) {
node = clone(sf.nodes[i].config);
var nid = getID();
node_map[node.id] = node;
node._alias = node.id;
node.id = nid;
node.z = sfn.id;
newNodes.push(node);
}
// Update all subflow interior wiring to reflect new node IDs
for (i=0;i<newNodes.length;i++) {
node = newNodes[i];
var outputs = node.wires;
for (j=0;j<outputs.length;j++) {
wires = outputs[j];
for (k=0;k<wires.length;k++) {
outputs[j][k] = node_map[outputs[j][k]].id
}
}
}
// Create a subflow node to accept inbound messages and route appropriately
var Node = require("./Node");
var subflowInstance = {
id: sfn.id,
type: sfn.type,
z: sfn.z,
name: sfn.name,
wires: []
}
if (sf.config.in) {
subflowInstance.wires = sf.config.in.map(function(n) { return n.wires.map(function(w) { return node_map[w.id].id;})})
subflowInstance._originalWires = clone(subflowInstance.wires);
}
var subflowNode = new Node(subflowInstance);
subflowNode.on("input", function(msg) { this.send(msg);});
subflowNode._updateWires = subflowNode.updateWires;
subflowNode.updateWires = function(newWires) {
// Wire the subflow outputs
if (sf.config.out) {
var node,wires,i,j;
// Restore the original wiring to the internal nodes
subflowInstance.wires = clone(subflowInstance._originalWires);
for (i=0;i<sf.config.out.length;i++) {
wires = sf.config.out[i].wires;
for (j=0;j<wires.length;j++) {
if (wires[j].id != sf.config.id) {
node = node_map[wires[j].id];
if (node._originalWires) {
node.wires = clone(node._originalWires);
}
}
}
}
var modifiedNodes = {};
var subflowInstanceModified = false;
for (i=0;i<sf.config.out.length;i++) {
wires = sf.config.out[i].wires;
for (j=0;j<wires.length;j++) {
if (wires[j].id === sf.config.id) {
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(newWires[i]);
subflowInstanceModified = true;
} else {
node = node_map[wires[j].id];
node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
modifiedNodes[node.id] = node;
}
}
}
Object.keys(modifiedNodes).forEach(function(id) {
var node = modifiedNodes[id];
subflowNode.instanceNodes[id].updateWires(node.wires);
});
if (subflowInstanceModified) {
subflowNode._updateWires(subflowInstance.wires);
}
}
}
nodes.push(subflowNode);
// Wire the subflow outputs
if (sf.config.out) {
var modifiedNodes = {};
for (i=0;i<sf.config.out.length;i++) {
wires = sf.config.out[i].wires;
for (j=0;j<wires.length;j++) {
if (wires[j].id === sf.config.id) {
// A subflow input wired straight to a subflow output
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(sfn.wires[i])
subflowNode._updateWires(subflowInstance.wires);
} else {
node = node_map[wires[j].id];
modifiedNodes[node.id] = node;
if (!node._originalWires) {
node._originalWires = clone(node.wires);
}
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(sfn.wires[i]);
}
}
}
}
// Instantiate the nodes
for (i=0;i<newNodes.length;i++) {
node = newNodes[i];
var type = node.type;
var m = /^subflow:(.+)$/.exec(type);
if (!m) {
nodes.push(createNode(type,node));
} else {
var subflowId = m[1];
nodes = nodes.concat(createSubflow(subflows[subflowId],node,subflows));
}
}
subflowNode.instanceNodes = {};
nodes.forEach(function(node) {
subflowNode.instanceNodes[node.id] = node;
});
return nodes;
}
function diffNodeConfigs(oldNode,newNode) {
if (oldNode == null) {
return true;
} else {
for (var p in newNode) {
if (newNode.hasOwnProperty(p) && p != "x" && p != "y" && p != "wires") {
if (!redUtil.compareObjects(oldNode[p],newNode[p])) {
return true;
}
}
}
}
return false;
}
function createCatchNodeMap(nodes) {
var catchNodes = {};
var subflowInstances = {};
var id;
/*
- a catchNode with same z as error node
- if error occurs on a subflow without catchNode, look at z of subflow instance
*/
for (id in nodes) {
if (nodes.hasOwnProperty(id)) {
if (nodes[id].type === "catch") {
catchNodes[nodes[id].z] = nodes[id];
}
}
}
for (id in nodes) {
if (nodes.hasOwnProperty(id)) {
var m = /^subflow:(.+)$/.exec(nodes[id].type);
if (m) {
subflowInstances[id] = nodes[id];
}
}
}
for (id in subflowInstances) {
if (subflowInstances.hasOwnProperty(id)) {
var z = id;
while(subflowInstances[z]) {
var sfi = subflowInstances[z];
if (!catchNodes[z]) {
z = sfi.z;
} else {
break;
}
}
if (catchNodes[z]) {
catchNodes[id] = catchNodes[z];
}
}
}
return catchNodes;
}
var subflowInstanceRE = /^subflow:(.+)$/;
function Flow(config) {
this.activeNodes = {};
this.subflowInstanceNodes = {};
this.catchNodeMap = {};
this.started = false;
this.parseConfig(config);
}
Flow.prototype.parseConfig = function(config) {
var i;
var nodeConfig;
var nodeType;
this.config = config;
this.allNodes = {};
this.nodes = {};
this.subflows = {};
this.configNodes = {};
var unknownTypes = {};
for (i=0;i<this.config.length;i++) {
nodeConfig = this.config[i];
nodeType = nodeConfig.type;
this.allNodes[nodeConfig.id] = nodeConfig;
if (nodeType == "subflow") {
this.subflows[nodeConfig.id] = {
type: "subflow",
config: nodeConfig,
nodes: []
}
}
}
//console.log("Known subflows:",Object.keys(this.subflows));
for (i=0;i<this.config.length;i++) {
nodeConfig = this.config[i];
nodeType = nodeConfig.type;
if (nodeConfig.credentials) {
delete nodeConfig.credentials;
}
if (nodeType != "tab" && nodeType != "subflow") {
var m = subflowInstanceRE.exec(nodeType);
if ((m && !this.subflows[m[1]]) || (!m && !typeRegistry.get(nodeType))) {
// This is an unknown subflow or an unknown type
unknownTypes[nodeType] = true;
} else {
var nodeInfo = {
type: nodeType,
config:nodeConfig
}
if (m) {
nodeInfo.subflow = m[1];
}
if (this.subflows[nodeConfig.z]) {
this.subflows[nodeConfig.z].nodes.push(nodeInfo);
} else {
this.nodes[nodeConfig.id] = nodeInfo;
}
for (var prop in nodeConfig) {
if (nodeConfig.hasOwnProperty(prop) &&
prop != "type" &&
prop != "id" &&
prop != "z" &&
prop != "wires" &&
this.allNodes[nodeConfig[prop]]) {
this.configNodes[nodeConfig[prop]] = this.allNodes[nodeConfig[prop]];
}
}
}
}
}
//console.log("NODES");
//for (i in this.nodes) {
// if (this.nodes.hasOwnProperty(i)) {
// console.log(" ",i,this.nodes[i].type,this.nodes[i].config.name||"");
// }
//}
//console.log("SUBFLOWS");
//for (i in this.subflows) {
// if (this.subflows.hasOwnProperty(i)) {
// console.log(" ",i,this.subflows[i].type,this.subflows[i].config.name||"");
// for (var j=0;j<this.subflows[i].nodes.length;j++) {
// console.log(" ",this.subflows[i].nodes[j].config.id,this.subflows[i].nodes[j].type,this.subflows[i].nodes[j].config.name||"");
// }
// }
//}
this.missingTypes = Object.keys(unknownTypes);
}
Flow.prototype.start = function(configDiff) {
if (configDiff) {
for (var j=0;j<configDiff.rewire.length;j++) {
var rewireNode = this.activeNodes[configDiff.rewire[j]];
if (rewireNode) {
rewireNode.updateWires(this.allNodes[rewireNode.id].wires);
}
}
}
this.started = true;
if (this.missingTypes.length > 0) {
throw new Error(Log._("nodes.flow.missing-types"));
}
events.emit("nodes-starting");
var id;
var node;
for (id in this.configNodes) {
if (this.configNodes.hasOwnProperty(id)) {
node = this.configNodes[id];
if (!this.activeNodes[id]) {
this.activeNodes[id] = createNode(node.type,node);
}
}
}
for (id in this.nodes) {
if (this.nodes.hasOwnProperty(id)) {
node = this.nodes[id];
if (!node.subflow) {
if (!this.activeNodes[id]) {
this.activeNodes[id] = createNode(node.type,node.config);
//console.log(id,"created");
} else {
//console.log(id,"already running");
}
} else {
if (!this.subflowInstanceNodes[id]) {
var nodes = createSubflow(this.subflows[node.subflow],node.config,this.subflows);
this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
for (var i=0;i<nodes.length;i++) {
this.activeNodes[nodes[i].id] = nodes[i];
}
//console.log(id,"(sf) created");
} else {
//console.log(id,"(sf) already running");
}
}
}
}
this.catchNodeMap = createCatchNodeMap(this.activeNodes);
credentials.clean(this.config);
events.emit("nodes-started");
}
Flow.prototype.stop = function(configDiff) {
var nodeList;
if (configDiff) {
nodeList = configDiff.stop;
} else {
nodeList = Object.keys(this.activeNodes);
}
var flow = this;
return when.promise(function(resolve) {
events.emit("nodes-stopping");
var promises = [];
for (var i=0;i<nodeList.length;i++) {
var node = flow.activeNodes[nodeList[i]];
if (node) {
try {
var p = node.close();
if (p) {
promises.push(p);
}
} catch(err) {
node.error(err);
}
delete flow.subflowInstanceNodes[nodeList[i]];
delete flow.activeNodes[nodeList[i]];
}
}
when.settle(promises).then(function() {
events.emit("nodes-stopped");
flow.started = false;
resolve();
});
});
}
Flow.prototype.getMissingTypes = function() {
return this.missingTypes;
}
Flow.prototype.typeRegistered = function(type) {
if (this.missingTypes.length > 0) {
var i = this.missingTypes.indexOf(type);
if (i != -1) {
this.missingTypes.splice(i,1);
if (this.missingTypes.length === 0 && this.started) {
this.start();
}
return true;
}
}
return false;
}
Flow.prototype.getNode = function(id) {
return this.activeNodes[id];
}
Flow.prototype.getFlow = function() {
//console.log(this.config);
return this.config;
}
Flow.prototype.eachNode = function(callback) {
for (var id in this.activeNodes) {
if (this.activeNodes.hasOwnProperty(id)) {
callback(this.activeNodes[id]);
}
}
}
Flow.prototype.diffConfig = function(config,type) {
var activeNodesToStop = [];
var nodesToRewire = [];
if (type && type!="full") {
var diff = diffFlow(this,config);
//var diff = {
// deleted:[]
// changed:[]
// linked:[]
// wiringChanged: []
//}
var nodesToStop = [];
nodesToRewire = diff.wiringChanged;
if (type == "nodes") {
nodesToStop = diff.deleted.concat(diff.changed);
} else if (type == "flows") {
nodesToStop = diff.deleted.concat(diff.changed).concat(diff.linked);
}
for (var i=0;i<nodesToStop.length;i++) {
var id = nodesToStop[i];
if (this.subflowInstanceNodes[id]) {
activeNodesToStop = activeNodesToStop.concat(this.subflowInstanceNodes[id]);
} else if (this.activeNodes[id]) {
activeNodesToStop.push(id);
}
}
} else {
activeNodesToStop = Object.keys(this.activeNodes);
}
return {
type: type,
stop: activeNodesToStop,
rewire: nodesToRewire,
config: config
}
}
function diffFlow(flow,config) {
//if (!flow.started) {
// throw new Error("Cannot diff an unstarted flow");
//}
var flowNodes = {};
var changedNodes = {};
var deletedNodes = {};
var deletedSubflows = {};
var deletedTabs = {};
var linkChangedNodes = {};
var activeLinks = {};
var newLinks = {};
var changedSubflowStack = [];
var changedSubflows = {};
var buildNodeLinks = function(nodeLinks,n,nodes) {
nodeLinks[n.id] = nodeLinks[n.id] || [];
if (n.wires) {
for (var j=0;j<n.wires.length;j++) {
var wires = n.wires[j];
for (var k=0;k<wires.length;k++) {
nodeLinks[n.id].push(wires[k]);
var nn = nodes[wires[k]];
if (nn) {
nodeLinks[nn.id] = nodeLinks[nn.id] || [];
nodeLinks[nn.id].push(n.id);
}
}
}
}
}
config.forEach(function(node) {
flowNodes[node.id] = node;
});
config.forEach(function(node) {
var changed = false;
if (node.credentials) {
changed = true;
delete node.credentials;
} else {
changed = diffNodeConfigs(flow.allNodes[node.id],node);
if (!changed) {
if (flowNodes[node.z] && flowNodes[node.z].type == "subflow") {
var originalNode = flow.allNodes[node.id];
if (originalNode && !redUtil.compareObjects(originalNode.wires,node.wires)) {
// This is a node in a subflow whose wiring has changed. Mark subflow as changed
changed = true;
}
}
}
}
if (changed) {
changedNodes[node.id] = node;
}
});
flow.config.forEach(function(node) {
if (!flowNodes[node.id]) {
if (node.type === "tab") {
deletedTabs[node.id] = node;
} else if (node.type === "subflow") {
deletedSubflows[node.id] = node;
} else {
deletedNodes[node.id] = node;
}
}
buildNodeLinks(activeLinks,node,flow.allNodes);
});
flow.config.forEach(function(node) {
for (var prop in node) {
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
// This node has a property that references a changed node
// Assume it is a config node change and mark this node as
// changed.
if (changedNodes[node[prop]]) {
changedNodes[node.id] = node;
}
}
}
});
var checkSubflowMembership = function(nodes,id) {
var node = nodes[id];
if (node) {
if (node.type == "subflow") {
changedSubflows[id] = node;
changedSubflowStack.push(id);
} else if (nodes[node.z] && nodes[node.z].type == "subflow") {
if (!changedSubflows[node.z]) {
changedSubflows[node.z] = nodes[node.z];
changedSubflowStack.push(node.z);
}
}
}
};
Object.keys(changedNodes).forEach(function(n) { checkSubflowMembership(flowNodes,n)});
Object.keys(deletedNodes).forEach(function(n) { checkSubflowMembership(flow.allNodes,n)});
while (changedSubflowStack.length > 0) {
var subflowId = changedSubflowStack.pop();
config.forEach(function(node) {
if (node.type == "subflow:"+subflowId) {
if (!changedNodes[node.id]) {
changedNodes[node.id] = node;
checkSubflowMembership(flowNodes,node.id);
}
}
});
}
config.forEach(function(node) {
buildNodeLinks(newLinks,node,flowNodes);
});
var markLinkedNodes = function(linkChanged,otherChangedNodes,linkMap,allNodes) {
var stack = Object.keys(changedNodes).concat(Object.keys(otherChangedNodes));
var visited = {};
while(stack.length > 0) {
var id = stack.pop();
var linkedNodes = linkMap[id];
if (linkedNodes) {
for (var i=0;i<linkedNodes.length;i++) {
var linkedNodeId = linkedNodes[i];
if (changedNodes[linkedNodeId] || deletedNodes[linkedNodeId] || otherChangedNodes[linkedNodeId] || linkChanged[linkedNodeId]) {
// Do nothing - this linked node is already marked as changed, so will get done
} else {
linkChanged[linkedNodeId] = true;
stack.push(linkedNodeId);
}
}
}
}
}
markLinkedNodes(linkChangedNodes,{},newLinks,flowNodes);
markLinkedNodes(linkChangedNodes,{},activeLinks,flow.allNodes);
var modifiedLinkNodes = {};
config.forEach(function(node) {
if (!changedNodes[node.id]) {
// only concerned about unchanged nodes whose wiring may have changed
var newNodeLinks = newLinks[node.id];
var oldNodeLinks = activeLinks[node.id];
var newLinkMap = {};
newNodeLinks.forEach(function(l) { newLinkMap[l] = (newLinkMap[l]||0)+1;});
var oldLinkMap = {};
oldNodeLinks.forEach(function(l) { oldLinkMap[l] = (oldLinkMap[l]||0)+1;});
newNodeLinks.forEach(function(link) {
if (newLinkMap[link] != oldLinkMap[link]) {
modifiedLinkNodes[node.id] = node;
linkChangedNodes[node.id] = node;
if (!changedNodes[link] && !deletedNodes[link]) {
modifiedLinkNodes[link] = flowNodes[link];
linkChangedNodes[link] = flowNodes[link];
}
}
});
oldNodeLinks.forEach(function(link) {
if (newLinkMap[link] != oldLinkMap[link]) {
modifiedLinkNodes[node.id] = node;
linkChangedNodes[node.id] = node;
if (!changedNodes[link] && !deletedNodes[link]) {
modifiedLinkNodes[link] = flowNodes[link];
linkChangedNodes[link] = flowNodes[link];
}
}
});
}
});
markLinkedNodes(linkChangedNodes,modifiedLinkNodes,newLinks,flowNodes);
// config.forEach(function(n) {
// console.log((changedNodes[n.id]!=null)?"[C]":"[ ]",(linkChangedNodes[n.id]!=null)?"[L]":"[ ]","[ ]",n.id,n.type,n.name);
// });
//
// Object.keys(deletedNodes).forEach(function(id) {
// var n = flow.allNodes[id];
// console.log("[ ] [ ] [D]",n.id,n.type);
// });
var diff = {
deleted: Object.keys(deletedNodes).filter(function(id) { return deletedNodes[id].type != "subflow" && (!deletedNodes[id].z || deletedTabs[deletedNodes[id].z] || !(deletedSubflows[deletedNodes[id].z] || flowNodes[deletedNodes[id].z].type == "subflow"))}),
changed: Object.keys(changedNodes).filter(function(id) { return changedNodes[id].type != "subflow" && (!changedNodes[id].z || flowNodes[changedNodes[id].z].type != "subflow")}),
linked: Object.keys(linkChangedNodes).filter(function(id) { return linkChangedNodes[id].type != "subflow" && (!linkChangedNodes[id].z || flowNodes[linkChangedNodes[id].z].type != "subflow")}),
wiringChanged: []
}
config.forEach(function(n) {
if (!flowNodes[n.z] || flowNodes[n.z].type != "subflow") {
var originalNode = flow.allNodes[n.id];
if (originalNode && !redUtil.compareObjects(originalNode.wires,n.wires)) {
diff.wiringChanged.push(n.id);
}
}
});
return diff;
}
Flow.prototype.handleError = function(node,logMessage,msg) {
var targetCatchNode = null;
if (this.catchNodeMap[node.z]) {
targetCatchNode = this.catchNodeMap[node.z];
} else if (this.activeNodes[node.z] && this.catchNodeMap[this.activeNodes[node.z].z]) {
targetCatchNode = this.catchNodeMap[this.activeNodes[node.z].z];
}
if (targetCatchNode) {
var count = 1;
if (msg && msg.hasOwnProperty("error")) {
if (msg.error.hasOwnProperty("source")) {
if (msg.error.source.id === node.id) {
count = msg.error.source.count+1;
if (count === 10) {
node.warn(Log._("nodes.flow.error-loop"));
return;
}
}
}
}
var errorMessage;
if (msg) {
errorMessage = redUtil.cloneMessage(msg);
} else {
errorMessage = {};
}
if (errorMessage.hasOwnProperty("error")) {
errorMessage._error = errorMessage.error;
}
errorMessage.error = {
message: logMessage.toString(),
source: {
id: node.id,
type: node.type,
count: count
}
};
targetCatchNode.receive(errorMessage);
}
}
module.exports = Flow;

View File

@@ -33,12 +33,16 @@ function Node(n) {
if (n.name) {
this.name = n.name;
}
if (n._alias) {
this._alias = n._alias;
}
this.updateWires(n.wires);
}
util.inherits(Node, EventEmitter);
Node.prototype.updateWires = function(wires) {
//console.log("UPDATE",this.id);
this.wires = wires || [];
delete this._wire;
@@ -189,7 +193,7 @@ Node.prototype.receive = function(msg) {
msg._msgid = redUtil.generateId();
}
this.metric("receive",msg);
try {
try {
this.emit("input", msg);
} catch(err) {
this.error(err,msg);
@@ -247,5 +251,6 @@ Node.prototype.metric = function(eventname, msg, metricValue) {
*/
Node.prototype.status = function(status) {
comms.publish("status/" + this.id, status, true);
flows.handleStatus(this,status);
};
module.exports = Node;

View File

@@ -23,52 +23,12 @@ var needsPermission = require("../api/auth").needsPermission;
var credentialCache = {};
var storage = null;
var credentialsDef = {};
var redApp = null;
/**
* Adds an HTTP endpoint to allow look up of credentials for a given node id.
*/
function registerEndpoint(type) {
redApp.get('/credentials/' + type + '/:id', needsPermission(type+".read"), function (req, res) {
// TODO: This could be a generic endpoint with the type value
// parameterised.
//
// TODO: It should verify the given node id is of the type specified -
// but that would add a dependency from this module to the
// registry module that knows about node types.
var nodeType = type;
var nodeID = req.params.id;
var credentials = credentialCache[nodeID];
if (credentials === undefined) {
res.json({});
return;
}
var definition = credentialsDef[nodeType];
var sendCredentials = {};
for (var cred in definition) {
if (definition.hasOwnProperty(cred)) {
if (definition[cred].type == "password") {
var key = 'has_' + cred;
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
continue;
}
sendCredentials[cred] = credentials[cred] || '';
}
}
res.json(sendCredentials);
});
}
module.exports = {
init: function (_storage,_app) {
init: function (_storage) {
storage = _storage;
redApp = _app;
},
/**
* Loads the credentials from storage.
*/
@@ -79,7 +39,7 @@ module.exports = {
log.warn(log._("nodes.credentials.error",{message: err}));
});
},
/**
* Adds a set of credentials for the given node id.
* @param id the node id for the credentials
@@ -118,7 +78,7 @@ module.exports = {
clean: function (config) {
var existingIds = {};
config.forEach(function(n) {
existingIds[n.id] = true;
existingIds[n.id] = true;
});
var deletedCredentials = false;
for (var c in credentialCache) {
@@ -135,7 +95,7 @@ module.exports = {
return when.resolve();
}
},
/**
* Registers a node credential definition.
* @param type the node type
@@ -144,16 +104,15 @@ module.exports = {
register: function (type, definition) {
var dashedType = type.replace(/\s+/g, '-');
credentialsDef[dashedType] = definition;
registerEndpoint(dashedType);
},
/**
* Extracts and stores any credential updates in the provided node.
* The provided node may have a .credentials property that contains
* new credentials for the node.
* This function loops through the credentials in the definition for
* the node-type and applies any of the updates provided in the node.
*
*
* This function does not save the credentials to disk as it is expected
* to be called multiple times when a new flow is deployed.
*
@@ -171,7 +130,7 @@ module.exports = {
log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
return;
}
for (var cred in definition) {
if (definition.hasOwnProperty(cred)) {
if (newCreds[cred] === undefined) {
@@ -191,7 +150,7 @@ module.exports = {
delete node.credentials;
}
},
/**
* Saves the credentials to storage
* @return a promise for the saving of credentials to storage
@@ -199,7 +158,7 @@ module.exports = {
save: function () {
return storage.saveCredentials(credentialCache);
},
/**
* Gets the credential definition for the given node type
* @param type the node type

View File

@@ -1,196 +0,0 @@
/**
* 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 clone = require("clone");
var when = require("when");
var typeRegistry = require("./registry");
var credentials = require("./credentials");
var Flow = require("./Flow");
var log = require("../log");
var events = require("../events");
var redUtil = require("../util");
var storage = null;
var settings = null;
var deprecated = require("./deprecated");
var activeFlow = null;
var nodes = {};
var subflows = {};
var activeConfig = [];
var activeConfigNodes = {};
events.on('type-registered',function(type) {
if (activeFlow && activeFlow.typeRegistered(type)) {
log.info(log._("nodes.flows.registered-missing", {type:type}));
}
});
var flowNodes = module.exports = {
init: function(_settings, _storage) {
settings = _settings;
storage = _storage;
},
/**
* Load the current activeConfig from storage and start it running
* @return a promise for the loading of the config
*/
load: function() {
return storage.getFlows().then(function(flows) {
return credentials.load().then(function() {
activeFlow = new Flow(flows);
flowNodes.startFlows();
});
}).otherwise(function(err) {
log.warn(log._("nodes.flows.error",{message:err.toString()}));
console.log(err.stack);
});
},
/**
* Get a node
* @param i the node id
* @return the node
*/
get: function(i) {
return activeFlow.getNode(i);
},
eachNode: function(cb) {
activeFlow.eachNode(cb);
},
/**
* @return the active configuration
*/
getFlows: function() {
return activeFlow.getFlow();
},
/**
* Sets the current active config.
* @param config the configuration to enable
* @param type the type of deployment to do: full (default), nodes, flows
* @return a promise for the starting of the new flow
*/
setFlows: function (config,type) {
type = type||"full";
var credentialsChanged = false;
var credentialSavePromise = null;
// Clone config and extract credentials prior to saving
// Original config needs to retain credentials so that flow.applyConfig
// knows which nodes have had changes.
var cleanConfig = clone(config);
cleanConfig.forEach(function(node) {
if (node.credentials) {
credentials.extract(node);
credentialsChanged = true;
}
});
if (credentialsChanged) {
credentialSavePromise = credentials.save();
} else {
credentialSavePromise = when.resolve();
}
if (type=="full") {
return credentialSavePromise
.then(function() { return storage.saveFlows(cleanConfig);})
.then(function() { return flowNodes.stopFlows(); })
.then(function() { activeFlow = new Flow(config); flowNodes.startFlows();});
} else {
return credentialSavePromise
.then(function() { return storage.saveFlows(cleanConfig);})
.then(function() {
var configDiff = activeFlow.diffConfig(config,type);
return flowNodes.stopFlows(configDiff).then(function() {
activeFlow.parseConfig(config);
flowNodes.startFlows(configDiff);
});
});
}
},
startFlows: function(configDiff) {
if (configDiff) {
log.info(log._("nodes.flows.starting-modified-"+configDiff.type));
} else {
log.info(log._("nodes.flows.starting-flows"));
}
try {
activeFlow.start(configDiff);
if (configDiff) {
log.info(log._("nodes.flows.started-modified-"+configDiff.type));
} else {
log.info(log._("nodes.flows.started-flows"));
}
} catch(err) {
var missingTypes = activeFlow.getMissingTypes();
if (missingTypes.length > 0) {
log.info(log._("nodes.flows.missing-types"));
var knownUnknowns = 0;
for (var i=0;i<missingTypes.length;i++) {
var type = missingTypes[i];
var info = deprecated.get(type);
if (info) {
log.info(log._("nodes.flows.missing-type-provided",{type:missingTypes[i],module:info.module}));
knownUnknowns += 1;
} else {
log.info(" - "+missingTypes[i]);
}
}
if (knownUnknowns > 0) {
log.info(log._("nodes.flows.missing-type-install-1"));
log.info(" npm install <module name>");
log.info(log._("nodes.flows.missing-type-install-2"));
log.info(" "+settings.userDir);
}
}
}
},
stopFlows: function(configDiff) {
if (configDiff) {
log.info(log._("nodes.flows.stopping-modified-"+configDiff.type));
} else {
log.info(log._("nodes.flows.stopping-flows"));
}
if (activeFlow) {
return activeFlow.stop(configDiff).then(function() {
if (configDiff) {
log.info(log._("nodes.flows.stopped-modified-"+configDiff.type));
} else {
log.info(log._("nodes.flows.stopped-flows"));
}
return;
});
} else {
log.info(log._("nodes.flows.stopped"));
return;
}
},
handleError: function(node,logMessage,msg) {
activeFlow.handleError(node,logMessage,msg);
}
};
var activeFlow = null;

466
red/nodes/flows/Flow.js Normal file
View File

@@ -0,0 +1,466 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require("when");
var clone = require("clone");
var typeRegistry = require("../registry");
var Log = require("../../log");
var redUtil = require("../../util");
var flowUtil = require("./util");
function Flow(global,flow) {
if (typeof flow === 'undefined') {
flow = global;
}
var activeNodes = {};
var subflowInstanceNodes = {};
var catchNodeMap = {};
var statusNodeMap = {};
this.start = function(diff) {
var node;
var id;
catchNodeMap = {};
statusNodeMap = {};
for (id in flow.configs) {
if (flow.configs.hasOwnProperty(id)) {
node = flow.configs[id];
if (!activeNodes[id]) {
activeNodes[id] = createNode(node.type,node);
}
}
}
if (diff) {
for (var j=0;j<diff.rewired.length;j++) {
var rewireNode = activeNodes[diff.rewired[j]];
if (rewireNode) {
rewireNode.updateWires(flow.nodes[rewireNode.id].wires);
}
}
}
for (id in flow.nodes) {
if (flow.nodes.hasOwnProperty(id)) {
node = flow.nodes[id];
if (!node.subflow) {
if (!activeNodes[id]) {
activeNodes[id] = createNode(node.type,node);
}
} else {
if (!subflowInstanceNodes[id]) {
try {
var nodes = createSubflow(flow.subflows[node.subflow]||global.subflows[node.subflow],node,flow.subflows,global.subflows,activeNodes);
subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
for (var i=0;i<nodes.length;i++) {
activeNodes[nodes[i].id] = nodes[i];
}
} catch(err) {
console.log(err.stack)
}
}
}
}
}
for (id in activeNodes) {
if (activeNodes.hasOwnProperty(id)) {
node = activeNodes[id];
if (node.type === "catch") {
catchNodeMap[node.z] = catchNodeMap[node.z] || [];
catchNodeMap[node.z].push(node);
} else if (node.type === "status") {
statusNodeMap[node.z] = statusNodeMap[node.z] || [];
statusNodeMap[node.z].push(node);
}
}
}
}
this.stop = function(stopList) {
return when.promise(function(resolve) {
var i;
if (stopList) {
for (i=0;i<stopList.length;i++) {
if (subflowInstanceNodes[stopList[i]]) {
// The first in the list is the instance node we already
// know about
stopList = stopList.concat(subflowInstanceNodes[stopList[i]].slice(1))
}
}
} else {
stopList = Object.keys(activeNodes);
}
var promises = [];
for (i=0;i<stopList.length;i++) {
var node = activeNodes[stopList[i]];
if (node) {
delete activeNodes[stopList[i]];
if (subflowInstanceNodes[stopList[i]]) {
delete subflowInstanceNodes[stopList[i]];
}
try {
var p = node.close();
if (p) {
promises.push(p);
}
} catch(err) {
node.error(err);
}
}
}
when.settle(promises).then(function() {
resolve();
});
});
}
this.update = function(_global,_flow) {
global = _global;
flow = _flow;
}
this.getNode = function(id) {
return activeNodes[id];
}
this.getActiveNodes = function() {
return activeNodes;
}
this.handleStatus = function(node,statusMessage) {
var targetStatusNodes = null;
var reportingNode = node;
var handled = false;
while(reportingNode && !handled) {
targetStatusNodes = statusNodeMap[reportingNode.z];
if (targetStatusNodes) {
targetStatusNodes.forEach(function(targetStatusNode) {
if (targetStatusNode.scope && targetStatusNode.scope.indexOf(node.id) === -1) {
return;
}
var message = {
status: {
text: "",
source: {
id: node.id,
type: node.type,
name: node.name
}
}
};
if (statusMessage.text) {
message.status.text = statusMessage.text;
}
targetStatusNode.receive(message);
handled = true;
});
}
if (!handled) {
reportingNode = activeNodes[reportingNode.z];
}
}
}
this.handleError = function(node,logMessage,msg) {
var count = 1;
if (msg && msg.hasOwnProperty("error")) {
if (msg.error.hasOwnProperty("source")) {
if (msg.error.source.id === node.id) {
count = msg.error.source.count+1;
if (count === 10) {
node.warn(Log._("nodes.flow.error-loop"));
return;
}
}
}
}
var targetCatchNodes = null;
var throwingNode = node;
var handled = false;
while (throwingNode && !handled) {
targetCatchNodes = catchNodeMap[throwingNode.z];
if (targetCatchNodes) {
targetCatchNodes.forEach(function(targetCatchNode) {
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(throwingNode.id) === -1) {
return;
}
var errorMessage;
if (msg) {
errorMessage = redUtil.cloneMessage(msg);
} else {
errorMessage = {};
}
if (errorMessage.hasOwnProperty("error")) {
errorMessage._error = errorMessage.error;
}
errorMessage.error = {
message: logMessage.toString(),
source: {
id: node.id,
type: node.type,
name: node.name,
count: count
}
};
targetCatchNode.receive(errorMessage);
handled = true;
});
}
if (!handled) {
throwingNode = activeNodes[throwingNode.z];
}
}
}
}
function getID() {
return (1+Math.random()*4294967295).toString(16);
}
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
function mapEnvVarProperties(obj,prop) {
if (Buffer.isBuffer(obj[prop])) {
return;
} else if (Array.isArray(obj[prop])) {
for (var i=0;i<obj[prop].length;i++) {
mapEnvVarProperties(obj[prop],i);
}
} else if (typeof obj[prop] === 'string') {
var m;
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) {
if (process.env.hasOwnProperty(m[1])) {
obj[prop] = process.env[m[1]];
}
}
} else {
for (var p in obj[prop]) {
if (obj[prop].hasOwnProperty) {
mapEnvVarProperties(obj[prop],p);
}
}
}
}
function createNode(type,config) {
// console.log("CREATE",type,config.id);
var nn = null;
var nt = typeRegistry.get(type);
if (nt) {
var conf = clone(config);
delete conf.credentials;
for (var p in conf) {
if (conf.hasOwnProperty(p)) {
mapEnvVarProperties(conf,p);
}
}
try {
nn = new nt(conf);
}
catch (err) {
Log.log({
level: Log.ERROR,
id:conf.id,
type: type,
msg: err
});
}
} else {
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
}
return nn;
}
function createSubflow(sf,sfn,subflows,globalSubflows,activeNodes) {
//console.log("CREATE SUBFLOW",sf.id,sfn.id);
var nodes = [];
var node_map = {};
var newNodes = [];
var node;
var wires;
var i,j,k;
var createNodeInSubflow = function(def) {
node = clone(def);
var nid = getID();
node_map[node.id] = node;
node._alias = node.id;
node.id = nid;
node.z = sfn.id;
newNodes.push(node);
}
// Clone all of the subflow node definitions and give them new IDs
for (i in sf.configs) {
if (sf.configs.hasOwnProperty(i)) {
createNodeInSubflow(sf.configs[i]);
}
}
// Clone all of the subflow node definitions and give them new IDs
for (i in sf.nodes) {
if (sf.nodes.hasOwnProperty(i)) {
createNodeInSubflow(sf.nodes[i]);
}
}
// Look for any catch/status nodes and update their scope ids
// Update all subflow interior wiring to reflect new node IDs
for (i=0;i<newNodes.length;i++) {
node = newNodes[i];
if (node.wires) {
var outputs = node.wires;
for (j=0;j<outputs.length;j++) {
wires = outputs[j];
for (k=0;k<wires.length;k++) {
outputs[j][k] = node_map[outputs[j][k]].id
}
}
if ((node.type === 'catch' || node.type === 'status') && node.scope) {
node.scope = node.scope.map(function(id) {
return node_map[id]?node_map[id].id:""
})
} else {
for (var prop in node) {
if (node.hasOwnProperty(prop) && prop !== '_alias') {
if (node_map[node[prop]]) {
//console.log("Mapped",node.type,node.id,prop,node_map[node[prop]].id);
node[prop] = node_map[node[prop]].id;
}
}
}
}
}
}
// Create a subflow node to accept inbound messages and route appropriately
var Node = require("../Node");
var subflowInstance = {
id: sfn.id,
type: sfn.type,
z: sfn.z,
name: sfn.name,
wires: []
}
if (sf.in) {
subflowInstance.wires = sf.in.map(function(n) { return n.wires.map(function(w) { return node_map[w.id].id;})})
subflowInstance._originalWires = clone(subflowInstance.wires);
}
var subflowNode = new Node(subflowInstance);
subflowNode.on("input", function(msg) { this.send(msg);});
subflowNode._updateWires = subflowNode.updateWires;
subflowNode.updateWires = function(newWires) {
// Wire the subflow outputs
if (sf.out) {
var node,wires,i,j;
// Restore the original wiring to the internal nodes
subflowInstance.wires = clone(subflowInstance._originalWires);
for (i=0;i<sf.out.length;i++) {
wires = sf.out[i].wires;
for (j=0;j<wires.length;j++) {
if (wires[j].id != sf.id) {
node = node_map[wires[j].id];
if (node._originalWires) {
node.wires = clone(node._originalWires);
}
}
}
}
var modifiedNodes = {};
var subflowInstanceModified = false;
for (i=0;i<sf.out.length;i++) {
wires = sf.out[i].wires;
for (j=0;j<wires.length;j++) {
if (wires[j].id === sf.id) {
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(newWires[i]);
subflowInstanceModified = true;
} else {
node = node_map[wires[j].id];
node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
modifiedNodes[node.id] = node;
}
}
}
Object.keys(modifiedNodes).forEach(function(id) {
var node = modifiedNodes[id];
subflowNode.instanceNodes[id].updateWires(node.wires);
});
if (subflowInstanceModified) {
subflowNode._updateWires(subflowInstance.wires);
}
}
}
nodes.push(subflowNode);
// Wire the subflow outputs
if (sf.out) {
var modifiedNodes = {};
for (i=0;i<sf.out.length;i++) {
wires = sf.out[i].wires;
for (j=0;j<wires.length;j++) {
if (wires[j].id === sf.id) {
// A subflow input wired straight to a subflow output
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(sfn.wires[i])
subflowNode._updateWires(subflowInstance.wires);
} else {
node = node_map[wires[j].id];
modifiedNodes[node.id] = node;
if (!node._originalWires) {
node._originalWires = clone(node.wires);
}
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(sfn.wires[i]);
}
}
}
}
// Instantiate the nodes
for (i=0;i<newNodes.length;i++) {
node = newNodes[i];
var type = node.type;
var m = /^subflow:(.+)$/.exec(type);
if (!m) {
var newNode = createNode(type,node);
activeNodes[node.id] = newNode;
nodes.push(newNode);
} else {
var subflowId = m[1];
nodes = nodes.concat(createSubflow(subflows[subflowId]||globalSubflows[subflowId],node,subflows,globalSubflows,activeNodes));
}
}
subflowNode.instanceNodes = {};
nodes.forEach(function(node) {
subflowNode.instanceNodes[node.id] = node;
});
return nodes;
}
module.exports = {
create: function(global,conf) {
return new Flow(global,conf);
}
}

392
red/nodes/flows/index.js Normal file
View File

@@ -0,0 +1,392 @@
/**
* 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 clone = require("clone");
var when = require("when");
var Flow = require('./Flow');
var typeRegistry = require("../registry");
var credentials = require("../credentials");
var flowUtil = require("./util");
var log = require("../../log");
var events = require("../../events");
var redUtil = require("../../util");
var deprecated = require("../registry/deprecated");
var storage = null;
var settings = null;
var activeConfig = null;
var activeFlowConfig = null;
var activeFlows = {};
var started = false;
var activeNodesToFlow = {};
var subflowInstanceNodeMap = {};
var typeEventRegistered = false;
function init(_settings, _storage) {
if (started) {
throw new Error("Cannot init without a stop");
}
settings = _settings;
storage = _storage;
started = false;
if (!typeEventRegistered) {
events.on('type-registered',function(type) {
if (activeFlowConfig && activeFlowConfig.missingTypes.length > 0) {
var i = activeFlowConfig.missingTypes.indexOf(type);
if (i != -1) {
log.info(log._("nodes.flows.registered-missing", {type:type}));
activeFlowConfig.missingTypes.splice(i,1);
if (activeFlowConfig.missingTypes.length === 0 && started) {
start();
}
}
}
});
typeEventRegistered = true;
}
}
function load() {
return storage.getFlows().then(function(flows) {
return credentials.load().then(function() {
return setConfig(flows,"load");
});
}).otherwise(function(err) {
log.warn(log._("nodes.flows.error",{message:err.toString()}));
console.log(err.stack);
});
}
function setConfig(_config,type) {
var config = clone(_config);
type = type||"full";
var credentialsChanged = false;
var credentialSavePromise = null;
var configSavePromise = null;
var diff;
var newFlowConfig = flowUtil.parseConfig(clone(config));
if (type !== 'full' && type !== 'load') {
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
}
config.forEach(function(node) {
if (node.credentials) {
credentials.extract(node);
credentialsChanged = true;
}
});
if (credentialsChanged) {
credentialSavePromise = credentials.save();
} else {
credentialSavePromise = when.resolve();
}
if (type === 'load') {
configSavePromise = credentialSavePromise;
type = 'full';
} else {
configSavePromise = credentialSavePromise.then(function() {
return storage.saveFlows(config);
});
}
return configSavePromise
.then(function() {
activeConfig = config;
activeFlowConfig = newFlowConfig;
return credentials.clean(activeConfig).then(function() {
if (started) {
return stop(type,diff).then(function() {
start(type,diff);
}).otherwise(function(err) {
})
}
});
});
}
function getNode(id) {
var node;
if (activeNodesToFlow[id]) {
return activeFlows[activeNodesToFlow[id]].getNode(id);
}
for (var flowId in activeFlows) {
if (activeFlows.hasOwnProperty(flowId)) {
node = activeFlows[flowId].getNode(id);
if (node) {
return node;
}
}
}
return null;
}
function eachNode(cb) {
for (var id in activeFlowConfig.allNodes) {
if (activeFlowConfig.allNodes.hasOwnProperty(id)) {
cb(activeFlowConfig.allNodes[id]);
}
}
}
function getConfig() {
return activeConfig;
}
function delegateError(node,logMessage,msg) {
if (activeFlows[node.z]) {
activeFlows[node.z].handleError(node,logMessage,msg);
} else if (activeNodesToFlow[node.z]) {
activeFlows[activeNodesToFlow[node.z]].handleError(node,logMessage,msg);
} else if (activeFlowConfig.subflows[node.z]) {
subflowInstanceNodeMap[node.id].forEach(function(n) {
delegateError(getNode(n),logMessage,msg);
});
}
}
function handleError(node,logMessage,msg) {
if (node.z) {
delegateError(node,logMessage,msg);
} else {
if (activeFlowConfig.configs[node.id]) {
activeFlowConfig.configs[node.id]._users.forEach(function(id) {
var userNode = activeFlowConfig.allNodes[id];
delegateError(userNode,logMessage,msg);
})
}
}
}
function delegateStatus(node,statusMessage) {
if (activeFlows[node.z]) {
activeFlows[node.z].handleStatus(node,statusMessage);
}
}
function handleStatus(node,statusMessage) {
if (node.z) {
delegateStatus(node,statusMessage);
} else {
if (activeFlowConfig.configs[node.id]) {
activeFlowConfig.configs[node.id]._users.forEach(function(id) {
var userNode = activeFlowConfig.allNodes[id];
delegateStatus(userNode,statusMessage);
})
}
}
}
function start(type,diff) {
type = type||"full";
started = true;
var i;
if (activeFlowConfig.missingTypes.length > 0) {
log.info(log._("nodes.flows.missing-types"));
var knownUnknowns = 0;
for (i=0;i<activeFlowConfig.missingTypes.length;i++) {
var nodeType = activeFlowConfig.missingTypes[i];
var info = deprecated.get(nodeType);
if (info) {
log.info(log._("nodes.flows.missing-type-provided",{type:activeFlowConfig.missingTypes[i],module:info.module}));
knownUnknowns += 1;
} else {
log.info(" - "+activeFlowConfig.missingTypes[i]);
}
}
if (knownUnknowns > 0) {
log.info(log._("nodes.flows.missing-type-install-1"));
log.info(" npm install <module name>");
log.info(log._("nodes.flows.missing-type-install-2"));
log.info(" "+settings.userDir);
}
return;
}
if (diff) {
log.info(log._("nodes.flows.starting-modified-"+type));
} else {
log.info(log._("nodes.flows.starting-flows"));
}
var id;
if (!diff) {
activeFlows['_GLOBAL_'] = Flow.create(activeFlowConfig);
for (id in activeFlowConfig.flows) {
if (activeFlowConfig.flows.hasOwnProperty(id)) {
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
}
}
} else {
activeFlows['_GLOBAL_'].update(activeFlowConfig,activeFlowConfig);
for (id in activeFlowConfig.flows) {
if (activeFlowConfig.flows.hasOwnProperty(id)) {
if (activeFlows[id]) {
activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]);
} else {
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
}
}
}
}
for (id in activeFlows) {
if (activeFlows.hasOwnProperty(id)) {
activeFlows[id].start(diff);
var activeNodes = activeFlows[id].getActiveNodes();
Object.keys(activeNodes).forEach(function(nid) {
activeNodesToFlow[nid] = id;
if (activeNodes[nid]._alias) {
subflowInstanceNodeMap[activeNodes[nid]._alias] = subflowInstanceNodeMap[activeNodes[nid]._alias] || [];
subflowInstanceNodeMap[activeNodes[nid]._alias].push(nid);
}
});
}
}
events.emit("nodes-started");
if (diff) {
log.info(log._("nodes.flows.started-modified-"+type));
} else {
log.info(log._("nodes.flows.started-flows"));
}
}
function stop(type,diff) {
type = type||"full";
if (diff) {
log.info(log._("nodes.flows.stopping-modified-"+type));
} else {
log.info(log._("nodes.flows.stopping-flows"));
}
started = false;
var promises = [];
var stopList;
if (type === 'nodes') {
stopList = diff.changed.concat(diff.removed);
} else if (type === 'flows') {
stopList = diff.changed.concat(diff.removed).concat(diff.linked);
}
for (var id in activeFlows) {
if (activeFlows.hasOwnProperty(id)) {
promises = promises.concat(activeFlows[id].stop(stopList));
if (!diff || diff.removed[id]) {
delete activeFlows[id];
}
}
}
return when.promise(function(resolve,reject) {
when.settle(promises).then(function() {
for (id in activeNodesToFlow) {
if (activeNodesToFlow.hasOwnProperty(id)) {
if (!activeFlows[activeNodesToFlow[id]]) {
delete activeNodesToFlow[id];
}
}
}
if (stopList) {
stopList.forEach(function(id) {
delete activeNodesToFlow[id];
});
}
// Ideally we'd prune just what got stopped - but mapping stopList
// id to the list of subflow instance nodes is something only Flow
// can do... so cheat by wiping the map knowing it'll be rebuilt
// in start()
subflowInstanceNodeMap = {};
if (diff) {
log.info(log._("nodes.flows.stopped-modified-"+type));
} else {
log.info(log._("nodes.flows.stopped-flows"));
}
resolve();
});
});
}
function checkTypeInUse(id) {
var nodeInfo = typeRegistry.getNodeInfo(id);
if (!nodeInfo) {
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
} else {
var inUse = {};
var config = getConfig();
config.forEach(function(n) {
inUse[n.type] = (inUse[n.type]||0)+1;
});
var nodesInUse = [];
nodeInfo.types.forEach(function(t) {
if (inUse[t]) {
nodesInUse.push(t);
}
});
if (nodesInUse.length > 0) {
var msg = nodesInUse.join(", ");
var err = new Error(log._("nodes.index.type-in-use", {msg:msg}));
err.code = "type_in_use";
throw err;
}
}
}
module.exports = {
init: init,
/**
* Load the current flow configuration from storage
* @return a promise for the loading of the config
*/
load: load,
get:getNode,
eachNode: eachNode,
/**
* Gets the current flow configuration
*/
getFlows: getConfig,
/**
* Sets the current active config.
* @param config the configuration to enable
* @param type the type of deployment to do: full (default), nodes, flows, load
* @return a promise for the saving/starting of the new flow
*/
setFlows: setConfig,
/**
* Starts the current flow configuration
*/
startFlows: start,
/**
* Stops the current flow configuration
* @return a promise for the stopping of the flow
*/
stopFlows: stop,
handleError: handleError,
handleStatus: handleStatus,
checkTypeInUse: checkTypeInUse
};

327
red/nodes/flows/util.js Normal file
View File

@@ -0,0 +1,327 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var clone = require("clone");
var redUtil = require("../../util");
var subflowInstanceRE = /^subflow:(.+)$/;
var typeRegistry = require("../registry");
function diffNodes(oldNode,newNode) {
if (oldNode == null) {
return true;
}
var oldKeys = Object.keys(oldNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
var newKeys = Object.keys(newNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
if (oldKeys.length != newKeys.length) {
return true;
}
for (var i=0;i<newKeys.length;i++) {
var p = newKeys[i];
if (!redUtil.compareObjects(oldNode[p],newNode[p])) {
return true;
}
}
return false;
}
module.exports = {
diffNodes: diffNodes,
parseConfig: function(config) {
var flow = {};
flow.allNodes = {};
flow.subflows = {};
flow.configs = {};
flow.flows = {};
flow.missingTypes = [];
config.forEach(function(n) {
flow.allNodes[n.id] = clone(n);
if (n.type === 'tab') {
flow.flows[n.id] = n;
flow.flows[n.id].subflows = {};
flow.flows[n.id].configs = {};
flow.flows[n.id].nodes = {};
}
});
config.forEach(function(n) {
if (n.type === 'subflow') {
flow.subflows[n.id] = n;
flow.subflows[n.id].configs = {};
flow.subflows[n.id].nodes = {};
flow.subflows[n.id].instances = [];
}
});
config.forEach(function(n) {
if (n.type !== 'subflow' && n.type !== 'tab') {
var subflowDetails = subflowInstanceRE.exec(n.type);
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
if (flow.missingTypes.indexOf(n.type) === -1) {
flow.missingTypes.push(n.type);
}
} else {
var container = null;
if (flow.flows[n.z]) {
container = flow.flows[n.z];
} else if (flow.subflows[n.z]) {
container = flow.subflows[n.z];
}
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
if (subflowDetails) {
var subflowType = subflowDetails[1]
n.subflow = subflowType;
flow.subflows[subflowType].instances.push(n)
}
if (container) {
container.nodes[n.id] = n;
}
} else {
if (container) {
container.configs[n.id] = n;
} else {
flow.configs[n.id] = n;
flow.configs[n.id]._users = [];
}
}
}
}
});
config.forEach(function(n) {
if (n.type !== 'subflow' && n.type !== 'tab') {
for (var prop in n) {
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== '_users' && flow.configs[n[prop]]) {
// This property references a global config node
flow.configs[n[prop]]._users.push(n.id)
}
}
}
});
return flow;
},
diffConfigs: function(oldConfig, newConfig) {
var id;
var node;
var nn;
var wires;
var j,k;
var changedSubflows = {};
var added = {};
var removed = {};
var changed = {};
var wiringChanged = {};
var linkMap = {};
for (id in oldConfig.allNodes) {
if (oldConfig.allNodes.hasOwnProperty(id)) {
node = oldConfig.allNodes[id];
// build the map of what this node was previously wired to
if (node.wires) {
linkMap[node.id] = linkMap[node.id] || [];
for (j=0;j<node.wires.length;j++) {
wires = node.wires[j];
for (k=0;k<wires.length;k++) {
linkMap[node.id].push(wires[k]);
nn = oldConfig.allNodes[wires[k]];
if (nn) {
linkMap[nn.id] = linkMap[nn.id] || [];
linkMap[nn.id].push(node.id);
}
}
}
}
// This node has been removed
if (!newConfig.allNodes.hasOwnProperty(id)) {
removed[id] = node;
// Mark the container as changed
if (newConfig.allNodes[removed[id].z]) {
changed[removed[id].z] = newConfig.allNodes[removed[id].z];
if (changed[removed[id].z].type === "subflow") {
changedSubflows[removed[id].z] = changed[removed[id].z];
delete removed[id];
}
}
} else {
// This node has a material configuration change
if (diffNodes(node,newConfig.allNodes[id]) || newConfig.allNodes[id].credentials) {
changed[id] = newConfig.allNodes[id];
if (changed[id].type === "subflow") {
changedSubflows[id] = changed[id];
}
// Mark the container as changed
if (newConfig.allNodes[changed[id].z]) {
changed[changed[id].z] = newConfig.allNodes[changed[id].z];
if (changed[changed[id].z].type === "subflow") {
changedSubflows[changed[id].z] = changed[changed[id].z];
delete changed[id];
}
}
}
// This node's wiring has changed
if (!redUtil.compareObjects(node.wires,newConfig.allNodes[id].wires)) {
wiringChanged[id] = newConfig.allNodes[id];
// Mark the container as changed
if (newConfig.allNodes[wiringChanged[id].z]) {
changed[wiringChanged[id].z] = newConfig.allNodes[wiringChanged[id].z];
if (changed[wiringChanged[id].z].type === "subflow") {
changedSubflows[wiringChanged[id].z] = changed[wiringChanged[id].z];
delete wiringChanged[id];
}
}
}
}
}
}
// Look for added nodes
for (id in newConfig.allNodes) {
if (newConfig.allNodes.hasOwnProperty(id)) {
node = newConfig.allNodes[id];
// build the map of what this node is now wired to
if (node.wires) {
linkMap[node.id] = linkMap[node.id] || [];
for (j=0;j<node.wires.length;j++) {
wires = node.wires[j];
for (k=0;k<wires.length;k++) {
if (linkMap[node.id].indexOf(wires[k]) === -1) {
linkMap[node.id].push(wires[k]);
}
nn = newConfig.allNodes[wires[k]];
if (nn) {
linkMap[nn.id] = linkMap[nn.id] || [];
if (linkMap[nn.id].indexOf(node.id) === -1) {
linkMap[nn.id].push(node.id);
}
}
}
}
}
// This node has been added
if (!oldConfig.allNodes.hasOwnProperty(id)) {
added[id] = node;
// Mark the container as changed
if (newConfig.allNodes[added[id].z]) {
changed[added[id].z] = newConfig.allNodes[added[id].z];
if (changed[added[id].z].type === "subflow") {
changedSubflows[added[id].z] = changed[added[id].z];
delete added[id];
}
}
}
}
}
for (id in newConfig.allNodes) {
if (newConfig.allNodes.hasOwnProperty(id)) {
node = newConfig.allNodes[id];
for (var prop in node) {
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
// This node has a property that references a changed/removed node
// Assume it is a config node change and mark this node as
// changed.
if (changed[node[prop]] || removed[node[prop]]) {
if (!changed[node.id]) {
changed[node.id] = node;
if (newConfig.allNodes[node.z]) {
changed[node.z] = newConfig.allNodes[node.z];
if (changed[node.z].type === "subflow") {
changedSubflows[node.z] = changed[node.z];
delete changed[node.id];
}
}
}
}
}
}
}
}
// Recursively mark all instances of changed subflows as changed
var changedSubflowStack = Object.keys(changedSubflows);
while(changedSubflowStack.length > 0) {
var subflowId = changedSubflowStack.pop();
for (id in newConfig.allNodes) {
if (newConfig.allNodes.hasOwnProperty(id)) {
node = newConfig.allNodes[id];
if (node.type === 'subflow:'+subflowId) {
if (!changed[node.id]) {
changed[node.id] = node;
if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) {
changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z];
if (newConfig.allNodes[changed[node.id].z].type === "subflow") {
// This subflow instance is inside a subflow. Add the
// containing subflow to the stack to mark
changedSubflowStack.push(changed[node.id].z);
delete changed[node.id];
}
}
}
}
}
}
}
var diff = {
added:Object.keys(added),
changed:Object.keys(changed),
removed:Object.keys(removed),
rewired:Object.keys(wiringChanged),
linked:[]
}
// Traverse the links of all modified nodes to mark the connected nodes
var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
var visited = {};
while(modifiedNodes.length > 0) {
node = modifiedNodes.pop();
if (!visited[node]) {
visited[node] = true;
if (linkMap[node]) {
if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) {
diff.linked.push(node);
}
modifiedNodes = modifiedNodes.concat(linkMap[node]);
}
}
}
// for (id in newConfig.allNodes) {
// console.log(
// (added[id]?"+":(changed[id]?"!":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"~":" "),
// id,
// newConfig.allNodes[id].type,
// newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
// );
// }
// for (id in removed) {
// console.log(
// "- "+(diff.linked.indexOf(id)!==-1?"~":" "),
// id,
// oldConfig.allNodes[id].type,
// oldConfig.allNodes[id].name||oldConfig.allNodes[id].label||""
// );
// }
return diff;
}
}

View File

@@ -13,12 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var when = require("when");
var path = require("path");
var fs = require("fs");
var registry = require("./registry");
var credentials = require("./credentials");
var flows = require("./flows");
var Node = require("./Node");
var log = require("../log");
var events = require("../events");
var child_process = require('child_process');
var settings;
/**
* Registers a node constructor
* @param type - the string type name
@@ -53,59 +64,30 @@ function createNode(node,def) {
}
}
function init(_settings,storage,app) {
credentials.init(storage,app);
function init(_settings,storage) {
settings = _settings;
credentials.init(storage);
flows.init(_settings,storage);
registry.init(_settings);
}
function checkTypeInUse(id) {
var nodeInfo = registry.getNodeInfo(id);
if (!nodeInfo) {
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
} else {
var inUse = {};
var config = flows.getFlows();
config.forEach(function(n) {
inUse[n.type] = (inUse[n.type]||0)+1;
});
var nodesInUse = [];
nodeInfo.types.forEach(function(t) {
if (inUse[t]) {
nodesInUse.push(t);
}
});
if (nodesInUse.length > 0) {
var msg = nodesInUse.join(", ");
var err = new Error(log._("nodes.index.type-in-use", {msg:msg}));
err.code = "type_in_use";
throw err;
}
}
function disableNode(id) {
flows.checkTypeInUse(id);
return registry.disableNode(id);
}
function removeNode(id) {
checkTypeInUse(id);
return registry.removeNode(id);
}
function removeModule(module) {
function uninstallModule(module) {
var info = registry.getModuleInfo(module);
if (!info) {
throw new Error(log._("nodes.index.unrecognised-module", {module:module}));
} else {
for (var i=0;i<info.nodes.length;i++) {
checkTypeInUse(module+"/"+info.nodes[i].name);
flows.checkTypeInUse(module+"/"+info.nodes[i].name);
}
return registry.removeModule(module);
return registry.uninstallModule(module);
}
}
function disableNode(id) {
checkTypeInUse(id);
return registry.disableNode(id);
}
module.exports = {
// Lifecycle
init: init,
@@ -116,9 +98,8 @@ module.exports = {
getNode: flows.get,
eachNode: flows.eachNode,
addFile: registry.addFile,
addModule: registry.addModule,
removeModule: removeModule,
installModule: registry.installModule,
uninstallModule: uninstallModule,
enableNode: registry.enableNode,
disableNode: disableNode,
@@ -140,6 +121,7 @@ module.exports = {
// Flow handling
loadFlows: flows.load,
startFlows: flows.startFlows,
stopFlows: flows.stopFlows,
setFlows: flows.setFlows,
getFlows: flows.getFlows,
@@ -147,6 +129,6 @@ module.exports = {
// Credentials
addCredentials: credentials.add,
getCredentials: credentials.get,
deleteCredentials: credentials.delete
deleteCredentials: credentials.delete,
getCredentialDefinition: credentials.getDefinition
};

View File

@@ -21,11 +21,13 @@ var path = require("path");
var events = require("../../events");
var registry = require("./registry");
var loader = require("./loader");
var installer = require("./installer");
var settings;
function init(_settings) {
settings = _settings;
installer.init(settings);
loader.init(settings);
registry.init(settings,loader);
}
@@ -36,12 +38,6 @@ function load(defaultNodesDir,disableNodePathScan) {
return loader.load(defaultNodesDir,disableNodePathScan);
}
function addFile(file) {
var info = "node-red/"+path.basename(file).replace(/^\d+-/,"").replace(/\.js$/,"");
return loader.addFile(file).then(function() {
return registry.getNodeInfo(info);
});
}
function addModule(module) {
return loader.addModule(module).then(function() {
return registry.getModuleInfo(module);
@@ -79,9 +75,11 @@ module.exports = {
enableNode: enableNodeSet,
disableNode: registry.disableNodeSet,
addFile: addFile,
addModule: addModule,
removeModule: registry.removeModule,
installModule: installer.installModule,
uninstallModule: installer.uninstallModule,
cleanModuleList: registry.cleanModuleList
};

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