Compare commits

...

158 Commits

Author SHA1 Message Date
Nick O'Leary
d59bf84470 Update changelog 2019-03-15 19:04:31 +00:00
Nick O'Leary
161ee17f45 Bump for 0.20.2 2019-03-15 19:03:30 +00:00
Nick O'Leary
8aa00b0cfc Filter out duplicate nodes when importing a flow 2019-03-15 19:02:24 +00:00
Nick O'Leary
afe89c3621 Handle node configs with multiple external scripts properly
If the config had multiple scripts, we were calling the done
callback once for each script. This in turn led to duplicate
flows being loaded.
2019-03-15 18:50:58 +00:00
Nick O'Leary
bdf68311b4 Bump for 0.20.1 2019-03-15 11:22:01 +00:00
Nick O'Leary
afa69f4c0e Ensure all subflow instances are stopped when flow stopping
Fixes #2095
2019-03-15 09:13:32 +00:00
Nick O'Leary
6fe2b24592 Merge pull request #2091 from node-red-hitachi/korean
modify name of korean locale folders
2019-03-14 15:25:52 +00:00
李赫柱
7442b356e3 modify name of korean locale forders 2019-03-14 09:53:24 +09:00
Nick O'Leary
1d7be6457f Ensure node names are sanitized before being presented 2019-03-13 16:08:11 +00:00
Nick O'Leary
c9ff05ba80 Subflow status node must pass status to parent flow
Fixes #2087
2019-03-13 13:14:34 +00:00
Nick O'Leary
faae184f1c Merge pull request #2090 from node-red-hitachi/fix-file-node-encoding-menu
Fix problem on displaying option label on Firefox
2019-03-13 10:46:23 +00:00
Hiroyasu Nishiyama
515a8a9bbb fix problem on displaying option label on Firefox 2019-03-13 19:17:32 +09:00
Nick O'Leary
58914e5c5f Bump package to 0.20.0 2019-03-12 14:53:12 +00:00
Nick O'Leary
c944eaab5c Fix max call stack err when getting node from inside subflow 2019-03-12 14:40:47 +00:00
Nick O'Leary
d3d9533493 Search subflows instances when looking for given node id 2019-03-12 14:25:36 +00:00
Nick O'Leary
9c474cc089 Bump for 0.20 2019-03-12 10:59:20 +00:00
Nick O'Leary
3f1b0b986f Handle node name as unsanitized text in debug sidebar 2019-03-12 10:37:24 +00:00
Nick O'Leary
28e08ebaf5 Add envVarExcludes setting to block named env vars 2019-03-07 22:54:20 +00:00
Nick O'Leary
3213c03754 Update settings.js docs on userDir to match reality
Fixes #2082
2019-03-07 19:58:54 +00:00
Nick O'Leary
4447288a4c Merge pull request #2083 from node-red-hitachi/fix-typo-JP-info-httprequest
Fix typo in Japanese info text of httprequest node
2019-03-07 19:58:26 +00:00
Hiroyasu Nishiyama
eee4e83a1e fix minor typo in Japanese info text of httprequest node 2019-03-07 22:50:56 +09:00
Nick O'Leary
a67b492620 Merge pull request #2081 from node-red-hitachi/dev-korean
Add Korean Language
2019-03-07 10:02:42 +00:00
Nick O'Leary
6062ff2748 Merge pull request #2080 from node-red-hitachi/update-JP-info-httprequest
Update info text of httprequest node
2019-03-07 10:02:09 +00:00
Dave Conway-Jones
3b11195caa Add --no-update-notifier flag to npm calls to speedup processing. 2019-03-07 09:03:25 +00:00
李赫柱
9946ea111c Add Korean Language 2019-03-07 10:33:55 +09:00
Hiroyasu Nishiyama
7074d66f8e fix unmatched tag in English info text of httprequest node 2019-03-07 10:06:57 +09:00
Hiroyasu Nishiyama
008b26f329 update JP info text of httprequest node 2019-03-07 10:04:06 +09:00
Dave Conway-Jones
b246f0779f re-fix merge of file nodes 2019-03-06 22:33:20 +00:00
Hiroyasu Nishiyama
dc89218702 add encoding support to file in/out node (#2066)
* add encoding support to file in/out node

* update package.json

* change default encoding label: 'none' -> 'utf8[default]'

* add a missing message catalogue entry

* change default encoding label
2019-03-06 22:28:33 +00:00
Nick O'Leary
3c013b3533 Add file upload test for http request node 2019-03-06 21:21:35 +00:00
Nick O'Leary
fe0d0f08e4 Merge branch 'pr_2078' into dev 2019-03-06 15:51:16 +00:00
Nick O'Leary
38b5063038 Tidy up blockquote css style 2019-03-05 20:56:35 +00:00
Nick Kasten
e55481a454 conditional formData assignment only 2019-03-05 11:12:59 -06:00
Nick Kasten
7063a88513 improved info panel UI 2019-03-05 10:31:25 -06:00
Nick Kasten
a9bf3d0226 add multipart/form-data support to http request node 2019-03-05 09:55:21 -06:00
Nick O'Leary
781b3aff1b Merge branch 'dev' into pr_2076 2019-03-05 14:37:33 +00:00
Nick O'Leary
b011b9203b Keep subflow palette appearance in sync with edits 2019-03-05 14:37:07 +00:00
Nick O'Leary
39344fcae5 Fix background of tab select icon 2019-03-05 13:25:44 +00:00
Nick O'Leary
a046b357da Tidy up registry/loader api used by unit tests
Fixes #2073
2019-03-05 13:25:44 +00:00
Nick O'Leary
d8e4020cec Merge pull request #2075 from kazuhitoyokoi/dev-fixlibrarybug
Fix module name bug in import menu
2019-03-05 13:05:36 +00:00
Kazuhito Yokoi
f80b172022 Add condition to handle existing node 2019-03-05 21:29:03 +09:00
Kazuhito Yokoi
66fc4b536c Automatic placing of node icon 2019-03-05 21:21:23 +09:00
Kazuhito Yokoi
1f97ccdddb Fix module name bug in library 2019-03-05 19:38:56 +09:00
Nick O'Leary
308d6889a7 Update changelog 2019-03-05 10:18:40 +00:00
Nick O'Leary
c3b9982c44 Add --no-audit flag to npm commands to reduce overhead 2019-03-05 10:17:30 +00:00
Nick O'Leary
fab796e4e4 Modify trigger spec timings to reduce false-positive test runs 2019-03-05 09:48:43 +00:00
Nick O'Leary
749db6ba82 Merge pull request #2072 from node-red-hitachi/update-messages-JP
Update Japanese messages
2019-03-05 08:36:24 +00:00
Hiroyasu Nishiyama
12d6c4ddf5 add missing Japanese translation 2019-03-05 11:57:06 +09:00
Hiroyasu Nishiyama
430a03bb14 update JP message catalogue 2019-03-05 11:14:02 +09:00
Nick O'Leary
43f21fc7aa Add list-flows action and button 2019-03-04 22:37:51 +00:00
Nick O'Leary
b27da3d1a0 Update changelog 2019-03-04 16:15:06 +00:00
Nick O'Leary
4463a8e3b2 Add exportGlobalContextKeys to prevent exposing fgc keys 2019-03-04 16:10:39 +00:00
Nick O'Leary
9e74ddac48 Bump dependencies 2019-03-04 12:33:36 +00:00
Nick O'Leary
5f62e41d62 update changelog 2019-03-04 11:17:19 +00:00
Nick O'Leary
19a103d3a0 Add _session and event to WS/TCP Status messages 2019-03-04 11:09:18 +00:00
Nick O'Leary
8fb6bc059e Pass complete status to Status node and filter to editor 2019-03-04 10:23:10 +00:00
Nick O'Leary
8f61a0d258 Fix message catalog formatting 2019-03-03 12:05:20 +00:00
Nick O'Leary
7fa589e430 Merge pull request #2068 from node-red-hitachi/i18n-port-label
Add i18n support for port label
2019-03-03 11:52:20 +00:00
Nick O'Leary
6d8d826764 Merge branch 'dev' into i18n-port-label 2019-03-03 11:52:12 +00:00
Nick O'Leary
a40e84e1f6 Merge pull request #2070 from bartbutenaers/dev
Fix hiding Bearer token
2019-03-03 11:49:48 +00:00
bartbutenaers
4844c2123f Fix hiding Bearer token 2019-03-01 23:18:48 +01:00
Nick O'Leary
236d437430 Add api docs landing content 2019-02-28 22:21:22 +00:00
Hiroyasu Nishiyama
ae726c199b add i18n support for port label of inject/exec/httprequest/file node 2019-02-28 20:22:33 +09:00
Nick O'Leary
e7f54f005c Merge pull request #2060 from jeancarl/dev
Transfer input attributes (placeholder, type) to generated TypedInput field
2019-02-28 10:50:15 +00:00
bartbutenaers
e7b1ec6904 Add Digest and Bearer Auth modes to http request node (#2061)
* Authentication methods

* Authentication methods

* Authentication methods

* Support undefined auth type

* Support undefined auth type

* Apply basic auth on existing nodes

* Use password as bearer token

* Use password as bearer token

* Switch between password/token labels

* Bearer token abbreviation

* Separate token span
2019-02-27 22:15:31 +00:00
Nick O'Leary
f4f664a4a2 Ensure flows wait for all nodes to close before restarting
Fixes #2067
2019-02-27 20:56:58 +00:00
Nick O'Leary
fec52a8151 Merge branch 'master' into dev 2019-02-26 21:44:36 +00:00
Nick O'Leary
d8b4c1e209 Rename issue templates to fix their order 2019-02-26 21:14:25 +00:00
Nick O'Leary
eac853c7dd Update issue templates 2019-02-26 21:12:42 +00:00
Nick O'Leary
a04337a270 Merge branch 'master' into dev 2019-02-25 14:46:25 +00:00
Nick O'Leary
50d7e16365 Bump for 0.19.6 2019-02-25 14:40:08 +00:00
Nick O'Leary
ef7bc931b7 Merge pull request #2051 from node-red-hitachi/fix-multi-byte-output
Fix output of multi-byte string with file node
2019-02-25 10:48:23 +00:00
Nick O'Leary
41de771074 Fix git clone with password protected key 2019-02-21 22:44:56 +00:00
Dave Conway-Jones
2ebdd6c5cb let join node handle merged objects with repeated properties and honour parts
to close issue from slack re repeated {"d":"d","d":"d","d":"d"}  messages
2019-02-20 00:11:31 +00:00
Nick O'Leary
b51cfcc753 Merge pull request #2062 from kazuhitoyokoi/dev-fixtypo
Fix typo and update message catalog
2019-02-19 09:35:35 +00:00
Kazuhito Yokoi
91cc03dd80 Fix typo and update message catalog 2019-02-19 14:24:37 +09:00
Nick O'Leary
9d673a213e Use absolute flow file path in project settings
This ensures the diff logic can recognise the project flow file
and apply merge resolution to paths that git knows
2019-02-17 22:18:40 +00:00
JeanCarl
97e789538e Transfer placeholder and type to generated TypedInput field 2019-02-15 14:40:19 -08:00
Nick O'Leary
e05ff01d57 Allow a project to be located below the root of repo 2019-02-15 22:11:25 +00:00
Dave Conway-Jones
0748dff355 And fix the JSON node test 2019-02-15 17:16:27 +00:00
Dave Conway-Jones
28d4084aa0 ensure JSON node handles single booleans and numbers 2019-02-15 17:07:11 +00:00
Nick O'Leary
afd2ccfb4f Detect the cloning of an empty git repo properly 2019-02-14 14:00:25 +00:00
Nick O'Leary
057127f4de Hitting enter in Comment node name field clicks markdown button
When Enter is pressed in a form, the browser will find the first
submittable element and trigger it. By default <button> elements
have type set to 'submit' which causes them to be targetted by
this behaviour.

Adding `type="button"` prevents this behaviour. This change
targets some main offenders - in particular the markdown toolbar.

There are of lots of other `<button>` elements without this attribute
set, so they need tidying up. Not currently aware of any others that
exist in a <Form> so may be immune from this behaviour.
2019-02-13 20:41:34 +00:00
Dave Conway-Jones
2937b25d6d Shift status text left if no shape specified 2019-02-13 14:36:36 +00:00
Nick O'Leary
419f26db87 Fix use of custom auth strategy plugins 2019-02-12 10:45:38 +00:00
Nick O'Leary
be1b9c0e43 Handle treeList labels as text not html 2019-02-11 16:15:25 +00:00
Nick O'Leary
894d28c60b Remove remnants of when library in git/index
Fixes #2057
2019-02-11 09:01:40 +00:00
Nick O'Leary
06cc08d9f7 Better align node status text to status dot 2019-02-09 21:27:54 +00:00
Nick O'Leary
75393c0b28 Clear subflow status no close 2019-02-09 21:24:31 +00:00
Nick O'Leary
bdc1da70c1 Change subflow edit dialog titles 2019-02-09 21:20:20 +00:00
Nick O'Leary
7cef990ba6 Resize subflow edit dialog properly 2019-02-09 20:44:21 +00:00
Nick O'Leary
fb0f12bb20 Bump to 0.20.0-beta.5 2019-02-08 10:41:22 +00:00
Nick O'Leary
e94b8d3e84 Update changelog 2019-02-08 10:41:01 +00:00
Nick O'Leary
8c00e1fdf4 Bump dependencies 2019-02-08 10:35:06 +00:00
Nick O'Leary
a31fa82284 Merge pull request #2056 from node-red-hitachi/update-logic-nodes-info-jp
Update Japanese info text of logic nodes
2019-02-08 09:38:17 +00:00
Nick O'Leary
5d0af45d8f Merge pull request #2055 from node-red-hitachi/update-io-nodes-info-jp
Update Japanese info text of io nodes
2019-02-08 09:38:04 +00:00
Hiroyasu Nishiyama
e9f248020e update Japanese info text of split node 2019-02-08 09:46:35 +09:00
Hiroyasu Nishiyama
a8e1058af6 fix typos in Japanese info text of range node 2019-02-08 09:35:02 +09:00
Hiroyasu Nishiyama
1a087fd799 update info text of switch node 2019-02-08 09:32:07 +09:00
Hiroyasu Nishiyama
50c81533e0 fix header level of switch node info text 2019-02-08 09:26:11 +09:00
Hiroyasu Nishiyama
5eab9aa4b1 fix typos in tcpin node info text 2019-02-08 09:14:32 +09:00
Hiroyasu Nishiyama
1970cbfe37 fix mismatched p-tag 2019-02-08 09:05:29 +09:00
Hiroyasu Nishiyama
6d736201f9 fix unmatched p-tag 2019-02-08 08:58:59 +09:00
Nick O'Leary
51ec52b573 Merge pull request #2053 from node-red-hitachi/update-node-messages-jp
Update Japanese message catalogue of core nodes
2019-02-07 22:00:55 +00:00
Nick O'Leary
d099387186 Merge pull request #2054 from node-red-hitachi/update-core-nodes-info-jp
Update Japanese info text of core nodes
2019-02-07 21:59:57 +00:00
Hiroyasu Nishiyama
3f91e4da66 update Japanese info text of template node 2019-02-07 23:35:09 +09:00
Hiroyasu Nishiyama
4124159378 update Japanese info text of function node 2019-02-07 23:24:12 +09:00
Hiroyasu Nishiyama
18f3789e29 update Japanese info text of catch node 2019-02-07 23:15:06 +09:00
Hiroyasu Nishiyama
a713c92530 convert to buffer before write 2019-02-07 22:46:21 +09:00
Hiroyasu Nishiyama
7828af591e update Japanese message catalogue of core nodes 2019-02-07 20:03:16 +09:00
Nick O'Leary
d432dba726 Merge pull request #2052 from node-red-hitachi/update-editor-messages-jp
Update Japanese editor message catalogue
2019-02-07 09:30:13 +00:00
Nick O'Leary
72ae87857f Delete package-lock.json 2019-02-07 09:29:38 +00:00
Nick O'Leary
724acff591 Properly sanitize node names in deploy warning dialogs 2019-02-07 09:11:06 +00:00
Hiroyasu Nishiyama
482b432e2c update Japanese message catalogue for JSONata 2019-02-07 12:49:36 +09:00
Hiroyasu Nishiyama
351c0cb0a8 add missing colon 2019-02-07 12:33:36 +09:00
Hiroyasu Nishiyama
314a0fb5d6 update Japanese message catalog 2019-02-07 12:28:59 +09:00
Nick O'Leary
a301bf8bf5 Fix XSS issues in library ui code 2019-02-06 22:25:25 +00:00
Nick O'Leary
37b3601c47 Link Node - scroll to current flow in node list 2019-02-06 15:38:35 +00:00
Nick O'Leary
6e944485f0 Merge pull request #2030 from node-red-hitachi/scope-parent
Allow access of scope parent
2019-02-06 14:10:57 +00:00
Nick O'Leary
431266069e Merge pull request #2050 from node-red/subflow-props
Display parent subflow properties in subflow instance edit dialog
2019-02-06 14:06:22 +00:00
Nick O'Leary
d48a09e68b Add env type to subflow env var types
Also remove date and regex types
2019-02-06 13:58:31 +00:00
Hiroyasu Nishiyama
1db1ec7b5e fix encoding of file node from binary to utf8 2019-02-06 21:53:23 +09:00
Nick O'Leary
2a8f0a4eab Display parent subflow properties in edit dialog 2019-02-05 23:08:39 +00:00
Nick O'Leary
79f3669fac Add 'catch uncaught only' mode to Catch node
Closes #1747

This was inspired by a PR from @mauriciom75 but implemented in a different way
due to some of the internal reworking done to Flow and Subflow in the dev branch
2019-02-05 14:29:50 +00:00
Nick O'Leary
aab0f2dcd5 Merge pull request #2047 from node-red-hitachi/fix-use-common-i18n-label
Fix use of i18n label
2019-02-05 08:31:38 +00:00
Nick O'Leary
a47831e278 Merge pull request #2049 from kazuhitoyokoi/dev-fixbug4outoputinsubflow
Fix direction value of subflow output
2019-02-05 08:31:04 +00:00
Kazuhito Yokoi
f1a5e8a42c Fix direction value of subflow output 2019-02-05 16:27:02 +09:00
Hiroyasu Nishiyama
723e9b3cba make $parent access without key return undefined 2019-02-05 14:47:30 +09:00
Hiroyasu Nishiyama
ff759a8074 use common i18 label for variable name placeholder 2019-02-05 13:12:21 +09:00
Nick O'Leary
4de1056d82 Tidy up HTTP Request payload to GET params work 2019-02-04 21:30:11 +00:00
Nick O'Leary
884b8da8bf Merge pull request #1981 from jonferreira/dev
Use payload properties as parameters on a GET request
2019-02-04 20:43:45 +00:00
Nick O'Leary
044ad77a4b Merge pull request #2044 from node-red-hitachi/cookie_encoding
Allow http request node to avoid encoding cookie
2019-02-04 20:39:05 +00:00
Nick O'Leary
1fe8b388a3 Allow subflow env-var list to resize with the dialog 2019-02-04 17:20:31 +00:00
Dave Conway-Jones
79fe7d684c Add parsed JSON output option to MQTT subscribe node 2019-02-04 16:35:42 +00:00
Dave Conway-Jones
c409af0ea8 Add local time display option to numerics in debug window 2019-02-04 15:51:42 +00:00
Nick O'Leary
5110eaff96 Merge branch 'dev' into pr_2042 2019-02-04 14:39:00 +00:00
Nick O'Leary
db3eee72b5 Do not convert falsey env vars to blank string
Only blank out undefined as that's what we've always done
2019-02-04 14:12:34 +00:00
Nick O'Leary
3bcff91328 Add Status Node to Subflow to allow subflow-specific status
Closes #597
2019-02-01 23:44:50 +00:00
Hiroyasu Nishiyama
e843f192ec convert subflow env vars to dict 2019-02-02 08:34:33 +09:00
Hiroki Uchikawa
f3d2053878 Make the encode option a boolean value to determine whether to encode 2019-02-01 17:15:07 +09:00
Nick O'Leary
efe8fbbd11 Better handling of multiple flow merges
Fixes #2039

Keeps better track of what was merged so a subsequent merge
properly identifies new-vs-old and doesn't remove thinks by mistake
2019-01-30 15:12:01 +00:00
Hiroyasu Nishiyama
ce507b3b52 simplified meta-data 2019-01-30 20:57:51 +09:00
Nick O'Leary
85de227003 Make Node._flow a writeable property
This is needed so an existing node constructor that does:

   Object.assign(this,config);

works when it tries to replace this._flow with config._flow.
2019-01-30 10:50:29 +00:00
Hiroki Uchikawa
7c6eb7c794 Allow http request node to change cookie value encoding 2019-01-30 19:33:23 +09:00
Hiroki Uchikawa
2037741b54 Revert cookie encoding behavior 2019-01-30 19:24:19 +09:00
Nick O'Leary
d534a8952d Do not propagate Flow.getNode to parent when called from outside flow 2019-01-29 21:49:20 +00:00
Hiroyasu Nishiyama
0b05b883cb add test cases 2019-01-30 00:04:41 +09:00
Hiroyasu Nishiyama
6937aa5ddd fix type of env values 2019-01-29 23:46:56 +09:00
Hiroyasu Nishiyama
8f6b24e0aa fixed to access last variable with same name 2019-01-29 21:46:50 +09:00
Hiroyasu Nishiyama
ba3b64a6c6 removed useless env setup & simplified env access in function node 2019-01-29 21:39:59 +09:00
Hiroyasu Nishiyama
0881c6a20b update test cases 2019-01-28 23:14:49 +09:00
Hiroyasu Nishiyama
f88a4b1791 fixed comments from @knolleary 2019-01-28 22:14:08 +09:00
Hiroyasu Nishiyama
2b43e3ee23 add placeholder for env var name 2019-01-27 21:56:13 +09:00
Hiroyasu Nishiyama
a413f3cded Add support of subflow env var 2019-01-26 23:15:20 +09:00
Hiroyasu Nishiyama
596fbfb517 allow $parent access of flow context 2019-01-16 23:10:03 +09:00
jonferreira
86bb5503ab Update 21-httprequest.html 2018-11-15 17:11:40 +00:00
jonferreira
21ce23d27d Update 21-httprequest.js 2018-11-15 17:11:27 +00:00
jonferreira
6c75baecb2 Update messages.json 2018-11-15 17:11:11 +00:00
110 changed files with 5639 additions and 533 deletions

39
.github/ISSUE_TEMPLATE/--bug_report.md vendored Normal file
View File

@@ -0,0 +1,39 @@
---
name: Bug report
about: Reproducable software issues in the core of Node-RED
title: ''
labels: ''
assignees: ''
---
<!--
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
If your issue is:
- a general 'how-to' type question,
- a feature request or suggestion for a change,
- or problems with 3rd party (`node-red-contrib-`) nodes
please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
That way the whole Node-RED user community can help, rather than rely on the core development team.
To help us understand the issue, please fill-in as much of the following information as you can:
-->
### What are the steps to reproduce?
### What happens?
### What do you expect to happen?
### Please tell us about your environment:
- [ ] Node-RED version:
- [ ] node.js version:
- [ ] npm version:
- [ ] Platform/OS:
- [ ] Browser:

View File

@@ -0,0 +1,14 @@
---
name: Anything Else
about: Something that is not a bug report
title: ''
labels: ''
assignees: ''
---
Please use the [Node-RED Forum](https://discourse.nodered.org) or [slack team](https://nodered.org/slack).
You could also consider asking a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
That way the whole Node-RED user community can help, rather than rely on the core development team.

15
API.md Normal file
View File

@@ -0,0 +1,15 @@
Node-RED Modules
---
Node-RED provides a set of node modules that implement different parts of the
application.
Module | Description
-------|-------
[node-red](node-red.html) | the main module that pulls together all of the internal modules and provides the executable version of Node-RED
[@node-red/editor-api](@node-red_editor-api.html) | an Express application that serves the Node-RED editor and provides the Admin HTTP API
[@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED
[@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules
@node-red/registry | the internal node registry
@node-red/nodes | the default set of core nodes
@node-red/editor-client | the client-side resources of the Node-RED editor application

View File

@@ -1,3 +1,87 @@
#### 0.20.2: Maintenance Release
- Filter out duplicate nodes when importing a flow
- Handle node configs with multiple external scripts properly
#### 0.20.1: Maintenance Release
- Ensure all subflow instances are stopped when flow stopping Fixes #2095
- modify name of korean locale forders #2091
- Ensure node names are sanitized before being presented
- Subflow status node must pass status to parent flow Fixes #2087
- fix problem on displaying option label on Firefox #2090
#### 0.20.0: Milestone Release
Runtime
- Pass complete status to Status node and filter to editor
- Ensure flows wait for all nodes to close before restarting Fixes #2067
- Fix git clone with password protected key
- Allow a project to be located below the root of repo
- Detect the cloning of an empty git repo properly
- Fix use of custom auth strategy plugins
- Remove remnants of when library in git/index Fixes #2057
- Clear subflow status on close
- Add exportGlobalContextKeys to prevent exposing functionGlobalContext keys
- Add --no-audit and --no-update-notifier flags to npm commands to reduce workload
- Add envVarExcludes setting to block named env vars
- Update settings.js docs on userDir to match reality Fixes #2082
- Add Korean Language
Editor
- Automatic placing of node icon according to input/output counts
- Transfer placeholder and type to generated TypedInput field
- Hitting enter in Comment node name field clicks markdown button
- Shift status text left if no shape specified
- Better align node status text to status dot
- Handle treeList labels as text not html
- Change subflow edit dialog titles
- Resize subflow edit dialog properly
- Add flow list button to tab bar
- Handle node name as unsanitized text in debug sidebar
Nodes
- HTTP Request: Add Digest and Bearer Auth modes to http request node (#2061)
- HTTP Request: Add multipart/form-data support to http request node (#2076)
- TCP: include session/event info in status events
- WebSocket: include session/event info in status events
- Add i18n support for port label of inject/exec/httprequest/file nodes
- Join node: handle merged objects with repeated properties and honour parts
- JSON node: handle single booleans and numbers
- File node: add encoding support to file in/out node (#2066)
#### 0.20.0-beta.5: Beta Release
Runtime
- Bump dependencies
- Allow `$parent` access of flow context
- Make Node.\_flow a writeable property
- Do not propagate Flow.getNode to parent when called from outside flow
- Add support of subflow env var
Editor
- Properly sanitize node names in deploy warning dialogs
- Fix XSS issues in library ui code
- Add env type to subflow env var types
- Display parent subflow properties in edit dialog
- Fix direction value of subflow output
- Add Status Node to Subflow to allow subflow-specific status Closes #597
- Better handling of multiple flow merges Fixes #2039
Nodes
- Various translation updates
- Catch: Add 'catch uncaught only' mode. Closes #1747
- Link: scroll to current flow in node list
- HTTPRequest: add option to urlencode cookies
- HTTPRequest: option to use msg.payload as query params on GET. #1981
- Debug: Add local time display option to numerics in debug window
- MQTT: Add parsed JSON output option
#### 0.20.0-beta.4: Beta Release
Runtime
@@ -164,6 +248,9 @@ Nodes
- Watch: add msg.filename so can feed direct to file in node
- WebSocket: preserve \_session on msg but don't send as part of wholemsg
#### 0.19.6: Maintenance Release
- Fix encoding of file node from binary to utf8 - #2051
#### 0.19.5: Maintenance Release

View File

@@ -438,6 +438,7 @@ module.exports = function(grunt) {
jsdoc : {
modules: {
src: [
'API.md',
'packages/node_modules/node-red/lib/red.js',
'packages/node_modules/@node-red/runtime/lib/index.js',
'packages/node_modules/@node-red/runtime/lib/api/*.js',
@@ -451,7 +452,7 @@ module.exports = function(grunt) {
configure: './jsdoc.json'
}
},
editor: {
_editor: {
src: [
'packages/node_modules/@node-red/editor-client/src/js'
],
@@ -612,5 +613,5 @@ module.exports = function(grunt) {
grunt.registerTask('docs',
'Generates API documentation',
['jsdoc','jsdoc2md']);
['jsdoc']);
};

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -24,16 +24,16 @@
}
],
"dependencies": {
"ajv": "6.7.0",
"ajv": "6.10.0",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"cheerio": "0.22.0",
"clone": "2.1.2",
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cookie-parser": "1.4.4",
"cors": "2.8.5",
"cron": "1.6.0",
"cron": "1.7.0",
"denque": "1.4.0",
"express": "4.16.4",
"express-session": "1.15.6",
@@ -41,18 +41,18 @@
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1",
"i18next": "13.1.0",
"i18next": "14.1.1",
"is-utf8": "0.2.1",
"js-yaml": "3.12.1",
"js-yaml": "3.12.2",
"json-stringify-safe": "5.0.1",
"jsonata": "1.6.4",
"media-typer": "1.0.1",
"memorystore": "1.6.0",
"memorystore": "1.6.1",
"mime": "2.4.0",
"mqtt": "2.18.8",
"multer": "1.4.1",
"mustache": "3.0.1",
"node-red-node-email": "1.0.*",
"node-red-node-email": "1.*",
"node-red-node-feedparser": "^0.1.14",
"node-red-node-rbe": "0.2.*",
"node-red-node-sentiment": "^0.1.0",
@@ -60,7 +60,7 @@
"node-red-node-twitter": "^1.1.0",
"nopt": "4.0.1",
"oauth2orize": "1.11.0",
"on-headers": "1.0.1",
"on-headers": "1.0.2",
"passport": "0.4.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
@@ -69,8 +69,9 @@
"semver": "5.6.0",
"uglify-js": "3.4.9",
"when": "3.7.8",
"ws": "6.1.3",
"xml2js": "0.4.19"
"ws": "6.2.0",
"xml2js": "0.4.19",
"iconv-lite": "0.4.24"
},
"optionalDependencies": {
"bcrypt": "~2.0.0"

View File

@@ -199,7 +199,7 @@ function genericStrategy(adminApp,strategy) {
if (/^post$/i.test(options.callbackMethod)) {
callbackMethodFunc = adminApp.post;
}
callbackMethodFunc('/auth/strategy/callback',
callbackMethodFunc.call(adminApp,'/auth/strategy/callback',
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
completeGenerateStrategyAuth
);

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,15 +16,15 @@
}
],
"dependencies": {
"@node-red/util": "0.20.0-beta.4",
"@node-red/editor-client": "0.20.0-beta.4",
"@node-red/util": "0.20.2",
"@node-red/editor-client": "0.20.2",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"clone": "2.1.2",
"cors": "2.8.5",
"express-session": "1.15.6",
"express": "4.16.4",
"memorystore": "1.6.0",
"memorystore": "1.6.1",
"mime": "2.4.0",
"mustache": "3.0.1",
"oauth2orize": "1.11.0",
@@ -32,6 +32,6 @@
"passport-oauth2-client-password": "0.1.2",
"passport": "0.4.0",
"when": "3.7.8",
"ws": "6.1.3"
"ws": "6.2.0"
}
}

View File

@@ -24,6 +24,7 @@
"delete": "Are you sure you want to delete '__label__'?",
"dropFlowHere": "Drop the flow here",
"addFlow": "Add Flow",
"listFlows": "List Flows",
"status": "Status",
"enabled": "Enabled",
"disabled":"Disabled",
@@ -135,7 +136,12 @@
"updated": "Project '__project__' updated",
"pull": "Project '__project__' reloaded",
"revert": "Project '__project__' reverted",
"merge-complete": "Git merge completed"
"merge-complete": "Git merge completed",
"setupCredentials": "Setup credentials",
"setupProjectFiles": "Setup project files",
"no": "No thanks",
"createDefault": "Create default project files",
"mergeConflict": "Show merge conflicts"
},
"label": {
"manage-project-dep": "Manage project dependencies",
@@ -266,16 +272,22 @@
"newVersionError": "New Version doesn't contain valid JSON:"
},
"subflow": {
"editSubflow": "Edit flow template: __name__",
"edit": "Edit flow template",
"editSubflowInstance": "Edit subflow instance: __name__",
"editSubflow": "Edit subflow template: __name__",
"edit": "Edit subflow template",
"subflowInstances": "There is __count__ instance of this subflow template",
"subflowInstances_plural": "There are __count__ instances of this subflow template",
"editSubflowProperties": "edit properties",
"input": "inputs:",
"output": "outputs:",
"status": "status node",
"deleteSubflow": "delete subflow",
"info": "Description",
"category": "Category",
"env": {
"restore": "Restore to subflow default",
"remove": "Remove environment variable"
},
"errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
@@ -375,7 +387,7 @@
},
"event": {
"nodeAdded": "Node added to palette:",
"nodeAdded_plural": "Nodes added to palette",
"nodeAdded_plural": "Nodes added to palette:",
"nodeRemoved": "Node removed from palette:",
"nodeRemoved_plural": "Nodes removed from palette:",
"nodeEnabled": "Node enabled:",
@@ -538,14 +550,19 @@
"removeFromProject": "remove from project",
"addToProject": "add to project",
"files": "Files",
"package": "Package",
"flow": "Flow",
"credentials": "Credentials",
"package":"Package",
"packageCreate":"File will be created when changes are saved",
"fileNotExist":"File does not exist",
"selectFile": "Select File",
"invalidEncryptionKey": "Invalid encryption key",
"encryptionEnabled": "Encryption enabled",
"encryptionDisabled": "Encryption disabled",
"setTheEncryptionKey": "Set the encryption key:",
"resetTheEncryptionKey": "Reset the encryption key:",
"changeTheEncryptionKey": "Change the encryption key:",
"setTheEncryptionKey": "Set the encryption key",
"resetTheEncryptionKey": "Reset the encryption key",
"changeTheEncryptionKey": "Change the encryption key",
"currentKey": "Current key",
"newKey": "New key",
"credentialsAlert": "This will delete all existing credentials",
@@ -744,6 +761,7 @@
"desc2": "If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.",
"create": "Create Project",
"clone": "Clone Repository",
"openExistingProject": "Open existing project",
"not-right-now": "Not right now"
},
"git-config": {
@@ -901,6 +919,7 @@
"editor-tab": {
"properties": "Properties",
"description": "Description",
"appearance": "Appearance"
"appearance": "Appearance",
"env": "Environment Variables"
}
}

View File

@@ -4,7 +4,7 @@
"tip1" : "Search for nodes using {{core:search}}",
"tip2" : "{{core:toggle-sidebar}} will toggle the view of this sidebar",
"tip3" : "You can manage your palette of nodes with {{core:manage-palette}}",
"tip4" : "Your flow configuration nodes are listed in the sidebar panel. It can been accessed from the menu or with {{core:show-config-tab}}",
"tip4" : "Your flow configuration nodes are listed in the sidebar panel. It can be accessed from the menu or with {{core:show-config-tab}}",
"tip5" : "Enable or disable these tips from the option in the settings",
"tip6" : "Move the selected nodes using the [left] [up] [down] and [right] keys. Hold [shift] to nudge them further",
"tip7" : "Dragging a node onto a wire will splice it into the link",

View File

@@ -24,6 +24,7 @@
"delete": "本当に '__label__' を削除しますか?",
"dropFlowHere": "ここにフローをドロップしてください",
"addFlow": "フローの追加",
"listFlows": "フロー一覧",
"status": "状態",
"enabled": "有効",
"disabled": "無効",
@@ -107,7 +108,7 @@
"undeployedChanges": "ノードの変更をデプロイしていません",
"nodeActionDisabled": "ノードのアクションは無効になっています",
"nodeActionDisabledSubflow": "ノードのアクションは、サブフロー内で無効になっています",
"missing-types": "不明なノードが存在するため、フローを停止しました。詳細はログを確認してください。",
"missing-types": "<p>不明なノードが存在するため、フローを停止しました。</p>",
"safe-mode": "<p>セーフモードでフローを停止しました</p><p>フローを変更し、再起動するために変更をデプロイできます</p>",
"restartRequired": "更新されたモジュールを有効化するため、Node-REDを再起動する必要があります",
"credentials_load_failed": "<p>認証情報を復号できないため、フローを停止しました</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です</p>",
@@ -125,7 +126,7 @@
"lostConnectionTry": "すぐに接続",
"cannotAddSubflowToItself": "サブフロー自身を追加できません",
"cannotAddCircularReference": "循環参照を検出したため、サブフローを追加できません",
"unsupportedVersion": "<p>サポートされていないバージョンのNode.jsを使用しています。</p><p><br/>最新のNode.js LTSに更新してください。</p>",
"unsupportedVersion": "<p>サポートされていないバージョンのNode.jsを使用しています。</p><p>最新のNode.js LTSに更新してください。</p>",
"failedToAppendNode": "<p>'__module__'がロードできませんでした。</p><p>__error__</p>"
},
"project": {
@@ -135,7 +136,12 @@
"updated": "プロジェクト'__project__'を更新しました",
"pull": "プロジェクト'__project__'を再ロードしました",
"revert": "プロジェクト'__project__'を取り消しました",
"merge-complete": "Gitマージが完了しました"
"merge-complete": "Gitマージが完了しました",
"setupCredentials": "認証情報を設定",
"setupProjectFiles": "プロジェクトファイルの設定",
"no": "結構です",
"createDefault": "デフォルトのプロジェクトファイルを作成",
"mergeConflict": "マージの衝突を表示"
},
"label": {
"manage-project-dep": "プロジェクトの依存関係の管理",
@@ -266,16 +272,22 @@
"newVersionError": "新しいバージョンは正しいJSON形式ではありません:"
},
"subflow": {
"editSubflow": "フローのテンプレートを編集: __name__",
"edit": "フローのテンプレートを編集",
"editSubflowInstance": "サブフローインスタンスを編集: __name__",
"editSubflow": "サブフローのテンプレートを編集: __name__",
"edit": "サブフローのテンプレートを編集",
"subflowInstances": "このサブフローのテンプレートのインスタンスが __count__ 個存在します",
"subflowInstances_plural": "このサブフローのテンプレートのインスタンスが __count__ 個存在します",
"editSubflowProperties": "プロパティを編集",
"input": "入力:",
"output": "出力:",
"status": "ステータスノード",
"deleteSubflow": "サブフローを削除",
"info": "詳細",
"category": "カテゴリ",
"env": {
"restore": "デフォルト値に戻す",
"remove": "環境変数を削除"
},
"errors": {
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
"multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています"
@@ -375,7 +387,7 @@
},
"event": {
"nodeAdded": "ノードをパレットへ追加しました:",
"nodeAdded_plural": "ノードをパレットへ追加しました",
"nodeAdded_plural": "ノードをパレットへ追加しました:",
"nodeRemoved": "ノードをパレットから削除しました:",
"nodeRemoved_plural": "ノードをパレットから削除しました:",
"nodeEnabled": "ノードを有効化しました:",
@@ -431,7 +443,7 @@
"more": "+ さらに __count__ 個",
"errors": {
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
"installFailed": "<p.追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"updateFailed": "<p>更新処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
@@ -538,14 +550,18 @@
"removeFromProject": "プロジェクトから削除",
"addToProject": "プロジェクトへ追加",
"files": "ファイル",
"package": "パッケージ",
"flow": "フロー",
"credentials": "認証情報",
"packageCreate": "変更が保存された時にファイルが作成されます",
"fileNotExist": "ファイルが存在しません",
"selectFile": "ファイルを選択",
"invalidEncryptionKey": "不正な暗号化キー",
"encryptionEnabled": "暗号化が有効になっています",
"encryptionDisabled": "暗号化が無効になっています",
"setTheEncryptionKey": "暗号化キーを設定:",
"resetTheEncryptionKey": "暗号化キーを初期化:",
"changeTheEncryptionKey": "暗号化キーを変更:",
"setTheEncryptionKey": "暗号化キーを設定",
"resetTheEncryptionKey": "暗号化キーを初期化",
"changeTheEncryptionKey": "暗号化キーを変更",
"currentKey": "現在のキー",
"newKey": "新規のキー",
"credentialsAlert": "既存の認証情報は全て削除されます",
@@ -744,6 +760,7 @@
"desc2": "とりあえずこの処理をスキップしてもかまいません。「プロジェクト」メニューから、いつでもプロジェクトの作成を開始できます。",
"create": "プロジェクトの作成",
"clone": "プロジェクトのクローン",
"openExistingProject": "既存のプロジェクトを開く",
"not-right-now": "後にする"
},
"git-config": {
@@ -901,6 +918,7 @@
"editor-tab": {
"properties": "プロパティ",
"description": "説明",
"appearance": "外観"
"appearance": "外観",
"env": "環境変数"
}
}

12
packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json vendored Normal file → Executable file
View File

@@ -218,5 +218,17 @@
"$env": {
"args": "arg",
"desc": "環境変数の値を返します。\n\n本関数はNode-REDの定義関数です。"
},
"$eval": {
"args": "expr [, context]",
"desc": "JSONリテラルもしくはJSONata式を表す`expr`を評価します。評価の際には現在のコンテキストをコンテキストとして用います。"
},
"$formatInteger": {
"args": "number, picture",
"desc": "`number`を`picture`指定に従って文字列に変換します。`picture`文字列は数値の変換方法をXPath F&O 3.1仕様の`fn:format-integer`に従って定義します。"
},
"$parseInteger": {
"args": "string, picture",
"desc": "`picture`文字列の指定に従って、`string`パラメータを整数(JSON数値)に変換します。`picture`文字列は`$formatInteger`と同じ形式です。"
}
}

View File

@@ -0,0 +1,907 @@
{
"common": {
"label": {
"name": "이름",
"ok": "확인",
"done": "완료",
"cancel": "취소",
"delete": "삭제",
"close": "닫기",
"load": "열기",
"save": "저장",
"import": "가져오기",
"export": "내보내기",
"back": "뒤로",
"next": "앞으로",
"clone": "프로젝트 복제",
"cont": "계속하기"
}
},
"workspace": {
"defaultName": "플로우 __number__",
"editFlow": "플로우 수정 : __name__",
"confirmDelete": "삭제 확인",
"delete": "정말로 '__label__' 을(를) 삭제하시겠습니까?",
"dropFlowHere": "플로우를 이곳에 가져오세요",
"addFlow": "플로우 추가",
"status": "상태",
"enabled": "사용가능",
"disabled": "사용불가능",
"info": "상세내역"
},
"menu": {
"label": {
"view": {
"view": "창",
"grid": "눈금선",
"showGrid": "눈금선 보이기",
"snapGrid": "노드 배치 보조 켜기",
"gridSize": "눈금선 크기",
"textDir": "텍스트 방향",
"defaultDir": "기본",
"ltr": "왼쪽 -> 오른쪽",
"rtl": "오른쪽 -> 왼쪽",
"auto": "자동배분"
},
"sidebar": {
"show": "우측사이드바 보이기"
},
"palette": {
"show": "팔렛트 보이기"
},
"settings": "설정",
"userSettings": "사용자 설정",
"nodes": "노드설정",
"displayStatus": "노드상태 보이기",
"displayConfig": "설정노드 보기",
"import": "가져오기",
"export": "내보내기",
"search": "플로우 겅색",
"searchInput": "플로우 검색",
"clipboard": "클립보드",
"library": "라이브러리",
"examples": "예시",
"subflows": "보조 플로우",
"createSubflow": "보조 플로우 생성",
"selectionToSubflow": "보조 플로우 선택",
"flows": "플로우",
"add": "추가",
"rename": "이름변경",
"delete": "삭제",
"keyboardShortcuts": "단축키",
"login": "로그인",
"logout": "로그아웃",
"editPalette": "팔렛트 관리",
"other": "기타",
"showTips": "Tip 보기",
"help": "Node-RED 웹사이트",
"projects": "프로젝트",
"projects-new": "신규",
"projects-open": "열기",
"projects-settings": "프로젝트 설정",
"showNodeLabelDefault": "새로 추가된 노드의 라벨 보이기"
}
},
"actions": {
"toggle-navigator": "네비게이터 표시/비표시",
"zoom-out": "축소하기",
"zoom-reset": "확대/축소 초기화",
"zoom-in": "확대하기"
},
"user": {
"loggedInAs": "__name__ 에 로그인됨",
"username": "사용자명",
"password": "비밀번호",
"login": "로그인",
"loginFailed": "로그인 실패",
"notAuthorized": "권한이 없습니다",
"errors": {
"settings": "로그인 후 설정이 가능합니다",
"deploy": "로그인 후 배포가 가능합니다",
"notAuthorized": "이 기능은 로그인 후 사용가능합니다"
}
},
"notification": {
"warning": "<strong>경고</strong>: __message__",
"warnings": {
"undeployedChanges": "변경사항 배포가 취소되었습니다",
"nodeActionDisabled": "노드 실행이 비활성화 되었습니다",
"nodeActionDisabledSubflow": "보조 플로우에서 노드 실행이 비활성화 되었습니다",
"missing-types": "<p>타입이 없는 노드로인해 플로우가 중지되었습니다</p>",
"safe-mode": "<p>[안전모드] 플로우가 정지되었습니다.</p><p>플로우의 수정과 배포가 가능합니다. 다시 배포버튼을 누르세요.</p>",
"restartRequired": "업그레이드한 모듈을 유효화하기 위해 Node-RED를 재시작 합니다 ",
"credentials_load_failed": "<p>인증정보 복호화에 실패하여 플로우가 멈췄습니다. </p><p>인증정보는 암호화 되어있습니다. 프로젝트의 암호화 키가 깨졌거나 정상적이지 않습니다.</p>",
"credentials_load_failed_reset": "<p>인증정보를 복호화할 수 없습니다</p><p>인증정보는 암호화 되어있습니다. 프로젝트의 암호화 키가 깨졌거나 정상적이지 않습니다.</p><p>다음 배포시 플로우의 인증정보는 초기화 될것입니다. 기존 모든 플로우의 인증정보가 지워집니다.</p>",
"missing_flow_file": "<p>프로젝트 플로우 파일을 찾을 수 없습니다</p><p>프로젝트의 플로우 파일이 설정되지 않았습니다</p>",
"missing_package_file": "<p>프로젝트 패키지 파일을 찾을 수 없습니다</p><p>프로젝트의 package.json 파일이 없습니다</p>",
"project_empty": "<p>프로젝트가 누락되어 있습니다.</p><p>기본 프로젝트 파일을 만드시겠습니까?<br/>그렇지 않으면 수동으로 편집가 외부에 프로젝트 파일을 만드셔야 합니다.</p>",
"project_not_found": "<p>'__project__' 가 없습니다.</p>",
"git_merge_conflict": "<p>변경사항 자동병합에 실패했습니다.</p><p>병합되지 않은 충돌을 수정 후 재등록 하세요.</p>"
},
"error": "<strong>에러</strong>: __message__",
"errors": {
"lostConnection": "서버와 연결이 끊어졌습니다. 재접속을 시도합니다 ...",
"lostConnectionReconnect": "서버와 연결이 끊어졌습니다. __time__ 초 안에 재접속을 시도합니다.",
"lostConnectionTry": "지금 재접속",
"cannotAddSubflowToItself": "서브플로우 자기자신을 추가할 수 없습니다",
"cannotAddCircularReference": "순환참조가 발견되었습니다. 서브플로우를 추가할 수 없습니다",
"unsupportedVersion": "<p>지원하지 않는 Node.js를 사용하고 있습니다</p><p>Node.js LTS 버전을 사용해 주세요</p>",
"failedToAppendNode": "<p>'__module__' 읽어오기 실패</p><p>__error__</p>"
},
"project": {
"change-branch": "로컬지점으로 '__project__' 변경",
"merge-abort": "Git 병합을 중지했습니다.",
"loaded": "'__project__' 프로젝트를 열었습니다",
"updated": "'__project__'가 변경 되었습니다",
"pull": "'__project__'를 다시 가져왔습니다",
"revert": "'__project__'를 취소했습니다",
"merge-complete": "Git 병합이 완료되었습니다"
},
"label": {
"manage-project-dep": "프로젝트 의존성 관리",
"setup-cred": "인증정보 설정",
"setup-project": "프로젝트 파일 설정",
"create-default-package": "기본 패키지 파일 생성",
"no-thanks": "괜찮습니다",
"create-default-project": "기본 프로젝트 파일 생성",
"show-merge-conflicts": "병합 충돌 보여주기"
}
},
"clipboard": {
"nodes": "노드",
"node": "__count__ 개의 노드",
"node_plural": "__count__ 개의 노드",
"configNode": "__count__ 개의 설정 노드",
"configNode_plural": "__count__ 개의 설정 노드",
"flow": "__count__ 개의 플로우",
"flow_plural": "__count__ 개의 플로우",
"subflow": "__count__ 개의 서브 플로우",
"subflow_plural": "__count__ 개의 서브 플로우",
"selectNodes": "텍스트를 선택하고 클립보드에 복사하세요",
"pasteNodes": "여기에 노드를 붙여넣기 하세요",
"selectFile": "불러올 파일을 선택하세요",
"importNodes": "노드 불러오기",
"exportNodes": "클립보드에 노드 내보내기",
"download": "다운로드",
"importUnrecognised": "알 수 없는 형식 :",
"importUnrecognised_plural": "알 수 없는 형식 :",
"nodesExported": "클립보드에 노드 내보내기",
"nodesImported": "불러오기 : ",
"nodeCopied": "__count__개의 노드가 복사 되었습니다",
"nodeCopied_plural": "__count__개의 노드가 복사 되었습니다",
"invalidFlow": "정상적지 않은 플로우 : __message__",
"export": {
"selected": "선택된 노드",
"current": "현재 플로우",
"all": "모든 플로우",
"compact": "압축형식",
"formatted": "서식유지",
"copy": "클립보드로 내보내기"
},
"import": {
"import": "가져올 위치 : ",
"newFlow": "새로운 플로우",
"errors": {
"notArray": "입력이 JSON 배열이 아닙니다",
"itemNotObject": "입력이 올바른 플로우가 아닙니다 - __index__는 노드 오브젝트가 아닙니다",
"missingId": "입력이 올바른 플로우가 아닙니다 - __index__의 'id' 속성이 없습니다",
"missingType": "입력이 올바른 플로우가 아닙니다 - __index__의 'type' 속성이 없습니다"
}
},
"copyMessagePath": "Path가 복사 되었습니다",
"copyMessageValue": "Value가 복사 되었습니다",
"copyMessageValue_truncated": "Truncated value가 복사 되었습니다"
},
"deploy": {
"deploy": "배포하기",
"full": "전체",
"fullDesc": "작업공간 내 모든 플로우를 배포합니다",
"modifiedFlows": "변경된 플로우",
"modifiedFlowsDesc": "변경사항이 있는 플로우만 배포합니다",
"modifiedNodes": "변경된 노드",
"modifiedNodesDesc": "변경사항이 있는 노드만 배포합니다",
"restartFlows": "플로우 재시작",
"restartFlowsDesc": "현재 배포된 플로우를 재시작합니다",
"successfulDeploy": "배포가 성공했습니다",
"successfulRestart": "플로우 재시작을 성공했습니다",
"deployFailed": "배포 실패 : __message__",
"unusedConfigNodes": "사용되지 않는 설정노드가 있습니다",
"unusedConfigNodesLink": "여기를 클릭하면 볼 수 있습니다",
"errors": {
"noResponse": "서버의 응답이 없습니다"
},
"confirm": {
"button": {
"ignore": "무시",
"confirm": "배포 확인",
"review": "변경사항 보기",
"cancel": "취소",
"merge": "병합",
"overwrite": "무시하고 배포하기"
},
"undeployedChanges": "배포되지 않은 변경사항이 있습니다.\n\n이 페이지를 떠나면 변경사항이 사라집니다",
"improperlyConfigured": "작업공간에 올바르게 구성되지 않은 노드가 있습니다 :",
"unknown": "작업공간에 알려지지 않는 노드타입이 있습니다 :",
"confirm": "배포하시겠습니까?",
"doNotWarn": "이 경고를 무시",
"conflict": "서버가 최신 플로우를 사용중입니다",
"backgroundUpdate": "플로우가 변경되었습니다",
"conflictChecking": "변경사항이 자동으로 병합될 수 있는지 확인",
"conflictAutoMerge": "변경사항에 충돌이 없습니다. 자동병합이 가능합니다",
"conflictManualMerge": "변경사항에 충돌이 있습니다. 배포하기 전에 충돌을 해결하세요",
"plusNMore": "+ __count__ 개 더보기"
}
},
"eventLog": {
"title": "이벤트 로그",
"view": "로그 보기"
},
"diff": {
"unresolvedCount": "__count__개의 충돌이 해결되지 않음",
"unresolvedCount_plural": "__count__개의 충돌이 해결되지 않음",
"globalNodes": "Global 노드",
"flowProperties": "플로우 속성",
"type": {
"added": "추가됨",
"changed": "변경됨",
"unchanged": "변경없음",
"deleted": "삭제됨",
"flowDeleted": "플로우 삭제됨",
"flowAdded": "플로우 추가됨",
"movedTo": "__id__로 이동됨",
"movedFrom": "__id__로 부터 이동됨"
},
"nodeCount": "__count__ 개의 노드",
"nodeCount_plural": "__count__ 개의 노드",
"local": "로컬 변경사항",
"remote": "원격 변경사항",
"reviewChanges": "변경사항 살펴보기",
"noBinaryFileShowed": "바이너리파일 내용을 볼수 없습니다",
"viewCommitDiff": "변경사항 보기",
"compareChanges": "변경사항 비교",
"saveConflict": "충돌 해결내용 저장",
"conflictHeader": "<span>__unresolved__</span> 개 중 <span>__resolved__</span> 충돌이 해결됨",
"commonVersionError": "Common Version의 JSON 형식이 올바르지 않습니다 :",
"oldVersionError": "Old Version의 JSON 형식이 올바르지 않습니다 :",
"newVersionError": "New Version의 JSON 형식이 올바르지 않습니다 :"
},
"subflow": {
"editSubflow": "플로우 템플릿 수정 : __name__",
"edit": "플로우 템플릿 수정",
"subflowInstances": "서브 플로우 템플릿에 __count__개의 인스턴스가 있습니다",
"subflowInstances_plural": "서브 플로우 템플릿에 __count__개의 인스턴스가 있습니다",
"editSubflowProperties": "속성 수정",
"input": "입력:",
"output": "출력:",
"deleteSubflow": "서브 플로우 삭제",
"info": "상세내역",
"category": "카테고리",
"format": "Markdown 형식",
"errors": {
"noNodesSelected": "<strong>서브 플로우를 생성할 수 없습니다</strong> : 노드가 선택되지 않았습니다",
"multipleInputsToSelection": "<strong>서브 플로우를 생성할 수 없습니다</strong> : 복수의 입력이 선택되었습니다"
}
},
"editor": {
"configEdit": "수정",
"configAdd": "추가",
"configUpdate": "변경",
"configDelete": "삭제",
"nodesUse": "__count__개의 노드가 이 설정을 사용중입니다",
"nodesUse_plural": "__count__개의 노드가 이 설정을 사용중입니다",
"addNewConfig": "__type__의 설정노드 추가",
"editNode": "__type__의 노드 수정",
"editConfig": "__type__의 설정노드 수정",
"addNewType": "__type__의 노드타입 추가 ...",
"nodeProperties": "노드 속성",
"label": "명칭",
"portLabels": "포트 설정",
"labelInputs": "입력",
"labelOutputs": "출력",
"settingIcon": "아이콘",
"noDefaultLabel": "없음",
"defaultLabel": "기본 명칭",
"searchIcons": "아이콘 조회",
"useDefault": "기본설정 사용",
"description": "상세 내역",
"show": "보이기",
"hide": "숨기기",
"errors": {
"scopeChange": "범위를 변경하게 되면 다른 플로우의 노드가 사용이 불가능해 집니다."
}
},
"keyboard": {
"title": "키보드 단축키",
"keyboard": "키보드",
"filterActions": "필터",
"shortcut": "단축키",
"scope": "범위",
"unassigned": "미할당",
"global": "글로벌",
"workspace": "작업공간",
"selectAll": "모든 노드 선택",
"selectAllConnected": "모든 연결된 노드 선택",
"addRemoveNode": "노드 추가/삭제",
"editSelected": "선택된 노드 수정",
"deleteSelected": "선택된 노드나 링크를 삭제",
"importNode": "노드 불러오기",
"exportNode": "노드 내보내기",
"nudgeNode": "선택된 노드 이동 (1px)",
"moveNode": "선택된 노드 이동 (20px)",
"toggleSidebar": "사이드바 표시/비표시",
"togglePalette": "팔렛트 표시/비표시",
"copyNode": "선택된 노드 복사",
"cutNode": "선택된 노드 잘라내기",
"pasteNode": "노드 붙여넣기",
"undoChange": "마지막 변경 되돌리기",
"searchBox": "검색창 열기",
"managePalette": "팔렛트 관리"
},
"library": {
"openLibrary": "라이브러리 열기...",
"saveToLibrary": "라이브러리로 저장...",
"typeLibrary": "__type__ 라이브러리",
"unnamedType": "이름없는 __type__",
"exportToLibrary": "라이브러리로 노드 내보내기",
"dialogSaveOverwrite": "__libraryType__이 __libraryName__으로 이미 등록되어있습니다. 덮어쓸까요?",
"invalidFilename": "파일명이 올바르지 않습니다",
"savedNodes": "저장된 노드",
"savedType": "저장된 __type__",
"saveFailed": "저장 실패 : __message__",
"filename": "파일명",
"folder": "폴더명",
"filenamePlaceholder": "파일",
"fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b",
"breadcrumb": "라이브러리"
},
"palette": {
"noInfo": "정보 없음",
"filter": "필터",
"search": "모듈 검색",
"addCategory": "추가 ...",
"label": {
"subflows": "서브 플로우",
"input": "입력",
"output": "출력",
"function": "기능",
"social": "소셜",
"storage": "저장",
"analysis": "분석",
"advanced": "그 외"
},
"actions": {
"collapse-all": "모든 카테고리 접기",
"expand-all": "모든 카테고리 펼치기"
},
"event": {
"nodeAdded": "팔렛트에 노드가 추가되었습니다:",
"nodeAdded_plural": "팔렛트에 노드가 추가되었습니다:",
"nodeRemoved": "팔렛트에서 노드가 삭제되었습니다:",
"nodeRemoved_plural": "팔렛트에서 노드가 삭제되었습니다:",
"nodeEnabled": "노드가 활성화 되었습니다:",
"nodeEnabled_plural": "노드가 활성화 되었습니다:",
"nodeDisabled": "노드가 비활성화 되었습니다:",
"nodeDisabled_plural": "노드가 비활성화 되었습니다:",
"nodeUpgraded": "__module__ 노드모듈이 __version__으로 업그레이드 되었습니다"
},
"editor": {
"title": "팔렛트 관리",
"palette": "팔렛트",
"times": {
"seconds": "몇초 전",
"minutes": "몇분 전",
"minutesV": "__count__분 전",
"hoursV": "__count__시간 전",
"hoursV_plural": "__count__시간 전",
"daysV": "__count__일 전",
"daysV_plural": "__count__일 전",
"weeksV": "__count__주 전",
"weeksV_plural": "__count__주 전",
"monthsV": "__count__달 전",
"monthsV_plural": "__count__달 전",
"yearsV": "__count__년 전",
"yearsV_plural": "__count__년 전",
"yearMonthsV": "__y__년, __count__월 전",
"yearMonthsV_plural": "__y__년, __count__월 전",
"yearsMonthsV": "__y__년, __count__월 전",
"yearsMonthsV_plural": "__y__년, __count__월 전"
},
"nodeCount": "__label__ 개의 노드",
"nodeCount_plural": "__label__ 개의 노드",
"moduleCount": "__count__ 개의 모듈 사용가능",
"moduleCount_plural": "__count__ 개의 모듈 사용가능",
"inuse": "사용중",
"enableall": "모두 활성화",
"disableall": "모두 비활성화",
"enable": "활성화",
"disable": "비활성화",
"remove": "삭제",
"update": "__version__으로 업데이트",
"updated": "업데이트 됨",
"install": "설치",
"installed": "설치됨",
"conflict": "충돌",
"conflictTip": "<p>노드타입이 이미 설치 되어 있습니다.<br/>/p><p>충돌모듈 : <code>__module__</code></p>",
"loading": "카탈로그 여는중...",
"tab-nodes": "설치된 노드",
"tab-install": "설치가능한 노드",
"sort": "정렬:",
"sortAZ": "a-z",
"sortRecent": "최근",
"more": "+ __count__ 개 더 보기",
"errors": {
"catalogLoadFailed": "<p>노드 카탈로그를 설치하지 못했습니다.</p><p>브라우저 콘솔로그를 참고하세요.</p>",
"installFailed": "<p>설치 실패 : __module__</p><p>__message__</p><p>브라우저 콘솔로그를 참고하세요.</p>",
"removeFailed": "<p>삭제 실패 : __module__</p><p>__message__</p><p>브라우저 콘솔로그를 참고하세요.</p>",
"updateFailed": "<p>업데이트 실패 : __module__</p><p>__message__</p><p>브라우저 콘솔로그를 참고하세요.</p>",
"enableFailed": "<p>활성화 실패 : __module__</p><p>__message__</p><p>브라우저 콘솔로그를 참고하세요.</p>",
"disableFailed": "<p>비활성화 실패 : __module__</p><p>__message__</p><p>브라우저 콘솔로그를 참고하세요.</p>"
},
"confirm": {
"install": {
"body": "<p>'__module__' 설치중</p><p>설치하기 전 노드 설명서를 읽으세요. 어떤 노드은 의존성이 자동으로 해결되지 않거나, Node-RED의 재시작이 필요할 수 있습니다.</p>",
"title": "노드 설치"
},
"remove": {
"body": "<p>'__module__' 삭제중</p><p>Node-RED에서 노드를 제거합니다. Node-RED가 재시작되기까지 리소스가 계속 사용될 수도 있습니다.</p>",
"title": "노드 삭제"
},
"update": {
"body": "<p>'__module__' 업데이트중</p><p>업데이트 반영을 위해 Node-RED를 수동으로 재시작해야 할 경우도 있습니다.</p>",
"title": "노드 변경"
},
"cannotUpdate": {
"body": "이 노드에 대한 업데이트가 있지만, 팔레트 관리자가 변경할 수 있는 위치에 설치되지 않았습니다.<br/><br/>이 노드를 변경하는 방법은 설명서를 참조하세요"
},
"button": {
"review": "노드정보 열기",
"install": "설치",
"remove": "삭제",
"update": "업데이트"
}
}
}
},
"sidebar": {
"info": {
"name": "노드정보",
"tabName": "이름",
"label": "정보",
"node": "노드",
"type": "타입",
"module": "모듈",
"id": "ID",
"status": "상태",
"enabled": "활성화",
"disabled": "비활성화",
"subflow": "서브 플로우",
"instances": "인스턴스",
"properties": "속성",
"info": "정보",
"desc": "상세 내역",
"blank": "공백",
"null": "null",
"showMore": "더 보기",
"showLess": "간단히",
"flow": "플로우",
"selection": "선택",
"nodes": "__count__ 개의 노드",
"flowDesc": "플로우 상세내역",
"subflowDesc": "서브 플로우 상세내역",
"nodeHelp": "노드 도움말",
"none": "없음",
"arrayItems": "__count__ 개의 항목",
"showTips": "설정에서 도움말을 열 수 있습니다. "
},
"config": {
"name": "노드 설정",
"label": "설정",
"global": "모든 플로우",
"none": "없음",
"subflows": "보조 플로우",
"flows": "플로우",
"filterUnused": "미사용",
"filterAll": "전체",
"filtered": "__count__ 개 숨김"
},
"context": {
"name": "Context 데이터",
"label": "context",
"none": "선택 없음",
"refresh": "새로고침",
"empty": "공백",
"node": "노드",
"flow": "플로우",
"global": "Global",
"deleteConfirm": "정말로 이 아이템을 지우시겠습니까?"
},
"palette": {
"name": "팔레트 관리",
"label": "팔레트"
},
"project": {
"label": "프로젝트",
"name": "프로젝트",
"description": "상세내역",
"dependencies": "의존성",
"settings": "설정",
"noSummaryAvailable": "요약 없음",
"editDescription": "프로젝트 상세내역 수정",
"editDependencies": "프로젝트 의존성 수정",
"editReadme": "README.md 수정",
"showProjectSettings": "프로젝트 설정 보이기",
"projectSettings": {
"title": "프로젝트 설정",
"edit": "수정",
"none": "없음",
"install": "설치",
"removeFromProject": "프로젝트에서 삭제",
"addToProject": "프로젝트에 추가",
"files": "파일",
"flow": "플로우",
"credentials": "인증정보",
"invalidEncryptionKey": "잘못된 암호화 키",
"encryptionEnabled": "암호화 활성화",
"encryptionDisabled": "암호화 비활성화",
"setTheEncryptionKey": "암호화 키 설정 :",
"resetTheEncryptionKey": "암호화 키 초기화 :",
"changeTheEncryptionKey": "암호화 키 변경:",
"currentKey": "현재 키",
"newKey": "새로운 키",
"credentialsAlert": "모든 인증정보를 삭제합니다",
"versionControl": "버전 관리",
"branches": "브랜치",
"noBranches": "브랜치 없음",
"deleteConfirm": "다시 되돌릴 수 없습니다. '__name__'의 로컬 브랜치를 삭제 히시겠습니까?",
"unmergedConfirm": "'__name__'의 병합되지 않은 수정사항을 잃어버릴 수 있습니다. 그래도 삭제 하시겠습니까?",
"deleteUnmergedBranch": "미병합 브랜치 삭제",
"gitRemotes": "Git 원격",
"addRemote": "원격 추가",
"addRemote2": "원격 추가",
"remoteName": "원격 이름",
"nameRule": "A-Z 0-9 _ -의 문자만 사용이 가능합니다",
"url": "URL",
"urlRule": "https://, ssh:// or file://",
"urlRule2": "URL안에 사용자아이디/비밀번호를 사용하지 마세요",
"noRemotes": "원격 없음",
"deleteRemoteConfrim": "원격 '__name__'를 정말로 삭제하시겠습니까?",
"deleteRemote": "원격 삭제"
},
"userSettings": {
"committerDetail": "Committer 상세내역",
"committerTip": "시스템 기본값을 사용하려면 비워두세요",
"userName": "사용자명",
"email": "이메일",
"sshKeys": "SSH키",
"sshKeysTip": "원격저장소에 대한 보안연결을 허용합니다",
"add": "키 추가",
"addSshKey": "SSH키 추가",
"addSshKeyTip": "public/private 키쌍을 추가합니다",
"name": "이름",
"nameRule": "A-Z 0-9 _ -의 문자만 사용이 가능합니다",
"passphrase": "암호",
"passphraseShort": "암호가 너무 짧습니다",
"optional": "선택항목",
"cancel": "취소",
"generate": "Key 생성",
"noSshKeys": "SSH키 없음",
"copyPublicKey": "클립보드로 public key 복사",
"delete": "키 삭제",
"gitConfig": "Git 설정",
"deleteConfirm": "다시 되돌릴 수 없습니다. __name__의 SSH키를 삭제하시겠습니까?"
},
"versionControl": {
"unstagedChanges": "변경사항을 언스테이징",
"stagedChanges": "스테이징된 변경사항",
"unstageChange": "스테이징 되지않은 변경사항",
"stageChange": "변경사항을 스테이징",
"unstageAllChange": "모든 변경사항 언스테이징",
"stageAllChange": "모든 변경사항 스테이징",
"commitChanges": "변경사항 커밋",
"resolveConflicts": "충돌 해결",
"head": "HEAD",
"staged": "스테이징 됨",
"unstaged": "스테이징 안됨",
"local": "로컬",
"remote": "리모트",
"revert": "다시 복원할 수 없습니다. '__file__'을 되돌리시겠습니까?",
"revertChanges": "변경사항 되돌리기",
"localChanges": "로컬 변경사항",
"none": "없음",
"conflictResolve": "모든 충돌이 해결되었습니다. 변경사항을 적용하여 병합을 완료하세요",
"localFiles": "로컬 파일",
"all": "전체",
"unmergedChanges": "병합되지 않은 변경사항",
"abortMerge": "병합 중단",
"commit": "커밋",
"changeToCommit": "커밋 변경사항",
"commitPlaceholder": "커밋 메시지를 입력하세요",
"cancelCapital": "취소",
"commitCapital": "커밋",
"commitHistory": "커밋 이력",
"branch": "브랜치 :",
"moreCommits": "커밋 더보기",
"changeLocalBranch": "로컬 브랜치 변경",
"createBranchPlaceholder": "브렌치 찾기/생성",
"upstream": "업스트림",
"localOverwrite": "브랜치에 반영할 변경사항이 있습니다. 변경사항을 커밋하거나, 변경내역을 취소해야 합니다",
"manageRemoteBranch": "원격 브랜치 관리",
"unableToAccess": "원격저장소에 접근할 수 없습니다",
"retry": "재시도",
"setUpstreamBranch": "업스트림 브랜치로 설정",
"createRemoteBranchPlaceholder": "리모드 브랜치 찾기/생성",
"trackedUpstreamBranch": "생성된 브랜치는 트래킹된 업스트림 브랜치로 설정됩니다",
"selectUpstreamBranch": "브랜치가 생성될 것입니다. 트래킹된 업스트림 브랜치로 설정하세요",
"pushFailed": "리모트에 최신 커밋이 있기 때문에 push할 수 없습니다. 먼저 pull과 병합을 하신 후 push하세요",
"push": "push",
"pull": "pull",
"unablePull": "<p>원격저장소의 변경사항을 가져올 수 없습니다, 당신의 unstaged 로컬 변경사항을 덮어씁니다.</p><p>변경사항을 적용하고 다시 시도하세요</p>",
"showUnstagedChanges": "unstaged 변경사항 보여주기",
"connectionFailed": "원격저장소 연결 불가 : ",
"pullUnrelatedHistory": "<p>원격저장소에 연관없는 커밋 기록이 있습니다.</p><p>모든 변경사항을 로컬 저장소로 가져 오시겠습니까?</p>",
"pullChanges": "Pull 변경사항",
"history": "이력",
"projectHistory": "프로젝트 이력",
"daysAgo": "__count__일 전",
"daysAgo_plural": "__count__일 전",
"hoursAgo": "__count__시간 전",
"hoursAgo_plural": "__count__시간 전",
"minsAgo": "__count__분 전",
"minsAgo_plural": "__count__분 전",
"secondsAgo": "몇초 전",
"notTracking": "당신의 로컬 브랜치는 원격브랜치를 트래킹하고 있지 않습니다",
"statusUnmergedChanged": "당신의 저장소는 병합되지 않은 변경사항을 가지고 있습니다. 충돌을 수정하고 결과를 커밋하세요",
"repositoryUpToDate": "당신의 저장소는 최신상태 입니다",
"commitsAhead": "당신의 저장소가 원격지보다 __count__ 커밋을 앞서 있습니다. 이제 커밋 할 수 있습니다.",
"commitsAhead_plural": "당신의 저장소가 원격지보다 __count__ 커밋을 앞서 있습니다. 지금 커밋할 수 있습니다.",
"commitsBehind": "당신의 저장소가 원격지보다 __count__ 커밋이 늦습니다. 이제 pull 할 수 있습니다.",
"commitsBehind_plural": "당신의 저장소가 원격지보다 __count__ 커밋이 늦습니다. 이제 pull 할 수 있습니다.",
"commitsAheadAndBehind1": "당신의 저장소가 __count__ 커밋이 늦고, ",
"commitsAheadAndBehind1_plural": "당신의 저장소가 __count__ 커밋이 늦고 ",
"commitsAheadAndBehind2": "__count__ 커밋이 원격지보다 앞서 있습니다. ",
"commitsAheadAndBehind2_plural": "__count__ 커밋이 원격지보다 앞서 있습니다.",
"commitsAheadAndBehind3": "push하기전에 리모트 저장소에서 pull을 먼저 수행하세요.",
"commitsAheadAndBehind3_plural": "push하기전에 리모트 저장소에서 pull을 먼저 수행하세요.",
"refreshCommitHistory": "커밋 기록 새로고침",
"refreshChanges": "변경사항 새로고침"
}
}
},
"typedInput": {
"type": {
"str": "string",
"num": "number",
"re": "regular expression",
"bool": "boolean",
"json": "JSON",
"bin": "buffer",
"date": "timestamp",
"jsonata": "expression",
"env": "env variable"
}
},
"editableList": {
"add": "추가"
},
"search": {
"empty": "결과 없음",
"addNode": "노드 추가 ..."
},
"expressionEditor": {
"functions": "기능",
"functionReference": "기능 참조",
"insert": "삽입",
"title": "JSONata 형식 에디터",
"test": "테스트",
"data": "예제 메세지",
"result": "결과",
"format": "형식",
"compatMode": "호환모드 사용",
"compatModeDesc": "<h3>JSONata호환 모드</h3><p> 입력된 형식은 <code>msg</code> 를 참조하고 있어, 호환모드로 평가합니다. 이 모드는 후에 폐지될 예정이니, <code>msg</code> 를 사용하지 않도록 해 주시길 바랍니다. </p><p> JSONata를 Node-RED에서 처음 지원했을 때에는 <code>msg</code> 오브젝트의 참조가 필요했습니다. 예를 들어 <code>msg.payload</code> 는 payload를 참고하기 위해 사용되었습니다. </p><p> 직접 메시지에 대하여 식을 평가하도록 되었기에, 이 형식은 사용할 수 없게 됩니다. payload를 참조하려면 단순히 <code>payload</code> 로 지정해 주십시오. </p>",
"noMatch": "결과 없음",
"errors": {
"invalid-expr": "유효하지 않은 JSONata 형식 :\n __message__",
"invalid-msg": "유효하지 않은 예시 JSON 메세지 :\n __message__",
"context-unsupported": "컨텍스트 기능을 테스트 할 수 없습니다.\n $flowContext 또는 $globalContext",
"eval": "형식 오류 :\n __message__"
}
},
"jsEditor": {
"title": "자바스크립트 에디터"
},
"jsonEditor": {
"title": "JSON 에디터",
"format": "JSON 형식"
},
"markdownEditor": {
"title": "Markdown 에디터",
"format": "Markdown 형식",
"heading1": "제목 레벨1",
"heading2": "제목 레벨2",
"heading3": "제목 레벨3",
"bold": "강조",
"italic": "이탤릭",
"code": "코드",
"ordered-list": "번호 목차",
"unordered-list": "목차",
"quote": "인용",
"link": "링크",
"horizontal-rule": "나눔줄",
"toggle-preview": "미리보기 전환"
},
"bufferEditor": {
"title": "Buffer 에디터",
"modeString": "UTF-8 문자열로 처리",
"modeArray": "JSON 배열로 처리",
"modeDesc": "<h3>Buffer 에디터</h3><p>버퍼타입은 byet값의 JSON배열로 저장됩니다. 이 에디터는 입력된 값을 JSON 배열로 구문분석 합니다. 만약 유효한 JSON이 아닌경우 UTF-8 문자열로 처리되어 각 문자코드 번호의 배열로 변환됩니다.</p><p>예를들어 <code>Hello World</code> 라는 값은 다음의 JSON 배열로 변환됩니다.<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
},
"projects": {
"config-git": "Git client 설정",
"welcome": {
"hello": "안녕하세요. Node-RED에서 프로젝트 기능을 이용할 수 있게 되었습니다.",
"desc0": "플로우 파일을 관리하는 새로운 방법이며, 버전을 관리할 수 도 있습니다.",
"desc1": "무선 프로젝트를 작성하거나 기존의 Git저장소에서 프로젝트를 복제할 수 있습니다.",
"desc2": "이 기능을 건너뛰어도 상관없습니다. 언제든지 프로젝트 메뉴에서 첫번째 프로젝트를 만들 수 있습니다.",
"create": "프로젝트 생성",
"clone": "프로젝트 복제",
"not-right-now": "나중에"
},
"git-config": {
"setup": "버전관리 클라이언트를 설정합니다",
"desc0": "Node-RED는 오픈소스 Git로 버전관리를 할 수 있습니다. 프로젝트 파일의 변경사항을 추적하고 원격저장소로 push할 수 있습니다.",
"desc1": "당신이 변경사항을 커밋하면 git은 누가 변경사항을 만들었는지 사용자명과 이메일 정보를 기록합니다. 사용자명은 꼭 당신의 실명일 필요는 없습니다.",
"desc2": "당신의 Git 클라이언트는 아래와 같이 이미 설정되었습니다.",
"desc3": "당신은 git config의 설정탭에서 설정을 변경할 수 있습니다.",
"username": "사용자명",
"email": "이메일"
},
"project-details": {
"create": "프로젝트 생성",
"desc0": "프로젝트는 Git 저장소로 관리되어집니다. 다른 사람과 협업하거나 공유하기 쉬워집니다.",
"desc1": "당신은 여러 개의 프로젝트를 생성할 수 있고 에디터에서 프로젝트를 선택할 수 있습니다.",
"desc2": "시작하려면 프로젝트 이름과 프로젝트의 상세설명이 필요합니다.",
"already-exists": "프로젝트가 이미 존재합니다",
"must-contain": "A-Z 0-9 _ -의 문자만 사용이 가능합니다",
"project-name": "프로젝트명",
"desc": "상세설명",
"opt": "옵션"
},
"clone-project": {
"clone": "프로젝트 복제",
"desc0": "프로젝트가 있는 저장소를 가지고 있다면, 즉시 복제하여 사용할 수 있습니다.",
"already-exists": "프로젝트가 이미 존재합니다",
"must-contain": "A-Z 0-9 _ -의 문자만 사용이 가능합니다",
"project-name": "프로젝트명",
"no-info-in-url": "URL안에 사용자아이디/비밀번호를 사용하지 마세요",
"git-url": "Git 저장소 URL",
"protocols": "https://, ssh:// 혹은 file://",
"auth-failed": "인증 실패",
"username": "사용자명",
"passwd": "패스워드",
"ssh-key": "SSH키",
"passphrase": "패스워드",
"ssh-key-desc": "저장소를 복제하기 전에 접속을 위해 SSH키를 먼저 추가하세요.",
"ssh-key-add": "ssh키 추가",
"credential-key": "인증 암호화 키",
"cant-get-ssh-key": "에러! 선택한 SSH키 경로를 가져올 수 없습니다",
"already-exists2": "이미 존재합니다",
"git-error": "git 에러",
"connection-failed": "접속 실패",
"not-git-repo": "Git저장소가 아닙니다",
"repo-not-found": "저장소가 없습니다"
},
"default-files": {
"create": "프로젝트 파일 생성",
"desc0": "프로젝트는 당신의 플로우, README, package.json 파일을 포함합니다.",
"desc1": "Git 저장소에서 관리하고 싶은 다른 파일들을 포함할 수 있습니다.",
"desc2": "당신이 이미 가지고 있는 flow, 자격증명파일이 프로젝트로 복사될 것입니다.",
"flow-file": "플로우 파일",
"credentials-file": "자격증명 파일"
},
"encryption-config": {
"setup": "자격인증 파일의 암호화 설정",
"desc0": "플로우의 자격인증 파일 암호화를 통해 내용을 안전하게 유지할 수 있습니다.",
"desc1": "자격증명을 공용 Git저장소에 저장하려면 비밀키 구문을 제공하여 암호화 해야 합니다",
"desc2": "당신의 플로우 자격인증 파일은 암호화 되어 있지 않습니다.",
"desc3": "즉, 암호 및 액세스 토큰과 같은 내용을 파일에 액세스 할 수있는 모든 사람이 열람할 수 있습니다.",
"desc4": "자격증명을 공용 Git저장소에 저장하려면 비밀키 구문을 제공하여 암호화 해야 합니다",
"desc5": "당신의 플로우 자격증명파일은 setting파일의 credentialSecret속성으로 암호화되어 있습니다.",
"desc6": "당신의 플로우 자격증명파일은 시스템이 생성된 키에 의해 암호화 되어있습니다. 이 프로젝트용 새로운 비밀키를 지정해 주세요.",
"desc7": "키는 프로젝트파일과는 별개로 보존됩니다. 다른 Node-RED에서 이 프로젝트를 이용하려면 이 프로젝트의 키가 필요합니다.",
"credentials": "자격인증",
"enable": "암호화 활성화",
"disable": "암호화 비활성화",
"disabled": "비활성화됨",
"copy": "기존 키를 복사",
"use-custom": "커스텀키 사용",
"desc8": "자격증명 파일이 암호화되어 있지 않아, 간단히 해당내용이 열람될 수 있습니다.",
"create-project-files": "프로젝트 생성",
"create-project": "프로젝트 생성",
"already-exists": "이미 존재합니다.",
"git-error": "git 에러",
"git-auth-error": "git 인증 에러"
},
"create-success": {
"success": "당신의 첫번째 프로젝트 생성이 성공하였습니다.",
"desc0": "앞으로 이와 같이 Node-RED를 사용할 수 있습니다.",
"desc1": "사이드바의 '정보'탭은 현재 활성화된 프로젝트를 보여줍니다. 이름 옆에 있는 버틀을 사용하여 프로젝트 설정화면을 불러올 수 있습니다.",
"desc2": "사이드바의 '이력'탭은 프로젝트의 변경된 파일을 확인하고 커밋할 수 있습니다. 커밋의 전체 기록을 보여주고 변경사항을 원격 저장소에 push할 수 있습니다."
},
"create": {
"projects": "프로젝트",
"already-exists": "프로젝트가 이미 존재합니다",
"must-contain": "A-Z 0-9 _ -의 문자만 사용이 가능합니다",
"no-info-in-url": "URL안에 사용자아이디/비밀번호를 사용하지 마세요",
"open": "프로젝트 열기",
"create": "프로젝트 생성",
"clone": "프로젝트 복제",
"project-name": "프로젝트명",
"desc": "상세내역",
"opt": "옵션",
"flow-file": "플로우 파일",
"credentials": "자격증명",
"enable-encryption": "암호화 활성화",
"disable-encryption": "암호화 비활성화",
"encryption-key": "암호화 키",
"desc0": "자격증명 정보를 안전하게 하는 문구",
"desc1": "자격증명 파일이 암호화되어 있지 않아, 간단히 해당내용이 열람될 수 있습니다.",
"git-url": "Git 저장소 URL",
"protocols": "https://, ssh:// 혹은 file://",
"auth-failed": "인증 실패",
"username": "사용자명",
"password": "패스워드",
"ssh-key": "SSH키",
"passphrase": "패스워드",
"desc2": "저장소를 복제하기 전에 접속을 위해 SSH키를 먼저 추가하세요.",
"add-ssh-key": "ssh키 추가",
"credentials-encryption-key": "자격인증 암호화 키",
"already-exists-2": "이미 존재합니다",
"git-error": "git 에러",
"con-failed": "접속 실패",
"not-git": "git 저장소가 아닙니다",
"no-resource": "저장소아 없습니다",
"cant-get-ssh-key-path": "에러! 선택한 SSH키 경로를 가져올 수 없습니다.",
"unexpected_error": "예기치 않은 에러"
},
"delete": {
"confirm": "프로젝트를 정말 지우시겠습니까?"
},
"create-project-list": {
"search": "프로젝트 검색",
"current": "현재"
},
"require-clean": {
"confirm": "<p>변경사항을 배포하지 않아 내용이 손실될 수 있습니다.</p><p>계속 할까요?</p>"
},
"send-req": {
"auth-req": "저장소에 대한 인증이 필요합니다.",
"username": "사용자명",
"password": "패스워드",
"passphrase": "패스워드",
"retry": "재시도",
"update-failed": "인증 변경 실패",
"unhandled": "오류 응답 미처리"
},
"create-branch-list": {
"invalid": "올바르지 않은 브랜치",
"create": "브랜치 생성",
"current": "현재"
},
"create-default-file-set": {
"no-active": "활성화된 프로젝트 없이 기본 파일을 만들 수 없습니다.",
"no-empty": "비어있지 않은 프로젝트에 기본 파일을 만들 수 없습니다.",
"git-error": "git 에러"
},
"errors": {
"no-username-email": "당신의 Git 클라이언트에 사용자명/이메일이 설정되지 않았습니다.",
"unexpected": "예기치 않은 에러가 발생했습니다.",
"code": "코드"
}
},
"editor-tab": {
"properties": "속성",
"description": "상세 내역",
"appearance": "모양"
}
}

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0": "{{core:delete-selection}}를 사용하여 선택된 노드나 링크를 삭제할 수 있습니다.",
"tip1": "{{core:search}}를 활용하여 노드를 검색할 수 있습니다.",
"tip2": "{{core:toggle-sidebar}}를 사용하여 사이드바를 표시/비표시 전환 할 수 있습니다.",
"tip3": "{{core:manage-palette}}를 사용하여 노드 팔레트를 관리 할 수 있습니다.",
"tip4": "플로우 안의 설정노드가 사이드바에 표시됩니다. 메뉴 혹은 {{core:show-config-tab}}를 사용하여 엑세스 할 수 있습니다.",
"tip5": "설정에서 이 팁을 활성화/비활성화 할 수 있습니다.",
"tip6": "[left] [up] [down] [right] 키를 사용하여 선택된 노드를 움직일 수 있습니다. [shift]키를 누른 채로 움직이면 이동폭이 늘어납니다.",
"tip7": "노드를 와이어 사이로 드래그 하여 연결할 수도 있습니다.",
"tip8": "{{core:show-export-dialog}}를 사용하여 선택한 노드 또는 현재탭을 내보낼 수 있습니다.",
"tip9": "JSON파일을 에디터로 드래그하거나 {{core:show-import-dialog}}를 사용하여 플로우 가져올 수 있습니다.",
"tip10": "[shift] [click] 하고서 드래그하여 선택한 와이어를 이동할 수 있습니다.",
"tip11": "{{core:show-info-tab}}를 사용하여 정보탭을 표시하거나 {{core:show-debug-tab}}를 사용하여 디버그탭을 표시할 수 있습니다.",
"tip12": "작업공간에서 [ctrl] [click]을 사용하여 빠른추가 대회상자를 열 수 있습니다.",
"tip13": "[ctrl]을 누른 상태로 노드의 포트를 클릭하여 빠르게 연결할 수 있습니다.",
"tip14": "[shift]를 누른 상태로 노드를 클릭하여 연결된 모든 노드를 선택할 수 있습니다.",
"tip15": "[ctrl]을 누른 상태로 노드를 클릭하여 현재 선택영역에 노드를 추가/제거 할 수 있습니다.",
"tip16": "{{core:show-previous-tab}}와 {{core:show-next-tab}}를 사용하여 탭을 전환할 수 있습니다.",
"tip17": "노드 편집 창에서 {{core : confirm-edit-tray}}로 변경 사항을 확인하거나 {{core : cancel-edit-tray}}로 취소 할 수 있습니다.",
"tip18": "{{core : edit-selected-node}}를 누르면 현재 선택 영역의 첫 번째 노드가 편집됩니다."
}
}

View File

@@ -0,0 +1,222 @@
{
"$string": {
"args": "arg",
"desc": "다음과 같은 규칙을 사용하여 인수 *arg*를 문자열로 변환합니다. \n\n - 문자열은 변경되지 않습니다. \n - 함수는 빈 문자열로 변환됩니다. \n - 무한대와 NaN은 JSON수치로 표현할 수 없기 때문에 오류처리 됩니다. \n - 다른 모든 값은 `JSON.stringify` 함수를 사용하여 JSON 문자열로 변환됩니다."
},
"$length": {
"args": "str",
"desc": "문자열 `str`의 문자 수를 반환합니다. `str`가 문자열이 아닌 경우 에러를 반환합니다."
},
"$substring": {
"args": "str, start[, length]",
"desc": "(zero-offset)의 `start`에서 시작하는 첫번째 인수 `str`의 문자열을 반환합니다. 만약 `length`가 지정된 경우, 부분 문자열은 최대 `length`의 크기를 갖습니다. 만약 `start` 인수가 음수이면 `str`의 끝에서부터의 문자수를 나타냅니다."
},
"$substringBefore": {
"args": "str, chars",
"desc": "`str`에 `chars`문자가 처음으로 나오기 전까지의 부분문자열을 반환합니다. 만약 `chars`가 없으면 `str`을 반환합니다."
},
"$substringAfter": {
"args": "str, chars",
"desc": "`str`에 `chars`문자가 처음으로 나온 이후의 부분문자열을 반환합니다. 만약 `chars`가 없으면 `str`을 반환합니다."
},
"$uppercase": {
"args": "str",
"desc": "`str`의 문자를 대문자로 반환합니다."
},
"$lowercase": {
"args": "str",
"desc": "`str`의 문자를 소문자로 반환합니다."
},
"$trim": {
"args": "str",
"desc": "다음의 순서대로 `str`의 모든 공백을 자르고 정규화 합니다:\n\n - 모든 탭, 캐리지 리턴 및 줄 바꿈은 공백으로 대체됩니다. \n- 연속된 공백은 하나로 줄입니다.\n- 후행 및 선행 공백은 삭제됩니다.\n\n 만일 `str`이 지정되지 않으면 (예: 이 함수를 인수없이 호출), context값을 `str`의 값으로 사용합니다. `str`이 문자열이 아니면 에러가 발생합니다."
},
"$contains": {
"args": "str, pattern",
"desc": "`str`이 `pattern`과 일치하면 `true`를, 일치하지 않으면 `false`를 반환합니다. 만약 `str`이 지정되지 않으면 (예: 이 함수를 인수없이 호출), context값을 `str`의 값으로 사용합니다. `pattern` 인수는 문자열이나 정규표현으로 할 수 있습니다."
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "`str`인수를 분할하여 부분문자열로 배열합니다. `str`이 문자열이 아니면 에러가 발생합니다. 생략가능한 인수 `separator`는 `str`을 분할하는 문자를 문자열 또는 정규표현으로 지정합니다. `separator`를 지정하지 않은 경우, 공백의 문자열로 간주하여 `str`은 단일 문자의 배열로 분리됩니다. `separator`가 문자열이 아니면 에러가 발생합니다. 생략가능한 인수 'limit`는 결과의 배열이 갖는 부분문자열의 최대수를 지정합니다. 이 수를 넘는 부분문자열은 파기됩니다. `limit`가 지정되지 않으면`str`은 결과 배열의 크기의 제한없이 완전히 분리됩니다. `limit`이 음수인 경우 에러가 발생합니다."
},
"$join": {
"args": "array[, separator]",
"desc": "문자열의 배열을 생략가능한 인수 `separator`로 구분한 하나의 문자열로 연결합니다. 배열 `array`가 문자열이 아닌 요소를 포함하는 경우, 에러가 발생합니다. `separator`를 지정하지 않은 경우, 공백의 문자열로 간주합니다(예: 문자열간의 `separator`없음). `separator`가 문자열이 아닌 경우, 에러가 발생합니다."
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "`str`문자열에 `pattern`를 적용하여, 오브젝트 배열을 반환합니다. 배열요소의 오브젝트는 `str`중 일치하는 부분의 정보를 보유합니다."
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "`str`문자열에서 `pattern` 패턴을 검색하여, `replacement`로 대체합니다.\n\n임의이ㅡ 인수 `limit`는 대체 횟수의 상한값을 지정합니다."
},
"$now": {
"args": "",
"desc": "ISO 8601 호환 형식으로 타임 스탬프를 생성하고 이를 문자열로 반환합니다."
},
"$base64encode": {
"args": "string",
"desc": "ASCII 문자열을 base 64 표현으로 변환합니다. 문자열의 각 문자는 이진 데이터의 바이트로 처리됩니다. 이렇게 하려면 문자열의 모든 문자가 URI로 인코딩 된 문자열을 포함하고, 0x00에서 0xFF 범위에 있어야합니다. 해당 범위를 벗어난 유니 코드 문자는 지원되지 않습니다"
},
"$base64decode": {
"args": "string",
"desc": "UTF-8코드페이지를 이용하여, Base 64형식의 바이트값을 문자열로 변환합니다."
},
"$number": {
"args": "arg",
"desc": "`arg`를 다음과 같은 규칙을 사요하여 숫자로 변환합니다. :\n\n - 숫자는 변경되지 않습니다.\n 올바른 JSON의 숫자는 숫자 그대로 변환됩니다.\n 그 외의 형식은 에러를 발생합니다."
},
"$abs": {
"args": "number",
"desc": "`number`의 절대값을 반환합니다."
},
"$floor": {
"args": "number",
"desc": "`number`를 `number`보다 같거나 작은 정수로 내림하여 반환합니다."
},
"$ceil": {
"args": "number",
"desc": "`number`를 `number`와 같거나 큰 정수로 올림하여 반환합니다."
},
"$round": {
"args": "number [, precision]",
"desc": "인수 `number`를 반올림한 값을 반환합니다. 임의의 인수 `precision`에는 반올립에서 사용할 소수점이하의 자릿수를 지정합니다."
},
"$power": {
"args": "base, exponent",
"desc": "기수 `base`의 값을 지수 `exponent`만큼의 거듭 제곱으로 반환합니다."
},
"$sqrt": {
"args": "number",
"desc": "인수 `number`의 제곱근을 반환합니다."
},
"$random": {
"args": "",
"desc": "0이상 1미만의 의사난수를 반환합니다."
},
"$millis": {
"args": "",
"desc": "Unix Epoch (1970 년 1 월 1 일 UTC)부터 경과된 밀리 초 수를 숫자로 반환합니다. 평가대상식에 포함되는 $millis()의 모든 호출은 모두 같은 값을 반환합니다."
},
"$sum": {
"args": "array",
"desc": "숫자 배열 `array`의 합계를 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
},
"$max": {
"args": "array",
"desc": "숫자 배열 `array`에서 최대값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
},
"$min": {
"args": "array",
"desc": "숫자 배열 `array`에서 최소값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
},
"$average": {
"args": "array",
"desc": "숫자 배열 `array`에서 평균값을 반환합니다. `array`에 숫자가 아닌 요소가 있는 경우, 에러가 발생합니다."
},
"$boolean": {
"args": "arg",
"desc": "`arg` 값을 다음의 규칙에 의해 Boolean으로 변환합니다::\n\n - `Boolean` : 변환하지 않음\n - `string`: 비어있음 : `false`\n - `string`: 비어있지 않음 : `true`\n - `number`: `0` : `false`\n - `number`: 0이 아님 : `true`\n - `null` : `false`\n - `array`: 비어있음 : `false`\n - `array`: `true`로 변환된 요소를 가짐 : `true`\n - `array`: 모든 요소가 `false`로 변환 : `false`\n - `object`: 비어있음 : `false`\n - `object`: 비어있지 않음 : `true`\n - `function` : `false`"
},
"$not": {
"args": "arg",
"desc": "인수의 부정을 Boolean으로 변환합니다. `arg`는 가장먼저boolean으로 변환됩니다."
},
"$exists": {
"args": "arg",
"desc": "`arg` 식의 평가값이 존재하는 경우 `true`, 식의 평가결과가 미정의인 경우 (예: 존재하지 않는 참조필드로의 경로)는 `false`를 반환합니다."
},
"$count": {
"args": "array",
"desc": "`array`의 요소 갯수를 반환합니다."
},
"$append": {
"args": "array, array",
"desc": "두개의 `array`를 병합합니다."
},
"$sort": {
"args": "array [, function]",
"desc": "배열 `array`의 모든 값을 순서대로 정렬하여 반환합니다. \n\n 비교함수 `function`을 이용하는 경우, 비교함수는 아래와 같은 두개의 인수를 가져야 합니다. \n\n `function(left,right)` \n\n 비교함수는 left와 right의 두개의 값을 비교하기에, 값을 정렬하는 처리에서 호출됩니다. 만약 요구되는 정렬에서 left값을 right값보다 뒤로 두고싶은 경우에는, 비교함수는 치환을 나타내는 Boolean형의 ``true`를, 그렇지 않은 경우에는 `false`를 반환해야 합니다."
},
"$reverse": {
"args": "array",
"desc": "`array`에 포함된 모든 값의 순서를 역순으로 변환하여 반환합니다."
},
"$shuffle": {
"args": "array",
"desc": "`array`에 포함된 모든 값의 순서를 랜덤으로 반환합니다."
},
"$zip": {
"args": "array, ...",
"desc": "배열 `array1` ... arrayN`의 위치 0, 1, 2…. 의 값으로 구성된 convolved (zipped) 배열을 반환합니다."
},
"$keys": {
"args": "object",
"desc": "`object` 키를 포함하는 배열을 반환합니다. 인수가 오브젝트의 배열이면 반환되는 배열은 모든 오브젝트에있는 모든 키의 중복되지 않은 목록이 됩니다."
},
"$lookup": {
"args": "object, key",
"desc": "`object` 내의 `key`가 갖는 값을 반환합니다. 최초의 인수가 객체의 배열 인 경우, 배열 내의 모든 오브젝트를 검색하여, 존재하는 모든 키가 갖는 값을 반환합니다."
},
"$spread": {
"args": "object",
"desc": "`object`의 키/값 쌍별로 각 요소가 하나인 오브젝트 배열로 분할합니다. 만일 오브젝트 배열인 경우, 배열의 결과는 각 오브젝트에서 얻은 키/값 쌍의 오브젝트를 갖습니다."
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "`object`배열을 하나의 `object`로 병합합니다. 병합결과의 오브젝트는 입력배열내의 각 오브젝트의 키/값 쌍을 포함합니다. 입력 오브젝트가 같은 키를 가질경우, 반환 된 `object`에는 배열 마지막의 오브젝트의 키/값이 격납됩니다. 입력 배열이 오브젝트가 아닌 요소를 포함하는 경우, 에러가 발생합니다."
},
"$sift": {
"args": "object, function",
"desc": "함수 `function`을 충족시키는 `object` 인수 키/값 쌍만 포함하는 오브젝트를 반환합니다. \n\n 함수 `function` 다음과 같은 인수를 가져야 합니다 : \n\n `function(value [, key [, object]])`"
},
"$each": {
"args": "object, function",
"desc": "`object`의 각 키/값 쌍에, 함수`function`을 적용한 값의 배열을 반환합니다."
},
"$map": {
"args": "array, function",
"desc": "`array`의 각 값에 `function`을 적용한 결과로 이루어진 배열을 반환합니다. \n\n 함수 `function`은 다음과 같은 인수를 가져야 합니다. \n\n `function(value[, index[, array]])`"
},
"$filter": {
"args": "array, function",
"desc": "`array`의 값중, 함수 `function`의 조건을 만족하는 값으로 이루어진 배열을 반환합니다. \n\n 함수 `function`은 다음과 같은 형식을 가져야 합니다. \n\n `function(value[, index[, array]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "배열의 각 요소값에 함수 `function`을 연속적으로 적용하여 얻어지는 집계값을 반환합니다. `function`의 적용에는 직전의 `function`의 적용결과와 요소값이 인수로 주어집니다. \n\n 함수 `function`은 인수를 두개 뽑아, 배열의 각 요소 사이에 배치하는 중치연산자처럼 작용해야 합니다. \n\n 임의의 인수 `init`에는 집약시의 초기값을 설정합니다."
},
"$flowContext": {
"args": "string[, string]",
"desc": "플로우 컨텍스트 속성을 취득합니다."
},
"$globalContext": {
"args": "string[, string]",
"desc": "플로우의 글로벌 컨텍스트 속성을 취득합니다."
},
"$pad": {
"args": "string, width [, char]",
"desc": "문자수가 인수 `width`의 절대값이상이 되도록, 필요한 경우 여분의 패딩을 사용하여 `string`의 복사본을 반환합니다. \n\n `width`가 양수인 경우, 오른쪽으로 채워지고, 음수이면 왼쪽으로 채워집니다. \n\n 임의의 `char`인수에는 이 함수에서 사용할 패딩을 지정합니다. 지정하지 않는 경우에는, 기본값으로 공백을 사용합니다."
},
"$fromMillis": {
"args": "number",
"desc": "Unix Epoch (1970 년 1 월 1 일 UTC) 이후의 밀리 초를 나타내는 숫자를 ISO 8601 형식의 타임 스탬프 문자열로 변환합니다."
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "`number`를 문자열로 변환하고 `picture` 문자열에 지정된 표현으로 서식을 변경합니다. \n\n 이 함수의 동작은 XPath F&O 3.1사양에 정의된 XPath/XQuery함수의 fn:format-number의 동작과 같습니다. 인수의 문자열 picture은 fn:format-number 과 같은 구문으로 수치의 서식을 정의합니다. \n\n 임의의 제3 인수 `option`은 소수점기호와 같은 기본 로케일 고유의 서식설정문자를 덮어쓰는데에 사용됩니다. 이 인수를 지정할 경우, XPath F&O 3.1사양의 수치형식에 기술되어있는 name/value 쌍을 포함하는 오브젝트여야 합니다."
},
"$formatBase": {
"args": "number [, radix]",
"desc": "`number`를 인수 `radix`에 지정한 값을 기수로하는 문자열로 변환합니다. `radix`가 지정되지 않은 경우, 기수 10이 기본값으로 설정됩니다. `radix`에는 2~36의 값을 설정할 수 있고, 그 외의 값의 경우에는 에러가 발생합니다."
},
"$toMillis": {
"args": "timestamp",
"desc": "ISO 8601 형식의 `timestamp`를 Unix Epoch (1970 년 1 월 1 일 UTC) 이후의 밀리 초 수로 변환합니다. 문자열이 올바른 형식이 아닌 경우 에러가 발생합니다."
},
"$env": {
"args": "arg",
"desc": "환경변수를 값으로 반환합니다.\n\n 이 함수는 Node-RED 정의 함수입니다."
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -125,14 +125,20 @@ 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 (ev.subflow) {
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;
}
});
}
if (ev.subflow.hasOwnProperty('status')) {
subflow = RED.nodes.subflow(ev.subflow.id);
subflow.status = ev.subflow.status;
}
}
if (subflow) {
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
@@ -232,6 +238,11 @@ RED.history = (function() {
}
});
}
if (ev.subflow.hasOwnProperty('status')) {
if (ev.subflow.status) {
delete ev.node.status;
}
}
RED.editor.validateNode(ev.node);
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.inputs = ev.node.in.length;
@@ -290,6 +301,7 @@ RED.history = (function() {
RED.workspaces.order(ev.order);
}
}
Object.keys(modifiedTabs).forEach(function(id) {
var subflow = RED.nodes.subflow(id);
if (subflow) {
@@ -303,6 +315,7 @@ RED.history = (function() {
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
RED.subflow.refresh();
}
}

View File

@@ -2,6 +2,7 @@
"*": {
"ctrl-shift-p":"core:manage-palette",
"ctrl-f": "core:search",
"ctrl-shift-f": "core:list-flows",
"ctrl-=": "core:zoom-in",
"ctrl--": "core:zoom-out",
"ctrl-0": "core:zoom-reset",

View File

@@ -358,7 +358,10 @@ RED.nodes = (function() {
}
subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
defaults:{
name:{value:""},
env:{value:[]}
},
icon: function() { return sf.icon||"subflow.png" },
category: sf.category || "subflows",
inputs: sf.in.length,
@@ -369,6 +372,16 @@ RED.nodes = (function() {
paletteLabel: function() { return RED.nodes.subflow(sf.id).name },
inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null },
outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null },
oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-env-container-row)");
var height = size.height;
for (var i=0; i<rows.size(); i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-env-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-env-container").editableList('height',height-80);
},
set:{
module: "node-red"
}
@@ -535,6 +548,7 @@ RED.nodes = (function() {
node.category = n.category;
node.in = [];
node.out = [];
node.env = n.env;
n.in.forEach(function(p) {
var nIn = {x:p.x,y:p.y,wires:[]};
@@ -571,6 +585,18 @@ RED.nodes = (function() {
node.icon = n.icon;
}
}
if (n.status) {
node.status = {x: n.status.x, y: n.status.y, wires:[]};
links.forEach(function(d) {
if (d.target === n.status) {
if (d.source.type != "subflow") {
node.status.wires.push({id:d.source.id, port:d.sourcePort})
} else {
node.status.wires.push({id:n.id, port:0})
}
}
});
}
return node;
}
@@ -740,6 +766,20 @@ RED.nodes = (function() {
if (!$.isArray(newNodes)) {
newNodes = [newNodes];
}
// Scan for any duplicate nodes and remove them. This is a temporary
// fix to help resolve corrupted flows caused by 0.20.0 where multiple
// copies of the flow would get loaded at the same time.
// If the user hit deploy they would have saved those duplicates.
var seenIds = {};
newNodes = newNodes.filter(function(n) {
if (seenIds[n.id]) {
return false;
}
seenIds[n.id] = true;
return true;
})
var isInitialLoad = false;
if (!initialLoad) {
isInitialLoad = true;
@@ -851,6 +891,12 @@ RED.nodes = (function() {
output.i = i;
output.id = getID();
});
if (n.status) {
n.status.type = "subflow";
n.status.direction = "status";
n.status.z = n.id;
n.status.id = getID();
}
new_subflows.push(n);
addSubflow(n,createNewIds);
}
@@ -1018,6 +1064,7 @@ RED.nodes = (function() {
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
node.env = n.env;
} else {
if (!node._def) {
if (node.x && node.y) {
@@ -1189,6 +1236,19 @@ RED.nodes = (function() {
});
delete output.wires;
});
if (n.status) {
n.status.wires.forEach(function(wire) {
var link;
if (subflow_map[wire.id] && subflow_map[wire.id].id == n.id) {
link = {source:n.in[wire.port], sourcePort:wire.port,target:n.status};
} else {
link = {source:node_map[wire.id]||subflow_map[wire.id], sourcePort:wire.port,target:n.status};
}
addLink(link);
new_links.push(link);
});
delete n.status.wires;
}
}
RED.workspaces.refresh();

View File

@@ -28,15 +28,25 @@ var RED = (function() {
var hasDeferred = false;
var nodeConfigEls = $("<div>"+nodeConfig+"</div>");
nodeConfigEls.find("script").each(function(i,el) {
var scripts = nodeConfigEls.find("script");
var scriptCount = scripts.length;
scripts.each(function(i,el) {
var srcUrl = $(el).attr('src');
if (srcUrl && !/^\s*(https?:|\/|\.)/.test(srcUrl)) {
$(el).remove();
var newScript = document.createElement("script");
newScript.onload = function() { $("body").append(nodeConfigEls); done() }
newScript.onload = function() {
scriptCount--;
if (scriptCount === 0) {
$("body").append(nodeConfigEls);
done()
}
}
$('body').append(newScript);
newScript.src = RED.settings.apiRootUrl+srcUrl;
hasDeferred = true;
} else {
scriptCount--;
}
})
if (!hasDeferred) {
@@ -211,7 +221,7 @@ var RED = (function() {
}
]
} else if (msg.error === "missing-types") {
text+="<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
text+="<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
if (!!RED.projects.getActiveProject()) {
options.buttons = [
{
@@ -239,7 +249,7 @@ var RED = (function() {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup credentials",
text: RED._("notification.project.setupCredentials"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showCredentialsPrompt();
@@ -250,7 +260,7 @@ var RED = (function() {
} else {
options.buttons = [
{
text: "Close",
text: RED._("common.label.close"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
@@ -261,7 +271,7 @@ var RED = (function() {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Setup project files",
text: RED._("notification.project.setupProjectFiles"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.showFilesPrompt();
@@ -273,10 +283,10 @@ var RED = (function() {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Create default package file",
text: RED._("notification.project.setupProjectFiles"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultPackageFile();
RED.projects.showFilesPrompt();
}
}
]
@@ -285,13 +295,13 @@ var RED = (function() {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "No thanks",
text: RED._("notification.project.no"),
click: function() {
persistentNotifications[notificationId].hideNotification();
}
},
{
text: "Create default project files",
text: RED._("notification.project.createDefault"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultFileSet();
@@ -305,7 +315,7 @@ var RED = (function() {
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Show merge conflicts",
text: RED._("notification.project.mergeConflict"),
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.sidebar.versionControl.showLocalChanges();

View File

@@ -327,6 +327,14 @@
},
length: function() {
return this.element.children().length;
},
show: function(item) {
var items = this.element.children().filter(function(f) {
return item === $(this).find(".red-ui-editableList-item-content").data('data');
});
if (items.length > 0) {
this.uiContainer.scrollTop(this.uiContainer.scrollTop()+items.position().top)
}
}
});
})(jQuery);

View File

@@ -15,6 +15,19 @@
**/
(function($) {
/**
* options:
* - minimumLength : the minimum length of text before firing a change event
* - delay : delay, in ms, after a keystroke before firing change event
*
* methods:
* - value([val]) - gets the current value, or, if `val` is provided, sets the value
* - count - sets or clears a sub-label on the input. This can be used to provide
* a feedback on the number of matches, or number of available entries to search
* - change - trigger a change event
*
*/
$.widget( "nodered.searchBox", {
_create: function() {
var that = this;

View File

@@ -36,7 +36,7 @@ RED.tabs = (function() {
}
if (options.addButton) {
wrapper.addClass("red-ui-tabs-add");
var addButton = $('<div class="red-ui-tab-button"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
var addButton = $('<div class="red-ui-tab-button red-ui-tabs-add"><a href="#"><i class="fa fa-plus"></i></a></div>').appendTo(wrapper);
addButton.find('a').click(function(evt) {
evt.preventDefault();
if (typeof options.addButton === 'function') {
@@ -69,7 +69,25 @@ RED.tabs = (function() {
RED.actions.invoke(options.addButton,{index:targetIndex});
}
});
}
if (options.searchButton) {
wrapper.addClass("red-ui-tabs-search");
var searchButton = $('<div class="red-ui-tab-button red-ui-tabs-search"><a href="#"><i class="fa fa-list-ul"></i></a></div>').appendTo(wrapper);
searchButton.find('a').click(function(evt) {
evt.preventDefault();
if (typeof options.searchButton === 'function') {
options.searchButton()
} else if (typeof options.searchButton === 'string') {
RED.actions.invoke(options.searchButton);
}
})
if (typeof options.searchButton === 'string') {
var l = options.searchButton;
if (options.searchButtonCaption) {
l = options.searchButtonCaption
}
RED.popover.tooltip(searchButton,l,options.searchButton);
}
}
var scrollLeft;

View File

@@ -148,7 +148,7 @@
if (item.icon) {
$('<span class="red-ui-treeList-icon"><i class="'+item.icon+'" /></span>').appendTo(label);
}
$('<span class="red-ui-treeList-label-text"></span>').html(item.label).appendTo(label);
$('<span class="red-ui-treeList-label-text"></span>').text(item.label).appendTo(label);
if (item.children) {
if (Array.isArray(item.children)) {
that._addChildren(container,item.children,depth);
@@ -171,6 +171,13 @@
} else {
return this._data;
}
},
show: function(id) {
for (var i=0;i<this._data.length;i++) {
if (this._data[i].id === id) {
this._topList.editableList('show',this._data[i]);
}
}
}
});

View File

@@ -159,6 +159,11 @@
that.uiSelect.css("margin"+d,m);
that.input.css("margin"+d,0);
});
["type","placeholder"].forEach(function(d) {
var m = that.element.attr(d);
that.input.attr(d,m);
});
this.uiSelect.addClass("red-ui-typedInput-container");

View File

@@ -261,7 +261,9 @@ RED.deploy = (function() {
}
return list;
}
function sanitize(html) {
return html.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")
}
function restart() {
var startTime = Date.now();
$(".deploy-button-content").css('opacity',0);
@@ -353,7 +355,7 @@ RED.deploy = (function() {
if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true;
notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(unknownNodes).join("</li><li>")+"</li></ul><p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(unknownNodes).map(function(n) { return sanitize(n) }).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
@@ -373,7 +375,7 @@ RED.deploy = (function() {
invalidNodes.sort(sortNodeInfo);
notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"})).join("</li><li>")+"</li></ul><p>"+
'<ul class="node-dialog-configm-deploy-list"><li>'+cropList(invalidNodes.map(function(A) { return sanitize( (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")")})).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
notificationButtons= [

View File

@@ -687,7 +687,7 @@ RED.diff = (function() {
diff: remoteDiff
}
}
var selectState = "";
if (conflicted) {
@@ -1158,19 +1158,19 @@ RED.diff = (function() {
}
});
return {
var diff = {
currentConfig: currentConfig,
newConfig: newConfig,
added: added,
deleted: deleted,
changed: changed,
moved: moved
}
};
return diff;
}
function resolveDiffs(localDiff,remoteDiff) {
var conflicted = {};
var resolutions = {};
var diff = {
localDiff: localDiff,
remoteDiff: remoteDiff,
@@ -1348,7 +1348,7 @@ RED.diff = (function() {
if (node) {
nodeChangedStates[id] = node.changed;
}
localChangedStates[id] = true;
localChangedStates[id] = 1;
newConfig.push(remoteDiff.newConfig.all[id]);
}
} else {
@@ -1363,7 +1363,7 @@ RED.diff = (function() {
nodeChangedStates[id] = node.changed;
}
if (!localDiff.added.hasOwnProperty(id)) {
localChangedStates[id] = true;
localChangedStates[id] = 2;
newConfig.push(remoteDiff.newConfig.all[id]);
}
}
@@ -1376,24 +1376,42 @@ RED.diff = (function() {
}
function mergeDiff(diff) {
//console.log(diff);
var appliedDiff = applyDiff(diff);
var newConfig = appliedDiff.config;
var nodeChangedStates = appliedDiff.nodeChangedStates;
var localChangedStates = appliedDiff.localChangedStates;
var isDirty = RED.nodes.dirty();
var historyEvent = {
t:"replace",
config: RED.nodes.createCompleteNodeSet(),
changed: nodeChangedStates,
dirty: RED.nodes.dirty(),
dirty: isDirty,
rev: RED.nodes.version()
}
RED.history.push(historyEvent);
var originalFlow = RED.nodes.originalFlow();
// originalFlow is what the editor things it loaded
// - add any newly added nodes from remote diff as they are now part of the record
for (var id in diff.remoteDiff.added) {
if (diff.remoteDiff.added.hasOwnProperty(id)) {
if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
}
}
}
RED.nodes.clear();
var imported = RED.nodes.import(newConfig);
// Restore the original flow so subsequent merge resolutions can properly
// identify new-vs-old
RED.nodes.originalFlow(originalFlow);
imported[0].forEach(function(n) {
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
n.changed = true;
@@ -1402,11 +1420,16 @@ RED.diff = (function() {
RED.nodes.version(diff.remoteDiff.rev);
if (isDirty) {
RED.nodes.dirty(true);
}
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
}
function showTestFlowDiff(index) {
if (index === 1) {
var localFlow = RED.nodes.createCompleteNodeSet();

View File

@@ -498,7 +498,6 @@ RED.editor = (function() {
}
function getEditStackTitle() {
var title = '<ul class="editor-tray-breadcrumbs">';
var label;
for (var i=editStack.length-1;i<editStack.length;i++) {
var node = editStack[i];
@@ -514,33 +513,174 @@ RED.editor = (function() {
} else if (node.type === '_buffer') {
label = RED._("bufferEditor.title");
} else if (node.type === 'subflow') {
label = RED._("subflow.editSubflow",{name:node.name})
label = RED._("subflow.editSubflow",{name:RED.utils.sanitize(node.name)})
} else if (node.type.indexOf("subflow:")===0) {
var subflow = RED.nodes.subflow(node.type.substring(8));
label = RED._("subflow.editSubflow",{name:subflow.name})
label = RED._("subflow.editSubflowInstance",{name:RED.utils.sanitize(subflow.name)})
} else {
if (typeof node._def.paletteLabel !== "undefined") {
try {
label = (typeof node._def.paletteLabel === "function" ? node._def.paletteLabel.call(node._def) : node._def.paletteLabel)||"";
label = RED.utils.sanitize((typeof node._def.paletteLabel === "function" ? node._def.paletteLabel.call(node._def) : node._def.paletteLabel)||"");
} catch(err) {
console.log("Definition error: "+node.type+".paletteLabel",err);
}
}
if (i === editStack.length-1) {
if (RED.nodes.node(node.id)) {
label = RED._("editor.editNode",{type:label});
label = RED._("editor.editNode",{type:RED.utils.sanitize(label)});
} else {
label = RED._("editor.addNewConfig",{type:label});
label = RED._("editor.addNewConfig",{type:RED.utils.sanitize(label)});
}
}
}
title += '<li>'+label+'</li>';
}
title += '</ul>';
return label;
}
function buildEditForm(container,formId,type,ns) {
function buildEnvForm(container, node) {
var env_container = $('#node-input-env-container');
env_container
.css({
'min-height':'150px',
'min-width':'450px'
})
.editableList({
addItem: function(container, i, opt) {
var row = $('<div/>').appendTo(container);
if (opt.parent) {
$('<div/>', {
class:"uneditable-input",
style: "margin-left: 5px; width: calc(40% - 8px)",
}).appendTo(row).text(opt.name);
} else {
$('<input/>', {
class: "node-input-env-name",
type: "text",
style: "margin-left: 5px; width: calc(40% - 8px)",
placeholder: RED._("common.label.name")
}).appendTo(row).val(opt.name);
}
var valueField = $('<input/>',{
class: "node-input-env-value",
type: "text",
style: "margin-left: 5px; width: calc(60% - 8px)"
}).appendTo(row)
valueField.typedInput({default:'str',
types:['str','num','bool','json','bin','env']
});
valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type);
valueField.typedInput('value', opt.parent?(opt.value||opt.parent.value):opt.value);
var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove editor-button editor-button-small"}).appendTo(container);
$('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton);
container.parent().addClass("red-ui-editableList-item-removable");
if (opt.parent) {
if (opt.value && (opt.value !== opt.parent.value || opt.type !== opt.parent.type)) {
actionButton.show();
} else {
actionButton.hide();
}
var restoreTip = RED.popover.tooltip(actionButton,RED._("subflow.env.restore"));
valueField.change(function(evt) {
var newType = valueField.typedInput('type');
var newValue = valueField.typedInput('value');
if (newType === opt.parent.type && newValue === opt.parent.value) {
actionButton.hide();
} else {
actionButton.show();
}
})
actionButton.click(function(evt) {
evt.preventDefault();
restoreTip.close();
valueField.typedInput('type', opt.parent.type);
valueField.typedInput('value', opt.parent.value);
})
} else {
var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove"));
actionButton.click(function(evt) {
evt.preventDefault();
removeTip.close();
container.parent().addClass("red-ui-editableList-item-deleting")
container.fadeOut(300, function() {
env_container.editableList('removeItem',opt);
});
});
}
},
sortable: false,
removable: false
});
var parentEnv = {};
var envList = [];
if (/^subflow:/.test(node.type)) {
var subflowDef = RED.nodes.subflow(node.type.substring(8));
if (subflowDef.env) {
subflowDef.env.forEach(function(env) {
var item = {
name:env.name,
parent: {
type: env.type,
value: env.value
}
}
envList.push(item);
parentEnv[env.name] = item;
})
}
}
if (node.env) {
for (var i = 0; i < node.env.length; i++) {
var env = node.env[i];
if (parentEnv.hasOwnProperty(env.name)) {
parentEnv[env.name].type = env.type;
parentEnv[env.name].value = env.value;
} else {
envList.push({
name: env.name,
type: env.type,
value: env.value
});
}
}
}
envList.forEach(function(env) {
env_container.editableList('addItem', env);
})
}
function exportEnvList(list) {
if (list) {
var env = [];
list.each(function(i) {
var entry = $(this);
var item = entry.data('data');
var name = item.parent?item.name:entry.find(".node-input-env-name").val();
var valueInput = entry.find(".node-input-env-value");
var value = valueInput.typedInput("value");
var type = valueInput.typedInput("type");
if (!item.parent || (item.parent.value !== value || item.parent.type !== type)) {
var item = {
name: name,
type: type,
value: value
};
env.push(item);
}
});
return env;
}
return null;
}
function isSameEnv(env0, env1) {
return (JSON.stringify(env0) === JSON.stringify(env1));
}
function buildEditForm(container,formId,type,ns,node) {
var dialogForm = $('<form id="'+formId+'" class="form-horizontal" autocomplete="off"></form>').appendTo(container);
dialogForm.html($("script[data-template-name='"+type+"']").html());
ns = ns||"node-red";
@@ -561,6 +701,11 @@ RED.editor = (function() {
}
$(this).attr("data-i18n",keys.join(";"));
});
if ((type === "subflow") || (type === "subflow-template")) {
buildEnvForm(dialogForm, node);
}
// Add dummy fields to prevent 'Enter' submitting the form in some
// cases, and also prevent browser auto-fill of password
// Add in reverse order as they are prepended...
@@ -692,7 +837,7 @@ RED.editor = (function() {
var id = "node-label-form-"+type+"-"+index;
$('<label>',{for:id}).text((index+1)+".").appendTo(result);
var input = $('<input>',{type:"text",id:id, placeholder: placeHolder}).val(value).appendTo(result);
var clear = $('<button class="editor-button editor-button-small"><i class="fa fa-times"></i></button>').appendTo(result);
var clear = $('<button type="button" class="editor-button editor-button-small"><i class="fa fa-times"></i></button>').appendTo(result);
clear.click(function(evt) {
evt.preventDefault();
input.val("");
@@ -749,7 +894,7 @@ RED.editor = (function() {
var iconList = $('<div class="red-ui-icon-list">').appendTo(picker);
var metaRow = $('<div class="red-ui-icon-meta"></div>').appendTo(picker);
var summary = $('<span>').appendTo(metaRow);
var resetButton = $('<button class="editor-button editor-button-small">'+RED._("editor.useDefault")+'</button>').appendTo(metaRow).click(function(e) {
var resetButton = $('<button type="button" class="editor-button editor-button-small">'+RED._("editor.useDefault")+'</button>').appendTo(metaRow).click(function(e) {
e.preventDefault();
hide();
done(null);
@@ -798,7 +943,7 @@ RED.editor = (function() {
$('<div class="form-row">'+
'<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+
'<button id="node-input-show-label-btn" class="editor-button" style="min-width: 80px; text-align: left;" type="button"><i id="node-input-show-label-btn-i" class="fa fa-toggle-on"></i> <span id="node-input-show-label-label"></span></button> '+
'<button type="button" id="node-input-show-label-btn" class="editor-button" style="min-width: 80px; text-align: left;" type="button"><i id="node-input-show-label-btn-i" class="fa fa-toggle-on"></i> <span id="node-input-show-label-label"></span></button> '+
'<input type="checkbox" id="node-input-show-label" style="display: none;"/>'+
'</div>').appendTo(dialogForm);
@@ -832,7 +977,7 @@ RED.editor = (function() {
var iconRow = $('<div class="form-row"></div>').appendTo(dialogForm);
$('<label data-i18n="editor.settingIcon">').appendTo(iconRow);
var iconButton = $('<button class="editor-button" id="node-settings-icon-button">').appendTo(iconRow);
var iconButton = $('<button type="button" class="editor-button" id="node-settings-icon-button">').appendTo(iconRow);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconButton);
var colour = RED.utils.getNodeColor(node.type, node._def);
@@ -1268,6 +1413,16 @@ RED.editor = (function() {
}
}
if (type === "subflow") {
var old_env = editing_node.env;
var new_env = exportEnvList($("#node-input-env-container").editableList("items"));
if (!isSameEnv(old_env, new_env)) {
editing_node.env = new_env;
changes.env = editing_node.env;
changed = true;
}
}
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
@@ -1373,7 +1528,7 @@ RED.editor = (function() {
content: $('<div>', {class:"editor-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog"
};
buildEditForm(nodePropertiesTab.content,"dialog-form",type,ns);
buildEditForm(nodePropertiesTab.content,"dialog-form",type,ns,node);
editorTabs.addTab(nodePropertiesTab);
if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) {
@@ -1580,7 +1735,7 @@ RED.editor = (function() {
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
$('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'></option>').text(workspaceLabel).appendTo(tabSelect);
});
tabSelect.append('<option disabled data-i18n="sidebar.config.subflows"></option>');
RED.nodes.eachSubflow(function(ws) {
@@ -1588,7 +1743,7 @@ RED.editor = (function() {
if (nodeUserFlows[ws.id]) {
workspaceLabel = "* "+workspaceLabel;
}
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
$('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'></option>').text(workspaceLabel).appendTo(tabSelect);
});
if (flowCount > 0) {
tabSelect.on('change',function() {
@@ -1909,7 +2064,7 @@ RED.editor = (function() {
}
configNodes.forEach(function(cn) {
select.append('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'>'+RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)+'</option>');
$('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
delete cn.__label__;
});
@@ -1986,6 +2141,14 @@ RED.editor = (function() {
changed = true;
}
var old_env = editing_node.env;
var new_env = exportEnvList($("#node-input-env-container").editableList("items"));
if (!isSameEnv(old_env, new_env)) {
editing_node.env = new_env;
changes.env = editing_node.env;
changed = true;
}
RED.palette.refresh();
if (changed) {
@@ -2024,9 +2187,17 @@ RED.editor = (function() {
}
}
],
resize: function(dimensions) {
$(".editor-tray-content").height(dimensions.height - 50);
var form = $(".editor-tray-content form").height(dimensions.height - 50 - 40);
resize: function(size) {
$(".editor-tray-content").height(size.height - 50);
// var form = $(".editor-tray-content form").height(size.height - 50 - 40);
var rows = $("#dialog-form>div:not(.node-input-env-container-row)");
var height = size.height;
for (var i=0; i<rows.size(); i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-env-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-env-container").editableList('height',height-80);
},
open: function(tray) {
var trayFooter = tray.find(".editor-tray-footer");
@@ -2063,7 +2234,7 @@ RED.editor = (function() {
content: $('<div>', {class:"editor-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog"
};
buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template");
buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node);
editorTabs.addTab(nodePropertiesTab);
var descriptionTab = {
@@ -2224,7 +2395,7 @@ RED.editor = (function() {
$(el).addClass("node-text-editor-container-toolbar");
editor.toolbar = customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
if (options.expandable !== false) {
var expandButton = $('<button class="editor-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar);
var expandButton = $('<button type="button" class="editor-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar);
expandButton.click(function(e) {
e.preventDefault();
@@ -2243,7 +2414,7 @@ RED.editor = (function() {
})
});
}
var helpButton = $('<button class="node-text-editor-help editor-button editor-button-small"><i class="fa fa-question"></i></button>').appendTo($(el).parent());
var helpButton = $('<button type="button" class="node-text-editor-help editor-button editor-button-small"><i class="fa fa-question"></i></button>').appendTo($(el).parent());
RED.popover.create({
target: helpButton,
trigger: 'click',

View File

@@ -17,21 +17,21 @@
var toolbarTemplate = '<div style="margin-bottom: 5px">'+
'<span class="button-group">'+
'<button class="editor-button" data-style="h1" style="font-size:1.1em; font-weight: bold">h1</button>'+
'<button class="editor-button" data-style="h2" style="font-size:1.0em; font-weight: bold">h2</button>'+
'<button class="editor-button" data-style="h3" style="font-size:0.9em; font-weight: bold">h3</button>'+
'<button type="button" class="editor-button" data-style="h1" style="font-size:1.1em; font-weight: bold">h1</button>'+
'<button type="button" class="editor-button" data-style="h2" style="font-size:1.0em; font-weight: bold">h2</button>'+
'<button type="button" class="editor-button" data-style="h3" style="font-size:0.9em; font-weight: bold">h3</button>'+
'</span>'+
'<span class="button-group">'+
'<button class="editor-button" data-style="b"><i class="fa fa-bold"></i></button>'+
'<button class="editor-button" data-style="i"><i class="fa fa-italic"></i></button>'+
'<button class="editor-button" data-style="code"><i class="fa fa-code"></i></button>'+
'<button type="button" class="editor-button" data-style="b"><i class="fa fa-bold"></i></button>'+
'<button type="button" class="editor-button" data-style="i"><i class="fa fa-italic"></i></button>'+
'<button type="button" class="editor-button" data-style="code"><i class="fa fa-code"></i></button>'+
'</span>'+
'<span class="button-group">'+
'<button class="editor-button" data-style="ol"><i class="fa fa-list-ol"></i></button>'+
'<button class="editor-button" data-style="ul"><i class="fa fa-list-ul"></i></button>'+
'<button class="editor-button" data-style="bq"><i class="fa fa-quote-left"></i></button>'+
'<button class="editor-button" data-style="hr"><i class="fa fa-minus"></i></button>'+
'<button class="editor-button" data-style="link"><i class="fa fa-link"></i></button>'+
'<button type="button" class="editor-button" data-style="ol"><i class="fa fa-list-ol"></i></button>'+
'<button type="button" class="editor-button" data-style="ul"><i class="fa fa-list-ul"></i></button>'+
'<button type="button" class="editor-button" data-style="bq"><i class="fa fa-quote-left"></i></button>'+
'<button type="button" class="editor-button" data-style="hr"><i class="fa fa-minus"></i></button>'+
'<button type="button" class="editor-button" data-style="link"><i class="fa fa-link"></i></button>'+
'</span>'
'</div>';
@@ -123,7 +123,7 @@
panels.ratio(1);
$('<span class="button-group" style="float:right">'+
'<button id="node-btn-markdown-preview" class="editor-button toggle single"><i class="fa fa-eye"></i></button>'+
'<button type="button" id="node-btn-markdown-preview" class="editor-button toggle single"><i class="fa fa-eye"></i></button>'+
'</span>').appendTo(expressionEditor.toolbar);
$("#node-btn-markdown-preview").click(function(e) {

15
packages/node_modules/@node-red/editor-client/src/js/ui/library.js vendored Normal file → Executable file
View File

@@ -44,8 +44,8 @@ RED.library = (function() {
li.className = "dropdown-submenu pull-left";
a = document.createElement("a");
a.href="#";
var label = i.replace(/^@.*\//,"").replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/," ").replace(/_/," ");
a.innerHTML = label;
var label = i.replace(/^@.*\//,"").replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/g," ").replace(/_/g," ");
a.innerText = label;
li.appendChild(a);
li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i));
ul.appendChild(li);
@@ -58,7 +58,7 @@ RED.library = (function() {
li = document.createElement("li");
a = document.createElement("a");
a.href="#";
a.innerHTML = data.f[i];
a.innerText = data.f[i];
a.flowName = root+(root!==""?"/":"")+data.f[i];
a.onclick = function() {
$.get('library/flows/'+this.flowName, function(data) {
@@ -125,8 +125,8 @@ RED.library = (function() {
li.onclick = (function () {
var dirName = v;
return function(e) {
var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>');
$("a",bcli).click(function(e) {
var bcli = $('<li class="active"><span class="divider">/</span> </li>');
$('<a href="#"></a>').text(dirName).appendTo(bcli).click(function(e) {
$(this).parent().nextAll().remove();
$.getJSON("library/"+options.url+root+dirName,function(data) {
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
@@ -141,12 +141,13 @@ RED.library = (function() {
});
}
})();
li.innerHTML = '<i class="fa fa-folder"></i> '+v+"</i>";
$('<i class="fa fa-folder"></i>').appendTo(li);
$('<span>').text(" "+v).appendTo(li);
ul.appendChild(li);
} else {
// file
li = buildFileListItem(v);
li.innerHTML = v.name;
li.innerText = v.name;
li.onclick = (function() {
var item = v;
return function(e) {

23
packages/node_modules/@node-red/editor-client/src/js/ui/palette.js vendored Normal file → Executable file
View File

@@ -78,6 +78,8 @@ RED.palette = (function() {
var lineHeight = 20;
var portHeight = 10;
label = RED.utils.sanitize(label);
var words = label.split(/[ -]/);
var displayLines = [];
@@ -175,13 +177,19 @@ RED.palette = (function() {
}
}
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
$('<div/>', {
class: "palette_label"
+ (((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " palette_label_right" : "")
}).appendTo(d);
d.className="palette_node";
if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
var iconContainer = $('<div/>', {
class: "palette_icon_container"
+ (((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " palette_icon_container_right" : "")
}).appendTo(d);
RED.utils.createIconElement(icon_url, iconContainer, true);
}
@@ -382,6 +390,17 @@ RED.palette = (function() {
var portInput = paletteNode.find(".palette_port_input");
var portOutput = paletteNode.find(".palette_port_output");
var paletteLabel = paletteNode.find(".palette_label");
paletteLabel.attr("class","palette_label"
+ (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " palette_label_right" : "")
);
var paletteIconContainer = paletteNode.find(".palette_icon_container");
paletteIconContainer.attr("class","palette_icon_container"
+ (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " palette_icon_container_right" : "")
);
if (portInput.length === 0 && sf.in.length > 0) {
var portIn = document.createElement("div");
portIn.className = "palette_port palette_port_input";

View File

@@ -526,7 +526,7 @@ RED.projects.settings = (function() {
}
function showProjectFileListing(row,activeProject,current,filter,done) {
function showProjectFileListing(row,activeProject,current,selectFilter,done) {
var dialog;
var dialogBody;
var filesList;
@@ -569,14 +569,15 @@ RED.projects.settings = (function() {
})
return result;
}
var files = sortFiles("",files,"");
createFileSubList(container,files.children,current,filter,done,"height: 175px");
var files = sortFiles("",files,"")
createFileSubList(container,files.children,current,selectFilter,done,"height: 175px");
spinner.remove();
});
return container;
}
function createFileSubList(container, files, current, filter, onselect, style) {
function createFileSubList(container, files, current, selectFilter, onselect, style) {
style = style || "";
var list = $('<ol>',{class:"projects-dialog-file-list", style:style}).appendTo(container).editableList({
addButton: false,
@@ -592,7 +593,7 @@ RED.projects.settings = (function() {
} else {
children.hide();
}
createFileSubList(children,entry.children,current,filter,onselect);
createFileSubList(children,entry.children,current,selectFilter,onselect);
header.addClass("selectable");
header.click(function(e) {
if ($(this).hasClass("expanded")) {
@@ -618,7 +619,7 @@ RED.projects.settings = (function() {
header.addClass("projects-dialog-file-list-entry-file-type-git");
}
$('<span class="projects-dialog-file-list-entry-file"> <i class="fa '+fileIcon+'"></i></span>').appendTo(header);
if (filter.test(entry.name)) {
if (selectFilter(entry)) {
header.addClass("selectable");
if (entry.path === current) {
header.addClass("selected");
@@ -626,7 +627,7 @@ RED.projects.settings = (function() {
header.click(function(e) {
$(".projects-dialog-file-list-entry.selected").removeClass("selected");
$(this).addClass("selected");
onselect(entry.path);
onselect(entry.path,true);
})
header.dblclick(function(e) {
e.preventDefault();
@@ -730,18 +731,27 @@ RED.projects.settings = (function() {
var title = $('<h3></h3>').text(RED._("sidebar.project.projectSettings.files")).appendTo(pane);
var filesContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
if (RED.user.hasPermission("projects.write")) {
var editFilesButton = $('<button class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.projectSettings.edit') + '</button>')
var editFilesButton = $('<button type="button" id="project-settings-tab-settings-file-edit" class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.projectSettings.edit') + '</button>')
.appendTo(title)
.click(function(evt) {
evt.preventDefault();
formButtons.show();
editFilesButton.hide();
// packageFileLabelText.hide();
if (!activeProject.files.package) {
packageFileSubLabel.find(".projects-edit-form-sublabel-text").text(RED._("sidebar.project.projectSettings.packageCreate"));
packageFileSubLabel.show();
}
packageFileInputSearch.show();
// packageFileInputCreate.show();
flowFileLabelText.hide();
flowFileInput.show();
flowFileInputSearch.show();
credFileLabel.hide();
credFileInput.show();
flowFileInput.focus();
flowFileInputResize();
// credentialStateLabel.parent().hide();
credentialStateLabel.addClass("uneditable-input");
$(".user-settings-row-credentials").show();
@@ -752,14 +762,83 @@ RED.projects.settings = (function() {
}
var row;
// Flow files
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.package")).appendTo(row);
var packageFileLabel = $('<div class="uneditable-input" style="padding:0">').appendTo(row);
var packageFileLabelText = $('<span style="display:inline-block; padding: 6px">').text(activeProject.files.package||"package.json").appendTo(packageFileLabel);
var packageFileInput = $('<input type="hidden">').val(activeProject.files.package||"package.json").appendTo(packageFileLabel);
var packageFileInputSearch = $('<button type="button" class="editor-button toggle single" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; width: 36px; height: 34px; position: absolute; top: -1px; right: -1px;"><i class="fa fa-folder-open-o"></i></button>')
.hide()
.appendTo(packageFileLabel)
.click(function(e) {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
packageFileLabel.find('.project-file-listing-container').slideUp(200,function() {
$(this).remove();
packageFileLabel.css('height','');
});
packageFileLabel.css('color','');
} else {
$(this).addClass('selected');
packageFileLabel.css('color','inherit');
var fileList = showProjectFileListing(packageFileLabel,activeProject,packageFileInput.val(),
function(entry) { return entry.children || /package\.json$/.test(entry.path); },
function(result,close) {
if (result) {
packageFileInput.val(result);
packageFileLabelText.text(result);
var rootDir = result.substring(0,result.length - 12);
flowFileLabelPrefixText.text(rootDir);
credFileLabelPrefixText.text(rootDir);
flowFileInputResize();
packageFileSubLabel.hide();
}
if (close) {
$(packageFileInputSearch).click();
}
checkFiles();
});
packageFileLabel.css('height','auto');
setTimeout(function() {
fileList.slideDown(200);
},50);
}
})
RED.popover.tooltip(packageFileInputSearch,RED._("sidebar.project.projectSettings.selectFile"));
var packageFileSubLabel = $('<label style="margin-left: 110px" class="projects-edit-form-sublabel"><small><span class="form-warning"><i class="fa fa-warning"></i> <span class="projects-edit-form-sublabel-text"></span></small></label>').appendTo(row).hide();
if (!activeProject.files.package) {
packageFileSubLabel.find(".projects-edit-form-sublabel-text").text(RED._("sidebar.project.projectSettings.fileNotExist"));
packageFileSubLabel.show();
}
var projectPackage = activeProject.files.package||"package.json";
var projectRoot = projectPackage.substring(0,projectPackage.length - 12);
// Flow files
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.flow")).appendTo(row);
var flowFileLabel = $('<div class="uneditable-input" style="padding:0">').appendTo(row);
var flowFileLabelText = $('<span style="display:inline-block; padding: 6px">').text(activeProject.files.flow).appendTo(flowFileLabel);
var flowFileInput = $('<input id="" type="text" style="margin-bottom: 0;width: 100%; border: none;">').val(activeProject.files.flow).hide().appendTo(flowFileLabel);
var flowFileInputSearch = $('<button class="editor-button" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; width: 36px; height: 34px; position: absolute; top: -1px; right: -1px;"><i class="fa fa-folder-open-o"></i></button>')
var flowFileLabelPrefixText = $('<span style="display:inline-block; padding: 6px 0 6px 6px">').text(projectRoot).appendTo(flowFileLabel);
var flowFileName = "flows.json";
if (activeProject.files.flow) {
if (activeProject.files.flow.indexOf(projectRoot) === 0) {
flowFileName = activeProject.files.flow.substring(projectRoot.length);
} else {
flowFileName = activeProject.files.flow;
}
}
var flowFileLabelText = $('<span style="display:inline-block; padding: 6px 6px 6px 0">').text(flowFileName).appendTo(flowFileLabel);
var flowFileInputResize = function() {
flowFileInput.css({
"width": "calc(100% - "+(flowFileInputSearch.width() + flowFileLabelPrefixText.width())+"px)"
});
}
var flowFileInput = $('<input type="text" style="padding-left:1px; margin-top: -2px; margin-bottom: 0;border: none;">').val(flowFileName).hide().appendTo(flowFileLabel);
var flowFileInputSearch = $('<button type="button" class="editor-button toggle single" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; width: 36px; height: 34px; position: absolute; top: -1px; right: -1px;"><i class="fa fa-folder-open-o"></i></button>')
.hide()
.appendTo(flowFileLabel)
.click(function(e) {
@@ -773,15 +852,24 @@ RED.projects.settings = (function() {
} else {
$(this).addClass('selected');
flowFileLabel.css('color','inherit');
var fileList = showProjectFileListing(flowFileLabel,activeProject,flowFileInput.val(), /.*\.json$/,function(result,isDblClick) {
if (result) {
flowFileInput.val(result);
}
if (isDblClick) {
$(flowFileInputSearch).click();
}
checkFiles();
});
var packageFile = packageFileInput.val();
var packagePrefix = packageFile.substring(0,packageFile.length - 12);
var re = new RegExp("^"+packagePrefix+".*\.json$");
var fileList = showProjectFileListing(flowFileLabel,
activeProject,
flowFileInput.val(),
function(entry) { return !/package.json$/.test(entry.path) && re.test(entry.path) && !/_cred\.json$/.test(entry.path) },
function(result,isDblClick) {
if (result) {
flowFileInput.val(result.substring(packagePrefix.length));
}
if (isDblClick) {
$(flowFileInputSearch).click();
}
checkFiles();
}
);
flowFileLabel.css('height','auto');
setTimeout(function() {
fileList.slideDown(200);
@@ -789,26 +877,41 @@ RED.projects.settings = (function() {
}
})
RED.popover.tooltip(flowFileInputSearch,RED._("sidebar.project.projectSettings.selectFile"));
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
$('<label for=""></label>').text(RED._("sidebar.project.projectSettings.credentials")).appendTo(row);
var credFileLabel = $('<div class="uneditable-input">').text(activeProject.files.credentials).appendTo(row);
var credFileInput = $('<div class="uneditable-input">').text(activeProject.files.credentials).hide().insertAfter(credFileLabel);
var credFileName = "flows_cred.json";
if (activeProject.files.credentials) {
if (activeProject.files.flow.indexOf(projectRoot) === 0) {
credFileName = activeProject.files.credentials.substring(projectRoot.length);
} else {
credFileName = activeProject.files.credentials;
}
}
var credFileLabel = $('<div class="uneditable-input" style="padding:0">').appendTo(row);
var credFileLabelPrefixText = $('<span style="display:inline-block;padding: 6px 0 6px 6px">').text(projectRoot).appendTo(credFileLabel);
var credFileLabelText = $('<span style="display:inline-block; padding: 6px 6px 6px 0">').text(credFileName).appendTo(credFileLabel);
var credFileInput = $('<input type="hidden">').val(credFileName).insertAfter(credFileLabel);
var checkFiles = function() {
var saveDisabled;
var currentFlowValue = flowFileInput.val();
var m = /^(.+?)(\.[^.]*)?$/.exec(currentFlowValue);
if (m) {
credFileInput.text(m[1]+"_cred"+(m[2]||".json"));
credFileLabelText.text(m[1]+"_cred"+(m[2]||".json"));
} else if (currentFlowValue === "") {
credFileInput.text("");
credFileLabelText.text("");
}
credFileInput.val(credFileLabelText.text());
var isFlowInvalid = currentFlowValue==="" ||
/\.\./.test(currentFlowValue) ||
/\/$/.test(currentFlowValue);
saveDisabled = isFlowInvalid || credFileInput.text()==="";
saveDisabled = isFlowInvalid || credFileLabelText.text()==="";
if (credentialSecretExistingInput.is(":visible")) {
credentialSecretExistingInput.toggleClass("input-error", credentialSecretExistingInput.val() === "");
@@ -821,19 +924,22 @@ RED.projects.settings = (function() {
flowFileInput.toggleClass("input-error", isFlowInvalid);
credFileInput.toggleClass("input-error",credFileInput.text()==="");
// credFileInput.toggleClass("input-error",credFileInput.text()==="");
saveButton.toggleClass('disabled',saveDisabled);
saveButton.prop('disabled',saveDisabled);
}
flowFileInput.on("change keyup paste",checkFiles);
if (!activeProject.files.flow) {
$('<span class="form-warning"><i class="fa fa-warning"></i> Missing</span>').appendTo(flowFileLabelText);
}
if (!activeProject.files.credentials) {
$('<span class="form-warning"><i class="fa fa-warning"></i> Missing</span>').appendTo(credFileLabel);
}
// if (!activeProject.files.package) {
// $('<span class="form-warning"><i class="fa fa-warning"></i> Missing</span>').appendTo(packageFileLabelText);
// }
// if (!activeProject.files.flow) {
// $('<span class="form-warning"><i class="fa fa-warning"></i> Missing</span>').appendTo(flowFileLabelText);
// }
// if (!activeProject.files.credentials) {
// $('<span class="form-warning"><i class="fa fa-warning"></i> Missing</span>').appendTo(credFileLabel);
// }
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
@@ -844,7 +950,7 @@ RED.projects.settings = (function() {
credentialStateLabel.css('color','#666');
credentialSecretButtons.css('vertical-align','top');
var credentialSecretResetButton = $('<button class="editor-button" style="vertical-align: top; width: 36px; margin-bottom: 10px"><i class="fa fa-trash-o"></i></button>')
var credentialSecretResetButton = $('<button type="button" class="editor-button" style="vertical-align: top; width: 36px; margin-bottom: 10px"><i class="fa fa-trash-o"></i></button>')
.appendTo(credentialSecretButtons)
.click(function(e) {
e.preventDefault();
@@ -866,7 +972,9 @@ RED.projects.settings = (function() {
}
checkFiles();
});
var credentialSecretEditButton = $('<button class="editor-button" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; vertical-align: top; width: 36px; margin-bottom: 10px"><i class="fa fa-pencil"></i></button>')
RED.popover.tooltip(credentialSecretResetButton,RED._("sidebar.project.projectSettings.resetTheEncryptionKey"));
var credentialSecretEditButton = $('<button type="button" class="editor-button" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; vertical-align: top; width: 36px; margin-bottom: 10px"><i class="fa fa-pencil"></i></button>')
.appendTo(credentialSecretButtons)
.click(function(e) {
e.preventDefault();
@@ -896,6 +1004,7 @@ RED.projects.settings = (function() {
checkFiles();
})
RED.popover.tooltip(credentialSecretEditButton,RED._("sidebar.project.projectSettings.changeTheEncryptionKey"));
row = $('<div class="user-settings-row user-settings-row-credentials"></div>').hide().appendTo(filesContainer);
@@ -930,11 +1039,13 @@ RED.projects.settings = (function() {
var hideEditForm = function() {
editFilesButton.show();
formButtons.hide();
// packageFileLabelText.show();
packageFileInputSearch.hide();
// packageFileInputCreate.hide();
flowFileLabelText.show();
flowFileInput.hide();
flowFileInputSearch.hide();
credFileLabel.show();
credFileInput.hide();
// credentialStateLabel.parent().show();
credentialStateLabel.removeClass("uneditable-input");
credentialStateLabel.css('height','');
@@ -954,13 +1065,26 @@ RED.projects.settings = (function() {
}
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').hide().appendTo(filesContainer);
$('<button class="editor-button">' + RED._("common.label.cancel") + '</button>')
$('<button type="button" class="editor-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
var projectPackage = activeProject.files.package||"package.json";
var projectRoot = projectPackage.substring(0,projectPackage.length - 12);
flowFileLabelPrefixText.text(projectRoot);
credFileLabelPrefixText.text(projectRoot);
packageFileLabelText.text(activeProject.files.package||"package.json");
if (!activeProject.files.package) {
packageFileSubLabel.find(".projects-edit-form-sublabel-text").text(RED._("sidebar.project.projectSettings.fileNotExist"));
packageFileSubLabel.show();
} else {
packageFileSubLabel.hide();
}
flowFileInput.val(flowFileLabelText.text());
credFileLabelText.text(credFileName);
hideEditForm();
});
var saveButton = $('<button class="editor-button">' + RED._("common.label.save") + '</button>')
var saveButton = $('<button type="button" class="editor-button">' + RED._("common.label.save") + '</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
@@ -972,13 +1096,17 @@ RED.projects.settings = (function() {
return;
}
flowFileLabelText.text(flowFileInput.val());
credFileLabel.text(credFileInput.text());
credFileLabelText.text(credFileInput.val());
packageFileSubLabel.hide();
hideEditForm();
}
var rootPath = packageFileInput.val();
rootPath = rootPath.substring(0,rootPath.length-12);
var payload = {
files: {
flow: flowFileInput.val(),
credentials: credFileInput.text()
package: packageFileInput.val(),
flow: rootPath+flowFileInput.val(),
credentials: rootPath+credFileInput.val()
}
}
@@ -991,8 +1119,6 @@ RED.projects.settings = (function() {
payload.currentCredentialSecret = credentialSecretExistingInput.val();
}
}
// console.log(JSON.stringify(payload,null,4));
RED.deploy.setDeployInflight(true);
utils.sendRequest({
url: "projects/"+activeProject.name,

View File

@@ -105,7 +105,7 @@ RED.projects = (function() {
buttons: [
{
// id: "clipboard-dialog-cancel",
text: "Open existing project", //RED._("projects.welcome.not-right-now"),
text: RED._("projects.welcome.openExistingProject"),
class: "secondary",
click: function() {
createProjectOptions = {
@@ -666,6 +666,10 @@ RED.projects = (function() {
// This is handled via a runtime notification.
dialog.dialog("close");
},
'missing_package_file': function(error) {
// This is handled via a runtime notification.
dialog.dialog("close");
},
'project_empty': function(error) {
// This is handled via a runtime notification.
dialog.dialog("close");
@@ -1565,6 +1569,10 @@ RED.projects = (function() {
// This is handled via a runtime notification.
dialog.dialog("close");
},
'missing_package_file': function(error) {
// This is handled via a runtime notification.
dialog.dialog("close");
},
'project_empty': function(error) {
// This is handled via a runtime notification.
dialog.dialog("close");
@@ -2053,7 +2061,6 @@ RED.projects = (function() {
console.log(xhr);
console.log(textStatus);
console.log(err);
console.log(stack);
}).always(function() {
var delta = Date.now() - start;
delta = Math.max(0,500-delta);
@@ -2381,6 +2388,9 @@ RED.projects = (function() {
return;
}
RED.projects.settings.show('settings');
setTimeout(function() {
$("#project-settings-tab-settings-file-edit").click();
},200)
},
showProjectDependencies: function() {
RED.projects.settings.show('deps');

View File

@@ -82,9 +82,18 @@ RED.search = (function() {
function search(val) {
searchResults.editableList('empty');
var typeFilter;
var m = /(?:^| )type:([^ ]+)/.exec(val);
if (m) {
val = val.replace(/(?:^| )type:[^ ]+/,"");
typeFilter = m[1];
}
val = val.trim();
selected = -1;
results = [];
if (val.length > 0) {
if (val.length > 0 || typeFilter) {
val = val.toLowerCase();
var i;
var j;
@@ -96,8 +105,10 @@ RED.search = (function() {
if (kpos > -1) {
for (j=0;j<index[key].length;j++) {
var node = index[key][j];
nodes[node.node.id] = nodes[node.node.id] = node;
nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos);
if (!typeFilter || node.node.type === typeFilter) {
nodes[node.node.id] = nodes[node.node.id] = node;
nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos);
}
}
}
}

View File

@@ -16,34 +16,36 @@
RED.subflow = (function() {
var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow">'+
'<div class="form-row"><label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="node-input-name"></div>'+
'<div class="form-row" style="margin-bottom: 0px;"><label style="width: auto;" data-i18n="[append]editor:editor-tab.env"><i class="fa fa-th-list"></i> </label></div>'+
'<div class="form-row node-input-env-container-row"><ol id="node-input-env-container"></ol></div>'+
'</script>';
var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow"><div class="form-row"><label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="node-input-name"></div></script>';
var _subflowTemplateEditTemplate = '<script type="text/x-red" data-template-name="subflow-template">'+
'<div class="form-row"><i class="fa fa-tag"></i> <label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name"></div>'+
'<div class="form-row"><i class="fa fa-folder-o"></i> <label for="subflow-input-category" data-i18n="editor:subflow.category"></label><select style="width: 250px;" id="subflow-input-category"></select><input style="display:none; margin-left: 10px; width:calc(100% - 250px)" type="text" id="subflow-input-custom-category"></div>'+
'<div class="form-row" style="margin-bottom: 0px;"><label style="width: auto;" data-i18n="[append]editor:editor-tab.env"><i class="fa fa-th-list"></i> </label></div>'+
'<div class="form-row node-input-env-container-row"><ol id="node-input-env-container"></ol></div>'+
'<div class="form-row form-tips" id="subflow-dialog-user-count"></div>'+
'</script>';
function getSubflow() {
return RED.nodes.subflow(RED.workspaces.active());
}
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) {
port = subflow.out[i];
} else {
port = subflow.in[i-subflow.out.length];
}
var ports = [].concat(subflow.out).concat(subflow.in);
if (subflow.status) {
ports.push(subflow.status);
}
ports.sort(function(A,B) {
return A.x-B.x;
});
for (var i=0; i<ports.length; i++) {
var port = ports[i];
if (port.x == pos.x && port.y == pos.y) {
pos.x += 55;
i=0;
}
}
return pos;
@@ -87,6 +89,7 @@ RED.subflow = (function() {
RED.view.redraw();
$("#workspace-subflow-input-add").addClass("active");
$("#workspace-subflow-input-remove").removeClass("active");
RED.palette.refresh();
}
function removeSubflowInput() {
@@ -108,6 +111,7 @@ RED.subflow = (function() {
$("#workspace-subflow-input-add").removeClass("active");
$("#workspace-subflow-input-remove").addClass("active");
activeSubflow.changed = true;
RED.palette.refresh();
return {subflowInputs: [ removedInput ], links:removedInputLinks};
}
@@ -148,6 +152,7 @@ RED.subflow = (function() {
RED.nodes.dirty(true);
RED.view.redraw();
$("#workspace-subflow-output .spinner-value").text(subflow.out.length);
RED.palette.refresh();
}
function removeSubflowOutput(removedSubflowOutputs) {
@@ -187,10 +192,65 @@ RED.subflow = (function() {
}
}
activeSubflow.changed = true;
RED.palette.refresh();
return {subflowOutputs: removedSubflowOutputs, links: removedLinks}
}
function addSubflowStatus() {
var subflow = RED.nodes.subflow(RED.workspaces.active());
if (subflow.status) {
return;
}
var position = findAvailableSubflowIOPosition(subflow,false);
var statusNode = {
type:"subflow",
direction:"status",
z:subflow.id,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
subflow.status = statusNode;
subflow.dirty = true;
var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
var result = refresh(true);
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: { status: true }
};
RED.history.push(historyEvent);
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw();
$("#workspace-subflow-status").prop("checked",!!subflow.status);
$("#workspace-subflow-status").parent().parent().toggleClass("active",!!subflow.status);
}
function removeSubflowStatus() {
var subflow = RED.nodes.subflow(RED.workspaces.active());
if (!subflow.status) {
return;
}
var subflowRemovedLinks = [];
RED.nodes.eachLink(function(l) {
if (l.target.type == "subflow" && l.target.z == subflow.id && l.target.direction == "status") {
subflowRemovedLinks.push(l);
}
});
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
delete subflow.status;
$("#workspace-subflow-status").prop("checked",!!subflow.status);
$("#workspace-subflow-status").parent().parent().toggleClass("active",!!subflow.status);
return { links: subflowRemovedLinks }
}
function refresh(markChange) {
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
refreshToolbar(activeSubflow);
@@ -219,12 +279,17 @@ RED.subflow = (function() {
}
}
}
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").text(activeSubflow.out.length);
$("#workspace-subflow-status").prop("checked",!!activeSubflow.status);
$("#workspace-subflow-status").parent().parent().toggleClass("active",!!activeSubflow.status);
}
}
@@ -232,22 +297,32 @@ RED.subflow = (function() {
var toolbar = $("#workspace-toolbar");
toolbar.empty();
// Edit properties
$('<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowProperties"><i class="fa fa-pencil"></i> </a>').appendTo(toolbar);
// Inputs
$('<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);
// Outputs
$('<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);
// Status
$('<span class="button-group"><span class="button" style="padding:0"><label for="workspace-subflow-status"><input id="workspace-subflow-status" type="checkbox"> <span data-i18n="subflow.status"></span></label></span></span>').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);
// Delete
$('<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>').appendTo(toolbar);
toolbar.i18n();
@@ -274,6 +349,7 @@ RED.subflow = (function() {
RED.view.redraw(true);
}
});
$("#workspace-subflow-output-add").click(function(event) {
event.preventDefault();
addSubflowOutput();
@@ -283,6 +359,7 @@ RED.subflow = (function() {
event.preventDefault();
addSubflowInput();
});
$("#workspace-subflow-input-remove").click(function(event) {
event.preventDefault();
var wasDirty = RED.nodes.dirty();
@@ -307,6 +384,33 @@ RED.subflow = (function() {
}
});
$("#workspace-subflow-status").change(function(evt) {
if (this.checked) {
addSubflowStatus();
} else {
var currentStatus = activeSubflow.status;
var wasChanged = activeSubflow.changed;
var result = removeSubflowStatus();
if (result) {
activeSubflow.changed = true;
var wasDirty = RED.nodes.dirty();
RED.history.push({
t:'delete',
links:result.links,
changed: wasChanged,
dirty:wasDirty,
subflow: {
id: activeSubflow.id,
status: currentStatus
}
});
RED.view.select();
RED.nodes.dirty(true);
RED.view.redraw();
}
}
})
$("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
event.preventDefault();
@@ -328,6 +432,7 @@ RED.subflow = (function() {
$("#chart").css({"margin-top": "40px"});
$("#workspace-toolbar").show();
}
function hideWorkspaceToolbar() {
$("#workspace-toolbar").hide().empty();
$("#chart").css({"margin-top": "0"});
@@ -373,6 +478,7 @@ RED.subflow = (function() {
subflows: [activeSubflow]
}
}
function init() {
RED.events.on("workspace:change",function(event) {
var activeSubflow = RED.nodes.subflow(event.workspace);
@@ -436,6 +542,7 @@ RED.subflow = (function() {
}
return x;
}
function convertToSubflow() {
var selection = RED.view.selection();
if (!selection.nodes) {
@@ -451,7 +558,6 @@ RED.subflow = (function() {
var candidateOutputs = [];
var candidateInputNodes = {};
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
selection.nodes[0].x,
@@ -467,8 +573,8 @@ RED.subflow = (function() {
Math.max(boundingBox[3],n.y)
]
}
var offsetX = snapToGrid(boundingBox[0] - 180);
var offsetY = snapToGrid(boundingBox[1] - 60);
var offsetX = snapToGrid(boundingBox[0] - 200);
var offsetY = snapToGrid(boundingBox[1] - 80);
var center = [
@@ -540,7 +646,7 @@ RED.subflow = (function() {
}}),
out: candidateOutputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
direction:"out",
x:snapToGrid(v.source.x+(v.source.w/2)+80 - offsetX),
y:snapToGrid(v.source.y - offsetY),
z:subflowId,
@@ -643,8 +749,6 @@ RED.subflow = (function() {
RED.view.redraw(true);
}
return {
init: init,
createSubflow: createSubflow,
@@ -652,6 +756,7 @@ RED.subflow = (function() {
removeSubflow: removeSubflow,
refresh: refresh,
removeInput: removeSubflowInput,
removeOutput: removeSubflowOutput
removeOutput: removeSubflowOutput,
removeStatus: removeSubflowStatus
}
})();

View File

@@ -294,7 +294,7 @@ RED.sidebar.info = (function() {
if (node.type !== 'tab') {
if (m) {
$('<tr class="blank"><th colspan="2">'+RED._("sidebar.info.subflow")+'</th></tr>').appendTo(tableBody);
$('<tr class="node-info-subflow-row"><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+subflowNode.name+'</span></td></tr>').appendTo(tableBody);
$('<tr class="node-info-subflow-row"><td>'+RED._("common.label.name")+'</td><td><span class="bidiAware" dir=\"'+RED.text.bidi.resolveBaseTextDir(subflowNode.name)+'">'+RED.utils.sanitize(subflowNode.name)+'</span></td></tr>').appendTo(tableBody);
}
}
}

View File

@@ -192,6 +192,14 @@ RED.utils = (function() {
format = 'hex'
}
} else if (format === 'dateMS' || format == 'dateS') {
if ((obj.toString().length===13) && (obj<=2147483647000)) {
format = 'dateML';
} else if ((obj.toString().length===10) && (obj<=2147483647)) {
format = 'dateL';
} else {
format = 'hex'
}
} else if (format === 'dateML' || format == 'dateL') {
format = 'hex';
} else {
format = 'dec';
@@ -210,6 +218,12 @@ RED.utils = (function() {
element.text((new Date(obj)).toISOString());
} else if (format === 'dateS') {
element.text((new Date(obj*1000)).toISOString());
} else if (format === 'dateML') {
var dd = new Date(obj);
element.text(dd.toLocaleString() + " [UTC" + ( dd.getTimezoneOffset()/-60 <=0?"":"+" ) + dd.getTimezoneOffset()/-60 +"]");
} else if (format === 'dateL') {
var ddl = new Date(obj*1000);
element.text(ddl.toLocaleString() + " [UTC" + ( ddl.getTimezoneOffset()/-60 <=0?"":"+" ) + ddl.getTimezoneOffset()/-60 +"]");
} else if (format === 'hex') {
element.text("0x"+(obj).toString(16));
}
@@ -953,6 +967,7 @@ RED.utils = (function() {
addSpinnerOverlay: addSpinnerOverlay,
decodeObject: decodeObject,
parseContextKey: parseContextKey,
createIconElement: createIconElement
createIconElement: createIconElement,
sanitize: sanitize
}
})();

104
packages/node_modules/@node-red/editor-client/src/js/ui/view.js vendored Normal file → Executable file
View File

@@ -1261,6 +1261,13 @@ RED.view = (function() {
moving_set.push({n:n});
}
});
if (activeSubflow.status) {
activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2);
if (activeSubflow.status.selected) {
activeSubflow.status.dirty = true;
moving_set.push({n:activeSubflow.status});
}
}
}
updateSelection();
lasso.remove();
@@ -1367,6 +1374,13 @@ RED.view = (function() {
moving_set.push({n:n});
}
});
if (activeSubflow.status) {
if (!activeSubflow.status.selected) {
activeSubflow.status.selected = true;
activeSubflow.status.dirty = true;
moving_set.push({n:activeSubflow.status});
}
}
}
selected_link = null;
@@ -1552,6 +1566,7 @@ RED.view = (function() {
var removedLinks = [];
var removedSubflowOutputs = [];
var removedSubflowInputs = [];
var removedSubflowStatus = undefined;
var subflowInstances = [];
var startDirty = RED.nodes.dirty();
@@ -1573,6 +1588,8 @@ RED.view = (function() {
removedSubflowOutputs.push(node);
} else if (node.direction === "in") {
removedSubflowInputs.push(node);
} else if (node.direction === "status") {
removedSubflowStatus = node;
}
node.dirty = true;
}
@@ -1590,12 +1607,19 @@ RED.view = (function() {
removedLinks = removedLinks.concat(result.links);
}
}
if (removedSubflowStatus) {
result = RED.subflow.removeStatus();
if (result) {
removedLinks = removedLinks.concat(result.links);
}
}
var instances = RED.subflow.refresh(true);
if (instances) {
subflowInstances = instances.instances;
}
moving_set = [];
if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) {
if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus) {
RED.nodes.dirty(true);
}
}
@@ -1651,10 +1675,14 @@ RED.view = (function() {
subflowOutputs:removedSubflowOutputs,
subflowInputs:removedSubflowInputs,
subflow: {
id: activeSubflow?activeSubflow.id:undefined,
instances: subflowInstances
},
dirty:startDirty
};
if (removedSubflowStatus) {
historyEvent.subflow.status = removedSubflowStatus;
}
}
RED.history.push(historyEvent);
@@ -2420,6 +2448,49 @@ RED.view = (function() {
inGroup.append("svg:text").attr("class","port_label").attr("x",18).attr("y",20).style("font-size","10px").text("input");
var subflowStatus = nodeLayer.selectAll(".subflowstatus").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;});
subflowStatus.exit().remove();
var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","node subflowstatus").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
statusGroup.each(function(d,i) {
d.w=40;
d.h=40;
});
statusGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
.on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown)
.on("touchstart",function(d) {
var obj = d3.select(this);
var touch0 = d3.event.touches.item(0);
var pos = [touch0.pageX,touch0.pageY];
startTouchCenter = [touch0.pageX,touch0.pageY];
startTouchDistance = 0;
touchStartTime = setTimeout(function() {
showTouchMenu(obj,pos);
},touchLongPressTimeout);
nodeMouseDown.call(this,d)
})
.on("touchend", function(d) {
clearTimeout(touchStartTime);
touchStartTime = null;
if (RED.touch.radialMenu.active()) {
d3.event.stopPropagation();
return;
}
nodeMouseUp.call(this,d);
});
statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
.on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
.on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
.on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
.on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);} )
.on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
.on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
statusGroup.append("svg:text").attr("class","port_label").attr("x",22).attr("y",20).style("font-size","10px").text("status");
subflowOutputs.each(function(d,i) {
if (d.dirty) {
var output = d3.select(this);
@@ -2439,9 +2510,22 @@ RED.view = (function() {
d.dirty = false;
}
});
subflowStatus.each(function(d,i) {
if (d.dirty) {
var output = d3.select(this);
output.selectAll(".subflowport").classed("node_selected",function(d) { return d.selected; })
output.selectAll(".port_index").text(function(d){ return d.i+1});
output.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
dirtyNodes[d.id] = d;
d.dirty = false;
}
});
} else {
nodeLayer.selectAll(".subflowoutput").remove();
nodeLayer.selectAll(".subflowinput").remove();
nodeLayer.selectAll(".subflowstatus").remove();
}
var node = nodeLayer.selectAll(".nodegroup").data(activeNodes,function(d){return d.id});
@@ -2672,7 +2756,7 @@ RED.view = (function() {
.attr("rx",2).attr("ry",2).attr("stroke-width","3");
var statusLabel = status.append("svg:text")
.attr("class","node_status_label")
.attr("x",20).attr("y",9);
.attr("x",20).attr("y",10);
//node.append("circle").attr({"class":"centerDot","cx":0,"cy":0,"r":5});
@@ -2717,6 +2801,15 @@ RED.view = (function() {
//thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w});
//thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30});
if ((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) {
thisNode.selectAll(".node_icon_group").classed("node_icon_group_right", true);
thisNode.selectAll(".node_label").classed("node_label_right", true).attr("text-anchor", "end");
} else {
thisNode.selectAll(".node_icon_group").classed("node_icon_group_right", false);
thisNode.selectAll(".node_label").classed("node_label_right", false).attr("text-anchor", "start");
}
thisNode.selectAll(".node_icon_group").attr("transform", function (d) { return "translate(0, 0)"; });
thisNode.selectAll(".node_label").attr("x", function (d) { return 38; });
thisNode.selectAll(".node_icon_group_right").attr("transform", function(d){return "translate("+(d.w-30)+",0)"});
thisNode.selectAll(".node_label_right").attr("x", function(d){return d.w-38});
//thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
@@ -2867,7 +2960,9 @@ RED.view = (function() {
thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;});
thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)});
thisNode.selectAll(".node_icon_shade_border").attr("d", function (d) {
return "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2);
});
thisNode.selectAll(".fa-lg").attr("y",function(d){return (d.h+13)/2;});
thisNode.selectAll(".node_button").attr("opacity",function(d) {
@@ -2924,11 +3019,12 @@ RED.view = (function() {
if (!showStatus || !d.status) {
thisNode.selectAll(".node_status_group").style("display","none");
} else {
thisNode.selectAll(".node_status_group").style("display","inline").attr("transform","translate(3,"+(d.h+3)+")");
var fill = status_colours[d.status.fill]; // Only allow our colours for now
if (d.status.shape == null && fill == null) {
thisNode.selectAll(".node_status").style("display","none");
thisNode.selectAll(".node_status_group").style("display","inline").attr("transform","translate(-14,"+(d.h+3)+")");
} else {
thisNode.selectAll(".node_status_group").style("display","inline").attr("transform","translate(3,"+(d.h+3)+")");
var style;
if (d.status.shape == null || d.status.shape == "dot") {
style = {

View File

@@ -309,7 +309,9 @@ RED.workspaces = (function() {
minimumActiveTabWidth: 150,
scrollable: true,
addButton: "core:add-flow",
addButtonCaption: RED._("workspace.addFlow")
addButtonCaption: RED._("workspace.addFlow"),
searchButton: "core:list-flows",
searchButtonCaption: RED._("workspace.listFlows")
});
workspaceTabCount = 0;
}
@@ -343,6 +345,10 @@ RED.workspaces = (function() {
RED.actions.add("core:edit-flow",editWorkspace);
RED.actions.add("core:remove-flow",removeWorkspace);
RED.actions.add("core:list-flows",function() {
RED.actions.invoke("core:search","type:tab ");
})
hideWorkspace();
}

View File

@@ -60,7 +60,7 @@
.project-settings-tab-pane {
& * .projects-edit-form-sublabel {
margin-right: 50px;
margin-top: -10px;
margin-top: -10px !important;
margin-bottom: 5px;
}
}

View File

@@ -71,6 +71,7 @@ body {
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;
padding-top: 100px;
background: $background-color;
color: #333333;
}
#main-container {
@@ -135,3 +136,16 @@ pre code {
.hide {
display: none;
}
blockquote {
padding: 0 0 0 15px;
margin: 0 0 20px;
border-left: 4px solid #ddd;
p {
font-size: 14px;
font-weight: inherit;
line-height: 1.25;
color: #777;
}
}

View File

@@ -111,6 +111,7 @@
font-weight: bold;
.red-ui-tabs-badge-selected {
display: inline;
background: white;
}
.red-ui-tabs-badge-changed {
display: none;
@@ -133,6 +134,10 @@
&.red-ui-tabs-add.red-ui-tabs-scrollable {
padding-right: 59px;
}
&.red-ui-tabs-add.red-ui-tabs-search.red-ui-tabs-scrollable {
padding-right: 95px;
}
&.red-ui-tabs-collapsible {
li:not(.active) {
display: none;
@@ -285,6 +290,14 @@
right: 38px;
}
.red-ui-tabs.red-ui-tabs-add.red-ui-tabs-search .red-ui-tab-scroll-right {
right: 76px;
}
.red-ui-tabs.red-ui-tabs-add.red-ui-tabs-search .red-ui-tabs-add {
right: 38px;
}
img.red-ui-tab-icon {
margin-left: -8px;
margin-right: 3px;

View File

@@ -33,6 +33,15 @@
transition: right 0.2s ease;
overflow: hidden;
label {
padding: 1px 8px;
margin: 0;
font-size: 12px;
}
input[type="checkbox"] {
margin: 0 3px 0 0 ;
padding: 0;
}
.button {
@include workspace-button;
margin-right: 10px;

View File

@@ -172,17 +172,23 @@
inputs:0,
outputs:1,
outputLabels: function(index) {
var labels = { str:"string", num:"number", bool:"boolean", json:"object", flow:"flow context", global:"global context" };
var lab = labels[this.payloadType] || this.payloadType;
if (lab === "object") {
var lab = this.payloadType;
if (lab === "json") {
try {
lab = typeof JSON.parse(this.payload);
if (lab === "object") {
if (Array.isArray(JSON.parse(this.payload))) { lab = "Array"; }
}
} catch(e) { lab = "Invalid JSON Object"; }
} catch(e) {
return this._("inject.label.invalid"); }
}
return lab; },
var name = "inject.label."+lab;
var label = this._(name);
if (name !== label) {
return label;
}
return lab;
},
label: function() {
var suffix = "";
// if fire once then add small indication

View File

@@ -7,6 +7,10 @@
<option value="target" data-i18n="catch.scope.selected"></options>
</select>
</div>
<div class="form-row node-input-uncaught-row">
<input type="checkbox" id="node-input-uncaught" style="display: inline-block; width: auto; vertical-align: top; margin-left: 30px; margin-right: 5px;">
<label for="node-input-uncaught" style="width: auto" data-i18n="catch.label.uncaught"></label>
</div>
<div class="form-row node-input-target-row" style="display: none;">
<div id="node-input-catch-target-container-div" style="min-height: 100px;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;">
@@ -64,13 +68,20 @@
color:"#e49191",
defaults: {
name: {value:""},
scope: {value:null}
scope: {value:null},
uncaught: {value:false}
},
inputs:0,
outputs:1,
icon: "alert.png",
label: function() {
return this.name||(this.scope?this._("catch.catchNodes",{number:this.scope.length}):this._("catch.catch"));
if (this.name) {
return this.name;
}
if (this.scope) {
return this._("catch.catchNodes",{number:this.scope.length});
}
return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")
},
labelStyle: function() {
return this.name?"node_label_italic":"";
@@ -210,8 +221,10 @@
if (scope === "target") {
createNodeList();
$(".node-input-target-row").show();
$(".node-input-uncaught-row").hide();
} else {
$(".node-input-target-row").hide();
$(".node-input-uncaught-row").show();
}
node.resize();
});
@@ -227,6 +240,7 @@
if (scope === 'all') {
this.scope = null;
} else {
$("#node-input-uncaught").prop("checked",false);
var node = this;
node.scope = [];
$(".node-input-target-node-checkbox").each(function(n) {

View File

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

View File

@@ -54,7 +54,7 @@
flowMap[activeSubflow.id] = {
id: activeSubflow.id,
class: 'palette-header',
label: "Subflow : "+(activeSubflow.name || activeSubflow.id),
label: "Subflow : "+(activeSubflow.name || activeSubflow.id)+(node.z===ws.id ? " *":""),
expanded: true,
children: []
};
@@ -64,8 +64,8 @@
flowMap[ws.id] = {
id: ws.id,
class: 'palette-header',
label: (ws.label || ws.id),
expanded: ws.id === node.z,
label: (ws.label || ws.id)+(node.z===ws.id ? " *":""),
expanded: true,
children: []
}
flows.push(flowMap[ws.id])
@@ -88,7 +88,10 @@
}
});
flows = flows.filter(function(f) { return f.children.length > 0 })
treeList.treeList('data',flows)
treeList.treeList('data',flows);
setTimeout(function() {
treeList.treeList('show',node.z);
},100);
}
function resizeNodeList() {

View File

@@ -66,7 +66,13 @@
},
inputs:1,
outputs:3,
outputLabels: ["stdout","stderr","return code"],
outputLabels: function(i) {
return [
this._("exec.label.stdout"),
this._("exec.label.stderr"),
this._("exec.label.retcode")
][i];
},
icon: "arrow-in.png",
align: "right",
label: function() {

View File

@@ -158,9 +158,8 @@ module.exports = function(RED) {
},
env: {
get: function(envVar) {
// For now, just return the env var. This will eventually
// also return project settings and subflow instance properties
return process.env[envVar]
var flow = node._flow;
return flow.getSetting(envVar);
}
},
setTimeout: function () {

View File

@@ -449,7 +449,7 @@ RED.debug = (function() {
var metaRow = $('<div class="debug-message-meta"></div>').appendTo(msg);
$('<span class="debug-message-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
if (sourceNode) {
$('<a>',{href:"#",class:"debug-message-name"}).html('node: '+(sourceNode.name||sourceNode.id))
$('<a>',{href:"#",class:"debug-message-name"}).text('node: '+(sourceNode.name||sourceNode.id))
.appendTo(metaRow)
.click(function(evt) {
evt.preventDefault();

View File

@@ -34,6 +34,7 @@
<option value="auto" data-i18n="mqtt.output.auto"></option>
<option value="buffer" data-i18n="mqtt.output.buffer"></option>
<option value="utf8" data-i18n="mqtt.output.string"></option>
<option value="json" data-i18n="mqtt.output.json"></option>
<option value="base64" data-i18n="mqtt.output.base64"></option>
</select>
</div>

View File

@@ -399,16 +399,23 @@ module.exports = function(RED) {
if (this.topic) {
node.brokerConn.register(this);
this.brokerConn.subscribe(this.topic,this.qos,function(topic,payload,packet) {
if (node.datatype =="buffer") {
if (node.datatype === "buffer") {
// payload = payload;
} else if (node.datatype =="base64") {
} else if (node.datatype === "base64") {
payload = payload.toString('base64');
} else if (node.datatype =="utf8") {
} else if (node.datatype === "utf8") {
payload = payload.toString('utf8');
} else if (node.datatype === "json") {
if (isUtf8(payload)) {
payload = payload.toString();
try { payload = JSON.parse(payload); }
catch(e) { node.error(RED._("mqtt.errors.invalid-json-parse"),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
}
else { node.error((RED._("mqtt.errors.invalid-json-string")),{payload:payload, topic:topic, qos:packet.qos, retain:packet.retain}); return; }
} else {
if (isUtf8(payload)) { payload = payload.toString(); }
}
var msg = {topic:topic,payload:payload, qos: packet.qos, retain: packet.retain};
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;
}

View File

@@ -25,11 +25,17 @@
<option value="use" data-i18n="httpin.setby"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input id="node-input-url" type="text" placeholder="http://">
</div>
<div class="form-row node-input-paytoqs-row">
<input type="checkbox" id="node-input-paytoqs" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-paytoqs" style="width: auto" data-i18n="httpin.label.paytoqs"></label>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
@@ -43,11 +49,19 @@
<label for="node-input-useAuth" style="width: 70%;"><span data-i18n="httpin.basicauth"></span></label>
<div style="margin-left: 20px" class="node-input-useAuth-row hide">
<div class="form-row">
<label for="node-input-authType"><i class="fa fa-user-secret "></i> <span data-i18n="httpin.label.authType"></span></label>
<select type="text" id="node-input-authType" style="width:70%;">
<option value="basic" data-i18n="httpin.basic"></option>
<option value="digest" data-i18n="httpin.digest"></option>
<option value="bearer" data-i18n="httpin.bearer"></option>
</select>
</div>
<div class="form-row node-input-basic-row">
<label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-input-user">
</div>
<div class="form-row">
<label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<label for="node-input-password"> <i class="fa fa-lock"></i> <span data-i18n="common.label.password" id="node-span-password"></span><span data-i18n="httpin.label.bearerToken" id="node-span-token" style="display:none"></span></label>
<input type="password" id="node-input-password">
</div>
</div>
@@ -84,9 +98,11 @@
name: {value:""},
method:{value:"GET"},
ret: {value:"txt"},
paytoqs: {value: false},
url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} },
tls: {type:"tls-config",required: false},
proxy: {type:"http proxy",required: false}
proxy: {type:"http proxy",required: false},
authType: {value: "basic"}
},
credentials: {
user: {type:"text"},
@@ -95,7 +111,11 @@
inputs:1,
outputs:1,
outputLabels: function(i) {
return ({txt:"UTF8 string", bin:"binary buffer", obj:"parsed JSON object"}[this.ret]);
return ({
txt: this._("httpin.label.utf8String"),
bin: this._("httpin.label.binaryBuffer"),
obj: this._("httpin.label.jsonObject")
}[this.ret]);
},
icon: "white-globe.png",
label: function() {
@@ -108,12 +128,36 @@
$("#node-input-useAuth").change(function() {
if ($(this).is(":checked")) {
$(".node-input-useAuth-row").show();
// Nodes (< version 0.20.x) with credentials but without authentication type, need type 'basic'
if (!$('#node-input-authType').val()) {
$('#node-input-authType').val('basic');
}
} else {
$(".node-input-useAuth-row").hide();
$('#node-input-authType').val('');
$('#node-input-user').val('');
$('#node-input-password').val('');
}
});
$("#node-input-authType").change(function() {
if ($(this).val() == "basic" || $(this).val() == "digest") {
$(".node-input-basic-row").show();
$('#node-span-password').show();
$('#node-span-token').hide();
} else if ($(this).val() == "bearer") {
$(".node-input-basic-row").hide();
$('#node-span-password').hide();
$('#node-span-token').show();
$('#node-input-user').val('');
}
});
$("#node-input-method").change(function() {
if ($(this).val() == "GET") {
$(".node-input-paytoqs-row").show();
} else {
$(".node-input-paytoqs-row").hide();
}
});
if (this.credentials.user || this.credentials.has_password) {
$('#node-input-useAuth').prop('checked', true);
} else {

View File

@@ -28,10 +28,12 @@ module.exports = function(RED) {
var nodeUrl = n.url;
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
var nodeMethod = n.method || "GET";
var paytoqs = n.paytoqs;
if (n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
}
this.ret = n.ret || "txt";
this.authType = n.authType || "basic";
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
else { this.reqTimeout = 120000; }
@@ -148,16 +150,9 @@ module.exports = function(RED) {
};
}
if (opts.headers.hasOwnProperty('cookie')) {
var cookies = cookie.parse(opts.headers.cookie);
var cookies = cookie.parse(opts.headers.cookie, {decode:String});
for (var name in cookies) {
if (cookies.hasOwnProperty(name)) {
if (cookies[name] === null) {
// This case clears a cookie for HTTP In/Response nodes.
// Ignore for this node.
} else {
opts.jar.setCookie(name + '=' + cookies[name], url);
}
}
opts.jar.setCookie(cookie.serialize(name, cookies[name], {encode:String}), url);
}
delete opts.headers.cookie;
}
@@ -168,45 +163,92 @@ module.exports = function(RED) {
// This case clears a cookie for HTTP In/Response nodes.
// Ignore for this node.
} else if (typeof msg.cookies[name] === 'object') {
opts.jar.setCookie(name + '=' + msg.cookies[name].value, url);
if(msg.cookies[name].encode === false){
// If the encode option is false, the value is not encoded.
opts.jar.setCookie(cookie.serialize(name, msg.cookies[name].value, {encode: String}), url);
} else {
// The value is encoded by encodeURIComponent().
opts.jar.setCookie(cookie.serialize(name, msg.cookies[name].value), url);
}
} else {
opts.jar.setCookie(name + '=' + msg.cookies[name], url);
opts.jar.setCookie(cookie.serialize(name, msg.cookies[name]), url);
}
}
}
}
if (this.credentials && this.credentials.user) {
opts.auth = {
user: this.credentials.user,
pass: this.credentials.password||""
};
if (this.credentials) {
if (this.authType === "basic") {
if (this.credentials.user) {
opts.auth = {
user: this.credentials.user,
pass: this.credentials.password || ""
};
}
} else if (this.authType === "digest") {
if (this.credentials.user) {
// The first request will be sent without auth information. Based on the 401 response, the library can determine
// which auth type is required by the server. Then the request is resubmitted with the appropriate auth header.
opts.auth = {
user: this.credentials.user,
pass: this.credentials.password || "",
sendImmediately: false
};
}
} else if (this.authType === "bearer") {
opts.auth = {
bearer: this.credentials.password || ""
};
}
}
var payload = null;
if (method !== 'GET' && method !== 'HEAD' && typeof msg.payload !== "undefined") {
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
payload = msg.payload;
} else if (typeof msg.payload == "number") {
payload = msg.payload+"";
if (opts.headers['content-type'] == 'multipart/form-data' && typeof payload === "object") {
opts.formData = msg.payload;
} else {
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
payload = querystring.stringify(msg.payload);
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
payload = msg.payload;
} else if (typeof msg.payload == "number") {
payload = msg.payload+"";
} else {
payload = JSON.stringify(msg.payload);
if (opts.headers['content-type'] == null) {
opts.headers[ctSet] = "application/json";
if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
payload = querystring.stringify(msg.payload);
} else {
payload = JSON.stringify(msg.payload);
if (opts.headers['content-type'] == null) {
opts.headers[ctSet] = "application/json";
}
}
}
}
if (opts.headers['content-length'] == null) {
if (Buffer.isBuffer(payload)) {
opts.headers[clSet] = payload.length;
} else {
opts.headers[clSet] = Buffer.byteLength(payload);
if (opts.headers['content-length'] == null) {
if (Buffer.isBuffer(payload)) {
opts.headers[clSet] = payload.length;
} else {
opts.headers[clSet] = Buffer.byteLength(payload);
}
}
opts.body = payload;
}
opts.body = payload;
}
if (method == 'GET' && typeof msg.payload !== "undefined" && paytoqs) {
if (typeof msg.payload === "object") {
try {
if (opts.url.indexOf("?") !== -1) {
opts.url += (opts.url.endsWith("?")?"":"&") + querystring.stringify(msg.payload);
} else {
opts.url += "?" + querystring.stringify(msg.payload);
}
} catch(err) {
node.error(RED._("httpin.errors.invalid-payload"),msg);
return;
}
} else {
node.error(RED._("httpin.errors.invalid-payload"),msg);
return;
}
}
// revert to user supplied Capitalisation if needed.
if (opts.headers.hasOwnProperty('content-type') && (ctSet !== 'content-type')) {
opts.headers[ctSet] = opts.headers['content-type'];

View File

@@ -72,19 +72,19 @@ module.exports = function(RED) {
var id = (1+Math.random()*4294967295).toString(16);
if (node.isServer) {
node._clients[id] = socket;
node.emit('opened',Object.keys(node._clients).length);
node.emit('opened',{count:Object.keys(node._clients).length,id:id});
}
socket.on('open',function() {
if (!node.isServer) {
node.emit('opened','');
node.emit('opened',{count:'',id:id});
}
});
socket.on('close',function() {
if (node.isServer) {
delete node._clients[id];
node.emit('closed',Object.keys(node._clients).length);
node.emit('closed',{count:Object.keys(node._clients).length,id:id});
} else {
node.emit('closed');
node.emit('closed',{count:'',id:id});
}
if (!node.closing && !node.isServer) {
clearTimeout(node.tout);
@@ -95,7 +95,7 @@ module.exports = function(RED) {
node.handleEvent(id,socket,'message',data,flags);
});
socket.on('error', function(err) {
node.emit('erro');
node.emit('erro',{err:err,id:id});
if (!node.closing && !node.isServer) {
clearTimeout(node.tout);
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
@@ -230,14 +230,30 @@ module.exports = function(RED) {
if (this.serverConfig) {
this.serverConfig.registerInputNode(this);
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"common.status.error"}); });
this.serverConfig.on('closed', function(n) {
if (n > 0) {
node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})});
this.serverConfig.on('opened', function(event) {
node.status({
fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
event:"connect",
_session: {type:"websocket",id:event.id}
});
});
this.serverConfig.on('erro', function(event) {
node.status({
fill:"red",shape:"ring",text:"common.status.error",
event:"error",
_session: {type:"websocket",id:event.id}
});
});
this.serverConfig.on('closed', function(event) {
var status;
if (event.count > 0) {
status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
} else {
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
}
status.event = "disconnect";
status._session = {type:"websocket",id:event.id}
node.status(status);
});
} else {
this.error(RED._("websocket.errors.missing-conf"));
@@ -261,11 +277,30 @@ module.exports = function(RED) {
}
else {
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"common.status.error"}); });
this.serverConfig.on('closed', function(n) {
if (n > 0) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); }
else { node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); }
this.serverConfig.on('opened', function(event) {
node.status({
fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count}),
event:"connect",
_session: {type:"websocket",id:event.id}
});
});
this.serverConfig.on('erro', function(event) {
node.status({
fill:"red",shape:"ring",text:"common.status.error",
event:"error",
_session: {type:"websocket",id:event.id}
})
});
this.serverConfig.on('closed', function(event) {
var status;
if (event.count > 0) {
status = {fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:event.count})};
} else {
status = {fill:"red",shape:"ring",text:"common.status.disconnected"};
}
status.event = "disconnect";
status._session = {type:"websocket",id:event.id}
node.status(status);
});
}
this.on("input", function(msg) {

View File

@@ -158,7 +158,13 @@ module.exports = function(RED) {
var fromp;
connectionPool[id] = socket;
count++;
node.status({text:RED._("tcpin.status.connections",{count:count})});
node.status({
text:RED._("tcpin.status.connections",{count:count}),
event:"connect",
ip:socket.remoteAddress,
port:socket.remotePort,
_session: {type:"tcp",id:id}
});
var buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
socket.on('data', function (data) {
@@ -209,7 +215,14 @@ module.exports = function(RED) {
socket.on('close', function() {
delete connectionPool[id];
count--;
node.status({text:RED._("tcpin.status.connections",{count:count})});
node.status({
text:RED._("tcpin.status.connections",{count:count}),
event:"disconnect",
ip:socket.remoteAddress,
port:socket.remotePort,
_session: {type:"tcp",id:id}
});
});
socket.on('error',function(err) {
node.log(err);

View File

@@ -94,7 +94,7 @@ module.exports = function(RED) {
this.error(RED._("change.errors.invalid-expr",{error:e.message}));
}
} else if (rule.tot === 'env') {
rule.to = RED.util.evaluateNodeProperty(rule.to,'env');
rule.to = RED.util.evaluateNodeProperty(rule.to,'env',node);
}
}

View File

@@ -631,8 +631,7 @@ module.exports = function(RED) {
}
if (payloadType === 'object') {
group.payload[propertyKey] = property;
group.currentCount = Object.keys(group.payload).length;
//msg.topic = node.topic || msg.topic;
group.currentCount = (group.currentCount || 0) + 1;
} else if (payloadType === 'merged') {
if (Array.isArray(property) || typeof property !== 'object') {
if (!msg.hasOwnProperty("complete")) {

View File

@@ -82,7 +82,7 @@ module.exports = function(RED) {
}
}
}
else if (typeof value === "object") {
else if ((typeof value === "object") || (typeof value === "boolean") || (typeof value === "number")) {
if (node.action === "" || node.action === "str") {
if (!Buffer.isBuffer(value)) {
try {

View File

@@ -22,6 +22,11 @@
<input type="checkbox" id="node-input-createDir" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-createDir" style="width: 70%;"><span data-i18n="file.label.createdir"></span></label>
</div>
<div class="form-row">
<label for="node-input-encoding"><i class="fa fa-flag"></i> <span data-i18n="file.label.encoding"></span></label>
<select type="text" id="node-input-encoding" style="width: 250px;">
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -48,6 +53,11 @@
<input type="checkbox" id="node-input-sendError" style="width:auto">
<label style="width:auto; margin-bottom:0; vertical-align: middle;" for="node-input-sendError" data-i18n="file.label.sendError"></label>
</div>
<div class="form-row" id="encoding-spec">
<label for="node-input-encoding"><i class="fa fa-flag"></i> <span data-i18n="file.label.encoding"></span></label>
<select type="text" id="node-input-encoding" style="width: 250px;">
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -56,6 +66,132 @@
</script>
<script type="text/javascript">
(function(){
var encodings = [
[ "file.encoding.native",
"utf8",
"ucs2",
"utf-16le",
"ascii",
"binary",
"base64",
"hex"
],
[ "file.encoding.unicode",
"utf-16be",
],
[ "file.encoding.japanese",
"Shift_JIS",
"Windows-31j",
"Windows932",
"EUC-JP"
],
[ "file.encoding.chinese",
"GB2312",
"GBK",
"GB18030",
"Windows936",
"EUC-CN"
],
[ "file.encoding.korean",
"KS_C_5601",
"Windows949",
"EUC-KR"
],
[ "file.encoding.taiwan",
"Big5",
"Big5-HKSCS",
"Windows950"
],
[ "file.encoding.windows",
"cp874",
"cp1250",
"cp1251",
"cp1252",
"cp1253",
"cp1254",
"cp1255",
"cp1256",
"cp1257",
"cp1258"
],
[ "file.encoding.iso",
"ISO-8859-1",
"ISO-8859-2",
"ISO-8859-3",
"ISO-8859-4",
"ISO-8859-5",
"ISO-8859-6",
"ISO-8859-7",
"ISO-8859-8",
"ISO-8859-9",
"ISO-8859-10",
"ISO-8859-11",
"ISO-8859-12",
"ISO-8859-13",
"ISO-8859-14",
"ISO-8859-15",
"ISO-8859-16"
],
[ "file.encoding.ibm",
"cp437",
"cp737",
"cp775",
"cp808",
"cp850",
"cp852",
"cp855",
"cp856",
"cp857",
"cp858",
"cp860",
"cp861",
"cp866",
"cp869",
"cp922",
"cp1046",
"cp1124",
"cp1125",
"cp1129",
"cp1133",
"cp1161",
"cp1162",
"cp1163"
],
[ "file.encoding.mac",
"maccroatian",
"maccyrillic",
"macgreek",
"maciceland",
"macroman",
"macromania",
"macthai",
"macturkish",
"macukraine",
"maccenteuro",
"macintosh"
],
[ "file.encoding.koi8",
"koi8-r",
"koi8-u",
"koi8-ru",
"koi8-t"
],
[ "file.encoding.misc",
"armscii8",
"rk1048",
"tcvn",
"georgianacademy",
"georgianps",
"pt154",
"viscii",
"iso646cn",
"iso646jp",
"hproman8",
"tis620"
]
];
RED.nodes.registerType('file',{
category: 'storage-output',
defaults: {
@@ -63,7 +199,8 @@
filename: {value:""},
appendNewline: {value:true},
createDir: {value:false},
overwriteFile: {value:"false"}
overwriteFile: {value:"false"},
encoding: {value:"none"}
},
color:"BurlyWood",
inputs:1,
@@ -80,6 +217,34 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var node = this;
var encSel = $("#node-input-encoding");
var label = node._("file.encoding.none");
$("<option/>", {
value: "none",
label: label
}).text(label).appendTo(encSel);
encodings.forEach(function(item) {
if(Array.isArray(item)) {
var group = $("<optgroup/>", {
label: node._(item[0])
}).appendTo(encSel);
for (var i = 1; i < item.length; i++) {
var enc = item[i];
$("<option/>", {
value: enc,
label: enc
}).text(enc).appendTo(group);
}
}
else {
$("<option/>", {
value: item,
label: item
}).text(item).appendTo(encSel);
}
});
encSel.val(node.encoding);
$("#node-input-overwriteFile").on("change",function() {
if (this.value === "delete") { $(".form-row-file-write-options").hide(); }
else { $(".form-row-file-write-options").show(); }
@@ -94,13 +259,14 @@
filename: {value:""},
format: {value:"utf8"},
chunk: {value:false},
sendError: {value: false}
sendError: {value: false},
encoding: {value: "none"}
},
color:"BurlyWood",
inputs:1,
outputs:1,
outputLabels: function(i) {
return (this.format === "utf8") ? "UTF8 string" : "binary buffer";
return (this._((this.format === "utf8") ? "file.label.utf8String" : "file.label.binaryBuffer"));
},
icon: "file-in.png",
label: function() {
@@ -110,19 +276,47 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var node = this;
var encSel = $("#node-input-encoding");
var label = node._("file.encoding.none");
$("<option/>", {
value: "none",
label: label
}).text(label).appendTo(encSel);
encodings.forEach(function(item) {
if(Array.isArray(item)) {
var group = $("<optgroup/>", {
label: node._(item[0])
}).appendTo(encSel);
for (var i = 1; i < item.length; i++) {
var enc = item[i];
$("<option/>", {
value: enc,
label: enc
}).text(enc).appendTo(group);
}
}
else {
$("<option/>", {
value: item,
label: item
}).text(item).appendTo(encSel);
}
});
encSel.val(node.encoding);
if (this.sendError === undefined) {
$("#node-input-sendError").prop("checked",true);
}
$("#node-input-format").on("change",function() {
if ($("#node-input-format").val() === "utf8") {
$("#buffer-input-type").hide();
$("#line-input-type").show();
var format = $("#node-input-format").val();
if ((format === "utf8") || (format === "lines")) {
$("#encoding-spec").show();
}
else {
$("#buffer-input-type").show();
$("#line-input-type").hide();
$("#encoding-spec").hide();
}
});
}
});
})();
</script>

View File

@@ -19,13 +19,29 @@ module.exports = function(RED) {
var fs = require("fs-extra");
var os = require("os");
var path = require("path");
var iconv = require("iconv-lite")
function encode(data, enc) {
if (enc !== "none") {
return iconv.encode(data, enc);
}
return Buffer.from(data);
}
function decode(data, enc) {
if (enc !== "none") {
return iconv.decode(data, enc);
}
return data.toString();
}
function FileNode(n) {
RED.nodes.createNode(this,n);
this.filename = n.filename;
this.appendNewline = n.appendNewline;
this.overwriteFile = n.overwriteFile.toString();
this.createDir = n.createDir || false;
this.encoding = n.encoding || "none";
var node = this;
node.wstream = null;
node.msgQueue = [];
@@ -75,6 +91,7 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
var buf = encode(data, node.encoding);
if (node.overwriteFile === "true") {
var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream = wstream;
@@ -83,7 +100,7 @@ module.exports = function(RED) {
done();
});
wstream.on("open", function() {
wstream.end(data, function() {
wstream.end(buf, function() {
node.send(msg);
done();
});
@@ -132,13 +149,13 @@ module.exports = function(RED) {
}
if (node.filename) {
// Static filename - write and reuse the stream next time
node.wstream.write(data, function() {
node.wstream.write(buf, function() {
node.send(msg);
done();
});
} else {
// Dynamic filename - write and close the stream
node.wstream.end(data, function() {
node.wstream.end(buf, function() {
node.send(msg);
delete node.wstream;
delete node.wstreamIno;
@@ -221,6 +238,7 @@ module.exports = function(RED) {
this.filename = n.filename;
this.format = n.format;
this.chunk = false;
this.encoding = n.encoding || "none";
if (n.sendError === undefined) {
this.sendError = true;
} else {
@@ -260,7 +278,7 @@ module.exports = function(RED) {
if (node.chunk === true) {
getout = true;
if (node.format === "lines") {
spare += chunk.toString();
spare += decode(chunk, node.encoding);
var bits = spare.split("\n");
for (var i=0; i < bits.length - 1; i++) {
var m = {
@@ -305,7 +323,9 @@ module.exports = function(RED) {
})
.on('end', function() {
if (node.chunk === false) {
if (node.format === "utf8") { msg.payload = lines.toString(); }
if (node.format === "utf8") {
msg.payload = decode(lines, node.encoding);
}
else { msg.payload = lines; }
node.send(msg);
}

View File

@@ -32,7 +32,8 @@
halt. This node can be used to catch those errors and handle them with a
dedicated flow.</p>
<p>By default, the node will catch errors thrown by any node on the same tab. Alternatively
it can be targetted at specific nodes.</p>
it can be targetted at specific nodes, or configured to only catch errors that
have not already been caught by a 'targeted' catch node.</p>
<p>When an error is thrown, all matching catch nodes will receive the message.</p>
<p>If an error is thrown within a subflow, the error will get handled by any
catch nodes within the subflow. If none exists, the error will be propagated

View File

@@ -78,5 +78,18 @@
<p>If <code>msg.payload</code> is an Object, the node will automatically set the content type
of the request to <code>application/json</code> and encode the body as such.</p>
<p>To encode the request as form data, <code>msg.headers["content-type"]</code> should be set to <code>application/x-www-form-urlencoded</code>.</p>
<h4>File Upload</h4>
<p>To perform a file upload, <code>msg.headers["content-type"]</code> should be set to <code>multipart/form-data</code>
and the <code>msg.payload</code> passed to the node must be an object with the following structure:</p>
<pre><code>{
"KEY": {
"value": FILE_CONTENTS,
"options": {
"filename": "FILENAME"
}
}
}</code></pre>
<p>The values of <code>KEY</code>, <code>FILE_CONTENTS</code> and <code>FILENAME</code>
should be set to the appropriate values.</p>
</script>

72
packages/node_modules/@node-red/nodes/locales/en-US/messages.json vendored Normal file → Executable file
View File

@@ -35,7 +35,22 @@
"stopped": "stopped",
"failed": "Inject failed: __error__",
"label": {
"repeat": "Repeat"
"repeat": "Repeat",
"flow": "flow context",
"global": "global context",
"str": "string",
"num": "number",
"bool": "boolean",
"json": "object",
"bin": "buffer",
"date": "timestamp",
"env": "env variable",
"object": "object",
"string": "string",
"boolean": "boolean",
"number": "number",
"Array": "Array",
"invalid": "Invalid JSON Object"
},
"timestamp": "timestamp",
"none": "none",
@@ -72,13 +87,15 @@
"catch": {
"catch": "catch: all",
"catchNodes": "catch: __number__",
"catchUncaught": "catch: uncaught",
"label": {
"source": "Catch errors from",
"node": "node",
"type": "type",
"selectAll": "select all",
"sortByLabel": "sort by label",
"sortByType": "sort by type"
"sortByType": "sort by type",
"uncaught": "Ignore errors handled by other Catch nodes"
},
"scope": {
"all": "all nodes",
@@ -172,7 +189,10 @@
"timeout": "Timeout",
"timeoutplace": "optional",
"return": "Output",
"seconds": "seconds"
"seconds": "seconds",
"stdout": "stdout",
"stderr": "stderr",
"retcode": "return code"
},
"placeholder": {
"extraparams": "extra input parameters"
@@ -222,7 +242,7 @@
"limittopic": "For each msg.topic",
"fairqueue": "Send each topic in turn",
"timedqueue": "Send all topics",
"milisecs": "Miliseconds",
"milisecs": "Milliseconds",
"secs": "Seconds",
"sec": "Second",
"mins": "Minutes",
@@ -353,7 +373,8 @@
"buffer": "a Buffer",
"string": "a String",
"base64": "a Base64 encoded string",
"auto": "auto-detect"
"auto": "auto-detect (string or buffer)",
"json": "a parsed JSON object"
},
"true": "true",
"false": "false",
@@ -362,7 +383,9 @@
"not-defined": "topic not defined",
"missing-config": "missing broker configuration",
"invalid-topic": "Invalid topic specified",
"nonclean-missingclientid": "No client ID set, using clean session"
"nonclean-missingclientid": "No client ID set, using clean session",
"invalid-json-string": "Invalid JSON string",
"invalid-json-parse": "Failed to parse JSON string"
}
},
"httpin": {
@@ -374,12 +397,21 @@
"upload": "Accept file uploads?",
"status": "Status code",
"headers": "Headers",
"other": "other"
"other": "other",
"paytoqs" : "Append msg.payload as query string parameters",
"utf8String": "UTF8 string",
"binaryBuffer": "binary buffer",
"jsonObject": "parsed JSON object",
"authType": "Type",
"bearerToken": "Token"
},
"setby": "- set by msg.method -",
"basicauth": "Use basic authentication",
"basicauth": "Use authentication",
"use-tls": "Enable secure (SSL/TLS) connection",
"tls-config":"TLS Configuration",
"basic": "basic authentication",
"digest": "digest authentication",
"bearer": "bearer authentication",
"use-proxy": "Use proxy",
"proxy-config": "Proxy Configuration",
"use-proxyauth": "Use proxy authentication",
@@ -402,7 +434,8 @@
"deprecated-call":"Deprecated call to __method__",
"invalid-transport":"non-http transport requested",
"timeout-isnan": "Timeout value is not a valid number, ignoring",
"timeout-isnegative": "Timeout value is negative, ignoring"
"timeout-isnegative": "Timeout value is negative, ignoring",
"invalid-payload": "Invalid payload"
},
"status": {
"requesting": "requesting"
@@ -430,7 +463,7 @@
"connected_plural": "connected __count__"
},
"errors": {
"connect-error": "An error occured on the ws connection: ",
"connect-error": "An error occurred on the ws connection: ",
"send-error": "An error occurred while sending: ",
"missing-conf": "Missing server configuration",
"duplicate-path": "Cannot have two WebSocket listeners on the same path: __path__"
@@ -826,7 +859,9 @@
"breaklines": "Break into lines",
"filelabel": "file",
"sendError": "Send message on error (legacy mode)",
"deletelabel": "delete __file__"
"deletelabel": "delete __file__",
"utf8String": "UTF8 string",
"binaryBuffer": "binary buffer"
},
"action": {
"append": "append to file",
@@ -844,6 +879,21 @@
"deletedfile": "deleted file: __file__",
"appendedfile": "appended to file: __file__"
},
"encoding": {
"none": "default",
"native": "Native",
"unicode": "Unicode",
"japanese": "Japanese",
"chinese": "Chinese",
"korean": "Korean",
"taiwan": "Taiwan/Hong Kong",
"windows": "Windows codepages",
"iso": "ISO codepages",
"ibm": "IBM codepages",
"mac": "Mac codepages",
"koi8": "KOI8 codepages",
"misc": "Miscellaneous"
},
"errors": {
"nofilename": "No filename specified",
"invaliddelete": "Warning: Invalid delete. Please use specific delete option in config dialog.",

View File

@@ -32,6 +32,7 @@
<p>It can be configured to overwrite the entire file rather than append. For example,
when writing binary data to a file, such as an image, this option should be used
and the option to append a newline should be disabled.</p>
<p>Encoding of data written to a file can be specified from list of encodings.</p>
<p>Alternatively, this node can be configured to delete the file.</p>
</script>
@@ -63,6 +64,7 @@
split into smaller buffer chunks - the chunk size being operating system dependant, but typically 64k (Linux/Mac) or 41k (Windows).</p>
<p>When split into multiple messages, each message will have a <code>parts</code>
property set, forming a complete message sequence.</p>
<p>Encoding of input data can be specified from list of encodings if output format is string.</p>
<h4>Legacy error handling</h4>
<p>Before Node-RED 0.17, if this node hit an error whilst reading the file, it would
send a message with no <code>msg.payload</code> and <code>msg.error</code> set to the

View File

@@ -29,7 +29,7 @@
</dl>
<h3>詳細</h3>
<p>メッセージの処理中にノードがエラーを送出した場合フロー実行は基本的に停止しますこのノードを使うとエラーをキャッチして対応するフローで処理させることができます</p>
<p>デフォルトでは同じタブの全てのノードが送出したエラーをキャッチします特定のノードをキャッチ対象とすることも可能です</p>
<p>デフォルトでは同じタブの全てのノードが送出したエラーをキャッチします特定のノードをキャッチ対象とする対象catchードで補足されていないエラーのみ補足するように指定することも可能です</p>
<p>エラー発生時にはマッチするすべてのcatchードがメッセージを受け取ります</p>
<p>サブフロー内でエラーが送出された場合まずサブフロー内のcatchードで処理されます対応するノードが存在しない場合にはそのサブフローが配置されたタブにエラーを伝播して処理します</p>
<p>メッセージが<code>error</code><code>error</code><code>_error</code></p>

View File

@@ -46,4 +46,6 @@
<li><code>node.id</code> - ID</li>
<li><code>node.name</code> - </li>
</ul>
<h4>環境変数の利用</h4>
<p>環境変数は<code>env.get("MY_ENV_VAR")</code></p>
</script>

View File

@@ -42,6 +42,8 @@
<p>というメッセージを受信した場合</p>
<pre>こんにちは山田さん今日は月曜日です</pre>
<p>というプロパティが生成されます</p>
<p>フローコンテキストもしくはグローバルコンテキストのプロパティ値を使うこともできますそれぞれ<code>{{flow.名前}}</code><code>{{global.}}</code></p>
<p>フローコンテキストもしくはグローバルコンテキストのプロパティ値を使うこともできますそれぞれ<code>{{flow.名前}}</code><code>{{global.}}</code>(<code>store</code>)<code>{{flow[store].}}</code>
<code>{{global[store].名前}}</code>
</p>
<p><b>: </b>デフォルトでは、<i>mustache</i>形式は置換対象のHTML要素をエスケープしますこれを抑止するには<code>{{{三重}}}</code>使</p>
</script>

View File

@@ -58,7 +58,7 @@
<h3>詳細</h3>
<p><code>statusCode</code><code>headers</code>使</p>
<h4>クッキーの処理</h4>
<p><code>cookies</code>/使<p>
<p><code>cookies</code>/使</p>
<p>以下の例では2つのクッキーを設定しています1つ目は<code>name</code><code>nick</code>2<code>session</code><code>1234</code>15</p>
<pre>
msg.cookies = {

View File

@@ -55,10 +55,21 @@
<h4>複数のHTTPリクエストードの利用</h4>
<p>同一フローで本ノードを複数利用するためには<code>msg.headers</code><code>msg.headers</code>2<code>msg.headers</code><code>{}</code>
<h4>クッキーの扱い</h4>
<p>ノードに<code>cookies</code>/<code>value</code><p>
<p>ノードに<code>cookies</code>/<code>value</code></p>
<p>リクエストに対して返却されたクッキーは<code>responseCookies</code></p>
<h4>要素タイプの扱い</h4>
<p><code>msg.payload</code><code>msg.payload</code>JSON</p>
<p>リクエストをフォームデータにエンコードするには<code>msg.headers["content-type"]</code><code>application/x-www-form-urlencoded</code></p>
<h4>ファイルのアップロード</h4>
<p><code>msg.headers["content-type"]</code><code>multipart/form-data</code><code>msg.payload</code>:</p>
<pre><code>{
"KEY": {
"value": FILE_CONTENTS,
"options": {
"filename": "FILENAME"
}
}
}</code></pre>
<p><code>KEY</code>, <code>FILE_CONTENTS</code> <code>FILENAME</code></p>
</script>

View File

@@ -15,7 +15,7 @@
-->
<script type="text/x-red" data-help-name="tcp in">
<p>TCPからの入力を行いますリモートTCPポートに接続するか外部からのコネクションを受け付けます</p>
<p>TCPからの入力を行いますリモートTCPポートに接続するか外部からのコネクションを受け付けます</p>
<p><b>: </b>1024rootadministrator</p>
</script>
@@ -30,6 +30,6 @@
<script type="text/x-red" data-help-name="tcp request">
<p>シンプルなTCPリクエストード<code>msg.payload</code>TCP</p>
<p>サーバに接続"リクエスト"送信"レスポンス"受信を行います固定長の文字数指定文字へのマッチ最初のリプライの到着から指定した時間待つデータの到着待ちデータ送信を行いリプライを待たず接続を即時解除などから動作を選択できます</p>
<p>レスポンスはバッファ形式で<code>msg.payload</code>toString()使</p>
<p>レスポンスはバッファ形式で<code>msg.payload</code>.toString()使</p>
<p>TCPホストのポート番号設定を空にした場合<code>msg.host</code><code>msg.port</code></p>
</script>

View File

@@ -20,7 +20,7 @@
<p>受信したメッセージに対し指定されたルールを順に評価しマッチしたルールに対応する出力ポートにメッセージを送出します</p>
<p>最初にルールがマッチしたところで評価を止めることも可能です</p>
<p>評価ルールにはメッセージプロパティフローコンテキスト/グローバルコンテキストのプロパティJSONata式の評価結果が利用できます</p>
<h3>ルール</h3>
<h4>ルール</h4>
<p>振り分けルールは以下の4つに分類されます</p>
<ol>
<li><b>(value)</b> - </li>
@@ -29,11 +29,11 @@
<li><b>その他</b> - </li>
</ol>
<h3>注釈</h3>
<h4>注釈</h4>
<p><code>is true/false</code><code>is null</code></p>
<p><code>is empty</code><code>null</code><code>undefined</code></p>
<h3>メッセージ列の扱い</h3>
<h4>メッセージ列の扱い</h4>
<p>switchードは入力メッセージの列に関する情報を保持する<code>msg.parts</code></p>
<p><b>メッセージ列の補正</b>switch<b>settings.js</b><code>nodeMessageBufferMaxLength</code></p>
</script>

View File

@@ -29,5 +29,5 @@
<dt>移動</dt>
<dd>プロパティの移動または名前の変更を行います</dd>
</dl>
<p>expressionには<a href="http://jsonata.org/" target="_new">JSONata</a></p>
<p>JSONata式には<a href="http://jsonata.org/" target="_new">JSONata</a></p>
</script>

View File

@@ -19,7 +19,7 @@
<h3>入力</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">数値</span></dt>
<dd>数値以外の場合は数値に変換します変換不能な場合はラーとなります</dd>
<dd>数値を指定します数値以外を指定した場合は数値に変換します変換不能な場合はラーとなります</dd>
</dl>
<h3>出力</h3>
<dl class="message-properties">

View File

@@ -97,7 +97,8 @@
<p>出力メッセージのその他のプロパティはメッセージを送信する直前のメッセージをコピーします</p>
<p><i>合計値</i></p>
<p><i></i></p>
<p><code>msg.complete</code></p>
<p><code>msg.complete</code></p>
<p><code>msg.reset</code></p>
<h4>列の集約モード</h4>
<p>列の集約モードを選択するとメッセージ列を構成する各々のメッセージに対して式を適用し集約した値を用いて一つのメッセージを構成します</p>

93
packages/node_modules/@node-red/nodes/locales/ja/messages.json vendored Normal file → Executable file
View File

@@ -35,9 +35,24 @@
"stopped": "stopped",
"failed": "inject失敗: __error__",
"label": {
"repeat": "繰り返し"
"repeat": "繰り返し",
"flow": "フローコンテクスト",
"global": "グローバルコンテクスト",
"str": "文字列",
"num": "数値",
"bool": "真偽値",
"json": "オブジェクト",
"bin": "バッファ",
"date": "タイムスタンプ",
"env": "環境変数",
"object": "オブジェクト",
"string": "文字列",
"boolean": "真偽値",
"number": "数値",
"Array": "配列",
"invalid": "不正なJSON"
},
"timestamp": "timestamp",
"timestamp": "タイムスタンプ",
"none": "なし",
"interval": "指定した時間間隔",
"interval-time": "指定した時間間隔、日時",
@@ -62,7 +77,7 @@
"on": "曜日",
"onstart": "Node-RED起動の",
"onceDelay": "秒後、以下を行う",
"tip": "<b>注釈:</b> 「指定した時間間隔、日時」と「指定した日時」はcronを使用します。詳細はードの「情報」を確認してください。",
"tip": "<b>注釈:</b> 「指定した時間間隔、日時」と「指定した日時」はcronを使用します。<br/>「時間間隔」には596時間より小さな値を指定します。<br/>詳細はノードの「情報」を確認してください。",
"success": "inject処理を実行しました: __label__",
"errors": {
"failed": "inject処理が失敗しました。詳細はログを確認してください。",
@@ -70,15 +85,17 @@
}
},
"catch": {
"catch": "catch all",
"catchNodes": "catch (__number__)",
"catch": "catch: 全て",
"catchNodes": "catch: __number__",
"catchUncaught": "catch: 未補足",
"label": {
"source": "エラー取得元",
"node": "ノード",
"type": "型",
"selectAll": "全て選択",
"sortByLabel": "ノード名で並べ替え",
"sortByType": "型で並べ替え"
"sortByType": "型で並べ替え",
"uncaught": "Catchードで処理済みのエラーを無視"
},
"scope": {
"all": "全てのノード",
@@ -86,8 +103,8 @@
}
},
"status": {
"status": "status (all)",
"statusNodes": "status (__number__)",
"status": "status: 全て",
"statusNodes": "status: __number__",
"label": {
"source": "状態取得元",
"node": "ノード",
@@ -172,7 +189,10 @@
"timeout": "タイムアウト",
"timeoutplace": "任意",
"return": "出力",
"seconds": "秒"
"seconds": "秒",
"stdout": "標準出力",
"stderr": "標準エラー出力",
"retcode": "返却コード"
},
"placeholder": {
"extraparams": "追加引数"
@@ -263,7 +283,7 @@
},
"error": {
"buffer": "バッファ上限の1000メッセージを超えました",
"buffer1": "バッファ上限の1000メッセージを超えました"
"buffer1": "バッファ上限の10000メッセージを超えました"
}
},
"trigger": {
@@ -353,7 +373,8 @@
"buffer": "バイナリバッファ",
"string": "文字列",
"base64": "Base64文字列",
"auto": "自動判定"
"auto": "自動判定(文字列もしくはバイナリバッファ)",
"json": "JSONオブジェクト"
},
"true": "する",
"false": "しない",
@@ -362,7 +383,9 @@
"not-defined": "トピックが設定されていません",
"missing-config": "ブローカが設定されていません",
"invalid-topic": "不正なトピックが設定されています",
"nonclean-missingclientid": "「セッションの初期化」使用時に、クライアントIDが設定されていません"
"nonclean-missingclientid": "「セッションの初期化」使用時に、クライアントIDが設定されていません",
"invalid-json-string": "不正なJSON文字列",
"invalid-json-parse": "JSON文字列のパースに失敗しました"
}
},
"httpin": {
@@ -374,19 +397,28 @@
"upload": "ファイルのアップロード",
"status": "状態コード",
"headers": "ヘッダ",
"other": "その他"
"other": "その他",
"paytoqs" : "msg.payloadをクエリパラメータに追加",
"utf8String": "UTF8文字列",
"binaryBuffer": "バイナリバッファ",
"jsonObject": "JSONオブジェクト",
"authType": "種別",
"bearerToken": "トークン"
},
"setby": "- msg.methodに定義 -",
"basicauth": "ベーシック認証を使用",
"basicauth": "認証を使用",
"use-tls": "SSL/TLS接続を有効化",
"tls-config": "TLS設定",
"basic": "Basic認証",
"digest": "Digest認証",
"bearer": "Bearer認証",
"use-proxy": "プロキシを使用",
"proxy-config": "プロキシ設定",
"use-proxyauth": "プロキシ認証を使用",
"noproxy-hosts": "例外ホスト",
"utf8": "文字列",
"utf8": "UTF8文字列",
"binary": "バイナリバッファ",
"json": "JSON",
"json": "JSONオブジェクト",
"tip": {
"in": "URLは相対パスになります。",
"res": "本ノードに送付するメッセージは、<i>http input</i>ノードを起点としてください。",
@@ -402,7 +434,8 @@
"deprecated-call": "非推奨の呼び出しです __method__",
"invalid-transport": "httpでないトランスポートが要求されました",
"timeout-isnan": "タイムアウト値が数値ではないため無視します",
"timeout-isnegative": "タイムアウト値が負数のため無視します"
"timeout-isnegative": "タイムアウト値が負数のため無視します",
"invalid-payload": "不正なペイロード"
},
"status": {
"requesting": "要求中"
@@ -432,7 +465,8 @@
"errors": {
"connect-error": "ws接続でエラーが発生しました: ",
"send-error": "送信中にエラーが発生しました: ",
"missing-conf": "サーバ設定が不足しています"
"missing-conf": "サーバ設定が不足しています",
"duplicate-path": "同じパスに対して2つのWebSocketリスナは指定できません: __path__"
}
},
"watch": {
@@ -823,7 +857,9 @@
"breaklines": "行へ分割",
"filelabel": "file",
"sendError": "エラーメッセージを送信(互換モード)",
"deletelabel": "delete __file__"
"deletelabel": "delete __file__",
"utf8String": "UTF8文字列",
"binaryBuffer": "バイナリバッファ"
},
"action": {
"append": "ファイルへ追記",
@@ -841,6 +877,21 @@
"deletedfile": "ファイルを削除しました: __file__",
"appendedfile": "ファイルへ追記しました: __file__"
},
"encoding": {
"none": "デフォルト",
"native": "ネイティブ",
"unicode": "UNICODE",
"japanese": "日本",
"chinese": "中国",
"korean": "韓国",
"taiwan": "台湾/香港",
"windows": "Windowsコードページ",
"iso": "ISOコードページ",
"ibm": "IBMコードページ",
"mac": "Macコードページ",
"koi8": "KOI8コードページ",
"misc": "その他"
},
"errors": {
"nofilename": "ファイル名が設定されていません",
"invaliddelete": "警告: 削除が無効です。設定ダイアログで特定の削除設定を使用してください",
@@ -849,7 +900,7 @@
"appendfail": "ファイルの追記処理が失敗しました: __error__",
"createfail": "ファイルの作成処理が失敗しました: __error__"
},
"tip": "注釈: 「ファイル名」フルパスを設定する必要があります。"
"tip": "注釈: 「ファイル名」フルパスを設定しない場合は、Node-REDプロセスの実行ディレクトリからの相対パスとなります。"
},
"split": {
"split": "split",
@@ -920,7 +971,7 @@
"ascending": "昇順",
"descending": "降順",
"as-number": "数値として比較",
"invalid-exp": "sortードで不正なJSONata式が指定されました",
"invalid-exp": "sortードで不正なJSONata式が指定されました: __message__",
"too-many": "sortードの未処理メッセージの数が許容数を超えました",
"clear": "sortードの未処理メッセージを破棄しました"
},

View File

@@ -27,6 +27,7 @@
<p>入力メッセージのペイロードをファイルの最後に追記します改行(\n)を各データの最後に追加することもできます</p>
<p><code>msg.filename</code>使</p>
<p>追記を行う代わりにファイル全体を上書きするように設定することもできます例えば画像のようなバイナリデータをファイルに書き出す場合はこのオプションを指定し改行を追記するオプションを指定しないようにします</p>
<p>ファイル出力の際のエンコーディングはエンコーディングリストから選択できます</p>
<p>この他ファイルの削除を行うことも可能です</p>
</script>
@@ -51,6 +52,7 @@
<p>Windowsではパスの区切り文字を(例えば<code>\\ユーザー\\名前</code>)</p>
<p>テキストファイルの場合行毎に分割して各々メッセージを送信することができますまたバイナリファイルの場合小さな塊のバッファに分割して送信できますバッファの分割単位はオペレーティングシステム依存ですが一般に64k(Linux/Mac)もしくは41k(Windows)です</p>
<p>複数のメッセージに分割する場合各メッセージには<code>parts</code></p>
<p>主力形式が文字列の場合入力データのエンコーディングをエンコーディングリストから選択できます</p>
<h4>旧式のエラー処理</h4>
<p>Node-RED 0.17より前の版ではファイルの読み込み時にエラーが発生すると<code>payload</code><code>error</code></p>
<p>エラーはcatchードで補足して処理することを推奨します</p>

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,28 +15,29 @@
}
],
"dependencies": {
"ajv": "6.7.0",
"ajv": "6.10.0",
"body-parser": "1.18.3",
"cheerio": "0.22.0",
"cookie-parser": "1.4.3",
"cookie-parser": "1.4.4",
"cookie": "0.3.1",
"cors": "2.8.5",
"cron": "1.6.0",
"cron": "1.7.0",
"denque": "1.4.0",
"fs-extra": "7.0.1",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1",
"is-utf8": "0.2.1",
"js-yaml": "3.12.1",
"js-yaml": "3.12.2",
"media-typer": "1.0.1",
"mqtt": "2.18.8",
"multer": "1.4.1",
"mustache": "3.0.1",
"on-headers": "1.0.1",
"on-headers": "1.0.2",
"raw-body": "2.3.3",
"request": "2.88.0",
"ws": "6.1.3",
"xml2js": "0.4.19"
"ws": "6.2.0",
"xml2js": "0.4.19",
"iconv-lite": "0.4.24"
}
}

View File

@@ -104,7 +104,7 @@ function installModule(module,version) {
}
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var args = ['install','--save','--save-prefix="~"','--production',installName];
var args = ['install','--no-audit','--no-update-notifier','--save','--save-prefix="~"','--production',installName];
log.trace(npmCommand + JSON.stringify(args));
exec.run(npmCommand,args,{
cwd: installDir
@@ -197,7 +197,7 @@ function uninstallModule(module) {
var list = registry.removeModule(module);
log.info(log._("server.install.uninstalling",{name:module}));
var args = ['remove','--save',module];
var args = ['remove','--no-audit','--no-update-notifier','--save',module];
log.trace(npmCommand + JSON.stringify(args));
exec.run(npmCommand,args,{

View File

@@ -34,14 +34,14 @@ function init(_runtime) {
registryUtil.init(runtime);
}
function load(defaultNodesDir,disableNodePathScan) {
function load(disableNodePathScan) {
// To skip node scan, the following line will use the stored node list.
// We should expose that as an option at some point, although the
// performance gains are minimal.
//return loadNodeFiles(registry.getModuleList());
runtime.log.info(runtime.log._("server.loading"));
var nodeFiles = localfilesystem.getNodeFiles(defaultNodesDir,disableNodePathScan);
var nodeFiles = localfilesystem.getNodeFiles(disableNodePathScan);
return loadNodeFiles(nodeFiles);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,7 +16,7 @@
}
],
"dependencies": {
"@node-red/util": "0.20.0-beta.4",
"@node-red/util": "0.20.2",
"semver": "5.6.0",
"uglify-js": "3.4.9",
"when": "3.7.8"

View File

@@ -20,6 +20,7 @@
*/
/**
* A WebSocket connection between the runtime and the editor.
* @typedef CommsConnection
* @type {object}
* @property {string} session - a unique session identifier
@@ -37,7 +38,12 @@ function handleCommsEvent(event) {
publish(event.topic,event.data,event.retain);
}
function handleStatusEvent(event) {
publish("status/"+event.id,event.status,true);
var status = {
text: event.status.text,
fill: event.status.fill,
shape: event.status.shape
};
publish("status/"+event.id,status,true);
}
function handleRuntimeEvent(event) {
runtime.log.trace("runtime event: "+JSON.stringify(event));

View File

@@ -38,7 +38,11 @@ function Node(n) {
// Make this a non-enumerable property as it may cause
// circular references. Any existing code that tries to JSON serialise
// the object (such as dashboard) will not like circular refs
Object.defineProperty(this,'_flow', {value: n._flow, })
// The value must still be writable in the case that a node does:
// Object.assign(this,config)
// as part of its constructure - config._flow will overwrite this._flow
// which we can tolerate as they are the same object.
Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true })
}
this.updateWires(n.wires);
}

View File

@@ -18,6 +18,7 @@ var clone = require("clone");
var log = require("@node-red/util").log;
var util = require("@node-red/util").util;
var memory = require("./memory");
var flows;
var settings;
@@ -47,6 +48,7 @@ function logUnknownStore(name) {
}
function init(_settings) {
flows = require("../flows");
settings = _settings;
contexts = {};
stores = {};
@@ -198,8 +200,24 @@ function getContextStorage(storage) {
}
}
function followParentContext(parent, key) {
if (key === "$parent") {
return [parent, undefined];
}
else if (key.startsWith("$parent.")) {
var len = "$parent.".length;
var new_key = key.substring(len);
var ctx = parent;
while (ctx && new_key.startsWith("$parent.")) {
ctx = ctx.$parent;
new_key = new_key.substring(len);
}
return [ctx, new_key];
}
return null;
}
function createContext(id,seed) {
function createContext(id,seed,parent) {
// Seed is only set for global context - sourced from functionGlobalContext
var scope = id;
var obj = seed || {};
@@ -254,6 +272,21 @@ function createContext(id,seed) {
if (!storage) {
storage = keyParts.store || "_";
}
var result = followParentContext(parent, key);
if (result) {
var [ctx, new_key] = result;
if (ctx && new_key) {
return ctx.get(new_key, storage, callback);
}
else {
if (callback) {
return callback(undefined);
}
else {
return undefined;
}
}
}
} else {
if (!storage) {
storage = "_";
@@ -321,6 +354,19 @@ function createContext(id,seed) {
if (!storage) {
storage = keyParts.store || "_";
}
var result = followParentContext(parent, key);
if (result) {
var [ctx, new_key] = result;
if (ctx && new_key) {
return ctx.set(new_key, value, storage, callback);
}
else {
if (callback) {
return callback();
}
return undefined;
}
}
} else {
if (!storage) {
storage = "_";
@@ -346,7 +392,7 @@ function createContext(id,seed) {
}
context = getContextStorage(storage);
}
if (seed) {
if (seed && settings.exportGlobalContextKeys !== false) {
if (callback) {
context.keys(scope, function(err,keys) {
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
@@ -361,10 +407,36 @@ function createContext(id,seed) {
}
}
});
if (parent) {
Object.defineProperty(obj, "$parent", {
value: parent
});
}
return obj;
}
function getContext(localId,flowId) {
function createRootContext() {
var obj = {};
Object.defineProperties(obj, {
get: {
value: function(key, storage, callback) {
return undefined;
}
},
set: {
value: function(key, value, storage, callback) {
}
},
keys: {
value: function(storage, callback) {
return undefined;
}
}
});
return obj;
}
function getContext(localId,flowId,parent) {
var contextId = localId;
if (flowId) {
contextId = localId+":"+flowId;
@@ -372,10 +444,19 @@ function getContext(localId,flowId) {
if (contexts.hasOwnProperty(contextId)) {
return contexts[contextId];
}
var newContext = createContext(contextId);
var newContext = createContext(contextId,undefined,parent);
if (flowId) {
var node = flows.get(flowId);
var parent = undefined;
if (node && node.type.startsWith("subflow:")) {
parent = node.context().flow;
}
else {
parent = createRootContext();
}
var flowContext = getContext(flowId,undefined,parent);
Object.defineProperty(newContext, 'flow', {
value: getContext(flowId)
value: flowContext
});
}
Object.defineProperty(newContext, 'global', {

View File

@@ -176,9 +176,9 @@ class Flow {
subflowDefinition,
node
);
this.subflowInstanceNodes[id] = subflow;
subflow.start();
this.activeNodes[id] = subflow.node;
this.subflowInstanceNodes[id] = subflow;
// this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
// for (var i=0;i<nodes.length;i++) {
@@ -212,6 +212,21 @@ class Flow {
}
}
}
this.catchNodes.sort(function(A,B) {
if (A.scope && !B.scope) {
return -1;
} else if (!A.scope && B.scope) {
return 1;
} else if (A.scope && B.scope) {
return 0;
} else if (A.uncaught && !B.uncaught) {
return 1;
} else if (!A.uncaught && B.uncaught) {
return -1;
}
return 0;
});
if (activeCount > 0) {
this.trace("------------------|--------------|-----------------");
}
@@ -232,6 +247,7 @@ class Flow {
if (!stopList) {
stopList = Object.keys(this.activeNodes);
}
// this.trace(" stopList: "+stopList.join(","))
// Convert the list to a map to avoid multiple scans of the list
var removedMap = {};
removedList = removedList || [];
@@ -246,8 +262,9 @@ class Flow {
delete this.activeNodes[stopList[i]];
if (this.subflowInstanceNodes[stopList[i]]) {
try {
var subflow = this.subflowInstanceNodes[stopList[i]];
promises.push(stopNode(node,false).then(() => { subflow.stop() }));
(function(subflow) {
promises.push(stopNode(node,false).then(() => { subflow.stop() }));
})(this.subflowInstanceNodes[stopList[i]]);
} catch(err) {
node.error(err);
}
@@ -265,7 +282,6 @@ class Flow {
return Promise.all(promises);
}
/**
* Update the flow definition. This doesn't change anything that is running.
* This should be called after `stop` and before `start`.
@@ -281,11 +297,13 @@ class Flow {
/**
* Get a node instance from this flow. If the node is not known to this
* flow, pass the request up to the parent.
* @param {[type]} id [description]
* @param {String} id [description]
* @param {Boolean} cancelBubble if true, prevents the flow from passing the request to the parent
* This stops infinite loops when the parent asked this Flow for the
* node to begin with.
* @return {[type]} [description]
*/
getNode(id) {
// console.log('getNode',id,!!this.activeNodes[id])
getNode(id, cancelBubble) {
if (!id) {
return undefined;
}
@@ -297,8 +315,22 @@ class Flow {
} else if (this.activeNodes[id]) {
// TEMP: this is a subflow internal node within this flow
return this.activeNodes[id];
} else if (cancelBubble) {
// The node could be inside one of this flow's subflows
var node;
for (var sfId in this.subflowInstanceNodes) {
if (this.subflowInstanceNodes.hasOwnProperty(sfId)) {
node = this.subflowInstanceNodes[sfId].getNode(id,cancelBubble);
if (node) {
return node;
}
}
}
} else {
// Node not found inside this flow - ask the parent
return this.parent.getNode(id);
}
return this.parent.getNode(id);
return undefined;
}
/**
@@ -358,18 +390,17 @@ class Flow {
return;
}
var message = {
status: {
text: "",
source: {
id: node.id,
type: node.type,
name: node.name
}
}
};
status: clone(statusMessage)
}
if (statusMessage.hasOwnProperty("text")) {
message.status.text = statusMessage.text.toString();
}
message.status.source = {
id: node.id,
type: node.type,
name: node.name
}
targetStatusNode.receive(message);
handled = true;
});
@@ -415,10 +446,20 @@ class Flow {
}
handled = true;
} else {
var handledByUncaught = false;
this.catchNodes.forEach(function(targetCatchNode) {
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(reportingNode.id) === -1) {
return;
}
if (!targetCatchNode.scope && targetCatchNode.uncaught && !handledByUncaught) {
if (handled) {
// This has been handled by a !uncaught catch node
return;
}
// This is an uncaught error
handledByUncaught = true;
}
var errorMessage;
if (msg) {
errorMessage = redUtil.cloneMessage(msg);
@@ -461,6 +502,7 @@ class Flow {
}
console.log("==================")
}
}
/**

View File

@@ -17,11 +17,14 @@
const clone = require("clone");
const Flow = require('./Flow').Flow;
const util = require("util");
const redUtil = require("@node-red/util").util;
const flowUtil = require("./util");
var Log;
/**
* This class represents a subflow - which is handled as a special type of Flow
*/
@@ -37,7 +40,6 @@ class Subflow extends Flow {
* @param {[type]} subflowInstance [description]
*/
constructor(parent,globalFlow,subflowDef,subflowInstance) {
// console.log(subflowDef);
// console.log("CREATE SUBFLOW",subflowDef.id,subflowInstance.id);
// console.log("SubflowInstance\n"+JSON.stringify(subflowInstance," ",2));
// console.log("SubflowDef\n"+JSON.stringify(subflowDef," ",2));
@@ -90,6 +92,15 @@ class Subflow extends Flow {
this.subflowDef = subflowDef;
this.subflowInstance = subflowInstance;
this.node_map = node_map;
var env = [];
if (this.subflowDef.env) {
this.subflowDef.env.forEach(e => { env[e.name] = e; });
}
if (this.subflowInstance.env) {
this.subflowInstance.env.forEach(e => { env[e.name] = e; });
}
this.env = env;
}
/**
@@ -104,6 +115,40 @@ class Subflow extends Flow {
var self = this;
// Create a subflow node to accept inbound messages and route appropriately
var Node = require("../Node");
if (this.subflowDef.status) {
var subflowStatusConfig = {
id: this.subflowInstance.id+":status",
type: "subflow-status",
z: this.subflowInstance.id,
_flow: this.parent
}
this.statusNode = new Node(subflowStatusConfig);
this.statusNode.on("input", function(msg) {
if (msg.payload !== undefined) {
if (typeof msg.payload === "string") {
// if msg.payload is a String, use it as status text
self.node.status({text:msg.payload})
return;
} else if (Object.prototype.toString.call(msg.payload) === "[object Object]") {
if (msg.payload.hasOwnProperty('text') || msg.payload.hasOwnProperty('fill') || msg.payload.hasOwnProperty('shape') || Object.keys(msg.payload).length === 0) {
// msg.payload is an object that looks like a status object
self.node.status(msg.payload);
return;
}
}
// Anything else - inspect it and use as status text
var text = util.inspect(msg.payload);
if (text.length > 32) { text = text.substr(0,32) + "..."; }
self.node.status({text:text});
} else if (msg.status !== undefined) {
// if msg.status exists
self.node.status(msg.status)
}
})
}
var subflowInstanceConfig = {
id: this.subflowInstance.id,
type: this.subflowInstance.type,
@@ -119,6 +164,8 @@ class Subflow extends Flow {
this.node = new Node(subflowInstanceConfig);
this.node.on("input", function(msg) { this.send(msg);});
this.node.on("close", function() { this.status({}); })
this.node.status = status => this.parent.handleStatus(this.node,status);
this.node._updateWires = this.node.updateWires;
@@ -164,15 +211,14 @@ class Subflow extends Flow {
self.node._updateWires(subflowInstanceConfig.wires);
}
}
}
};
// Wire the subflow outputs
if (this.subflowDef.out) {
var modifiedNodes = {};
for (var i=0;i<this.subflowDef.out.length;i++) {
// i: the output index
// This is what this Output is wired to
wires = this.subflowDef.out[i].wires;
var wires = this.subflowDef.out[i].wires;
for (var j=0;j<wires.length;j++) {
if (wires[j].id === this.subflowDef.id) {
// A subflow input wired straight to a subflow output
@@ -180,7 +226,6 @@ class Subflow extends Flow {
this.node._updateWires(subflowInstanceConfig.wires);
} else {
var node = self.node_map[wires[j].id];
modifiedNodes[node.id] = node;
if (!node._originalWires) {
node._originalWires = clone(node.wires);
}
@@ -189,9 +234,72 @@ class Subflow extends Flow {
}
}
}
if (this.subflowDef.status) {
var subflowStatusId = this.statusNode.id;
wires = this.subflowDef.status.wires;
for (var j=0;j<wires.length;j++) {
if (wires[j].id === this.subflowDef.id) {
// A subflow input wired straight to a subflow output
subflowInstanceConfig.wires[wires[j].port].push(subflowStatusId);
this.node._updateWires(subflowInstanceConfig.wires);
} else {
var node = self.node_map[wires[j].id];
if (!node._originalWires) {
node._originalWires = clone(node.wires);
}
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]);
node.wires[wires[j].port].push(subflowStatusId);
}
}
}
super.start(diff);
}
/**
* Get environment variable of subflow
* @param {String} name name of env var
* @return {Object} val value of env var
*/
getSetting(name) {
var env = this.env;
if (env && env.hasOwnProperty(name)) {
var val = env[name];
try {
var ret = redUtil.evaluateNodeProperty(val.value, val.type, this.node, null, null);
return ret;
}
catch (e) {
this.error(e);
return undefined;
}
}
var parent = this.parent;
if (parent) {
var val = parent.getSetting(name);
return val;
}
return undefined;
}
/**
* Get a node instance from this subflow.
* If the subflow has a status node, check for that, otherwise use
* the super-class function
* @param {String} id [description]
* @param {Boolean} cancelBubble if true, prevents the flow from passing the request to the parent
* This stops infinite loops when the parent asked this Flow for the
* node to begin with.
* @return {[type]} [description]
*/
getNode(id, cancelBubble) {
if (this.statusNode && this.statusNode.id === id) {
return this.statusNode;
}
return super.getNode(id,cancelBubble);
}
/**
* Handle a status event from a node within this flow.
* @param {Node} node The original node that triggered the event
@@ -205,10 +313,14 @@ class Subflow extends Flow {
handleStatus(node,statusMessage,reportingNode,muteStatus) {
let handled = super.handleStatus(node,statusMessage,reportingNode,muteStatus);
if (!handled) {
// No status node on this subflow caught the status message.
// Pass up to the parent with this subflow's instance as the
// reporting node
handled = this.parent.handleStatus(node,statusMessage,this.node,true);
if (!this.statusNode || node === this.node) {
// No status node on this subflow caught the status message.
// AND there is no Subflow Status node - so the user isn't
// wanting to manage status messages themselves
// Pass up to the parent with this subflow's instance as the
// reporting node
handled = this.parent.handleStatus(node,statusMessage,this.node,true);
}
}
return handled;
@@ -234,7 +346,6 @@ class Subflow extends Flow {
return handled;
}
}

View File

@@ -70,6 +70,7 @@ function init(runtime) {
typeEventRegistered = true;
}
Flow.init(runtime);
flowUtil.init(runtime);
}
function loadFlows() {
@@ -207,11 +208,11 @@ function setFlows(_config,type,muteLog,forceStart) {
function getNode(id) {
var node;
if (activeNodesToFlow[id] && activeFlows[activeNodesToFlow[id]]) {
return activeFlows[activeNodesToFlow[id]].getNode(id);
return activeFlows[activeNodesToFlow[id]].getNode(id,true);
}
for (var flowId in activeFlows) {
if (activeFlows.hasOwnProperty(flowId)) {
node = activeFlows[flowId].getNode(id);
node = activeFlows[flowId].getNode(id,true);
if (node) {
return node;
}
@@ -388,14 +389,14 @@ function stop(type,diff,muteLog) {
if (activeFlows.hasOwnProperty(id)) {
var flowStateChanged = diff && (diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1);
log.debug("red/nodes/flows.stop : stopping flow : "+id);
promises = promises.concat(activeFlows[id].stop(flowStateChanged?null:stopList,removedList));
promises.push(activeFlows[id].stop(flowStateChanged?null:stopList,removedList));
if (type === "full" || flowStateChanged || diff.removed.indexOf(id)!==-1) {
delete activeFlows[id];
}
}
}
return Promise.resolve(promises).then(function() {
return Promise.all(promises).then(function() {
for (id in activeNodesToFlow) {
if (activeNodesToFlow.hasOwnProperty(id)) {
if (!activeFlows[activeNodesToFlow[id]]) {
@@ -676,7 +677,7 @@ const flowAPI = {
getNode: getNode,
handleError: () => false,
handleStatus: () => false,
getSetting: k => process.env[k]
getSetting: k => flowUtil.getEnvVar(k)
}

View File

@@ -19,6 +19,8 @@ var Log = require("@node-red/util").log;
var subflowInstanceRE = /^subflow:(.+)$/;
var typeRegistry = require("@node-red/registry");
var envVarExcludes = {};
function diffNodes(oldNode,newNode) {
if (oldNode == null) {
return true;
@@ -52,12 +54,8 @@ function mapEnvVarProperties(obj,prop,flow) {
} else if (typeof obj[prop] === 'string') {
if (obj[prop][0] === "$" && (EnvVarPropertyRE_old.test(v) || EnvVarPropertyRE.test(v)) ) {
var envVar = v.substring(2,v.length-1);
if (!flow) {
obj[prop] = process.env.hasOwnProperty(envVar)?process.env[envVar]:v;
} else {
var r = flow.getSetting(envVar);
obj[prop] = r!==undefined?r:obj[prop];
}
var r = flow.getSetting(envVar);
obj[prop] = r!==undefined?r:obj[prop];
}
} else {
for (var p in v) {
@@ -69,7 +67,15 @@ function mapEnvVarProperties(obj,prop,flow) {
}
module.exports = {
init: function(runtime) {
envVarExcludes = {};
if (runtime.settings.hasOwnProperty('envVarExcludes') && Array.isArray(runtime.settings.envVarExcludes)) {
runtime.settings.envVarExcludes.forEach(v => envVarExcludes[v] = true);
}
},
getEnvVar: function(k) {
return !envVarExcludes[k]?process.env[k]:undefined
},
diffNodes: diffNodes,
mapEnvVarProperties: mapEnvVarProperties,

View File

@@ -53,6 +53,7 @@ function getGitUser(user) {
}
return null;
}
function Project(path) {
this.path = path;
this.name = fspath.basename(path);
@@ -72,7 +73,7 @@ Project.prototype.load = function () {
projectSettings = globalProjectSettings.projects[this.name] || {};
}
}
this.paths.root = projectSettings.rootPath || "";
this.credentialSecret = projectSettings.credentialSecret;
this.git = projectSettings.git || { user:{} };
@@ -83,14 +84,15 @@ Project.prototype.load = function () {
return checkProjectFiles(project).then(function(missingFiles) {
project.missingFiles = missingFiles;
if (missingFiles.indexOf('package.json') === -1) {
project.paths['package.json'] = fspath.join(project.path,"package.json");
promises.push(fs.readFile(project.paths['package.json'],"utf8").then(function(content) {
// We have a package.json in project.path+project.paths.root+"package.json"
project.paths['package.json'] = fspath.join(project.paths.root,"package.json");
promises.push(fs.readFile(fspath.join(project.path,project.paths['package.json']),"utf8").then(function(content) {
try {
project.package = util.parseJSON(content);
if (project.package.hasOwnProperty('node-red')) {
if (project.package['node-red'].hasOwnProperty('settings')) {
project.paths.flowFile = project.package['node-red'].settings.flowFile;
project.paths.credentialsFile = project.package['node-red'].settings.credentialsFile;
project.paths.flowFile = fspath.join(project.paths.root,project.package['node-red'].settings.flowFile);
project.paths.credentialsFile = fspath.join(project.paths.root,project.package['node-red'].settings.credentialsFile);
}
} else {
// TODO: package.json doesn't have a node-red section
@@ -101,17 +103,19 @@ Project.prototype.load = function () {
project.package = {};
}
}));
if (missingFiles.indexOf('README.md') === -1) {
project.paths['README.md'] = fspath.join(project.paths.root,"README.md");
promises.push(fs.readFile(fspath.join(project.path,project.paths['README.md']),"utf8").then(function(content) {
project.description = content;
}));
} else {
project.description = "";
}
} else {
project.package = {};
}
if (missingFiles.indexOf('README.md') === -1) {
project.paths['README.md'] = fspath.join(project.path,"README.md");
promises.push(fs.readFile(project.paths['README.md'],"utf8").then(function(content) {
project.description = content;
}));
} else {
project.description = "";
}
// if (missingFiles.indexOf('flow.json') !== -1) {
// console.log("MISSING FLOW FILE");
// } else {
@@ -224,6 +228,7 @@ Project.prototype.parseRemoteBranch = function (remoteBranch) {
Project.prototype.isEmpty = function () {
return this.empty;
};
Project.prototype.isMerging = function() {
return this.merging;
}
@@ -243,6 +248,7 @@ Project.prototype.update = function (user, data) {
var savePackage = false;
var flowFilesChanged = false;
var credentialSecretChanged = false;
var reloadProject = false;
var globalProjectSettings = settings.get("projects");
if (!globalProjectSettings.projects.hasOwnProperty(this.name)) {
@@ -250,7 +256,6 @@ Project.prototype.update = function (user, data) {
saveSettings = true;
}
if (data.credentialSecret && data.credentialSecret !== this.credentialSecret) {
var existingSecret = data.currentCredentialSecret;
var isReset = data.resetCredentialSecret;
@@ -278,6 +283,54 @@ Project.prototype.update = function (user, data) {
credentialSecretChanged = true;
}
if (this.missingFiles.indexOf('package.json') !== -1) {
if (!data.files || !data.files.package) {
// Cannot update a project that doesn't have a known package.json
return Promise.reject("Cannot update project with missing package.json");
}
}
if (data.hasOwnProperty('files')) {
this.package['node-red'] = this.package['node-red'] || { settings: {}};
if (data.files.hasOwnProperty('package') && data.files.package !== fspath.join(this.paths.root,"package.json")) {
// We have a package file. It could be one that doesn't exist yet,
// or it does exist and we need to load it.
if (!/package\.json$/.test(data.files.package)) {
return Promise.reject("Invalid package file: "+data.files.package)
}
var root = data.files.package.substring(0,data.files.package.length-12);
this.paths.root = root;
this.paths['package.json'] = data.files.package;
globalProjectSettings.projects[this.name].rootPath = root;
saveSettings = true;
// 1. check if it exists
if (fs.existsSync(fspath.join(this.path,this.paths['package.json']))) {
// Load the existing one....
} else {
var newPackage = defaultFileSet["package.json"](this);
fs.writeFileSync(fspath.join(this.path,this.paths['package.json']),newPackage);
this.package = JSON.parse(newPackage);
}
reloadProject = true;
flowFilesChanged = true;
}
if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow.substring(this.paths.root.length)) {
this.paths.flowFile = data.files.flow;
this.package['node-red'].settings.flowFile = data.files.flow.substring(this.paths.root.length);
savePackage = true;
flowFilesChanged = true;
}
if (data.files.hasOwnProperty('credentials') && this.package['node-red'].settings.credentialsFile !== data.files.credentials.substring(this.paths.root.length)) {
this.paths.credentialsFile = data.files.credentials;
this.package['node-red'].settings.credentialsFile = data.files.credentials.substring(this.paths.root.length);
// Don't know if the credSecret is invalid or not so clear the flag
delete this.credentialSecretInvalid;
savePackage = true;
flowFilesChanged = true;
}
}
if (data.hasOwnProperty('description')) {
saveREADME = true;
this.description = data.description;
@@ -333,47 +386,32 @@ Project.prototype.update = function (user, data) {
}
}
if (data.hasOwnProperty('files')) {
this.package['node-red'] = this.package['node-red'] || { settings: {}};
if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow) {
this.paths.flowFile = data.files.flow;
this.package['node-red'].settings.flowFile = data.files.flow;
savePackage = true;
flowFilesChanged = true;
}
if (data.files.hasOwnProperty('credentials') && this.package['node-red'].settings.credentialsFile !== data.files.credentials) {
this.paths.credentialsFile = data.files.credentials;
this.package['node-red'].settings.credentialsFile = data.files.credentials;
// Don't know if the credSecret is invalid or not so clear the flag
delete this.credentialSecretInvalid;
savePackage = true;
flowFilesChanged = true;
}
}
if (saveSettings) {
promises.push(settings.set("projects",globalProjectSettings));
}
if (saveREADME) {
promises.push(util.writeFile(this.paths['README.md'], this.description));
promises.push(util.writeFile(fspath.join(this.path,this.paths['README.md']), this.description));
}
if (savePackage) {
promises.push(fs.readFile(project.paths['package.json'],"utf8").then(content => {
promises.push(fs.readFile(fspath.join(project.path,project.paths['package.json']),"utf8").then(content => {
var currentPackage = {};
try {
currentPackage = util.parseJSON(content);
} catch(err) {
}
this.package = Object.assign(currentPackage,this.package);
return util.writeFile(this.paths['package.json'], JSON.stringify(this.package,"",4));
return util.writeFile(fspath.join(project.path,this.paths['package.json']), JSON.stringify(this.package,"",4));
}));
}
return when.settle(promises).then(function(res) {
return {
return when.settle(promises).then(res => {
if (reloadProject) {
return this.load()
}
}).then(() => { return {
flowFilesChanged: flowFilesChanged,
credentialSecretChanged: credentialSecretChanged
}
})
}})
};
Project.prototype.getFiles = function () {
@@ -384,12 +422,15 @@ Project.prototype.getFiles = function () {
throw err;
});
};
Project.prototype.stageFile = function(file) {
return gitTools.stageFile(this.path,file);
};
Project.prototype.unstageFile = function(file) {
return gitTools.unstageFile(this.path,file);
}
Project.prototype.commit = function(user, options) {
var self = this;
return gitTools.commit(this.path,options.message,getGitUser(user)).then(function() {
@@ -399,9 +440,11 @@ Project.prototype.commit = function(user, options) {
}
});
}
Project.prototype.getFileDiff = function(file,type) {
return gitTools.getFileDiff(this.path,file,type);
}
Project.prototype.getCommits = function(options) {
return gitTools.getCommits(this.path,options).catch(function(err) {
if (/bad default revision/i.test(err.message) || /ambiguous argument/i.test(err.message) || /does not have any commits yet/i.test(err.message)) {
@@ -414,9 +457,11 @@ Project.prototype.getCommits = function(options) {
throw err;
})
}
Project.prototype.getCommit = function(sha) {
return gitTools.getCommit(this.path,sha);
}
Project.prototype.getFile = function (filePath,treeish) {
if (treeish !== "_") {
return gitTools.getFile(this.path, filePath, treeish);
@@ -424,6 +469,7 @@ Project.prototype.getFile = function (filePath,treeish) {
return fs.readFile(fspath.join(this.path,filePath),"utf8");
}
};
Project.prototype.revertFile = function (filePath) {
var self = this;
return gitTools.revertFile(this.path, filePath).then(function() {
@@ -431,8 +477,6 @@ Project.prototype.revertFile = function (filePath) {
});
};
Project.prototype.status = function(user, includeRemote) {
var self = this;
@@ -459,7 +503,7 @@ Project.prototype.status = function(user, includeRemote) {
gitTools.getStatus(self.path),
fs.exists(fspath.join(self.path,".git","MERGE_HEAD"))
];
return when.all(promises).then(function(results) {
return Promise.all(promises).then(function(results) {
var result = results[0];
if (results[1]) {
result.merging = true;
@@ -615,6 +659,7 @@ Project.prototype.resolveMerge = function (file,resolutions) {
})
});
};
Project.prototype.abortMerge = function () {
var self = this;
return gitTools.abortMerge(this.path).then(function() {
@@ -675,12 +720,11 @@ Project.prototype.setBranch = function (branchName, isCreate) {
return self.load();
})
};
Project.prototype.getBranchStatus = function (branchName) {
return gitTools.getBranchStatus(this.path,branchName);
};
Project.prototype.getRemotes = function (user) {
return gitTools.getRemotes(this.path).then(function(remotes) {
var result = [];
@@ -693,12 +737,14 @@ Project.prototype.getRemotes = function (user) {
return {remotes:result};
})
};
Project.prototype.addRemote = function(user,remote,options) {
var project = this;
return gitTools.addRemote(this.path,remote,options).then(function() {
return project.loadRemotes()
});
}
Project.prototype.updateRemote = function(user,remote,options) {
var username;
if (!user) {
@@ -716,6 +762,7 @@ Project.prototype.updateRemote = function(user,remote,options) {
}
return Promise.resolve();
}
Project.prototype.removeRemote = function(user, remote) {
// TODO: if this was the last remote using this url, then remove the authCache
// details.
@@ -725,7 +772,6 @@ Project.prototype.removeRemote = function(user, remote) {
});
}
Project.prototype.getFlowFile = function() {
// console.log("Project.getFlowFile = ",this.paths.flowFile);
if (this.paths.flowFile) {
@@ -742,6 +788,7 @@ Project.prototype.getFlowFileBackup = function() {
}
return null;
}
Project.prototype.getCredentialsFile = function() {
// console.log("Project.getCredentialsFile = ",this.paths.credentialsFile);
if (this.paths.credentialsFile) {
@@ -750,6 +797,7 @@ Project.prototype.getCredentialsFile = function() {
return this.paths.credentialsFile;
}
}
Project.prototype.getCredentialsFileBackup = function() {
return getBackupFilename(this.getCredentialsFile());
}
@@ -763,10 +811,11 @@ Project.prototype.export = function () {
dependencies: this.package.dependencies||{},
empty: this.empty,
settings: {
credentialsEncrypted: (typeof this.credentialSecret === "string"),
credentialsEncrypted: (typeof this.credentialSecret === "string") && this.credentialSecret.length > 0,
credentialSecretInvalid: this.credentialSecretInvalid
},
files: {
package: this.paths['package.json'],
flow: this.paths.flowFile,
credentials: this.paths.credentialsFile
},
@@ -777,7 +826,6 @@ Project.prototype.export = function () {
}
};
function getCredentialsFilename(filename) {
filename = filename || "undefined";
// TODO: DRY - ./index.js
@@ -793,7 +841,6 @@ function getBackupFilename(filename) {
var ffDir = fspath.dirname(filename);
return fspath.join(ffDir,"."+ffName+".backup");
}
function checkProjectExists(projectPath) {
return fs.pathExists(projectPath).then(function(exists) {
if (!exists) {
@@ -805,7 +852,6 @@ function checkProjectExists(projectPath) {
}
});
}
function createDefaultProject(user, project) {
var projectPath = fspath.join(projectsDir,project.name);
// Create a basic skeleton of a project
@@ -869,15 +915,13 @@ function createDefaultProject(user, project) {
})
});
}
function checkProjectFiles(project) {
var projectPath = project.path;
var promises = [];
var paths = [];
for (var file in defaultFileSet) {
if (defaultFileSet.hasOwnProperty(file)) {
paths.push(file);
promises.push(fs.stat(fspath.join(projectPath,file)));
promises.push(fs.stat(fspath.join(project.path,project.paths.root,file)));
}
}
return when.settle(promises).then(function(results) {
@@ -900,7 +944,6 @@ function checkProjectFiles(project) {
// }
});
}
function createProject(user, metadata) {
var username;
if (!user) {
@@ -933,6 +976,9 @@ function createProject(user, metadata) {
}
projects.projects[project] = {};
if (metadata.hasOwnProperty('credentialSecret')) {
if (metadata.credentialSecret === "") {
metadata.credentialSecret = false;
}
projects.projects[project].credentialSecret = metadata.credentialSecret;
}
return settings.set('projects',projects);
@@ -970,7 +1016,6 @@ function createProject(user, metadata) {
})
})
}
function deleteProject(user, projectPath) {
return checkProjectExists(projectPath).then(function() {
return fs.remove(projectPath).then(function() {
@@ -981,14 +1026,12 @@ function deleteProject(user, projectPath) {
});
});
}
function loadProject(projectPath) {
return checkProjectExists(projectPath).then(function() {
var project = new Project(projectPath);
return project.load();
});
}
function init(_settings, _runtime) {
settings = _settings;
runtime = _runtime;

View File

@@ -14,8 +14,6 @@
* limitations under the License.
**/
var when = require('when');
var exec = require("../../../../exec");
var authResponseServer = require('./authServer').ResponseServer;
@@ -31,12 +29,12 @@ function runGitCommand(args,cwd,env,emit) {
log.trace(gitCommand + JSON.stringify(args));
args.unshift("credential.helper=")
args.unshift("-c");
return exec.run(gitCommand, args, {cwd:cwd, env:env}, emit).then(result => {
return exec.run(gitCommand, args, {cwd:cwd, detached:true, env:env}, emit).then(result => {
return result.stdout;
}).catch(result => {
var err = new Error(stderr);
var stdout = result.stdout;
var stderr = result.stderr;
var err = new Error(stderr);
err.stdout = stdout;
err.stderr = stderr;
if (/Connection refused/i.test(stderr)) {
@@ -84,8 +82,12 @@ function runGitCommandWithAuth(args,cwd,auth,emit) {
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
return runGitCommand(args,cwd,commandEnv,emit).then( result => {
rs.close();
return result;
}).catch(err => {
rs.close();
throw err;
});
})
}
@@ -104,8 +106,12 @@ function runGitCommandWithSSHCommand(args,cwd,auth,emit) {
// GIT_SSH_COMMAND - added in git 2.3.0
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
// console.log('commandEnv:', commandEnv);
return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
return runGitCommand(args,cwd,commandEnv,emit).then( result => {
rs.close();
return result;
}).catch(err => {
rs.close();
throw err;
});
})
}
@@ -362,7 +368,7 @@ function getBranchStatus(cwd,remoteBranch) {
// #commits master behind
runGitCommand(['rev-list', '^HEAD',remoteBranch, '--count'],cwd)
];
return when.all(commands).then(function(results) {
return Promise.all(commands).then(function(results) {
return {
commits: {
ahead: parseInt(results[0]),
@@ -589,7 +595,7 @@ module.exports = {
runGitCommand(['rev-list', 'HEAD', '--count'],cwd),
runGitCommand(args,cwd).then(parseLog)
];
return when.all(commands).then(function(results) {
return Promise.all(commands).then(function(results) {
var result = results[0];
result.count = results[1].length;
result.before = before;

View File

@@ -0,0 +1,167 @@
{
"runtime": {
"welcome": "Node-RED에 오신것을 환영합니다.",
"version": "__component__ 버전: __version__",
"unsupported_version": "__component__는 지원하지 않는 버전입니다. 요구버전: __requires__ 현재버전: __version__",
"paths": {
"settings": "설정 파일 : __path__",
"httpStatic": "HTTP Static : __path__"
}
},
"server": {
"loading": "팔렛트 노드 읽는 중",
"palette-editor": {
"disabled": "팔렛트 에디터 사용불가 : 사용자 설정",
"npm-not-found": "팔렛트 에디터 사용불가 : npm 명령어가 없습니다.",
"npm-too-old": "팔렛트 에디터 사용불가 : npm 버전이 너무 오래되었습니다. 3.x이상의 npm을 사용하세요."
},
"errors": "__count__개의 노드타입 등록 에러",
"errors_plural": "__count__개의 노드타입 등록 에러",
"errors-help": "-v 를 실행하여 상세내역을 확인하세요",
"missing-modules": "노드모듈이 없습니다:",
"node-version-mismatch": "버전이 잘못 되었습니다. 요구버전: __version__ ",
"type-already-registered": "'__type__' 은 __module__ 으로 이미 등록되어 있습니다.",
"removing-modules": "설정에서 모듈 제거중",
"added-types": "노드타입 추가:",
"removed-types": "노드타입 제거:",
"install": {
"invalid": "잘못된 모듈명",
"installing": "모듈 설치중: __name__, 버전: __version__",
"installed": "모듈이 설치되었습니다: __name__",
"install-failed": "설치 실패",
"install-failed-long": "__name__ 모듈 설치 실패:",
"install-failed-not-found": "$t(install-failed-long) 모듈이 없습니다.",
"upgrading": "모듈 업그레이드: __name__ to 버전: __version__",
"upgraded": "모듈 업그레이드: __name__. 새 버전을 사용하기 위해 Node-RED를 재시작 합니다.",
"upgrade-failed-not-found": "$t(server.install.install-failed-long) 버전이 없습니다.",
"uninstalling": "모듈 제거중: __name__",
"uninstall-failed": "제거 실패",
"uninstall-failed-long": "__name__ 모듈 제거 실패:",
"uninstalled": "모듈 제거: __name__"
},
"unable-to-listen": "__listenpath__에서 listen 할 수 없습니다.",
"port-in-use": "에러: 포트 사용중",
"uncaught-exception": "Uncaught Exception:",
"admin-ui-disabled": "관리 UI 비활성화",
"now-running": "__listenpath__에서 서버가 실행중 입니다.",
"failed-to-start": "서버시작 실패:",
"headless-mode": "headless 모드로 실행중입니다.",
"httpadminauth-deprecated": "httpAdminAuth는 더 이상 사용되지 않습니다. adminAuth를 사용하세요."
},
"api": {
"flows": {
"error-save": "플로우 저장 에러: __message__",
"error-reload": "플로우 새로고침 에러: __message__"
},
"library": {
"error-load-entry": "라이브러리 '__path__'불러오기 에러: __message__",
"error-save-entry": "라이브러리 '__path__'저장 에러: __message__",
"error-load-flow": "플로우 '__path__'불러오기 에러: __message__",
"error-save-flow": "플로우 '__path__'저장 에러: __message__"
},
"nodes": {
"enabled": "노드타입 활성화:",
"disabled": "노드타입 비활성화:",
"error-enable": "노드 활성화 에러:"
}
},
"comms": {
"error": "통신채널 에러: __message__",
"error-server": "통신서버 에러: __message__",
"error-send": "전송 에러: __message__"
},
"settings": {
"user-not-available": "사용자 설정을 저장할수 없습니다: __message__",
"not-available": "설정을 사용할 수 없습니다.",
"property-read-only": "'__prop__' 속성은 읽기 전용입니다."
},
"nodes": {
"credentials": {
"error": "인증정보 읽어오기 에러: __message__",
"error-saving": "인증정보 저장 에러: __message__",
"not-registered": "인증정보 '__type__'는 등록되어 있지않습니다.",
"system-key-warning": "\n\n---------------------------------------------------------------------\n 시스템에서 생성한 키를 사용하여 플로우 자격증명 파일이 암호화되어 있습니다. \n\n 만일 시스템 생성 키가 어떤 이유로든 손실되면 자격증명파일을\n 복구 할 수 없습니다. 그러한 경우엔 삭제하고 자격증명을 다시 \n 입력해야 합니다.\n\n 'credentialSecret' 옵션을 사용하여 자신의 키를 설정해야 합니다. \n Node-RED는 변경내용을 다음 배포시에 선택한 키를 사용하여 \n 자격증명파일을 다시 암호화합니다.\n---------------------------------------------------------------------\n"
},
"flows": {
"safe-mode": "[안전모드] 플로우가 정지되었습니다. 시작하려면 배포하세요.",
"registered-missing": "누락된 노드를 등록합니다: __type__",
"error": "플로우 불러오기 에러: __message__",
"starting-modified-nodes": "수정된 노드 시작중",
"starting-modified-flows": "수정된 플로우 시작중",
"starting-flows": "플로우 시작중",
"started-modified-nodes": "수정된 노드 시작됨",
"started-modified-flows": "수정된 플로우 시작됨",
"started-flows": "플로우 시작됨",
"stopping-modified-nodes": "수정된 노드 중지중",
"stopping-modified-flows": "수정된 플로우 중지중",
"stopping-flows": "플로우 중지중",
"stopped-modified-nodes": "수정된 노드 중지됨",
"stopped-modified-flows": "수정된 플로우 중지됨",
"stopped-flows": "플로우 중지됨",
"stopped": "중지됨",
"stopping-error": "노드 중지 오류: __message__",
"added-flow": "플로우 추가: __label__",
"updated-flow": "플로우 변경: __label__",
"removed-flow": "플로우 삭제: __label__",
"missing-types": "누락된 플로우타입이 등록되기를 기다림:",
"missing-type-provided": " - __type__ (provided by npm module __module__)",
"missing-type-install-1": "누락된 모듈을 설치하려면, 실행:",
"missing-type-install-2": "디렉토리에서:"
},
"flow": {
"unknown-type": "알수없는 타입: __type__",
"missing-types": "누락된 타입",
"error-loop": "메세지 최대 캐치수를 초과했습니다."
},
"index": {
"unrecognised-id": "인식할 수 없는 ID: __id__",
"type-in-use": "사용하는 타입: __msg__",
"unrecognised-module": "인식할 수 없는 모듈: __module__"
},
"registry": {
"localfilesystem": {
"module-not-found": "'__module__' 모듈을 찾을 수 없습니다."
}
}
},
"storage": {
"index": {
"forbidden-flow-name": "올바르지 않은 플로우명"
},
"localfilesystem": {
"user-dir": "사용자 디렉토리: __path__",
"flows-file": "플로우 파일 : __path__",
"create": "새로운 __type__ 파일 만듭니다.",
"empty": "기존 __type__ 파일이 비어있습니다.",
"invalid": "기존 __type__ 파일이 json형식이 아닙니다.",
"restore": "__type__ 파일을 __path__ 에서 복원합니다.",
"restore-fail": "__type__ 파일 복원 실패 : __message__",
"fsync-fail": "__path__ 파일 디스크쓰기 실패 : __message__",
"projects": {
"changing-project": "프로젝트 설정: __project__",
"active-project": "선택중인 프로젝트: __project__",
"project-not-found": "프로젝트가 없습니다: __project__",
"no-active-project": "선택된 프로젝트가 없습니다: 기본 플로우 파일을 사용합니다.",
"disabled": "프로젝트가 비활성화 되어있습니다: editorTheme.projects.enabled=false",
"disabledNoFlag": "프로젝트가 비활성화 되어있습니다: set editorTheme.projects.enabled=true to enable",
"git-not-found": "프로젝트가 비활성화 되어있습니다: git 명령어가 없습니다.",
"git-version-old": "프로젝트가 비활성화 되어있습니다: git __version__ 을 지원하지 않습니다. 2.x가 요구됩니다.",
"summary": "Node-RED 프로젝트",
"readme": "### 설명\n\n 이것은 프로젝트 README.md 파일입니다. 이 파일에는 프로젝트의 설명, \n 이용방법, 그 외 정보를 기재합니다."
}
}
},
"context": {
"log-store-init": "Context 저장소 : '__name__' [__info__]",
"error-loading-module": "context 저장소 불러오기 에러: __message__",
"error-loading-module2": "context 저장소 불러오기 에러 '__module__': __message__ ",
"error-module-not-defined": "Context 저장소 '__storage__'에 'module'옵션이 지정되지 않았습니다.",
"error-invalid-module-name": "context 저장소 이름 에러: '__name__'",
"error-invalid-default-module": "기본 context 저장소가 없음: '__storage__'",
"unknown-store": "알 수 없는 context 저장소 '__name__' 가 지정되었습니다. 기본 저장소를 사용합니다.",
"localfilesystem": {
"error-circular": "Context __scope__ 는 지속할 수 없는 순환참조를 포함합니다.",
"error-write": "context 저장 에러: __message__"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "0.20.0-beta.4",
"@node-red/util": "0.20.0-beta.4",
"@node-red/registry": "0.20.2",
"@node-red/util": "0.20.2",
"clone": "2.1.2",
"express": "4.16.4",
"fs-extra": "7.0.1",

View File

@@ -413,6 +413,23 @@ function setObjectProperty(msg,prop,value,createMissing) {
}
}
/*!
* Get value of environment variable.
* @param {Node} node - accessing node
* @param {String} name - name of variable
* @return {String} value of env var
*/
function getSetting(node, name) {
if (node && node._flow) {
var flow = node._flow;
if (flow) {
return flow.getSetting(name);
}
}
return process.env[name];
}
/**
* Checks if a String contains any Environment Variable specifiers and returns
* it with their values substituted in place.
@@ -420,24 +437,28 @@ function setObjectProperty(msg,prop,value,createMissing) {
* For example, if the env var `WHO` is set to `Joe`, the string `Hello ${WHO}!`
* will return `Hello Joe!`.
* @param {String} value - the string to parse
* @param {Node} node - the node evaluating the property
* @return {String} The parsed string
* @memberof @node-red/util_util
* @memberof @node-red/util_util
*/
function evaluateEnvProperty(value) {
function evaluateEnvProperty(value, node) {
var result;
if (/^\${[^}]+}$/.test(value)) {
// ${ENV_VAR}
value = value.substring(2,value.length-1);
value = process.env.hasOwnProperty(value)?process.env[value]:""
var name = value.substring(2,value.length-1);
result = getSetting(node, name);
} else if (!/\${\S+}/.test(value)) {
// ENV_VAR
value = process.env.hasOwnProperty(value)?process.env[value]:""
result = getSetting(node, value);
} else {
// FOO${ENV_VAR}BAR
value = value.replace(/\${([^}]+)}/g, function(match, v) {
return process.env.hasOwnProperty(v)?process.env[v]:""
return value.replace(/\${([^}]+)}/g, function(match, name) {
var val = getSetting(node, name);
return (val === undefined)?"":val;
});
}
return value;
return (result === undefined)?"":result;
}
@@ -513,7 +534,7 @@ function evaluateNodeProperty(value, type, node, msg, callback) {
var expr = prepareJSONataExpression(value,node);
result = evaluateJSONataExpression(expr,msg);
} else if (type === 'env') {
result = evaluateEnvProperty(value);
result = evaluateEnvProperty(value, node);
}
if (callback) {
callback(null,result);
@@ -540,8 +561,9 @@ function prepareJSONataExpression(value,node) {
expr.assign('globalContext',function(val) {
return node.context().global.get(val);
});
expr.assign('env', function(val) {
return process.env[val];
expr.assign('env', function(name) {
var val = getSetting(node, name);
return (val ? val : "");
})
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
],
"dependencies": {
"clone": "2.1.2",
"i18next": "13.1.0",
"i18next": "14.1.1",
"json-stringify-safe": "5.0.1",
"jsonata": "1.6.4",
"when": "3.7.8"

View File

@@ -39,6 +39,9 @@ function checkVersion(userSettings) {
* This module provides the full Node-RED application, with both the runtime
* and editor components built in.
*
* The API this module exposes allows it to be embedded within another node.js
* application.
*
* @namespace node-red
*/
module.exports = {
@@ -125,7 +128,11 @@ module.exports = {
/**
* This provides access to the internal nodes module of the
* runtime.
* runtime. The details of this API remain undocumented as they should not
* be used directly.
*
* Most administrative actions should be performed use the runtime api
* under [node-red.runtime]{@link node-red.runtime}.
*
* @memberof node-red
*/

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "0.20.0-beta.4",
"version": "0.20.2",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -31,15 +31,15 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "0.20.0-beta.4",
"@node-red/runtime": "0.20.0-beta.4",
"@node-red/util": "0.20.0-beta.4",
"@node-red/nodes": "0.20.0-beta.4",
"@node-red/editor-api": "0.20.2",
"@node-red/runtime": "0.20.2",
"@node-red/util": "0.20.2",
"@node-red/nodes": "0.20.2",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.16.4",
"fs-extra": "7.0.1",
"node-red-node-email": "1.0.*",
"node-red-node-email": "1.*",
"node-red-node-feedparser": "^0.1.14",
"node-red-node-rbe": "0.2.*",
"node-red-node-sentiment": "^0.1.0",

View File

@@ -79,11 +79,12 @@ module.exports = {
// lost.
//credentialSecret: "a-secret-key",
// By default, all user data is stored in the Node-RED install directory. To
// use a different location, the following property can be used
// By default, all user data is stored in a directory called `.node-red` under
// the user's home directory. To use a different location, the following
// property can be used
//userDir: '/home/nol/.node-red/',
// Node-RED scans the `nodes` directory in the install directory to find nodes.
// Node-RED scans the `nodes` directory in the userDir to find local node files.
// The following property can be used to specify an additional directory to scan.
//nodesDir: '/home/nol/.node-red/nodes',
@@ -205,18 +206,27 @@ module.exports = {
// // - reason: if result is false, the HTTP reason string to return
//},
// Anything in this hash is globally available to all functions.
// It is accessed as context.global.
// eg:
// The following property can be used to seed Global Context with predefined
// values. This allows extra node modules to be made available with the
// Function node.
// For example,
// functionGlobalContext: { os:require('os') }
// can be accessed in a function block as:
// context.global.os
// global.get("os")
functionGlobalContext: {
// os:require('os'),
// jfive:require("johnny-five"),
// j5board:require("johnny-five").Board({repl:false})
},
// `global.keys()` returns a list of all properties set in global context.
// This allows them to be displayed in the Context Sidebar within the editor.
// In some circumstances it is not desirable to expose them to the editor. The
// following property can be used to hide any property set in `functionGlobalContext`
// from being list by `global.keys()`.
// By default, the property is set to false to avoid accidental exposure of
// their values. Setting this to true will cause the keys to be listed.
exportGlobalContextKeys: false,
// Context Storage
// The following property can be used to enable context storage. The configuration

View File

@@ -663,7 +663,7 @@ describe('trigger node', function() {
});
it('should be able to extend the delay (but with no 2nd output)', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:"100", wires:[["n2"]] },
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:"200", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
@@ -677,20 +677,25 @@ describe('trigger node', function() {
}
else {
msg.should.have.a.property("payload", "World");
(Date.now() - ss).should.be.greaterThan(149);
(Date.now() - ss).should.be.greaterThan(300);
done();
}
} catch(err) {
console.log(err);
done(err);
}
catch(err) { done(err); }
});
var ss = Date.now();
n1.emit("input", {payload:"Hello"});
setTimeout( function() {
n1.emit("input", {payload:"Error"});
},30);
},50);
setTimeout( function() {
n1.emit("input", {payload:"Error"});
},100);
setTimeout( function() {
n1.emit("input", {payload:"World"});
},150);
},330);
});
});

View File

@@ -28,6 +28,7 @@ var httpProxyNode = require("nr-test-utils").require("@node-red/nodes/core/io/06
var hashSum = require("hash-sum");
var httpProxy = require('http-proxy');
var cookieParser = require('cookie-parser');
var multer = require("multer");
var RED = require("nr-test-utils").require("node-red/lib/red");
var fs = require('fs-extra');
var auth = require('basic-auth');
@@ -118,9 +119,30 @@ describe('HTTP Request Node', function() {
}
before(function(done) {
testApp = express();
// The fileupload test needs a different set of middleware - so mount
// as a separate express instance
var fileUploadApp = express();
var mp = multer({ storage: multer.memoryStorage() }).any();
fileUploadApp.post("/file-upload",function(req,res,next) {
mp(req,res,function(err) {
req._body = true;
next(err);
})
},bodyParser.json(),function(req,res) {
res.json({
body: req.body,
files: req.files
})
});
testApp.use(fileUploadApp);
testApp.use(bodyParser.raw({type:"*/*"}));
testApp.use(cookieParser());
testApp.use(cookieParser(undefined,{decode:String}));
testApp.get('/statusCode204', function(req,res) { res.status(204).end();});
testApp.get('/text', function(req, res){ res.send('hello'); });
testApp.get('/redirectToText', function(req, res){ res.status(302).set('Location', getTestURL('/text')).end(); });
@@ -138,8 +160,7 @@ describe('HTTP Request Node', function() {
}, 50);
});
testApp.get('/checkCookie', function(req, res){
var value = req.cookies.data;
res.send(value);
res.send(req.cookies);
});
testApp.get('/setCookie', function(req, res){
res.cookie('data','hello');
@@ -219,6 +240,12 @@ describe('HTTP Request Node', function() {
res.cookie('redirectReturn','return1');
res.status(200).end();
});
testApp.get('/getQueryParams', function(req,res) {
res.json({
query:req.query,
url: req.originalUrl
});
})
startServer(function(err) {
if (err) {
done(err);
@@ -237,7 +264,6 @@ describe('HTTP Request Node', function() {
});
});
beforeEach(function() {
preEnvHttpProxyLowerCase = process.env.http_proxy;
preEnvHttpProxyUpperCase = process.env.HTTP_PROXY;
@@ -971,7 +997,31 @@ describe('HTTP Request Node', function() {
});
});
it('should append query params to url - obj', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",paytoqs:true,ret:"obj",url:getTestURL('/getQueryParams')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload',{
query:{a:'1',b:'2',c:'3'},
url: '/getQueryParams?a=1&b=2&c=3'
});
msg.should.have.property('statusCode',200);
msg.should.have.property('headers');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:{a:1,b:2,c:3}});
});
});
});
describe('HTTP header', function() {
it('should receive cookie', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/setCookie')},
@@ -1001,7 +1051,7 @@ describe('HTTP Request Node', function() {
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload','abc');
msg.payload.should.have.property('data','abc');
msg.should.have.property('statusCode',200);
done();
} catch(err) {
@@ -1012,6 +1062,26 @@ describe('HTTP Request Node', function() {
});
});
it('should send multiple cookies with string', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.payload.should.have.property('data','abc');
msg.payload.should.have.property('foo','bar');
msg.should.have.property('statusCode',200);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", cookies:{data:'abc',foo:'bar'}});
});
});
it('should send cookie with object data', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
@@ -1020,7 +1090,7 @@ describe('HTTP Request Node', function() {
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload','abc');
msg.payload.should.have.property('data','abc');
msg.should.have.property('statusCode',200);
done();
} catch(err) {
@@ -1031,6 +1101,86 @@ describe('HTTP Request Node', function() {
});
});
it('should send multiple cookies with object data', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.payload.should.have.property('data','abc');
msg.payload.should.have.property('foo','bar');
msg.should.have.property('statusCode',200);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", cookies:{data:{value:'abc'},foo:{value:'bar'}}});
});
});
it('should encode cookie value', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var value = ';,/?:@ &=+$#';
n2.on("input", function(msg) {
try {
msg.payload.should.have.property('data',encodeURIComponent(value));
msg.should.have.property('statusCode',200);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", cookies:{data:value}});
});
});
it('should encode cookie object', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var value = ';,/?:@ &=+$#';
n2.on("input", function(msg) {
try {
msg.payload.should.have.property('data',encodeURIComponent(value));
msg.should.have.property('statusCode',200);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", cookies:{data:{value:value, encode:true}}});
});
});
it('should not encode cookie when encode option is false', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var value = '!#$%&\'()*+-./:<>?@[]^_`{|}~';
n2.on("input", function(msg) {
try {
msg.payload.should.have.property('data',value);
msg.should.have.property('statusCode',200);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", cookies:{data:{value:value, encode:false}}});
});
});
it('should send cookie by msg.headers', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
@@ -1039,7 +1189,7 @@ describe('HTTP Request Node', function() {
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload','abc');
msg.payload.should.have.property('data','abc');
msg.should.have.property('statusCode',200);
done();
} catch(err) {
@@ -1050,6 +1200,26 @@ describe('HTTP Request Node', function() {
});
});
it('should send multiple cookies by msg.headers', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.payload.should.have.property('data','abc');
msg.payload.should.have.property('foo','bar');
msg.should.have.property('statusCode',200);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", cookies:{boo:'123'}, headers:{'cookie':'data=abc; foo=bar;'}});
});
});
it('should convert all HTTP headers into lower case', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')},
{id:"n2", type:"helper"}];
@@ -1497,6 +1667,44 @@ describe('HTTP Request Node', function() {
});
});
describe('file-upload', function() {
it('should upload a file', function(done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'POST',ret:'obj',url:getTestURL('/file-upload')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.payload.should.have.property("body",{"other":"123"});
msg.payload.should.have.property("files");
msg.payload.files.should.have.length(1);
msg.payload.files[0].should.have.property('fieldname','file');
msg.payload.files[0].should.have.property('originalname','file.txt');
msg.payload.files[0].should.have.property('buffer',{"type":"Buffer","data":[72,101,108,108,111,32,87,111,114,108,100]});
done();
} catch(err) {
done(err);
}
});
n1.receive({
headers: {
'content-type':'multipart/form-data'
},
payload: {
file: {
value: Buffer.from("Hello World"),
options: {
filename: "file.txt"
}
},
other: 123
}
});
});
})
})
describe('redirect-cookie', function() {
it('should send cookies to the same domain when redirected(no cookies)', function(done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:getTestURL('/redirectToSameDomain')},

View File

@@ -531,8 +531,8 @@ describe('JOIN node', function() {
msg.payload.should.have.property("c",true);
msg.payload.should.have.property("d");
msg.payload.d.should.have.property("e",7);
msg.payload.should.have.property("g");
msg.payload.g.should.have.property("f",6);
// msg.payload.should.have.property("g");
// msg.payload.g.should.have.property("f",6);
done();
}
catch(e) { done(e)}

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