mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
137 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
80716cc45b | ||
|
300ec635d5 | ||
|
eea7abb30b | ||
|
7512fdca49 | ||
|
fa26111339 | ||
|
27eb992d01 | ||
|
9e227a0769 | ||
|
1da0379b51 | ||
|
30d992f889 | ||
|
e3526cbe1c | ||
|
aa2b2b0fb1 | ||
|
50134307fe | ||
|
df0eb1a7e4 | ||
|
b22f2e704d | ||
|
c06ebb7e33 | ||
|
7e34a253b4 | ||
|
7322cd0a06 | ||
|
3fe4c12468 | ||
|
2a4fb7123d | ||
|
38a77d2b78 | ||
|
dc239db256 | ||
|
4ba3c937a8 | ||
|
02893d3e78 | ||
|
5124bc6bf8 | ||
|
1048b16f3c | ||
|
bbbbb1b1e0 | ||
|
14b452c996 | ||
|
bb91a08939 | ||
|
526b3fda91 | ||
|
d70b7ea924 | ||
|
1d342a778d | ||
|
476016cbcc | ||
|
61b12f6bbe | ||
|
d71b22412b | ||
|
e408c6b376 | ||
|
bf30c24e8e | ||
|
6c14ed0ef5 | ||
|
6d41ecdae0 | ||
|
3f89bc2733 | ||
|
87a25df162 | ||
|
341f43610a | ||
|
67cdf3ef96 | ||
|
cd98f448e9 | ||
|
fac79fd068 | ||
|
da97c5d558 | ||
|
ae7b9fe62e | ||
|
e52c2911da | ||
|
07a29ff779 | ||
|
1b4a8ebe83 | ||
|
abf2eacf18 | ||
|
f808f4e2e8 | ||
|
ca33d6b799 | ||
|
940740f15d | ||
|
51208fcd0c | ||
|
707152d82f | ||
|
5538f6dd8a | ||
|
d601e2caa4 | ||
|
46fdf56c79 | ||
|
f55ee6e665 | ||
|
03648dc7e8 | ||
|
66a667fe58 | ||
|
1bb3a0eca5 | ||
|
08927dfb55 | ||
|
b27483de9c | ||
|
211d420fb2 | ||
|
b8ca4665c1 | ||
|
960af87fb0 | ||
|
de7339ae97 | ||
|
0995af62b6 | ||
|
c2e03a40b4 | ||
|
c855050bcf | ||
|
29ed5b2792 | ||
|
e39216e65a | ||
|
7ac7f9b4c8 | ||
|
4709eb9d49 | ||
|
c13b8266dd | ||
|
bd58431603 | ||
|
9a3cb0b2b5 | ||
|
6beae5a806 | ||
|
a0636632a1 | ||
|
5dfa47ab6c | ||
|
ade4679e8c | ||
|
410b938442 | ||
|
19dcc3a683 | ||
|
20d067c1ea | ||
|
9526566799 | ||
|
0b9dd82c91 | ||
|
19213434f9 | ||
|
014691346a | ||
|
6738b95c29 | ||
|
6a8230ec1e | ||
|
5679d264b6 | ||
|
37265cf4ef | ||
|
8a63275989 | ||
|
7fc64a84e8 | ||
|
02f7cdd5aa | ||
|
d7dcceef60 | ||
|
ae5e1570ae | ||
|
3ca045394a | ||
|
179032cd4d | ||
|
6a6f0d04d6 | ||
|
add4d9758c | ||
|
a0d3ea62b2 | ||
|
7447e88a50 | ||
|
a193b79d3d | ||
|
da380f7464 | ||
|
269cf02c0b | ||
|
fb50e2772a | ||
|
058c97138a | ||
|
828ae29aed | ||
|
6a0f45140c | ||
|
50a267528d | ||
|
220786be60 | ||
|
fa78bb3d78 | ||
|
9a32ebd0c0 | ||
|
4643f5e8cc | ||
|
7de0984d6d | ||
|
635334f096 | ||
|
f0d0990b5a | ||
|
43b3589451 | ||
|
016a19ba7c | ||
|
aeb79bce2a | ||
|
0ab9b9a5fd | ||
|
56e58521bd | ||
|
b10ef4c98c | ||
|
3ff038fb98 | ||
|
adb498af24 | ||
|
fc67a2efc2 | ||
|
55771c7241 | ||
|
109fa5f04e | ||
|
28907082f1 | ||
|
f83174c40a | ||
|
ec062d008f | ||
|
a587655a5a | ||
|
3e6f0acf79 | ||
|
7f93d943d7 | ||
|
12543d2c2a |
90
CHANGELOG.md
90
CHANGELOG.md
@@ -1,3 +1,93 @@
|
||||
#### 3.1.15: Maintenance Release
|
||||
|
||||
- Bump express 3.1.x (#4988) @hardillb
|
||||
|
||||
#### 3.1.14: Maintenance Release
|
||||
|
||||
- Update mermaid version
|
||||
|
||||
#### 3.1.13: Maintenance Release
|
||||
|
||||
- Update cookie/DOMPurify dependencies
|
||||
|
||||
#### 3.1.12: Maintenance Release
|
||||
|
||||
- Update express/body-parser dependencies
|
||||
|
||||
#### 3.1.11: Maintenance Release
|
||||
|
||||
- Add/Update German Translations for delay node (#4762) @dceejay
|
||||
- Update ws dependency
|
||||
|
||||
#### 3.1.10: Maintenance Release
|
||||
|
||||
- Include rewired nodes when calculating Modified Flows stop list (#4754) @knolleary
|
||||
- Fix clone of group env var properties (#4753) @knolleary
|
||||
- Fix losing links when importing a copy of links into a subflow (#4750) @GogoVega
|
||||
- Ensure all CSS variables are in the output file (#3743) @bonanitech
|
||||
- Fix the Sidebar Config is not refreshed after a deploy (#4734) @GogoVega
|
||||
- Fix checkboxes are not updated when calling `typedInput("value", "")` (#4729) @GogoVega
|
||||
- Fix panning with middle mouse button on windows 10/11 (#4716) @corentin-sodebo-voile
|
||||
- Add Japanese translation for sidebar tooltip (#4727) @kazuhitoyokoi
|
||||
- Translate the number of items selected in the options list (#4730) @GogoVega
|
||||
- Fix a checkbox should return a Boolean value and not the string `on` (#4715) @GogoVega
|
||||
- Deleting a grouped node should update the group (#4714) @GogoVega
|
||||
- Change the Config Node cursor to `pointer` (#4711) @GogoVega
|
||||
- Add missing tooltips to Sidebar (#4713) @GogoVega
|
||||
- Allow nodes to return additional history entries in onEditSave (#4710) @knolleary
|
||||
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
|
||||
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
|
||||
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
|
||||
- Fix undo of subflow env property edits (#4667) @knolleary
|
||||
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
|
||||
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
|
||||
|
||||
#### 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
|
||||
|
||||
Editor
|
||||
|
26
package.json
26
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "https://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,17 +32,17 @@
|
||||
"async-mutex": "0.4.0",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.2",
|
||||
"body-parser": "1.20.3",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"clone": "2.1.2",
|
||||
"content-type": "1.0.5",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-parser": "1.4.6",
|
||||
"cookie": "0.7.2",
|
||||
"cookie-parser": "1.4.7",
|
||||
"cors": "2.8.5",
|
||||
"cronosjs": "1.7.1",
|
||||
"denque": "2.1.0",
|
||||
"express": "4.18.2",
|
||||
"express-session": "1.17.3",
|
||||
"express": "4.21.2",
|
||||
"express-session": "1.18.1",
|
||||
"form-data": "4.0.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"got": "12.6.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": "1.8.7",
|
||||
"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.2",
|
||||
"node-red-admin": "^3.1.3",
|
||||
"node-watch": "0.7.4",
|
||||
"nopt": "5.0.0",
|
||||
"oauth2orize": "1.11.1",
|
||||
@@ -74,18 +74,18 @@
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"raw-body": "2.5.2",
|
||||
"semver": "7.5.4",
|
||||
"tar": "6.1.13",
|
||||
"tar": "6.2.1",
|
||||
"tough-cookie": "4.1.3",
|
||||
"uglify-js": "3.17.4",
|
||||
"uuid": "9.0.0",
|
||||
"ws": "7.5.6",
|
||||
"ws": "7.5.10",
|
||||
"xml2js": "0.6.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dompurify": "2.4.1",
|
||||
"dompurify": "2.5.7",
|
||||
"grunt": "1.6.1",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-cli": "~1.4.3",
|
||||
@@ -109,10 +109,10 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
||||
"marked": "4.3.0",
|
||||
"mermaid": "^10.4.0",
|
||||
"mermaid": "11.3.0",
|
||||
"minami": "1.2.3",
|
||||
"mocha": "9.2.2",
|
||||
"node-red-node-test-helper": "^0.3.2",
|
||||
"node-red-node-test-helper": "^0.3.3",
|
||||
"nodemon": "2.0.20",
|
||||
"proxy": "^1.0.2",
|
||||
"sass": "1.62.1",
|
||||
|
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var apiUtils = require("../util");
|
||||
var runtimeAPI;
|
||||
var settings;
|
||||
var theme = require("../editor/theme");
|
||||
|
@@ -205,9 +205,10 @@ function genericStrategy(adminApp,strategy) {
|
||||
passport.use(new strategy.strategy(options, verify));
|
||||
|
||||
adminApp.get('/auth/strategy',
|
||||
passport.authenticate(strategy.name, {session:false,
|
||||
passport.authenticate(strategy.name, {
|
||||
session:false,
|
||||
failureMessage: true,
|
||||
failureRedirect: settings.httpAdminRoot
|
||||
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
|
||||
}),
|
||||
completeGenerateStrategyAuth,
|
||||
handleStrategyError
|
||||
@@ -221,7 +222,7 @@ function genericStrategy(adminApp,strategy) {
|
||||
passport.authenticate(strategy.name, {
|
||||
session:false,
|
||||
failureMessage: true,
|
||||
failureRedirect: settings.httpAdminRoot
|
||||
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
|
||||
}),
|
||||
completeGenerateStrategyAuth,
|
||||
handleStrategyError
|
||||
|
@@ -18,7 +18,6 @@ var BearerStrategy = require('passport-http-bearer').Strategy;
|
||||
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
|
||||
|
||||
var passport = require("passport");
|
||||
var crypto = require("crypto");
|
||||
var util = require("util");
|
||||
|
||||
var Tokens = require("./tokens");
|
||||
|
@@ -14,11 +14,9 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var express = require("express");
|
||||
var path = require('path');
|
||||
|
||||
var comms = require("./comms");
|
||||
var library = require("./library");
|
||||
var info = require("./settings");
|
||||
|
||||
var auth = require("../auth");
|
||||
|
@@ -15,8 +15,6 @@
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
var fs = require('fs');
|
||||
var fspath = require('path');
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
|
@@ -13,9 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* 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
|
||||
|
||||
|
@@ -15,7 +15,6 @@
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
var express = require("express");
|
||||
var runtimeAPI;
|
||||
var settings;
|
||||
|
||||
|
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var express = require("express");
|
||||
var util = require("util");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
@@ -99,7 +99,7 @@ module.exports = {
|
||||
// settings.instanceId is set asynchronously to the editor-api
|
||||
// being initiaised. So we defer calculating the cacheBuster hash
|
||||
// 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;
|
||||
|
@@ -24,11 +24,8 @@
|
||||
* @namespace @node-red/editor-api
|
||||
*/
|
||||
|
||||
var express = require("express");
|
||||
var bodyParser = require("body-parser");
|
||||
var util = require('util');
|
||||
var passport = require('passport');
|
||||
var cors = require('cors');
|
||||
|
||||
var auth = require("./auth");
|
||||
var apiUtil = require("./util");
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-api",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,14 +16,14 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "3.1.4",
|
||||
"@node-red/editor-client": "3.1.4",
|
||||
"@node-red/util": "3.1.15",
|
||||
"@node-red/editor-client": "3.1.15",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.2",
|
||||
"body-parser": "1.20.3",
|
||||
"clone": "2.1.2",
|
||||
"cors": "2.8.5",
|
||||
"express-session": "1.17.3",
|
||||
"express": "4.18.2",
|
||||
"express-session": "1.18.1",
|
||||
"express": "4.21.2",
|
||||
"memorystore": "1.6.7",
|
||||
"mime": "3.0.0",
|
||||
"multer": "1.4.5-lts.1",
|
||||
@@ -32,7 +32,7 @@
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"passport": "0.6.0",
|
||||
"ws": "7.5.6"
|
||||
"ws": "7.5.10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "5.1.0"
|
||||
|
@@ -719,6 +719,7 @@
|
||||
"nodeHelp": "Node Help",
|
||||
"showHelp": "Show help",
|
||||
"showInOutline": "Show in outline",
|
||||
"hideTopics": "Hide topics",
|
||||
"showTopics": "Show topics",
|
||||
"noHelp": "No help topic selected",
|
||||
"changeLog": "Change Log"
|
||||
@@ -914,6 +915,8 @@
|
||||
}
|
||||
},
|
||||
"typedInput": {
|
||||
"selected": "__count__ selected",
|
||||
"selected_plural": "__count__ selected",
|
||||
"type": {
|
||||
"str": "string",
|
||||
"num": "number",
|
||||
|
@@ -719,6 +719,7 @@
|
||||
"nodeHelp": "Aide sur les noeuds",
|
||||
"showHelp": "Afficher l'aide",
|
||||
"showInOutline": "Afficher dans les grandes lignes",
|
||||
"hideTopics": "Masquer les sujets",
|
||||
"showTopics": "Afficher les sujets",
|
||||
"noHelp": "Aucune rubrique d'aide sélectionnée",
|
||||
"changeLog": "Journal des modifications"
|
||||
@@ -914,6 +915,8 @@
|
||||
}
|
||||
},
|
||||
"typedInput": {
|
||||
"selected": "__count__ sélectionnée",
|
||||
"selected_plural": "__count__ sélectionnées",
|
||||
"type": {
|
||||
"str": "chaîne de caractères",
|
||||
"num": "nombre",
|
||||
|
@@ -303,7 +303,8 @@
|
||||
"missingType": "不正なフロー - __index__ 番目の要素に'type'プロパティがありません"
|
||||
},
|
||||
"conflictNotification1": "読み込もうとしているノードのいくつかは、既にワークスペース内に存在しています。",
|
||||
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。"
|
||||
"conflictNotification2": "読み込むノードを選択し、また既存のノードを置き換えるか、もしくはそれらのコピーを読み込むかも選択してください。",
|
||||
"alreadyExists": "本ノードは既に存在"
|
||||
},
|
||||
"copyMessagePath": "パスをコピーしました",
|
||||
"copyMessageValue": "値をコピーしました",
|
||||
@@ -718,6 +719,7 @@
|
||||
"nodeHelp": "ノードヘルプ",
|
||||
"showHelp": "ヘルプを表示",
|
||||
"showInOutline": "アウトラインに表示",
|
||||
"hideTopics": "トピックを非表示",
|
||||
"showTopics": "トピックを表示",
|
||||
"noHelp": "ヘルプのトピックが未選択",
|
||||
"changeLog": "更新履歴"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-client",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -547,12 +547,16 @@ RED.nodes = (function() {
|
||||
* @param {String} z tab id
|
||||
*/
|
||||
checkTabState: function (z) {
|
||||
const ws = workspaces[z]
|
||||
const ws = workspaces[z] || subflows[z]
|
||||
if (ws) {
|
||||
const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
|
||||
if (Boolean(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, {
|
||||
defaults:{
|
||||
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" },
|
||||
category: sf.category || "subflows",
|
||||
@@ -2360,6 +2379,13 @@ RED.nodes = (function() {
|
||||
} else {
|
||||
delete n.g
|
||||
}
|
||||
// If importing into a subflow, ensure an outbound-link doesn't get added
|
||||
if (activeSubflow && /^link /.test(n.type) && n.links) {
|
||||
n.links = n.links.filter(function(id) {
|
||||
const otherNode = node_map[id] || RED.nodes.node(id);
|
||||
return (otherNode && otherNode.z === activeWorkspace);
|
||||
});
|
||||
}
|
||||
for (var d3 in n._def.defaults) {
|
||||
if (n._def.defaults.hasOwnProperty(d3)) {
|
||||
if (n._def.defaults[d3].type) {
|
||||
@@ -2383,14 +2409,6 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// If importing into a subflow, ensure an outbound-link doesn't
|
||||
// get added
|
||||
if (activeSubflow && /^link /.test(n.type) && n.links) {
|
||||
n.links = n.links.filter(function(id) {
|
||||
const otherNode = node_map[id] || RED.nodes.node(id);
|
||||
return (otherNode && otherNode.z === activeWorkspace)
|
||||
});
|
||||
}
|
||||
}
|
||||
for (i=0;i<new_subflows.length;i++) {
|
||||
n = new_subflows[i];
|
||||
|
@@ -734,12 +734,12 @@
|
||||
}
|
||||
if (menu.opts.multiple) {
|
||||
var selected = {};
|
||||
this.value().split(",").forEach(function(f) {
|
||||
selected[f] = true;
|
||||
})
|
||||
this.value().split(",").forEach(function(f) {
|
||||
selected[f] = true;
|
||||
});
|
||||
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()]);
|
||||
}
|
||||
} else {
|
||||
this.optionSelectLabel.text(o.length+" selected");
|
||||
this.optionSelectLabel.text(RED._("typedInput.selected", { count: o.length }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -118,10 +118,16 @@ RED.contextMenu = (function () {
|
||||
onselect: 'core:split-wire-with-link-nodes',
|
||||
disabled: !canEdit || !hasLinks
|
||||
},
|
||||
null,
|
||||
{ onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
|
||||
{ onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
|
||||
null
|
||||
)
|
||||
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) {
|
||||
const nodeOptions = []
|
||||
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:delete-selection', label: RED._('keyboard.deleteSelected'), 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") }
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -612,7 +612,10 @@ RED.deploy = (function() {
|
||||
}
|
||||
});
|
||||
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) {
|
||||
if (ws.changed || ws.added) {
|
||||
@@ -628,6 +631,7 @@ RED.deploy = (function() {
|
||||
// Once deployed, cannot undo back to a clean state
|
||||
RED.history.markAllDirty();
|
||||
RED.view.redraw();
|
||||
RED.sidebar.config.refresh();
|
||||
RED.events.emit("deploy");
|
||||
}).fail(function (xhr, textStatus, err) {
|
||||
RED.nodes.dirty(true);
|
||||
|
@@ -248,6 +248,8 @@ RED.editor = (function() {
|
||||
var value = input.val();
|
||||
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
|
||||
value = input.text();
|
||||
} else if (input.attr("type") === "checkbox") {
|
||||
value = input.prop("checked");
|
||||
}
|
||||
var valid = validateNodeProperty(node, defaults, property,value);
|
||||
if (((typeof valid) === "string") || !valid) {
|
||||
@@ -741,9 +743,16 @@ RED.editor = (function() {
|
||||
}
|
||||
|
||||
try {
|
||||
var rc = editing_node._def.oneditsave.call(editing_node);
|
||||
const rc = editing_node._def.oneditsave.call(editing_node);
|
||||
if (rc === 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) {
|
||||
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
|
||||
@@ -914,6 +923,17 @@ RED.editor = (function() {
|
||||
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.view.redraw(true);
|
||||
RED.history.push(historyEvent);
|
||||
@@ -1015,7 +1035,7 @@ RED.editor = (function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
var historyEvent = {
|
||||
let historyEvent = {
|
||||
t:'edit',
|
||||
node:editing_node,
|
||||
changes:editState.changes,
|
||||
@@ -1031,6 +1051,15 @@ RED.editor = (function() {
|
||||
instances:subflowInstances
|
||||
}
|
||||
}
|
||||
|
||||
if (editState.history) {
|
||||
historyEvent = {
|
||||
t: 'multi',
|
||||
events: [ historyEvent, ...editState.history ],
|
||||
dirty: wasDirty
|
||||
}
|
||||
}
|
||||
|
||||
RED.history.push(historyEvent);
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
@@ -1623,8 +1652,8 @@ RED.editor = (function() {
|
||||
}
|
||||
|
||||
if (!isSameObj(old_env, new_env)) {
|
||||
editing_node.env = new_env;
|
||||
editState.changes.env = editing_node.env;
|
||||
editing_node.env = new_env;
|
||||
editState.changed = true;
|
||||
}
|
||||
|
||||
|
@@ -514,7 +514,7 @@ RED.editor.codeEditor.monaco = (function() {
|
||||
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
|
||||
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
|
||||
} 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(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
|
||||
} 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(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
|
||||
} catch (error) {
|
||||
console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
|
||||
console.warn("monaco - Error setting up CSS/SCSS/LESS options", error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -382,9 +382,11 @@ RED.sidebar.config = (function() {
|
||||
refreshConfigNodeList();
|
||||
}
|
||||
});
|
||||
|
||||
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-collapse-all'), RED._("palette.actions.collapse-all"));
|
||||
RED.popover.tooltip($('#red-ui-sidebar-config-expand-all'), RED._("palette.actions.expand-all"));
|
||||
}
|
||||
|
||||
function flashConfigNode(el) {
|
||||
|
@@ -36,7 +36,13 @@ RED.sidebar.help = (function() {
|
||||
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)
|
||||
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) {
|
||||
e.preventDefault();
|
||||
if ($(this).hasClass('selected')) {
|
||||
@@ -158,8 +164,10 @@ RED.sidebar.help = (function() {
|
||||
|
||||
function refreshSubflow(sf) {
|
||||
var item = treeList.treeList('get',"node-type:subflow:"+sf.id);
|
||||
item.subflowLabel = sf._def.label().toLowerCase();
|
||||
item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
|
||||
if (item) {
|
||||
item.subflowLabel = sf._def.label().toLowerCase();
|
||||
item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
|
||||
}
|
||||
}
|
||||
|
||||
function hideTOC() {
|
||||
|
@@ -906,7 +906,10 @@ RED.utils = (function() {
|
||||
* @returns true if valid, String if invalid
|
||||
*/
|
||||
function validateTypedProperty(propertyValue, propertyType, opt) {
|
||||
|
||||
if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) {
|
||||
// Allow ${ENV_VAR} value
|
||||
return true
|
||||
}
|
||||
let error
|
||||
if (propertyType === 'json') {
|
||||
try {
|
||||
|
@@ -646,120 +646,128 @@ RED.view = (function() {
|
||||
}
|
||||
d3.event = event;
|
||||
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 {
|
||||
var isLink = (nn.type === "link in" || nn.type === "link out")
|
||||
var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
|
||||
|
||||
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);
|
||||
var result = createNode(selected_tool);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
var historyEvent = result.historyEvent;
|
||||
var nn = RED.nodes.add(result.node);
|
||||
|
||||
mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
|
||||
mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
|
||||
mousePos[1] /= scaleFactor;
|
||||
mousePos[0] /= scaleFactor;
|
||||
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;
|
||||
}
|
||||
|
||||
nn.x = mousePos[0];
|
||||
nn.y = mousePos[1];
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
try {
|
||||
var isLink = (nn.type === "link in" || nn.type === "link out")
|
||||
var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
|
||||
|
||||
if (snapGrid) {
|
||||
var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
|
||||
nn.x -= gridOffset.x;
|
||||
nn.y -= gridOffset.y;
|
||||
}
|
||||
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 linkToSplice = $(ui.helper).data("splice");
|
||||
if (linkToSplice) {
|
||||
spliceLink(linkToSplice, nn, historyEvent)
|
||||
}
|
||||
mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
|
||||
mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
|
||||
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],
|
||||
|
||||
};
|
||||
if (moveEvent) {
|
||||
historyEvent.events.push(moveEvent)
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
// Middle Click pan
|
||||
d3.event.preventDefault();
|
||||
mouse_mode = RED.state.PANNING;
|
||||
mouse_position = [d3.event.pageX,d3.event.pageY]
|
||||
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
|
||||
@@ -4156,7 +4165,7 @@ RED.view = (function() {
|
||||
}
|
||||
var width = img.width * scaleFactor;
|
||||
if (width > 20) {
|
||||
scalefactor *= 20/width;
|
||||
scaleFactor *= 20/width;
|
||||
width = 20;
|
||||
}
|
||||
var height = img.height * scaleFactor;
|
||||
@@ -6063,14 +6072,19 @@ RED.view = (function() {
|
||||
function createNode(type, x, y, z) {
|
||||
const wasDirty = RED.nodes.dirty()
|
||||
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) {
|
||||
var subflowId = m[1];
|
||||
let err
|
||||
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)) {
|
||||
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") }))
|
||||
if (err) {
|
||||
err.code = 'NODE_RED'
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -491,6 +491,11 @@ RED.workspaces = (function() {
|
||||
createWorkspaceTabs();
|
||||
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() {
|
||||
var oldActive = activeWorkspace;
|
||||
workspace_tabs.nextTab();
|
||||
@@ -657,6 +662,9 @@ RED.workspaces = (function() {
|
||||
RED.events.on("flows:change", (ws) => {
|
||||
$("#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();
|
||||
}
|
||||
|
@@ -16,8 +16,20 @@
|
||||
RED.validators = {
|
||||
number: function(blankAllowed,mopt){
|
||||
return function(v, opt) {
|
||||
if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) {
|
||||
return true;
|
||||
if (blankAllowed && (v === '' || v === undefined)) {
|
||||
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) {
|
||||
return RED._("validator.errors.invalid-num-prop", {
|
||||
|
@@ -37,7 +37,6 @@ ul.red-ui-sidebar-node-config-list {
|
||||
}
|
||||
.red-ui-palette-node {
|
||||
// overflow: hidden;
|
||||
cursor: default;
|
||||
&.selected {
|
||||
border-color: transparent;
|
||||
box-shadow: 0 0 0 2px var(--red-ui-node-selected-color);
|
||||
|
@@ -227,34 +227,42 @@
|
||||
name: {value:""},
|
||||
props:{value:[{p:"payload"},{p:"topic",vt:"str"}], validate:function(v, opt) {
|
||||
if (!v || v.length === 0) { return true }
|
||||
const errors = []
|
||||
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 (!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") {
|
||||
try{ jsonata(v[i].v); }
|
||||
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") {
|
||||
try{ JSON.parse(v[i].v); }
|
||||
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"){
|
||||
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;
|
||||
}
|
||||
},
|
||||
repeat: {
|
||||
value:"", validate: function(v, opt) {
|
||||
if ((v === "") ||
|
||||
(RED.validators.number(v) &&
|
||||
(RED.validators.number()(v) &&
|
||||
(v >= 0) && (v <= 2147483))) {
|
||||
return true;
|
||||
}
|
||||
@@ -263,7 +271,7 @@
|
||||
},
|
||||
crontab: {value:""},
|
||||
once: {value:false},
|
||||
onceDelay: {value:0.1},
|
||||
onceDelay: {value:0.1, validate: RED.validators.number(true)},
|
||||
topic: {value:""},
|
||||
payload: {value:"", validate: RED.validators.typedInput("payloadType", false) },
|
||||
payloadType: {value:"date"},
|
||||
|
@@ -378,7 +378,7 @@
|
||||
return { id: id, label: RED.nodes.workspace(id).label } //flow id + name
|
||||
} else {
|
||||
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 }
|
||||
}
|
||||
})
|
||||
|
@@ -194,27 +194,46 @@
|
||||
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)) {
|
||||
n = RED.nodes.node(id);
|
||||
if (n) {
|
||||
editHistories.push({
|
||||
t:'edit',
|
||||
node: n,
|
||||
changes: {
|
||||
links: [...n.links]
|
||||
},
|
||||
changed: n.changed
|
||||
})
|
||||
if (nodeMap[id].old && !nodeMap[id].new) {
|
||||
// Removed id
|
||||
i = n.links.indexOf(node.id);
|
||||
if (i > -1) {
|
||||
n.links.splice(i,1);
|
||||
n.changed = true
|
||||
n.dirty = true
|
||||
}
|
||||
} else if (!nodeMap[id].old && nodeMap[id].new) {
|
||||
// Added id
|
||||
i = n.links.indexOf(id);
|
||||
if (i === -1) {
|
||||
n.links.push(node.id);
|
||||
n.changed = true
|
||||
n.dirty = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (editHistories.length > 0) {
|
||||
return {
|
||||
history: editHistories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onAdd() {
|
||||
@@ -254,13 +273,14 @@
|
||||
onEditPrepare(this,"link out");
|
||||
},
|
||||
oneditsave: function() {
|
||||
onEditSave(this);
|
||||
const result = onEditSave(this);
|
||||
// In case the name has changed, ensure any link call nodes on this
|
||||
// tab are redrawn with the updated name
|
||||
var localCallNodes = RED.nodes.filterNodes({z:RED.workspaces.active(), type:"link call"});
|
||||
localCallNodes.forEach(function(node) {
|
||||
node.dirty = true;
|
||||
});
|
||||
return result
|
||||
},
|
||||
onadd: onAdd,
|
||||
oneditresize: resizeNodeList
|
||||
@@ -329,7 +349,7 @@
|
||||
onEditPrepare(this,"link in");
|
||||
},
|
||||
oneditsave: function() {
|
||||
onEditSave(this);
|
||||
return onEditSave(this);
|
||||
},
|
||||
oneditresize: resizeNodeList
|
||||
});
|
||||
@@ -373,7 +393,7 @@
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
onEditSave(this);
|
||||
return onEditSave(this);
|
||||
},
|
||||
onadd: onAdd,
|
||||
oneditresize: resizeNodeList
|
||||
|
@@ -374,7 +374,7 @@ module.exports = function(RED) {
|
||||
iniOpt.breakOnSigint = true;
|
||||
}
|
||||
}
|
||||
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||
node.script = new vm.Script(functionText, createVMOpt(node, ""));
|
||||
if (node.fin && (node.fin !== "")) {
|
||||
var finText = `(function () {
|
||||
var node = {
|
||||
@@ -438,10 +438,9 @@ module.exports = function(RED) {
|
||||
|
||||
//store the error in msg to be used in flows
|
||||
msg.error = err;
|
||||
|
||||
var line = 0;
|
||||
var errorMessage;
|
||||
if (stack.length > 0) {
|
||||
let line = 0;
|
||||
let errorMessage;
|
||||
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
|
||||
line++;
|
||||
}
|
||||
@@ -455,11 +454,13 @@ module.exports = function(RED) {
|
||||
errorMessage += " (line "+lineno+", col "+cha+")";
|
||||
}
|
||||
}
|
||||
if (errorMessage) {
|
||||
err.message = errorMessage
|
||||
}
|
||||
}
|
||||
if (!errorMessage) {
|
||||
errorMessage = err.toString();
|
||||
}
|
||||
done(errorMessage);
|
||||
// Pass the whole error object so any additional properties
|
||||
// (such as cause) are preserved
|
||||
done(err);
|
||||
}
|
||||
else if (typeof err === "string") {
|
||||
done(err);
|
||||
|
@@ -20,6 +20,7 @@ module.exports = function(RED) {
|
||||
var exec = require('child_process').exec;
|
||||
var fs = require('fs');
|
||||
var isUtf8 = require('is-utf8');
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
function ExecNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
@@ -85,9 +86,12 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
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 */
|
||||
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});
|
||||
var unknownCommand = (child.pid === undefined);
|
||||
if (node.timer !== 0) {
|
||||
|
@@ -20,12 +20,26 @@
|
||||
<dt class="optional">delay <span class="property-type">number</span></dt>
|
||||
<dd>Legt die Verzögerung in Millisekunden fest, die auf die Nachricht angewendet werden soll.
|
||||
Zur Nutzung dieser Option muss <i>Verzög. mit msg.delay überschreibbar</i> aktiviert sein.</dd>
|
||||
<dt class="optional">rate <span class="property-type">number</span></dt>
|
||||
<dd>Setzt die Verzögerung in Millisekunden zwischen den Nachrichten. Diese Node überschreibt die
|
||||
bestehende Verzögerung die in der Node konfiguration, wenn die empfangende Nachricht <code>msg.rate</code>
|
||||
in Millisekunden enthält. Dies trifft nur zu, wenn in der Node konfiguriert ist, das empfangene
|
||||
Nachrichten den konfigurierten Wert überschreiben können.</dd>
|
||||
<dt class="optional">reset</dt>
|
||||
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist,
|
||||
werden alle im Node gepufferten Nachrichten gelöscht.</dd>
|
||||
<dt class="optional">flush</dt>
|
||||
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen beliebigen Wert gesetzt ist,
|
||||
werden alle im Node gepufferten Nachrichten sofort gesendet.</dd>
|
||||
<dt class="optional">flush</dt>
|
||||
<dd>Wenn bei der empfangenen Nachricht diese Eigenschaft auf einen numerischen Wert gesetzt ist,
|
||||
wird diese Anzahl an Nachrichten sofort gesendet. Wenn ein anderer Typ gesetzt ist (z.B. Boolean),
|
||||
werden alle in der Node gepufferten Nachrichten gesendet.</dd>
|
||||
<dt class="optional">toFront</dt>
|
||||
<dd>Wenn diese Eigenschaft im Ratenbegrenzungsmodus für die empfangene Nachricht auf den booleschen Wert
|
||||
<code>true</code> gesetzt ist, Anschließend wird die Nachricht an den Anfang der Warteschlange verschoben
|
||||
und als nächstes freigegeben. Dies kann in Kombination mit <code>msg.flush=1</code> verwendet werden, um sofort erneut zu senden.
|
||||
</dd>
|
||||
</dl>
|
||||
<h3>Details</h3>
|
||||
<p>Wenn Verzögerung als Nachrichtenaktion eingestellt ist, kann die Verzögerungszeit ein fixer Wert,
|
||||
|
@@ -103,7 +103,7 @@
|
||||
<h4>Automatic mode</h4>
|
||||
<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
|
||||
reverse the action of a <b>split</b> node.
|
||||
reverse the action of a <b>split</b> node.</p>
|
||||
|
||||
<h4>Manual mode</h4>
|
||||
<p>When configured to join in manual mode, the node is able to join sequences
|
||||
|
@@ -1,3 +1,3 @@
|
||||
<script type="text/html" data-help-name="global-config">
|
||||
<p>大域的なフローの設定を保持するノード。大域的な環境変数の定義を含みます。</p>
|
||||
</script>p
|
||||
</script>
|
||||
|
@@ -23,7 +23,7 @@
|
||||
<dt class="optional">template <span class="property-type">string</span></dt>
|
||||
<dd>由<code>msg.payload</code>填充的模板。如果未在编辑面板中配置,则可以将设为msg的属性。</dd>
|
||||
</dl>
|
||||
<h3>Outputs</h3>
|
||||
<h3>输出</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>msg <span class="property-type">object</span></dt>
|
||||
<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>例如:
|
||||
<pre>Hello {{payload.name}}. Today is {{date}}</pre>
|
||||
<p>receives a message containing:
|
||||
<p>接收一条消息,其中包含:
|
||||
<pre>{
|
||||
date: "Monday",
|
||||
payload: {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/nodes",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -18,11 +18,11 @@
|
||||
"acorn": "8.8.2",
|
||||
"acorn-walk": "8.2.0",
|
||||
"ajv": "8.12.0",
|
||||
"body-parser": "1.20.2",
|
||||
"body-parser": "1.20.3",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"content-type": "1.0.5",
|
||||
"cookie-parser": "1.4.6",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-parser": "1.4.7",
|
||||
"cookie": "0.7.2",
|
||||
"cors": "2.8.5",
|
||||
"cronosjs": "1.7.1",
|
||||
"denque": "2.1.0",
|
||||
@@ -43,7 +43,7 @@
|
||||
"raw-body": "2.5.2",
|
||||
"tough-cookie": "4.1.3",
|
||||
"uuid": "9.0.0",
|
||||
"ws": "7.5.6",
|
||||
"ws": "7.5.10",
|
||||
"xml2js": "0.6.2",
|
||||
"iconv-lite": "0.6.3"
|
||||
}
|
||||
|
@@ -273,7 +273,7 @@ async function installModule(moduleDetails) {
|
||||
let extraArgs = triggerPayload.args || [];
|
||||
let args = ['install', ...extraArgs, installSpec]
|
||||
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 {
|
||||
log.trace("skipping npm install");
|
||||
}
|
||||
|
@@ -25,12 +25,15 @@ const registryUtil = require("./util");
|
||||
const library = require("./library");
|
||||
const {exec,log,events,hooks} = require("@node-red/util");
|
||||
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;
|
||||
|
||||
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
|
||||
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
|
||||
const slashRe = isWindows ? /\\|[/]/ : /[/]/;
|
||||
const pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
|
||||
const localtgzRe = /^([a-zA-Z]:|\/).+tgz$/;
|
||||
|
||||
@@ -225,7 +228,7 @@ async function installModule(module,version,url) {
|
||||
let extraArgs = triggerPayload.args || [];
|
||||
let args = ['install', ...extraArgs, installName]
|
||||
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 {
|
||||
log.trace("skipping npm install");
|
||||
}
|
||||
@@ -260,7 +263,7 @@ async function installModule(module,version,url) {
|
||||
log.warn("------------------------------------------");
|
||||
e = new Error(log._("server.install.install-failed")+": "+err.toString());
|
||||
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;
|
||||
})
|
||||
}
|
||||
@@ -356,7 +359,7 @@ async function getModuleVersionFromNPM(module, version) {
|
||||
}
|
||||
|
||||
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 {
|
||||
if (!stdout) {
|
||||
log.warn(log._("server.install.install-failed-not-found",{name:module}));
|
||||
@@ -511,7 +514,7 @@ function uninstallModule(module) {
|
||||
let extraArgs = triggerPayload.args || [];
|
||||
let args = ['remove', ...extraArgs, module]
|
||||
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 {
|
||||
log.trace("skipping npm uninstall");
|
||||
}
|
||||
@@ -578,7 +581,7 @@ async function checkPrereq() {
|
||||
installerEnabled = false;
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
child_process.execFile(npmCommand,['-v'],function(err,stdout) {
|
||||
child_process.execFile(npmCommand,['-v'],{ shell: true },function(err,stdout) {
|
||||
if (err) {
|
||||
log.info(log._("server.palette-editor.npm-not-found"));
|
||||
installerEnabled = false;
|
||||
|
@@ -36,7 +36,7 @@ async function getFlowsFromPath(path) {
|
||||
promises.push(getFlowsFromPath(fullPath));
|
||||
} else if (/\.json$/.test(file)){
|
||||
validFiles.push(file);
|
||||
promises.push(Promise.resolve(file.split(".")[0]))
|
||||
promises.push(Promise.resolve(file.replace(/\.json$/, '')))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/registry",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,11 +16,11 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "3.1.4",
|
||||
"@node-red/util": "3.1.15",
|
||||
"clone": "2.1.2",
|
||||
"fs-extra": "11.1.1",
|
||||
"semver": "7.5.4",
|
||||
"tar": "6.1.13",
|
||||
"tar": "6.2.1",
|
||||
"uglify-js": "3.17.4"
|
||||
}
|
||||
}
|
||||
|
@@ -485,7 +485,7 @@ class Flow {
|
||||
}
|
||||
if (!key.startsWith("$parent.")) {
|
||||
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 {
|
||||
key = key.substring(8);
|
||||
@@ -678,6 +678,9 @@ class Flow {
|
||||
if (logMessage.hasOwnProperty('stack')) {
|
||||
errorMessage.error.stack = logMessage.stack;
|
||||
}
|
||||
if (logMessage.hasOwnProperty('cause')) {
|
||||
errorMessage.error.cause = logMessage.cause;
|
||||
}
|
||||
targetCatchNode.receive(errorMessage);
|
||||
handled = true;
|
||||
});
|
||||
|
@@ -1,5 +1,6 @@
|
||||
const flowUtil = require("./util");
|
||||
const credentials = require("../nodes/credentials");
|
||||
const clone = require("clone");
|
||||
|
||||
/**
|
||||
* This class represents a group within the runtime.
|
||||
@@ -41,7 +42,7 @@ class Group {
|
||||
}
|
||||
if (!key.startsWith("$parent.")) {
|
||||
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 {
|
||||
key = key.substring(8);
|
||||
|
@@ -376,7 +376,7 @@ class Subflow extends Flow {
|
||||
}
|
||||
if (!key.startsWith("$parent.")) {
|
||||
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 {
|
||||
key = key.substring(8);
|
||||
|
@@ -462,9 +462,8 @@ function stop(type,diff,muteLog,isDeploy) {
|
||||
if (type === 'nodes') {
|
||||
stopList = diff.changed.concat(diff.removed);
|
||||
} else if (type === 'flows') {
|
||||
stopList = diff.changed.concat(diff.removed).concat(diff.linked);
|
||||
stopList = diff.changed.concat(diff.removed).concat(diff.linked).concat(diff.rewired);
|
||||
}
|
||||
|
||||
events.emit("flows:stopping",{config: activeConfig, type: type, diff: diff})
|
||||
|
||||
// Stop the global flow object last
|
||||
|
@@ -106,14 +106,22 @@ async function evaluateEnvProperties(flow, env, credentials) {
|
||||
result = { value: result, __clone__: true}
|
||||
}
|
||||
evaluatedEnv[name] = result
|
||||
} else {
|
||||
evaluatedEnv[name] = undefined
|
||||
flow.error(`Error evaluating env property '${name}': ${err.toString()}`)
|
||||
}
|
||||
resolve()
|
||||
});
|
||||
}))
|
||||
} else {
|
||||
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
|
||||
if (typeof value === 'object') {
|
||||
value = { value: value, __clone__: true}
|
||||
try {
|
||||
value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null);
|
||||
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
|
||||
|
@@ -384,7 +384,8 @@ var api = module.exports = {
|
||||
}
|
||||
}
|
||||
} 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 || [])
|
||||
existingCredentialKeys.forEach(key => {
|
||||
if (!newCreds.map?.[key]) {
|
||||
@@ -396,7 +397,7 @@ var api = module.exports = {
|
||||
})
|
||||
newCredentialKeys.forEach(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
|
||||
// value has been changed
|
||||
savedCredentials.map[key] = newCreds.map[key]
|
||||
|
@@ -77,7 +77,7 @@ var storageModuleInterface = {
|
||||
flows: flows,
|
||||
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;
|
||||
})
|
||||
});
|
||||
@@ -95,7 +95,7 @@ var storageModuleInterface = {
|
||||
|
||||
return credentialSavePromise.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");
|
||||
})
|
||||
});
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/runtime",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,11 +16,11 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/registry": "3.1.4",
|
||||
"@node-red/util": "3.1.4",
|
||||
"@node-red/registry": "3.1.15",
|
||||
"@node-red/util": "3.1.15",
|
||||
"async-mutex": "0.4.0",
|
||||
"clone": "2.1.2",
|
||||
"express": "4.18.2",
|
||||
"express": "4.21.2",
|
||||
"fs-extra": "11.1.1",
|
||||
"json-stringify-safe": "5.0.1"
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/util",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"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": "1.8.7",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"moment": "2.29.4",
|
||||
"moment-timezone": "0.5.43"
|
||||
|
4
packages/node_modules/node-red/lib/red.js
vendored
4
packages/node_modules/node-red/lib/red.js
vendored
@@ -26,8 +26,8 @@ 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');
|
||||
}
|
||||
|
||||
|
14
packages/node_modules/node-red/package.json
vendored
14
packages/node_modules/node-red/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "3.1.4",
|
||||
"version": "3.1.15",
|
||||
"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.4",
|
||||
"@node-red/runtime": "3.1.4",
|
||||
"@node-red/util": "3.1.4",
|
||||
"@node-red/nodes": "3.1.4",
|
||||
"@node-red/editor-api": "3.1.15",
|
||||
"@node-red/runtime": "3.1.15",
|
||||
"@node-red/util": "3.1.15",
|
||||
"@node-red/nodes": "3.1.15",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"express": "4.18.2",
|
||||
"express": "4.21.2",
|
||||
"fs-extra": "11.1.1",
|
||||
"node-red-admin": "^3.1.2",
|
||||
"node-red-admin": "^3.1.3",
|
||||
"nopt": "5.0.0",
|
||||
"semver": "7.5.4"
|
||||
},
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// 4. Edit your settings file to set the theme:
|
||||
// editorTheme: {
|
||||
// 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 nopt = require("nopt");
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const sass = require("sass");
|
||||
const os = require('os');
|
||||
const nopt = require('nopt');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const sass = require('sass');
|
||||
|
||||
const knownOpts = {
|
||||
"help": Boolean,
|
||||
"long": Boolean,
|
||||
"in": [path],
|
||||
"out": [path]
|
||||
'help': Boolean,
|
||||
'long': Boolean,
|
||||
'in': [path],
|
||||
'out': [path]
|
||||
};
|
||||
const shortHands = {
|
||||
"?":["--help"]
|
||||
'?':['--help']
|
||||
};
|
||||
nopt.invalidHandler = function(k,v,t) {}
|
||||
|
||||
const parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
|
||||
|
||||
if (parsedArgs.help) {
|
||||
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();
|
||||
showUsageAndExit(0)
|
||||
}
|
||||
|
||||
|
||||
const ruleRegex = /(\$.*?) *: *(\S[\S\s]*?);/g;
|
||||
var match;
|
||||
|
||||
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];
|
||||
}
|
||||
if (!parsedArgs.in) {
|
||||
console.warn('Missing argument: in')
|
||||
showUsageAndExit(1)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
const tmpDir = os.tmpdir();
|
||||
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({
|
||||
outputStyle: "expanded",
|
||||
file: path.join(workingDir,"style-custom-theme.scss"),
|
||||
});
|
||||
await fs.copy(path.join(__dirname, '../packages/node_modules/@node-red/editor-client/src/sass/'), workingDir);
|
||||
await fs.copyFile(parsedArgs.in, path.join(workingDir,'colors.scss'));
|
||||
|
||||
const css = result.css.toString()
|
||||
const lines = css.split("\n");
|
||||
const colorCSS = []
|
||||
const nonColorCSS = [];
|
||||
const output = sass.compile(
|
||||
path.join(workingDir, 'style-custom-theme.scss'),
|
||||
{style: parsedArgs.long === true ? 'expanded' : 'compressed'}
|
||||
);
|
||||
|
||||
let inKeyFrameBlock = false;
|
||||
|
||||
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 nrPkg = require('../package.json');
|
||||
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) {
|
||||
|
||||
await fs.writeFile(parsedArgs.out,header+"\n"+output.css);
|
||||
await fs.writeFile(parsedArgs.out, header+'\n'+output.css);
|
||||
} else {
|
||||
console.log(header);
|
||||
console.log(output.css.toString());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@@ -4,7 +4,7 @@ const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const should = require("should");
|
||||
|
||||
const LATEST = "3";
|
||||
const LATEST = "4";
|
||||
|
||||
function generateScript() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@@ -390,7 +390,8 @@ describe('function node', function() {
|
||||
msg.should.have.property('level', helper.log().ERROR);
|
||||
msg.should.have.property('id', 'n1');
|
||||
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();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
@@ -659,7 +660,8 @@ describe('function node', function() {
|
||||
msg.should.have.property('level', helper.log().ERROR);
|
||||
msg.should.have.property('id', name);
|
||||
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();
|
||||
}
|
||||
catch (e) {
|
||||
|
@@ -60,6 +60,7 @@ describe('HTTP Request Node', function() {
|
||||
function startServer(done) {
|
||||
testPort += 1;
|
||||
testServer = stoppable(http.createServer(testApp));
|
||||
const promises = []
|
||||
testServer.listen(testPort,function(err) {
|
||||
testSslPort += 1;
|
||||
console.log("ssl port", testSslPort);
|
||||
@@ -81,13 +82,17 @@ describe('HTTP Request Node', function() {
|
||||
*/
|
||||
};
|
||||
testSslServer = stoppable(https.createServer(sslOptions,testApp));
|
||||
testSslServer.listen(testSslPort, function(err){
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log("started testSslServer");
|
||||
}
|
||||
});
|
||||
console.log('> start testSslServer')
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
testSslServer.listen(testSslPort, function(err){
|
||||
console.log(' done testSslServer')
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
});
|
||||
}))
|
||||
|
||||
testSslClientPort += 1;
|
||||
var sslClientOptions = {
|
||||
@@ -97,10 +102,17 @@ describe('HTTP Request Node', function() {
|
||||
requestCert: true
|
||||
};
|
||||
testSslClientServer = stoppable(https.createServer(sslClientOptions, testApp));
|
||||
testSslClientServer.listen(testSslClientPort, function(err){
|
||||
console.log("ssl-client", err)
|
||||
});
|
||||
|
||||
console.log('> start testSslClientServer')
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
testSslClientServer.listen(testSslClientPort, function(err){
|
||||
console.log(' done testSslClientServer')
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
});
|
||||
}))
|
||||
testProxyPort += 1;
|
||||
testProxyServer = stoppable(httpProxy(http.createServer()))
|
||||
|
||||
@@ -109,7 +121,17 @@ describe('HTTP Request Node', function() {
|
||||
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
|
||||
testProxyServerAuth = stoppable(httpProxy(http.createServer()))
|
||||
@@ -131,9 +153,19 @@ describe('HTTP Request Node', function() {
|
||||
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) {
|
||||
done(err);
|
||||
}
|
||||
helper.startServer(done);
|
||||
console.log('> helper.startServer')
|
||||
helper.startServer(function(err) {
|
||||
console.log('> helper started')
|
||||
done(err)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -33,16 +33,15 @@ describe("library api", function() {
|
||||
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.addExamplesDir("test-module",path.resolve(__dirname+'/resources/examples')).then(function() {
|
||||
try {
|
||||
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');
|
||||
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'))
|
||||
|
||||
examplePath.should.eql(path.resolve(__dirname+'/resources/examples/one.json'));
|
||||
|
||||
library.removeExamplesDir('test-module');
|
||||
|
||||
@@ -57,6 +56,5 @@ describe("library api", function() {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@@ -16,6 +16,31 @@ describe('Group', function () {
|
||||
group.getSetting("NR_GROUP_NAME").should.equal("g1")
|
||||
group.getSetting("NR_GROUP_ID").should.equal("group1")
|
||||
})
|
||||
it("returns cloned env var property", async function () {
|
||||
const group = new Group({
|
||||
getSetting: v => v+v
|
||||
}, {
|
||||
name: "g1",
|
||||
id: "group1",
|
||||
env: [
|
||||
{
|
||||
name: 'jsonEnvVar',
|
||||
type: 'json',
|
||||
value: '{"a":1}'
|
||||
}
|
||||
]
|
||||
})
|
||||
await group.start()
|
||||
const result = group.getSetting('jsonEnvVar')
|
||||
result.should.have.property('a', 1)
|
||||
result.a = 2
|
||||
result.b = 'hello'
|
||||
|
||||
const result2 = group.getSetting('jsonEnvVar')
|
||||
result2.should.have.property('a', 1)
|
||||
result2.should.not.have.property('b')
|
||||
|
||||
})
|
||||
it("delegates to parent if not found", async function () {
|
||||
const group = new Group({
|
||||
getSetting: v => v+v
|
||||
|
Reference in New Issue
Block a user