Compare commits

..

105 Commits

Author SHA1 Message Date
Nick O'Leary
61b12f6bbe Merge pull request #3743 from bonanitech/fix-build-script
Ensure all CSS variables are in the output file
2024-05-30 13:43:25 +01:00
Nick O'Leary
d71b22412b Merge pull request #4734 from GogoVega/fix-sidebar-config
Fix the Sidebar Config is not refreshed after a deploy
2024-05-30 13:40:12 +01:00
Nick O'Leary
e408c6b376 Merge pull request #4729 from GogoVega/fix-4728
Fix checkboxes are not updated when calling `typedInput("value", "")`
2024-05-30 10:32:27 +01:00
GogoVega
bf30c24e8e Fix the Sidebar Config is not refreshed after a deploy 2024-05-29 20:44:49 +02:00
GogoVega
6c14ed0ef5 Revert and fix an undefined value returned instead of a false value 2024-05-28 22:08:42 +02:00
Nick O'Leary
6d41ecdae0 Merge pull request #4716 from corentin-sodebo-voile/master
Fix panning with middle mouse button on windows 10/11
2024-05-28 10:28:57 +01:00
Nick O'Leary
3f89bc2733 Merge pull request #4727 from kazuhitoyokoi/master-addjpn
Add Japanese translation for sidebar tooltip
2024-05-28 09:49:35 +01:00
Nick O'Leary
87a25df162 Merge pull request #4730 from GogoVega/translate-multiple-option-label
Translate the number of items selected in the options list
2024-05-28 09:49:03 +01:00
GogoVega
341f43610a Translate the number of items selected in the options list 2024-05-27 19:56:51 +02:00
GogoVega
67cdf3ef96 Fix checkboxes are not updated when calling typedInput("value", "") 2024-05-27 18:59:25 +02:00
Kazuhito Yokoi
cd98f448e9 Add Japanese translation for sidebar tooltip 2024-05-26 23:34:32 +09:00
corentin-sodebo-voile
fac79fd068 Fix panning with middle mouse button on windows 10/11
Without preventDefault, when you try to drag the canvas with middle mouse button on Windows (e.g. in Chrome), the cursor change to a "scroll cursor" and the canvas scrolls endlessly instead of being dragged accurately.
2024-05-23 15:30:13 +02:00
Nick O'Leary
da97c5d558 Merge pull request #4715 from GogoVega/fix-validateNodeEditorProperty
Fix a checkbox should return a Boolean value and not the string `on`
2024-05-23 13:43:55 +01:00
Nick O'Leary
ae7b9fe62e Merge pull request #4714 from GogoVega/fix-4712
Deleting a grouped node should update the group
2024-05-23 13:43:26 +01:00
GogoVega
e52c2911da Fix a checkbox must return a boolean value and not on 2024-05-23 10:57:51 +02:00
Nick O'Leary
07a29ff779 Merge pull request #4711 from GogoVega/change-config-node-cursor
Change the Config Node cursor to `pointer`
2024-05-22 17:02:05 +01:00
Nick O'Leary
1b4a8ebe83 Merge branch 'master' into fix-4712 2024-05-22 17:01:29 +01:00
GogoVega
abf2eacf18 Revert and fix without using RED.group.removeFromGroup 2024-05-21 19:48:21 +02:00
Gauthier Dandele
f808f4e2e8 Apply suggestions from code review
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-05-21 19:01:57 +02:00
Nick O'Leary
ca33d6b799 Merge pull request #4713 from GogoVega/add-missing-sidebar-tooltips
Add missing tooltips to Sidebar
2024-05-21 17:15:52 +01:00
Nick O'Leary
940740f15d Merge pull request #4710 from node-red/4704-node-edit-history
Allow nodes to return additional history entries in onEditSave
2024-05-21 17:10:23 +01:00
GogoVega
51208fcd0c Fix indexOf returns -1 2024-05-21 16:06:15 +02:00
GogoVega
707152d82f Deleting a grouped node should update the group 2024-05-21 15:07:26 +02:00
GogoVega
5538f6dd8a Add missing tooltips to Sidebar 2024-05-21 12:58:29 +02:00
GogoVega
d601e2caa4 Change the config node cursor to pointer 2024-05-21 11:37:01 +02:00
Nick O'Leary
46fdf56c79 Allow nodes to return additional history entries in onEditSave 2024-05-20 16:41:44 +01:00
Nick O'Leary
f55ee6e665 Merge pull request #4685 from node-red/4683-preserve-full-error-obj
Pass full error object in Function node and copy over cause property
2024-05-13 15:25:02 +01:00
Nick O'Leary
03648dc7e8 Update tests for changed function node low-level output 2024-05-09 17:25:47 +01:00
Nick O'Leary
66a667fe58 Pass full error object in Function node and copy over cause property
Fixes #4683
2024-05-09 16:48:51 +01:00
Nick O'Leary
1bb3a0eca5 Merge pull request #4534 from patlux/master
Replacing vm.createScript in favour of vm.Script
2024-05-09 15:19:45 +01:00
Nick O'Leary
08927dfb55 Merge pull request #4684 from node-red/4363-autoLogin-redirect-loop
Avoid login loops when autoLogin enabled but login fails
2024-05-08 15:48:21 +01:00
Nick O'Leary
b27483de9c Avoid login loops when autoLogin enabled but login fails
Fixes #4363
2024-05-08 15:09:51 +01:00
Nick O'Leary
211d420fb2 Merge pull request #4667 from node-red/fix-subflow-property-undo
Fix undo of subflow env property edits
2024-04-23 23:45:46 +02:00
Nick O'Leary
b8ca4665c1 Merge pull request #4660 from JoshuaCWebDeveloper/patch-1
Fix three error typos in monaco.js
2024-04-23 23:45:16 +02:00
Nick O'Leary
960af87fb0 Ensure subflow change state is cleared after deploy 2024-04-23 21:17:35 +02:00
Nick O'Leary
de7339ae97 Fix undo of subflow env property edits 2024-04-23 20:39:14 +02:00
Stephen McLaughlin
0995af62b6 Merge pull request #4664 from ZJvandeWeg/patch-3
docs: Add closing paragraph tag
2024-04-20 13:54:37 +01:00
Zeger-Jan van de Weg
c2e03a40b4 docs: Add closing paragraph tag
Minor change that only improves xpath parsing.
2024-04-20 14:20:59 +02:00
Joshua Carter
c855050bcf Fix three error typos in monaco.js 2024-04-15 08:09:26 -07:00
Nick O'Leary
29ed5b2792 Merge pull request #4655 from node-red/rel319
Bump for 3.1.9 release
2024-04-11 19:22:24 +01:00
Nick O'Leary
e39216e65a Bump for 3.1.9 release 2024-04-11 19:15:46 +01:00
Nick O'Leary
7ac7f9b4c8 Merge pull request #4654 from node-red/fix-subflow-recursion-check
Prevent subflow being added to itself
2024-04-11 19:12:43 +01:00
Stephen McLaughlin
4709eb9d49 Merge pull request #4652 from node-red/fix-windows-spawn
Fix use of spawn on windows with cmd files
2024-04-11 17:51:13 +01:00
Nick O'Leary
c13b8266dd Prevent subflow being added to itself 2024-04-11 17:05:10 +01:00
Nick O'Leary
bd58431603 Fix use of spawn on windows with cmd files 2024-04-11 14:40:29 +01:00
Nick O'Leary
9a3cb0b2b5 Merge pull request #4640 from node-red/fix-subflow-init-err
Guard refresh of unknown subflow
2024-04-02 20:06:47 +01:00
Nick O'Leary
6beae5a806 Merge pull request #4642 from node-red/4641-fix-subflow-module-debug-logging
Fix subflow module sending messages to debug sidebar
2024-04-02 20:06:31 +01:00
Nick O'Leary
a0636632a1 Fix subflow module sending messages to debug sidebar
Fixes #4641
2024-04-02 17:42:19 +01:00
Nick O'Leary
5dfa47ab6c Guard refresh of unknown subflow 2024-04-02 15:54:34 +01:00
Nick O'Leary
ade4679e8c Merge pull request #4636 from node-red/rel318
Bump for 3.1.8
2024-03-28 15:23:07 +00:00
Nick O'Leary
410b938442 Bump for 3.1.8 2024-03-28 15:02:02 +00:00
Nick O'Leary
19dcc3a683 Merge pull request #4632 from node-red/4625-sf-env-err-handling
Add validation and error handling on subflow instance properties
2024-03-28 11:10:28 +00:00
Nick O'Leary
20d067c1ea Merge pull request #4633 from node-red/4617-hide-library-context-options
Hide import/export context menu if disabled in theme
2024-03-28 11:10:14 +00:00
Nick O'Leary
9526566799 Hide import/export context menu if disabled in theme 2024-03-28 11:00:10 +00:00
Nick O'Leary
0b9dd82c91 Merge pull request #4631 from node-red/4626-subflow-change-notification
Show change indicator on subflow tabs
2024-03-27 19:10:39 +00:00
Nick O'Leary
19213434f9 Add validation to subflow instance env properties 2024-03-27 19:08:25 +00:00
Nick O'Leary
014691346a Handle malformed env var values and log errors 2024-03-27 18:23:12 +00:00
Nick O'Leary
6738b95c29 Merge pull request #4630 from node-red/bump-express
Bump dependencies
2024-03-27 18:11:54 +00:00
Nick O'Leary
6a8230ec1e Show change icon on subflow tabs
Fixes #4626
2024-03-27 18:10:04 +00:00
Nick O'Leary
5679d264b6 Bump dependencies 2024-03-27 18:00:06 +00:00
Nick O'Leary
37265cf4ef Merge pull request #4619 from node-red/4600-reset-workspace-index
Reset workspace index when clearing nodes
2024-03-21 17:38:39 +00:00
Nick O'Leary
8a63275989 Merge pull request #4613 from kazuhitoyokoi/master-fixglobalconfig
Remove typo in global config
2024-03-21 16:54:01 +00:00
Nick O'Leary
7fc64a84e8 Bump test helper 2024-03-21 15:16:49 +00:00
Nick O'Leary
02f7cdd5aa Ensure all httpRequest test servers are ready before tests run 2024-03-21 15:03:37 +00:00
Nick O'Leary
d7dcceef60 Add debug for http tests 2024-03-21 11:32:29 +00:00
Nick O'Leary
ae5e1570ae Reset workspace index when clearing nodes
Fixes #4600
2024-03-21 11:14:34 +00:00
Kazuhito Yokoi
3ca045394a Remove typo in global config 2024-03-16 18:51:13 +09:00
Nick O'Leary
179032cd4d Merge pull request #4608 from node-red/rel317
Bump for 3.1.7 release
2024-03-12 17:43:32 +00:00
Nick O'Leary
6a6f0d04d6 Bump for 3.1.7 release 2024-03-12 14:25:41 +00:00
Nick O'Leary
add4d9758c Merge pull request #4603 from kazuhitoyokoi/master-addjpn
Add Japanese translation for v3.1.6
2024-03-11 16:07:28 +00:00
Kazuhito Yokoi
a0d3ea62b2 Add Japanese translation for v3.1.6 2024-03-10 23:36:20 +09:00
Nick O'Leary
7447e88a50 Merge pull request #4593 from hardillb/hardillb-patch-1
Update jsonata version
2024-03-07 14:26:02 +00:00
Ben Hardill
a193b79d3d Bump jsonata to match utils 2024-03-05 10:31:03 +00:00
Ben Hardill
da380f7464 Update jsonata version
Pulls in fix for CVE-2024-27307
2024-03-05 10:22:49 +00:00
Nick O'Leary
269cf02c0b Merge pull request #4586 from node-red/rel316
Bump for 3.1.6 release
2024-03-01 11:47:57 +00:00
Nick O'Leary
fb50e2772a Bump for 3.1.6 release 2024-03-01 10:50:06 +00:00
Nick O'Leary
058c97138a Merge pull request #4582 from node-red/3795-allow-env-var-in-num-field-validation
Do not flag env var in num typedInput as error
2024-02-26 17:01:45 +00:00
Nick O'Leary
828ae29aed Merge pull request #4581 from node-red/4579-fix-undef-env-vars
Handle undefined env vars
2024-02-26 17:01:27 +00:00
Nick O'Leary
6a0f45140c Merge pull request #4568 from JaysonHurst/fips
fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 …
2024-02-26 17:00:26 +00:00
Nick O'Leary
50a267528d Merge pull request #4580 from giscafer/remove-never-use-code
chore: remove never use import code
2024-02-26 16:58:20 +00:00
Nick O'Leary
220786be60 Do not flag env var in num typedInput as error 2024-02-26 16:55:01 +00:00
Nick O'Leary
fa78bb3d78 Handle undefined env vars
Fixes #4579
2024-02-26 16:17:09 +00:00
Nick O'Leary
9a32ebd0c0 Merge pull request #4578 from kazuhitoyokoi/master-fiximportdialog
Fix example flow name in import dialog
2024-02-26 16:09:10 +00:00
giscafer
4643f5e8cc chore: remove never use import code 2024-02-25 22:44:01 +08:00
Kazuhito Yokoi
7de0984d6d Update test case for example flow name 2024-02-25 17:38:46 +09:00
Kazuhito Yokoi
635334f096 Fix example flow name in import dialog 2024-02-25 17:04:42 +09:00
Nick O'Leary
f0d0990b5a Merge pull request #4575 from giscafer/master
fix: template node zh-CN translation
2024-02-22 13:03:24 +00:00
giscafer
43b3589451 fix: template node zh-CN translation 2024-02-22 13:02:06 +08:00
Nick O'Leary
016a19ba7c Merge pull request #4570 from node-red/fix-icon-scaling
Fix missing node icons in workspace
2024-02-20 10:37:33 +00:00
Nick O'Leary
aeb79bce2a Fix missing node icons in workspace 2024-02-19 16:07:22 +00:00
Jayson Hurst
0ab9b9a5fd Merge branch 'master' into fips 2024-02-16 17:53:30 -07:00
Jayson Hurst
56e58521bd Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 crypto hashes to work with the FIPS crypto policy. 2024-02-17 00:35:03 +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
Patrick Wozniak
28907082f1 fix usage of vm.Script() 2024-01-21 02:16:00 +01:00
Patrick Wozniak
f83174c40a fix use of vm.Script by adding new 2024-01-21 01:23:07 +01:00
Patrick Wozniak
ec062d008f replace vm.createScript in favor of vm.Script 2024-01-21 01:13:00 +01:00
Patrick Wozniak
a587655a5a adding pollyfill for vm.createScript
adds support for bun.sh
2024-01-21 01:00:02 +01:00
Mauricio Bonani
3e6f0acf79 Refactor code 2023-10-03 15:42:33 -04:00
Mauricio Bonani
7f93d943d7 Merge remote-tracking branch 'upstream/master' into fix-build-script 2023-10-03 09:04:40 -04:00
Mauricio Bonani
12543d2c2a Ensure all CSS variables are in the output file 2022-07-06 19:06:11 -04:00
58 changed files with 526 additions and 332 deletions

View File

@@ -1,3 +1,49 @@
#### 3.1.9: Maintenance Release
- Prevent subflow being added to itself (#4654) @knolleary
- Fix use of spawn on windows with cmd files (#4652) @knolleary
- Guard refresh of unknown subflow (#4640) @knolleary
- Fix subflow module sending messages to debug sidebar (#4642) @knolleary
#### 3.1.8: Maintenance Release
- Add validation and error handling on subflow instance properties (#4632) @knolleary
- Hide import/export context menu if disabled in theme (#4633) @knolleary
- Show change indicator on subflow tabs (#4631) @knolleary
- Bump dependencies (#4630) @knolleary
- Reset workspace index when clearing nodes (#4619) @knolleary
- Remove typo in global config (#4613) @kazuhitoyokoi
#### 3.1.7: Maintenance Release
- Add Japanese translation for v3.1.6 (#4603) @kazuhitoyokoi
- Update jsonata version (#4593) @hardillb
#### 3.1.6: Maintenance Release
Editor
- Do not flag env var in num typedInput as error (#4582) @knolleary
- Fix example flow name in import dialog (#4578) @kazuhitoyokoi
- Fix missing node icons in workspace (#4570) @knolleary
Runtime
- Handle undefined env vars (#4581) @knolleary
- fix: Removed offending MD5 crypto hash and replaced with SHA1 and SHA256 … (#4568) @JaysonHurst
- chore: remove never use import code (#4580) @giscafer
Nodes
- fix: template node zh-CN translation (#4575) @giscafer
#### 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 #### 3.1.4: Maintenance Release
Editor Editor

View File

@@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "3.1.4", "version": "3.1.9",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "https://nodered.org", "homepage": "https://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -41,7 +41,7 @@
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
"denque": "2.1.0", "denque": "2.1.0",
"express": "4.18.2", "express": "4.19.2",
"express-session": "1.17.3", "express-session": "1.17.3",
"form-data": "4.0.0", "form-data": "4.0.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
@@ -54,7 +54,7 @@
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.6", "jsonata": "1.8.7",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"memorystore": "1.6.7", "memorystore": "1.6.7",
@@ -64,7 +64,7 @@
"mqtt": "4.3.7", "mqtt": "4.3.7",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"mustache": "4.2.0", "mustache": "4.2.0",
"node-red-admin": "^3.1.2", "node-red-admin": "^3.1.3",
"node-watch": "0.7.4", "node-watch": "0.7.4",
"nopt": "5.0.0", "nopt": "5.0.0",
"oauth2orize": "1.11.1", "oauth2orize": "1.11.1",
@@ -74,7 +74,7 @@
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"semver": "7.5.4", "semver": "7.5.4",
"tar": "6.1.13", "tar": "6.2.1",
"tough-cookie": "4.1.3", "tough-cookie": "4.1.3",
"uglify-js": "3.17.4", "uglify-js": "3.17.4",
"uuid": "9.0.0", "uuid": "9.0.0",
@@ -112,7 +112,7 @@
"mermaid": "^10.4.0", "mermaid": "^10.4.0",
"minami": "1.2.3", "minami": "1.2.3",
"mocha": "9.2.2", "mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.2", "node-red-node-test-helper": "^0.3.3",
"nodemon": "2.0.20", "nodemon": "2.0.20",
"proxy": "^1.0.2", "proxy": "^1.0.2",
"sass": "1.62.1", "sass": "1.62.1",

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var apiUtils = require("../util");
var runtimeAPI; var runtimeAPI;
var settings; var settings;
var theme = require("../editor/theme"); var theme = require("../editor/theme");

View File

@@ -205,9 +205,10 @@ function genericStrategy(adminApp,strategy) {
passport.use(new strategy.strategy(options, verify)); passport.use(new strategy.strategy(options, verify));
adminApp.get('/auth/strategy', adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, {session:false, passport.authenticate(strategy.name, {
session:false,
failureMessage: true, failureMessage: true,
failureRedirect: settings.httpAdminRoot failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
}), }),
completeGenerateStrategyAuth, completeGenerateStrategyAuth,
handleStrategyError handleStrategyError
@@ -221,7 +222,7 @@ function genericStrategy(adminApp,strategy) {
passport.authenticate(strategy.name, { passport.authenticate(strategy.name, {
session:false, session:false,
failureMessage: true, failureMessage: true,
failureRedirect: settings.httpAdminRoot failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
}), }),
completeGenerateStrategyAuth, completeGenerateStrategyAuth,
handleStrategyError handleStrategyError

View File

@@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy;
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy; var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
var passport = require("passport"); var passport = require("passport");
var crypto = require("crypto");
var util = require("util"); var util = require("util");
var Tokens = require("./tokens"); var Tokens = require("./tokens");

View File

@@ -14,11 +14,9 @@
* limitations under the License. * limitations under the License.
**/ **/
var express = require("express");
var path = require('path'); var path = require('path');
var comms = require("./comms"); var comms = require("./comms");
var library = require("./library");
var info = require("./settings"); var info = require("./settings");
var auth = require("../auth"); var auth = require("../auth");

View File

@@ -15,8 +15,6 @@
**/ **/
var apiUtils = require("../util"); var apiUtils = require("../util");
var fs = require('fs');
var fspath = require('path');
var runtimeAPI; var runtimeAPI;

View File

@@ -13,9 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var fs = require('fs');
var path = require('path');
// var apiUtil = require('../util');
var i18n = require("@node-red/util").i18n; // TODO: separate module var i18n = require("@node-red/util").i18n; // TODO: separate module

View File

@@ -15,7 +15,6 @@
**/ **/
var apiUtils = require("../util"); var apiUtils = require("../util");
var express = require("express");
var runtimeAPI; var runtimeAPI;
var settings; var settings;

View File

@@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var express = require("express");
var util = require("util"); var util = require("util");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");

View File

@@ -99,7 +99,7 @@ module.exports = {
// settings.instanceId is set asynchronously to the editor-api // settings.instanceId is set asynchronously to the editor-api
// being initiaised. So we defer calculating the cacheBuster hash // being initiaised. So we defer calculating the cacheBuster hash
// until the first load of the editor // until the first load of the editor
cacheBuster = crypto.createHash('md5').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12) cacheBuster = crypto.createHash('sha1').update(`${settings.version || 'version'}-${settings.instanceId || 'instanceId'}`).digest("hex").substring(0,12)
} }
let sessionMessages; let sessionMessages;

View File

@@ -24,11 +24,8 @@
* @namespace @node-red/editor-api * @namespace @node-red/editor-api
*/ */
var express = require("express");
var bodyParser = require("body-parser"); var bodyParser = require("body-parser");
var util = require('util');
var passport = require('passport'); var passport = require('passport');
var cors = require('cors');
var auth = require("./auth"); var auth = require("./auth");
var apiUtil = require("./util"); var apiUtil = require("./util");

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,14 +16,14 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "3.1.4", "@node-red/util": "3.1.9",
"@node-red/editor-client": "3.1.4", "@node-red/editor-client": "3.1.9",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.5", "cors": "2.8.5",
"express-session": "1.17.3", "express-session": "1.17.3",
"express": "4.18.2", "express": "4.19.2",
"memorystore": "1.6.7", "memorystore": "1.6.7",
"mime": "3.0.0", "mime": "3.0.0",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",

View File

@@ -719,6 +719,7 @@
"nodeHelp": "Node Help", "nodeHelp": "Node Help",
"showHelp": "Show help", "showHelp": "Show help",
"showInOutline": "Show in outline", "showInOutline": "Show in outline",
"hideTopics": "Hide topics",
"showTopics": "Show topics", "showTopics": "Show topics",
"noHelp": "No help topic selected", "noHelp": "No help topic selected",
"changeLog": "Change Log" "changeLog": "Change Log"
@@ -914,6 +915,8 @@
} }
}, },
"typedInput": { "typedInput": {
"selected": "__count__ selected",
"selected_plural": "__count__ selected",
"type": { "type": {
"str": "string", "str": "string",
"num": "number", "num": "number",

View File

@@ -719,6 +719,7 @@
"nodeHelp": "Aide sur les noeuds", "nodeHelp": "Aide sur les noeuds",
"showHelp": "Afficher l'aide", "showHelp": "Afficher l'aide",
"showInOutline": "Afficher dans les grandes lignes", "showInOutline": "Afficher dans les grandes lignes",
"hideTopics": "Masquer les sujets",
"showTopics": "Afficher les sujets", "showTopics": "Afficher les sujets",
"noHelp": "Aucune rubrique d'aide sélectionnée", "noHelp": "Aucune rubrique d'aide sélectionnée",
"changeLog": "Journal des modifications" "changeLog": "Journal des modifications"
@@ -914,6 +915,8 @@
} }
}, },
"typedInput": { "typedInput": {
"selected": "__count__ sélectionnée",
"selected_plural": "__count__ sélectionnées",
"type": { "type": {
"str": "chaîne de caractères", "str": "chaîne de caractères",
"num": "nombre", "num": "nombre",

View File

@@ -303,7 +303,8 @@
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません" "missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
}, },
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。", "conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。" "conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。",
"alreadyExists": "本ノードは既に存在"
}, },
"copyMessagePath": "パスをコピーしました", "copyMessagePath": "パスをコピーしました",
"copyMessageValue": "値をコピーしました", "copyMessageValue": "値をコピーしました",
@@ -718,6 +719,7 @@
"nodeHelp": "ノードヘルプ", "nodeHelp": "ノードヘルプ",
"showHelp": "ヘルプを表示", "showHelp": "ヘルプを表示",
"showInOutline": "アウトラインに表示", "showInOutline": "アウトラインに表示",
"hideTopics": "トピックを非表示",
"showTopics": "トピックを表示", "showTopics": "トピックを表示",
"noHelp": "ヘルプのトピックが未選択", "noHelp": "ヘルプのトピックが未選択",
"changeLog": "更新履歴" "changeLog": "更新履歴"

View File

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

View File

@@ -547,12 +547,16 @@ RED.nodes = (function() {
* @param {String} z tab id * @param {String} z tab id
*/ */
checkTabState: function (z) { checkTabState: function (z) {
const ws = workspaces[z] const ws = workspaces[z] || subflows[z]
if (ws) { if (ws) {
const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0 const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
if (Boolean(ws.contentsChanged) !== contentsChanged) { if (Boolean(ws.contentsChanged) !== contentsChanged) {
ws.contentsChanged = contentsChanged ws.contentsChanged = contentsChanged
RED.events.emit("flows:change", ws); if (ws.type === 'tab') {
RED.events.emit("flows:change", ws);
} else {
RED.events.emit("subflows:change", ws);
}
} }
} }
} }
@@ -1025,7 +1029,22 @@ RED.nodes = (function() {
RED.nodes.registerType("subflow:"+sf.id, { RED.nodes.registerType("subflow:"+sf.id, {
defaults:{ defaults:{
name:{value:""}, name:{value:""},
env:{value:[]} env:{value:[], validate: function(value) {
const errors = []
if (value) {
value.forEach(env => {
const r = RED.utils.validateTypedProperty(env.value, env.type)
if (r !== true) {
errors.push(env.name+': '+r)
}
})
}
if (errors.length === 0) {
return true
} else {
return errors
}
}}
}, },
icon: function() { return sf.icon||"subflow.svg" }, icon: function() { return sf.icon||"subflow.svg" },
category: sf.category || "subflows", category: sf.category || "subflows",

View File

@@ -734,12 +734,12 @@
} }
if (menu.opts.multiple) { if (menu.opts.multiple) {
var selected = {}; var selected = {};
this.value().split(",").forEach(function(f) { this.value().split(",").forEach(function(f) {
selected[f] = true; selected[f] = true;
}) });
menu.find('input[type="checkbox"]').each(function() { menu.find('input[type="checkbox"]').each(function() {
$(this).prop("checked",selected[$(this).data('value')]) $(this).prop("checked", selected[$(this).data('value')] || false);
}) });
} }
@@ -830,7 +830,7 @@
this.input.trigger('change',[this.propertyType,this.value()]); this.input.trigger('change',[this.propertyType,this.value()]);
} }
} else { } else {
this.optionSelectLabel.text(o.length+" selected"); this.optionSelectLabel.text(RED._("typedInput.selected", { count: o.length }));
} }
} }
}, },

View File

@@ -118,10 +118,16 @@ RED.contextMenu = (function () {
onselect: 'core:split-wire-with-link-nodes', onselect: 'core:split-wire-with-link-nodes',
disabled: !canEdit || !hasLinks disabled: !canEdit || !hasLinks
}, },
null, null
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
) )
if (RED.settings.theme("menu.menu-item-import-library", true)) {
insertOptions.push(
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
)
}
if (hasSelection && canEdit) { if (hasSelection && canEdit) {
const nodeOptions = [] const nodeOptions = []
if (!hasMultipleSelection && !isGroup) { if (!hasMultipleSelection && !isGroup) {
@@ -194,8 +200,14 @@ RED.contextMenu = (function () {
{ onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() }, { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
{ onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete }, { onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete },
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete }, { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, )
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }, if (RED.settings.theme("menu.menu-item-export-library", true)) {
menuItems.push(
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
)
}
menuItems.push(
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
) )
} }

View File

@@ -612,7 +612,10 @@ RED.deploy = (function() {
} }
}); });
RED.nodes.eachSubflow(function (subflow) { RED.nodes.eachSubflow(function (subflow) {
subflow.changed = false; if (subflow.changed) {
subflow.changed = false;
RED.events.emit("subflows:change", subflow);
}
}); });
RED.nodes.eachWorkspace(function (ws) { RED.nodes.eachWorkspace(function (ws) {
if (ws.changed || ws.added) { if (ws.changed || ws.added) {
@@ -628,6 +631,7 @@ RED.deploy = (function() {
// Once deployed, cannot undo back to a clean state // Once deployed, cannot undo back to a clean state
RED.history.markAllDirty(); RED.history.markAllDirty();
RED.view.redraw(); RED.view.redraw();
RED.sidebar.config.refresh();
RED.events.emit("deploy"); RED.events.emit("deploy");
}).fail(function (xhr, textStatus, err) { }).fail(function (xhr, textStatus, err) {
RED.nodes.dirty(true); RED.nodes.dirty(true);

View File

@@ -248,6 +248,8 @@ RED.editor = (function() {
var value = input.val(); var value = input.val();
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") { if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
value = input.text(); value = input.text();
} else if (input.attr("type") === "checkbox") {
value = input.prop("checked");
} }
var valid = validateNodeProperty(node, defaults, property,value); var valid = validateNodeProperty(node, defaults, property,value);
if (((typeof valid) === "string") || !valid) { if (((typeof valid) === "string") || !valid) {
@@ -741,9 +743,16 @@ RED.editor = (function() {
} }
try { try {
var rc = editing_node._def.oneditsave.call(editing_node); const rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) { if (rc === true) {
editState.changed = true; editState.changed = true;
} else if (typeof rc === 'object' && rc !== null ) {
if (rc.changed === true) {
editState.changed = true
}
if (Array.isArray(rc.history) && rc.history.length > 0) {
editState.history = rc.history
}
} }
} catch(err) { } catch(err) {
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString()); console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
@@ -914,6 +923,17 @@ RED.editor = (function() {
dirty: startDirty dirty: startDirty
} }
if (editing_node.g) {
const group = RED.nodes.group(editing_node.g);
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
const index = group?.nodes.indexOf(editing_node) ?? -1;
if (index > -1) {
group.nodes.splice(index, 1);
RED.group.markDirty(group);
}
}
RED.nodes.dirty(true); RED.nodes.dirty(true);
RED.view.redraw(true); RED.view.redraw(true);
RED.history.push(historyEvent); RED.history.push(historyEvent);
@@ -1015,7 +1035,7 @@ RED.editor = (function() {
} }
}); });
} }
var historyEvent = { let historyEvent = {
t:'edit', t:'edit',
node:editing_node, node:editing_node,
changes:editState.changes, changes:editState.changes,
@@ -1031,6 +1051,15 @@ RED.editor = (function() {
instances:subflowInstances instances:subflowInstances
} }
} }
if (editState.history) {
historyEvent = {
t: 'multi',
events: [ historyEvent, ...editState.history ],
dirty: wasDirty
}
}
RED.history.push(historyEvent); RED.history.push(historyEvent);
} }
editing_node.dirty = true; editing_node.dirty = true;
@@ -1623,8 +1652,8 @@ RED.editor = (function() {
} }
if (!isSameObj(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env;
editState.changes.env = editing_node.env; editState.changes.env = editing_node.env;
editing_node.env = new_env;
editState.changed = true; editState.changed = true;
} }

View File

@@ -514,7 +514,7 @@ RED.editor.codeEditor.monaco = (function() {
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions); _monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); } if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
} catch (error) { } catch (error) {
console.warn("monaco - Error setting up json options", err) console.warn("monaco - Error setting up json options", error)
} }
} }
@@ -526,7 +526,7 @@ RED.editor.codeEditor.monaco = (function() {
if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); } if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); } if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
} catch (error) { } catch (error) {
console.warn("monaco - Error setting up html options", err) console.warn("monaco - Error setting up html options", error)
} }
} }
@@ -546,7 +546,7 @@ RED.editor.codeEditor.monaco = (function() {
if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); } if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); } if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
} catch (error) { } catch (error) {
console.warn("monaco - Error setting up CSS/SCSS/LESS options", err) console.warn("monaco - Error setting up CSS/SCSS/LESS options", error)
} }
} }

View File

@@ -382,9 +382,11 @@ RED.sidebar.config = (function() {
refreshConfigNodeList(); refreshConfigNodeList();
} }
}); });
RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes")); RED.popover.tooltip($('#red-ui-sidebar-config-filter-all'), RED._("sidebar.config.showAllConfigNodes"));
RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes")); RED.popover.tooltip($('#red-ui-sidebar-config-filter-unused'), RED._("sidebar.config.showAllUnusedConfigNodes"));
RED.popover.tooltip($('#red-ui-sidebar-config-collapse-all'), RED._("palette.actions.collapse-all"));
RED.popover.tooltip($('#red-ui-sidebar-config-expand-all'), RED._("palette.actions.expand-all"));
} }
function flashConfigNode(el) { function flashConfigNode(el) {

View File

@@ -36,7 +36,13 @@ RED.sidebar.help = (function() {
toolbar = $("<div>", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content); toolbar = $("<div>", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content);
$('<span class="button-group"><a id="red-ui-sidebar-help-show-toc" class="red-ui-button red-ui-button-small selected" href="#"><i class="fa fa-list-ul"></i></a></span>').appendTo(toolbar) $('<span class="button-group"><a id="red-ui-sidebar-help-show-toc" class="red-ui-button red-ui-button-small selected" href="#"><i class="fa fa-list-ul"></i></a></span>').appendTo(toolbar)
var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc') var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc')
RED.popover.tooltip(showTOCButton,RED._("sidebar.help.showTopics")); RED.popover.tooltip(showTOCButton, function () {
if ($(showTOCButton).hasClass('selected')) {
return RED._("sidebar.help.hideTopics");
} else {
return RED._("sidebar.help.showTopics");
}
});
showTOCButton.on("click",function(e) { showTOCButton.on("click",function(e) {
e.preventDefault(); e.preventDefault();
if ($(this).hasClass('selected')) { if ($(this).hasClass('selected')) {
@@ -158,8 +164,10 @@ RED.sidebar.help = (function() {
function refreshSubflow(sf) { function refreshSubflow(sf) {
var item = treeList.treeList('get',"node-type:subflow:"+sf.id); var item = treeList.treeList('get',"node-type:subflow:"+sf.id);
item.subflowLabel = sf._def.label().toLowerCase(); if (item) {
item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); item.subflowLabel = sf._def.label().toLowerCase();
item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
}
} }
function hideTOC() { function hideTOC() {

View File

@@ -906,7 +906,10 @@ RED.utils = (function() {
* @returns true if valid, String if invalid * @returns true if valid, String if invalid
*/ */
function validateTypedProperty(propertyValue, propertyType, opt) { function validateTypedProperty(propertyValue, propertyType, opt) {
if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) {
// Allow ${ENV_VAR} value
return true
}
let error let error
if (propertyType === 'json') { if (propertyType === 'json') {
try { try {

View File

@@ -646,120 +646,128 @@ RED.view = (function() {
} }
d3.event = event; d3.event = event;
var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
var result = createNode(selected_tool);
if (!result) {
return;
}
var historyEvent = result.historyEvent;
var nn = RED.nodes.add(result.node);
var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
nn.l = showLabel;
}
var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
var helperWidth = ui.helper.width();
var helperHeight = ui.helper.height();
var mousePos = d3.touches(this)[0]||d3.mouse(this);
try { try {
var isLink = (nn.type === "link in" || nn.type === "link out") var result = createNode(selected_tool);
var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; if (!result) {
return;
var label = RED.utils.getNodeLabel(nn, nn.type);
var labelParts = getLabelParts(label, "red-ui-flow-node-label");
if (hideLabel) {
nn.w = node_height;
nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
} else {
nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
} }
} catch(err) { var historyEvent = result.historyEvent;
} var nn = RED.nodes.add(result.node);
mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
mousePos[1] /= scaleFactor; nn.l = showLabel;
mousePos[0] /= scaleFactor; }
nn.x = mousePos[0]; var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
nn.y = mousePos[1]; var helperWidth = ui.helper.width();
var helperHeight = ui.helper.height();
var mousePos = d3.touches(this)[0]||d3.mouse(this);
var minX = nn.w/2 -5; try {
if (nn.x < minX) { var isLink = (nn.type === "link in" || nn.type === "link out")
nn.x = minX; var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
}
var minY = nn.h/2 -5;
if (nn.y < minY) {
nn.y = minY;
}
var maxX = space_width -nn.w/2 +5;
if (nn.x > maxX) {
nn.x = maxX;
}
var maxY = space_height -nn.h +5;
if (nn.y > maxY) {
nn.y = maxY;
}
if (snapGrid) { var label = RED.utils.getNodeLabel(nn, nn.type);
var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); var labelParts = getLabelParts(label, "red-ui-flow-node-label");
nn.x -= gridOffset.x; if (hideLabel) {
nn.y -= gridOffset.y; nn.w = node_height;
} nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
} else {
nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
}
} catch(err) {
}
var linkToSplice = $(ui.helper).data("splice"); mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
if (linkToSplice) { mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
spliceLink(linkToSplice, nn, historyEvent) mousePos[1] /= scaleFactor;
} mousePos[0] /= scaleFactor;
nn.x = mousePos[0];
nn.y = mousePos[1];
var minX = nn.w/2 -5;
if (nn.x < minX) {
nn.x = minX;
}
var minY = nn.h/2 -5;
if (nn.y < minY) {
nn.y = minY;
}
var maxX = space_width -nn.w/2 +5;
if (nn.x > maxX) {
nn.x = maxX;
}
var maxY = space_height -nn.h +5;
if (nn.y > maxY) {
nn.y = maxY;
}
if (snapGrid) {
var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
nn.x -= gridOffset.x;
nn.y -= gridOffset.y;
}
var linkToSplice = $(ui.helper).data("splice");
if (linkToSplice) {
spliceLink(linkToSplice, nn, historyEvent)
}
var group = $(ui.helper).data("group");
if (group) {
var oldX = group.x;
var oldY = group.y;
RED.group.addToGroup(group, nn);
var moveEvent = null;
if ((group.x !== oldX) ||
(group.y !== oldY)) {
moveEvent = {
t: "move",
nodes: [{n: group,
ox: oldX, oy: oldY,
dx: group.x -oldX,
dy: group.y -oldY}],
dirty: true
};
}
historyEvent = {
t: 'multi',
events: [historyEvent],
var group = $(ui.helper).data("group");
if (group) {
var oldX = group.x;
var oldY = group.y;
RED.group.addToGroup(group, nn);
var moveEvent = null;
if ((group.x !== oldX) ||
(group.y !== oldY)) {
moveEvent = {
t: "move",
nodes: [{n: group,
ox: oldX, oy: oldY,
dx: group.x -oldX,
dy: group.y -oldY}],
dirty: true
}; };
if (moveEvent) {
historyEvent.events.push(moveEvent)
}
historyEvent.events.push({
t: "addToGroup",
group: group,
nodes: nn
})
} }
historyEvent = {
t: 'multi',
events: [historyEvent],
}; RED.history.push(historyEvent);
if (moveEvent) { RED.editor.validateNode(nn);
historyEvent.events.push(moveEvent) RED.nodes.dirty(true);
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
movingSet.add(nn);
updateActiveNodes();
updateSelection();
redraw();
if (nn._def.autoedit) {
RED.editor.edit(nn);
}
} catch (error) {
if (error.code != "NODE_RED") {
RED.notify(RED._("notification.error",{message:error.toString()}),"error");
} else {
RED.notify(RED._("notification.error",{message:error.message}),"error");
} }
historyEvent.events.push({
t: "addToGroup",
group: group,
nodes: nn
})
}
RED.history.push(historyEvent);
RED.editor.validateNode(nn);
RED.nodes.dirty(true);
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
movingSet.add(nn);
updateActiveNodes();
updateSelection();
redraw();
if (nn._def.autoedit) {
RED.editor.edit(nn);
} }
} }
}); });
@@ -1182,6 +1190,7 @@ RED.view = (function() {
if (d3.event.button === 1) { if (d3.event.button === 1) {
// Middle Click pan // Middle Click pan
d3.event.preventDefault();
mouse_mode = RED.state.PANNING; mouse_mode = RED.state.PANNING;
mouse_position = [d3.event.pageX,d3.event.pageY] mouse_position = [d3.event.pageX,d3.event.pageY]
scroll_position = [chart.scrollLeft(),chart.scrollTop()]; scroll_position = [chart.scrollLeft(),chart.scrollTop()];
@@ -4156,7 +4165,7 @@ RED.view = (function() {
} }
var width = img.width * scaleFactor; var width = img.width * scaleFactor;
if (width > 20) { if (width > 20) {
scalefactor *= 20/width; scaleFactor *= 20/width;
width = 20; width = 20;
} }
var height = img.height * scaleFactor; var height = img.height * scaleFactor;
@@ -6063,14 +6072,19 @@ RED.view = (function() {
function createNode(type, x, y, z) { function createNode(type, x, y, z) {
const wasDirty = RED.nodes.dirty() const wasDirty = RED.nodes.dirty()
var m = /^subflow:(.+)$/.exec(type); var m = /^subflow:(.+)$/.exec(type);
var activeSubflow = z ? RED.nodes.subflow(z) : null; var activeSubflow = (z || RED.workspaces.active()) ? RED.nodes.subflow(z || RED.workspaces.active()) : null;
if (activeSubflow && m) { if (activeSubflow && m) {
var subflowId = m[1]; var subflowId = m[1];
let err
if (subflowId === activeSubflow.id) { if (subflowId === activeSubflow.id) {
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") })) err = new Error(RED._("notification.errors.cannotAddSubflowToItself"))
} else if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
err = new Error(RED._("notification.errors.cannotAddCircularReference"))
} }
if (RED.nodes.subflowContains(m[1], activeSubflow.id)) { if (err) {
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) err.code = 'NODE_RED'
throw err
} }
} }

View File

@@ -491,6 +491,11 @@ RED.workspaces = (function() {
createWorkspaceTabs(); createWorkspaceTabs();
RED.events.on("sidebar:resize",workspace_tabs.resize); RED.events.on("sidebar:resize",workspace_tabs.resize);
RED.events.on("workspace:clear", () => {
// Reset the index used to generate new flow names
workspaceIndex = 0
})
RED.actions.add("core:show-next-tab",function() { RED.actions.add("core:show-next-tab",function() {
var oldActive = activeWorkspace; var oldActive = activeWorkspace;
workspace_tabs.nextTab(); workspace_tabs.nextTab();
@@ -657,6 +662,9 @@ RED.workspaces = (function() {
RED.events.on("flows:change", (ws) => { RED.events.on("flows:change", (ws) => {
$("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added)); $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
}) })
RED.events.on("subflows:change", (ws) => {
$("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
})
hideWorkspace(); hideWorkspace();
} }

View File

@@ -16,8 +16,20 @@
RED.validators = { RED.validators = {
number: function(blankAllowed,mopt){ number: function(blankAllowed,mopt){
return function(v, opt) { return function(v, opt) {
if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) { if (blankAllowed && (v === '' || v === undefined)) {
return true; return true
}
if (v !== '') {
if (/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(v)) {
return true
}
if (/^\${[^}]+}$/.test(v)) {
// Allow ${ENV_VAR} value
return true
}
}
if (!isNaN(v)) {
return true
} }
if (opt && opt.label) { if (opt && opt.label) {
return RED._("validator.errors.invalid-num-prop", { return RED._("validator.errors.invalid-num-prop", {

View File

@@ -37,7 +37,6 @@ ul.red-ui-sidebar-node-config-list {
} }
.red-ui-palette-node { .red-ui-palette-node {
// overflow: hidden; // overflow: hidden;
cursor: default;
&.selected { &.selected {
border-color: transparent; border-color: transparent;
box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); box-shadow: 0 0 0 2px var(--red-ui-node-selected-color);

View File

@@ -227,34 +227,42 @@
name: {value:""}, name: {value:""},
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) { props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
if (!v || v.length === 0) { return true } if (!v || v.length === 0) { return true }
const errors = []
for (var i=0;i<v.length;i++) { for (var i=0;i<v.length;i++) {
if (/^\${[^}]+}$/.test(v[i].v)) {
// Allow ${ENV_VAR} value
continue
}
if (/msg|flow|global/.test(v[i].vt)) { if (/msg|flow|global/.test(v[i].vt)) {
if (!RED.utils.validatePropertyExpression(v[i].v)) { if (!RED.utils.validatePropertyExpression(v[i].v)) {
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }); errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
} }
} else if (v[i].vt === "jsonata") { } else if (v[i].vt === "jsonata") {
try{ jsonata(v[i].v); } try{ jsonata(v[i].v); }
catch(e){ catch(e){
return RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message }); errors.push(RED._("node-red:inject.errors.invalid-jsonata", { prop: 'msg.'+v[i].p, error: e.message }))
} }
} else if (v[i].vt === "json") { } else if (v[i].vt === "json") {
try{ JSON.parse(v[i].v); } try{ JSON.parse(v[i].v); }
catch(e){ catch(e){
return RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message }); errors.push(RED._("node-red:inject.errors.invalid-json", { prop: 'msg.'+v[i].p, error: e.message }))
} }
} else if (v[i].vt === "num"){ } else if (v[i].vt === "num"){
if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) { if (!/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v[i].v)) {
return RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }); errors.push(RED._("node-red:inject.errors.invalid-prop", { prop: 'msg.'+v[i].p, error: v[i].v }))
} }
} }
} }
if (errors.length > 0) {
return errors
}
return true; return true;
} }
}, },
repeat: { repeat: {
value:"", validate: function(v, opt) { value:"", validate: function(v, opt) {
if ((v === "") || if ((v === "") ||
(RED.validators.number(v) && (RED.validators.number()(v) &&
(v >= 0) && (v <= 2147483))) { (v >= 0) && (v <= 2147483))) {
return true; return true;
} }
@@ -263,7 +271,7 @@
}, },
crontab: {value:""}, crontab: {value:""},
once: {value:false}, once: {value:false},
onceDelay: {value:0.1}, onceDelay: {value:0.1, validate: RED.validators.number(true)},
topic: {value:""}, topic: {value:""},
payload: {value:"", validate: RED.validators.typedInput("payloadType", false) }, payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
payloadType: {value:"date"}, payloadType: {value:"date"},

View File

@@ -378,7 +378,7 @@
return { id: id, label: RED.nodes.workspace(id).label } //flow id + name return { id: id, label: RED.nodes.workspace(id).label } //flow id + name
} else { } else {
const instanceNode = RED.nodes.node(id) const instanceNode = RED.nodes.node(id)
const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name) const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8))?.name || instanceNode.type)
return { id: id, label: pathLabel } return { id: id, label: pathLabel }
} }
}) })

View File

@@ -194,27 +194,46 @@
nodeMap[node.links[i]].new = true; nodeMap[node.links[i]].new = true;
} }
} }
var n;
for (var id in nodeMap) { let editHistories = []
let n;
for (let id in nodeMap) {
if (nodeMap.hasOwnProperty(id)) { if (nodeMap.hasOwnProperty(id)) {
n = RED.nodes.node(id); n = RED.nodes.node(id);
if (n) { if (n) {
editHistories.push({
t:'edit',
node: n,
changes: {
links: [...n.links]
},
changed: n.changed
})
if (nodeMap[id].old && !nodeMap[id].new) { if (nodeMap[id].old && !nodeMap[id].new) {
// Removed id // Removed id
i = n.links.indexOf(node.id); i = n.links.indexOf(node.id);
if (i > -1) { if (i > -1) {
n.links.splice(i,1); n.links.splice(i,1);
n.changed = true
n.dirty = true
} }
} else if (!nodeMap[id].old && nodeMap[id].new) { } else if (!nodeMap[id].old && nodeMap[id].new) {
// Added id // Added id
i = n.links.indexOf(id); i = n.links.indexOf(id);
if (i === -1) { if (i === -1) {
n.links.push(node.id); n.links.push(node.id);
n.changed = true
n.dirty = true
} }
} }
} }
} }
} }
if (editHistories.length > 0) {
return {
history: editHistories
}
}
} }
function onAdd() { function onAdd() {
@@ -254,13 +273,14 @@
onEditPrepare(this,"link out"); onEditPrepare(this,"link out");
}, },
oneditsave: function() { oneditsave: function() {
onEditSave(this); const result = onEditSave(this);
// In case the name has changed, ensure any link call nodes on this // In case the name has changed, ensure any link call nodes on this
// tab are redrawn with the updated name // tab are redrawn with the updated name
var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"}); var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"});
localCallNodes.forEach(function(node) { localCallNodes.forEach(function(node) {
node.dirty = true; node.dirty = true;
}); });
return result
}, },
onadd: onAdd, onadd: onAdd,
oneditresize: resizeNodeList oneditresize: resizeNodeList
@@ -329,7 +349,7 @@
onEditPrepare(this,"link in"); onEditPrepare(this,"link in");
}, },
oneditsave: function() { oneditsave: function() {
onEditSave(this); return onEditSave(this);
}, },
oneditresize: resizeNodeList oneditresize: resizeNodeList
}); });
@@ -373,7 +393,7 @@
}, },
oneditsave: function() { oneditsave: function() {
onEditSave(this); return onEditSave(this);
}, },
onadd: onAdd, onadd: onAdd,
oneditresize: resizeNodeList oneditresize: resizeNodeList

View File

@@ -374,7 +374,7 @@ module.exports = function(RED) {
iniOpt.breakOnSigint = true; iniOpt.breakOnSigint = true;
} }
} }
node.script = vm.createScript(functionText, createVMOpt(node, "")); node.script = new vm.Script(functionText, createVMOpt(node, ""));
if (node.fin && (node.fin !== "")) { if (node.fin && (node.fin !== "")) {
var finText = `(function () { var finText = `(function () {
var node = { var node = {
@@ -438,10 +438,9 @@ module.exports = function(RED) {
//store the error in msg to be used in flows //store the error in msg to be used in flows
msg.error = err; msg.error = err;
var line = 0;
var errorMessage;
if (stack.length > 0) { if (stack.length > 0) {
let line = 0;
let errorMessage;
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) { while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
line++; line++;
} }
@@ -455,11 +454,13 @@ module.exports = function(RED) {
errorMessage += " (line "+lineno+", col "+cha+")"; errorMessage += " (line "+lineno+", col "+cha+")";
} }
} }
if (errorMessage) {
err.message = errorMessage
}
} }
if (!errorMessage) { // Pass the whole error object so any additional properties
errorMessage = err.toString(); // (such as cause) are preserved
} done(err);
done(errorMessage);
} }
else if (typeof err === "string") { else if (typeof err === "string") {
done(err); done(err);

View File

@@ -20,6 +20,7 @@ module.exports = function(RED) {
var exec = require('child_process').exec; var exec = require('child_process').exec;
var fs = require('fs'); var fs = require('fs');
var isUtf8 = require('is-utf8'); var isUtf8 = require('is-utf8');
const isWindows = process.platform === 'win32'
function ExecNode(n) { function ExecNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
@@ -85,9 +86,12 @@ module.exports = function(RED) {
} }
}); });
var cmd = arg.shift(); var cmd = arg.shift();
// Since 18.20.2/20.12.2, it is invalid to call spawn on Windows with a .bat/.cmd file
// without using shell: true.
const opts = isWindows ? { ...node.spawnOpt, shell: true } : node.spawnOpt
/* istanbul ignore else */ /* istanbul ignore else */
node.debug(cmd+" ["+arg+"]"); node.debug(cmd+" ["+arg+"]");
child = spawn(cmd,arg,node.spawnOpt); child = spawn(cmd,arg,opts);
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid}); node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
var unknownCommand = (child.pid === undefined); var unknownCommand = (child.pid === undefined);
if (node.timer !== 0) { if (node.timer !== 0) {

View File

@@ -103,7 +103,7 @@
<h4>Automatic mode</h4> <h4>Automatic mode</h4>
<p>Automatic mode uses the <code>parts</code> property of incoming messages to <p>Automatic mode uses the <code>parts</code> property of incoming messages to
determine how the sequence should be joined. This allows it to automatically determine how the sequence should be joined. This allows it to automatically
reverse the action of a <b>split</b> node. reverse the action of a <b>split</b> node.</p>
<h4>Manual mode</h4> <h4>Manual mode</h4>
<p>When configured to join in manual mode, the node is able to join sequences <p>When configured to join in manual mode, the node is able to join sequences

View File

@@ -1,3 +1,3 @@
<script type="text/html" data-help-name="global-config"> <script type="text/html" data-help-name="global-config">
<p>大域的なフローの設定を保持するノード大域的な環境変数の定義を含みます</p> <p>大域的なフローの設定を保持するノード大域的な環境変数の定義を含みます</p>
</script>p </script>

View File

@@ -23,7 +23,7 @@
<dt class="optional">template <span class="property-type">string</span></dt> <dt class="optional">template <span class="property-type">string</span></dt>
<dd><code>msg.payload</code>msg</dd> <dd><code>msg.payload</code>msg</dd>
</dl> </dl>
<h3>Outputs</h3> <h3>输出</h3>
<dl class="message-properties"> <dl class="message-properties">
<dt>msg <span class="property-type">object</span></dt> <dt>msg <span class="property-type">object</span></dt>
<dd>由来自传入msg的属性来填充已配置的模板后输出的带有属性的msg</dd> <dd>由来自传入msg的属性来填充已配置的模板后输出的带有属性的msg</dd>
@@ -32,7 +32,7 @@
<p>默认情况下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式如有需要也可以切换其他格式</p> <p>默认情况下使用<i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>格式如有需要也可以切换其他格式</p>
<p>例如: <p>例如:
<pre>Hello {{payload.name}}. Today is {{date}}</pre> <pre>Hello {{payload.name}}. Today is {{date}}</pre>
<p>receives a message containing: <p>接收一条消息其中包含:
<pre>{ <pre>{
date: "Monday", date: "Monday",
payload: { payload: {

View File

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

View File

@@ -273,7 +273,7 @@ async function installModule(moduleDetails) {
let extraArgs = triggerPayload.args || []; let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installSpec] let args = ['install', ...extraArgs, installSpec]
log.trace(NPM_COMMAND + JSON.stringify(args)); log.trace(NPM_COMMAND + JSON.stringify(args));
return exec.run(NPM_COMMAND, args, { cwd: installDir },true) return exec.run(NPM_COMMAND, args, { cwd: installDir, shell: true },true)
} else { } else {
log.trace("skipping npm install"); log.trace("skipping npm install");
} }

View File

@@ -25,12 +25,15 @@ const registryUtil = require("./util");
const library = require("./library"); const library = require("./library");
const {exec,log,events,hooks} = require("@node-red/util"); const {exec,log,events,hooks} = require("@node-red/util");
const child_process = require('child_process'); const child_process = require('child_process');
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
let installerEnabled = false;
const isWindows = process.platform === 'win32'
const npmCommand = isWindows ? 'npm.cmd' : 'npm';
let installerEnabled = false;
let settings; let settings;
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/; const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; const slashRe = isWindows ? /\\|[/]/ : /[/]/;
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//; const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/; const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
@@ -225,7 +228,7 @@ async function installModule(module,version,url) {
let extraArgs = triggerPayload.args || []; let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installName] let args = ['install', ...extraArgs, installName]
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
return exec.run(npmCommand,args,{ cwd: installDir}, true) return exec.run(npmCommand,args,{ cwd: installDir, shell: true }, true)
} else { } else {
log.trace("skipping npm install"); log.trace("skipping npm install");
} }
@@ -260,7 +263,7 @@ async function installModule(module,version,url) {
log.warn("------------------------------------------"); log.warn("------------------------------------------");
e = new Error(log._("server.install.install-failed")+": "+err.toString()); e = new Error(log._("server.install.install-failed")+": "+err.toString());
if (err.hook === "postInstall") { if (err.hook === "postInstall") {
return exec.run(npmCommand,["remove",module],{ cwd: installDir}, false).finally(() => { return exec.run(npmCommand,["remove",module],{ cwd: installDir, shell: true }, false).finally(() => {
throw e; throw e;
}) })
} }
@@ -356,7 +359,7 @@ async function getModuleVersionFromNPM(module, version) {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
child_process.execFile(npmCommand,['info','--json',installName],function(err,stdout,stderr) { child_process.execFile(npmCommand,['info','--json',installName],{ shell: true },function(err,stdout,stderr) {
try { try {
if (!stdout) { if (!stdout) {
log.warn(log._("server.install.install-failed-not-found",{name:module})); log.warn(log._("server.install.install-failed-not-found",{name:module}));
@@ -511,7 +514,7 @@ function uninstallModule(module) {
let extraArgs = triggerPayload.args || []; let extraArgs = triggerPayload.args || [];
let args = ['remove', ...extraArgs, module] let args = ['remove', ...extraArgs, module]
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
return exec.run(npmCommand,args,{ cwd: installDir}, true) return exec.run(npmCommand,args,{ cwd: installDir, shell: true }, true)
} else { } else {
log.trace("skipping npm uninstall"); log.trace("skipping npm uninstall");
} }
@@ -578,7 +581,7 @@ async function checkPrereq() {
installerEnabled = false; installerEnabled = false;
} else { } else {
return new Promise(resolve => { return new Promise(resolve => {
child_process.execFile(npmCommand,['-v'],function(err,stdout) { child_process.execFile(npmCommand,['-v'],{ shell: true },function(err,stdout) {
if (err) { if (err) {
log.info(log._("server.palette-editor.npm-not-found")); log.info(log._("server.palette-editor.npm-not-found"));
installerEnabled = false; installerEnabled = false;

View File

@@ -36,7 +36,7 @@ async function getFlowsFromPath(path) {
promises.push(getFlowsFromPath(fullPath)); promises.push(getFlowsFromPath(fullPath));
} else if (/\.json$/.test(file)){ } else if (/\.json$/.test(file)){
validFiles.push(file); validFiles.push(file);
promises.push(Promise.resolve(file.split(".")[0])) promises.push(Promise.resolve(file.replace(/\.json$/, '')))
} }
}) })
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,11 +16,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "3.1.4", "@node-red/util": "3.1.9",
"clone": "2.1.2", "clone": "2.1.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"semver": "7.5.4", "semver": "7.5.4",
"tar": "6.1.13", "tar": "6.2.1",
"uglify-js": "3.17.4" "uglify-js": "3.17.4"
} }
} }

View File

@@ -485,7 +485,7 @@ class Flow {
} }
if (!key.startsWith("$parent.")) { if (!key.startsWith("$parent.")) {
if (this._env.hasOwnProperty(key)) { if (this._env.hasOwnProperty(key)) {
return (Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key] return (this._env[key] && Object.hasOwn(this._env[key], 'value') && this._env[key].__clone__) ? clone(this._env[key].value) : this._env[key]
} }
} else { } else {
key = key.substring(8); key = key.substring(8);
@@ -678,6 +678,9 @@ class Flow {
if (logMessage.hasOwnProperty('stack')) { if (logMessage.hasOwnProperty('stack')) {
errorMessage.error.stack = logMessage.stack; errorMessage.error.stack = logMessage.stack;
} }
if (logMessage.hasOwnProperty('cause')) {
errorMessage.error.cause = logMessage.cause;
}
targetCatchNode.receive(errorMessage); targetCatchNode.receive(errorMessage);
handled = true; handled = true;
}); });

View File

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

View File

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

View File

@@ -106,14 +106,22 @@ async function evaluateEnvProperties(flow, env, credentials) {
result = { value: result, __clone__: true} result = { value: result, __clone__: true}
} }
evaluatedEnv[name] = result evaluatedEnv[name] = result
} else {
evaluatedEnv[name] = undefined
flow.error(`Error evaluating env property '${name}': ${err.toString()}`)
} }
resolve() resolve()
}); });
})) }))
} else { } else {
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); try {
if (typeof value === 'object') { value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
value = { value: value, __clone__: true} if (typeof value === 'object') {
value = { value: value, __clone__: true}
}
} catch (err) {
value = undefined
flow.error(`Error evaluating env property '${name}': ${err.toString()}`)
} }
} }
evaluatedEnv[name] = value evaluatedEnv[name] = value

View File

@@ -384,7 +384,8 @@ var api = module.exports = {
} }
} }
} else if (nodeType === "global-config") { } else if (nodeType === "global-config") {
const existingCredentialKeys = Object.keys(savedCredentials?.map || []) savedCredentials.map = savedCredentials.map || {}
const existingCredentialKeys = Object.keys(savedCredentials.map)
const newCredentialKeys = Object.keys(newCreds?.map || []) const newCredentialKeys = Object.keys(newCreds?.map || [])
existingCredentialKeys.forEach(key => { existingCredentialKeys.forEach(key => {
if (!newCreds.map?.[key]) { if (!newCreds.map?.[key]) {
@@ -396,7 +397,7 @@ var api = module.exports = {
}) })
newCredentialKeys.forEach(key => { newCredentialKeys.forEach(key => {
if (!/^has_/.test(key)) { if (!/^has_/.test(key)) {
if (!savedCredentials.map?.[key] || newCreds.map[key] !== '__PWRD__') { if (!savedCredentials.map[key] || newCreds.map[key] !== '__PWRD__') {
// This key either doesn't exist in current saved, or the // This key either doesn't exist in current saved, or the
// value has been changed // value has been changed
savedCredentials.map[key] = newCreds.map[key] savedCredentials.map[key] = newCreds.map[key]

View File

@@ -77,7 +77,7 @@ var storageModuleInterface = {
flows: flows, flows: flows,
credentials: creds credentials: creds
}; };
result.rev = crypto.createHash('md5').update(JSON.stringify(result.flows)).digest("hex"); result.rev = crypto.createHash('sha256').update(JSON.stringify(result.flows)).digest("hex");
return result; return result;
}) })
}); });
@@ -95,7 +95,7 @@ var storageModuleInterface = {
return credentialSavePromise.then(function() { return credentialSavePromise.then(function() {
return storageModule.saveFlows(flows, user).then(function() { return storageModule.saveFlows(flows, user).then(function() {
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex"); return crypto.createHash('sha256').update(JSON.stringify(config.flows)).digest("hex");
}) })
}); });
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "3.1.4", "version": "3.1.9",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@@ -16,11 +16,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "3.1.4", "@node-red/registry": "3.1.9",
"@node-red/util": "3.1.4", "@node-red/util": "3.1.9",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.18.2", "express": "4.19.2",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"json-stringify-safe": "5.0.1" "json-stringify-safe": "5.0.1"
} }

View File

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

View File

@@ -26,8 +26,8 @@ var server = null;
var apiEnabled = false; var apiEnabled = false;
const NODE_MAJOR_VERSION = process.versions.node.split('.')[0]; const NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
if (NODE_MAJOR_VERSION > 14) { if (NODE_MAJOR_VERSION >= 16) {
const dns = require('node:dns'); const dns = require('dns');
dns.setDefaultResultOrder('ipv4first'); dns.setDefaultResultOrder('ipv4first');
} }

View File

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

View File

@@ -13,7 +13,7 @@
// 4. Edit your settings file to set the theme: // 4. Edit your settings file to set the theme:
// editorTheme: { // editorTheme: {
// page: { // page: {
// css: "/path/to/file/generated/by/this/script" // css: '/path/to/file/generated/by/this/script'
// } // }
// } // }
// //
@@ -22,110 +22,69 @@
const os = require("os"); const os = require('os');
const nopt = require("nopt"); const nopt = require('nopt');
const path = require("path"); const path = require('path');
const fs = require("fs-extra"); const fs = require('fs-extra');
const sass = require("sass"); const sass = require('sass');
const knownOpts = { const knownOpts = {
"help": Boolean, 'help': Boolean,
"long": Boolean, 'long': Boolean,
"in": [path], 'in': [path],
"out": [path] 'out': [path]
}; };
const shortHands = { const shortHands = {
"?":["--help"] '?':['--help']
}; };
nopt.invalidHandler = function(k,v,t) {} nopt.invalidHandler = function(k,v,t) {}
const parsedArgs = nopt(knownOpts,shortHands,process.argv,2) const parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
if (parsedArgs.help) { if (parsedArgs.help) {
console.log("Usage: build-custom-theme [-?] [--in FILE] [--out FILE]"); showUsageAndExit(0)
console.log("");
console.log("Options:");
console.log(" --in FILE Custom colors sass file");
console.log(" --out FILE Where you write the result");
console.log(" --long Do not compress the output");
console.log(" -?, --help Show this help");
console.log("");
process.exit();
} }
if (!parsedArgs.in) {
const ruleRegex = /(\$.*?) *: *(\S[\S\s]*?);/g; console.warn('Missing argument: in')
var match; showUsageAndExit(1)
const customColors = {};
if (parsedArgs.in && fs.existsSync(parsedArgs.in)) {
let customColorsFile = fs.readFileSync(parsedArgs.in,"utf-8");
while((match = ruleRegex.exec(customColorsFile)) !== null) {
customColors[match[1]] = match[2];
}
} }
// Load base colours
let colorsFile = fs.readFileSync(path.join(__dirname,"../packages/node_modules/@node-red/editor-client/src/sass/colors.scss"),"utf-8")
let updatedColors = [];
while((match = ruleRegex.exec(colorsFile)) !== null) {
updatedColors.push(match[1]+": "+(customColors[match[1]]||match[2])+";")
}
(async function() { (async function() {
const tmpDir = os.tmpdir(); const tmpDir = os.tmpdir();
const workingDir = await fs.mkdtemp(`${tmpDir}${path.sep}`); const workingDir = await fs.mkdtemp(`${tmpDir}${path.sep}`);
await fs.copy(path.join(__dirname,"../packages/node_modules/@node-red/editor-client/src/sass/"),workingDir)
await fs.writeFile(path.join(workingDir,"colors.scss"),updatedColors.join("\n"))
const result = sass.renderSync({ await fs.copy(path.join(__dirname, '../packages/node_modules/@node-red/editor-client/src/sass/'), workingDir);
outputStyle: "expanded", await fs.copyFile(parsedArgs.in, path.join(workingDir,'colors.scss'));
file: path.join(workingDir,"style-custom-theme.scss"),
});
const css = result.css.toString() const output = sass.compile(
const lines = css.split("\n"); path.join(workingDir, 'style-custom-theme.scss'),
const colorCSS = [] {style: parsedArgs.long === true ? 'expanded' : 'compressed'}
const nonColorCSS = []; );
let inKeyFrameBlock = false; const nrPkg = require('../package.json');
lines.forEach(l => {
if (inKeyFrameBlock) {
nonColorCSS.push(l);
if (/^}/.test(l)) {
inKeyFrameBlock = false;
}
} else if (/^@keyframes/.test(l)) {
nonColorCSS.push(l);
inKeyFrameBlock = true;
} else if (!/^ /.test(l)) {
colorCSS.push(l);
nonColorCSS.push(l);
} else if (/color|border|background|fill|stroke|outline|box-shadow/.test(l)) {
colorCSS.push(l);
} else {
nonColorCSS.push(l);
}
});
const nrPkg = require("../package.json");
const now = new Date().toISOString(); const now = new Date().toISOString();
const header = `/*\n* Theme generated with Node-RED ${nrPkg.version} on ${now}\n*/`;
const header = `/*
* Theme generated with Node-RED ${nrPkg.version} on ${now}
*/`;
var output = sass.renderSync({outputStyle: parsedArgs.long?"expanded":"compressed",data:colorCSS.join("\n")});
if (parsedArgs.out) { if (parsedArgs.out) {
await fs.writeFile(parsedArgs.out, header+'\n'+output.css);
await fs.writeFile(parsedArgs.out,header+"\n"+output.css);
} else { } else {
console.log(header); console.log(header);
console.log(output.css.toString()); console.log(output.css.toString());
} }
await fs.remove(workingDir); await fs.remove(workingDir);
})() })()
function showUsageAndExit (exitCode) {
console.log('');
console.log('Usage: build-custom-theme [-?] [--in FILE] [--out FILE]');
console.log('');
console.log('Options:');
console.log(' --in FILE Custom colors sass file');
console.log(' --out FILE Where you write the result');
console.log(' --long Do not compress the output');
console.log(' -?, --help Show this help');
console.log('');
process.exit(exitCode);
}

View File

@@ -390,7 +390,8 @@ describe('function node', function() {
msg.should.have.property('level', helper.log().ERROR); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', 'n1'); msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function'); msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)'); msg.should.have.property('msg')
msg.msg.message.should.equal('ReferenceError: retunr is not defined (line 2, col 1)');
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);
@@ -659,7 +660,8 @@ describe('function node', function() {
msg.should.have.property('level', helper.log().ERROR); msg.should.have.property('level', helper.log().ERROR);
msg.should.have.property('id', name); msg.should.have.property('id', name);
msg.should.have.property('type', 'function'); msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'Error: Callback must be a function'); msg.should.have.property('msg')
msg.msg.message.should.equal('Callback must be a function');
done(); done();
} }
catch (e) { catch (e) {

View File

@@ -60,6 +60,7 @@ describe('HTTP Request Node', function() {
function startServer(done) { function startServer(done) {
testPort += 1; testPort += 1;
testServer = stoppable(http.createServer(testApp)); testServer = stoppable(http.createServer(testApp));
const promises = []
testServer.listen(testPort,function(err) { testServer.listen(testPort,function(err) {
testSslPort += 1; testSslPort += 1;
console.log("ssl port", testSslPort); console.log("ssl port", testSslPort);
@@ -81,13 +82,17 @@ describe('HTTP Request Node', function() {
*/ */
}; };
testSslServer = stoppable(https.createServer(sslOptions,testApp)); testSslServer = stoppable(https.createServer(sslOptions,testApp));
testSslServer.listen(testSslPort, function(err){ console.log('> start testSslServer')
if (err) { promises.push(new Promise((resolve, reject) => {
console.log(err); testSslServer.listen(testSslPort, function(err){
} else { console.log(' done testSslServer')
console.log("started testSslServer"); if (err) {
} reject(err)
}); } else {
resolve()
}
});
}))
testSslClientPort += 1; testSslClientPort += 1;
var sslClientOptions = { var sslClientOptions = {
@@ -97,10 +102,17 @@ describe('HTTP Request Node', function() {
requestCert: true requestCert: true
}; };
testSslClientServer = stoppable(https.createServer(sslClientOptions, testApp)); testSslClientServer = stoppable(https.createServer(sslClientOptions, testApp));
testSslClientServer.listen(testSslClientPort, function(err){ console.log('> start testSslClientServer')
console.log("ssl-client", err) promises.push(new Promise((resolve, reject) => {
}); testSslClientServer.listen(testSslClientPort, function(err){
console.log(' done testSslClientServer')
if (err) {
reject(err)
} else {
resolve()
}
});
}))
testProxyPort += 1; testProxyPort += 1;
testProxyServer = stoppable(httpProxy(http.createServer())) testProxyServer = stoppable(httpProxy(http.createServer()))
@@ -109,7 +121,17 @@ describe('HTTP Request Node', function() {
res.setHeader("x-testproxy-header", "foobar") res.setHeader("x-testproxy-header", "foobar")
} }
}) })
testProxyServer.listen(testProxyPort) console.log('> testProxyServer')
promises.push(new Promise((resolve, reject) => {
testProxyServer.listen(testProxyPort, function(err) {
console.log(' done testProxyServer')
if (err) {
reject(err)
} else {
resolve()
}
})
}))
testProxyAuthPort += 1 testProxyAuthPort += 1
testProxyServerAuth = stoppable(httpProxy(http.createServer())) testProxyServerAuth = stoppable(httpProxy(http.createServer()))
@@ -131,9 +153,19 @@ describe('HTTP Request Node', function() {
res.setHeader("x-testproxy-header", "foobar") res.setHeader("x-testproxy-header", "foobar")
} }
}) })
testProxyServerAuth.listen(testProxyAuthPort) console.log('> testProxyServerAuth')
promises.push(new Promise((resolve, reject) => {
testProxyServerAuth.listen(testProxyAuthPort, function(err) {
console.log(' done testProxyServerAuth')
if (err) {
reject(err)
} else {
resolve()
}
})
}))
done(err); Promise.all(promises).then(() => { done() }).catch(done)
}); });
} }
@@ -429,7 +461,11 @@ describe('HTTP Request Node', function() {
if (err) { if (err) {
done(err); done(err);
} }
helper.startServer(done); console.log('> helper.startServer')
helper.startServer(function(err) {
console.log('> helper started')
done(err)
});
}); });
}); });

View File

@@ -33,16 +33,15 @@ describe("library api", function() {
should.not.exist(library.getExampleFlowPath('foo','bar')); should.not.exist(library.getExampleFlowPath('foo','bar'));
}); });
it('returns a valid example path', function(done) { it('returns valid example paths', function(done) {
library.init(); library.init();
library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() { library.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
try { try {
var flows = library.getExampleFlows(); var flows = library.getExampleFlows();
flows.should.deepEqual({"test-module":{"f":["one"]}}); flows.should.deepEqual({"test-module":{"f":["1.2.3","one"]}});
var examplePath = library.getExampleFlowPath('test-module','one'); var examplePath = library.getExampleFlowPath('test-module','one');
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json')) examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'));
library.removeExamplesDir('test-module'); library.removeExamplesDir('test-module');
@@ -57,6 +56,5 @@ describe("library api", function() {
done(err); done(err);
} }
}); });
});
})
}); });