Compare commits

..

113 Commits

Author SHA1 Message Date
Nick O'Leary
742aa2fa0d Merge branch 'dev' into make-split/join-more-flexible 2024-03-07 15:40:56 +00:00
Nick O'Leary
ce133c1c04 Merge pull request #4540 from Steve-Mcl/3934-csv-rfc4180
Add RFC4180 compliant mode to CSV node
2024-03-07 15:39:25 +00:00
Nick O'Leary
e4dc1779c3 Merge pull request #4590 from node-red/jsonata-update
Upgrade to JSONata 2.x
2024-03-07 14:28:35 +00:00
Nick O'Leary
22b4ab6bb2 Merge pull request #4468 from node-red/timestamp-formatting
Timestamp formatting
2024-03-07 14:28:23 +00:00
Nick O'Leary
2dcff51125 Update timestamp formats 2024-03-04 16:51:06 +00:00
Nick O'Leary
b50e0533eb Add js date option to inject 2024-03-04 16:35:59 +00:00
Nick O'Leary
711545539f Allow typedInput timestamp to specify format 2024-03-04 16:35:54 +00:00
Nick O'Leary
a6cbceed28 Upgrade to JSONata 2.x 2024-03-04 16:32:31 +00:00
Nick O'Leary
6802539ccc Merge pull request #4571 from node-red/bump-node-version
Bump minimum version to node 18
2024-02-20 10:37:25 +00:00
Nick O'Leary
a5223709ba Bump minimum version to node 18 2024-02-19 16:38:06 +00:00
Nick O'Leary
2291dc6132 Merge branch 'master' into dev 2024-02-19 16:14:58 +00:00
Steve-Mcl
b2548c158d fix layout and missing tip 2024-02-09 20:50:24 +00:00
Steve-Mcl
5a48d6d4cd Merge branch 'dev' into 3934-csv-rfc4180 2024-02-09 20:33:49 +00:00
Nick O'Leary
b10ef4c98c Merge pull request #4564 from node-red/rel315
Bump for 3.1.5 release
2024-02-08 15:37:48 +00:00
Nick O'Leary
3ff038fb98 Bump for 3.1.5 release 2024-02-08 15:32:53 +00:00
Nick O'Leary
adb498af24 Merge pull request #4562 from node-red/fix-require
Fix require of dns module
2024-02-07 15:24:10 +00:00
Nick O'Leary
fc67a2efc2 Merge pull request #4561 from node-red/4560-fix-global-env-cred
Ensure global creds object is initialised when adding first cred
2024-02-07 14:52:17 +00:00
Nick O'Leary
55771c7241 Fix require of dns module 2024-02-07 14:50:46 +00:00
Nick O'Leary
109fa5f04e Ensure global creds object is initialised when adding first cred 2024-02-07 10:02:22 +00:00
Nick O'Leary
1f412f3d78 Merge pull request #4558 from node-red/rel314
Updates for 3.1.4 release
2024-02-06 16:58:29 +00:00
Nick O'Leary
2b69f52c92 Bump dependencies in packages 2024-02-06 16:54:05 +00:00
Nick O'Leary
6e90798f16 Updates for 3.1.4 release 2024-02-06 16:51:59 +00:00
Nick O'Leary
3994b404a1 Merge pull request #4477 from GogoVega/french-translation-v3.1.3-changes
Add French translation of v3.1.3 changes
2024-02-06 16:37:20 +00:00
Nick O'Leary
0b7e8ec323 Merge pull request #4495 from joebordes/joebordes/i18n_001
i18n(es-ES) Spanish Spain translation
2024-02-06 16:36:45 +00:00
Nick O'Leary
bb0b547d5a Merge pull request #4550 from node-red/4549-improve-import-confict-dialog
Improve feedback in import dialog to show conflicted nodes
2024-02-06 16:36:15 +00:00
Nick O'Leary
1e1acc7ad7 Merge pull request #4556 from node-red/4548-handle-replacement-config-unknown
Handle modified-nodes deploy after replacing unknown config node
2024-02-06 16:36:02 +00:00
Nick O'Leary
1419432b04 Merge branch 'master' into joebordes/i18n_001 2024-02-05 16:47:55 +00:00
Nick O'Leary
1a521d7e09 Merge branch 'master' into 4548-handle-replacement-config-unknown 2024-02-05 16:46:04 +00:00
Nick O'Leary
5858d2789a Add FR translation 2024-02-05 16:44:55 +00:00
Nick O'Leary
6a1e4fac5a Merge pull request #4552 from guidoffm/patch-1
Update editor.json fix typo in German translation
2024-02-05 16:42:24 +00:00
Nick O'Leary
bab6e57a59 Merge pull request #4539 from node-red/4536-handle-undefined-default-export
Handle undefined default export when importing module
2024-02-05 16:36:06 +00:00
Nick O'Leary
4ed53fb622 Merge pull request #4531 from GogoVega/i18n-languages-list
Do not translate the list of available languages
2024-02-05 16:35:45 +00:00
Nick O'Leary
e17775c435 Merge pull request #4554 from node-red/dependabot/github_actions/github-actions-894fc5cb1d
Bump the github-actions group with 1 update
2024-02-05 16:32:44 +00:00
Nick O'Leary
7ee2b93b10 Merge pull request #4553 from lgrkvst/dev
Allow RED.view.select to select links
2024-02-05 16:28:17 +00:00
Nick O'Leary
565c212779 Handle modified-nodes deploy after replacing unknown config node
Fixes #4548
2024-02-05 16:22:58 +00:00
dependabot[bot]
b54e9d8d55 Bump the github-actions group with 1 update
Bumps the github-actions group with 1 update: [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request).


Updates `peter-evans/create-pull-request` from 5 to 6
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v5...v6)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 07:29:17 +00:00
Christian Lagerkvist
cc611a7a02 Fixes #4551 2024-01-30 10:37:52 +01:00
Guido Mülller (Guido Mueller)
861c89a0cc Update editor.json fix typo in German translation 2024-01-30 10:35:54 +01:00
Nick O'Leary
4268a04a04 Improve feedback in import dialog to show conflicted nodes
Fixes #4549
2024-01-29 17:48:01 +00:00
Nick O'Leary
9bd7131914 Merge pull request #4544 from GogoVega/fix-creds-convert-node
(convertNode) Do not create the credentials object if there is nothing to export
2024-01-26 13:49:56 +00:00
Nick O'Leary
8485ca254f Merge pull request #4538 from node-red/4533-fix-subflow-instance-g-property-mapping
Ensure subflow instance node has g property set
2024-01-26 13:48:07 +00:00
Nick O'Leary
507f9b68eb Merge pull request #4546 from node-red/4545-importing-duplicate-subflow
Handle importing flow with existing subflow and instance node
2024-01-26 13:47:20 +00:00
Nick O'Leary
f7b726372f Handle importing flow with existing subflow and instance node
Fixes #4545
2024-01-25 17:26:52 +00:00
GogoVega
14811b5aec Do not create the credentials object if not exported 2024-01-25 15:35:40 +01:00
Steve-Mcl
1a9c34fe40 Merge branch 'dev' into 3934-csv-rfc4180 2024-01-23 10:59:05 +00:00
Steve-Mcl
ff8eb0ec2b Add RFC spec mode to CSV node
closes #3934
2024-01-23 10:35:08 +00:00
Nick O'Leary
c24f05c2cd Handle undefined default export when importing module
Fixes #4536
2024-01-22 16:54:51 +00:00
Nick O'Leary
d2dc1fcc80 Ensure subflow instance node has g property set 2024-01-22 16:28:22 +00:00
Joe Bordes
97e05c8784 i18n(Editor) sync ES with #4531 to centralize list of languages 2024-01-20 20:36:47 +01:00
Joe Bordes
26cb03da42 i18n(Editor) sync ES with lastest changes 2024-01-20 20:36:01 +01:00
Joe Bordes
d5a8b1592c Merge branch 'master' into joebordes/i18n_001 2024-01-20 20:26:05 +01:00
Nick O'Leary
5b096bfd5e Merge pull request #4483 from gorenje/patch-1
Update index.mst
2024-01-19 17:13:09 +00:00
Nick O'Leary
a1e242ec1e Merge branch 'master' into patch-1 2024-01-19 16:14:20 +00:00
Nick O'Leary
5b9d002f56 Merge pull request #4529 from node-red/4397-hightlight-config-node-errors
Highlight errors in config node sidebar
2024-01-19 16:08:17 +00:00
GogoVega
dd57323889 Do not translate the list of available languages 2024-01-19 10:00:32 +01:00
Nick O'Leary
6620679008 Show standard validation triangle on config nodes 2024-01-16 17:35:50 +00:00
Nick O'Leary
f93654f680 Merge pull request #4527 from node-red/4485-copy-context-path
Include top level property name when copying path from context
2024-01-16 11:59:50 +00:00
Nick O'Leary
1bef0c32a2 Merge pull request #4519 from node-red/4479-ensure-env-not-modified
Clone objects types when getting env values
2024-01-16 11:59:37 +00:00
Nick O'Leary
f66b48e586 Merge pull request #4525 from node-red/fix-change-node-boolean-response
Fix change node to return boolean if asked
2024-01-16 11:58:30 +00:00
Nick O'Leary
a5725c59fd Merge pull request #4526 from node-red/4508
Ensure global-config credential env vars are merged on deploy
2024-01-16 11:58:20 +00:00
Nick O'Leary
4168bbb751 Merge pull request #4528 from node-red/4497-fix-config-footer-css
Modify node users info in config editor footer
2024-01-16 11:58:07 +00:00
Nick O'Leary
b0086edcf9 Update packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss
Co-authored-by: Mauricio Bonani <bonanitech@gmail.com>
2024-01-15 20:16:18 +00:00
Nick O'Leary
89c2efe17d Highlight errors in config node sidebar
Fixes #4397
2024-01-15 17:49:17 +00:00
Nick O'Leary
9030b7d27c Merge pull request #4491 from ralphwetzel/master_fix_icon_resize
Fix icon scaling for non .svg icons
2024-01-15 17:20:14 +00:00
Nick O'Leary
de5111b13f Merge pull request #4522 from gorenje/remove_unused_code
21-httprequest.js remove unused code, because of broken use of toLowercase
2024-01-15 17:17:43 +00:00
Nick O'Leary
8600f4131e Modify node users info in config editor footer
Fixes #4497
2024-01-15 17:11:54 +00:00
Nick O'Leary
58e2fcbeee Ensure global-config credential env vars are merged on deploy
Fixes #4508
2024-01-15 16:44:43 +00:00
Nick O'Leary
931a2344b4 Merge pull request #4480 from node-red/context-auto-complete
Add auto-complete to flow/global typedInput types
2024-01-15 15:53:46 +00:00
Dave Conway-Jones
dd3c75d298 Fix change node to return boolean if asked
to fix #4372
2024-01-14 12:56:25 +00:00
Gerrit Riessen
962fc5990e Merge remote-tracking branch 'nodered/master' into remove_unused_code 2024-01-11 12:04:02 +01:00
Gerrit Riessen
eb2f57fc0d removed unused code 2024-01-11 12:03:28 +01:00
Nick O'Leary
4a4a15de93 Fix context store handling in autocomplete 2024-01-09 01:05:09 +00:00
Nick O'Leary
a007ab7f2e Merge pull request #4492 from node-red/envvar-auto-complete
Add auto-complete for env vars
2024-01-08 23:41:12 +00:00
Nick O'Leary
3a6b1e86dc Clone objects types when getting env values
Fixes #4479
2024-01-08 20:56:17 +00:00
Nick O'Leary
54e6d60fe5 Add simple caching of env var lookup 2024-01-05 21:07:20 +00:00
GogoVega
c3536fd7c7 Removes translation of interpolation keys 2024-01-01 15:02:48 +01:00
Joe Bordes
83279df0fa i18n(es-ES) node help screens 2023-12-27 18:28:54 +01:00
Joe Bordes
2550da9c6e i18n(es-ES) node help screens 2023-12-27 16:06:41 +01:00
Joe Bordes
041f00b811 i18n(es-ES) node help screens 2023-12-27 11:57:02 +01:00
Joe Bordes
21cd4aaeb6 i18n(es-ES) node help screens 2023-12-26 13:16:02 +01:00
Joe Bordes
70ce1e648d i18n(es-ES) messages and runtime. start working on node help screens 2023-12-26 11:42:23 +01:00
Joe Bordes
eab5a9772b i18n(es-ES) Spanish Spain translation 2023-12-24 20:50:40 +01:00
Nick O'Leary
c2710f4f6f Add auto-complete for env vars 2023-12-20 17:52:52 +00:00
Nick O'Leary
20187b51b1 Fix up cache scope 2023-12-20 16:51:34 +00:00
Nick O'Leary
4be6d57d98 Apply suggestions from code review
Co-authored-by: Gauthier Dandele <92022724+GogoVega@users.noreply.github.com>
2023-12-20 16:39:52 +00:00
Ralph Wetzel
c31e622160 Fix icon scaling for non .svg icons 2023-12-20 17:14:12 +01:00
Nick O'Leary
a77f8cc3e9 Clear context cache when closing edit dialog 2023-12-15 15:09:15 +00:00
Gerrit Riessen
0b0f1f8701 Update index.mst
Update two additional path specifications
2023-12-15 11:32:26 +01:00
Nick O'Leary
ea4c0cdbee Fix error when switching context types 2023-12-14 17:14:56 +00:00
Gerrit Riessen
b5e955bd5e Update index.mst
Avoid escaping slashes (`/`) in asset paths. 

Content is currently generated as:

```
<title>Node-RED</title>
<link rel="icon" type="image/png" href="favicon.ico">
<link rel="mask-icon" href="red&#x2F;images&#x2F;node-red-icon-black.svg" color="#8f0000">
<link rel="stylesheet" href="vendor/jquery/css/base/jquery-ui.min.css?v=">
<link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css?v=">
<link rel="stylesheet" href="red/style.min.css?v=">
<link rel="stylesheet" href="vendor/monaco/style.css?v=">
</head>
<body spellcheck="false">
<div id="red-ui-editor"></div>
<script src="vendor/vendor.js?v="></script>
<script src="vendor&#x2F;monaco&#x2F;monaco-bootstrap.js?v="></script>
<script src="red&#x2F;red.min.js?v="></script>
<script src="red&#x2F;main.min.js?v="></script>
```

It still works of course, so feel free to ignore this change.
2023-12-13 17:29:39 +01:00
Nick O'Leary
7197153fd5 Support bracket-notation in auto complete when needed 2023-12-11 21:18:44 +00:00
Nick O'Leary
b9c1dedab3 Add auto-complete to flow/global typedInput types 2023-12-11 17:55:02 +00:00
Nick O'Leary
918943816f Merge branch 'master' into dev 2023-12-08 10:27:04 +00:00
GogoVega
b4b5d296d9 French translation of v3.1.3 changes 2023-12-08 09:47:00 +01:00
Nick O'Leary
33cf34f7c7 Merge branch 'master' into dev 2023-12-04 15:58:45 +00:00
Nick O'Leary
5b5b06cc06 Merge pull request #4406 from node-red/tcp-request-node-reset-when-in-stay-connected-mode
Let msg.reset  reset Tcp request node connection when in stay connected mode
2023-11-07 17:42:17 +00:00
Dave Conway-Jones
f49f692ffa Better fix for TCP node reset
now handles reply out node,
and can specify which connection to reset.
2023-11-03 11:57:16 +00:00
Nick O'Leary
08c6ea94cb Merge pull request #4347 from ZJvandeWeg/zj-remove-production-flag-npm
npm: Remove production flag on npm invocation
2023-11-01 14:18:14 +01:00
Nick O'Leary
fea1da5542 Merge pull request #4402 from node-red/Let-debug-status-length-be-settable
Let debug node status msg length be settable via settings
2023-11-01 14:13:38 +01:00
Dave Conway-Jones
32e8f4eac6 Add help info 2023-11-01 12:33:57 +00:00
Dave Conway-Jones
bfe5a8a986 Update 31-tcpin.js
don't send if payload not defined.
2023-11-01 12:27:11 +00:00
Dave Conway-Jones
f2cb5ea44e Allow msg.reset to reset connection when tcp request in stay connected mode 2023-11-01 12:07:50 +00:00
Dave Conway-Jones
c7335ed25b Let debug node status msg length be settable via settings 2023-10-31 09:11:17 +00:00
Dave Conway-Jones
5fda57c730 too enthusiastic clearing up property
reverted
2023-10-18 13:51:40 +01:00
Dave Conway-Jones
9fd929ac1e let split node specify property to split on
and let join auto join the correct property
or manually the specified one.
2023-10-17 21:12:13 +01:00
Nick O'Leary
eb940d6d57 Merge pull request #4367 from hlovdal/timer_testing_fix
Timer testing fix
2023-09-25 18:19:30 +01:00
Håkon Løvdal
9091935d77 Update variable names 2023-09-25 18:53:11 +02:00
Håkon Løvdal
34e8d2b051 Add workaround for timers triggering too early in test 2023-09-24 18:16:59 +02:00
Håkon Løvdal
0c2ab13c48 Print all delta values in case of error, not just the last value
Which might not even be the one triggering the error condition.
2023-09-24 18:16:59 +02:00
Håkon Løvdal
9489953a8f Introduce timeout constant 2023-09-24 18:16:59 +02:00
ZJ van de Weg
54d4079457 npm: Remove production flag on npm invocation
When installing packages the `--production` flag used to be added to the
arguments that `npm` received. As npm wants developers to use the
`--omit=dev` flag instead it warned users on STDERR. Standard error was
captured by Node-RED and output to the logs as being an error. This
caught users off-guard and they expected something to have gone
wrong.

With this change the `--omit=dev` is used instead, to remove the
warning.

This change works for NPM of version 8 and beyond[1], included in
Node.JS 16. This change will not work on NPM version 6[2] which is included
in Node.JS 14[3].

[1]: https://docs.npmjs.com/cli/v8/commands/npm-install#omit
[2]: https://docs.npmjs.com/cli/v6/commands/npm-install
[3]: https://nodejs.org/en/download/releases#looking-for-latest-release-of-a-version-branch
2023-09-17 08:43:11 +02:00
Nick O'Leary
cef3a01042 Merge pull request #4322 from node-red/prep4
Bump to 4.0.0-dev
2023-09-06 15:04:41 +01:00
Nick O'Leary
0c042abcab Bump to 4.0.0-dev 2023-09-06 14:45:45 +01:00
103 changed files with 7951 additions and 781 deletions

View File

@@ -32,7 +32,7 @@ jobs:
node-version: '16'
- run: node ./node-red/.github/scripts/update-node-red-docker.js
- name: Create Docker Pull Request
uses: peter-evans/create-pull-request@v5
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com>
@@ -48,7 +48,7 @@ jobs:
This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary
- run: node ./node-red/.github/scripts/update-node-red-website.js
- name: Create Website Pull Request
uses: peter-evans/create-pull-request@v5
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.NR_REPO_TOKEN }}
committer: GitHub <noreply@github.com>

View File

@@ -12,12 +12,11 @@ permissions:
jobs:
build:
permissions:
checks: write # for coverallsapp/github-action to create new checks
contents: read # for actions/checkout to fetch code
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
node-version: [18, 20]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
@@ -29,8 +28,3 @@ jobs:
- name: Run tests
run: |
npm run test
# - name: Publish to coveralls.io
# if: ${{ matrix.node-version == 16 }}
# uses: coverallsapp/github-action@v1.1.2
# with:
# github-token: ${{ github.token }}

View File

@@ -1,3 +1,49 @@
#### 3.1.5: Maintenance Release
Runtime
- Fix require of dns module (#4562) @knolleary
- Ensure global creds object is initialised when adding first cred (#4561) @knolleary
#### 3.1.4: Maintenance Release
Editor
- Highlight errors in config node sidebar (#4529) @knolleary
- Improve feedback in import dialog to show conflicted nodes (#4550) @knolleary
- Modify node users info in config editor footer (#4528) @knolleary
- Handle modified-nodes deploy after replacing unknown config node (#4556) @knolleary
- Handle undefined default export when importing module (#4539) @knolleary
- Fix icon scaling for non .svg icons (#4491) @ralphwetzel
- (convertNode) Do not create the credentials object if there is nothing to export (#4544) @GogoVega
- Ensure subflow instance node has g property set (#4538) @knolleary
- Handle importing flow with existing subflow and instance node (#4546) @knolleary
- Update index.mst (#4483) @gorenje
- Include top level property name when copying path from context (#4527) @knolleary
- Add handling to disable items on context menu (#4500) @kazuhitoyokoi
- Focus Quick Add dialog from context menu (#4516) @kazuhitoyokoi
- Fix subflow ports in Quick Add dialog (#4518) @kazuhitoyokoi
- Fix location of subflow ports in palette (#4502) @kazuhitoyokoi
- Client/Editor Events: fix off-in-on pattern emulating once (#4484) @gorenje
- Restore caching busting functionality without using explict version number (#4512) @knolleary
- Do not translate the list of available languages (#4531) @GogoVega
- Add French translation of v3.1.3 changes (#4477) @GogoVega
- i18n(es-ES) Spanish Spain translation (#4495) @joebordes
- Add missing validation messages (#4487) @GogoVega
- Add Japanese translations for v3.1.3 (#4498) @kazuhitoyokoi
- Replace `rename` by `edit` for the menu flow label (#4506) @GogoVega
- Update editor.json fix typo in German translation (#4552) @guidoffm
Runtime
- Bump the github-actions group with 1 update (#4554) @app/dependabot
- Clone objects types when getting env values (#4519) @knolleary
- Ensure global-config credential env vars are merged on deploy (#4526) @knolleary
Nodes
- 21-httprequest.js remove unused code, because of broken use of toLowercase (#4522) @gorenje
#### 3.1.3: Maintenance Release
Editor

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "3.1.3",
"version": "4.0.0-dev",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@@ -54,7 +54,7 @@
"is-utf8": "0.2.1",
"js-yaml": "4.1.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.6",
"jsonata": "2.0.4",
"lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0",
"memorystore": "1.6.7",
@@ -64,7 +64,7 @@
"mqtt": "4.3.7",
"multer": "1.4.5-lts.1",
"mustache": "4.2.0",
"node-red-admin": "^3.1.1",
"node-red-admin": "^3.1.2",
"node-watch": "0.7.4",
"nopt": "5.0.0",
"oauth2orize": "1.11.1",
@@ -122,6 +122,6 @@
"supertest": "6.3.3"
},
"engines": {
"node": ">=14"
"node": ">=18"
}
}

View File

@@ -33,6 +33,9 @@ module.exports = {
store: req.query['store'],
req: apiUtils.getRequestLogObject(req)
}
if (req.query['keysOnly'] !== undefined) {
opts.keysOnly = true
}
runtimeAPI.context.getValue(opts).then(function(result) {
res.json(result);
}).catch(function(err) {

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "3.1.3",
"version": "4.0.0-dev",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "3.1.3",
"@node-red/editor-client": "3.1.3",
"@node-red/util": "4.0.0-dev",
"@node-red/editor-client": "4.0.0-dev",
"bcryptjs": "2.4.3",
"body-parser": "1.20.2",
"clone": "2.1.2",

View File

@@ -1075,7 +1075,7 @@
"git-auth-error": "Git-Authentifizierungsfehler"
},
"create-success": {
"success": "Sie haben Ihr erstes Projekt erfolgreich erstduellt!",
"success": "Sie haben Ihr erstes Projekt erfolgreich erstellt!",
"desc0": "Sie können jetzt Node-RED wie bisher verwenden.",
"desc1": "Im Tab 'Info' in der Seitenleiste wird angezeigt, welches das aktuelle Projekt ist. Über die Schaltfläche rechts neben dem Projektnamen gelangt man zu 'Projekteinstellungen'.",
"desc2": "Im Tab 'Commit-Historie' in der Seitenleiste werden alle Dateien angezeigt, die sich in Ihrem Projekt geändert haben, und um sie ins lokale Repository zu übertragen (commit). Es zeigt Ihnen eine vollständige Historie Ihrer Commits an und ermöglicht es Ihnen, Ihre Commits in ein (remote) Server-Repository zu schieben (push)."
@@ -1171,17 +1171,6 @@
"diagnostics": {
"title": "System-Informationen"
},
"languages": {
"de": "Deutsch",
"en-US": "Englisch",
"fr": "Französisch",
"ja": "Japanisch",
"ko": "Koreanisch",
"pt-BR":"Portugiesisch",
"ru": "Russisch",
"zh-CN": "Chinesisch (Vereinfacht)",
"zh-TW": "Chinesisch (Traditionell)"
},
"validator": {
"errors": {
"invalid-json": "Ungültige JSON-Daten: __error__",

View File

@@ -303,7 +303,8 @@
"missingType": "Input not a valid flow - item __index__ missing 'type' property"
},
"conflictNotification1": "Some of the nodes you are importing already exist in your workspace.",
"conflictNotification2": "Select which nodes to import and whether to replace the existing nodes, or to import a copy of them."
"conflictNotification2": "Select which nodes to import and whether to replace the existing nodes, or to import a copy of them.",
"alreadyExists": "This node already exists"
},
"copyMessagePath": "Path copied",
"copyMessageValue": "Value copied",
@@ -707,7 +708,7 @@
"triggerAction": "Trigger action",
"find": "Find in workspace",
"copyItemUrl": "Copy item url",
"copyURL2Clipboard": "Copied url to clipboard",
"copyURL2Clipboard": "Copied url to clipboard",
"showFlow": "Show",
"hideFlow": "Hide"
},
@@ -924,6 +925,12 @@
"jsonata": "expression",
"env": "env variable",
"cred": "credential"
},
"date": {
"format": {
"timestamp": "milliseconds since epoch",
"object": "JavaScript Date Object"
}
}
},
"editableList": {
@@ -1206,15 +1213,16 @@
"title": "System Info"
},
"languages": {
"de": "German",
"de": "Deutsch",
"en-US": "English",
"fr": "French",
"ja": "Japanese",
"es-ES": "Español (España)",
"fr": "Français",
"ja": "日本語",
"ko": "Korean",
"pt-BR":"Portuguese",
"ru": "Russian",
"zh-CN": "Chinese(Simplified)",
"zh-TW": "Chinese(Traditional)"
"pt-BR": "Português (Brasil)",
"ru": "Русский",
"zh-CN": "简体中文",
"zh-TW": "繁體中文"
},
"validator": {
"errors": {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
{
"info": {
"tip0": "Puedes eliminar los nodos o enlaces seleccionados con {{core:delete-selection}}",
"tip1": "Busca nodos con {{core:search}}",
"tip2": "{{core:toggle-sidebar}} alternará la vista de esta barra lateral",
"tip3": "Puedes gestionar tu paleta de nodos con {{core:manage-palette}}",
"tip4": "Tus nodos de configuración de flujo aparecen en el panel de la barra lateral. Se puede acceder desde el menú o con {{core:show-config-tab}}",
"tip5": "Activa o desactiva estos consejos desde la opción en la configuración",
"tip6": "Mueve los nodos seleccionados usando las teclas [izquierda] [arriba] [abajo] y [derecha]. Mantén pulsada [Mayús] para desplazarlos más",
"tip7": "Arrastrar un nodo a un cable lo insertará en el enlace",
"tip8": "Exporta los nodos seleccionados, o la pestaña actual con {{core:show-export-dialog}}",
"tip9": "Importa un flujo arrastrando su JSON al editor, o con {{core:show-import-dialog}}",
"tip10": "[shift][clic] y arrastrar en un puerto de nodo para mover todos los cables conectados o sólo el seleccionado",
"tip11": "Mostrar la pestaña Información con {{core:show-info-tab}} o la pestaña Depuración con {{core:show-debug-tab}}",
"tip12": "[ctrl] [clic] en el área de trabajo para abrir el diálogo de adición rápida",
"tip13": "Mantén pulsada [ctrl] cuando [haces clic] en un puerto de nodo para habilitar el enlazado rápido",
"tip14": "Mantén pulsada [shift] cuando [haces clic] en un nodo para seleccionar también todos sus nodos conectados",
"tip15": "Mantén pulsada [ctrl] cuando [haces clic] en un nodo para añadirlo o eliminarlo de la selección actual",
"tip16": "Cambia de pestaña de flujo con {{core:show-previous-tab}} y {{core:show-next-tab}}",
"tip17": "Puedes confirmar tus cambios en la bandeja de edición de nodos con {{core:confirm-edit-tray}} o cancelarlos con {{core:cancel-edit-tray}}",
"tip18": "Al pulsar {{core:edit-selected-node}} se editará el primer nodo de la selección actual"
}
}

View File

@@ -0,0 +1,278 @@
{
"$string": {
"args": "arg[, prettify]",
"desc": "Convierte el parámetro `arg` a una cadena usando las siguientes reglas de conversión:\n\n - Las cadenas no cambian\n - Las funciones se convierten en una cadena vacía\n - El infinito numérico y NaN arrojan un error porque no se pueden representar como un número JSON\n: todos los demás valores se convierten a una cadena JSON usando la función `JSON.stringify`. Si `prettify` es verdadero, entonces se produce JSON \"prettified\". es decir, una línea por campo y las líneas se indentarán según la profundidad del campo."
},
"$length": {
"args": "str",
"desc": "Devuelve el número de caracteres de la cadena `str`. Se genera un error si `str` no es una cadena."
},
"$substring": {
"args": "str, start[, length]",
"desc": "Devuelve una cadena que contiene los caracteres del primer parámetro `str` comenzando en la posición `start` (desplazamiento cero). Si se especifica 'longitud', la subcadena contendrá el máximo de caracteres de 'longitud'. Si 'inicio' es negativo, indica el número de caracteres desde el final de 'cadena'."
},
"$substringBefore": {
"args": "str, chars",
"desc": "Devuelve la subcadena antes de la primera aparición de la secuencia de caracteres `chars` en `str`. Si `str` no contiene `caracteres`, entonces devuelve `str`."
},
"$substringAfter": {
"args": "str, chars",
"desc": "Devuelve la subcadena después de la primera aparición de la secuencia de caracteres `chars` en `str`. Si `str` no contiene `caracteres`, entonces devuelve `str`."
},
"$uppercase": {
"args": "str",
"desc": "Devuelve una cadena con todos los caracteres de `str` convertidos a mayúsculas."
},
"$lowercase": {
"args": "str",
"desc": "Devuelve una cadena con todos los caracteres de `str` convertidos a minúsculas."
},
"$trim": {
"args": "str",
"desc": "Normaliza y recorta todos los caracteres de espacio en blanco en `str` aplicando los siguientes pasos:\n\n - Todas las tabulaciones, retornos de carro y avances de línea se reemplazan con espacios.\n- Las secuencias contiguas de espacios se reducen a un solo espacio.\n- Se eliminan los espacios iniciales y finales.\n\n Si no se especifica `str` (es decir, esta función se invoca sin argumentos), entonces el valor de contexto se utiliza como el valor de `str`. Se genera un error si `str` no es una cadena."
},
"$contains": {
"args": "str, pattern",
"desc": "Devuelve 'verdadero' si 'cadena' coincide con 'patrón', de lo contrario, devuelve 'falso'. Si no se especifica `str` (es decir, esta función se invoca con un argumento), entonces el valor del contexto se utiliza como valor de `str`. El parámetro `patrón` puede ser una cadena o una expresión regular."
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "Divide el parámetro `str` en una matriz de subcadenas. Es un error si `str` no es una cadena. El parámetro opcional `separador` especifica los caracteres dentro de la `cadena` sobre los cuales se debe dividir como una cadena o una expresión regular. Si no se especifica 'separador', se supone que la cadena está vacía y 'cadena' se dividirá en una matriz de caracteres individuales. Es un error si el 'separador' no es una cadena. El parámetro opcional 'límite' es un número que especifica el número máximo de subcadenas que se incluirán en la matriz resultante. Cualquier subcadena adicional se descarta. Si no se especifica `límite`, entonces `str` se divide completamente sin límite para el tamaño de la matriz resultante. Es un error si 'límite' no es un número positivo."
},
"$join": {
"args": "array[, separator]",
"desc": "Une una matriz de cadenas de componentes en una única cadena concatenada con cada cadena de componentes separada por el parámetro 'separador' opcional. Es un error si la 'matriz' de entrada contiene un elemento que no es una cadena. Si no se especifica 'separador', se supone que es una cadena vacía, es decir, que no hay 'separador' entre las cadenas componentes. Es un error si el 'separador' no es una cadena."
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "Aplica la cadena `str` a la expresión regular `pattern` y devuelve una matriz de objetos, cada objeto contiene información sobre cada aparición de una coincidencia dentro de `str`."
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "Encuentra apariciones de `patrón` dentro de `str` y las reemplaza con `reemplazo`.\n\nEl parámetro opcional `límite` es el número máximo de reemplazos."
},
"$now": {
"args": "$[picture [, timezone]]",
"desc": "Genera una marca de tiempo en formato compatible con ISO 8601 y la devuelve como una cadena. Si se proporcionan los parámetros opcionales `picture` y `zona horaria`, entonces la marca de tiempo actual se formatea como se describe en la función `$fromMillis()`"
},
"$base64encode": {
"args": "string",
"desc": "Convierte una cadena ASCII a una representación base 64. Cada carácter de la cadena se trata como un byte de datos binarios. Esto requiere que todos los caracteres de la cadena estén en el rango de 0x00 a 0xFF, que incluye todos los caracteres de las cadenas codificadas con URI. No se admiten caracteres Unicode fuera de ese rango."
},
"$base64decode": {
"args": "string",
"desc": "Convierte bytes codificados en base 64 en una cadena, utilizando una página de códigos Unicode UTF-8."
},
"$number": {
"args": "arg",
"desc": "Convierte el parámetro `arg` a un número usando las siguientes reglas de conversión:\n\n - Los números no cambian\n - Las cadenas que contienen una secuencia de caracteres que representan un número JSON legal se convierten a ese número\n - Todos los demás valores provocar que se arroje un error."
},
"$abs": {
"args": "number",
"desc": "Devuelve el valor absoluto del parámetro 'número'."
},
"$floor": {
"args": "number",
"desc": "Devuelve el valor de 'número' redondeado hacia abajo al entero más cercano que sea menor o igual a 'número'."
},
"$ceil": {
"args": "number",
"desc": "Devuelve el valor de 'número' redondeado al número entero más cercano que sea mayor o igual a 'número'."
},
"$round": {
"args": "number [, precision]",
"desc": "Devuelve el valor del parámetro 'número' redondeado al número de decimales especificado por el parámetro opcional 'precisión'."
},
"$power": {
"args": "base, exponent",
"desc": "Devuelve el valor de 'base' elevado a la potencia de 'exponente'."
},
"$sqrt": {
"args": "number",
"desc": "Devuelve la raíz cuadrada del valor del parámetro 'número'."
},
"$random": {
"args": "",
"desc": "Devuelve un número pseudoaleatorio mayor o igual a cero y menor que uno."
},
"$millis": {
"args": "",
"desc": "Devuelve el número de milisegundos desde la época Unix (1 de enero de 1970 UTC) como un número. Todas las invocaciones de `$millis()` dentro de una evaluación de una expresión devolverán el mismo valor."
},
"$sum": {
"args": "array",
"desc": "Devuelve la suma aritmética de una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
},
"$max": {
"args": "array",
"desc": "Devuelve el número máximo en una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
},
"$min": {
"args": "array",
"desc": "Devuelve el número mínimo en una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
},
"$average": {
"args": "array",
"desc": "Devuelve el valor medio de una 'matriz' de números. Es un error si la 'matriz' de entrada contiene un elemento que no es un número."
},
"$boolean": {
"args": "arg",
"desc": "Convierte el argumento a un booleano usando las siguientes reglas:\n\n - `Booleano`: sin cambios\n - `cadena`: vacía: `falso`\n - `cadena`: no vacía: `verdadero`\n - `número`: `0`: `falso`\n - `número`: distinto de cero: `verdadero`\n - `nulo`: `falso`\n - `matriz`: vacía: `falso`\n - `array`: contiene un miembro que se convierte en `true`: `true`\n - `array`: todos los miembros se convierten en `false`: `false`\n - `object`: vacío: `false`\n - `objeto`: no vacío: `verdadero`\n - `función`: `falso`"
},
"$not": {
"args": "arg",
"desc": "Devuelve booleano NEGADO del argumento. `arg` se convierte antes en un booleano"
},
"$exists": {
"args": "arg",
"desc": "Devuelve booleano 'verdadero' si la expresión 'arg' se evalúa como un valor, o 'falso' si la expresión no coincide con nada (por ejemplo, una ruta a una referencia de campo inexistente)."
},
"$count": {
"args": "array",
"desc": "Devuelve el número de elementos de la matriz."
},
"$append": {
"args": "array, array",
"desc": "Agrega dos matrices"
},
"$sort": {
"args": "array [, function]",
"desc": "Devuelve una matriz que contiene todos los valores en el parámetro `array`, pero ordenados.\n\nSi se proporciona una `función` de comparador, entonces debe ser una función que toma dos parámetros:\n\n`function(left , derecha)`\n\nEsta función es invocada por el algoritmo de clasificación para comparar dos valores `izquierda` y `derecha`. Si el valor de `izquierda` debe colocarse después del valor de `derecha` en el orden de clasificación deseado, entonces la función debe devolver un valor booleano 'verdadero' para indicar un intercambio. De lo contrario debe devolver 'falso'."
},
"$reverse": {
"args": "array",
"desc": "Devuelve una matriz que contiene todos los valores del parámetro `matriz`, pero en orden inverso."
},
"$shuffle": {
"args": "array",
"desc": "Devuelve una matriz que contiene todos los valores del parámetro `array`, pero mezclados en orden aleatorio."
},
"$zip": {
"args": "array, ...",
"desc": "Devuelve una matriz convolucionada (comprimida) que contiene matrices agrupadas de valores de los argumentos `matriz1`... `matrizN` del índice 0, 1, 2...."
},
"$keys": {
"args": "object",
"desc": "Devuelve una matriz que contiene las claves del objeto. Si el argumento es una matriz de objetos, entonces la matriz devuelta contiene una lista deduplicada de todas las claves de todos los objetos."
},
"$lookup": {
"args": "object, key",
"desc": "Devuelve el valor asociado con la clave en el objeto. Si el primer argumento es una matriz de objetos, entonces se buscan todos los objetos de la matriz y se devuelven los valores asociados con todas las apariciones de la clave."
},
"$spread": {
"args": "object",
"desc": "Divide un objeto que contiene pares clave/valor en una matriz de objetos, cada uno de los cuales tiene un único par clave/valor del objeto de entrada. Si el parámetro es una matriz de objetos, entonces la matriz resultante contiene un objeto para cada par clave/valor en cada objeto de la matriz proporcionada."
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "Fusiona una matriz de objetos en un único objeto que contiene todos los pares clave/valor de cada uno de los objetos en la matriz de entrada. Si alguno de los objetos de entrada contiene la misma clave, entonces el objeto devuelto contendrá el valor del último en la matriz. Es un error si la matriz de entrada contiene un elemento que no es un objeto."
},
"$sift": {
"args": "object, function",
"desc": "Devuelve un objeto que contiene solo los pares clave/valor del parámetro `objeto` que satisfacen el predicado `función` pasado como segundo parámetro.\n\nLa `función` que se proporciona como segundo parámetro debe tener la siguiente firma:\n\n`función(valor [, clave [, objeto]])`"
},
"$each": {
"args": "object, function",
"desc": "Devuelve una matriz que contiene los valores devueltos por la función cuando se aplica a cada par clave/valor en el objeto."
},
"$map": {
"args": "array, function",
"desc": "Devuelve una matriz que contiene los resultados de aplicar el parámetro `función` a cada valor en el parámetro `matriz`.\n\nLa `función` que se proporciona como segundo parámetro debe tener la siguiente firma:\n\n`función( valor [, índice [, matriz]])`"
},
"$filter": {
"args": "array, function",
"desc": "Devuelve una matriz que contiene solo los valores en el parámetro `matriz` que satisfacen el predicado `función`.\n\nLa `función` que se proporciona como segundo parámetro debe tener la siguiente firma:\n\n`función(valor [ , índice [, matriz]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "Devuelve un valor agregado derivado de aplicar el parámetro `función` sucesivamente a cada valor en `matriz` en combinación con el resultado de la aplicación anterior de la función.\n\nLa función debe aceptar dos argumentos y se comporta como un operador infijo entre cada valor dentro de la matriz. La firma de la `función` debe tener la forma: `myfunc($accumulator, $value[, $index[, $array]])`\n\nEl parámetro opcional `init` se utiliza como valor inicial en la agregación."
},
"$flowContext": {
"args": "string[, string]",
"desc": "Recupera una propiedad de contexto de flujo.\n\nEsta es una función definida por Node-RED."
},
"$globalContext": {
"args": "string[, string]",
"desc": "Recupera una propiedad de contexto global.\n\nEsta es una función definida por Node-RED."
},
"$pad": {
"args": "string, width [, char]",
"desc": "Devuelve una copia de la `cadena` con relleno adicional, si es necesario, de modo que su número total de caracteres sea al menos el valor absoluto del parámetro `ancho`.\n\nSi `ancho` es un número positivo, entonces la cadena está acolchado hacia la derecha; si es negativo, se rellena hacia la izquierda.\n\nEl argumento opcional `char` especifica los caracteres de relleno que se utilizarán. Si no se especifica, el valor predeterminado es el carácter de espacio."
},
"$fromMillis": {
"args": "number, [, picture [, timezone]]",
"desc": "Convierte el `número` que representa milisegundos desde la época Unix (1 de enero de 1970 UTC) en una representación de cadena formateada según la plantilla en picture.\n\nSi se omite el parámetro opcional `picture`, entonces la marca de tiempo es formateado en el formato ISO 8601.\n\nSi se proporciona la cadena opcional `picture`, entonces la marca de tiempo se formatea de acuerdo con la representación especificada en esa cadena. El comportamiento de esta función es consistente con la versión de dos argumentos de la función XPath/XQuery `format-dateTime` tal como se define en la especificación XPath F&O 3.1. El parámetro de plantilla define cómo se formatea la marca de tiempo y tiene la misma sintaxis que `format-dateTime`.\n\nSi se proporciona la cadena opcional `timezone`, entonces la marca de tiempo formateada estará en esa zona horaria. La cadena `timezone` debe tener el formato '±HHMM', donde ± es el signo más o menos y HHMM es el desplazamiento en horas y minutos desde UTC. Desplazamiento positivo para zonas horarias al este de UTC, desplazamiento negativo para zonas horarias al oeste de UTC."
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "Convierte el `número` en una cadena y lo formatea en una representación decimal según lo especificado en la cadena `picture`.\n\n El comportamiento de esta función es coherente con la función XPath/XQuery `fn:format-number` tal como se define en la especificación XPath F&O 3.1. El parámetro de cadena `picture` define cómo se formatea el número y tiene la misma sintaxis que `fn:formato-número`.\n\nEl tercer argumento opcional `opciones` se utiliza para anular los caracteres de formato específicos de la configuración regional predeterminada, como el decimal. separador. Si se proporciona, este argumento debe ser un objeto que contenga pares de nombre/valor especificados en la sección de formato decimal de la especificación XPath F&O 3.1."
},
"$formatBase": {
"args": "number [, radix]",
"desc": "Convierte el número en una cadena y lo formatea como un número entero representado en la base numérica especificada por el argumento `radix`. Si no se especifica `radix`, el valor predeterminado es la base 10. `radix` puede estar entre 2 y 36; de lo contrario, se genera un error."
},
"$toMillis": {
"args": "timestamp",
"desc": "Convierte una cadena de `marca de tiempo` en el formato ISO 8601 al número de milisegundos desde la época Unix (1 de enero de 1970 UTC) como un número. Se genera un error si la cadena no tiene el formato correcto."
},
"$env": {
"args": "arg",
"desc": "Devuelve el valor de una variable de entorno.\n\nEsta es una función definida por Node-RED."
},
"$eval": {
"args": "expr [, context]",
"desc": "Analiza y evalúa la cadena `expr` que contiene JSON literal o una expresión JSONata utilizando el contexto actual como contexto para la evaluación."
},
"$formatInteger": {
"args": "number, picture",
"desc": "Convierte el número en una cadena y lo formatea en una representación entera como lo especifica la cadena `picture`. El parámetro de define cómo se formatea el número y tiene la misma sintaxis que `fn:format-integer` de la especificación XPath F&O 3.1."
},
"$parseInteger": {
"args": "string, picture",
"desc": "Analiza el contenido del parámetro cadena en un número entero (como un número JSON) utilizando el formato especificado por la cadena `picture`. El parámetro tiene el mismo formato que `$formatInteger`."
},
"$error": {
"args": "[str]",
"desc": "Lanza un error con un mensaje. El parámetro `str` opcional reemplazará el mensaje predeterminado de `$error() función evaluada`"
},
"$assert": {
"args": "arg, str",
"desc": "Si `arg` es `verdadero`, la función devuelve indefinido. Si `arg` es `falso`, se lanza una excepción con `str` como mensaje de excepción."
},
"$single": {
"args": "array, function",
"desc": "Devuelve el único valor en el parámetro `array` que satisface el predicado de `función` (es decir, la `función` devuelve booleano `verdadero` cuando se pasa el valor). Lanza una excepción si el número de valores coincidentes no es exactamente uno.\n\nLa función debe proporcionarse con la siguiente firma: `función(valor [, índice [, matriz]])` donde el valor es cada entrada de la matriz. El índice es la posición de ese valor y toda la matriz se pasa como tercer argumento."
},
"$encodeUrlComponent": {
"args": "str",
"desc": "Codifica un componente de URL reemplazando cada instancia de ciertos caracteres por una, dos, tres o cuatro secuencias de escape que representan la codificación UTF-8 del carácter.\n\nEjemplo: `$encodeUrlComponent(\"?x=prueba\")` => `\"%3Fx%3Dprueba\"`"
},
"$encodeUrl": {
"args": "str",
"desc": "Codifica una URL reemplazando cada instancia de ciertos caracteres por una, dos, tres o cuatro secuencias de escape que representan la codificación UTF-8 del carácter.\n\nEjemplo: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "Decodifica un componente de URL creado previamente por encodeUrlComponent.\n\nEjemplo: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "Decodifica una URL creado previamente por encodeUrl.\n\nEjemplo: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "Devuelve una matriz con valores duplicados eliminados de `matriz`"
},
"$type": {
"args": "value",
"desc": "Devuelve el tipo de `valor` como una cadena. Si `valor` no está definido, esto devolverá indefinido."
},
"$moment": {
"args": "[str]",
"desc": "Obtiene un objeto de fecha usando la biblioteca Moment."
},
"$clone": {
"args": "value",
"desc": "Clona un objeto de forma segura."
}
}

View File

@@ -129,6 +129,11 @@
"editPalette": "Gérer la palette",
"other": "Autre",
"showTips": "Afficher les astuces",
"showNodeHelp": "Afficher l'aide du noeud",
"enableSelectedNodes": "Activer les noeuds sélectionnés",
"disableSelectedNodes": "Désactiver les noeuds sélectionnés",
"showSelectedNodeLabels": "Afficher les étiquettes des noeuds sélectionnés",
"hideSelectedNodeLabels": "Masquer les étiquettes des noeuds sélectionnés",
"showWelcomeTours": "Afficher les visites guidées pour les nouvelles versions",
"help": "Site web de Node-RED",
"projects": "Projets",
@@ -298,7 +303,8 @@
"missingType": "L'entrée n'est pas un flux valide - l'élément '__index__' n'a pas de propriété 'type'"
},
"conflictNotification1": "Certains des noeuds que vous avez importés existent déjà dans votre espace de travail.",
"conflictNotification2": "Sélectionnez les noeuds à importer et choisissez s'il faut remplacer les noeuds existants ou en importer une copie."
"conflictNotification2": "Sélectionnez les noeuds à importer et choisissez s'il faut remplacer les noeuds existants ou en importer une copie.",
"alreadyExists": "Ce noeud existe déjà"
},
"copyMessagePath": "Chemin copié",
"copyMessageValue": "Valeur copiée",
@@ -510,7 +516,7 @@
"selectAllConnected": "Sélectionner tous les éléments connectés",
"addRemoveNode": "Ajouter/supprimer un noeud de la sélection",
"editSelected": "Modifier le noeud sélectionné",
"deleteSelected": "Supprimer les noeuds ou le lien sélectionné(s)",
"deleteSelected": "Supprimer la sélection",
"deleteReconnect": "Supprimer et reconnecter",
"importNode": "Importer les noeuds",
"exportNode": "Exporter les noeuds",
@@ -828,7 +834,7 @@
"copyPublicKey": "Copier la clé publique dans le presse-papiers",
"delete": "Supprimer une clé",
"gitConfig": "Configuration Git",
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __nom__ ? Ça ne peut pas être annulé."
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer la clé SSH __name__ ? Ça ne peut pas être annulé."
},
"versionControl": {
"unstagedChanges": "Abandon des changements",
@@ -1200,17 +1206,6 @@
"diagnostics": {
"title": "Information système"
},
"languages": {
"de": "Allemand",
"en-US": "Anglais",
"fr": "Français",
"ja": "Japonais",
"ko": "Coréen",
"pt-BR": "Portugais brésilien",
"ru": "Russe",
"zh-CN": "Chinois (Simplifié)",
"zh-TW": "Chinois (Traditionnel)"
},
"validator": {
"errors": {
"invalid-json": "Données JSON invalides : __error__",
@@ -1227,6 +1222,7 @@
}
},
"contextMenu": {
"showActionList": "Afficher la liste des actions",
"insert": "Insérer",
"node": "Noeud",
"junction": "Jonction",

View File

@@ -1205,17 +1205,6 @@
"diagnostics": {
"title": "システム情報"
},
"languages": {
"de": "ドイツ語",
"en-US": "英語",
"fr": "フランス語",
"ja": "日本語",
"ko": "韓国語",
"pt-BR": "ポルトガル語",
"ru": "ロシア語",
"zh-CN": "中国語(簡体)",
"zh-TW": "中国語(繁体)"
},
"validator": {
"errors": {
"invalid-json": "JSONデータが不正: __error__",

View File

@@ -1172,16 +1172,6 @@
"diagnostics": {
"title": "informações do Sistema"
},
"languages": {
"de": "Alemão",
"en-US": "Inglês",
"ja": "Japonês",
"ko": "Coreano",
"pt-BR": "Português(Brasil)",
"ru": "Russo",
"zh-CN": "Chinês(Simplificado)",
"zh-TW": "Chinês(Tradicional)"
},
"validator": {
"errors": {
"invalid-json": "Dados JSON inválidos: __error__",

View File

@@ -1128,16 +1128,5 @@
"appearance": "Внешний вид",
"preview": "Предпросмотр редактора",
"defaultValue": "Значение по умолчанию"
},
"languages" : {
"de": "Немецкий",
"en-US": "Английский",
"fr": "Французский",
"ja": "Японский",
"ko": "Корейский",
"pt-BR":"португальский",
"ru": "Русский",
"zh-CN": "Китайский (упрощенный)",
"zh-TW": "Китайский (традиционный)"
}
}

View File

@@ -1203,17 +1203,6 @@
"diagnostics": {
"title": "系统信息"
},
"languages": {
"de": "德语",
"en-US": "英文",
"fr": "法语",
"ja": "日语",
"ko": "韩文",
"pt-BR":"葡萄牙语",
"ru":"俄語",
"zh-CN": "简体中文",
"zh-TW": "繁体中文"
},
"validator": {
"errors": {
"invalid-json": "无效的 JSON 数据: __error__",

View File

@@ -1203,17 +1203,6 @@
"diagnostics": {
"title": "系统信息"
},
"languages": {
"de": "德語",
"en-US": "英語",
"fr": "法語",
"ja": "日語",
"ko": "韓語",
"pt-BR":"葡萄牙语",
"ru":"俄語",
"zh-CN": "簡體中文",
"zh-TW": "繁體中文"
},
"validator": {
"errors": {
"invalid-json": "無效的 JSON 數據: __error__",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "3.1.3",
"version": "4.0.0-dev",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -1228,7 +1228,6 @@ RED.nodes = (function() {
}
}
} else if (n.credentials) {
node.credentials = {};
// All other nodes have a well-defined list of possible credentials
for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) {
@@ -2217,7 +2216,7 @@ RED.nodes = (function() {
set: registry.getNodeSet("node-red/unknown")
}
} else {
if (createNewIds || options.importMap[n.id] === "copy") {
if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);

View File

@@ -819,7 +819,7 @@ RED.clipboard = (function() {
flow.forEach(function(node) {
if (node.type === "tab") {
flows[node.id] = {
element: getFlowLabel(node,false),
element: getFlowLabel(node),
deferBuild: type !== "flow",
expanded: type === "flow",
children: []
@@ -1000,7 +1000,6 @@ RED.clipboard = (function() {
try {
RED.view.importNodes(newNodes, importOptions);
} catch(error) {
console.log(error.importConfig)
// Thrown for import_conflict
confirmImport(error.importConfig, newNodes, importOptions);
}
@@ -1170,9 +1169,9 @@ RED.clipboard = (function() {
function getNodeElement(n, isConflicted, isSelected, parent) {
var element;
if (n.type === "tab") {
element = getFlowLabel(n, isSelected);
element = getFlowLabel(n, isConflicted);
} else {
element = getNodeLabel(n, isConflicted, isSelected);
element = getNodeLabel(n, isConflicted, isSelected, parent);
}
var controls = $('<div>',{class:"red-ui-clipboard-dialog-import-conflicts-controls"}).appendTo(element);
controls.on("click", function(evt) { evt.stopPropagation(); });
@@ -1222,14 +1221,14 @@ RED.clipboard = (function() {
}
}
function getFlowLabel(n) {
function getFlowLabel(n, isConflicted) {
n = JSON.parse(JSON.stringify(n));
n._def = RED.nodes.getType(n.type) || {};
if (n._def) {
n._ = n._def._;
}
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow red-ui-node-list-item"});
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
var label = (typeof n === "string")? n : n.label;
var newlineIndex = label.indexOf("\\n");
@@ -1237,11 +1236,17 @@ RED.clipboard = (function() {
label = label.substring(0,newlineIndex)+"...";
}
contentDiv.text(label);
if (!!isConflicted) {
const conflictIcon = $('<span style="padding: 0 10px;"><i class="fa fa-exclamation-circle"></span>').appendTo(div)
RED.popover.tooltip(conflictIcon, RED._('clipboard.import.alreadyExists'))
}
// A conflicted flow should not be imported by default.
return div;
}
function getNodeLabel(n, isConflicted) {
function getNodeLabel(n, isConflicted, isSelected, parent) {
n = JSON.parse(JSON.stringify(n));
n._def = RED.nodes.getType(n.type) || {};
if (n._def) {
@@ -1249,6 +1254,11 @@ RED.clipboard = (function() {
}
var div = $('<div>',{class:"red-ui-node-list-item"});
RED.utils.createNodeIcon(n,true).appendTo(div);
if (!parent && !!isConflicted) {
const conflictIcon = $('<span style="padding: 0 10px;"><i class="fa fa-exclamation-circle"></span>').appendTo(div)
RED.popover.tooltip(conflictIcon, RED._('clipboard.import.alreadyExists'))
}
return div;
}

View File

@@ -54,25 +54,26 @@
return icon;
}
var autoComplete = function(options) {
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,idx+len),
post: value.substring(idx+len),
}
}
function generateSpans(match) {
const els = [];
if(match.pre) { els.push($('<span/>').text(match.pre)); }
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
if(match.post) { els.push($('<span/>').text(match.post)); }
return els;
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,idx+len),
post: value.substring(idx+len),
}
}
function generateSpans(match) {
const els = [];
if(match.pre) { els.push($('<span/>').text(match.pre)); }
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
if(match.post) { els.push($('<span/>').text(match.post)); }
return els;
}
const msgAutoComplete = function(options) {
return function(val) {
var matches = [];
options.forEach(opt => {
@@ -102,6 +103,197 @@
}
}
function getEnvVars (obj, envVars = {}) {
contextKnownKeys.env = contextKnownKeys.env || {}
if (contextKnownKeys.env[obj.id]) {
return contextKnownKeys.env[obj.id]
}
let parent
if (obj.type === 'tab' || obj.type === 'subflow') {
RED.nodes.eachConfig(function (conf) {
if (conf.type === "global-config") {
parent = conf;
}
})
} else if (obj.g) {
parent = RED.nodes.group(obj.g)
} else if (obj.z) {
parent = RED.nodes.workspace(obj.z) || RED.nodes.subflow(obj.z)
}
if (parent) {
getEnvVars(parent, envVars)
}
if (obj.env) {
obj.env.forEach(env => {
envVars[env.name] = obj
})
}
contextKnownKeys.env[obj.id] = envVars
return envVars
}
const envAutoComplete = function (val) {
const editStack = RED.editor.getEditStack()
if (editStack.length === 0) {
done([])
return
}
const editingNode = editStack.pop()
if (!editingNode) {
return []
}
const envVarsMap = getEnvVars(editingNode)
const envVars = Object.keys(envVarsMap)
const matches = []
const i = val.lastIndexOf('${')
let searchKey = val
let isSubkey = false
if (i > -1) {
if (val.lastIndexOf('}') < i) {
searchKey = val.substring(i+2)
isSubkey = true
}
}
envVars.forEach(v => {
let valMatch = getMatch(v, searchKey);
if (valMatch.found) {
const optSrc = envVarsMap[v]
const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch))
valEl.appendTo(element)
if (optSrc) {
const optEl = $('<div>').css({ "font-size": "0.8em" });
let label
if (optSrc.type === 'global-config') {
label = RED._('sidebar.context.global')
} else if (optSrc.type === 'group') {
label = RED.utils.getNodeLabel(optSrc) || (RED._('sidebar.info.group') + ': '+optSrc.id)
} else {
label = RED.utils.getNodeLabel(optSrc) || optSrc.id
}
optEl.append(generateSpans({ match: label }));
optEl.appendTo(element);
}
matches.push({
value: isSubkey ? val + v + '}' : v,
label: element,
i: valMatch.index
});
}
})
matches.sort(function(A,B){return A.i-B.i})
return matches
}
let contextKnownKeys = {}
let contextCache = {}
if (RED.events) {
RED.events.on("editor:close", function () {
contextCache = {}
contextKnownKeys = {}
});
}
const contextAutoComplete = function() {
const that = this
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set()
if (searchKey.length > 0) {
try {
RED.utils.normalisePropertyExpression(searchKey)
} catch (err) {
// Not a valid context key, so don't try looking up
done()
return
}
}
const url = `context/${scope}/${encodeURIComponent(searchKey)}?store=${store}&keysOnly`
if (contextCache[url]) {
// console.log('CACHED', url)
done()
} else {
// console.log('GET', url)
$.getJSON(url, function(data) {
// console.log(data)
contextCache[url] = true
const result = data[store] || {}
const keys = result.keys || []
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
keys.forEach(key => {
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
contextKnownKeys[scope][store].add(keyPrefix + key)
} else {
contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]")
}
})
done()
})
}
}
const getContextKeys = function(key, done) {
const keyParts = key.split('.')
const partialKey = keyParts.pop()
let scope = that.propertyType
if (scope === 'flow') {
// Get the flow id of the node we're editing
const editStack = RED.editor.getEditStack()
if (editStack.length === 0) {
done([])
return
}
const editingNode = editStack.pop()
if (editingNode.z) {
scope = `${scope}/${editingNode.z}`
} else {
done([])
return
}
}
const store = (contextStoreOptions.length === 1) ? contextStoreOptions[0].value : that.optionValue
const searchKey = keyParts.join('.')
getContextKeysFromRuntime(scope, store, searchKey, function() {
if (contextKnownKeys[scope][store].has(key) || key.endsWith(']')) {
getContextKeysFromRuntime(scope, store, key, function() {
done(contextKnownKeys[scope][store])
})
}
done(contextKnownKeys[scope][store])
})
}
return function(val, done) {
getContextKeys(val, function (keys) {
const matches = []
keys.forEach(v => {
let optVal = v
let valMatch = getMatch(optVal, val);
if (!valMatch.found && val.length > 0 && val.endsWith('.')) {
// Search key ends in '.' - but doesn't match. Check again
// with [" at the end instead so we match bracket notation
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
}
if (valMatch.found) {
const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch))
valEl.appendTo(element)
matches.push({
value: optVal,
label: element,
});
}
})
matches.sort(function(a, b) { return a.value.localeCompare(b.value) });
done(matches);
})
}
}
// This is a hand-generated list of completions for the core nodes (based on the node help html).
var msgCompletions = [
{ value: "payload" },
@@ -166,20 +358,22 @@
{ value: "_session", source: ["websocket out","tcp out"] },
]
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions)},
flow: {value:"flow",label:"flow.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel
valueLabel: contextLabel,
autoComplete: contextAutoComplete
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel
valueLabel: contextLabel,
autoComplete: contextAutoComplete
},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
@@ -214,7 +408,25 @@
}
},
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"},
date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false},
date: {
value:"date",
label:"timestamp",
icon:"fa fa-clock-o",
options:[
{
label: 'milliseconds since epoch',
value: ''
},
{
label: 'YYYY-MM-DDTHH:mm:ss.sssZ',
value: 'iso'
},
{
label: 'JavaScript Date Object',
value: 'object'
}
]
},
jsonata: {
value: "jsonata",
label: "expression",
@@ -251,7 +463,8 @@
env: {
value: "env",
label: "env variable",
icon: "red/images/typedInput/env.svg"
icon: "red/images/typedInput/env.svg",
autoComplete: envAutoComplete
},
node: {
value: "node",
@@ -427,6 +640,7 @@
}
var nlsd = false;
let contextStoreOptions;
$.widget( "nodered.typedInput", {
_create: function() {
@@ -438,7 +652,7 @@
}
}
var contextStores = RED.settings.context.stores;
var contextOptions = contextStores.map(function(store) {
contextStoreOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
}).sort(function(A,B) {
if (A.value === RED.settings.context.default) {
@@ -449,13 +663,17 @@
return A.value.localeCompare(B.value);
}
})
if (contextOptions.length < 2) {
if (contextStoreOptions.length < 2) {
allOptions.flow.options = [];
allOptions.global.options = [];
} else {
allOptions.flow.options = contextOptions;
allOptions.global.options = contextOptions;
allOptions.flow.options = contextStoreOptions;
allOptions.global.options = contextStoreOptions;
}
// Translate timestamp options
allOptions.date.options.forEach(opt => {
opt.label = RED._("typedInput.date.format." + (opt.value || 'timestamp'), {defaultValue: opt.label})
})
}
nlsd = true;
var that = this;
@@ -544,7 +762,7 @@
that.element.trigger('paste',evt);
});
this.input.on('keydown', function(evt) {
if (that.typeMap[that.propertyType].autoComplete) {
if (that.typeMap[that.propertyType].autoComplete || that.input.hasClass('red-ui-autoComplete')) {
return
}
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
@@ -967,6 +1185,9 @@
// If previousType is !null, then this is a change of the type, rather than the initialisation
var previousType = this.typeMap[this.propertyType];
previousValue = this.input.val();
if (this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete("destroy");
}
if (previousType && this.typeChanged) {
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
@@ -1013,7 +1234,9 @@
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
}
if (previousType.autoComplete) {
this.input.autoComplete("destroy");
if (this.input.hasClass('red-ui-autoComplete')) {
this.input.autoComplete("destroy");
}
}
}
this.propertyType = type;
@@ -1141,6 +1364,16 @@
} else {
this.optionSelectTrigger.hide();
}
if (opt.autoComplete) {
let searchFunction = opt.autoComplete
if (searchFunction.length === 0) {
searchFunction = opt.autoComplete.call(this)
}
this.input.autoComplete({
search: searchFunction,
minLength: 0
})
}
}
this.optionMenu = this._createMenu(opt.options,opt,function(v){
if (!opt.multiple) {
@@ -1183,8 +1416,12 @@
this.valueLabelContainer.hide();
this.elementDiv.show();
if (opt.autoComplete) {
let searchFunction = opt.autoComplete
if (searchFunction.length === 0) {
searchFunction = opt.autoComplete.call(this)
}
this.input.autoComplete({
search: opt.autoComplete,
search: searchFunction,
minLength: 0
})
}

View File

@@ -1231,7 +1231,11 @@ RED.editor = (function() {
})
if (node_def.hasUsers !== false) {
$('<span><i class="fa fa-info-circle"></i> <span id="red-ui-editor-config-user-count"></span></span>').css("margin-left", "10px").appendTo(trayFooterLeft);
// $('<span><i class="fa fa-info-circle"></i> <span id="red-ui-editor-config-user-count"></span></span>').css("margin-left", "10px").appendTo(trayFooterLeft);
$('<button type="button" class="red-ui-button"><i class="fa fa-user"></i><span id="red-ui-editor-config-user-count"></span></button>').on('click', function() {
RED.sidebar.info.outliner.search('uses:'+editing_config_node.id)
RED.sidebar.info.show()
}).appendTo(trayFooterLeft);
}
trayFooter.append('<span class="red-ui-tray-footer-right"><span id="red-ui-editor-config-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="red-ui-editor-config-scope"></select></span>');
@@ -1289,7 +1293,8 @@ RED.editor = (function() {
});
}
if (node_def.hasUsers !== false) {
$("#red-ui-editor-config-user-count").text(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show();
$("#red-ui-editor-config-user-count").text(editing_config_node.users.length).parent().show();
RED.popover.tooltip($("#red-ui-editor-config-user-count").parent(), function() { return RED._('editor.nodesUse',{count:editing_config_node.users.length})});
}
trayBody.i18n();
trayFooter.i18n();
@@ -2082,6 +2087,7 @@ RED.editor = (function() {
}
},
editBuffer: function(options) { showTypeEditor("_buffer", options) },
getEditStack: function () { return [...editStack] },
buildEditForm: buildEditForm,
validateNode: validateNode,
updateNodeProperties: updateNodeProperties,

View File

@@ -71,7 +71,7 @@ RED.envVar = (function() {
};
if (item.name.trim() !== "") {
new_env.push(item);
if ((item.type === "cred") && (item.value !== "__PWRD__")) {
if (item.type === "cred") {
credentials.map[item.name] = item.value;
credentials.map["has_"+item.name] = (item.value !== "");
item.value = "__PWRD__";

View File

@@ -158,6 +158,7 @@ RED.sidebar.config = (function() {
entry.data('node',node.id);
nodeDiv.data('node',node.id);
var label = $('<div class="red-ui-palette-label"></div>').text(labelText).appendTo(nodeDiv);
if (node.d) {
nodeDiv.addClass("red-ui-palette-node-config-disabled");
$('<i class="fa fa-ban"></i>').prependTo(label);
@@ -179,6 +180,20 @@ RED.sidebar.config = (function() {
nodeDiv.addClass("red-ui-palette-node-config-unused");
}
}
if (!node.valid) {
nodeDiv.addClass("red-ui-palette-node-config-invalid")
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv)
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z");
nodeDivAnnotations.append($(errorBadge))
RED.popover.tooltip(nodeDivAnnotations, function () {
if (node.validationErrors && node.validationErrors.length > 0) {
return RED._("editor.errors.invalidProperties")+"<br> - "+node.validationErrors.join("<br> - ")
}
})
}
nodeDiv.on('click',function(e) {
e.stopPropagation();
RED.view.select(false);

View File

@@ -4155,10 +4155,15 @@ RED.view = (function() {
scaleFactor = 30/largestEdge;
}
var width = img.width * scaleFactor;
if (width > 20) {
scalefactor *= 20/width;
width = 20;
}
var height = img.height * scaleFactor;
icon.attr("width",width);
icon.attr("height",height);
icon.attr("x",15-width/2);
icon.attr("y",(30-height)/2);
}
icon.attr("xlink:href",iconUrl);
icon.style("display",null);
@@ -6247,6 +6252,10 @@ RED.view = (function() {
}
})
}
if (selection.links) {
selectedLinks.clear();
selection.links.forEach(selectedLinks.add);
}
}
}
updateSelection();

View File

@@ -194,10 +194,6 @@
}
}
.red-ui-clipboard-dialog-import-conflicts-controls {
position: absolute;
top:0;
bottom: 0;
right: 0px;
text-align: center;
color: var(--red-ui-form-text-color);
.form-row & label {
@@ -218,9 +214,21 @@
margin: 0;
}
}
#red-ui-clipboard-dialog-import-conflicts-list .disabled .red-ui-info-outline-item {
opacity: 0.4;
#red-ui-clipboard-dialog-import-conflicts-list .disabled {
.red-ui-info-outline-item,
.red-ui-node-list-item {
opacity: 0.4;
}
}
#red-ui-clipboard-dialog-import-conflicts-list .red-ui-node-list-item {
display: flex;
align-items: center;
& > :first-child {
flex-grow: 1
}
}
.form-row label.red-ui-clipboard-dialog-import-conflicts-gutter {
box-sizing: border-box;
width: 22px;

View File

@@ -36,7 +36,7 @@ ul.red-ui-sidebar-node-config-list {
text-align: center;
}
.red-ui-palette-node {
overflow: hidden;
// overflow: hidden;
cursor: default;
&.selected {
border-color: transparent;
@@ -113,6 +113,15 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type {
margin-right: 5px;
}
}
.red-ui-palette-node-config-invalid {
border-color: var(--red-ui-form-input-border-error-color)
}
.red-ui-palette-node-annotations {
position: absolute;
left: calc(100% - 15px);
top: -8px;
display: block;
}
.red-ui-sidebar-node-config-filter-info {
position: absolute;
top: 0;

View File

@@ -22,8 +22,8 @@
limitations under the License.
-->
<title>{{ page.title }}</title>
<link rel="icon" type="image/png" href="{{ page.favicon }}">
<link rel="mask-icon" href="{{ page.tabicon.icon }}" color="{{ page.tabicon.colour }}">
<link rel="icon" type="image/png" href="{{{ page.favicon }}}">
<link rel="mask-icon" href="{{{ page.tabicon.icon }}}" color="{{ page.tabicon.colour }}">
<link rel="stylesheet" href="vendor/jquery/css/base/jquery-ui.min.css?v={{ cacheBuster }}">
<link rel="stylesheet" href="vendor/font-awesome/css/font-awesome.min.css?v={{ cacheBuster }}">
<link rel="stylesheet" href="red/style.min.css?v={{ cacheBuster }}">
@@ -38,10 +38,10 @@
<div id="red-ui-editor"></div>
<script src="vendor/vendor.js?v={{ cacheBuster }}"></script>
{{#asset.vendorMonaco}}
<script src="{{ asset.vendorMonaco }}?v={{ cacheBuster }}"></script>
<script src="{{{ asset.vendorMonaco }}}?v={{ cacheBuster }}"></script>
{{/asset.vendorMonaco}}
<script src="{{ asset.red }}?v={{ cacheBuster }}"></script>
<script src="{{ asset.main }}?v={{ cacheBuster }}"></script>
<script src="{{{ asset.red }}}?v={{ cacheBuster }}"></script>
<script src="{{{ asset.main }}}?v={{ cacheBuster }}"></script>
{{# page.scripts }}
<script src="{{.}}"></script>
{{/ page.scripts }}

View File

@@ -5,6 +5,7 @@ module.exports = function(RED) {
const fs = require("fs-extra");
const path = require("path");
var debuglength = RED.settings.debugMaxLength || 1000;
var statuslength = RED.settings.debugStatusLength || 32;
var useColors = RED.settings.debugUseColors || false;
util.inspect.styles.boolean = "red";
const { hasOwnProperty } = Object.prototype;
@@ -164,7 +165,7 @@ module.exports = function(RED) {
}
}
if (st.length > 32) { st = st.substr(0,32) + "..."; }
if (st.length > statuslength) { st = st.substr(0,statuslength) + "..."; }
var newStatus = {fill:fill, shape:shape, text:st};
if (JSON.stringify(newStatus) !== node.oldState) { // only send if we have to

View File

@@ -315,7 +315,7 @@ module.exports = function(RED) {
var spec = module.module;
if (spec && (spec !== "")) {
moduleLoadPromises.push(RED.import(module.module).then(lib => {
sandbox[vname] = lib.default;
sandbox[vname] = lib.default || lib;
}).catch(err => {
node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:err.toString()}))
throw err;

View File

@@ -117,7 +117,7 @@ module.exports = function(RED) {
});
return
} else if (rule.tot === 'date') {
value = Date.now();
value = RED.util.evaluateNodeProperty(rule.to, rule.tot, node)
} else if (rule.tot === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
if (err) {
@@ -233,7 +233,9 @@ module.exports = function(RED) {
// only replace if they match exactly
RED.util.setMessageProperty(msg,property,value);
} else {
current = current.replace(fromRE,value);
// if target is boolean then just replace it
if (rule.tot === "bool") { current = value; }
else { current = current.replace(fromRE,value); }
RED.util.setMessageProperty(msg,property,current);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {

View File

@@ -141,15 +141,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
});
}
}
/**
* @param {Object} headersObject
* @param {string} name
* @return {any} value
*/
const getHeaderValue = (headersObject, name) => {
const asLowercase = name.toLowercase();
return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)];
}
this.on("input",function(msg,nodeSend,nodeDone) {
checkNodeAgentPatch();
//reset redirectList on each request
@@ -300,7 +292,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
}
opts.headers = {};
//add msg.headers
//add msg.headers
//NOTE: ui headers will take precidence over msg.headers
if (msg.headers) {
if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
@@ -633,7 +625,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
msg.payload = msg.payload.toString('utf8'); // txt
if (node.ret === "obj") {
if (msg.statusCode == 204){msg.payload= "{}"};
if (msg.statusCode == 204){msg.payload= "{}"};
try { msg.payload = JSON.parse(msg.payload); } // obj
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
}
@@ -740,7 +732,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
*
* If the algorithm directive's value ends with "-sess", then HA1 is
* HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
*
*
* If the algorithm directive's value does not end with "-sess", then HA1 is
* HA1=digestCompute(username:realm:password)
*/

View File

@@ -411,23 +411,33 @@ module.exports = function(RED) {
if (msg._session && msg._session.type == "tcp") {
var client = connectionPool[msg._session.id];
if (client) {
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
client.write(Buffer.from(msg.payload,'base64'));
} else {
client.write(Buffer.from(""+msg.payload));
if (msg?.reset === true) {
client.destroy();
}
else {
if (Buffer.isBuffer(msg.payload)) {
client.write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
client.write(Buffer.from(msg.payload,'base64'));
} else {
client.write(Buffer.from(""+msg.payload));
}
}
}
}
else {
for (var i in connectionPool) {
if (Buffer.isBuffer(msg.payload)) {
connectionPool[i].write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
connectionPool[i].write(Buffer.from(msg.payload,'base64'));
} else {
connectionPool[i].write(Buffer.from(""+msg.payload));
if (msg?.reset === true) {
connectionPool[i].destroy();
}
else {
if (Buffer.isBuffer(msg.payload)) {
connectionPool[i].write(msg.payload);
} else if (typeof msg.payload === "string" && node.base64) {
connectionPool[i].write(Buffer.from(msg.payload,'base64'));
} else {
connectionPool[i].write(Buffer.from(""+msg.payload));
}
}
}
}
@@ -547,13 +557,33 @@ module.exports = function(RED) {
this.on("input", function(msg, nodeSend, nodeDone) {
var i = 0;
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
if (msg.payload !== undefined && (!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
msg.payload = msg.payload.toString();
}
var host = node.server || msg.host;
var port = node.port || msg.port;
if (node.out === "sit" && msg?.reset) {
if (msg.reset === true) { // kill all connections
for (var cl in clients) {
if (clients[cl].hasOwnProperty("client")) {
clients[cl].client.destroy();
delete clients[cl];
}
}
}
if (typeof(msg.reset) === "string" && msg.reset.includes(":")) { // just kill connection host:port
if (clients.hasOwnProperty(msg.reset) && clients[msg.reset].hasOwnProperty("client")) {
clients[msg.reset].client.destroy();
delete clients[msg.reset];
}
}
const cc = Object.keys(clients).length;
node.status({fill:"green",shape:cc===0?"ring":"dot",text:RED._("tcpin.status.connections",{count:cc})});
if ((host === undefined || port === undefined) && !msg.hasOwnProperty("payload")) { return; }
}
// Store client information independently
// the clients object will have:
// clients[id].client, clients[id].msg, clients[id].timeout
@@ -621,13 +651,16 @@ module.exports = function(RED) {
clients[connection_id].connecting = true;
clients[connection_id].client.connect(connOpts, function() {
//node.log(RED._("tcpin.errors.client-connected"));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
// node.status({fill:"green",shape:"dot",text:"common.status.connected"});
node.status({fill:"green",shape:"dot",text:RED._("tcpin.status.connections",{count:Object.keys(clients).length})});
if (clients[connection_id] && clients[connection_id].client) {
clients[connection_id].connected = true;
clients[connection_id].connecting = false;
let event;
while (event = dequeue(clients[connection_id].msgQueue)) {
clients[connection_id].client.write(event.msg.payload);
if (event.msg.payload !== undefined) {
clients[connection_id].client.write(event.msg.payload);
}
event.nodeDone();
}
if (node.out === "time" && node.splitc < 0) {
@@ -823,7 +856,9 @@ module.exports = function(RED) {
else if (!clients[connection_id].connecting && clients[connection_id].connected) {
if (clients[connection_id] && clients[connection_id].client) {
let event = dequeue(clients[connection_id].msgQueue)
clients[connection_id].client.write(event.msg.payload);
if (event.msg.payload !== undefined ) {
clients[connection_id].client.write(event.msg.payload);
}
event.nodeDone();
}
}

View File

@@ -17,7 +17,20 @@
</select>
<input style="width:40px;" type="text" id="node-input-sep" pattern=".">
</div>
<div class="form-row">
<label><i class="fa fa-code"></i> <span data-i18n="csv.label.spec"></span></label>
<div style="display: inline-grid;width: 70%;">
<select style="width:100%" id="csv-option-spec">
<option value="rfc" data-i18n="csv.spec.rfc"></option>
<option value="" data-i18n="csv.spec.legacy"></option>
</select>
<div>
<div class="form-tips csv-lecacy-warning" data-i18n="node-red:csv.spec.legacy_warning"
style="width: calc(100% - 18px); margin-top: 4px; max-width: unset;">
</div>
</div>
</div>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@@ -60,10 +73,10 @@
<div class="form-row" style="padding-left:20px;">
<label></label>
<label style="width:auto; margin-right:10px;" for="node-input-ret"><span data-i18n="csv.label.newline"></span></label>
<select style="width:150px;" id="node-input-ret">
<select style="width:calc(70% - 108px);" id="node-input-ret">
<option value='\r\n' data-i18n="csv.newline.windows"></option>
<option value='\n' data-i18n="csv.newline.linux"></option>
<option value='\r' data-i18n="csv.newline.mac"></option>
<option value='\r\n' data-i18n="csv.newline.windows"></option>
</select>
</div>
</script>
@@ -75,6 +88,7 @@
color:"#DEBD5C",
defaults: {
name: {value:""},
spec: {value:"rfc"},
sep: {
value:',', required:true,
label:RED._("node-red:csv.label.separator"),
@@ -83,7 +97,7 @@
hdrin: {value:""},
hdrout: {value:"none"},
multi: {value:"one",required:true},
ret: {value:'\\n'},
ret: {value:'\\r\\n'}, // default to CRLF (RFC4180 Sec 2.1: "Each record is located on a separate line, delimited by a line break (CRLF)")
temp: {value:""},
skip: {value:"0"},
strings: {value:true},
@@ -123,6 +137,27 @@
$("#node-input-sep").hide();
}
});
$("#csv-option-spec").on("change", function() {
if ($("#csv-option-spec").val() == "rfc") {
$(".form-tips.csv-lecacy-warning").hide();
} else {
$(".form-tips.csv-lecacy-warning").show();
}
});
// new nodes will have `spec` set to "rfc" (default), but existing nodes will either not have
// a spec value or it will be empty - we need to maintain the legacy behaviour for existing
// flows but default to rfc for new nodes
let spec = !this.spec ? "" : "rfc"
$("#csv-option-spec").val(spec).trigger("change")
},
oneditsave: function() {
const specFormVal = $("#csv-option-spec").val() || '' // empty === legacy
const spectNodeVal = this.spec || '' // empty === legacy, null/undefined means in-place node upgrade (keep as is)
if (specFormVal !== spectNodeVal) {
// only update the flow value if changed (avoid marking the node dirty unnecessarily)
this.spec = specFormVal
}
}
});
</script>

View File

@@ -15,322 +15,674 @@
**/
module.exports = function(RED) {
const csv = require('./lib/csv')
"use strict";
function CSVNode(n) {
RED.nodes.createNode(this,n);
this.template = (n.temp || "");
this.sep = (n.sep || ',').replace(/\\t/g,"\t").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
this.quo = '"';
this.ret = (n.ret || "\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
this.winflag = (this.ret === "\r\n");
this.lineend = "\n";
this.multi = n.multi || "one";
this.hdrin = n.hdrin || false;
this.hdrout = n.hdrout || "none";
this.goodtmpl = true;
this.skip = parseInt(n.skip || 0);
this.store = [];
this.parsestrings = n.strings;
this.include_empty_strings = n.include_empty_strings || false;
this.include_null_values = n.include_null_values || false;
if (this.parsestrings === undefined) { this.parsestrings = true; }
if (this.hdrout === false) { this.hdrout = "none"; }
if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true;
var node = this;
var re = new RegExp(node.sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') + '(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
RED.nodes.createNode(this,n)
const node = this
const RFC4180Mode = n.spec === 'rfc'
const legacyMode = !RFC4180Mode
// pass in an array of column names to be trimmed, de-quoted and retrimmed
var clean = function(col,sep) {
if (sep) { re = new RegExp(sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') +'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
col = col.trim().split(re) || [""];
col = col.map(x => x.replace(/"/g,'').trim());
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
else { node.goodtmpl = true; }
return col;
}
var template = clean(node.template,',');
var notemplate = template.length === 1 && template[0] === '';
node.hdrSent = false;
node.status({}) // clear status
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("reset")) {
node.hdrSent = false;
if (legacyMode) {
this.template = (n.temp || "");
this.sep = (n.sep || ',').replace(/\\t/g,"\t").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
this.quo = '"';
this.ret = (n.ret || "\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r");
this.winflag = (this.ret === "\r\n");
this.lineend = "\n";
this.multi = n.multi || "one";
this.hdrin = n.hdrin || false;
this.hdrout = n.hdrout || "none";
this.goodtmpl = true;
this.skip = parseInt(n.skip || 0);
this.store = [];
this.parsestrings = n.strings;
this.include_empty_strings = n.include_empty_strings || false;
this.include_null_values = n.include_null_values || false;
if (this.parsestrings === undefined) { this.parsestrings = true; }
if (this.hdrout === false) { this.hdrout = "none"; }
if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true;
// var node = this;
var re = new RegExp(node.sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') + '(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
// pass in an array of column names to be trimmed, de-quoted and retrimmed
var clean = function(col,sep) {
if (sep) { re = new RegExp(sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') +'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
col = col.trim().split(re) || [""];
col = col.map(x => x.replace(/"/g,'').trim());
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
else { node.goodtmpl = true; }
return col;
}
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") { // convert object to CSV string
try {
if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
template = clean(node.template);
}
const ou = [];
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
if (node.hdrout !== "none" && node.hdrSent === false) {
if ((template.length === 1) && (template[0] === '')) {
if (msg.hasOwnProperty("columns")) {
template = clean(msg.columns || "",",");
}
else {
template = Object.keys(msg.payload[0]);
}
var template = clean(node.template,',');
var notemplate = template.length === 1 && template[0] === '';
node.hdrSent = false;
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("reset")) {
node.hdrSent = false;
}
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") { // convert object to CSV string
try {
if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
template = clean(node.template);
}
ou.push(template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep));
if (node.hdrout === "once") { node.hdrSent = true; }
}
for (var s = 0; s < msg.payload.length; s++) {
if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
for (var t = 0; t < msg.payload[s].length; t++) {
if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; }
if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas"
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
else if (msg.payload[s][t].toString().indexOf("\n") !== -1) { // add quotes if any "\n"
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
}
ou.push(msg.payload[s].join(node.sep));
}
else {
if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
template = clean(msg.columns || "",",");
}
const ou = [];
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
if (node.hdrout !== "none" && node.hdrSent === false) {
if ((template.length === 1) && (template[0] === '')) {
/* istanbul ignore else */
if (tmpwarn === true) { // just warn about missing template once
node.warn(RED._("csv.errors.obj_csv"));
tmpwarn = false;
if (msg.hasOwnProperty("columns")) {
template = clean(msg.columns || "",",");
}
const row = [];
for (var p in msg.payload[0]) {
else {
template = Object.keys(msg.payload[0]);
}
}
ou.push(template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep));
if (node.hdrout === "once") { node.hdrSent = true; }
}
for (var s = 0; s < msg.payload.length; s++) {
if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
for (var t = 0; t < msg.payload[s].length; t++) {
if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; }
if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas"
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
else if (msg.payload[s][t].toString().indexOf("\n") !== -1) { // add quotes if any "\n"
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
}
}
ou.push(msg.payload[s].join(node.sep));
}
else {
if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
template = clean(msg.columns || "",",");
}
if ((template.length === 1) && (template[0] === '')) {
/* istanbul ignore else */
if (msg.payload[s].hasOwnProperty(p)) {
if (tmpwarn === true) { // just warn about missing template once
node.warn(RED._("csv.errors.obj_csv"));
tmpwarn = false;
}
const row = [];
for (var p in msg.payload[0]) {
/* istanbul ignore else */
if (typeof msg.payload[s][p] !== "object") {
// Fix to honour include null values flag
//if (typeof msg.payload[s][p] !== "object" || (node.include_null_values === true && msg.payload[s][p] === null)) {
var q = "";
if (msg.payload[s][p] !== undefined) {
q += msg.payload[s][p];
if (msg.payload[s].hasOwnProperty(p)) {
/* istanbul ignore else */
if (typeof msg.payload[s][p] !== "object") {
// Fix to honour include null values flag
//if (typeof msg.payload[s][p] !== "object" || (node.include_null_values === true && msg.payload[s][p] === null)) {
var q = "";
if (msg.payload[s][p] !== undefined) {
q += msg.payload[s][p];
}
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""');
row.push(node.quo + q + node.quo);
}
else if (q.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
row.push(node.quo + q + node.quo);
}
else { row.push(q); } // otherwise just add
}
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""');
row.push(node.quo + q + node.quo);
}
else if (q.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
row.push(node.quo + q + node.quo);
}
else { row.push(q); } // otherwise just add
}
}
ou.push(row.join(node.sep)); // add separator
}
ou.push(row.join(node.sep)); // add separator
else {
const row = [];
for (var t=0; t < template.length; t++) {
if (template[t] === '') {
row.push('');
}
else {
var tt = template[t];
if (template[t].indexOf('"') >=0 ) { tt = "'"+tt+"'"; }
else { tt = '"'+tt+'"'; }
var p = RED.util.getMessageProperty(msg,'payload["'+s+'"]['+tt+']');
/* istanbul ignore else */
if (p === undefined) { p = ""; }
// fix to honour include null values flag
//if (p === null && node.include_null_values !== true) { p = "";}
p = RED.util.ensureString(p);
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
row.push(node.quo + p + node.quo);
}
else if (p.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
row.push(node.quo + p + node.quo);
}
else { row.push(p); } // otherwise just add
}
}
ou.push(row.join(node.sep)); // add separator
}
}
}
// join lines, don't forget to add the last new line
msg.payload = ou.join(node.ret) + node.ret;
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
if (msg.payload !== '') {
send(msg);
}
done();
}
catch(e) { done(e); }
}
else if (typeof msg.payload == "string") { // convert CSV string to object
try {
var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
var j = 0; // pointer into array of template items
var k = [""]; // array of data for each of the template items
var o = {}; // output object to build up
var a = []; // output array is needed for multiline option
var first = true; // is this the first line
var last = false;
var line = msg.payload;
var linecount = 0;
var tmp = "";
var has_parts = msg.hasOwnProperty("parts");
var reg = /^[-]?(?!E)(?!0\d)\d*\.?\d*(E-?\+?)?\d+$/i;
if (msg.hasOwnProperty("parts")) {
linecount = msg.parts.index;
if (msg.parts.index > node.skip) { first = false; }
if (msg.parts.hasOwnProperty("count") && (msg.parts.index+1 >= msg.parts.count)) { last = true; }
}
// For now we are just going to assume that any \r or \n means an end of line...
// got to be a weird csv that has singleton \r \n in it for another reason...
// Now process the whole file/line
var nocr = (line.match(/[\r\n]/g)||[]).length;
if (has_parts && node.multi === "mult" && nocr > 1) { tmp = ""; first = true; }
for (var i = 0; i < line.length; i++) {
if (first && (linecount < node.skip)) {
if (line[i] === "\n") { linecount += 1; }
continue;
}
if ((node.hdrin === true) && first) { // if the template is in the first line
if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
if (line.length - i === 1) { tmp += line[i]; }
template = clean(tmp,node.sep);
first = false;
}
else { tmp += line[i]; }
}
else {
const row = [];
for (var t=0; t < template.length; t++) {
if (template[t] === '') {
row.push('');
if (line[i] === node.quo) { // if it's a quote toggle inside or outside
f = !f;
if (line[i-1] === node.quo) {
if (f === false) { k[j] += '\"'; }
} // if it's a quotequote then it's actually a quote
//if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; }
}
else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
if ( template[j] && (template[j] !== "") ) {
// if no value between separators ('1,,"3"...') or if the line beings with separator (',1,"2"...') treat value as null
if (line[i-1] === node.sep || line[i-1].includes('\n','\r')) k[j] = null;
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
}
else {
var tt = template[t];
if (template[t].indexOf('"') >=0 ) { tt = "'"+tt+"'"; }
else { tt = '"'+tt+'"'; }
var p = RED.util.getMessageProperty(msg,'payload["'+s+'"]['+tt+']');
/* istanbul ignore else */
if (p === undefined) { p = ""; }
// fix to honour include null values flag
//if (p === null && node.include_null_values !== true) { p = "";}
p = RED.util.ensureString(p);
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
row.push(node.quo + p + node.quo);
}
else if (p.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n"
row.push(node.quo + p + node.quo);
}
else { row.push(p); } // otherwise just add
j += 1;
// if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",'
k[j] = line.length - 1 === i ? null : "";
}
else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines
//console.log(j,k,o,k[j]);
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
if ( template[j] && (template[j] !== "") ) {
// if separator before end of line, set null value ie. '1,2,"3"\n1,2,\n1,2,3'
if (line[i-1] === node.sep) k[j] = null;
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
else { if (k[j] !== null) k[j].replace(/\r$/,''); }
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array
}
j = 0;
k = [""];
o = {};
f = true; // reset in/out flag ready for next line.
}
else { // just add to the part of the message
k[j] += line[i];
}
ou.push(row.join(node.sep)); // add separator
}
}
}
// join lines, don't forget to add the last new line
msg.payload = ou.join(node.ret) + node.ret;
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
if (msg.payload !== '') { send(msg); }
done();
}
catch(e) { done(e); }
}
else if (typeof msg.payload == "string") { // convert CSV string to object
try {
var f = true; // flag to indicate if inside or outside a pair of quotes true = outside.
var j = 0; // pointer into array of template items
var k = [""]; // array of data for each of the template items
var o = {}; // output object to build up
var a = []; // output array is needed for multiline option
var first = true; // is this the first line
var last = false;
var line = msg.payload;
var linecount = 0;
var tmp = "";
var has_parts = msg.hasOwnProperty("parts");
var reg = /^[-]?(?!E)(?!0\d)\d*\.?\d*(E-?\+?)?\d+$/i;
if (msg.hasOwnProperty("parts")) {
linecount = msg.parts.index;
if (msg.parts.index > node.skip) { first = false; }
if (msg.parts.hasOwnProperty("count") && (msg.parts.index+1 >= msg.parts.count)) { last = true; }
}
// Finished so finalize and send anything left
if (f === false) { node.warn(RED._("csv.errors.bad_csv")); }
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
// For now we are just going to assume that any \r or \n means an end of line...
// got to be a weird csv that has singleton \r \n in it for another reason...
// Now process the whole file/line
var nocr = (line.match(/[\r\n]/g)||[]).length;
if (has_parts && node.multi === "mult" && nocr > 1) { tmp = ""; first = true; }
for (var i = 0; i < line.length; i++) {
if (first && (linecount < node.skip)) {
if (line[i] === "\n") { linecount += 1; }
continue;
if ( template[j] && (template[j] !== "") ) {
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
else { if (k[j] !== null) k[j].replace(/\r$/,''); }
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
}
if ((node.hdrin === true) && first) { // if the template is in the first line
if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
if (line.length - i === 1) { tmp += line[i]; }
template = clean(tmp,node.sep);
first = false;
}
else { tmp += line[i]; }
if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array
}
else {
if (line[i] === node.quo) { // if it's a quote toggle inside or outside
f = !f;
if (line[i-1] === node.quo) {
if (f === false) { k[j] += '\"'; }
} // if it's a quotequote then it's actually a quote
//if ((line[i-1] !== node.sep) && (line[i+1] !== node.sep)) { k[j] += line[i]; }
}
else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
if ( template[j] && (template[j] !== "") ) {
// if no value between separators ('1,,"3"...') or if the line beings with separator (',1,"2"...') treat value as null
if (line[i-1] === node.sep || line[i-1].includes('\n','\r')) k[j] = null;
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
}
j += 1;
// if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",'
k[j] = line.length - 1 === i ? null : "";
}
else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines
//console.log(j,k,o,k[j]);
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
if ( template[j] && (template[j] !== "") ) {
// if separator before end of line, set null value ie. '1,2,"3"\n1,2,\n1,2,3'
if (line[i-1] === node.sep) k[j] = null;
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
else { if (k[j] !== null) k[j].replace(/\r$/,''); }
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array
}
j = 0;
k = [""];
o = {};
f = true; // reset in/out flag ready for next line.
}
else { // just add to the part of the message
k[j] += line[i];
}
}
}
// Finished so finalize and send anything left
if (f === false) { node.warn(RED._("csv.errors.bad_csv")); }
if (!node.goodtmpl) { template[j] = "col"+(j+1); }
if ( template[j] && (template[j] !== "") ) {
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j].trim()) ) { k[j] = parseFloat(k[j].trim()); }
else { if (k[j] !== null) k[j].replace(/\r$/,''); }
if (node.include_null_values && k[j] === null) o[template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array
}
if (node.multi !== "one") {
msg.payload = a;
if (has_parts && nocr <= 1) {
if (JSON.stringify(o) !== "{}") {
node.store.push(o);
if (node.multi !== "one") {
msg.payload = a;
if (has_parts && nocr <= 1) {
if (JSON.stringify(o) !== "{}") {
node.store.push(o);
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store;
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
delete msg.parts;
send(msg);
node.store = [];
}
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store;
else {
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
delete msg.parts;
send(msg);
node.store = [];
send(msg); // finally send the array
}
}
else {
msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
send(msg); // finally send the array
}
}
else {
var len = a.length;
for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg);
newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
newMessage.payload = a[i];
if (!has_parts) {
newMessage.parts = {
id: msg._msgid,
index: i,
count: len
};
var len = a.length;
for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg);
newMessage.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
newMessage.payload = a[i];
if (!has_parts) {
newMessage.parts = {
id: msg._msgid,
index: i,
count: len
};
}
else {
newMessage.parts.index -= node.skip;
newMessage.parts.count -= node.skip;
if (node.hdrin) { // if we removed the header line then shift the counts by 1
newMessage.parts.index -= 1;
newMessage.parts.count -= 1;
}
}
if (last) { newMessage.complete = true; }
send(newMessage);
}
else {
newMessage.parts.index -= node.skip;
newMessage.parts.count -= node.skip;
if (node.hdrin) { // if we removed the header line then shift the counts by 1
newMessage.parts.index -= 1;
newMessage.parts.count -= 1;
if (has_parts && last && len === 0) {
send({complete:true});
}
}
node.linecount = 0;
done();
}
catch(e) { done(e); }
}
else { node.warn(RED._("csv.errors.csv_js")); done(); }
}
else {
if (!msg.hasOwnProperty("reset")) {
node.send(msg); // If no payload and not reset - just pass it on.
}
done();
}
});
}
if(RFC4180Mode) {
node.template = (n.temp || "")
node.sep = (n.sep || ',').replace(/\\t/g, "\t").replace(/\\n/g, "\n").replace(/\\r/g, "\r")
node.quo = '"'
// default to CRLF (RFC4180 Sec 2.1: "Each record is located on a separate line, delimited by a line break (CRLF)")
node.ret = (n.ret || "\r\n").replace(/\\n/g, "\n").replace(/\\r/g, "\r")
node.multi = n.multi || "one"
node.hdrin = n.hdrin || false
node.hdrout = n.hdrout || "none"
node.goodtmpl = true
node.skip = parseInt(n.skip || 0)
node.store = []
node.parsestrings = n.strings
node.include_empty_strings = n.include_empty_strings || false
node.include_null_values = n.include_null_values || false
if (node.parsestrings === undefined) { node.parsestrings = true }
if (node.hdrout === false) { node.hdrout = "none" }
if (node.hdrout === true) { node.hdrout = "all" }
const dontSendHeaders = node.hdrout === "none"
const sendHeadersOnce = node.hdrout === "once"
const sendHeadersAlways = node.hdrout === "all"
const sendHeaders = !dontSendHeaders && (sendHeadersOnce || sendHeadersAlways)
const quoteables = [node.sep, node.quo, "\n", "\r"]
const templateQuoteables = [',', '"', "\n", "\r"]
let badTemplateWarnOnce = true
const columnStringToTemplateArray = function (col, sep) {
// NOTE: enforce strict column template parsing in RFC4180 mode
const parsed = csv.parse(col, { separator: sep, quote: node.quo, outputStyle: 'array', strict: true })
if (parsed.headers.length > 0) { node.goodtmpl = true } else { node.goodtmpl = false }
return parsed.headers.length ? parsed.headers : null
}
const templateArrayToColumnString = function (template, keepEmptyColumns) {
// NOTE: enforce strict column template parsing in RFC4180 mode
const parsed = csv.parse('', {headers: template, headersOnly:true, separator: ',', quote: node.quo, outputStyle: 'array', strict: true })
return keepEmptyColumns
? parsed.headers.map(e => addQuotes(e || '', { separator: ',', quoteables: templateQuoteables}))
: parsed.header // exclues empty columns
// TODO: resolve inconsistency between CSV->JSON and JSON->CSV
// CSV->JSON: empty columns are excluded
// JSON->CSV: empty columns are kept in some cases
}
function addQuotes(cell, options) {
options = options || {}
return csv.quoteCell(cell, {
quote: options.quote || node.quo || '"',
separator: options.separator || node.sep || ',',
quoteables: options.quoteables || quoteables
})
}
const hasTemplate = (t) => t?.length > 0 && !(t.length === 1 && t[0] === '')
let template
try {
template = columnStringToTemplateArray(node.template, ',') || ['']
} catch (e) {
node.warn(RED._("csv.errors.bad_template")) // is warning really necessary now we have status?
node.status({ fill: "red", shape: "dot", text: RED._("csv.errors.bad_template") })
return // dont hook up the node
}
const noTemplate = hasTemplate(template) === false
node.hdrSent = false
node.on("input", function (msg, send, done) {
node.status({}) // clear status
if (msg.hasOwnProperty("reset")) {
node.hdrSent = false
}
if (msg.hasOwnProperty("payload")) {
let inputData = msg.payload
if (typeof inputData == "object") { // convert object to CSV string
try {
// first determine the payload kind. Array or objects? Array of primitives? Array of arrays? Just an object?
// then, if necessary, convert to an array of objects/arrays
let isObject = !Array.isArray(inputData) && typeof inputData === 'object'
let isArrayOfObjects = Array.isArray(inputData) && inputData.length > 0 && typeof inputData[0] === 'object'
let isArrayOfArrays = Array.isArray(inputData) && inputData.length > 0 && Array.isArray(inputData[0])
let isArrayOfPrimitives = Array.isArray(inputData) && inputData.length > 0 && typeof inputData[0] !== 'object'
if (isObject) {
inputData = [inputData]
isArrayOfObjects = true
isObject = false
} else if (isArrayOfPrimitives) {
inputData = [inputData]
isArrayOfArrays = true
isArrayOfPrimitives = false
}
const stringBuilder = []
if (!(noTemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
template = columnStringToTemplateArray(node.template) || ['']
}
// build header line
if (sendHeaders && node.hdrSent === false) {
if (hasTemplate(template) === false) {
if (msg.hasOwnProperty("columns")) {
template = columnStringToTemplateArray(msg.columns || "", ",") || ['']
}
else {
template = Object.keys(inputData[0]) || ['']
}
}
if (last) { newMessage.complete = true; }
send(newMessage);
stringBuilder.push(templateArrayToColumnString(template, true))
if (sendHeadersOnce) { node.hdrSent = true }
}
if (has_parts && last && len === 0) {
send({complete:true});
// build csv lines
for (let s = 0; s < inputData.length; s++) {
let row = inputData[s]
if (isArrayOfArrays) {
/*** row is an array of arrays ***/
const _hasTemplate = hasTemplate(template)
const len = _hasTemplate ? template.length : row.length
const result = []
for (let t = 0; t < len; t++) {
let cell = row[t]
if (cell === undefined) { cell = "" }
if(_hasTemplate) {
const header = template[t]
if (header) {
result[t] = addQuotes(RED.util.ensureString(cell))
}
} else {
result[t] = addQuotes(RED.util.ensureString(cell))
}
}
stringBuilder.push(result.join(node.sep))
} else {
/*** row is an object ***/
if (hasTemplate(template) === false && (msg.hasOwnProperty("columns"))) {
template = columnStringToTemplateArray(msg.columns || "", ",")
}
if (hasTemplate(template) === false) {
/*** row is an object but we still don't have a template ***/
if (badTemplateWarnOnce === true) {
node.warn(RED._("csv.errors.obj_csv"))
badTemplateWarnOnce = false
}
const rowData = []
for (let header in inputData[0]) {
if (row.hasOwnProperty(header)) {
const cell = row[header]
if (typeof cell !== "object") {
let cellValue = ""
if (cell !== undefined) {
cellValue += cell
}
rowData.push(addQuotes(cellValue))
}
}
}
stringBuilder.push(rowData.join(node.sep))
} else {
/*** row is an object and we have a template ***/
const rowData = []
for (let t = 0; t < template.length; t++) {
if (!template[t]) {
rowData.push('')
}
else {
let cellValue = inputData[s][template[t]]
if (cellValue === undefined) { cellValue = "" }
cellValue = RED.util.ensureString(cellValue)
rowData.push(addQuotes(cellValue))
}
}
stringBuilder.push(rowData.join(node.sep)); // add separator
}
}
}
// join lines, don't forget to add the last new line
msg.payload = stringBuilder.join(node.ret) + node.ret
msg.columns = templateArrayToColumnString(template)
if (msg.payload !== '') { send(msg) }
done()
}
catch (e) {
done(e)
}
node.linecount = 0;
done();
}
catch(e) { done(e); }
else if (typeof inputData == "string") { // convert CSV string to object
try {
let firstLine = true; // is this the first line
let last = false
let linecount = 0
const has_parts = msg.hasOwnProperty("parts")
// determine if this is a multi part message and if so what part we are processing
if (msg.hasOwnProperty("parts")) {
linecount = msg.parts.index
if (msg.parts.index > node.skip) { firstLine = false }
if (msg.parts.hasOwnProperty("count") && (msg.parts.index + 1 >= msg.parts.count)) { last = true }
}
// If skip is set, compute the cursor position to start parsing from
let _cursor = 0
if (node.skip > 0 && linecount < node.skip) {
for (; _cursor < inputData.length; _cursor++) {
if (firstLine && (linecount < node.skip)) {
if (inputData[_cursor] === "\r" || inputData[_cursor] === "\n") {
linecount += 1
}
continue
}
break
}
if (_cursor >= inputData.length) {
return // skip this line
}
}
// count the number of line breaks in the string
const noofCR = ((_cursor ? inputData.slice(_cursor) : inputData).match(/[\r\n]/g) || []).length
// if we have `parts` and we are outputting multiple objects and we have more than one line
// then we need to set firstLine to true so that we process the header line
if (has_parts && node.multi === "mult" && noofCR > 1) {
firstLine = true
}
// if we are processing the first line and the node has been set to extract the header line
// update the template with the header line
if (firstLine && node.hdrin === true) {
/** @type {import('./lib/csv/index.js').CSVParseOptions} */
const csvOptionsForHeaderRow = {
cursor: _cursor,
separator: node.sep,
quote: node.quo,
dataHasHeaderRow: true,
headersOnly: true,
outputStyle: 'array',
strict: true // enforce strict parsing of the header row
}
try {
const csvHeader = csv.parse(inputData, csvOptionsForHeaderRow)
template = csvHeader.headers
_cursor = csvHeader.cursor
} catch (e) {
// node.warn(RED._("csv.errors.bad_template")) // add warning?
node.status({ fill: "red", shape: "dot", text: RED._("csv.errors.bad_template") })
throw e
}
}
// now we process the data lines
/** @type {import('./lib/csv/index.js').CSVParseOptions} */
const csvOptions = {
cursor: _cursor,
separator: node.sep,
quote: node.quo,
dataHasHeaderRow: false,
headers: hasTemplate(template) ? template : null,
outputStyle: 'object',
includeNullValues: node.include_null_values,
includeEmptyStrings: node.include_empty_strings,
parseNumeric: node.parsestrings,
strict: false // relax the strictness of the parser for data rows
}
const csvParseResult = csv.parse(inputData, csvOptions)
const data = csvParseResult.data
// output results
if (node.multi !== "one") {
if (has_parts && noofCR <= 1) {
if (data.length > 0) {
node.store.push(...data)
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store
msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode'
delete msg.parts
send(msg)
node.store = []
}
}
else {
msg.columns = csvParseResult.header
// msg._mode = 'RFC4180 mode'
msg.payload = data
send(msg); // finally send the array
}
}
else {
const len = data.length
for (let row = 0; row < len; row++) {
const newMessage = RED.util.cloneMessage(msg)
newMessage.columns = csvParseResult.header
newMessage.payload = data[row]
if (!has_parts) {
newMessage.parts = {
id: msg._msgid,
index: row,
count: len
}
}
else {
newMessage.parts.index -= node.skip
newMessage.parts.count -= node.skip
if (node.hdrin) { // if we removed the header line then shift the counts by 1
newMessage.parts.index -= 1
newMessage.parts.count -= 1
}
}
if (last) { newMessage.complete = true }
// newMessage._mode = 'RFC4180 mode'
send(newMessage)
}
if (has_parts && last && len === 0) {
// send({complete:true, _mode: 'RFC4180 mode'})
send({ complete: true })
}
}
node.linecount = 0
done()
}
catch (e) {
done(e)
}
}
else {
// RFC-vs-legacy mode difference: In RFC mode, we throw catchable errors and provide a status message
const err = new Error(RED._("csv.errors.csv_js"))
node.status({ fill: "red", shape: "dot", text: err.message })
done(err)
}
}
else { node.warn(RED._("csv.errors.csv_js")); done(); }
}
else {
if (!msg.hasOwnProperty("reset")) {
node.send(msg); // If no payload and not reset - just pass it on.
else {
if (!msg.hasOwnProperty("reset")) {
node.send(msg); // If no payload and not reset - just pass it on.
}
done()
}
done();
}
});
})
}
}
RED.nodes.registerType("csv",CSVNode);
RED.nodes.registerType("csv",CSVNode)
}

View File

@@ -0,0 +1,324 @@
/**
* @typedef {Object} CSVParseOptions
* @property {number} [cursor=0] - an index into the CSV to start parsing from
* @property {string} [separator=','] - the separator character
* @property {string} [quote='"'] - the quote character
* @property {boolean} [headersOnly=false] - only parse the headers and return them
* @property {string[]} [headers=[]] - an array of headers to use instead of the first row of the CSV data
* @property {boolean} [dataHasHeaderRow=true] - whether the CSV data to parse has a header row
* @property {boolean} [outputHeader=true] - whether the output data should include a header row (only applies to array output)
* @property {boolean} [parseNumeric=false] - parse numeric values into numbers
* @property {boolean} [includeNullValues=false] - include null values in the output
* @property {boolean} [includeEmptyStrings=true] - include empty strings in the output
* @property {string} [outputStyle='object'] - output an array of arrays or an array of objects
* @property {boolean} [strict=false] - throw an error if the CSV is malformed
*/
/**
* Parses a CSV string into an array of arrays or an array of objects.
*
* NOTES:
* * Deviations from the RFC4180 spec (for the sake of user fiendliness, system implementations and flexibility), this parser will:
* * accept any separator character, not just `,`
* * accept any quote character, not just `"`
* * parse `\r`, `\n` or `\r\n` as line endings (RRFC4180 2.1 states lines are separated by CRLF)
* * Only single character `quote` is supported
* * `quote` is `"` by default
* * Any cell that contains a `quote` or `separator` will be quoted
* * Any `quote` characters inside a cell will be escaped as per RFC 4180 2.6
* * Only single character `separator` is supported
* * Only `array` and `object` output styles are supported
* * `array` output style is an array of arrays [[],[],[]]
* * `object` output style is an array of objects [{},{},{}]
* * Only `headers` or `dataHasHeaderRow` are supported, not both
* @param {string} csvIn - the CSV string to parse
* @param {CSVParseOptions} parseOptions - options
* @throws {Error}
*/
function parse(csvIn, parseOptions) {
/* Normalise options */
parseOptions = parseOptions || {};
const separator = parseOptions.separator ?? ',';
const quote = parseOptions.quote ?? '"';
const headersOnly = parseOptions.headersOnly ?? false;
const headers = Array.isArray(parseOptions.headers) ? parseOptions.headers : []
const dataHasHeaderRow = parseOptions.dataHasHeaderRow ?? true;
const outputHeader = parseOptions.outputHeader ?? true;
const parseNumeric = parseOptions.parseNumeric ?? false;
const includeNullValues = parseOptions.includeNullValues ?? false;
const includeEmptyStrings = parseOptions.includeEmptyStrings ?? true;
const outputStyle = ['array', 'object'].includes(parseOptions.outputStyle) ? parseOptions.outputStyle : 'object'; // 'array [[],[],[]]' or 'object [{},{},{}]
const strict = parseOptions.strict ?? false
/* Local variables */
const cursorMax = csvIn.length;
const ouputArrays = outputStyle === 'array';
const headersSupplied = headers.length > 0
// The original regex was an "is-a-number" positive logic test. /^ *[-]?(?!E)(?!0\d)\d*\.?\d*(E-?\+?)?\d+ *$/i;
// Below, is less strict and inverted logic but coupled with +cast it is 13%+ faster than original regex+parsefloat
// and has the benefit of understanding hexadecimals, binary and octal numbers.
const skipNumberConversion = /^ *(\+|-0\d|0\d)/
const cellBuilder = []
let rowBuilder = []
let cursor = typeof parseOptions.cursor === 'number' ? parseOptions.cursor : 0;
let newCell = true, inQuote = false, closed = false, output = [];
/* inline helper functions */
const finaliseCell = () => {
let cell = cellBuilder.join('')
cellBuilder.length = 0
// push the cell:
// NOTE: if cell is empty but newCell==true, then this cell had zero chars - push `null`
// otherwise push empty string
return rowBuilder.push(cell || (newCell ? null : ''))
}
const finaliseRow = () => {
if (cellBuilder.length) {
finaliseCell()
}
if (rowBuilder.length) {
output.push(rowBuilder)
rowBuilder = []
}
}
/* Main parsing loop */
while (cursor < cursorMax) {
const char = csvIn[cursor]
if (inQuote) {
if (char === quote && csvIn[cursor + 1] === quote) {
cellBuilder.push(quote)
cursor += 2;
newCell = false;
closed = false;
} else if (char === quote) {
inQuote = false;
cursor += 1;
newCell = false;
closed = true;
} else {
cellBuilder.push(char)
newCell = false;
closed = false;
cursor++;
}
} else {
if (char === separator) {
finaliseCell()
cursor += 1;
newCell = true;
closed = false;
} else if (char === quote) {
if (newCell) {
inQuote = true;
cursor += 1;
newCell = false;
closed = false;
}
else if (strict) {
throw new UnquotedQuoteError(cursor)
} else {
// not strict, keep 1 quote if the next char is not a cell/record separator
cursor++
if (csvIn[cursor] && csvIn[cursor] !== '\n' && csvIn[cursor] !== '\r' && csvIn[cursor] !== separator) {
cellBuilder.push(char)
if (csvIn[cursor] === quote) {
cursor++ // skip the next quote
}
}
}
} else {
if (char === '\n' || char === '\r') {
finaliseRow()
if (csvIn[cursor + 1] === '\n') {
cursor += 2;
} else {
cursor++
}
newCell = true;
closed = false;
if (headersOnly) {
break
}
} else {
if (closed) {
if (strict) {
throw new DataAfterCloseError(cursor)
} else {
cursor--; // move back to grab the previously discarded char
closed = false
}
} else {
cellBuilder.push(char)
newCell = false;
cursor++;
}
}
}
}
}
if (strict && inQuote) {
throw new ParseError(`Missing quote, unclosed cell`, cursor)
}
// finalise the last cell/row
finaliseRow()
let firstRowIsHeader = false
// if no headers supplied, generate them
if (output.length >= 1) {
if (headersSupplied) {
// headers already supplied
} else if (dataHasHeaderRow) {
// take the first row as the headers
headers.push(...output[0])
firstRowIsHeader = true
} else {
// generate headers col1, col2, col3, etc
for (let i = 0; i < output[0].length; i++) {
headers.push("col" + (i + 1))
}
}
}
const finalResult = {
/** @type {String[]} headers as an array of string */
headers: headers,
/** @type {String} headers as a comma-separated string */
header: null,
/** @type {Any[]} Result Data (may include header row: check `firstRowIsHeader` flag) */
data: [],
/** @type {Boolean|undefined} flag to indicate if the first row is a header row (only applies when `outputStyle` is 'array') */
firstRowIsHeader: undefined,
/** @type {'array'|'object'} flag to indicate the output style */
outputStyle: outputStyle,
/** @type {Number} The current cursor position */
cursor: cursor,
}
const quotedHeaders = []
for (let i = 0; i < headers.length; i++) {
if (!headers[i]) {
continue
}
quotedHeaders.push(quoteCell(headers[i], { quote, separator: ',' }))
}
finalResult.header = quotedHeaders.join(',') // always quote headers and join with comma
// output is an array of arrays [[],[],[]]
if (ouputArrays || headersOnly) {
if (!firstRowIsHeader && !headersOnly && outputHeader && headers.length > 0) {
if (output.length > 0) {
output.unshift(headers)
} else {
output = [headers]
}
firstRowIsHeader = true
}
if (headersOnly) {
delete finalResult.firstRowIsHeader
return finalResult
}
finalResult.firstRowIsHeader = firstRowIsHeader
finalResult.data = (firstRowIsHeader && !outputHeader) ? output.slice(1) : output
return finalResult
}
// output is an array of objects [{},{},{}]
const outputObjects = []
let i = firstRowIsHeader ? 1 : 0
for (; i < output.length; i++) {
const rowObject = {}
let isEmpty = true
for (let j = 0; j < headers.length; j++) {
if (!headers[j]) {
continue
}
let v = output[i][j] === undefined ? null : output[i][j]
if (v === null && !includeNullValues) {
continue
} else if (v === "" && !includeEmptyStrings) {
continue
} else if (parseNumeric === true && v && !skipNumberConversion.test(v)) {
const vTemp = +v
const isNumber = !isNaN(vTemp)
if(isNumber) {
v = vTemp
}
}
rowObject[headers[j]] = v
isEmpty = false
}
// determine if this row is empty
if (!isEmpty) {
outputObjects.push(rowObject)
}
}
finalResult.data = outputObjects
delete finalResult.firstRowIsHeader
return finalResult
}
/**
* Quotes a cell in a CSV string if necessary. Addiionally, any double quotes inside the cell will be escaped as per RFC 4180 2.6 (https://datatracker.ietf.org/doc/html/rfc4180#section-2).
* @param {string} cell - the string to quote
* @param {*} options - options
* @param {string} [options.quote='"'] - the quote character
* @param {string} [options.separator=','] - the separator character
* @param {string[]} [options.quoteables] - an array of characters that, when encountered, will trigger the application of outer quotes
* @returns
*/
function quoteCell(cell, { quote = '"', separator = ",", quoteables } = {
quote: '"',
separator: ",",
quoteables: [quote, separator, '\r', '\n']
}) {
quoteables = quoteables || [quote, separator, '\r', '\n'];
let doubleUp = false;
if (cell.indexOf(quote) !== -1) { // add double quotes if any quotes
doubleUp = true;
}
const quoteChar = quoteables.some(q => cell.includes(q)) ? quote : '';
return quoteChar + (doubleUp ? cell.replace(/"/g, '""') : cell) + quoteChar;
}
// #region Custom Error Classes
class ParseError extends Error {
/**
* @param {string} message - the error message
* @param {number} cursor - the cursor index where the error occurred
*/
constructor(message, cursor) {
super(message)
this.name = 'ParseError'
this.cursor = cursor
}
}
class UnquotedQuoteError extends ParseError {
/**
* @param {number} cursor - the cursor index where the error occurred
*/
constructor(cursor) {
super('Quote found in the middle of an unquoted field', cursor)
this.name = 'UnquotedQuoteError'
}
}
class DataAfterCloseError extends ParseError {
/**
* @param {number} cursor - the cursor index where the error occurred
*/
constructor(cursor) {
super('Data found after closing quote', cursor)
this.name = 'DataAfterCloseError'
}
}
// #endregion
exports.parse = parse
exports.quoteCell = quoteCell
exports.ParseError = ParseError
exports.UnquotedQuoteError = UnquotedQuoteError
exports.DataAfterCloseError = DataAfterCloseError

View File

@@ -15,7 +15,11 @@
-->
<script type="text/html" data-template-name="split">
<div class="form-row"><span data-i18n="[html]split.intro"></span></div>
<!-- <div class="form-row"><span data-i18n="[html]split.intro"></span></div> -->
<div class="form-row">
<label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.split"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row"><span data-i18n="[html]split.strBuff"></span></div>
<div class="form-row">
<label for="node-input-splt" style="padding-left:10px; margin-right:-10px;" data-i18n="split.splitUsing"></label>
@@ -39,10 +43,9 @@
<label for="node-input-addname-cb" style="width:auto;" data-i18n="split.addname"></label>
<input type="text" id="node-input-addname" style="width:70%">
</div>
<hr/>
<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">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
</script>
@@ -57,7 +60,8 @@
arraySplt: {value:1},
arraySpltType: {value:"len"},
stream: {value:false},
addname: {value:"", validate: RED.validators.typedInput({ type: 'msg', allowBlank: true })}
addname: {value:"", validate: RED.validators.typedInput({ type: 'msg', allowBlank: true })},
property: {value:"payload",required:true}
},
inputs:1,
outputs:1,
@@ -69,6 +73,10 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
$("#node-input-splt").typedInput({
default: 'str',
typeField: $("#node-input-spltType"),

View File

@@ -19,13 +19,13 @@ module.exports = function(RED) {
function sendArray(node,msg,array,send) {
for (var i = 0; i < array.length-1; i++) {
msg.payload = array[i];
RED.util.setMessageProperty(msg,node.property,array[i]);
msg.parts.index = node.c++;
if (node.stream !== true) { msg.parts.count = array.length; }
send(RED.util.cloneMessage(msg));
}
if (node.stream !== true) {
msg.payload = array[i];
RED.util.setMessageProperty(msg,node.property,array[i]);
msg.parts.index = node.c++;
msg.parts.count = array.length;
send(RED.util.cloneMessage(msg));
@@ -40,10 +40,12 @@ module.exports = function(RED) {
node.stream = n.stream;
node.spltType = n.spltType || "str";
node.addname = n.addname || "";
node.property = n.property||"payload";
try {
if (node.spltType === "str") {
this.splt = (n.splt || "\\n").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0");
} else if (node.spltType === "bin") {
}
else if (node.spltType === "bin") {
var spltArray = JSON.parse(n.splt);
if (Array.isArray(spltArray)) {
this.splt = Buffer.from(spltArray);
@@ -51,7 +53,8 @@ module.exports = function(RED) {
throw new Error("not an array");
}
this.spltBuffer = spltArray;
} else if (node.spltType === "len") {
}
else if (node.spltType === "len") {
this.splt = parseInt(n.splt);
if (isNaN(this.splt) || this.splt < 1) {
throw new Error("invalid split length: "+n.splt);
@@ -69,18 +72,22 @@ module.exports = function(RED) {
node.buffer = Buffer.from([]);
node.pendingDones = [];
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("payload")) {
var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) {
if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack
else { msg.parts = {}; }
msg.parts.id = RED.util.generateId(); // generate a random id
if (node.property !== "payload") {
msg.parts.property = node.property;
}
delete msg._msgid;
if (typeof msg.payload === "string") { // Split String into array
msg.payload = (node.remainder || "") + msg.payload;
if (typeof value === "string") { // Split String into array
value = (node.remainder || "") + value;
msg.parts.type = "string";
if (node.spltType === "len") {
msg.parts.ch = "";
msg.parts.len = node.splt;
var count = msg.payload.length/node.splt;
var count = value.length/node.splt;
if (Math.floor(count) !== count) {
count = Math.ceil(count);
}
@@ -89,9 +96,9 @@ module.exports = function(RED) {
node.c = 0;
}
var pos = 0;
var data = msg.payload;
var data = value;
for (var i=0; i<count-1; i++) {
msg.payload = data.substring(pos,pos+node.splt);
RED.util.setMessageProperty(msg,node.property,data.substring(pos,pos+node.splt));
msg.parts.index = node.c++;
pos += node.splt;
send(RED.util.cloneMessage(msg));
@@ -102,7 +109,7 @@ module.exports = function(RED) {
}
node.remainder = data.substring(pos);
if ((node.stream !== true) || (node.remainder.length === node.splt)) {
msg.payload = node.remainder;
RED.util.setMessageProperty(msg,node.property,node.remainder);
msg.parts.index = node.c++;
send(RED.util.cloneMessage(msg));
node.pendingDones.forEach(d => d());
@@ -119,47 +126,48 @@ module.exports = function(RED) {
if (!node.spltBufferString) {
node.spltBufferString = node.splt.toString();
}
a = msg.payload.split(node.spltBufferString);
a = value.split(node.spltBufferString);
msg.parts.ch = node.spltBuffer; // pass the split char to other end for rejoin
} else if (node.spltType === "str") {
a = msg.payload.split(node.splt);
a = value.split(node.splt);
msg.parts.ch = node.splt; // pass the split char to other end for rejoin
}
sendArray(node,msg,a,send);
done();
}
}
else if (Array.isArray(msg.payload)) { // then split array into messages
else if (Array.isArray(value)) { // then split array into messages
msg.parts.type = "array";
var count = msg.payload.length/node.arraySplt;
var count = value.length/node.arraySplt;
if (Math.floor(count) !== count) {
count = Math.ceil(count);
}
msg.parts.count = count;
var pos = 0;
var data = msg.payload;
var data = value;
msg.parts.len = node.arraySplt;
for (var i=0; i<count; i++) {
msg.payload = data.slice(pos,pos+node.arraySplt);
var m = data.slice(pos,pos+node.arraySplt);
if (node.arraySplt === 1) {
msg.payload = msg.payload[0];
m = m[0];
}
RED.util.setMessageProperty(msg,node.property,m);
msg.parts.index = i;
pos += node.arraySplt;
send(RED.util.cloneMessage(msg));
}
done();
}
else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) {
else if ((typeof value === "object") && !Buffer.isBuffer(value)) {
var j = 0;
var l = Object.keys(msg.payload).length;
var pay = msg.payload;
var l = Object.keys(value).length;
var pay = value;
msg.parts.type = "object";
for (var p in pay) {
if (pay.hasOwnProperty(p)) {
msg.payload = pay[p];
RED.util.setMessageProperty(msg,node.property,pay[p]);
if (node.addname !== "") {
msg[node.addname] = p;
RED.util.setMessageProperty(msg,node.addname,p);
}
msg.parts.key = p;
msg.parts.index = j;
@@ -170,9 +178,9 @@ module.exports = function(RED) {
}
done();
}
else if (Buffer.isBuffer(msg.payload)) {
var len = node.buffer.length + msg.payload.length;
var buff = Buffer.concat([node.buffer, msg.payload], len);
else if (Buffer.isBuffer(value)) {
var len = node.buffer.length + value.length;
var buff = Buffer.concat([node.buffer, value], len);
msg.parts.type = "buffer";
if (node.spltType === "len") {
var count = buff.length/node.splt;
@@ -186,7 +194,7 @@ module.exports = function(RED) {
var pos = 0;
msg.parts.len = node.splt;
for (var i=0; i<count-1; i++) {
msg.payload = buff.slice(pos,pos+node.splt);
RED.util.setMessageProperty(msg,node.property,buff.slice(pos,pos+node.splt));
msg.parts.index = node.c++;
pos += node.splt;
send(RED.util.cloneMessage(msg));
@@ -197,7 +205,7 @@ module.exports = function(RED) {
}
node.buffer = buff.slice(pos);
if ((node.stream !== true) || (node.buffer.length === node.splt)) {
msg.payload = node.buffer;
RED.util.setMessageProperty(msg,node.property,node.buffer);
msg.parts.index = node.c++;
send(RED.util.cloneMessage(msg));
node.pendingDones.forEach(d => d());
@@ -230,7 +238,7 @@ module.exports = function(RED) {
var i = 0, p = 0;
pos = buff.indexOf(node.splt);
while (pos > -1) {
msg.payload = buff.slice(p,pos);
RED.util.setMessageProperty(msg,node.property,buff.slice(p,pos));
msg.parts.index = node.c++;
send(RED.util.cloneMessage(msg));
i++;
@@ -242,7 +250,7 @@ module.exports = function(RED) {
node.pendingDones = [];
}
if ((node.stream !== true) && (p < buff.length)) {
msg.payload = buff.slice(p,buff.length);
RED.util.setMessageProperty(msg,node.property,buff.slice(p,buff.length));
msg.parts.index = node.c++;
msg.parts.count = node.c++;
send(RED.util.cloneMessage(msg));
@@ -298,7 +306,6 @@ module.exports = function(RED) {
return exp
}
function reduceMessageGroup(node,msgInfos,exp,fixup,count,accumulator,done) {
var msgInfo = msgInfos.shift();
exp.assign("I", msgInfo.msg.parts.index);
@@ -515,13 +522,13 @@ module.exports = function(RED) {
if (typeof group.joinChar !== 'string') {
groupJoinChar = group.joinChar.toString();
}
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
RED.util.setMessageProperty(group.msg,group?.prop||"payload",group.payload.join(groupJoinChar));
}
else {
if (node.propertyType === 'full') {
group.msg = RED.util.cloneMessage(group.msg);
}
RED.util.setMessageProperty(group.msg,node.property,group.payload);
RED.util.setMessageProperty(group.msg,group?.prop||"payload",group.payload);
}
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
group.msg.parts = group.msg.parts.parts;
@@ -589,7 +596,7 @@ module.exports = function(RED) {
}
if (node.mode === 'auto' && (!msg.hasOwnProperty("parts")||!msg.parts.hasOwnProperty("id"))) {
// if a blank reset messag erest it all.
// if a blank reset message reset it all.
if (msg.hasOwnProperty("reset")) {
if (inflight && inflight.hasOwnProperty("partId") && inflight[partId].timeout) {
clearTimeout(inflight[partId].timeout);
@@ -618,6 +625,7 @@ module.exports = function(RED) {
propertyKey = msg.parts.key;
arrayLen = msg.parts.len;
propertyIndex = msg.parts.index;
property = RED.util.getMessageProperty(msg,msg.parts.property||"payload");
}
else if (node.mode === 'reduce') {
return processReduceMessageQueue({msg, send, done});
@@ -719,6 +727,8 @@ module.exports = function(RED) {
completeSend(partId)
}, node.timer)
}
if (node.mode === "auto") { inflight[partId].prop = msg.parts.property; }
else { inflight[partId].prop = node.property; }
}
inflight[partId].dones.push(done);

View File

@@ -849,7 +849,13 @@
"newline": "Newline",
"usestrings": "parse numerical values",
"include_empty_strings": "include empty strings",
"include_null_values": "include null values"
"include_null_values": "include null values",
"spec": "Parser"
},
"spec": {
"rfc": "RFC4180",
"legacy": "Legacy",
"legacy_warning": "Legacy mode will be removed in a future release."
},
"placeholder": {
"columns": "comma-separated column names"
@@ -878,6 +884,7 @@
"once": "send headers once, until msg.reset"
},
"errors": {
"bad_template": "Malformed columns template.",
"csv_js": "This node only handles CSV strings or js objects.",
"obj_csv": "No columns template specified for object -> CSV.",
"bad_csv": "Malformed CSV data - output probably corrupt."
@@ -1001,7 +1008,7 @@
"tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process."
},
"split": {
"split": "split",
"split": "Split",
"intro": "Split <code>msg.payload</code> based on type:",
"object": "<b>Object</b>",
"objectSend": "Send a message for each key/value pair",

View File

@@ -30,6 +30,8 @@
before being sent.</p>
<p>If <code>msg._session</code> is not present the payload is
sent to <b>all</b> connected clients.</p>
<p>In Reply-to mode, setting <code>msg.reset = true</code> will reset the connection
specified by _session.id, or all connections if no _session.id is specified.</p>
<p><b>Note: </b>On some systems you may need root or administrator access
to access ports below 1024.</p>
</script>
@@ -40,6 +42,8 @@
returned characters into a fixed buffer, match a specified character before returning,
wait a fixed timeout from first reply and then return, sit and wait for data, or send then close the connection
immediately, without waiting for a reply.</p>
<p>If in sit and wait mode (remain connected) you can send <code>msg.reset = true</code> or <code>msg.reset = "host:port"</code> to force a break in
the connection and an automatic reconnection.</p>
<p>The response will be output in <code>msg.payload</code> as a buffer, so you may want to .toString() it.</p>
<p>If you leave tcp host or port blank they must be set by using the <code>msg.host</code> and <code>msg.port</code> properties in every message sent to the node.</p>
</script>

View File

@@ -36,7 +36,9 @@
</dl>
<h3>Details</h3>
<p>The column template can contain an ordered list of column names. When converting CSV to an object, the column names
will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.</p>
will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.
<p>When the RFC parser is selected, the column template must be compliant with RFC4180.</p>
</p>
<p>When converting to CSV, the columns template is used to identify which properties to extract from the object and in what order.</p>
<p>If the columns template is blank then you can use a simple comma separated list of properties supplied in <code>msg.columns</code> to
determine what to extract and in what order. If neither are present then all the object properties are output in the order
@@ -49,4 +51,5 @@
<p>If outputting multiple messages they will have their <code>parts</code> property set and form a complete message sequence.</p>
<p>If the node is set to only send column headers once, then setting <code>msg.reset</code> to any value will cause the node to resend the headers.</p>
<p><b>Note:</b> the column template must be comma separated - even if a different separator is chosen for the data.</p>
<p><b>Note:</b> in RFC mode, catchable errors will be thrown for malformed CSV headers and invalid input payload data</p>
</script>

View File

@@ -0,0 +1,37 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="inject">
<p>Inyecta un mensaje en un flujo ya sea manualmente o a intervalos regulares. La carga del mensaje puede ser de diversos tipos, incluidas cadenas, objetos JavaScript o la hora actual.</p>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">varios</span></dt>
<dd>La carga útil configurada del mensaje.</dd>
<dt class="optional">topic <span class="property-type">texto</span></dt>
<dd>Una propiedad opcional que se puede configurar en el nodo.</dd>
</dl>
<h3>Detalles</h3>
<p>El nodo Inject puede iniciar un flujo con un valor de carga específico.
La carga predeterminada es una marca de tiempo de la hora actual en milisegundos desde el 1 de enero de 1970.</p>
<p>El nodo también admite la inyección de cadenas, números, valores booleanos, objetos JavaScript o valores de contexto global/de flujo.</p>
<p>De forma predeterminada, el nodo se activa manualmente haciendo clic en su botón dentro del editor. También se puede configurar para inyectar a intervalos regulares o según un cronograma.</p>
<p>También se puede configurar para inyectar una vez cuando se inician los flujos.</p>
<p>El <i>intervalo</i> máximo que se puede especificar es de aproximadamente 596 horas/24 días. Sin embargo, si necesitas intervalos superiores a un día, deberías considerar el uso de un nodo programador que pueda hacer frente a cortes de energía y reinicios.</p>
<p><b>Nota</b>: Las opciones <i>"Intervalo entre tiempos"</i> y <i>"en un momento específico"</i> utilizan el sistema cron estándar.
Esto significa que 20 minutos serán en la próxima hora, 20 minutos después y 40 minutos después, no dentro de 20 minutos.
Si quieres cada 20 minutos a partir de ahora, utiliza la opción <i>"intervalo"</i>.</p>
<p><b>Nota</b>: Para incluir una nueva línea en una cadena, debes usar el nodo Función o Plantilla para crear la carga.</p>
</script>

View File

@@ -0,0 +1,26 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="debug">
<p>Muestra las propiedades del mensaje seleccionado en la pestaña de la barra lateral de depuración y, opcionalmente, el registro de tiempo de ejecución. De forma predeterminada muestra <code>msg.payload</code>, pero se puede configurar para mostrar cualquier propiedad, el mensaje completo o el resultado de una expresión JSONata.</p>
<h3>Detalles</h3>
<p>La barra lateral de depuración proporciona una vista estructurada de los mensajes que se envían, lo que facilita la comprensión de su estructura.</p>
<p>Los objetos y matrices de JavaScript se pueden contraer y expandir según sea necesario. Los objetos del búfer se pueden mostrar como datos sin procesar o como una cadena, si es posible.</p>
<p>Junto a cada mensaje, la barra lateral de depuración incluye información sobre la hora en que se recibió el mensaje, el nodo que lo envió y el tipo de mensaje.
Al hacer clic en la identificación del nodo de origen, se mostrará ese nodo dentro del espacio de trabajo.</p>
<p>El botón del nodo se puede utilizar para habilitar o deshabilitar su salida. Se recomienda deshabilitar o eliminar cualquier nodo de depuración que no se esté utilizando.</p>
<p>El nodo también se puede configurar para enviar todos los mensajes al registro de ejecución o para enviar mensajes breves (32 caracteres) al texto de estado en el nodo de depuración.</p>
</script>

View File

@@ -0,0 +1,24 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="complete">
<p>Activar un flujo cuando otro nodo completa su manejo de un mensaje.</p>
<h3>Detalles</h3>
<p>Si un nodo informa cuando ha terminado de manejar un mensaje, este nodo se puede utilizar para desencadenar un segundo flujo.</p>
<p>Por ejemplo, esto se puede utilizar junto con un nodo sin puerto de salida, como el nodo de envío de correo electrónico, para continuar el flujo.</p>
<p>Este nodo debe configurarse para manejar el evento para los nodos seleccionados en el flujo. A diferencia del nodo Catch (Captura), no proporciona un modo de "manejar todo" que se aplica automáticamente a todos los nodos del flujo.</p>
<p>No todos los nodos activarán este evento; dependerá de si se han implementado para admitir esta característica tal como se introdujo en Node-RED 1.0.</p>
</script>

View File

@@ -0,0 +1,36 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="catch">
<p>Capturar errores arrojados por nodos en la misma pestaña.</p>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>error.message <span class="property-type">texto</span></dt>
<dd>el mensaje de error.</dd>
<dt>error.source.id <span class="property-type">texto</span></dt>
<dd>la identificación del nodo que arrojó el error.</dd>
<dt>error.source.type <span class="property-type">texto</span></dt>
<dd>el tipo de nodo que arrojó el error.</dd>
<dt>error.source.name <span class="property-type">texto</span></dt>
<dd>el nombre, si está configurado, del nodo que arrojó el error.</dd>
</dl>
<h3>Detalles</h3>
<p>Si un nodo genera un error mientras maneja un mensaje, el flujo normalmente se detendrá. Este nodo se puede utilizar para detectar esos errores y manejarlos con un flujo dedicado.</p>
<p>De forma predeterminada, el nodo detectará los errores generados por cualquier nodo en la misma pestaña. Alternativamente, puede dirigirse a nodos específicos o configurarse para detectar solo errores que aún no hayan sido detectados por un nodo de captura "dirigido".</p>
<p>Cuando se produce un error, todos los nodos de captura coincidentes recibirán el mensaje.</p>
<p>Si se produce un error dentro de un subflujo, el error será manejado por cualquier nodo de captura dentro del subflujo. Si no existe ninguno, el error se propagará hasta la pestaña en la que se encuentra la instancia del subflujo.</p>
<p>Si el mensaje ya tiene una propiedad <code>error</code>, se copia a <code>_error</code>.</p>
</script>

View File

@@ -0,0 +1,34 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="status">
<p>Informar mensajes de estado de otros nodos en la misma pestaña.</p>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>status.text <span class="property-type">texto</span></dt>
<dd>el texto de estado.</dd>
<dt>status.source.type <span class="property-type">texto</span></dt>
<dd>el tipo de nodo que informó el estado.</dd>
<dt>status.source.id <span class="property-type">texto</span></dt>
<dd>la identificación del nodo que informó el estado.</dd>
<dt>status.source.name <span class="property-type">texto</span></dt>
<dd>el nombre, si está configurado, del nodo que informó el estado.</dd>
</dl>
<h3>Detalles</h3>
<p>Este nodo no produce una <code>carga</code>.</p>
<p>De forma predeterminada, el nodo informa el estado de todos los nodos en la misma pestaña del espacio de trabajo.
Se puede configurar para informar selectivamente el estado de nodos individuales.</p>
</script>

View File

@@ -0,0 +1,53 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="link in">
<p>Crea cables virtuales entre flujos.</p>
<h3>Detalles</h3>
<p>El nodo se puede conectar a cualquier nodo <code>enlace salida</code> que exista en cualquier pestaña. Una vez conectados, se comportan como si estuvieran conectados entre si.</p>
<p>Los cables entre los nodos de enlace solo se muestran cuando se selecciona un nodo de enlace. Si hay cables a otras pestañas, se muestra un nodo virtual en el que se puede hacer clic para saltar a la pestaña correspondiente.</p>
<p><b>Nota: </b>No se pueden crear enlaces que entren o salgan de un subflujo.</p>
</script>
<script type="text/html" data-help-name="link out">
<p>Crea cables virtuales entre flujos.</p>
<h3>Detalles</h3>
<p>Este nodo se puede configurar para enviar mensajes a todos los nodos <code>enlace entrada</code> a los que está conectado o para enviar una respuesta al nodo <code>enlace llamada</code> que activó el flujo.</p>
<p>Cuando está en modo 'enviar a todos', los cables entre los nodos de enlace solo se muestran cuando se selecciona el nodo. Si hay cables a otras pestañas, se muestra un nodo virtual en el que se puede hacer clic para saltar a la pestaña correspondiente.</p>
<p><b>Nota: </b>No se pueden crear enlaces que entren o salgan de un subflujo.</p>
</script>
<script type="text/html" data-help-name="link call">
<p>Llama a un flujo que comienza con un enlace <code>entrada</code> y transmite la respuesta.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">target<span class="property-type">texto</span></dt>
<dd>Cuando la opción <b>Tipo de enlace</b> está configurada en "Destino dinámico", establece <code>msg.target</code> al nombre del nodo <code>enlace entrada</code> al que quieres llamar.</dd>
</dl>
<h3>Detalles</h3>
<p>Este nodo se puede conectar a un nodo <code>enlace entrada</code> que existe en cualquier pestaña. El flujo conectado a ese nodo debe finalizar con un nodo <code>enlace salida</code> configurado en modo 'retorno'.</p>
<p>Cuando este nodo recibe un mensaje, se pasa al nodo <code>enlace entrada</code> conectado.
Luego espera una respuesta que enviará.</p>
<p>Si no se recibe respuesta dentro del tiempo de espera configurado, predeterminado de 30 segundos, el nodo registrará un error que se puede detectar utilizando el nodo <code>captura</code>.</p>
<p>Cuando la opción <b>Tipo de enlace</b> está configurada en "Destino dinámico", code>msg.target</code> puedes usarse para realizar una llamada al nodo <code>enlace entrada</code> por nombre o ID.
<ul>
<li>Si hay un nodo <code>enlace entrada</code> con el mismo ID, se llamará</li>
<li>Si hay dos o más nodos <code>enlace entrada</code> con el mismo nombre, se generará un error</li>
<li>Un <code>enlace llamada</code> no puede llamar a un nodo <code>enlace entrada</code> dentro de un subflujo</li>
</ul>
</p>
El flujo conectado a ese nodo debe finalizar con un nodo <code>enlace salida</code> configurado en modo 'retorno'.</p>
</script>

View File

@@ -0,0 +1,21 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="comment">
<p>Un nodo que puedes utilizar para agregar comentarios a tus flujos.</p>
<h3>Detalles</h3>
<p>El panel de edición aceptará la sintaxis de Markdown. El texto se representará en el panel lateral de información.</p>
</script>

View File

@@ -0,0 +1,3 @@
<script type="text/html" data-help-name="global-config">
<p>Un nodo para mantener la configuración global de flujos.</p>
</script>

View File

@@ -0,0 +1,24 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="unknown">
<p>Este nodo es un tipo desconocido para tu instalación de Node-RED.</p>
<h3>Detalles</h3>
<p><i>Si realizas la instanciación con el nodo en este estado, se conservará tu configuración, pero el flujo no se iniciará hasta que se instales el tipo que falta.</i></p>
<p>Utiliza la opción <code>Menú - Administrar paleta</code> para buscar e instalar nodos, o <b>npm install &lt;module&gt;</b> para instalar cualquier módulo que falte, reinicia Node-RED y vuelva a importar los nodos.</p>
<p>Es posible que este tipo de nodo ya esté instalado, pero le falte una dependencia. Consulta el registro de inicio de Node-RED para ver si hay mensajes de error asociados con el tipo de nodo que falta.</p>
<p>De lo contrario, debe ponerse en contacto con el autor del flujo para obtener una copia del tipo de nodo que falta.</p>
</script>

View File

@@ -0,0 +1,55 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="function">
<p>Una función de JavaScript que se ejecuta en los mensajes que recibe el nodo.</p>
<p>Los mensajes se pasan como un objeto JavaScript llamado <code>msg</code>.</p>
<p>Por convención, tendrá una propiedad <code>msg.payload</code> que contiene el cuerpo del mensaje.</p>
<p>Se espera que la función devuelva un objeto de mensaje (o varios objetos de mensaje), pero puede optar por no devolver nada para detener un flujo.</p>
<p>La pestaña <b>Al iniciar</b> contiene código que se ejecutará cada vez que se inicie el nodo. La pestaña <b>Al detener</b> contiene código que se ejecutará cuando se detenga el nodo.</p>
<p>Si el código <b>Al iniciar</b> devuelve un objeto Promise, el nodo no comenzará a manejar mensajes hasta que se resuelva la promesa.</p>
<h3>Detalles</h3>
<p>Ver la <a target="_blank" href="https://nodered.org/docs/writing-functions.html">documentación online</a> para obtener más información sobre cómo escribir funciones.</p>
<h4>Enviando mensajes</h4>
<p>La función puede devolver los mensajes que quieras pasar a los siguientes nodos del flujo o puede llamar a <code>node.send(messages)</code>.</p>
<p>Puede devolver/enviar:</p>
<ul>
<li>un único objeto de mensaje - pasado a los nodos conectados a la primera salida</li>
<li>una matriz de objetos de mensaje - pasados a nodos conectados a las salidas correspondientes</li>
</ul>
<p>Nota: El código de configuración se ejecuta durante la inicialización de los nodos. Por lo tanto, si se llama a <code>node.send</code> en la pestaña de configuración, es posible que los nodos posteriores no puedan recibir el mensaje.</p>
<p>Si algún elemento de la matriz es en mismo una matriz de mensajes, se envían varios mensajes a la salida correspondiente.</p>
<p>Si se devuelve nulo, ya sea solo o como elemento de la matriz, no se transmite ningún mensaje.</p>
<h4>Registro y manejo de errores</h4>
<p>Para registrar cualquier información o informar de un error, están disponibles las siguientes funciones:</p>
<ul>
<li><code>node.log("Log message")</code></li>
<li><code>node.warn("Warning")</code></li>
<li><code>node.error("Error")</code></li>
</ul>
</p>
<p>El nodo Captura (Catch) también se puede utilizar para gestionar errores. Para invocar un nodo Catch, pasa <code>msg</code> como segundo argumento a <code>node.error</code>:</p>
<pre>node.error("Error",msg);</pre>
<h4>Accediendo a la información del nodo</h4>
<p>Las siguientes propiedades están disponibles para acceder a información sobre el nodo:</p>
<ul>
<li><code>node.id</code> - identificación del nodo</li>
<li><code>node.name</code> - nombre del nodo</li>
<li><code>node.outputCount</code> - número de salidas de nodo</li>
</ul>
<h4>Usar variables de entorno</h4>
<p>Se puede acceder a las variables de entorno utilizando <code>env.get("MY_ENV_VAR")</code>.</p>
</script>

View File

@@ -0,0 +1,37 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="switch">
<p>Enruta mensajes según los valores de sus propiedades o la posición de la secuencia.</p>
<h3>Detalles</h3>
<p>Cuando llega un mensaje, el nodo evaluará cada una de las reglas definidas y reenviará el mensaje a las salidas correspondientes de cualquier regla coincidente.</p>
<p>Opcionalmente, se puede configurar el nodo para que deje de evaluar reglas una vez que encuentre una que coincida.</p>
<p>Las reglas se pueden evaluar en función de una propiedad de mensaje individual, una propiedad de flujo o contexto global, una variable de entorno o el resultado de una expresión JSONata.</p>
<h4>Reglas</h4>
<p>Hay cuatro tipos de reglas.:</p>
<ol>
Las reglas <li><b>valor</b> se evalúan con respecto a la propiedad configurada</li>
Las reglas <li><b>Secuencia</b> se pueden utilizar en secuencias de mensajes, como las generadas por el nodo Dividir</li>
<li>Se puede proporcionar una <b>expresión</b> JSONata que se evaluará en relación con todo el mensaje y coincidirá si la expresión devuelve un valor verdadero.</li>
<li>Se puede utilizar una regla <b>de lo contrario</b> para hacer coincidir si ninguna de las reglas anteriores coincide.</li>
</ol>
<h4>Notas</h4>
<p>Las reglas <code>verdadero/falso</code> y <code>es nulo</code> realizan comparaciones estrictas con esos tipos. No convierten entre tipos.</p>
<p>Las reglas <code>está vacío</code> y <code>no está vacío</code> se pueden utilizar para probar la longitud de cadenas, matrices y buffers, o el número de propiedades que tiene un objeto. Ninguna regla se aprobará si la propiedad que se está probando tiene un valor <code>booleano</code>, <code>null</code> o <code>indefinido</code>.</p>
<h4>Manejo de secuencias de mensajes</h4>
<p>De forma predeterminada, el nodo no modifica la propiedad <code>msg.parts</code> de los mensajes que forman parte de una secuencia.</p>
<p>La opción <b>recrear secuencias de mensajes</b> se puede habilitar para generar nuevas secuencias de mensajes para cada regla que coincida. En este modo, el nodo almacenará en buffer toda la secuencia entrante antes de enviar las nuevas secuencias. La configuración de tiempo de ejecución <code>nodeMessageBufferMaxLength</code> se puede utilizar para limitar cuántos nodos de mensajes almacenarán en el buffer.</p>
</script>

View File

@@ -0,0 +1,33 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="change">
<p>Establecer, cambiar, eliminar o mover propiedades de un mensaje, contexto de flujo o contexto global.</p>
<p>El nodo puede especificar múltiples reglas que se aplicarán en el orden en que se definan.</p>
<h3>Detalles</h3>
<p>Las operaciones disponibles son:</p>
<dl class="message-properties">
<dt>Establecer</dt>
<dd>establecer una propiedad. El valor puede ser de varios tipos diferentes o puede tomarse de un mensaje existente o de una propiedad de contexto.</dd>
<dt>Cambiar</dt>
<dd>buscar y reemplazar partes de la propiedad. Si las expresiones regulares están habilitadas, la propiedad "reemplazar con" puede incluir grupos de captura, por ejemplo <code>$1</code>. Reemplazar solo cambiará el tipo si hay una coincidencia completa.</dd>
<dt>Eliminar</dt>
<dd>eliminar una propiedad.</dd>
<dt>Mover</dt>
<dd>mover o cambiar el nombre de una propiedad.</dd>
</dl>
<p>El tipo "expresión" utiliza el lenguaje de consulta y expresión <a href="http://jsonata.org/" target="_new">JSONata</a>.</p>
</script>

View File

@@ -0,0 +1,42 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="range">
<p>Asigna un valor numérico a un rango diferente.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">número</span></dt>
<dd>La carga <i>debe</i> ser un número. Cualquier otra cosa intentará analizarse como un número y rechazarse si falla.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">número</span></dt>
<dd>El valor asignado al nuevo rango.</dd>
</dl>
<h3>Detalles</h3>
<p>Este nodo escalará linealmente el valor recibido. De forma predeterminada, el resultado no está restringido al rango definido en el nodo.</p>
<p><i>Escalar y limitar al rango objetivo</i> significa que el resultado nunca estará fuera del rango especificado dentro del rango objetivo.</p>
<p><i>Escalar y ajustar dentro del rango objetivo</i> significa que el resultado se ajustará dentro del rango objetivo.</p>
<p><i>Escalar, pero eliminar si está fuera del rango de entrada</i> significa que el resultado se escalará, pero cualquier entrada fuera del rango de entrada y salida se eliminará.</p>
<p>Por ejemplo, una entrada 0 - 10 asignada a 0 - 100.</p>
<table style="outline-width:#888 solid thin">
<tr><th width="80px">modo</th><th width="80px">entrada</th><th width="80px">salida</th></tr>
<tr><td><center>scale</center></td><td><center>12</center></td><td><center>120</center></td></tr>
<tr><td><center>limit</center></td><td><center>12</center></td><td><center>100</center></td></tr>
<tr><td><center>wrap</center></td><td><center>12</center></td><td><center>20</center></td></tr>
<tr><td><center>drop</center></td><td><center>12</center></td><td><center><i>(sin salida)</i></center></td></tr>
</table>
</script>

View File

@@ -0,0 +1,51 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="template">
<p>Establece una propiedad basada en la plantilla proporcionada.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>msg <span class="property-type">objeto</span></dt>
<dd>Un objeto de mensaje que contiene información para completar la plantilla.</dd>
<dt class="optional">template <span class="property-type">texto</span></dt>
<dd>Una plantilla que se completará desde <code>msg.payload</code>. Si no está configurado en el panel de edición, esto se puede configurar como una propiedad de msg.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>msg <span class="property-type">objeto</span></dt>
<dd>un mensaje con una propiedad establecida al completar la plantilla configurada con propiedades del mensaje entrante.</dd>
</dl>
<h3>Detalles</h3>
<p>De forma predeterminada, esto utiliza el formato <i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>, pero se puede desactivar si es necesario.</p>
<p>Por ejemplo, cuando una plantilla de:
<pre>Hola {{payload.name}}. Hoy es {{date}}</pre>
<p>recibe un mensaje que contiene:
<pre>{
date: "lunes",
payload: {
name: "Fred"
}
}</pre>
<p>La propiedad resultante será:
<pre>Hola Fred. Hoy es lunes</pre>
<p>Es posible utilizar una propiedad del contexto de flujo o del contexto global. Simplemente usa <code>{{flow.name}}</code> o <code>{{global.name}}</code>, o para el almacén persistente <code>store</code> usa <code>{{ flow[store].name}}</code> o <code>{{global[store].name}}</code>.
<p><b>Nota: </b>De forma predeterminada, <i>mustache</i> codificará cualquier entidad HTML o no alfanumérica en los valores que sustituye. Para evitar esto, utilice llaves <code>{{{triple}}}</code>.</p>
<p>Si necesita utilizar <code>{{ }}</code> en su contenido, puede cambiar los caracteres utilizados para marcar las secciones con plantilla. Por ejemplo, para usar <code>[[ ]]</code> en su lugar, agregue la siguiente línea en la parte superior de la plantilla:</p>
<pre>{{=[[ ]]=}}</pre>
<h4>Usando variables de entorno</h4>
<p>El nodo de plantilla puede acceder a variables de entorno utilizando la sintaxis:</p>
<pre>Mi color favorito es {{env.COLOUR}}.</pre>
</script>

View File

@@ -0,0 +1,39 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="delay">
<p>Retrasa cada mensaje que pasa por el nodo o limita la velocidad a la que pueden pasar.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">delay <span class="property-type">número</span></dt>
<dd>Establece el retraso, en milisegundos, que se aplicará al mensaje. Esta opción solo se aplica si el nodo está configurado para permitir que el mensaje anule el intervalo de retardo predeterminado configurado.</dd>
<dt class="optional">rate <span class="property-type">número</span></dt>
<dd>Establece el valor de la tasa en milisegundos entre mensajes. Esta propiedad sobrescribe el valor de tasa existente definido en la configuración del nodo cuando recibe el mensaje que contiene el valor <code>msg.rate</code> en milisegundos. Esta opción solo se aplica si el nodo está configurado para permitir que el mensaje anule el intervalo de velocidad predeterminado configurado.</dd>
<dt class="optional">reset</dt>
<dd>Si el mensaje recibido tiene esta propiedad establecida en cualquier valor, todos los mensajes pendientes retenidos por el nodo se borran sin enviarse.</dd>
<dt class="optional">flush</dt>
<dd>Si el mensaje recibido tiene esta propiedad establecida en un valor numérico, esa cantidad de mensajes se publicará inmediatamente. Si se establece en cualquier otro tipo (por ejemplo, booleano), todos los mensajes pendientes retenidos por el nodo se envían inmediatamente.</dd>
<dt class="optional">toFront</dt>
<dd>Cuando está en modo de límite de velocidad, si el mensaje recibido tiene esta propiedad establecida en booleano <code>verdadero</code>, entonces el mensaje se envía al frente de la cola y se publicará a continuación. Esto se puede utilizar en combinación con <code>msg.flush=1</code> para reenviar inmediatamente.
</dd>
</dl>
<h3>Detalles</h3>
<p>Cuando se configura para retrasar mensajes, el intervalo de retraso puede ser un valor fijo, un valor aleatorio dentro de un rango o establecerse dinámicamente para cada mensaje. Cada mensaje se retrasa independientemente de cualquier otro mensaje, según la hora de su llegada.</p>
<p>Cuando se configura para calificar los mensajes con límite, su entrega se distribuye durante el período de tiempo configurado. El estado muestra la cantidad de mensajes actualmente en la cola. Opcionalmente, puede descartar mensajes intermedios a medida que llegan.</p>
<p>Si se configura para permitir modificar la frecuencia, la nueva tasa se aplicará inmediatamente y permanecerá vigente hasta que se cambie nuevamente, se restablezca el nodo o se reinicie el flujo.</p>
<p>La limitación de velocidad se puede aplicar a todos los mensajes o agruparlos según su valor <code>msg.topic</code>. Al agrupar, los mensajes intermedios se eliminan automáticamente. En cada intervalo de tiempo, el nodo puede publicar el mensaje más reciente para todos los temas o publicar el mensaje más reciente para el siguiente tema.</p>
<p><b>Nota</b>: En el modo de límite de velocidad, la profundidad máxima de la cola se puede establecer mediante una propiedad en su archivo <i>settings.js</i>. Por ejemplo <code>nodeMessageBufferMaxLength: 1000,</code></p>
</script>

View File

@@ -0,0 +1,37 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="trigger">
<p>Cuando se activa, puede enviar un mensaje y luego, opcionalmente, un segundo mensaje, a menos que se extienda o se restablezca.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">delay <span class="property-type">número</span></dt>
<dd>Establece el retraso, en milisegundos, que se aplicará al mensaje. Esta opción solo se aplica si el nodo está configurado para permitir que el mensaje anule el intervalo de retardo predeterminado configurado.</dd>
<dt class="optional">reset</dt>
<dd>Si se recibe un mensaje con esta propiedad, se borrará cualquier tiempo de espera o repetición actualmente en curso y no se activará ningún mensaje.</dd>
</dl>
<h3>Detalles</h3>
<p>Este nodo se puede utilizar para crear un tiempo de espera dentro de un flujo. De forma predeterminada, cuando recibe un mensaje, envía un mensaje con una <code>carga</code> de <code>1</code>. Luego espera 250 ms antes de enviar un segundo mensaje con una <code>carga</code> de <code>0</code>. Esto podría usarse, por ejemplo, para hacer parpadear un LED conectado a un pin GPIO de Raspberry Pi.</p>
<p>Las cargas de cada mensaje enviado se pueden configurar con una variedad de valores, incluida la opción de no enviar nada. Por ejemplo, configurando el mensaje inicial en <i>nada</i> y seleccionando la opción de extender el temporizador con cada mensaje recibido, el nodo actuará como un temporizador de vigilancia; solo enviar un mensaje si no se recibe nada dentro del intervalo establecido.</p>
<p>Si se establece en un tipo <i>cadena</i>, el nodo admite la sintaxis de plantilla mustache.</p>
<p>El retraso entre el envío de mensajes puede ser anulado por <code>msg.delay</code> si esa opción está habilitada en el nodo. El valor debe proporcionarse en milisegundos.</p>
<p>Si el nodo recibe un mensaje con una propiedad <code>reset</code> o una <code>carga</code> que coincide con la configurada en el nodo, cualquier tiempo de espera o repetición actualmente en curso se borrará y no se activa ningún mensaje.</p>
<p>El nodo se puede configurar para reenviar un mensaje a intervalos regulares hasta que se restablezca mediante un mensaje recibido.</p>
<p>Opcionalmente, el nodo se puede configurar para tratar los mensajes como si fueran secuencias separadas, utilizando una propiedad msg para identificar cada secuencia. <code>msg.topic</code> predeterminado.</p>
<p>El estado indica que el nodo está actualmente activo. Si se utilizan varias transmisiones, el estado indica la cantidad de transmisiones que se están realizando.</p>
</script>

View File

@@ -0,0 +1,75 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="exec">
<p>Ejecuta un comando del sistema y devuelve su salida.</p>
<p>El nodo se puede configurar para esperar hasta que se complete el comando o para enviar su salida a medida que el comando lo genera.</p>
<p>El comando que se ejecuta puede configurarse en el nodo o proporcionarse mediante el mensaje recibido.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">payload <span class="property-type">texto</span></dt>
<dd>si está configurado para hacerlo, se agregará al comando ejecutado.</dd>
<dt class="optional">kill <span class="property-type">texto</span></dt>
<dd>el tipo de señal de interrupción para enviar al proceso de ejecución existente.</dd>
<dt class="optional">pid <span class="property-type">número|texto</span></dt>
<dd>el ID de proceso del proceso de ejecución existente que se va a eliminar.</dd>
</dl>
<h3>Salidas</h3>
<ol class="node-ports">
<li>Salida estándar
<dl class="message-properties">
<dt>payload <span class="property-type">texto</span></dt>
<dd>la salida estándar del comando.</dd>
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">objeto</span></dt>
<dd>solo en modo ejecución, una copia del objeto de código de retorno (también disponible en el puerto 3)</dd>
</dl>
</li>
<li>Salida error
<dl class="message-properties">
<dt>payload <span class="property-type">texto</span></dt>
<dd>el error estándar del comando.</dd>
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">objeto</span></dt>
<dd>solo en modo ejecución, una copia del objeto de código de retorno (también disponible en el puerto 3)</dd>
</dl>
</li>
<li>Código de retorno
<dl class="message-properties">
<dt>payload <span class="property-type">objeto</span></dt>
<dd>un objeto que contiene el código de retorno y posiblemente las propiedades <code>message</code>, <code>signal</code>.</dd>
</dl>
</li>
</ol>
<h3>Detalles</h3>
<p>De forma predeterminada, utiliza la llamada al sistema <code>exec</code> que llama al comando, espera a que se complete y luego devuelve el resultado. Por ejemplo, un comando exitoso debe tener un código de retorno de <code>{ code: 0 }</code>.</p>
<p>Opcionalmente, puedes usar <code>spawn</code> en su lugar, que devuelve la salida de stdout y stderr a medida que se ejecuta el comando, generalmente una línea a la vez. Al finalizar, devuelve un objeto en el tercer puerto. Por ejemplo, un comando exitoso debería devolver <code>{ code: 0 }</code>.</p>
<p>Los errores pueden devolver información adicional en el tercer puerto <code>msg.payload</code>, como una cadena <code>message</code>, <code>señal</code>.</p>
<p>El comando que se ejecuta se define dentro del nodo, con una opción para agregar <code>msg.payload</code> y un conjunto adicional de parámetros.</p>
<p>Los comandos o parámetros con espacios deben estar entre comillas - <code>"Este es un solo parámetro"</code></p>
<p>La <code>carga</code> devuelta suele ser una <i>cadena</i>, a menos que se detecten caracteres que no sean UTF8, en cuyo caso es un <i>búfer</i>.</p>
<p>El icono de estado del nodo y el PID serán visibles mientras el nodo esté activo. Los cambios a esto pueden ser leídos por el nodo <code>Estado</code>.</p>
<p>La opción <code>Ocultar consola</code> ocultará la consola de procesos que normalmente se muestra en los sistemas Windows.</p>
<h4>Eliminando Procesos</h4>
<p>Enviar <code>msg.kill</code> eliminará un único proceso activo. <code>msg.kill</code> debe ser una cadena que contenga el tipo de señal que se enviará, por ejemplo, <code>SIGINT</code>, <code>SIGQUIT</code> o <code>SIGHUP</code>.
El valor predeterminado es <code>SIGTERM</code> si se establece en una cadena vacía.</p>
<p>Si el nodo tiene más de un proceso en ejecución, entonces <code>msg.pid</code> también debe configurarse con el valor del PID que se va a eliminar.</p>
<p>Si se proporciona un valor en el campo <code>Timeout</code>, si el proceso no se ha completado cuando haya transcurrido el número de segundos especificado, el proceso se finalizará automáticamente</p>
<p>Consejo: si ejecutas una aplicación Python, es posible que necesites usar el parámetro <code>-u</code> para detener la salida que se almacena en el búfer.</p>
</script>

View File

@@ -0,0 +1,32 @@
<script type="text/html" data-help-name="rbe">
<p>Nodo Informe por excepción (RBE): solo transmite datos si la carga ha cambiado.
También puede bloquear o ignorar si el valor cambia en una cantidad específica (modo de banda muerta y de banda estrecha).</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload
<span class="property-type">número | texto | (objeto)</span>
</dt>
<dd>El modo RBE aceptará números, cadenas y objetos simples.
Otros modos deben proporcionar un número analizable.</dd>
<dt class="optional">topic <span class="property-type">texto</span>
</dt>
<dd>Si se especifica, funcionará por tema. Esta propiedad se puede establecer mediante configuración.</dd>
<dt class="optional">reset<span class="property-type">cualquiera</span></dt>
<dd>si está configurado, borra el valor almacenado para el msg.topic especificado, o todos los temas si no se especifica msg.topic.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>carga <span class="property-type">según la entrada</span></dt>
<dd>Si se activa, la salida será la misma que la entrada.</dd>
</dl>
<h3>Detalles</h3>
<p>En modo RBE, este nodo se bloqueará hasta que el valor de <code>msg.payload</code> (o propiedad seleccionada) sea diferente al anterior.
Si es necesario, puede ignorar el valor inicial para no enviar nada al inicio.</p>
<p>El modo <a href="https://en.wikipedia.org/wiki/Deadband" target="_blank">Deadband</a> bloqueará el valor entrante <i>hasta</i> que el cambio sea mayor o igual que &plusmn; la banda dada.</p>
<p>El modo de banda estrecha bloqueará el valor entrante, <i>si</i> su cambio es mayor o igual que &plusmn; la banda dada.
Es útil para ignorar valores atípicos de un sensor defectuoso, por ejemplo.</p>
<p>Tanto en el modo Banda Muerta como en el Modo Banda Estrecha, el valor entrante debe contener un número analizable y ambos también admiten %: solo se envía si/a menos que la entrada difiera en más del x% del valor original.</p>
<p>Tanto la banda muerta como la banda estrecha permiten la comparación con el valor de salida válido anterior, ignorando así cualquier valor fuera de rango, o con el valor de entrada anterior, que restablece el punto de ajuste, permitiendo así una deriva gradual (banda muerta) o un cambio en pasos (banda estrecha).</p>
<p><b>Nota:</b> Esto funciona por <code>msg.topic</code>, aunque se puede cambiar a otra propiedad si se desea.
Esto significa que un único nodo de filtro puede manejar varios temas diferentes al mismo tiempo.</p>
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="tls-config">
<p>Opciones de configuración para conexiones TLS.</p>
</script>

View File

@@ -0,0 +1,22 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="http proxy">
<p>Opciones de configuración para el proxy HTTP.</p>
<h3>Detalles</h3>
<p>Al acceder un host en la lista de hosts ignorados, no se utilizará ningún proxy.</p>
</script>

View File

@@ -0,0 +1,135 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="mqtt in">
<p>Conecta a un gestor MQTT y se suscribe a mensajes del tema especificado.</p>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">texto | buffer</span></dt>
<dd>una cadena a menos que se detecte como un búfer binario.</dd>
<dt>topic <span class="property-type">texto</span></dt>
<dd>el tema MQTT, utiliza / como separador de jerarquía.</dd>
<dt>qos <span class="property-type">número</span> </dt>
<dd>0, dispara y olvida - 1, al menos una vez - 2, una vez y sólo una vez.</dd>
<dt>retain <span class="property-type">booleano</span></dt>
<dd>verdadero indica que el mensaje se retuvo y puede ser antiguo.</dd>
<dt class="optional">responseTopic <span class="property-type">texto</span></dt>
<dd><b>MQTTv5</b>: el tema de respuesta MQTT para el mensaje</dd>
<dt class="optional">correlationData <span class="property-type">Buffer</span></dt>
<dd><b>MQTTv5</b>: los datos de correlación para el mensaje</dd>
<dt class="optional">contentType <span class="property-type">texto</span></dt>
<dd><b>MQTTv5</b>: el tipo de contenido de la carga</dd>
<dt class="optional">userProperties <span class="property-type">objeto</span></dt>
<dd><b>MQTTv5</b>: cualquier propiedad de usuario del mensaje</dd>
<dt class="optional">messageExpiryInterval <span class="property-type">número</span></dt>
<dd><b>MQTTv5</b>: el tiempo de expiración, en segundos, del mensaje</dd>
</dl>
<h3>Detalles</h3>
<p>El tema de suscripción puede incluir comodines MQTT, + para un nivel, # para múltiples niveles.</p>
<p>Este nodo requiere una conexión a un gestor MQTT para configurarse. Esto se configura haciendo clic en el icono del lápiz.</p>
<p>Varios nodos MQTT (dentro o fuera) pueden compartir la misma conexión de gestor si es necesario.</p>
<h4>Suscripción Dinámica</h4>
El nodo se puede configurar para controlar dinámicamente la conexión MQTT y sus suscripciones. Cuando esté habilitado, el nodo tendrá una entrada y podrá controlarse pasándole mensajes.
<h3>Entradas</h3>
<p>Estos solo se aplican cuando el nodo ha sido configurado para suscripciones dinámicas.</p>
<dl class="message-properties">
<dt>action <span class="property-type">texto</span></dt>
<dd>el nombre de la acción que debe realizar el nodo. Las acciones disponibles son: <code>"connect"</code>, <code>"disconnect"</code>, <code>"subscribe"</code> y <code>"unsubscribe"</code>.</dd>
<dt class="optional">topic <span class="property-type">texto|objeto|matriz</span></dt>
<dd>Para las acciones <code>"subscribe"</code> y <code>"unsubscribe"</code>, esta propiedad proporciona el tema. Se puede configurar como:<ul>
<li>una cadena que contiene el filtro de tema</li>
<li>un objeto que contiene las propiedades <code>topic</code> y <code>qos</code></li>
<li>una matriz de cadenas u objetos para manejar múltiples temas en uno</li>
</ul>
</dd>
<dt class="optional">broker <span class="property-type">gestor</span> </dt>
<dd>Para la acción <code>"connect"</code>, esta propiedad puede anular cualquiera de las configuraciones de gestor individuales, incluyendo: <ul>
<li><code>broker</code></li>
<li><code>port</code></li>
<li><code>url</code> - anula el gestor/puerto para proporcionar una URL de conexión completa</li>
<li><code>username</code></li>
<li><code>password</code></li>
</ul>
<p>Si esta propiedad está configurada y el intermediario ya está conectado, se registrará un error a menos que tenga establecida la propiedad <code>force</code>; en cuyo caso se desconectará del intermediario, aplicará la nueva configuración y se volverá a conectar.</p>
</dd>
</dl>
</script>
<script type="text/html" data-help-name="mqtt out">
<p>Conecta a un gestor MQTT y publica mensajes.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">texto | buffer</span></dt>
<dd> la carga a publicar. Si esta propiedad no está configurada, no se enviará ningún mensaje. Para enviar un mensaje en blanco, establece esta propiedad a una cadena vacía.</dd>
<dt class="optional">topic <span class="property-type">texto</span></dt>
<dd>el tema MQTT para publicar.</dd>
<dt class="optional">qos <span class="property-type">número</span></dt>
<dd>0, dispara y olvida - 1, al menos una vez - 2, una vez y sólo una vez. Predeterminado 0.</dd>
<dt class="optional">retain <span class="property-type">booleano</span></dt>
<dd>configúralo en verdadero para retener el mensaje en el gestor. Por defecto es falso.</dd>
<dt class="optional">responseTopic <span class="property-type">texto</span></dt>
<dd><b>MQTTv5</b>: el tema de respuesta MQTT para el mensaje</dd>
<dt class="optional">correlationData <span class="property-type">Buffer</span></dt>
<dd><b>MQTTv5</b>: los datos de correlación para el mensaje</dd>
<dt class="optional">contentType <span class="property-type">texto</span></dt>
<dd><b>MQTTv5</b>: el tipo de contenido de la carga</dd>
<dt class="optional">userProperties <span class="property-type">objeto</span></dt>
<dd><b>MQTTv5</b>: cualquier propiedad de usuario del mensaje</dd>
<dt class="optional">messageExpiryInterval <span class="property-type">número</span></dt>
<dd><b>MQTTv5</b>: el tiempo de expiración, en segundos, del mensaje</dd>
<dt class="optional">topicAlias <span class="property-type">número</span></dt>
<dd><b>MQTTv5</b>: el alias del tema MQTT a utilizar</dd>
</dl>
<h3>Detalles</h3>
<code>msg.payload</code> se utiliza como carga útil del mensaje publicado.
Si contiene un Objeto, se convertirá en una cadena JSON antes de enviarse.
Si contiene un búfer binario, el mensaje se publicará tal cual.</p>
<p>El tema utilizado se puede configurar en el nodo o, si se deja en blanco, se puede establecer mediante <code>msg.topic</code>.</p>
<p>Del mismo modo, los valores de QoS y retención se pueden configurar en el nodo o, si se dejan en blanco, establecer mediante <code>msg.qos</code> y <code>msg.retain</code> respectivamente. Para borrar un tema previamente retenido del gestor, envía un mensaje en blanco a ese tema con el indicador de retención configurado.</p>
<p>Este nodo requiere una conexión a un gestor MQTT para configurarse. Esto se configura haciendo clic en el icono del lápiz.</p>
<p>Varios nodos MQTT (dentro o fuera) pueden compartir la misma conexión de gestor si es necesario.</p>
<h4>Control Dinámico</h4>
La conexión compartida por el nodo se puede controlar dinámicamente. Si el nodo recibe uno de los siguientes mensajes de control, tampoco publicará la carga del mensaje.
<h3>Entradas</h3>
<dl class="message-properties">
<dt>action <span class="property-type">texto</span></dt>
<dd>el nombre de la acción que debe realizar el nodo. Las acciones disponibles son: <code>"connect"</code> y <code>"disconnect"</code>.</dd>
<dt class="optional">broker <span class="property-type">gestor</span> </dt>
<dd>Para la acción <code>"connect"</code>, esta propiedad puede anular cualquiera de las configuraciones de gestor individuales, incluyendo: <ul>
<li><code>broker</code></li>
<li><code>port</code></li>
<li><code>url</code> - anula el gestor/puerto para proporcionar una URL de conexión completa</li>
<li><code>username</code></li>
<li><code>password</code></li>
</ul>
<p>Si esta propiedad está configurada y el gestor ya está conectado, se registrará un error a menos que tenga establecida la propiedad <code>force</code>; en cuyo caso se desconectará del gestor, aplicará la nueva configuración y se volverá a conectar.</p>
</dd>
</dl>
</script>
<script type="text/html" data-help-name="mqtt-broker">
<p>Configuración para una conexión a un gestor MQTT.</p>
<p>Esta configuración creará una conexión única con el gestor que luego podrá ser reutilizada por los nodos <code>MQTT In</code> y <code>MQTT Out</code>.</p>
<p>El nodo generará una ID de cliente aleatoria si no se establece ninguna y el nodo está configurado para utilizar una conexión de sesión limpia. Si se establece un ID de cliente, debe ser exclusivo del corredor al que se está conectando.</p>
<h4>Mensaje Inicial</h4>
<p>Este es un mensaje que se publicará en el tema configurado cada vez que se establezca la conexión.</p>
<h4>Mensaje Cierre</h4>
<p>Este es un mensaje que se publicará en el tema configurado antes de que la conexión se cierre normalmente, ya sea volviendo a implementar el nodo o apagándolo.</p>
<h4>Mensaje Voluntad</h4>
<p>Este es un mensaje que será publicado por el gestor en caso de que el nodo pierda inesperadamente su conexión.</p>
<h4>WebSockets</h4>
<p>El nodo se puede configurar para utilizar una conexión WebSocket. Para hacerlo, el campo Servidor debe configurarse con un URI completo para la conexión. Por ejemplo:</p>
<pre>ws://example.com:4000/mqtt</pre>
</script>

View File

@@ -0,0 +1,83 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="http in">
<p>Crea un punto final HTTP para crear servicios web.</p>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload</dt>
<dd>Para una solicitud GET, contiene un objeto con cualquier parámetro de cadena de consulta.
De lo contrario, contiene el cuerpo de la solicitud HTTP.</dd>
<dt>req<span class="property-type">objeto</span></dt>
<dd>Un objeto de solicitud HTTP. Este objeto contiene varias propiedades que proporcionan información sobre la solicitud.
<ul>
<li><code>body</code> - el cuerpo de la solicitud entrante. El formato dependerá de la solicitud.</li>
<li><code>headers</code> - un objeto que contiene los encabezados de solicitud HTTP.</li>
<li><code>query</code> - un objeto que contiene cualquier parámetro de cadena de consulta.</li>
<li><code>params</code> - un objeto que contiene cualquier parámetro de ruta.</li>
<li><code>cookies</code> - un objeto que contiene las cookies para la solicitud.</li>
<li><code>files</code> - si está habilitado dentro del nodo, un objeto que contiene los archivos cargados como parte de una solicitud POST.</li>
</ul>
</dd>
<dt>res<span class="property-type">objeto</span></dt>
<dd>Un objeto de respuesta HTTP. Esta propiedad no debe usarse directamente; el nodo <code>Respuesta HTTP</code> documenta cómo responder a una solicitud.
Esta propiedad debe permanecer adjunta al mensaje pasado al nodo de respuesta.</dd>
</dl>
<h3>Detalles</h3>
<p>El nodo escuchará en la ruta configurada solicitudes de un tipo particular. La ruta se puede especificar completamente, como <code>/user</code>, o incluir parámetros con nombre que acepten cualquier valor, como <code>/user/:name</code>. Cuando se utilizan parámetros con nombre, se puede acceder a su valor real en una solicitud en <code>msg.req.params</code>.</p>
<p>Para solicitudes que incluyen un cuerpo, como POST o PUT, el contenido de la solicitud está disponible como <code>msg.payload</code>.</p>
<p>Si se puede determinar el tipo de contenido de la solicitud, el cuerpo se analizará a cualquier tipo apropiado. Por ejemplo, <code>application/json</code> se analizará según su representación de objeto JavaScript.</p>
<p><b>Nota:</b> este nodo no envía ninguna respuesta a la solicitud. El flujo debe incluir un nodo de respuesta HTTP para completar la solicitud.</p>
</script>
<script type="text/html" data-help-name="http response">
<p>Envía respuestas a las solicitudes recibidas desde un nodo de entrada HTTP.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">texto</span></dt>
<dd>El cuerpo de la respuesta.</dd>
<dt class="optional">statusCode <span class="property-type">número</span></dt>
<dd>Si se establece, se utiliza como código de estado de respuesta. Predeterminado: 200.</dd>
<dt class="optional">headers <span class="property-type">objeto</span></dt>
<dd>Si está configurado, proporciona encabezados HTTP para incluirlos en la respuesta.</dd>
<dt class="optional">cookies <span class="property-type">objeto</span></dt>
<dd>Si está configurado, se puede utilizar para configurar o eliminar cookies.</dd>
</dl>
<h3>Detalles</h3>
<p>El <code>statusCode</code> y los <code>headers</code> también se pueden configurar dentro del propio nodo. Si una propiedad se establece dentro del nodo, la propiedad del mensaje correspondiente no puede anularla.</p>
<h4>Manejo de cookies</h4>
<p>La propiedad <code>cookies</code> debe ser un objeto de pares nombre/valor.
El valor puede ser una cadena para establecer el valor de la cookie con opciones predeterminadas o puede ser un objeto de opciones.</p>
<p>El siguiente ejemplo establece dos cookies: una llamada <code>name</code> con un valor de <code>nick</code>, la otra llamada <code>session</code> con un valor de <code >1234</code> y un vencimiento establecido en 15 minutos.</p>
<pre>
msg.cookies = {
name: 'nick',
session: {
value: '1234',
maxAge: 900000
}
}</pre>
<p>Las opciones válidas incluyen:</p>
<ul>
<li><code>domain</code> - (texto) nombre de dominio para la cookie</li>
<li><code>expires</code> - (fecha) fecha de vencimiento en GMT. Si no se especifica o se establece en 0, crea una cookie de sesión</li>
<li><code>maxAge</code> - (texto) fecha de vencimiento en relación con la hora actual en milisegundos</li>
<li><code>path</code> - (texto) ruta para la cookie. El valor predeterminado es /</li>
<li><code>value</code> - (texto) el valor a utilizar para la cookie</li>
</ul>
<p>Para eliminar una cookie, establece su <code>valor</code> en <code>null</code>.</p>
</script>

View File

@@ -0,0 +1,81 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="http request">
<p>Envía solicitudes HTTP y devuelve la respuesta.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">url <span class="property-type">texto</span></dt>
<dd>Si no está configurada en el nodo, esta propiedad opcional establece la URL de la solicitud.</dd>
<dt class="optional">method <span class="property-type">texto</span></dt>
<dd>Si no está configurada en el nodo, esta propiedad opcional establece el método HTTP de la solicitud.
Debe ser uno de los siguientes: <code>GET</code>, <code>PUT</code>, <code>POST</code>, <code>PATCH</code> o <code>DELETE</code>.</dd>
<dt class="optional">headers <span class="property-type">objeto</span></dt>
<dd>Establece los encabezados HTTP de la solicitud. NOTA: Cualquier encabezado establecido en la configuración del nodo sobrescribirá cualquier encabezado coincidente en <code>msg.headers</code> </dd>
<dt class="optional">cookies <span class="property-type">objeto</span></dt>
<dd>Si está configurado, se puede utilizar para enviar cookies con la solicitud.</dd>
<dt class="optional">payload</dt>
<dd>Enviado como cuerpo de la solicitud.</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd>Si se establece en <code>falso</code>, permite realizar solicitudes a sitios https que utilizan certificados autofirmados.</dd>
<dt class="optional">followRedirects</dt>
<dd>Si se establece en <code>falso</code>, se impide seguir el redireccionamiento (HTTP 301).<code>verdadero</code> de forma predeterminada</dd>
<dt class="optional">requestTimeout</dt>
<dd>Si se establece en un número positivo de milisegundos, anulará el parámetro <code>httpRequestTimeout</code> establecido globalmente.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">texto | objeto | buffer</span></dt>
<dd>El cuerpo de la respuesta. El nodo se puede configurar para devolver el cuerpo como una cadena, intentar analizarlo como una cadena JSON o dejarlo como un búfer binario.</dd>
<dt>statusCode <span class="property-type">número</span></dt>
<dd>El código de estado de la respuesta o el código de error si no se pudo completar la solicitud.</dd>
<dt>headers <span class="property-type">objeto</span></dt>
<dd>Un objeto que contiene los encabezados de respuesta.</dd>
<dt>responseUrl <span class="property-type">texto</span></dt>
<dd>En caso de que se produzcan redirecciones durante el procesamiento de la solicitud, esta propiedad es la URL redirigida final.
De lo contrario, la URL de la solicitud original.</dd>
<dt>responseCookies <span class="property-type">objeto</span></dt>
<dd>Si la respuesta incluye cookies, esta propiedad es un objeto de pares de nombre/valor para cada cookie.</dd>
<dt>redirectList <span class="property-type">matriz</span></dt>
<dd>Si la solicitud fue redirigida una o más veces, la información acumulada se agregará en esta propiedad. `location` es el siguiente destino de redireccionamiento. `cookies` son las cookies devueltas por la fuente de redireccionamiento.</dd>
</dl>
<h3>Detalles</h3>
<p>Cuando se configura dentro del nodo, la propiedad URL puede contener etiquetas <a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a>. Estos permiten construir la URL utilizando valores del mensaje entrante. Por ejemplo, si la URL está configurada en <code>example.com/{{{topic}}}</code>, se insertará automáticamente el valor de <code>msg.topic</code>.
Usar {{{...}}} evita que el mustache codifique caracteres como / & etc.</p>
<p>Opcionalmente, el nodo puede codificar automáticamente <code>msg.payload</code> como parámetros de cadena de consulta para una solicitud GET, en cuyo caso <code>msg.payload</code> tiene que ser un objeto.</p>
<p><b>Nota</b>: Si se ejecuta detrás de un proxy, se debe configurar la variable de entorno estándar <code>http_proxy=...</code> y reiniciar Node-RED, o usar la configuración de proxy. Si hay una configuración de proxy definidia, tiene prioridad sobre la variable de entorno.</p>
<h4>Usar múltiples nodos de solicitud HTTP</h4>
<p>Para utilizar más de uno de estos nodos en el mismo flujo, se debe tener cuidado con la propiedad <code>msg.headers</code>. El primer nodo establecerá esta propiedad con los encabezados de respuesta. El siguiente nodo utilizará esos encabezados para su solicitud; esto no suele ser lo correcto. Si la propiedad <code>msg.headers</code> no se modifica entre nodos, el segundo nodo la ignorará. Para configurar encabezados personalizados, primero se debe eliminar <code>msg.headers</code> o restablecerlo a un objeto vacío: <code>{}</code>.
<h4>Manejo de cookies</h4>
<p>La propiedad <code>cookies</code> pasada al nodo debe ser un objeto de pares nombre/valor.
El valor puede ser una cadena para establecer el valor de la cookie o puede ser un objeto con una única propiedad <code>value</code>.</p>
<p>Cualquier cookie devuelto por la solicitud se guarda en la propiedad <code>responseCookies</code>.</p>
<h4>Manejo del tipo de contenido</h4>
<p>Si <code>msg.payload</code> es un objeto, el nodo establecerá automáticamente el tipo de contenido de la solicitud en <code>application/json</code> y codificará el cuerpo como tal.</p >
<p>Para codificar la solicitud como datos de formulario, <code>msg.headers["content-type"]</code> debe establecerse en <code>application/x-www-form-urlencoded</code>.< /p>
<h4>Subir archivo</h4>
<p>Para realizar una carga de archivos, <code>msg.headers["content-type"]</code> debe configurarse en <code>multipart/form-data</code> y <code>msg.payload</code > pasado al nodo debe ser un objeto con la siguiente estructura:</p>
<pre><code>{
"KEY": {
"value": FILE_CONTENTS,
"options": {
"filename": "FILENAME"
}
}
}</code></pre>
<p>Los valores de <code>KEY</code>, <code>FILE_CONTENTS</code> y <code>FILENAME</code> deben establecerse en los valores apropiados.</p>
</script>

View File

@@ -0,0 +1,36 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="websocket in">
<p>Nodo de entrada WebSocket.</p>
<p>De forma predeterminada, los datos recibidos del WebSocket estarán en <code>msg.payload</code>.
El socket se puede configurar para esperar una cadena JSON formada correctamente, en cuyo caso analizará el JSON y enviará el objeto resultante como el mensaje completo.</p>
</script>
<script type="text/html" data-help-name="websocket out">
<p>Nodo de salida WebSocket.</p>
<p>De forma predeterminada, <code>msg.payload</code> se enviará a través del WebSocket. El socket se puede configurar para codificar todo el objeto <code>msg</code> como una cadena JSON y enviarlo a través del WebSocket.</p>
<p>Si el mensaje que llega a este nodo comenzó en un nodo WebSocket In, el mensaje se enviará de vuelta al cliente que desencadenó el flujo. De lo contrario, el mensaje se transmitirá a todos los clientes conectados.</p>
<p>Si quieres transmitir un mensaje que comenzó en un nodo WebSocket In, debes eliminar la propiedad <code>msg._session</code> del flujo.</p>
</script>
<script type="text/html" data-help-name="websocket-listener">
<p>Este nodo de configuración crea un punto final de WebSocket Server utilizando la ruta especificada.</p>
</script>
<script type="text/html" data-help-name="websocket-client">
<p>Este nodo de configuración conecta un cliente WebSocket a la URL especificada.</p>
</script>

View File

@@ -0,0 +1,35 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="tcp in">
<p>Proporciona una selección de entradas TCP. Puede conectarse a un puerto TCP remoto o aceptar conexiones entrantes.</p>
<p><b>Nota: </b>En algunos sistemas, es posible que necesites acceso raíz o de administrador para acceder a los puertos inferiores a 1024.</p>
</script>
<script type="text/html" data-help-name="tcp out">
<p>Proporciona una selección de salidas TCP. Puede conectarse a un puerto TCP remoto, aceptar conexiones entrantes o responder a mensajes recibidos desde un nodo TCP In.</p>
<p>Solo se envía el <code>msg.payload</code>.</p>
<p>Si <code>msg.payload</code> es una cadena que contiene una codificación Base64 de datos binarios, la opción de decodificación Base64 hará que se vuelva a convertir a binario antes de enviarse.</p>
<p>Si <code>msg._session</code> no está presente, la carga se envía a <b>todos</b> los clientes conectados.</p>
<p><b>Nota: </b>En algunos sistemas, es posible que necesites acceso raíz o de administrador para acceder a los puertos inferiores a 1024.</p>
</script>
<script type="text/html" data-help-name="tcp request">
<p>Un nodo de solicitud TCP simple: envía el <code>msg.payload</code> a un puerto tcp del servidor y espera una respuesta.</p>
<p>Se conecta, envía la "solicitud" y lee la "respuesta". Puede contar una cantidad de caracteres devueltos en un búfer fijo, hacer coincidir un carácter específico antes de regresar, esperar un tiempo de espera fijo desde la primera respuesta y luego regresar, esperar datos, o enviar y luego cerrar la conexión inmediatamente, sin esperar una respuesta.</p>
<p>La respuesta se generará en <code>msg.payload</code> como un búfer, por lo que es posible que quieras utilizar .toString().</p>
<p>Si dejas el host TCP o el puerto en blanco, debes configurarlos utilizando las propiedades <code>msg.host</code> y <code>msg.port</code> en cada mensaje enviado al nodo.</p>
</script>

View File

@@ -0,0 +1,28 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="udp in">
<p>Un nodo de entrada UDP que produce un <code>msg.payload</code> que contiene un búfer, una cadena o una cadena codificada en base64. Admite multidifusión.</p>
<p>También proporciona <code>msg.ip</code> y <code>msg.port</code> configurados para la dirección IP y el puerto desde el que se recibió el mensaje.</p>
<p><b>Nota</b>: En algunos sistemas, es posible que necesites acceso de administrador para usar puertos inferiores a 1024 y/o transmisión.</p>
</script>
<script type="text/html" data-help-name="udp out">
<p>Este nodo envía <code>msg.payload</code> al host y puerto UDP designado. Admite multidifusión.</p>
<p>También puede utilizar <code>msg.ip</code> y <code>msg.port</code> para establecer los valores de destino, pero los valores configurados estáticamente tienen prioridad.</p>
<p>Si seleccionas transmisión, establece la dirección IP de transmisión local o tal vez prueba con 255.255.255.255, que es la dirección de transmisión global.</p>
<p><b>Nota</b>: En algunos sistemas, es posible que necesites ser root para usar puertos inferiores a 1024 y/o transmisión.</p>
</script>

View File

@@ -0,0 +1,49 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="csv">
<p>Convierte entre una cadena con formato CSV y su representación de objeto JavaScript, en cualquier dirección.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | matriz | texto</span></dt>
<dd>Un objeto JavaScript, una matriz o una cadena CSV.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | matriz | texto</span></dt>
<dd>
<ul>
<li>Si la entrada es una cadena, intenta analizarla como CSV y crea un objeto JavaScript de pares clave/valor para cada línea.
Luego, el nodo enviará un mensaje para cada línea o un mensaje único que contenga una serie de objetos.</li>
<li>Si la entrada es un objeto JavaScript, intenta crear una cadena CSV.</li>
<li>Si la entrada es una matriz de valores simples, crea una cadena CSV de una sola línea.</li>
<li>Si la entrada es una matriz de matrices o una matriz de objetos, se crea una cadena CSV de varias líneas.</li>
</ul>
</dd>
</dl>
<h3>Detalles</h3>
<p>La plantilla de columnas puede contener una lista ordenada de nombres de columnas. Al convertir CSV en un objeto, los nombres de las columnas se utilizarán como nombres de propiedades. Alternativamente, los nombres de las columnas se pueden tomar de la primera fila del CSV.</p>
<p>Al convertir a CSV, la plantilla de columnas se utiliza para identificar qué propiedades extraer del objeto y en qué orden.</p>
<p>Si la plantilla de columnas está en blanco, puede utilizar una lista simple de propiedades separadas por comas proporcionada en <code>msg.columns</code> para determinar qué extraer y en qué orden. Si ninguno de los dos está presente, todas las propiedades del objeto se muestran en el orden en que se encuentran en la primera fila.</p>
<p>Si la entrada es una matriz, entonces la plantilla de columnas solo se usa para generar opcionalmente una fila de títulos de columnas.</p>
<p>Si se marca la opción 'analizar valores numéricos', los valores numéricos de cadena se devolverán como números, es decir. valor de enmedio '1, "1,5", 2'.</p>
<p>Si se marca la opción 'incluir cadenas vacías', se devolverán cadenas vacías como resultado, es decir. valor de enmedio '"1","",3'.</p>
<p>Si se marca la opción 'incluir valores nulos', se devolverán valores nulos como resultado, es decir. valor de enmedio '"1",,3'.</p>
<p>El nodo puede aceptar una entrada de varias partes siempre que la propiedad <code>parts</code> esté configurada correctamente, por ejemplo, desde un nodo de entrada de archivo o un nodo dividido.</p>
<p>Si genera varios mensajes, tendrán su propiedad <code>parts</code> configurada y formarán una secuencia de mensajes completa.</p>
<p>Si el nodo está configurado para enviar encabezados de columna solo una vez, si se configura <code>msg.reset</code> en cualquier valor hará que el nodo reenvíe los encabezados.</p>
<p><b>Nota:</b> la plantilla de columna debe estar separada por comas, incluso si se elige un separador diferente para los datos.</p>
</script>

View File

@@ -0,0 +1,33 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="html">
<p>Extrae elementos de un documento HTML contenido en <code>msg.payload</code> usando un selector CSS.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">texto</span></dt>
<dd>la cadena HTML de la que extraer elementos.</dd>
<dt class="optional">select <span class="property-type">texto</span></dt>
<dd>Si no está configurado en el panel de edición, el selector se puede configurar como esta propiedad de msg.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">matriz | texto</span></dt>
<dd>el resultado puede ser un único mensaje con una carga que contenga una matriz de elementos coincidentes o varios mensajes que contengan cada uno un elemento coincidente. Si se envían varios mensajes, también tendrán <code>parts</code> configuradas.</dd>
</dl>
<h3>Detalles</h3>
<p>Este nodo admite una combinación de selectores CSS y jQuery. Consulta la <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_blank">documentación de css-select</a> para obtener más información sobre la sintaxis admitida.</p>
</script>

View File

@@ -0,0 +1,44 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="json">
<p>Convierte entre una cadena JSON y su representación de objeto JavaScript, en cualquier dirección.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | texto</span></dt>
<dd>Un objeto JavaScript o una cadena JSON.</dd>
<dt>schema<span class="property-type">objeto</span></dt>
<dd>Un objeto de esquema JSON opcional para validar la carga.
La propiedad se eliminará antes de que el <code>msg</code> se envíe al siguiente nodo.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | texto</span></dt>
<dd>
<ul>
<li>Si la entrada es una cadena JSON, intenta analizarla en un objeto JavaScript.</li>
<li>Si la entrada es un objeto JavaScript, crea una cadena JSON. Opcionalmente, la cadena se puede formatear.</li>
</ul>
</dd>
<dt>schemaError<span class="property-type">matriz</span></dt>
<dd>Si falla la validación del esquema JSON, el nodo cpatura (catch) tendrá una propiedad <code>schemaError</code> que contiene una serie de errores.</dd>
</dl>
<h3>Detalles</h3>
<p>De forma predeterminada, el nodo opera en <code>msg.payload</code>, pero se puede configurar para convertir cualquier propiedad de mensaje.</p>
<p>El nodo también se puede configurar para garantizar una codificación particular en lugar de alternar entre las dos. Esto se puede usar, por ejemplo, con el nodo <code>HTTP In</code> para garantizar que la carga sea un objeto analizado incluso si una solicitud entrante no configuró su tipo de contenido correctamente para que el nodo HTTP In realice la conversión.</p>
<p>Si el nodo está configurado para garantizar que la propiedad esté codificada como una cadena y recibe una cadena, no se realizarán más comprobaciones de la propiedad. No comprobará que la cadena sea JSON válida ni la reformateará si se selecciona la opción de formato.</p>
<p>Para más detalles sobre el esquema JSON puedes consultar la especificación <a href="http://json-schema.org/latest/json-schema-validation.html">aquí</a>.</p>
</script>

View File

@@ -0,0 +1,49 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="xml">
<p>Convierte entre una cadena XML y su representación de objeto JavaScript, en cualquier dirección.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | texto</span></dt>
<dd>Un objeto JavaScript o una cadena XML.</dd>
<dt class="optional">options <span class="property-type">objeto</span></dt>
<dd>Esta propiedad opcional se puede utilizar para pasar cualquiera de las opciones admitidas por la librería subyacente utilizada para convertir hacia y desde XML. Consulta <a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options" target="_blank">los documentos xml2js</a> para obtener más información.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | texto</span></dt>
<dd>
<ul>
<li>Si la entrada es una cadena, intenta analizarla como XML y crea un objeto JavaScript.</li>
<li>Si la entrada es un objeto JavaScript, intenta construir una cadena XML.</li>
</ul>
</dd>
</dl>
<h3>Detalles</h3>
<p>Al convertir entre XML y un objeto, cualquier atributo XML se agrega como una propiedad denominada <code>$</code> de forma predeterminada.
Cualquier contenido de texto se agrega como una propiedad denominada <code>_</code>. Estos nombres de propiedades se pueden especificar en la configuración del nodo.</p>
<p>Por ejemplo, el siguiente XML se convertirá como se muestra:</p>
<pre>&lt;p class="tag"&gt;Hello World&lt;/p&gt;</pre>
<pre>{
"p": {
"$": {
"class": "tag"
},
"_": "Hello World"
}
}</pre>
</script>

View File

@@ -0,0 +1,34 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="yaml">
<p>Convierte entre una cadena con formato YAML y su representación de objeto JavaScript, en cualquier dirección.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | texto</span></dt>
<dd>Un objeto JavaScript o una cadena YAML.</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | texto</span></dt>
<dd>
<ul>
<li>Si la entrada es una cadena YAML, intenta convertirla en un objeto JavaScript.</li>
<li>Si la entrada es un objeto JavaScript, crea una cadena YAML.</li>
</ul>
</dd>
</dl>
</script>

View File

@@ -0,0 +1,146 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="split">
<p>Divide un mensaje en una secuencia de mensajes.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">objeto | texto | matriz | búfer</span></dt>
<dd>El comportamiento del nodo está determinado por el tipo de <code>msg.payload</code>:
<ul>
<li><b>texto</b>/<b>búfer</b> - el mensaje se divide utilizando el carácter especificado (predeterminado: <code>\n</code>), secuencia de búfer o en longitudes fijas.</li>
<li><b>matriz</b> - el mensaje se divide en elementos de matriz individuales o en matrices de longitud fija.</li>
<li><b>objeto</b> - Se envía un mensaje para cada par clave/valor del objeto.</li>
</ul>
</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>parts<span class="property-type">objeto</span></dt>
<dd>Esta propiedad contiene información sobre cómo se dividió el mensaje original. Si se pasa al nodo <b>unir</b>, la secuencia se puede volver a ensamblar en un solo mensaje. Tiene las siguientes propiedades:
<ul>
<li><code>id</code> - un identificador para el grupo de mensajes</li>
<li><code>index</code> - la posición dentro del grupo</li>
<li><code>count</code> - si se conoce, el número total de mensajes en el grupo. Consulta 'modo de transmisión' a continuación.</li>
<li><code>type</code> - el tipo de mensaje: texto/matriz/objeto/búfer</li>
<li><code>ch</code> - para una cadena o un búfer, los datos utilizados para dividir el mensaje como una cadena o una matriz de bytes</li>
<li><code>key</code> - para un objeto, la clave de la propiedad desde la que se creó este mensaje. El nodo se puede configurar para copiar también este valor en otras propiedades del mensaje, como <code>msg.topic</code>.</li>
<li><code>len</code> - la longitud de cada mensaje cuando se divide utilizando un valor de longitud fija</li>
</ul>
</dd>
</dl>
<h3>Detalles</h3>
<p>Este nodo facilita la creación de un flujo que realiza acciones comunes en una secuencia de mensajes antes de usar el nodo <b>unir</b> y recombinar la secuencia en un solo mensaje.</p>
<p>Utiliza la propiedad <code>msg.parts</code> para rastrear las partes individuales de una secuencia.</p>
<h4>Modo de transmisión</h4>
<p>El nodo también se puede utilizar para redistribuir un flujo de mensajes. Por ejemplo, un dispositivo serie que envía comandos terminados en nueva línea puede entregar un único mensaje con un comando parcial al final. En el 'modo de transmisión', este nodo dividirá un mensaje y enviará cada segmento completo. Si hay un segmento parcial al final, el nodo lo conservará y lo antepondrá al siguiente mensaje que se reciba.</p>
<p>Cuando se opera en este modo, el nodo no establecerá la propiedad <code>msg.parts.count</code> ya que no sabe cuántos mensajes esperar en la secuencia. Esto significa que no se puede utilizar con el nodo <b>unir</b> en su modo automático.</p>
</script>
<script type="text/html" data-help-name="join">
<p>Une secuencias de mensajes en un solo mensaje.</p>
<p>Hay tres modos disponibles:</p>
<dl>
<dt>automático</dt>
<dd>Cuando se empareja con el nodo <b>dividr</b>, unirá automáticamente los mensajes para revertir la división que se realizó.</dd>
<dt>manual</dt>
<dd>Une secuencias de mensajes de diversas formas.</dd>
<dt>reducir secuencia</dt>
<dd>Aplica una expresión a todos los mensajes de una secuencia para reducirla a un solo mensaje.</dd>
</dl>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">parts<span class="property-type">objeto</span></dt>
<dd>Para unir automáticamente una secuencia de mensajes, todos deben tener esta propiedad establecida. El nodo <b>dividr</b> genera esta propiedad pero se puede crear manualmente. Tiene las siguientes propiedades:
<ul>
<li><code>id</code> - un identificador para el grupo de mensajes</li>
<li><code>index</code> - la posición dentro del grupo</li>
<li><code>count</code> - el número total de mensajes en el grupo</li>
<li><code>type</code> - el tipo de mensaje: cadena/matriz/objeto/búfer</li>
<li><code>ch</code> - para una cadena o un búfer, los datos utilizados para dividir el mensaje como una cadena o una matriz de bytes</li>
<li><code>key</code> - para un objeto, la clave de la propiedad desde la que se creó este mensaje</li>
<li><code>len</code> - la longitud de cada mensaje cuando se divide utilizando un valor de longitud fija</li>
</ul>
</dd>
<dt class="optional">completa</dt>
<dd>Si está configurado, el nodo agregará la carga y luego enviará el mensaje de salida en su estado actual.
Si no quieres añadir la carga, elimínala del mensaje.</dd>
<dt class="optional">reset</dt>
<dd>Si se establece, el nodo borrará cualquier mensaje parcialmente completo y no lo enviará.</dd>
<dt class="optional">restartTimeout</dt>
<dd>Si está configurado y el nodo tiene un tiempo de espera configurado, ese tiempo de espera se reiniciará.</dd>
</dl>
<h3>Detalles</h3>
<h4>Modo Automático</h4>
<p>El modo automático utiliza la propiedad <code>parts</code> de los mensajes entrantes para determinar cómo se debe unir la secuencia. Esto te permite revertir automáticamente la acción de un nodo <b>dividido</b>.</p>
<h4>Modo Manual</h4>
<p>Cuando se configura para unirse en modo manual, el nodo puede unir secuencias de mensajes en varios resultados diferentes:</p>
<ul>
<li>a <b>texto</b> o <b>buffer</b> - creado uniendo la propiedad seleccionada de cada mensaje con los caracteres de unión o el búfer especificados.</li>
<li>an <b>matriz</b> - creado agregando cada propiedad seleccionada, o mensaje completo, a la matriz de salida.</li>
<li>a <b>objeto clave/valor</b> - creado utilizando una propiedad de cada mensaje para determinar la clave bajo la cual se almacena el valor requerido.</li>
<li>a <b>objeto combinado</b> - creado fusionando la propiedad de cada mensaje en un solo objeto.</li>
</ul>
<p>Las otras propiedades del mensaje de salida se toman del último mensaje recibido antes de enviar el resultado.</p>
<p>Se puede establecer un <i>count</i> para determinar cuántos mensajes se deben recibir antes de generar el mensaje de salida.
Para las salidas de objetos, una vez que se haya alcanzado este recuento, el nodo se puede configurar para enviar un mensaje por cada mensaje recibido a continuación.</p>
<p>Se puede establecer un <i>tiempo de espera</i> para activar el envío del nuevo mensaje utilizando lo que se haya recibido hasta el momento.
Este tiempo de espera se puede reiniciar enviando un mensaje con la propiedad <code>msg.restartTimeout</code> establecida.</p>
<p>Si se recibe un mensaje con la propiedad <code>msg.complete</code> configurada, el mensaje de salida se finaliza y se envía.
Esto restablece el recuento de segmentos.</p>
<p>Si se recibe un mensaje con la propiedad <code>msg.reset</code> configurada, el mensaje parcialmente completo se elimina y no se envía.
Esto restablece el recuento de segmentos.</p>
<h4>Modo Reducir Secuencia</h4>
<p>Cuando se configura para unirse en modo de reducción, se aplica una expresión a cada mensaje en una secuencia y el resultado se acumula para producir un solo mensaje.</p>
<dl class="message-properties">
<dt>Valor inicial</dt>
<dd>El valor inicial del valor acumulado. (<code>$A</code>).</dd>
<dt>Expresión de reducción</dt>
<dd>Una expresión JSONata que se llama para cada mensaje de la secuencia.
El resultado se pasa a la siguiente llamada de la expresión como valor acumulado.
En la expresión, se pueden utilizar las siguientes variables especiales:
<ul>
<li><code>$A</code>: el valor acumulado,</li>
<li><code>$I</code>: índice del mensaje en la secuencia,</li>
<li><code>$N</code>: número de mensajes en la secuencia.</li>
</ul>
</dd>
<dt>Expresión de reparación</dt>
<dd>Una expresión JSONata opcional que se aplica después de que la expresión de reducción se haya aplicado a todos los mensajes de la secuencia.
En la expresión, se pueden utilizar las siguientes variables especiales:
<ul>
<li><code>$A</code>: el valor acumulado,</li>
<li><code>$N</code>: número de mensajes en la secuencia.</li>
</ul>
</dd>
<p>De forma predeterminada, la expresión de reducción se aplica en orden, desde el primero hasta el último mensaje de la secuencia. Opcionalmente se puede aplicar en orden inverso.</p>
<p>$N es el número de mensajes que llegan, incluso si son idénticos.</p>
</dl>
<p><b>Ejemplo:</b> la siguiente configuración, dada una secuencia de valores numéricos, calcula el valor promedio:
<ul>
<li><b>Expresión de reducción</b>: <code>$A+payload</code></li>
<li><b>Valor inicial</b>: <code>0</code></li>
<li><b>Expresión de reparación</b>: <code>$A/$N</code></li>
</ul>
</p>
<h4>Almacenar mensajes</h4>
<p>Este nodo almacenará en el búfer los mensajes internamente para poder trabajar en secuencias. La configuración de tiempo de ejecución <code>nodeMessageBufferMaxLength</code> se puede utilizar para limitar cuántos mensajes se almacenarán en el buffer.</p>
</script>

View File

@@ -0,0 +1,41 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="sort">
<p>Una función que ordena las propiedades del mensaje o una secuencia de mensajes.</p>
<p>Cuando se configura para ordenar la propiedad del mensaje, el nodo ordena los datos de la matriz a los que apunta la propiedad del mensaje especificado.</p>
<p>Cuando se configura para ordenar una secuencia de mensajes, reordenará los mensajes.</p>
<p>El orden de clasificación puede ser:</p>
<ul>
<li><b>ascendente</b>,</li>
<li><b>descendente</b>.</li>
</ul>
<p>Para números, el orden numérico se puede especificar mediante una casilla de verificación.</p>
<p>La clave de ordenación puede ser un valor de elemento o una expresión JSONata para ordenar el valor de una propiedad, o una propiedad de mensaje o una expresión JSONata para ordenar una secuencia de mensajes.<p>
<p>Al ordenar una secuencia de mensajes, el nodo de ordenación depende de que los mensajes recibidos tengan <code>msg.parts</code> configurado. El nodo dividido genera esta propiedad, pero se puede crear manualmente. Tiene las siguientes propiedades:</p>
<p>
<ul>
<li><code>id</code> - un identificador para el grupo de mensajes</li>
<li><code>index</code> - la posición dentro del grupo</li>
<li><code>count</code> - el número total de mensajes en el grupo</li>
</ul>
</p>
<p><b>Nota:</b> Este nodo guarda internamente mensajes para su funcionamiento. Para evitar un uso inesperado de la memoria, se puede especificar la cantidad máxima de mensajes conservados. El valor predeterminado no es límite en la cantidad de mensajes.
<ul>
Propiedad <li><code>nodeMessageBufferMaxLength</code> establecida en <b>settings.js</b>.</li>
</ul>
</p>
</script>

View File

@@ -0,0 +1,35 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="batch">
<p>Crea secuencias de mensajes basadas en varias reglas.</p>
<h3>Detalles</h3>
<p>Hay tres modos para crear secuencias de mensajes:</p>
<dl>
<dt>Número de mensajes</dt>
<dd>agrupa mensajes en secuencias de una longitud determinada. La opción <b>superposición</b> especifica cuántos mensajes al final de una secuencia deben repetirse al comienzo de la siguiente secuencia.</dd>
<dt>Intervalo de tiempo</dt>
<dd>agrupa los mensajes que llegan dentro del intervalo especificado. Si no llega ningún mensaje dentro del intervalo, el nodo puede opcionalmente enviar un mensaje vacío.</dd>
<dt>Concatenar secuencias</dt>
<dd>crea una secuencia de mensajes concatenando secuencias entrantes. Cada mensaje debe tener una propiedad <code>msg.topic</code> y una propiedad <code>msg.parts</code> que identifique su secuencia. El nodo está configurado con una lista de valores de <code>topic</code> para identificar las secuencias de orden que están concatenadas.
</dd>
</dl>
<h4>Almacenar mensajes</h4>
<p>Este nodo almacenará en búfer los mensajes internamente para poder trabajar en secuencias. La configuración de tiempo de ejecución <code>nodeMessageBufferMaxLength</code> se puede utilizar para limitar cuántos nodos de mensajes almacenarán en el buffer.</p>
<p>Si se recibe un mensaje con la propiedad <code>msg.reset</code> configurada, los mensajes almacenados en el búfer se eliminan y no se envían.</p>
</script>

View File

@@ -0,0 +1,62 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="file">
<p>Escribe <code>msg.payload</code> en un archivo, ya sea agregándolo al final o reemplazando el contenido existente.
Alternativamente, puede eliminar el archivo.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">texto</span></dt>
<dd>El nombre del archivo que se actualizará se puede proporcionar en la configuración del nodo o como una propiedad del mensaje.
De forma predeterminada utilizará <code>msg.filename</code> pero esto se puede personalizar en el nodo.
</dd>
<dt class="optional">encoding <span class="property-type">texto</span></dt>
<dd>Si la codificación está configurada para que se establezca mediante mensaje, entonces esta propiedad opcional puede establecer la codificación.</dt>
</dl>
<h3>Salidas</h3>
<p>Al finalizar la escritura, el mensaje de entrada se envía al puerto de salida.</p>
<h3>Detalles</h3>
<p>Cada carga de mensaje se agregará al final del archivo, añadiendo opcionalmente un carácter de nueva línea (\n) entre cada uno.</p>
<p>Si se utiliza <code>msg.filename</code>, el archivo se cerrará después de cada escritura. Para obtener el mejor rendimiento, utiliza un nombre de archivo fijo.</p>
<p>Se puede configurar para sobrescribir el archivo completo en lugar de agregarlo. Por ejemplo, al escribir datos binarios en un archivo, como una imagen, se debe usar esta opción y la opción de agregar una nueva línea debe estar deshabilitada.</p>
<p>La codificación de los datos escritos en un archivo se puede especificar desde la lista de codificaciones.</p>
<p>Como alternativa, este nodo se puede configurar para eliminar el archivo.</p>
</script>
<script type="text/html" data-help-name="file in">
<p>Lee el contenido de un archivo como una cadena o un búfer binario.</p>
<h3>Entradas</h3>
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">texto</span></dt>
<dd>El nombre del archivo que se va a leer se puede proporcionar en la configuración del nodo o como una propiedad del mensaje.
De forma predeterminada utilizará <code>msg.filename</code> pero esto se puede personalizar en el nodo.
</dd>
</dl>
<h3>Salidas</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">texto | buffer</span></dt>
<dd>El contenido del archivo como una cadena o un búfer binario.</dd>
<dt class="optional">filename <span class="property-type">texto</span></dt>
<dd>Si no está configurada en el nodo, esta propiedad opcional establece el nombre del archivo que se leerá.</dd>
</dl>
<h3>Detalles</h3>
<p>El nombre del archivo debe ser una ruta absoluta; de lo contrario, será relativa al directorio de trabajo del proceso Node-RED.</p>
<p>En Windows, es posible que sea necesario utilizar caracteres de escape en los separadores de ruta, por ejemplo: <code>\\Users\\myUser</code>.</p>
<p>Opcionalmente, un archivo de texto se puede dividir en líneas, generando un mensaje por línea, o un archivo binario dividido en fragmentos de búfer más pequeños; el tamaño del fragmento depende del sistema operativo, pero normalmente 64k (Linux/Mac) o 41k (Windows).</p>
<p>Cuando se divide en varios mensajes, cada mensaje tendrá una propiedad <code>parts</code> establecida, formando una secuencia de mensajes completa.</p>
<p>La codificación de los datos de entrada se puede especificar desde la lista de codificaciones si el formato de salida es una cadena.</p>
<p>Los errores deben detectarse y gestionarse mediante un nodo Captura (Catch).</p>
</script>

View File

@@ -0,0 +1,30 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/html" data-help-name="watch">
<p>Observa un directorio o archivo en busca de cambios.</p>
<p>Puedes ingresar una lista de directorios y/o archivos separados por comas. Tendrás que
poner comillas "..." alrededor de cualquier nombre que tenga espacios</p>
<p>En Windows debes utilizar barras invertidas dobles \\ en cualquier nombre de directorio.</p>
<p>El nombre completo del archivo que realmente cambió se coloca en <code>msg.payload</code> y <code>msg.filename</code>,
mientras que en <code>msg.topic</code> se devuelve una versión en texto de la lista de vigilancia.</p>
<p><code>msg.file</code> contiene solo el nombre corto del archivo que cambió.
<code>msg.type</code> tiene el tipo de cosa cambiada, generalmente <i>archivo</i> o <i>directorio</i>,
mientras que <code>msg.size</code> contiene el tamaño del archivo en bytes.</p>
<p>Por supuesto, en Linux, <i>todo</i> es un archivo y, por lo tanto, se puede observar...</p>
<p><b>Nota: </b>El directorio o archivo debe existir para poder ser observado. Si el archivo
o el directorio se elimina, es posible que ya no se vigile incluso si se vuelve a crear.</p>
</script>

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "3.1.3",
"version": "4.0.0-dev",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -264,7 +264,7 @@ async function installModule(moduleDetails) {
"module": moduleDetails.module,
"version": moduleDetails.version,
"dir": installDir,
"args": ["--production","--engine-strict"]
"args": ["--omit=dev","--engine-strict"]
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {
// preInstall passed

View File

@@ -215,7 +215,7 @@ async function installModule(module,version,url) {
"dir": installDir,
"isExisting": isExisting,
"isUpgrade": isUpgrade,
"args": ['--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--production','--engine-strict']
"args": ['--no-audit','--no-update-notifier','--no-fund','--save','--save-prefix=~','--omit=dev','--engine-strict']
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "3.1.3",
"version": "4.0.0-dev",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,7 +16,7 @@
}
],
"dependencies": {
"@node-red/util": "3.1.3",
"@node-red/util": "4.0.0-dev",
"clone": "2.1.2",
"fs-extra": "11.1.1",
"semver": "7.5.4",

View File

@@ -68,6 +68,7 @@ var api = module.exports = {
* @param {String} opts.store - the context store
* @param {String} opts.key - the context key
* @param {Object} opts.req - the request to log (optional)
* @param {Boolean} opts.keysOnly - whether to return keys only
* @return {Promise} - the node information
* @memberof @node-red/runtime_context
*/
@@ -102,6 +103,15 @@ var api = module.exports = {
if (key) {
store = store || availableStores.default;
ctx.get(key,store,function(err, v) {
if (opts.keysOnly) {
if (Array.isArray(v)) {
resolve({ [store]: { format: `array[${v.length}]`}})
} else if (typeof v === 'object') {
resolve({ [store]: { keys: Object.keys(v), format: 'Object' } })
} else {
resolve({ [store]: { keys: [] }})
}
}
var encoded = util.encodeObject({msg:v});
if (store !== availableStores.default) {
encoded.store = store;
@@ -118,32 +128,58 @@ var api = module.exports = {
stores = [store];
}
var result = {};
var c = stores.length;
var errorReported = false;
stores.forEach(function(store) {
exportContextStore(scope,ctx,store,result,function(err) {
if (err) {
// TODO: proper error reporting
if (!errorReported) {
errorReported = true;
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
var err = new Error();
err.code = "unexpected_error";
err.status = 400;
return reject(err);
if (opts.keysOnly) {
ctx.keys(store,function(err, keys) {
if (err) {
// TODO: proper error reporting
if (!errorReported) {
errorReported = true;
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
var err = new Error();
err.code = "unexpected_error";
err.status = 400;
return reject(err);
}
return
}
result[store] = { keys }
c--;
if (c === 0) {
if (!errorReported) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve(result);
}
}
})
} else {
exportContextStore(scope,ctx,store,result,function(err) {
if (err) {
// TODO: proper error reporting
if (!errorReported) {
errorReported = true;
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"}, opts.req);
var err = new Error();
err.code = "unexpected_error";
err.status = 400;
return reject(err);
}
return;
}
c--;
if (c === 0) {
if (!errorReported) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve(result);
return;
}
}
});
c--;
if (c === 0) {
if (!errorReported) {
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key},opts.req);
resolve(result);
}
}
});
}
})
}
} else {

View File

@@ -485,7 +485,7 @@ class Flow {
}
if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) {
return this._env[key]
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
}
} else {
key = key.substring(8);

View File

@@ -41,7 +41,7 @@ class Group {
}
if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) {
return this._env[key]
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
}
} else {
key = key.substring(8);

View File

@@ -212,6 +212,7 @@ class Subflow extends Flow {
var subflowInstanceConfig = {
id: this.subflowInstance.id,
type: this.subflowInstance.type,
g: this.subflowInstance.g,
z: this.subflowInstance.z,
name: this.subflowInstance.name,
wires: [],
@@ -375,7 +376,7 @@ class Subflow extends Flow {
}
if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) {
return this._env[key]
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
}
} else {
key = key.substring(8);

View File

@@ -374,7 +374,12 @@ async function start(type,diff,muteLog,isDeploy) {
// A modified-type deploy means restarting things that have changed
// Update the global flow
activeFlows['global'].update(activeFlowConfig,activeFlowConfig);
if (activeFlows['global']) {
activeFlows['global'].update(activeFlowConfig,activeFlowConfig);
} else {
log.debug("red/nodes/flows.start : starting flow : global");
activeFlows['global'] = Flow.create(flowAPI,activeFlowConfig);
}
for (id in activeFlowConfig.flows) {
if (activeFlowConfig.flows.hasOwnProperty(id)) {
if (!activeFlowConfig.flows[id].disabled) {

View File

@@ -102,6 +102,9 @@ async function evaluateEnvProperties(flow, env, credentials) {
pendingEvaluations.push(new Promise((resolve, _) => {
redUtil.evaluateNodeProperty(value, 'jsonata', {_flow: flow}, null, (err, result) => {
if (!err) {
if (typeof result === 'object') {
result = { value: result, __clone__: true}
}
evaluatedEnv[name] = result
}
resolve()
@@ -109,6 +112,9 @@ async function evaluateEnvProperties(flow, env, credentials) {
}))
} else {
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
if (typeof value === 'object') {
value = { value: value, __clone__: true}
}
}
evaluatedEnv[name] = value
}
@@ -138,8 +144,13 @@ async function evaluateEnvProperties(flow, env, credentials) {
}
}}, null, null);
}
if (typeof value === 'object' && !value.__clone__) {
value = { value: value, __clone__: true}
}
evaluatedEnv[name] = value
}
// console.log(evaluatedEnv)
return evaluatedEnv
}

View File

@@ -154,7 +154,7 @@ function start() {
log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
if (settings.UNSUPPORTED_VERSION) {
log.error("*****************************************************************");
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=8.9.0"})+" *");
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=18"})+" *");
log.error("*****************************************************************");
events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true});
}

View File

@@ -384,10 +384,28 @@ var api = module.exports = {
}
}
} else if (nodeType === "global-config") {
if (JSON.stringify(savedCredentials.map) !== JSON.stringify(newCreds.map)) {
savedCredentials.map = newCreds.map;
dirty = true;
}
savedCredentials.map = savedCredentials.map || {}
const existingCredentialKeys = Object.keys(savedCredentials.map)
const newCredentialKeys = Object.keys(newCreds?.map || [])
existingCredentialKeys.forEach(key => {
if (!newCreds.map?.[key]) {
// This key doesn't exist in the new credentials list - remove
delete savedCredentials.map[key]
delete savedCredentials.map[`has_${key}`]
dirty = true
}
})
newCredentialKeys.forEach(key => {
if (!/^has_/.test(key)) {
if (!savedCredentials.map[key] || newCreds.map[key] !== '__PWRD__') {
// This key either doesn't exist in current saved, or the
// value has been changed
savedCredentials.map[key] = newCreds.map[key]
savedCredentials.map[`has_${key}`] = newCreds.map[`has_${key}`]
dirty = true
}
}
})
} else {
var dashedType = nodeType.replace(/\s+/g, '-');
var definition = credentialsDef[dashedType];

View File

@@ -0,0 +1,195 @@
{
"runtime": {
"welcome": "Bienvenid@ a Node-RED",
"version": "__component__ versión: __version__",
"unsupported_version": "Versión no soportada de __component__. Requiere: __requires__ Encontrado: __version__",
"paths": {
"settings": "Fichero de Ajustes : __path__",
"httpStatic": "HTTP Estático : __path__"
}
},
"server": {
"loading": "Cargando paleta de nodos",
"palette-editor": {
"disabled": "Editor de paletas desactivado : ajustes de usuario",
"npm-not-found": "Editor de paletas desactivado : comando npm no encontrado",
"npm-too-old": "Editor de paletas desactivado : versión npm demasiado vieja. Requiere npm >= 3.x"
},
"errors": "Fallo al registrar __count__ tipo de nodo.",
"errors_plural": "Fallo al registrar __count__ tipos de nodo.",
"errors-help": "Ejecutar con -v para más detalles",
"missing-modules": "Faltan módulos de nodos:",
"node-version-mismatch": "El nodo de módulo no puede cargarse en esta versión. Requiere: __version__ ",
"set-has-no-types": "Establece no tiene ningún tipo. nombre: '__name__', módulo: '__module__', fichero: '__file__'",
"type-already-registered": "'__type__' ya registrado por módulo __module__",
"removing-modules": "Eliminando módulos de la configuración",
"added-types": "Tipos de nodos añadidos:",
"removed-types": "Tipos de nodos eliminados:",
"install": {
"invalid": "Nombre de módulo no válido",
"installing": "Instalando módulo: __name__, versión: __version__",
"installed": "Módulo instalado: __name__",
"install-failed": "Error de instalación",
"install-failed-long": "Fallo en la instalación del módulo __name__:",
"install-failed-not-found": "$t(server.install.install-failed-long) módulo no encontrado",
"install-failed-name": "$t(server.install.install-failed-long) nombre de módulo inválido: __name__",
"install-failed-url": "$t(server.install.install-failed-long) URL inválida: __url__",
"post-install-error": "Error ejecutando código 'postInstall':",
"upgrading": "Actualizando módulo: __name__ a la versión: __version__",
"upgraded": "Módulo actualizado: __name__. Reinicia Node-RED para utilizar la nueva versión",
"upgrade-failed-not-found": "$t(server.install.install-failed-long) versión no encontrada",
"uninstalling": "Desinstalando el módulo: __name__",
"uninstall-failed": "Error de desinstalación",
"uninstall-failed-long": "Error en la desinstalación del módulo __name__:",
"uninstalled": "Desinstalando módulo: __name__",
"old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nDirectorio de módulos externos Node-RED 1.3 detectado:\n __oldDir__\nEste directorio ya no se utiliza. Los módulos externos serán reinstalado en tu directorio de usuario Node-RED:__newDir__\nBorra el antiguo directorio externalModules para eliminar este mensaje.\n---------------------------------------------------------------------\n"
},
"deprecatedOption": "__old__ está en DESUSO. Utiliza __new__",
"unable-to-listen": "No se puede escuchar __listenpath__",
"port-in-use": "Error: puerto en uso",
"uncaught-exception": "Excepción no detectada:",
"admin-ui-disabled": "IU de administrador deshabilitado",
"now-running": "El servidor está funcionando en __listenpath__",
"failed-to-start": "No se pudo iniciar el servidor:",
"headless-mode": "Ejecutando en modo sin interfaz",
"httpadminauth-deprecated": "httpAdminAuth está en DESUSO. Utiliza adminAuth",
"https": {
"refresh-interval": "Actualizando la configuración HTTPS cada __interval__ horas",
"settings-refreshed": "La configuración HTTPS del servidor se ha actualizado",
"refresh-failed": "No se pudo actualizar la configuración HTTPS: __message__",
"nodejs-version": "httpsRefreshInterval requiere Node.js 11 o superior",
"function-required": "httpsRefreshInterval requiere que la propiedad HTTPS sea una función"
}
},
"api": {
"flows": {
"error-save": "Error al guardar flujos: __message__",
"error-reload": "Error al recargar flujos: __message__"
},
"library": {
"error-load-entry": "Error al cargar la entrada de la librería '__path__': __message__",
"error-save-entry": "Error al guardar la entrada de la librería '__path__': __message__",
"error-load-flow": "Error al cargar el flujo '__path__': __message__",
"error-save-flow": "Error al guardar el flujo '__path__': __message__"
},
"nodes": {
"enabled": "Tipos de nodo habilitados:",
"disabled": "Tipos de nodo deshabilitados:",
"error-enable": "Fallo al habilitar nodo:"
}
},
"comms": {
"error": "Error del canal de comunicación: __message__",
"error-server": "Error del servidor de comunicación: __message__",
"error-send": "Error de envío de comunicación: __message__"
},
"settings": {
"user-not-available": "No se puede guardar la configuración del usuario: __message__",
"not-available": "Ajustes no disponibles",
"property-read-only": "La propiedad '__prop__' es de sólo lectura",
"readonly-mode": "Ejecución en modo de sólo lectura. Los cambios no se guardarán."
},
"library": {
"unknownLibrary": "Librería desconocida: __library__",
"unknownType": "Tipo de librería desconocida: __type__",
"readOnly": "La librería __library__ es de sólo lectura",
"failedToInit": "Error al inicializar la librería __library__: __error__",
"invalidProperty": "Propiedad inválida __prop__: '__value__'"
},
"nodes": {
"credentials": {
"error": "Error al cargar las credenciales: __message__",
"error-saving": "Error al guardar credenciales: __message__",
"not-registered": "El tipo de credencial '__type__' no está registrado",
"system-key-warning": "\n\n---------------------------------------------------------------------\nTu archivo de credenciales de flujo se cifra utilizando una clave generada por el sistema. Si la clave generada por el sistema se pierde por cualquier motivo, tu archivo de credenciales no será recuperable, tendrás que borrarlo y volver a introducir tus credenciales. Node-RED volverá a cifrar tu archivo de credenciales utilizando la clave elegida la próxima vez que instancias un cambio.\n---------------------------------------------------------------------\n",
"unencrypted": "Usando credenciales no encriptadas",
"encryptedNotFound": "Credenciales encriptadas no encontradas"
},
"flows": {
"safe-mode": "Flujos detenidos en modo seguro. Instancia para iniciar",
"registered-missing": "Falta tipo registrado: __type__",
"error": "Error al cargar flujos: __message__",
"starting-modified-nodes": "Iniciando nodos modificados",
"starting-modified-flows": "Iniciando flujos modificados",
"starting-flows": "Iniciando flujos",
"started-modified-nodes": "Nodos modificados iniciados",
"started-modified-flows": "Flujos modificados iniciados",
"started-flows": "Flujos iniciados",
"stopping-modified-nodes": "Detención de nodos modificados",
"stopping-modified-flows": "Detención de flujos modificados",
"stopping-flows": "Flujos detenidos",
"stopped-modified-nodes": "Nodos modificados detenidos",
"stopped-modified-flows": "Flujos modificados detenidos",
"stopped-flows": "Flujos detenidos",
"stopped": "Detenido",
"stopping-error": "Error al detener el nodo: __message__",
"updated-flows": "Flujos actualizados",
"added-flow": "Añadiendo flujo: __label__",
"updated-flow": "Flujo actualizado: __label__",
"removed-flow": "Flujo eliminado: __label__",
"missing-types": "Esperando a que se registren los tipos que faltan:",
"missing-type-provided": " - __type__ (proporcionado por el módulo npm __module__)",
"missing-type-install-1": "Para instalar cualquiera de estos módulos que faltan, ejecuta:",
"missing-type-install-2": "en el directorio:"
},
"flow": {
"unknown-type": "Tipo desconocido: __type__",
"missing-types": "tipos que faltan",
"error-loop": "El mensaje superó el número máximo de capturas",
"non-message-returned": "El nodo intentó enviar un mensaje del tipo __type__"
},
"index": {
"unrecognised-id": "id no reconocido: __id__",
"type-in-use": "Tipo en uso: __msg__",
"unrecognised-module": "Módulo no reconocido: __module__"
},
"registry": {
"localfilesystem": {
"module-not-found": "No se puede encontrar el módulo '__module__'"
}
}
},
"storage": {
"index": {
"forbidden-flow-name": "nombre de flujo prohibido"
},
"localfilesystem": {
"user-dir": "Directorio de usuario : __path__",
"flows-file": "Archivo de flujos : __path__",
"create": "Creando nuevo archivo __type__",
"empty": "El archivo __type__ existente está vacío",
"invalid": "El archivo __type__ existente no es un JSON válido",
"restore": "Restaurando copia de seguridad de archivo __type__ : __path__",
"restore-fail": "Error al restaurar la copia de seguridad del archivo __type__ : __message__",
"fsync-fail": "Fallo en el volcado del archivo __path__ al disco : __message__",
"warn_name": "Nombre de archivo de flujos indefinido. Generando usando nombre de servidor",
"projects": {
"changing-project": "Configuración del proyecto activo : __project__",
"active-project": "Proyecto activo : __project__",
"projects-directory": "Directorio de proyectos: __projectsDirectory__",
"project-not-found": "Proyecto no encontrado : __project__",
"no-active-project": "No hay proyecto activo: se utiliza el archivo de flujos por defecto",
"disabled": "Proyectos desactivados : editorTheme.projects.enabled=false",
"disabledNoFlag": "Proyectos desactivados : establece editorTheme.projects.enabled=true a habilitado",
"git-not-found": "Proyectos desactivados : comando git no encontrado",
"git-version-old": "Proyectos desactivados : git __version__ no soportada. Requiere 2.x",
"summary": "Un Proyecto Node-RED",
"readme": "### Acerca de\n\nEste es el archivo README.md de tu proyecto. Ayuda a los usuarios a entender qué hace tu proyecto, cómo usarlo y cualquier otra cosa que necesiten saber."
}
}
},
"context": {
"log-store-init": "Almacén de contexto : '__name__' [__info__]",
"error-loading-module": "Error al cargar el almacén de contexto: __message__",
"error-loading-module2": "Error al cargar el almacén de contexto '__module__': __message__",
"error-module-not-defined": "Falta la opción 'module' en el almacén de contexto '__storage__'.",
"error-invalid-module-name": "Nombre de almacén de contexto no válido: '__name__'",
"error-invalid-default-module": "Almacén de contexto por defecto desconocido: '__storage__'",
"unknown-store": "Se ha especificado un almacén de contexto desconocido '__name__'. Usando almacén por defecto.",
"localfilesystem": {
"invalid-json": "JSON no válido en el archivo de contexto '__file__'",
"error-circular": "El contexto __scope__ contiene una referencia circular que no se puede mantener",
"error-write": "Error al escribir el contexto: __message__"
}
}
}

View File

@@ -26,8 +26,8 @@
"removed-types": "Types de noeuds supprimés :",
"install": {
"invalid": "Nom de module invalide",
"installing": "Installation du module : __nom__, version : __version__",
"installed": "Module installé : __nom__",
"installing": "Installation du module : __name__, version : __version__",
"installed": "Module installé : __name__",
"install-failed": "L'installation a échoué",
"install-failed-long": "L'installation du module __name__ a échoué :",
"install-failed-not-found": "Module $t(server.install.install-failed-long) introuvable",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "3.1.3",
"version": "4.0.0-dev",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "3.1.3",
"@node-red/util": "3.1.3",
"@node-red/registry": "4.0.0-dev",
"@node-red/util": "4.0.0-dev",
"async-mutex": "0.4.0",
"clone": "2.1.2",
"express": "4.18.2",

View File

@@ -636,7 +636,15 @@ function evaluateNodeProperty(value, type, node, msg, callback) {
} else if (type === 're') {
result = new RegExp(value);
} else if (type === 'date') {
result = Date.now();
if (!value) {
result = Date.now();
} else if (value === 'object') {
result = new Date()
} else if (value === 'iso') {
result = (new Date()).toISOString()
} else {
result = moment().format(value)
}
} else if (type === 'bin') {
var data = JSON.parse(value);
if (Array.isArray(data) || (typeof(data) === "string")) {
@@ -769,12 +777,15 @@ function evaluateJSONataExpression(expr,msg,callback) {
});
}
} else {
log.warn('Deprecated API warning: Calls to RED.util.evaluateJSONataExpression must include a callback. '+
'This will not be optional in Node-RED 4.0. Please identify the node from the following stack '+
'and check for an update on npm. If none is available, please notify the node author.')
log.warn(new Error().stack)
const error = new Error('Calls to RED.util.evaluateJSONataExpression must include a callback.')
throw error
}
return expr.evaluate(context, bindings, callback);
expr.evaluate(context, bindings).then(result => {
callback(null, result)
}).catch(err => {
callback(err)
})
}
/**

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "3.1.3",
"version": "4.0.0-dev",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -18,7 +18,7 @@
"fs-extra": "11.1.1",
"i18next": "21.10.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.6",
"jsonata": "2.0.4",
"lodash.clonedeep": "^4.5.0",
"moment": "2.29.4",
"moment-timezone": "0.5.43"

View File

@@ -26,15 +26,14 @@ var server = null;
var apiEnabled = false;
const NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
if (NODE_MAJOR_VERSION > 14) {
const dns = require('node:dns');
if (NODE_MAJOR_VERSION >= 16) {
const dns = require('dns');
dns.setDefaultResultOrder('ipv4first');
}
function checkVersion(userSettings) {
var semver = require('semver');
if (!semver.satisfies(process.version,">=14.0.0")) {
// TODO: in the future, make this a hard error.
if (!semver.satisfies(process.version,">=18.0.0")) {
// var e = new Error("Unsupported version of Node.js");
// e.code = "unsupported_version";
// throw e;

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "3.1.3",
"version": "4.0.0-dev",
"description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org",
"license": "Apache-2.0",
@@ -31,15 +31,15 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "3.1.3",
"@node-red/runtime": "3.1.3",
"@node-red/util": "3.1.3",
"@node-red/nodes": "3.1.3",
"@node-red/editor-api": "4.0.0-dev",
"@node-red/runtime": "4.0.0-dev",
"@node-red/util": "4.0.0-dev",
"@node-red/nodes": "4.0.0-dev",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.18.2",
"fs-extra": "11.1.1",
"node-red-admin": "^3.1.1",
"node-red-admin": "^3.1.2",
"nopt": "5.0.0",
"semver": "7.5.4"
},
@@ -47,6 +47,6 @@
"bcrypt": "5.1.0"
},
"engines": {
"node": ">=14"
"node": ">=18"
}
}

View File

@@ -26,6 +26,13 @@ if (process.argv[2] === 'admin') {
return;
}
var semver = require('semver');
if (!semver.satisfies(process.version, ">=18.0.0")) {
console.log("Unsupported version of Node.js:", process.version);
console.log("Node-RED requires Node.js v18 or later");
process.exit(1)
}
var http = require('http');
var https = require('https');
var util = require("util");
@@ -346,7 +353,7 @@ httpsPromise.then(function(startupHttps) {
} catch(err) {
if (err.code == "unsupported_version") {
console.log("Unsupported version of Node.js:",process.version);
console.log("Node-RED requires Node.js v8.9.0 or later");
console.log("Node-RED requires Node.js v18 or later");
} else {
console.log("Failed to start server:");
if (err.stack) {

View File

@@ -449,6 +449,7 @@ module.exports = {
* - ui (for use with Node-RED Dashboard)
* - debugUseColors
* - debugMaxLength
* - debugStatusLength
* - execMaxBufferSize
* - httpRequestTimeout
* - mqttReconnectTime
@@ -504,6 +505,9 @@ module.exports = {
/** The maximum length, in characters, of any message sent to the debug sidebar tab */
debugMaxLength: 1000,
/** The maximum length, in characters, of status messages under the debug node */
//debugStatusLength: 32,
/** Maximum buffer size for the exec node. Defaults to 10Mb */
//execMaxBufferSize: 10000000,

View File

@@ -1718,9 +1718,13 @@ describe('function node', function() {
describe("init function", function() {
it('should delay handling messages until init completes', function(done) {
const timeoutMS = 200;
// Since helper.load uses process.nextTick timers might occasionally finish
// a couple of milliseconds too early, so give some leeway to the check.
const timeoutCheckMargin = 5;
var flow = [{id:"n1",type:"function",wires:[["n2"]],initialize: `
return new Promise((resolve,reject) => {
setTimeout(resolve,200)
setTimeout(resolve, ${timeoutMS});
})`,
func:"return msg;"
},
@@ -1733,9 +1737,10 @@ describe('function node', function() {
msg.delta = Date.now() - msg.payload;
receivedMsgs.push(msg)
if (receivedMsgs.length === 5) {
var errors = receivedMsgs.filter(msg => msg.delta < 200)
let deltas = receivedMsgs.map(msg => msg.delta);
var errors = deltas.filter(delta => delta < (timeoutMS - timeoutCheckMargin))
if (errors.length > 0) {
done(new Error(`Message received before init completed - was ${msg.delta} expected >300`))
done(new Error(`Message received before init completed - delta values ${JSON.stringify(deltas)} expected to be > ${timeoutMS - timeoutCheckMargin}`))
} else {
done();
}

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